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
JavaScript
'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;