UNPKG

co2monitor

Version:

Node for the Dostmann CO2 Monitor mini. If there are permission errors on LIBUSB, you have to add a new file to '/etc/udev/rules.d' with the following record SUBSYSTEM=="usb", MODE="0660", GROUP="plugdev"

178 lines (159 loc) 5.55 kB
'use strict'; const os = require('os'); const usb = require('usb'); const eventEmitter = require('events'); class airCo2ntrol extends eventEmitter { constructor() { super(); this._vendorID = 0x04D9; this._productID = 0xA052; this._key = Buffer.from([ 0xc4, 0xc6, 0xc0, 0x92, 0x40, 0x23, 0xdc, 0x96 ]); this._device = null; this._interface = null; this._endpoint = null; this._co2 = null; this._tempC = null; this._humidity = null; } connect(callback) { this._device = usb.findByIds(this._vendorID, this._productID); if (!this._device) { const err = new Error('Device not found!'); this.emit('error', err); return callback(err); } this._device.open(); this._interface = this._device.interfaces[0]; if (os.platform() === 'linux' && this._interface.isKernelDriverActive()) { this._interface.detachKernelDriver(); } if (!this._interface) { const err = new Error('Interface not found!'); this.emit('error', err); return callback(err); } // Parameters for `libusb_control_transfer`. const bmReqType = 0x21, bReq = 0x09, wValue = 0x0300, wIdx = 0x00; // Setup OUT transfer. this._device.controlTransfer(bmReqType, bReq, wValue, wIdx, this._key, (err) => { if (err) { this.emit('error', err); return callback(err); } this._interface.claim(); this._endpoint = this._interface.endpoints[0]; this.emit('connect', this._endpoint); return callback(); }); } disconnect(callback) { this._endpoint.stopPoll(() => { if (os.platform() === 'linux') { this._interface.attachKernelDriver(); } this._interface.release(true, (err) => { if (err) { this.emit('error', err); } this._device.close(); this.emit('disconnect'); return callback(err); }); }); } transfer(callback) { callback = callback || (() => {}); const transLen = 8; this._endpoint.transfer(transLen, (err) => { if (err) { this.emit('error', err); return callback(err); } const nTransfers = 8, transferSize = 8; this._endpoint.startPoll(nTransfers, transferSize); this._endpoint.on('data', (data) => { // The data of the AirCO2NTROL Mini needs to be decrypted. if (this._device.deviceDescriptor.bcdDevice === 0x0100) { data = CO2Monitor._decrypt(this._key, data); } const checksum = data[3], sum = data.slice(0, 3) .reduce((s, item) => (s + item), 0) & 0xff; // Validate checksum. if (data[4] !== 0x0d || checksum !== sum) { return this.emit( 'error', new Error('Checksum Error.') ); } const op = data[0]; const value = data[1] << 8 | data[2]; switch (op) { case 0x42: // Temperature this._tempC = parseFloat( (value / 16 - 273.15).toFixed(2) ); this.emit('tempC', this._tempC); break; case 0x50: // CO2 this._co2 = value; this.emit('co2', this._co2); break; case 0x41: // hum this._humidity = value / 100; this.emit('hum', this._humidity); break; default: break; } }); this._endpoint.on('error', (err) => this.emit('error', err) ); return callback(); }); } get temperatureC() { return this._tempC; } get temperatureF() { return this._tempF; } get co2() { return this._co2; } get humidity() { return this._humidity; } static _decrypt(key, data) { const cstate = [ 0x48, 0x74, 0x65, 0x6D, 0x70, 0x39, 0x39, 0x65 ]; const shuffle = [2, 4, 0, 7, 1, 6, 5, 3]; const length = cstate.length; let i; const dataXor = []; for (i = 0; i < length; i++) { const idx = shuffle[i]; dataXor[idx] = data[i] ^ key[idx]; } const dataTmp = []; for (i = 0; i < length; i++) { dataTmp[i] = ((dataXor[i] >> 3) | (dataXor[(i - 1 + 8) % 8] << 5)) & 0xff; } const results = []; for (i = 0; i < length; i++) { const ctmp = ((cstate[i] >> 4) | (cstate[i] << 4)) & 0xff; results[i] = ((0x100 + dataTmp[i] - ctmp) & 0xff); } return results; } } module.exports = airCo2ntrol;