UNPKG

clexi

Version:

Node.js CLEXI is a lightweight client extension interface that enhances connected clients with functions of the underlying operating system using a duplex, realtime Websocket connection.

270 lines (250 loc) 7.62 kB
//might be required (change group 'gpio' as you like): //- sudo su -c "echo 'SUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"2886\", ATTRS{idProduct}==\"0018\", GROUP=\"gpio\", MODE=\"0666\"' > /etc/udev/rules.d/99-ReSpeakerUSB.rules" //- sudo usermod -a -G gpio $USER //- reboot or replug USB device const USB = require('usb'); //const BMRT = require('bmrequesttype'); /* Required interface functions: - description(): Info about the item options and commands - Class: - constructor(options) - init(successCallback, errorCallback) - writeData(data, successCallback, errorCallback) - readData(options, successCallback, errorCallback) - release(successCallback, errorCallback) - test(options): Function to test item (init, write, read, release) */ function description(){ return { type: "ledArrayUsb", options: [ {name: "mode", type: "string", values: ["sepia", "seeed"]}, {name: "brightness", type: "number", min: 1, max: 31} ], writeInterface: [ {name: "state", type: "string", values: [ "idle", "loading", "listening", "speaking", "awaitDialog", "wakeWordActive", "wakeWordInactive"]}, {name: "rgb0Array", type: "array"} ], info: "ReSpeaker Mic Array v2.0 USB LED interface." }; } class PixelRing { constructor(device, mode) { this.device = device; this.mode = mode; this.state = ""; this.vadState = ""; //init. LED buffer this._numOfLeds = 12; this._ledBits = this._numOfLeds * 4; //rgb0 this.ledArray = new Array(this._ledBits); //full LEDs configuration this.ledArray.fill(0); //set LED array this.setLedArray = function(ledIndex, rgbRed, rgbGreen, rgbBlue, brightness){ let currentLed = (ledIndex - 1) * 4; this.ledArray[currentLed + 0] = rgbRed; this.ledArray[currentLed + 1] = rgbGreen; this.ledArray[currentLed + 2] = rgbBlue; this.ledArray[currentLed + 3] = brightness; } //set array for all LEDs this.setLedArrayAll = function(red, green, blue, brightness){ for (let i=0; i < this._numOfLeds; i++) { this.setLedArray(i, red, green, blue, brightness); } } } get availableStates() { return ["idle", "loading", "listening", "speaking", "awaitDialog", "wakeWordActive", "wakeWordInactive", "custom"]; } get currentState() { return {state: this.state, vad: this.vadState}; } write(cmd, data, stateInfo) { if (data == undefined) data = [0]; const buffer = Buffer.from(data); //var bmRt = BMRT.bmRequestType(BMRT.DIRECTION.Out, BMRT.TYPE.Vendor, BMRT.RECIPIENT.Device); var bmRt = 0x40; var that = this; return new Promise(function(resolve, reject){ that.device.controlTransfer(bmRt, 0, cmd, 0x1C, buffer, function(err, data){ if (err){ reject(err); }else{ if (stateInfo) that.state = stateInfo; resolve(data); } }); }); } //settings traceMode() { return this.write(0, [0], "trace"); } vadLedMode(i) { this.vadState = i; //TODO: unchecked state return this.write(0x22, [i]); //0 - off, 1 - on, 3 - auto } brightness(i) { return this.write(0x20, [i]); //range: 0x00 to 0x1F - or 0 to 31 } //states custom(rgb0Array, stateInfo) { return this.write(6, rgb0Array, stateInfo); //TODO } idle() { return this.write(1, [0, 0, 0, 0], "idle"); } loading() { return this.write(5, [0], "loading"); } listening() { return this.write(2, [0], "listening"); } speaking() { return this.write(4, [0], "speaking"); } awaitDialog() { this.setLedArray(1, 120, 120, 0, 0); return this.custom(this.ledArray, "awaitDialog"); //TODO: test } wakeWordActive() { return this.vadLedMode(1); //TODO: test } wakeWordInactive() { return this.vadLedMode(0); //TODO: test } } class GpioItem { //ReSpeaker Mic Array v2.0 vendor specific USB LED interface. //Reference 1: https://github.com/respeaker/pixel_ring/blob/master/pixel_ring/usb_pixel_ring_v2.py //Reference 2: https://wiki.seeedstudio.com/ReSpeaker_Mic_Array_v2.0/#control-the-leds //Node USB API: https://github.com/node-usb/node-usb#legacy-api constructor(options){ if (!options) options = {}; this.isReady = false; //USB Device this._device = USB.findByIds(0x2886, 0x0018); //hardcoded vendor/product ID this._device.timeout = 8000; this.pixelRing; //settings this._mode = options.mode || "sepia"; //TODO: implement this._brightness = options.brightness || 10; } init(successCallback, errorCallback){ if (!this._device){ errorCallback({name: "MissingDevice", message: "USB device not found"}); }else{ this.pixelRing = new PixelRing(this._device, this._mode); var that = this; try { //open device this._device.open(); //set inital modes (VAD LED off, brightness half) that.pixelRing.vadLedMode(0).then(function(){ that.pixelRing.brightness(that._brightness) }).then(function(){ //done that.isReady = true; successCallback(); }).catch(function(err){ errorCallback(err); }); }catch (err){ errorCallback(err); } } } writeData(data, successCallback, errorCallback){ if (!this.isReady){ errorCallback({name: "NotReady", message: "Interface not yet ready"}); }else if (!data || !data.state){ errorCallback({name: "MissingData", message: "Required: state"}); }else if (this.pixelRing.availableStates.indexOf(data.state) < 0){ errorCallback({name: "WrongData", message: "Unknown state"}); }else{ try { //write state this.pixelRing[data.state](data.rgb0Array).then(function(res){ successCallback(); }).catch(function(err){ errorCallback(err); }); }catch (err){ errorCallback(err); } } } readData(options, successCallback, errorCallback){ if (this.pixelRing){ var res = this.pixelRing.currentState; successCallback({result: res}); }else{ errorCallback({name: "NoData", message: "No data found"}); } } release(successCallback, errorCallback){ if (this._device){ //switch all off if (this.pixelRing){ //write state var that = this; this.pixelRing.idle().then(function(res){ //done that._device.close(); successCallback(); }).catch(function(err){ //done that._device.close(); errorCallback(err); }); }else{ this._device.close(); successCallback(); } }else{ successCallback(); } } } //console test call example: node -e 'require("./respeaker-usb-array-v2").test({init: {}, write: {state: "speaking"}})' function test(options){ if (!options) options = {}; var desc = description(); console.log("GPIO Item Test: " + desc.info); var gpioItem = new GpioItem(options.init || { mode: "sepia" }); console.log("GPIO init"); gpioItem.init(function(){ console.log("GPIO init: success - NEXT: write"); gpioItem.writeData(options.write || { state: "loading" }, function(){ console.log("GPIO write: success - NEXT: read in 3s"); setTimeout(function(){ gpioItem.readData(options.read || {}, function(data){ console.log("GPIO read: success - data:", data, "- NEXT: release"); gpioItem.release(function(data){ console.log("GPIO release: success - Item test: DONE"); }, function(err){ console.error("GPIO release: error", err); }); }, function(err){ console.error("GPIO read: error", err); }); }, 3000); }, function(err){ console.error("GPIO write: error", err); }); }, function(err){ console.error("GPIO init: error", err); }); } module.exports = { description: description, GpioItem: GpioItem, test: test, };