diff --git a/bin/hubinfo b/bin/hubinfo new file mode 100755 index 00000000..2e78436a --- /dev/null +++ b/bin/hubinfo @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require("../dist/node/hubinfo").main(process.argv.slice(2)); diff --git a/package.json b/package.json index eb4646a1..c84c73ea 100644 --- a/package.json +++ b/package.json @@ -32,5 +32,8 @@ "typescript": "^3.7.2", "webpack": "^4.41.2", "webpack-cli": "^3.3.10" + }, + "bin": { + "lpf2-hubinfo": "./bin/hubinfo" } } diff --git a/src/hub.ts b/src/hub.ts index 9e3df17f..aea6a246 100644 --- a/src/hub.ts +++ b/src/hub.ts @@ -46,7 +46,6 @@ export class Hub extends EventEmitter { }); } - /** * @readonly * @property {string} name Name of the hub diff --git a/src/hubinfo.ts b/src/hubinfo.ts new file mode 100644 index 00000000..19945d69 --- /dev/null +++ b/src/hubinfo.ts @@ -0,0 +1,85 @@ +import * as Consts from "./consts"; +import { Hub } from "./hub"; +import { LPF2Hub } from "./lpf2hub"; +import { PoweredUP } from "./poweredup-node"; +import { toBin, toHex } from "./utils"; + +function sanitizeString(s: string) { + return s.replace(/\0/g, " ").replace(/ +$/, ""); +} + +export function main(argv: string[]) { + let portInfo = false; + let quiet = false; + for (const arg of argv) { + switch (arg) { + case "-p": + case "--portinfo": + portInfo = true; + break; + case "-q": + case "--quiet": + quiet = true; + break; + default: + console.log("Usage: lpf2-hubinfo [-p|--portinfo] [-q|--quiet]"); + return; + } + } + if (portInfo) { + LPF2Hub.requestPortModeInfo = true; + } + const pup = new PoweredUP(); + pup.on("discover", async (hub: Hub) => { + const hubName = sanitizeString(hub.name); + if (!quiet) { + console.log(`Discovered ${hub.uuid} (${hubName})`); + } + await hub.connect(); + hub.on("portInfo", ({ port, type, hardwareVersion, softwareVersion }) => { + const typeName = Consts.DeviceTypeNames[type] || "unknown"; + console.log(`${hub.uuid} Port ${toHex(port)}, type ${toHex(type, 4)} (${typeName})`); + console.log(`${hub.uuid} Port ${toHex(port)}, hardware v${hardwareVersion}, software v${softwareVersion}`); + }); + hub.on("portModes", ({ port, count, input, output }) => { + console.log(`${hub.uuid} Port ${toHex(port)}, total modes ${count}, input modes ${toBin(input, count)}, output modes ${toBin(output, count)}`); + }); + hub.on("portModeCombinations", ({ port, modeCombinationMasks }) => { + console.log(`${hub.uuid} Port ${toHex(port)}, mode combinations [${modeCombinationMasks.map((c: number) => toBin(c, 0)).join(", ")}]`); + }); + hub.on("portModeInfo", (info) => { + const { port, mode, type } = info; + const prefix = `${hub.uuid} Port ${toHex(port)}, mode ${mode}`; + switch (type) { + case 0x00: // Mode Name + console.log(`${prefix}, name ${sanitizeString(info.name)}`); + break; + case 0x01: // RAW Range + console.log(`${prefix}, RAW min ${info.min}, max ${info.max}`); + break; + case 0x02: // PCT Range + console.log(`${prefix}, PCT min ${info.min}, max ${info.max}`); + break; + case 0x03: // SI Range + console.log(`${prefix}, SI min ${info.min}, max ${info.max}`); + break; + case 0x04: // SI Symbol + console.log(`${prefix}, SI symbol ${sanitizeString(info.name)}`); + break; + case 0x80: // Value Format + console.log(`${prefix}, Value ${info.numValues} x ${info.dataType}, Decimal format ${info.decimalFormat}`); + break; + } + }); + await hub.sleep(2000); + const typeName = Consts.HubTypeNames[hub.type] || `unknown (${toHex(hub.type)})`; + console.log(`${hub.uuid} firmware v${hub.firmwareVersion} hardware v${hub.hardwareVersion} ${typeName} (${hubName})`); + if (hub instanceof LPF2Hub && !portInfo) { + await hub.shutdown(); + } + }); + pup.scan(); + if (!quiet) { + console.log("Waiting for hubs..."); + } +} diff --git a/src/lpf2hub.ts b/src/lpf2hub.ts index c58e2c63..86d44a5c 100644 --- a/src/lpf2hub.ts +++ b/src/lpf2hub.ts @@ -4,11 +4,10 @@ import { Hub } from "./hub"; import { Port } from "./port"; import * as Consts from "./consts"; -import { toBin, toHex } from "./utils"; +import { toHex } from "./utils"; import Debug = require("debug"); const debug = Debug("lpf2hub"); -const modeInfoDebug = Debug("lpf2hubmodeinfo"); /** @@ -16,6 +15,8 @@ const modeInfoDebug = Debug("lpf2hubmodeinfo"); * @extends Hub */ export class LPF2Hub extends Hub { + public static requestPortModeInfo = false; + private static decodeVersion(v: number) { const t = v.toString(16).padStart(8, "0"); return [t[0], t[1], t.substring(2, 4), t.substring(4)].join("."); @@ -294,12 +295,10 @@ export class LPF2Hub extends Hub { let port = this._getPortForPortNumber(data[3]); const type = data[4] ? data.readUInt16LE(5) : 0; - if (data[4] === 0x01 && modeInfoDebug.enabled) { - const typeName = Consts.DeviceTypeNames[data[5]] || "unknown"; - modeInfoDebug(`Port ${toHex(data[3])}, type ${toHex(type, 4)} (${typeName})`); - const hwVersion = LPF2Hub.decodeVersion(data.readInt32LE(7)); - const swVersion = LPF2Hub.decodeVersion(data.readInt32LE(11)); - modeInfoDebug(`Port ${toHex(data[3])}, hardware version ${hwVersion}, software version ${swVersion}`); + if (data[4] === 0x01 && LPF2Hub.requestPortModeInfo) { + const hardwareVersion = LPF2Hub.decodeVersion(data.readInt32LE(7)); + const softwareVersion = LPF2Hub.decodeVersion(data.readInt32LE(11)); + this.emit("portInfo", { port: data[3], type, hardwareVersion, softwareVersion }); this._sendPortInformationRequest(data[3]); } @@ -343,13 +342,13 @@ export class LPF2Hub extends Hub { for (let i = 5; i < data.length; i += 2) { modeCombinationMasks.push(data.readUInt16LE(i)); } - modeInfoDebug(`Port ${toHex(port)}, mode combinations [${modeCombinationMasks.map((c) => toBin(c, 0)).join(", ")}]`); + this.emit("portModeCombinations", { port, modeCombinationMasks }); return; } const count = data[6]; - const input = toBin(data.readUInt16LE(7), count); - const output = toBin(data.readUInt16LE(9), count); - modeInfoDebug(`Port ${toHex(port)}, total modes ${count}, input modes ${input}, output modes ${output}`); + const input = data.readUInt16LE(7); + const output = data.readUInt16LE(9); + this.emit("portModes", { port, count, input, output }); for (let i = 0; i < count; i++) { this._sendModeInformationRequest(port, i, 0x00); // Mode Name @@ -368,31 +367,26 @@ export class LPF2Hub extends Hub { private _parseModeInformationResponse (data: Buffer) { - const port = toHex(data[3]); + const port = data[3]; const mode = data[4]; const type = data[5]; switch (type) { case 0x00: // Mode Name - modeInfoDebug(`Port ${port}, mode ${mode}, name ${data.slice(6, data.length).toString()}`); + case 0x04: // SI Symbol + this.emit("portModeInfo", { port, mode, type, name: data.slice(6, data.length).toString() }); break; case 0x01: // RAW Range - modeInfoDebug(`Port ${port}, mode ${mode}, RAW min ${data.readFloatLE(6)}, max ${data.readFloatLE(10)}`); - break; case 0x02: // PCT Range - modeInfoDebug(`Port ${port}, mode ${mode}, PCT min ${data.readFloatLE(6)}, max ${data.readFloatLE(10)}`); - break; case 0x03: // SI Range - modeInfoDebug(`Port ${port}, mode ${mode}, SI min ${data.readFloatLE(6)}, max ${data.readFloatLE(10)}`); - break; - case 0x04: // SI Symbol - modeInfoDebug(`Port ${port}, mode ${mode}, SI symbol ${data.slice(6, data.length).toString()}`); + this.emit("portModeInfo", { port, mode, type, min: data.readFloatLE(6), max: data.readFloatLE(10) }); break; case 0x80: // Value Format const numValues = data[6]; const dataType = ["8bit", "16bit", "32bit", "float"][data[7]]; const totalFigures = data[8]; const decimals = data[9]; - modeInfoDebug(`Port ${port}, mode ${mode}, Value ${numValues} x ${dataType}, Decimal format ${totalFigures}.${decimals}`); + this.emit("portModeInfo", { port, mode, type, numValues, dataType, decimalFormat: `${totalFigures}.${decimals}`}); + break; } }