I have castv2-client and google-tts-api used to output audio messages to my google home using ip address. It works fine, but the volume is kind of loud and I'm having issues figuring out how to lower the volume. If I lower the volume manually on google home then the app overrides it and it's loud still. How can I set the volume lower?
Also the console.log("VOLUME", volume) outputs
{
controlType: 'master',
level: 1,
muted: false,
stepInterval: 0.019999999552965164
}
var Client = require("castv2-client").Client;
var DefaultMediaReceiver = require("castv2-client").DefaultMediaReceiver;
const googleTTS = require("google-tts-api");
var cron = require("node-cron");
function broadcastMessage(message) {
var App = {
playin: false,
DeviceIp: "",
Player: null,
GoogleHome: function (host, url) {
var client = new Client();
client.connect(host, function () {
client.launch(DefaultMediaReceiver, function (err, player) {
client.setVolume({ level: 1 }, function (err, volume) {
console.log("VOLUME", volume);
if (err) {
// Handle error as appropriate
console.log("Error on setVolume:", err);
} else {
console.log("Sending new alert");
}
});
var media = {
contentId: url,
contentType: "audio/mp3",
streamType: "BUFFERED",
};
App.Player = player;
App.Player.load(media, { autoplay: true }, function (err, status) {
App.Player.on("status", function (status) {
if (status.playerState === "IDLE" && App.playin === false) {
client.close();
}
});
});
});
});
client.on("error", function (err) {
console.log("Error: %s", err.message);
client.close();
});
},
run: function (ip, text) {
App.DeviceIp = ip;
const url = googleTTS.getAudioUrl(text, {
lang: "en-US",
slow: false,
host: "https://translate.google.com",
});
App.GoogleHome(App.DeviceIp, url, function (res) {
console.log(res);
});
},
broadcast: function (text) {
const ip = "192.168.0.17";
App.run(ip, text);
},
};
App.broadcast(message);
setTimeout(() => {
App.broadcast(message);
}, 4000);
}
module.exports = {
broadcastMessage,
};
broadcastMessage("Incoming message");
As far as I can tell, you set the volume in your code to 1, which is full volume. So, your audio will play at full volume. This is where you set the volume:
client.setVolume({ level: 1 }, function (err, volume) {
console.log("VOLUME", volume);
if (err) {
// Handle error as appropriate
console.log("Error on setVolume:", err);
} else {
console.log("Sending new alert");
}
});
Simply remove this code and your message will play at the volume you set manually on the device.
Also: if you intend to set the volume of the device, you should wait for the callback to return, and only then send the message.
client.setVolume({ level: 1 }, function (err, volume) {
console.log("VOLUME", volume);
if (err) {
// Handle error as appropriate
console.log("Error on setVolume:", err);
} else {
// Here you should send the message, since only now you know for sure that the volume was set.
}
});
The solution was I had to define the value before client.setVolume({ level: 1 } call
so basically
const volume = 0.6;
client.setVolume({ level: volume }
Related
I'm following this http://siglerdev.us/blog/2021/02/26/google-home-message-broadcast-system-node-js/31 which uses this library castv2-client to send messages to my google home. It works. I get the messages no problem, but the code throws
C:\Users\Phil\Documents\google home\node_modules\castv2-client\lib\controllers\receiver.js:72
callback(null, response.status.volume);
^
TypeError: callback is not a function
at C:\Users\Phil\Documents\google home\node_modules\castv2-client\lib\controllers\receiver.js:72:5 ver.js:72
at fn.onmessage (C:\Users\Phil\Documents\google home\node_modules\castv2-client\lib\controllers\request-response.js:27:7)
at fn.emit (events.js:203:15)
at Channel.onmessage (C:\Users\Phil\Documents\google home\node_modules\castv2-client\lib\controllers\controller.js:16:10) s\receiver.js:72:5
at Channel.emit (events.js:198:13) lib\controllers\request-response.js:27:7)
at Client.onmessage (C:\Users\Phil\Documents\google home\node_modules\castv2\lib\channel.js:23:10) ient\lib\controllers\controller.js:16:10)
at Client.emit (events.js:203:15)
at PacketStreamWrapper.onpacket (C:\Users\Phil\Documents\google home\node_module\channel.js:23:10)s\castv2\lib\client.js:81:10)
at PacketStreamWrapper.emit (events.js:198:13) s\castv2\lib\client.js:81:10)
at TLSSocket.<anonymous> (C:\Users\Phil\Documents\google home\node_modules\castv2\lib\packet-stream-wrapper.js:28:16)
What's wrong with the code that is throwing this AND/OR how can I fix it so it's either more graceful in catching error and doesn't throw since the message still delivers to google home or fix it to not throw this at all?
I appreciate any help!
I believe it's here in the castv2-client library that it's referencing, but I haven't been able to make it happy.
ReceiverController.prototype.launch = function(appId, callback) {
this.request({ type: 'LAUNCH', appId: appId }, function(err, response) {
if(err) return callback(err);
if(response.type === 'LAUNCH_ERROR') {
return callback(new Error('Launch failed. Reason: ' + response.reason));
}
callback(null, response.status.applications || []);
});
};
my code
var Client = require('castv2-client').Client;
var DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver;
const googleTTS = require('google-tts-api');
var App = {
playin: false,
DeviceIp: "",
Player: null,
GoogleHome: function (host, url) {
var client = new Client();
client.connect(host, function () {
client.launch(DefaultMediaReceiver, function (err, player) {
client.setVolume({ level: 1 });
var media = {
contentId: url,
contentType: 'audio/mp3',
streamType: 'BUFFERED'
};
App.Player = player;
App.Player.load(media, { autoplay: true }, function (err, status) {
App.Player.on('status', function (status) {
if (status.playerState === "IDLE" && App.playin === false) {
client.close();
}
});
});
});
});
client.on('error', function (err) {
console.log('Error: %s', err.message);
client.close();
});
},
run: function (ip, text) {
App.DeviceIp = ip;
const url = googleTTS.getAudioUrl(text, {
lang: 'en-US',
slow: false,
host: 'https://translate.google.com',
});
App.GoogleHome(App.DeviceIp, url, function (res) {
console.log(res);
})
},
broadcast: function(text){
const ips = '192.168.0.15'.split(","); //From config, 192.168.68.105,192.168.68.107,192.168.68.124
for (var s of ips){
App.run(s, text);
}
}
}
App.broadcast("Broadcasted to all of the devices"); //Only works if you did step 4.5
The error you reported:
C:\Users\Phil\Documents\google home\node_modules\castv2-client\lib\controllers\receiver.js:72
callback(null, response.status.volume);
^
TypeError: callback is not a function
at C:\Users\Phil\Documents\google home\node_modules\castv2-client\lib\controllers\receiver.js:72:5
seems to be related to the invocation of the method setVolume in your client:
client.setVolume({ level: 1 });
Please, consider review the source code of receiver.js in the castv2-client library:
ReceiverController.prototype.setVolume = function(options, callback) {
var data = {
type: 'SET_VOLUME',
volume: options // either `{ level: 0.5 }` or `{ muted: true }`
};
this.request(data, function(err, response) {
if(err) return callback(err);
callback(null, response.status.volume);
});
};
The library is claiming because you aren't providing a proper callback when invoking that function.
I have never used the library but probably providing something similar to the following callback could be of help:
client.setVolume({ level: 1 }, function(err, volume) {
if (err) {
// Handle error as appropriate
console.log('Error on setVolume:', err);
} else {
console.log('Volume:', volume)
}
});
Your final code would look like this:
var Client = require('castv2-client').Client;
var DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver;
const googleTTS = require('google-tts-api');
var App = {
playin: false,
DeviceIp: "",
Player: null,
GoogleHome: function (host, url) {
var client = new Client();
client.connect(host, function () {
client.launch(DefaultMediaReceiver, function (err, player) {
client.setVolume({ level: 1 }, function(err, volume) {
if (err) {
// Handle error as appropriate
console.log('Error on setVolume:', err);
} else {
console.log('Volume:', volume)
}
});
var media = {
contentId: url,
contentType: 'audio/mp3',
streamType: 'BUFFERED'
};
App.Player = player;
App.Player.load(media, { autoplay: true }, function (err, status) {
App.Player.on('status', function (status) {
if (status.playerState === "IDLE" && App.playin === false) {
client.close();
}
});
});
});
});
client.on('error', function (err) {
console.log('Error: %s', err.message);
client.close();
});
},
run: function (ip, text) {
App.DeviceIp = ip;
const url = googleTTS.getAudioUrl(text, {
lang: 'en-US',
slow: false,
host: 'https://translate.google.com',
});
App.GoogleHome(App.DeviceIp, url, function (res) {
console.log(res);
})
},
broadcast: function(text){
const ips = '192.168.0.15'.split(","); //From config, 192.168.68.105,192.168.68.107,192.168.68.124
for (var s of ips){
App.run(s, text);
}
}
}
App.broadcast("Broadcasted to all of the devices"); //Only works if you did step 4.5
I currently have a script that checks for an incoming email (in a mailbox) every 30 seconds, using a recursion.
The package I'm using for this testing is imap-simple.
The below script currently does this as required;
var imaps = require('imap-simple');
const { connect } = require('net');
var config = {
imap: {
user: 'qatestspecialist#outlook.com',
password: 'specialistQa',
host: 'imap-mail.outlook.com',
port: 993,
tls: true,
authTimeout: 30000
}
};
module.exports = {
'delete any existing emails...': function () {
imaps.connect(config).then(function (connection) {
connection.openBox('INBOX').then(function () {
var searchCriteria = ['ALL'];
var fetchOptions = { bodies: ['TEXT'], struct: true };
return connection.search(searchCriteria, fetchOptions);
//Loop over each message
}).then(function (messages) {
let taskList = messages.map(function (message) {
return new Promise((res, rej) => {
var parts = imaps.getParts(message.attributes.struct);
parts.map(function (part) {
return connection.getPartData(message, part)
.then(function (partData) {
//Display e-mail body
if (part.disposition == null && part.encoding != "base64"){
console.log(partData);
}
//Mark message for deletion
connection.addFlags(message.attributes.uid, "\Deleted", (err) => {
if (err){
console.log('Problem marking message for deletion');
rej(err);
}
res(); //Final resolve
});
});
});
});
});
return Promise.all(taskList).then(() => {
connection.imap.closeBox(true, (err) => { //Pass in false to avoid delete-flagged messages being removed
if (err){
console.log(err);
}
});
connection.end();
});
});
});
},
'send email to seller and wait for mailbox notification': function (browser) {
// script to send an email to the mailbox...
},
'get new email info': function(browser) {
const createPromise = ms => new Promise((resolve, reject) => {
setTimeout(() => resolve(ms), ms)
});
function findUnseenEmails(connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = ['UNSEEN'];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: false
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
var subjects = results.map(function (res) {
return res.parts.filter(function (part) {
return part.which === 'HEADER';
})
[0].body.subject[0];
});
return subjects.length > 0 ? subjects : createPromise(30000).then(function() { return findUnseenEmails(connection);
});
});
});
}
imaps.connect(config).then(function (connection) {
return findUnseenEmails(connection)
})
.then((subjects) => console.log(JSON.stringify(subjects)));
},
'Closing the browser': function (browser) {
browser.browserEnd();
}
};
This waits for an email and then displays the email 'header'.
However, the imap connection does not close, and stays open which is stopping my test suite from completing as the associated test never actually finishes.
I've tried adding the imap-simple command connection.end() in several places after the
imaps.connect(config).then(function (connection) {
return findUnseenEmails(connection)
})
part of the script, but it doesn't work.
So I'm just wondering if anyone knows where I should be adding this connection.end() command in order for the connection to be closed once an email has been received?
Any help would be greatly appreciated.
This has now been resolved in another post, using the following;
if (subjects.length > 0) {
connection.end();
return subjects;
} else {
return createPromise(5000).then(function() { return findUnseenEmails(connection)});
}
I am using SQL Server with Node.js. When the connection fails in first attempt the Node.js does not reattempt to connect. I am using setTimeout() to keep trying periodically until it connects.
const poolPromise = new sql.ConnectionPool(config.db);
poolPromise
.connect()
.then(pool => {
console.log('Connected to MSSQL');
return pool;
})
.catch(err => {
if (err && err.message.match(/Failed to connect to /)) {
console.log(new Date(), String(err));
// Wait for a bit, then try to connect again
setTimeout(function() {
console.log('Retrying first connect...');
poolPromise.connect().catch(() => {});
}, 5000);
} else {
console.error(new Date(), String(err.message));
}
});
The above code attempt to connect, fails and try for second time but does not continue for third, fourth and so on.
I wrote this small snippet that works. I wrapped connection part into a function and then invoke it using a recursive function.
In this example you'll see an infinity.
function sql() {
this.connect = function() {
return new Promise((resolve, reject) => reject("error connecting"));
}
}
function connect() {
return new Promise((resolve, reject) => {
// const poolPromise = new sql.ConnectionPool("config.db");
const poolPromise = new sql();
poolPromise
.connect()
.then(pool => {
console.log("connected");
resolve(pool);
})
.catch(err => {
console.error(err);
reject(err);
});
});
}
function establishConnection() {
var a = connect();
a.then(a => console.log("success"))
.catch(err => {
console.error("Retrying");
// I suggest using some variable to avoid the infinite loop.
setTimeout(establishConnection, 2000);
});
};
establishConnection();
After checking out the answers here I agree that callbacks are the way to go. I wrote the follow script to attempt to connect to MySQL until connection is established, and then to occasionally check that the connection is still valid, and if not, attempt connection again. I placed console.log's in a few places so that as things run you can see and understand what's happening.
var mysql = require('mysql');
var env = require('dotenv').config()
// ENVIRONMENT LOADS
var env = process.env.NODE_ENV.trim();
var host = process.env.MYSQL_HOST.trim();
var user = process.env.MYSQL_USER.trim();
var password = process.env.MYSQL_ROOT_PASSWORD.trim();
var database = process.env.MYSQL_DB.trim();
var port = process.env.MYSQL_PORT.trim();
console.log('\n\n********\n\nMySQL Credentials\n\n********\n\n');
if (env != 'production') {
console.log("Host: ", host, ":", port);
console.log("User: ", user);
console.log("Database: ", database);
console.log("Password: ", password);
}else{
console.log('Using Production Credentials');
}
console.log('\n\n************************\n\n');
let mysqlDB = null; // db handler
let connected = null; // default null / boolean
let connectFreq = 1000; // When database is disconnected, how often to attempt reconnect? Miliseconds
let testFreq = 5000; // After database is connected, how often to test connection is still good? Miliseconds
function attemptMySQLConnection(callback) {
console.log('attemptMySQLConnection')
if (host && user && database) {
mysqlDB = mysql.createPool({
host: host,
port: port, // Modified for Dev env
user: user,
password: password,
database: database,
connectionLimit: 300,
waitForConnections: true, // Default value.
queueLimit: 300, // Unlimited
acquireTimeout: 60000,
timeout: 60000,
debug: false
});
testConnection((result) => {
callback(result)
})
} else {
console.error('Check env variables: MYSQL_HOST, MYSQL_USER & MYSQL_DB')
callback(false)
}
}
function testConnection(cb) {
console.log('testConnection')
mysqlDB.query('SELECT 1 + 1 AS solution', (error, results, fields) => {
try {
if (error) {
throw new Error('No DB Connection');
} else {
if (results[0].solution) {
cb(true)
} else {
cb(false)
}
}
} catch (e) {
// console.error(e.name + ': ' + e.message);
cb(false)
}
});
}
function callbackCheckLogic(res) {
if (res) {
console.log('Connect was good. Scheduling next test for ', testFreq, 'ms')
setTimeout(testConnectionCB, testFreq);
} else {
console.log('Connection was bad. Scheduling connection attempt for ', connectFreq, 'ms')
setTimeout(connectMySQL, connectFreq);
}
}
function testConnectionCB() {
testConnection((result) => {
callbackCheckLogic(result);
})
}
function connectMySQL() {
attemptMySQLConnection(result => {
callbackCheckLogic(result);
});
}
connectMySQL(); // Start the process by calling this once
module.exports = mysqlDB;
Let's say i need to constantly collecting some data from a lot of clients and in parallel running some complex loop that solving some stuff with this data. How can i do it? Should i just write this in my piece of code:
app.get('/', function(req, res) {
res.sendFile(__dirname + '/public/views/index0.html');
});
io.sockets.on('connection', function(socket) {
// SOME STUFF WITH THE SOCKET
socket.on('disconnect', function(data) {
//SOME OTHER STUFF
});
});
while(...) {
//THE LOOP STUFF
}
Or i need to use the setTimeout() and setInterval() functions? How can i do the loop on the server that runs in parallel with the callbacks' stuff?
Don’t use while for make it, this block a thread. setTimeout() will run only once. You need to use setInterval() function.
You can use the async module to handle the async operation with the callback or use promise to avoid callback.
Here how I handle a complex async for each operation, that might helpfull to you get idea handeling ayncs forach
var cond = { _schedule: schedule_id }; // find curse by schedule id
Course.find(cond, function (err, courses) {
if (err) {
callback({ "success": false, "message": "Not able to update" });
} else {
async.forEachLimit(courses, 1, function (course, coursesCallback) {
async.waterfall([
function (callback) {
var schedule_date = moment(change_data.date).format('YYYY-MM-DD') + "T00:00:00.000Z"
var Assignmentcond = {
assignment_schedule_order: {
$gte: schedule_date
},
_course: course._id,
_schedule: schedule_id,
_user: userid
};
Assignment.find(Assignmentcond)
.populate({
path: '_course',
})
.lean()
.sort({ assignment_schedule_order: 1 })
.exec(function (err, AssignmentList) {
if (err) {
callback(null, '');
} else {
//console.log("------------------AssignmentList---------------------------");
//console.log(AssignmentList);
async.forEachLimit(AssignmentList, 1, function (ThisAssignmentCell, ThisAssignmentCellCallback) {
async.waterfall([
function (callback) {
var SearchObj = items;
var lebelObject = {};
for (var i = 0, flag = 0, insert = 0; i < SearchObj.length; i++) {
if (SearchObj[i].date == ThisAssignmentCell.assignment_schedule_date) {
flag = 1;
}
if (flag == 1 && SearchObj[i].label != "") {
if (ThisAssignmentCell.day == SearchObj[i].day_index) {
insert = 1;
var lebelObject = SearchObj[i];
break;
}
}
}
callback(null, ThisAssignmentCell, lebelObject, insert);
},
function (ThisAssignmentCell, SearchObj, insert, callback) {
console.log('----------------------');
console.log('ThisAssignmentCell', ThisAssignmentCell);
console.log('SearchObj', SearchObj);
console.log('----------------------');
if (insert > 0) {
var query = { _id: ThisAssignmentCell._id },
fields = {
assignment_date: moment(SearchObj.date).format('MM/DD/YYYY'),
assignment_schedule_date: moment(SearchObj.date).format('YYYY-MM-DD'),
assignment_schedule_order: new Date(SearchObj.date),
day: SearchObj.day_index,
dayNum: SearchObj.weekday_num
},
options = { upsert: false };
Assignment.update(query, fields, options, function (err, affected) {
callback(null, '');
});
} else {
// var cond = { _id: ThisAssignmentCell._id};
// Assignment.remove(cond)
// .exec(function (err, cnt) {
// callback(null, '');
// });
}
}
], function (err, result) {
// result now equals 'done'
console.log('done')
ThisAssignmentCellCallback();
});
}, function (err) {
console.log("Assignment For Loop Completed");
callback(null, AssignmentList);
});
}
});
}
], function (err, result) {
// result now equals 'done'
console.log('done')
coursesCallback();
});
}, function (err) {
console.log("courses For Loop Completed");
});
}
});
I have a client integration test to ensure my admin user can change user roles via the user management interface in my app. However, when I query for the user I want to change, the query comes back empty even though it has been created in the fixture.
describe('Admin users', function() {
beforeEach(function(done) {
Meteor.loginWithPassword('admin#gmail.com', '12345678', function(error) {
Router.go('/users');
Tracker.afterFlush(done);
});
});
beforeEach(waitForRouter);
afterEach(function(done) {
Meteor.logout(function() {
done();
});
});
it('should be able to change user roles', function(done) {
var changeUser = Meteor.users.findOne({ emails: { $elemMatch: { address: 'user#gmail.com' } } });
console.log('changeUser: ', changeUser);
console.log('Users: ', Meteor.users.find().fetch());
$('#user-' + changeUser._id + '-roles').val('manage-users').change();
expect(Roles.userIsInRole(changeUser, 'manage-users')).toBe(true);
expect(Roles.userIsInRole(changeUser, 'edit-any')).toBe(false);
done();
});
});
This test fails with the following error:
TypeError: Cannot read property '_id' of undefined
Here's the fixture file that creates the two users:
/* globals
resetDatabase: true,
loadDefaultFixtures: true,
*/
var Future = Npm.require('fibers/future');
resetDatabase = function () {
console.log('Resetting database');
// safety check
if (!process.env.IS_MIRROR) {
console.error('velocityReset is not allowed outside of a mirror. Something has gone wrong.');
return false;
}
var fut = new Future();
var collectionsRemoved = 0;
var db = Meteor.users.find()._mongo.db;
db.collections(function (err, collections) {
var appCollections = _.reject(collections, function (col) {
return col.collectionName.indexOf('velocity') === 0 ||
col.collectionName === 'system.indexes';
});
_.each(appCollections, function (appCollection) {
appCollection.remove(function (e) {
if (e) {
console.error('Failed removing collection', e);
fut.return('fail: ' + e);
}
collectionsRemoved++;
console.log('Removed collection');
if (appCollections.length === collectionsRemoved) {
console.log('Finished resetting database');
fut['return']('success');
}
});
});
});
return fut.wait();
};
loadDefaultFixtures = function () {
console.log('Loading default fixtures');
var adminId = Accounts.createUser({email: 'admin#gmail.com', password: '12345678'});
var standardUserId = Accounts.createUser({email: 'user#gmail.com', password: '12345678'});
console.log('Users: ', Meteor.users.find().fetch());
console.log('Finished loading default fixtures');
};
if (process.env.IS_MIRROR) {
resetDatabase();
loadDefaultFixtures();
}
I can see the output of the fixture console.log in the Jasmine logs, and it shows both users. The log from the test logs undefined for the changeUser and an array of only the current user for the full collection fetch.
The only other problems I can imagine are the publication and the subscription. I can't see anything wrong with them, but I could be missing it. Here's the publication:
Meteor.publish('allUsers', function () {
if (Roles.userIsInRole(this.userId, ['manage-users'])) {
return Meteor.users.find({}, { fields: { emails: true, roles: true, id: true}});
} else {
return this.ready();
}
});
and the subscription:
subscriptions: function() {
return [Meteor.subscribe('allUsers'), Meteor.subscribe('allRoles')];
},
It seems like the default Meteor users publication containing only the current user is being delivered for the test, but shouldn't waiting on the route and that route's user subscription mean that the entire user list is being published/subscribed?