node-red-contrib-adxl-vibration-fft
Version:
Node-RED nodes for Analog Devices' ADXL series FFT(Fast Fourier Transform) stream data parser
16 lines • 9.68 kB
JavaScript
/**
* @license
* Copyright (c) 2018 CANDY LINE INC.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ADXL100xFFTClient=void 0,require("source-map-support/register");var _events=require("events"),_serialport=_interopRequireDefault(require("serialport")),_hexdumpNodejs=_interopRequireDefault(require("hexdump-nodejs"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const debug=(0,_interopRequireDefault(require("debug")).default)("node-red-contrib-adxl-vibration-fft:index"),EDGE_DEVICE_CONFIG={generic:{sampleFreq:102.4,ampFullscale:20,ampCorrection:34,freqRange:20,fftDataOffset:0,peakFinder:"Header"},uq:{sampleFreq:107.5,ampFullscale:20,ampCorrection:20,freqRange:2,fftDataOffset:6,peakFinder:"Body",peakFinderByBody:{startIndex:2}},uq1002:{sampleFreq:102,ampFullscale:20,ampCorrection:20,freqRange:20,fftDataOffset:10,peakFinder:"Header"}};Object.values(EDGE_DEVICE_CONFIG).forEach(e=>{e.samples=parseInt(4096/e.sampleFreq*e.freqRange)});exports.ADXL100xFFTClient=class{constructor(e={}){this.serialport=e.serialport,e instanceof _events.EventEmitter?this.bus=e:this.bus=e.bus||new _events.EventEmitter,this.emitFftValues=e.emitFftValues,this.closed=!0,this.shutdownRequested=!1,this.frequencyLabels=[],this.edgeDeviceModel=e.edgeDeviceModel||"generic",this.edgeDeviceConfig=EDGE_DEVICE_CONFIG[this.edgeDeviceModel];const{samples:t,freqRange:r,peakFinder:s}=this.edgeDeviceConfig;for(let e=0;e<t;e++)this.frequencyLabels.push(Math.floor(r*e/(t-1)*10)/10+"kHz");this._findPeaks=this["_findPeaksBy"+s].bind(this),debug(`$$$$$$$$$$ [ADXL100xFFTClient] opts.edgeDeviceModel=>[${e.edgeDeviceModel}], this.edgeDeviceModel=>[${this.edgeDeviceModel}]`)}async _createUartCommand(e,t){return await this._createCommand("CPS","0000"),this._createCommand(e,t)}_createCommand(e,t){return new Promise((r,s)=>{const a=`:0 ${e} ${t}`;debug(`req(text):[${a}]`),this.port.write(a+"\r"),this.bus.once("command-response",a=>{const i=a.toString();if(i.startsWith("OK"))return debug(`res(text):[${i}]`),r();s(Error(`CMD: ${e} ${t}, Unexpected response:\n${(0,_hexdumpNodejs.default)(a)}`))})})}_onStopRequested(){return new Promise(e=>{this.commandMode=!0;const t=()=>{this._createCommand("CRE","0000").then(()=>{e()}).catch(()=>{debug("[_onShutdownRequested] error retry..."),setTimeout(t,100)})};setTimeout(t,0)})}async _onShutdownRequested(){return await this._onStopRequested(),this._createCommand("SRS","0000")}async _onInitCompleted(){return await this._createCommand("CPS","0000"),await this._createUartCommand("RMC","0000"),await this._createUartCommand("RRP","001s"),await this._createUartCommand("BSZ","0000"),await this._createUartCommand("DFA","0001"),await this._createUartCommand("AL1","0002"),await this._createUartCommand("AH1","0800"),await this._createUartCommand("AL8","0300"),await this._createUartCommand("AH8","0600"),await this._createUartCommand("AC8","0003"),await this._createCommand("CRS","0000"),this._onFftReady()}_onFftReady(){this.commandMode=!1,this.rawMsgHeaderInProgress=!0,this.rawMsgHeader=null,this.bus.emit("connected"),debug("[_onFftReady] OK"),this.bus.on("fft",e=>{for(;0<e.length;){if(this.rawMsgHeaderInProgress){this.rawMsgHeader=this.rawMsgHeader||Buffer.from([]);const t=this.rawMsgHeader.length;if(this.rawMsgHeader=Buffer.concat([this.rawMsgHeader,e.slice(0,12-t)]),12!==this.rawMsgHeader.length)return;this.rawMsgBodySize=256*this.rawMsgHeader[9]+this.rawMsgHeader[10]+4,debug("[RAW MSG header]\n"+(0,_hexdumpNodejs.default)(this.rawMsgHeader)),debug("[RAW MSG body size] => "+this.rawMsgBodySize),this.rawMsgHeaderInProgress=!1,this.rawMsgBody=Buffer.from([]),e=e.slice(12-t)}const t=this.rawMsgBodySize-this.rawMsgBody.length;this.rawMsgBody=Buffer.concat([this.rawMsgBody,e.slice(0,t)]),e=e.slice(t),this.rawMsgBody.length===this.rawMsgBodySize&&(this.bus.emit("fft-data-arrived",{header:this._parseNotifyBuf(this.rawMsgHeader),body:this._parseDataBuf(this.rawMsgBody)}),this.rawMsgHeaderInProgress=!0,this.rawMsgHeader=null,this.rawMsgBody=null,this.rawMsgBodySize=0)}}),this.bus.on("fft-data-arrived",e=>{debug("[fft-data-arrived] command => "+e.header.command),"XFD"===e.header.command&&this.bus.emit("data",e.body)})}async start(){const e=()=>{this.port=new _serialport.default(this.serialport,{baudRate:230400,autoOpen:!1}),this.port.on("close",()=>{this.bus.emit("disconnected"),this.closed=!0,this.shutdownRequested||(debug("[SerialPort:close] trying to re-connect"),setTimeout(e,5e3))}),this.port.on("error",t=>{debug("[SerialPort:error] "+t.stack),this.port.isOpen||this.port.close(()=>{debug("[SerialPort:error] trying to re-connect"),setTimeout(e,5e3)}),this.bus.emit("error")}),this.port.on("open",()=>{this.closed=!1,this.commandMode=!0,debug(`[SerialPort:open] Serial port (${this.serialport}) is now open.`);let e=null;const t=()=>{this.port.write("\r"),e=setTimeout(t,100)};e=setTimeout(t,100),this.port.write("\r"),this.bus.once("command-response",t=>{debug("[command-response]\n"+(0,_hexdumpNodejs.default)(t)),clearTimeout(e),this._startProcess()})});let t=null;this.port.on("data",e=>{this.commandMode?(t=t||Buffer.from([]),1<(t=Buffer.concat([t,e])).length&&10===t[t.length-2]||13===t[t.length-1]?(this.bus.emit("command-response",t),t=null):this.bus.emit("command-response-data",e)):this.bus.emit("fft",e)}),this.port.open(t=>{t&&(debug("[Serialport:open] opening error, trying to re-connect. Err:"+t.message),setTimeout(e,5e3))})};setTimeout(e,0)}async _startProcess(){return this._resetListeners(),new Promise((e,t)=>{let r=0;const s=()=>{debug("[start] initialzing parameters..."),this._onInitCompleted().then(e).catch(e=>{if(debug(`[start] Error while initialzing: trial=${r}, err=${e.message||e}`),r>3)return t(e);this._onStopRequested().finally(()=>{setTimeout(s,100),++r})})};process.nextTick(s)})}_resetListeners(){["fft","fft-data-arrived","command-response"].forEach(e=>{this.bus.listeners(e).forEach(t=>this.bus.removeListener(e,t))})}async shutdown(){if(!this.closed)return this.commandMode&&await new Promise(e=>{this.debug("Schedule the shutdown command..."),this.bus.once("data",()=>e())}),await this._onShutdownRequested(),this._resetListeners(),new Promise(e=>{this.shutdownRequested=!0,this.port.close(()=>e(!0))})}_parseNotifyBuf(e){if(!e)throw Error("No input!");return debug(`[_parseNotifyBuf] buf => ${e}, len => ${e.length}`),Array.isArray(e)?e=Buffer.from(e):Buffer.isBuffer(e)||(e=Buffer.from(e.toString())),{command:e.slice(3,6).toString(),size:256*e[9]+e[10]}}_byte2binary16(e){let t=32768&e,r=e>>10&31,s=1023&e;return 31===r?0===s?t?-1/0:1/0:NaN:0===r?0===s?t?-0:0:6103515625e-14*(t?-1:1)*(0+.0009765625*s):(t?-1:1)*Math.pow(2,r-15)*(1+.0009765625*s)}_parseDataBuf(e){if(!e)throw Error("No input!");Array.isArray(e)?e=Buffer.from(e):Buffer.isBuffer(e)||(e=Buffer.from(e.toString()));const{samples:t,fftDataOffset:r}=this.edgeDeviceConfig,s=e[0]+256*e[1]+65536*e[2]+16777216*e[3];let{peaks:a,raw:i}=this._findPeaks(e);if(this.emitFftValues){if(!i){i=Array(t);const s=36+r;for(let r=0;r<t;r++){const t=s+2*r;i[r]=this._byte2binary16(e[t]+256*e[t+1])}}}else i=null;return{raw:i,timestamp:s,peaks:a}}_findPeaksByHeader(e){const t=[];let r=null;debug("[FFT Data Header:Peak Freq.]\n"+(0,_hexdumpNodejs.default)(e.slice(8,20)));for(let s=8;s<20;s+=3)r=((15&e[s+1])<<8)+e[s],debug(`[_findPeaksByHeader(i:${s})] ${r}, 0x0f & dataBuf[i + 1]<< 8=${15&e[s+1]<<8}, dataBuf[i]=${e[s]}`),t.push({frequency:r}),r=(e[s+2]<<4)+((240&e[s+1])>>4),debug(`[_findPeaksByHeader(i:${s})] ${r}, 0x0f & dataBuf[i + 2] << 4=${15&e[s+1]<<8}, (0xf0 & dataBuf[i + 1]) >> 4=${(240&e[s+1])>>4}`),t.push({frequency:r});debug("[_findPeaksByHeader] [FFT Data Header:Peak Amp.]\n"+(0,_hexdumpNodejs.default)(e.slice(20,36)));for(let r=0;r<8;r++){const s=20+2*r,a=e[s],i=e[s+1];t[r].amplitude=(1-2*((128&i)>>7))*Math.pow(2,(124&i)>>2)/32768*(1024+256*(3&i)+a)/1024}return{raw:null,peaks:t}}_findPeaksByBody(e){const{peakFinderByBody:t,fftDataOffset:r,samples:s,ampFullscale:a,ampCorrection:i}=this.edgeDeviceConfig,o=Array(s),n=36+r;for(let t=0;t<s;t++){const r=n+2*t;o[t]=this._byte2binary16(e[r]+256*e[r+1])}const d=o.slice(t.startIndex).sort((e,t)=>t-e).slice(0,8).map(e=>({frequency:o.indexOf(e),amplitude:e}));return{raw:o,peaks:d}}_convertAmp(e,t,r){return t*Math.log10(e)-r}format(e,t,r,s){const{samples:a,sampleFreq:i,ampFullscale:o,ampCorrection:n}=this.edgeDeviceConfig,d={timestamp:e.timestamp,topic:t};r<0?r=0:8<r&&(r=8);const u=e.peaks.slice(0,r);switch(s){case"chart":case"chartWithoutPeak":const t=["FFT"],r=[e.raw?e.raw.map(e=>this._convertAmp(e,o,n)):[]];"chart"===s&&(u.forEach((e,s)=>{t.push("Peak"+(s+1));const i=Array(a).fill(0);i[e.frequency]=this._convertAmp(e.amplitude,o,n)+10,r.push(i)}),d.payload=[{series:t,data:r,labels:this.frequencyLabels}]);break;case"all":const h=e.raw?Array.prototype.slice.call(e.raw,0).map(e=>this._convertAmp(e,o,n)):null;d.payload={peaks:u.map(e=>({frequency:i/4096*e.frequency,amplitude:this._convertAmp(e.amplitude,o,n)})),fft:h};break;case"peak":d.payload=u.map(e=>({frequency:i/4096*e.frequency,amplitude:this._convertAmp(e.amplitude,o,n)}))}return d}};