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.

253 lines (237 loc) 7.53 kB
//might be required: sudo usermod -a -G spi $USER //double-check: ls -l /dev/spi* //if you have connection issues: activate SPI interface in OS, check pin 5 const Gpio = require('onoff').Gpio; //required for pin 5 voltage const spi = require('spi-device'); /* 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: "ledArray", options: [ {name: "numOfLeds", type: "number", min: 1}, {name: "model", type: "string", values: ["2mic", "4mic", "6mic", "4micL"]} ], writeInterface: [ {name: "ledIndex", type: "number", min: 1}, {name: "red", type: "number", min: 0, max: 255}, {name: "green", type: "number", min: 0, max: 255}, {name: "blue", type: "number", min: 0, max: 255} ], info: "ReSpeaker-ish audio HAT APA102 LEDs interface." }; } class GpioItem { //APA102 LEDs - ReSpeaker MIC HAT //Reference: https://github.com/respeaker/mic_hat/blob/master/interfaces/apa102.py //Useful: https://github.com/jonnypage-d3/hooloovoo/blob/master/hooloovoo.js // https://github.com/respeaker/4mics_hat/issues/2#issuecomment-471563162 constructor(options){ if (!options) options = {}; this.isReady = false; //LED config this._model = options.model || "2mic"; //2mic, 4mic, 6mic, 4micL this._numOfLeds = options.numOfLeds; //2mic HAT has 3, 4mic has 12? ... if (this._numOfLeds == undefined){ if (this._model == "2mic"){ this._numOfLeds = 3; }else if (this._model == "4mic" || this._model == "6mic"){ this._numOfLeds = 12; }else if (this._model == "4micL"){ this._numOfLeds = 0; //4mic linear array has no APA102s (use pin 5 LED?) }else{ this._numOfLeds = 1; } } //APA102 IC this.apa102; this.apa102Speed = 4000000; //4Mhz //SPI bus/device this._spiBusAndDevice = [0, 0]; //TODO: e.g. 4mic uses [0, 1] this.pin5 = undefined; if (this._model == "4mic"){ this._spiBusAndDevice = [0, 1]; //activate pin 5 (required for some mic HATs) this.pin5 = new Gpio(5, "out"); this.pin5.writeSync(1); } //init. LED buffer this._ledBits = this._numOfLeds * 4 + 8; //BGRb + 4 start frame bits + 4 end frame bits this._ledBuffer = Buffer.alloc(this._ledBits); //full LEDs configuration //set LED buffer this.setLedBuffer = function(ledIndex, rgbRed, rgbGreen, rgbBlue, brightness){ let currentLed = 4 + (ledIndex * 4); this._ledBuffer[currentLed + 1] = rgbBlue; this._ledBuffer[currentLed + 2] = rgbGreen; this._ledBuffer[currentLed + 3] = rgbRed; this._ledBuffer[currentLed + 0] = brightness; } //set buffer for all LEDs this.setBufferAll = function(red, green, blue, brightness){ for (let i=0; i < this._numOfLeds; i++) { this.setLedBuffer(i, red, green, blue, brightness); } } //transfer data this.transferBuffer = function(successCallback, errorCallback){ let message = [{ sendBuffer: this._ledBuffer, //receiveBuffer: Buffer.alloc(led_bits), byteLength: this._ledBits, speedHz: this.apa102Speed }]; this.apa102.transfer(message, (err, response) => { if (err){ errorCallback(err); }else{ //console.log("transfer response", response); //DEBUG //TODO: use response? successCallback({status: "transfered"}); } }); } //release pin 5 this.releasePin5 = function(){ if (this.pin5){ //reset pin 5 try { this.pin5.writeSync(0); this.pin5.unexport(); }catch (err){ //we ignore the error, just print it console.error("GPIO-Interface: Failed to release pin 5 in 'rpi-respeaker-mic-hat-leds' item.", err); } } } } init(successCallback, errorCallback){ //bus 0, device 0 this.apa102 = spi.open(this._spiBusAndDevice[0], this._spiBusAndDevice[1], err => { if (err){ errorCallback(err); }else{ //init. buffer for (let i=0; i < this._ledBits; i++){ if (i < (this._ledBits - 4)){ this._ledBuffer[i] = 0x00; }else{ this._ledBuffer[i] = 255; } } //set all off this.setBufferAll(0, 0, 0, 255); var that = this; this.transferBuffer(function(){ //done that.isReady = true; successCallback(); }, errorCallback); } }); } writeData(data, successCallback, errorCallback){ if (!this.isReady){ errorCallback({name: "NotReady", message: "Interface not yet ready"}); }else if (!data || data.red == undefined || data.blue == undefined || data.green == undefined || data.ledIndex == undefined ){ errorCallback({name: "MissingData", message: "Required: ledIndex, red, green, blue"}); }else if (data.ledIndex < 1 || data.ledIndex > this._numOfLeds){ errorCallback({name: "WrongLedIndex", message: ("LED index must be between 1 and " + this._numOfLeds)}); }else{ try { let brightness = 255; //NOTE: we keep this constant because its quirky anyway let internalIndex = data.ledIndex - 1; this.setLedBuffer(internalIndex, +data.red, +data.green, +data.blue, brightness); //write this.transferBuffer(successCallback, errorCallback); }catch (err){ errorCallback(err); } } } readData(options, successCallback, errorCallback){ //TODO: converted to RGB data? read fresh from device? if (this._ledBuffer){ successCallback({result: this._ledBuffer}); }else{ errorCallback({name: "NoData", message: "No data found"}); } } release(successCallback, errorCallback){ if (this.apa102){ //switch all off this.setBufferAll(0, 0, 0, 255); var that = this; this.transferBuffer(function(){ //close controller that.apa102.close(function(err){ if (err){ errorCallback(err); }else{ //try to release pin 5 (if required) that.releasePin5(); successCallback(); } }); }, function(err){ //try to release pin 5 anyway (if required) that.releasePin5(); errorCallback(err); }); }else if (this.pin5){ this.releasePin5(); }else{ successCallback(); } } } //console test call example: node -e 'require("./rpi-respeaker-mic-hat-leds").test({init: {model: "2mic"}})' function test(options){ if (!options) options = {}; var desc = description(); console.log("GPIO Item Test: " + desc.info); var gpioItem = new GpioItem(options.init || { model: "", numOfLeds: 1 }); console.log("GPIO init"); gpioItem.init(function(){ console.log("GPIO init: success - NEXT: write"); gpioItem.writeData(options.write || { ledIndex: 1, red: 150, green: 0, blue: 150 }, 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 };