@twilio/plugin-microvisor
Version:
Interact with your Twilio Microvisor devices
192 lines (191 loc) • 7.65 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.processResponse = exports.checkUserPermissions = exports.crc16 = exports.didTimeout = exports.getDataFromDevice = void 0;
const serialport_1 = require("serialport");
const proto_1 = require("../../../proto_gen/proto");
const { execSync } = require('child_process');
const { DelimiterParser } = require('@serialport/parser-delimiter');
const EOL = (process.platform == "linux" || process.platform == "darwin") ? "\n" : "\r\n";
function getDataFromDevice(passThis) {
// Check user perms on Ubuntu/Linux
if (!checkUserPermissions()) {
passThis.logger.error(`${passThis.mainError}. Please re-run as root or as a member of dialout.${EOL}`);
process.exit(1);
}
// Open the serial port
const port = new serialport_1.SerialPort({ path: passThis.args.devicePath, baudRate: 115200 });
port.on("open", function () {
// Clear the pipes
port.flush();
// Assemble the command packet
const packet = makeDataLessPacket(passThis.requestType);
// Prepare a listener for lines ending in \n
const parser = port.pipe(new DelimiterParser({ delimiter: '\r\n' }));
parser.on('data', function (rxData) {
if (processResponse(passThis, rxData)) {
clearTimeout(timer);
port.close();
}
});
// Set a timeout value
const timer = setTimeout(didTimeout, 5000, passThis, port);
// Send the command to the device via the serial port
port.write(packet, "utf8", function (err) {
if (err instanceof Error) {
clearTimeout(timer);
port.close();
passThis.logger.error(`${passThis.mainError} (${err.message})\n`);
}
});
});
port.on("error", function (err) {
passThis.logger.error(`${passThis.mainError} (${err.message})\n`);
});
}
exports.getDataFromDevice = getDataFromDevice;
function didTimeout(passThis, port) {
port.close();
passThis.logger.error(`${passThis.mainError} (Error: operation timed out).${EOL}`);
}
exports.didTimeout = didTimeout;
function crc16(data) {
var crc16 = 0xFFFF;
let poly_reversed = 0xA001;
for (const byte of data) {
crc16 ^= byte;
for (var j = 0; j < 8; j++) {
let carry = crc16 & 0x0001;
crc16 = crc16 >> 1;
if (carry)
crc16 ^= poly_reversed;
}
}
return (crc16 & 0xFFFF);
}
exports.crc16 = crc16;
function checkUserPermissions() {
if (process.platform == "linux") {
if (process.getuid() != 0) {
let username = process.env.USER || "";
return String(execSync(`groups ${username}`)).includes("dialout");
}
}
return true;
}
exports.checkUserPermissions = checkUserPermissions;
function makeDataLessPacket(cmdType) {
// Build the binary command
const msg = new Uint8Array(3);
msg[0] = cmdType;
const b64Data = Buffer.from(msg).toString('base64');
const utf8Encoder = new TextEncoder();
const payload = utf8Encoder.encode("CMD:" + b64Data);
// Assemble packet
const header = new Uint8Array([0x0D, 0x0A]);
let outro = new Uint8Array([0x3A, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x0A]);
// Insert CRC16
const crcn = crc16(payload);
const crcs = crcn.toString(16).padStart(4, '0');
const crc_data = utf8Encoder.encode(crcs);
outro.set(crc_data, 1);
const packet = new Uint8Array(header.length + payload.length + outro.length);
packet.set(header);
packet.set(payload, header.length);
packet.set(outro, header.length + payload.length);
return packet;
}
function processResponse(passThis, data) {
const rx = data.toString();
if (rx.startsWith("OK:") || rx.startsWith("ERROR:")) {
const parts = rx.split(":");
if (parts.length != 3)
return true;
if (rx.startsWith("OK:")) {
if (!checkRespCRC(passThis, data) || !decodeMessage(passThis, parts[1])) {
passThis.logger.error(`Could not parse response from device.${EOL}`);
}
}
else {
passThis.logger.info(`${passThis.mainError} (error: ${+parts[1]})`);
}
return true;
}
// No valid response yet
return false;
}
exports.processResponse = processResponse;
function decodeMessage(passThis, data) {
const protoMsgBuf = checkResponse(data, passThis.requestType);
if (typeof protoMsgBuf == "number")
return false;
switch (passThis.requestType) {
case proto_1.provision.ProvisioningCommands_MessageTypes_F2D.TYPE_F2D_GET_VERSION_REQUEST:
{
const protoMsg = proto_1.provision.GetVersionResponse.decode(protoMsgBuf);
const version = new TextDecoder().decode(protoMsg.version);
const parts = version.split(' - ');
const version_parts = parts[0].split('+');
const msg = parts.length > 1 ? `Microvisor version: ${version_parts[0]}` : "Microvisor version: unknown";
passThis.logger.info(msg);
if (parts.length > 1)
passThis.logger.info(`Build date: ${parts[1]}`);
}
return true;
;
case proto_1.provision.ProvisioningCommands_MessageTypes_F2D.TYPE_F2D_GET_WIFI_CONFIGURATION_REQUEST:
{
const protoMsg = proto_1.provision.GetWifiConfigurationResponse.decode(protoMsgBuf);
const ssid = new TextDecoder().decode(protoMsg.ssid);
const pwd = protoMsg.hasPassword ? "<set>" : "<not set>";
if (ssid == "" && pwd == "<not set>") {
passThis.logger.info("WiFi credentials not set");
return true;
}
if (!protoMsg.success)
return false;
passThis.logger.info(`SSID: ${ssid}${EOL}Password: ${pwd}`);
}
return true;
case proto_1.provision.ProvisioningCommands_MessageTypes_F2D.TYPE_F2D_SET_WIFI_CONFIGURATION_REQUEST:
{
const protoMsg = proto_1.provision.SetWifiConfigurationResponse.decode(protoMsgBuf);
passThis.logger.info(protoMsg.success ? "WiFi configuration set" : "WiFi configuration not set");
}
return true;
case proto_1.provision.ProvisioningCommands_MessageTypes_F2D.TYPE_F2D_GET_SID_REQUEST:
{
const protoMsg = proto_1.provision.GetDeviceSidResponse.decode(protoMsgBuf);
const sid = new TextDecoder().decode(protoMsg.sid);
passThis.logger.info(`Device SID: ${sid}`);
}
return true;
default:
break;
}
return false;
}
function checkRespCRC(passThis, data) {
var pcrc = 1;
var ccrc = 0;
for (var i = data.length - 1; i >= 0; i--) {
if (data[i] == 0x3A) {
pcrc = crc16(data.slice(0, i));
const ccrcs = data.slice(i + 1, i + 5).toString();
ccrc = Number("0x" + ccrcs);
break;
}
}
return (ccrc == pcrc);
}
function checkResponse(data, cmdType) {
let buffer = Buffer.from(data, 'base64');
let len = (buffer[1] << 8) | buffer[2];
if (buffer[0] != cmdType)
return -1;
if (len + 3 != buffer.length)
return -1;
let protoMsgBuf = new Uint8Array(buffer.length - 3);
for (var i = 3; i < buffer.length; i++)
protoMsgBuf[i - 3] = buffer[i];
return protoMsgBuf;
}