I am using the SerialPort library in nodejs to list the available ports, and send data to each of them. If any of them return "OK", I would like to find out which is the port that returns it:
SerialPort.list().then(ports => {
ports.forEach(port => {
var rtuSocket = new SerialPort(port.path, { baudRate: 9600 }, (err, data) => {
rtuSocket.on('data', (err, data) => {
console.log(rtuSocket.path)
console.log("[RTU][CONNECTED]")
})
rtuSocket.write("AT")
})
})
})
Obviously the rtuSocket will be a different variable by the time data return. Is there a way to know which port is returning the data inside .on("data") ?
You need some sort of timeout to detect if a port is not responding. I also strongly suggest you check the response data and send something more than a simple "AT" command because a lot of other devices respond to AT commands such as RGB controllers for gaming PCs and some Android phones configured as modems.
Something like the following should work:
function checkPort (path, callback) { // callback(error, path)
let sock = new SerialPort(path, { baudRate: 9600 }, (err, data) => {
let timer = setTimeout(() => {
callback(new Error('Timeout'));
}, 100); // 100ms should be enough. If not increase it
sock.on('data', (err, data) => {
clearTimeout(timer); // clear the timer
if (err) {
return callback(err);
}
else {
return callback(null, path);
}
})
sock.write("AT");
})
}
Now you can check which port is connected:
function findConnectedPort (callback) {
SerialPort.list().then(ports => {
portCount = ports.length;
ports.forEach(port => {
checkPort(port.path, (err, foundPath) => {
if (err) {
// Ignore errors or deal with them how you want
}
else {
// Great! Got response. Return the path:
if (portCount > 0) { // prevent calling callback
// more than once
callback(null, foundPath);
}
portCount = 0;
}
portCount--;
if (portCount <= 0) {
// Well, none of the ports responded so we
// return an error:
callback(new Error('Not found'));
}
})
})
})
}
Now to find the connected port simply do:
findConnectedPort((err,path) => {
if (err) {
console.error(err);
}
else {
console.log("RTU CONNECTED at " + path);
// Do what you need with the port here...
}
})
While the code above works. For asynchronous code that requires a lot of conditional logic like this I find using async/await much easier to reason about. For that you need to convert your code to return promises:
function checkPort (path) {
return new Promise((resolve, reject) => {
let sock = new SerialPort(path, { baudRate: 9600 }, (err, data) => {
let timer = setTimeout(() => {
reject(new Error('Timeout'));
}, 100); // 100ms should be enough. If not increase it
sock.on('data', (err, data) => {
clearTimeout(timer); // clear the timer
if (err) {
return reject(err);
}
else {
return resolve(path);
}
})
sock.write("AT");
})
});
}
Now the for loop is easier to understand albeit now you cannot use forEach
or map
or filter
or any array methods. You need to use for
or while
if you want to use async/await:
async function findConnectedPort () {
let ports = await SerialPort.list();
for (let i=0; i<ports.length; i++) {
let port = ports[i];
try {
// return the first successful result:
return await checkPort(port.path);
}
catch (err) {
// ignore errors or handle them how you like
// just remember that we use an error to signal
// a timeout which simply means no device
// responded
}
}
// we didn't find anything. In this case I prefer to return
// nothing instead of returning an error:
return null;
}
Now you can simply get the connected port by doing:
async function main () {
let connectedPath = await findConnectedPort();
if (connectedPath === null) {
console.log('No devices found!');
}
else {
// Do what you need with the port here...
}
}
main();