UNPKG

mcp9808-temperature-sensor

Version:
443 lines (371 loc) 12.9 kB
'use strict'; const EventEmitter = require('events'); const Gpio = require('onoff').Gpio; const i2c = require('i2c-bus'); const mutexify = require('mutexify'); const DEFAULT_I2C_BUS = 1; const DEFAULT_I2C_ADDRESS = 0x18; const MANUFACTURER_ID = 0x54; const DEVICE_ID = 0x04; const CONFIGURATION_REG = 0x01; const UPPER_ALERT_TEMP_REG = 0x02; const LOWER_ALERT_TEMP_REG = 0x03; const CRITICAL_TEMP_REG = 0x04; const TEMP_REG = 0x05; const MANUFACTURER_ID_REG = 0x06; const DEVICE_ID_REVISION_REG = 0x07; const RESOLUTION_REG = 0x08; const MIN_TEMPERATURE = -256; const MAX_TEMPERATURE = 0xffc / 16; // 255.75 const MIN_RESOLUTION = 0; const MAX_RESOLUTION = 3; const MIN_HYSTERESIS = 0; const MAX_HYSTERESIS = 3; const CONFIGURATION_BITS = 0x7ff; const HYSTERESIS_BITS = 0x0600; const SHUTDOWN_BIT = 0x0100; const INTERRUPT_CLEAR_BIT = 0x0020; const ALERT_ENABLED_BIT = 0x0008; const ALERT_MODE_BIT = 0x0001; const configRegLock = mutexify(); const validateOpenOptions = options => { if (typeof options !== 'object') { return 'Expected options to be of type object.' + ' Got type ' + typeof options + '.'; } if (options.i2cBusNumber !== undefined && (!Number.isSafeInteger(options.i2cBusNumber) || options.i2cBusNumber < 0 ) ) { return 'Expected i2cBusNumber to be a non-negative integer.' + ' Got "' + options.i2cBusNumber + '".'; } if (options.i2cAddress !== undefined && (!Number.isSafeInteger(options.i2cAddress) || options.i2cAddress < 0 || options.i2cAddress > 0x7f ) ) { return 'Expected i2cAddress to be an integer' + ' >= 0 and <= 0x7f. Got "' + options.i2cAddress + '".'; } if (options.alertGpioNumber !== undefined && (!Number.isSafeInteger(options.alertGpioNumber) || options.alertGpioNumber < 0 ) ) { return 'Expected alertGpioNumber to be a non-negative integer. ' + 'Got "' + options.alertGpioNumber + '".'; } if (options.lowerAlertTemperature !== undefined && (typeof options.lowerAlertTemperature !== 'number' || options.lowerAlertTemperature < MIN_TEMPERATURE || options.lowerAlertTemperature > MAX_TEMPERATURE ) ) { return 'Expected lowerAlertTemperature to be a number >= ' + MIN_TEMPERATURE + ' and <= ' + MAX_TEMPERATURE + '. Got "' + options.lowerAlertTemperature + '".'; } if (options.upperAlertTemperature !== undefined && (typeof options.upperAlertTemperature !== 'number' || options.upperAlertTemperature < MIN_TEMPERATURE || options.upperAlertTemperature > MAX_TEMPERATURE ) ) { return 'Expected upperAlertTemperature to be a number >= ' + MIN_TEMPERATURE + ' and <= ' + MAX_TEMPERATURE + '. Got "' + options.upperAlertTemperature + '".'; } if (options.criticalTemperature !== undefined && (typeof options.criticalTemperature !== 'number' || options.criticalTemperature < MIN_TEMPERATURE || options.criticalTemperature > MAX_TEMPERATURE ) ) { return 'Expected criticalTemperature to be a number >= ' + MIN_TEMPERATURE + ' and <= ' + MAX_TEMPERATURE + '. Got "' + options.criticalTemperature + '".'; } if (options.criticalTemperature !== undefined && (typeof options.criticalTemperature !== 'number' || options.criticalTemperature < MIN_TEMPERATURE || options.criticalTemperature > MAX_TEMPERATURE ) ) { return 'Expected criticalTemperature to be a number >= ' + MIN_TEMPERATURE + ' and <= ' + MAX_TEMPERATURE + '. Got "' + options.criticalTemperature + '".'; } if (options.resolution !== undefined && (!Number.isSafeInteger(options.resolution) || options.resolution < MIN_RESOLUTION || options.resolution > MAX_RESOLUTION ) ) { return 'Expected resolution to be an integer >= ' + MIN_RESOLUTION + ' and <= ' + MAX_RESOLUTION + '. Got "' + options.resolution + '".'; } if (options.hysteresis !== undefined && (!Number.isSafeInteger(options.hysteresis) || options.hysteresis < MIN_HYSTERESIS || options.hysteresis > MAX_HYSTERESIS ) ) { return 'Expected hysteresis to be an integer >= ' + MIN_HYSTERESIS + ' and <= ' + MAX_HYSTERESIS + '. Got "' + options.hysteresis + '".'; } if (options.lowerAlertTemperature !== undefined || options.upperAlertTemperature !== undefined || options.criticalTemperature !== undefined) { if (options.lowerAlertTemperature === undefined || options.upperAlertTemperature === undefined || options.criticalTemperature === undefined) { return 'Expected all alert temperatures' + ' or no alert temperatures to be specified.'; } let lowerAlertTemperature = Math.round(options.lowerAlertTemperature * 4) / 4; let upperAlertTemperature = Math.round(options.upperAlertTemperature * 4) / 4; let criticalTemperature = Math.round(options.criticalTemperature * 4) / 4; if (lowerAlertTemperature >= upperAlertTemperature) { return 'Expected lowerAlertTemperature to be < upperAlertTemperature.'; } if (upperAlertTemperature >= criticalTemperature) { return 'Expected upperAlertTemperature to be < criticalTemperature.'; } } return null; }; const watchAlertGpio = (alertGpio, tempSensor, i2cMcp9808) => { let lastAlertWasCritical = false; alertGpio.watch((err, value) => { if (err) { tempSensor.emit('error', err); return; } let fallingEdge = (value === 0); tempSensor.temperature().then(temperature => { if (fallingEdge || lastAlertWasCritical) { tempSensor.emit('alert', temperature); } lastAlertWasCritical = temperature.critical; if (fallingEdge && !temperature.critical) { return i2cMcp9808.configuration(INTERRUPT_CLEAR_BIT, 0); } }).catch(err => tempSensor.emit('error', err)); }); }; class I2cMcp9808 { constructor(i2cBus, i2cAddress) { this._i2cBus = i2cBus; this._i2cAddress = i2cAddress; } close() { return this._i2cBus.close(); } writeByte(register, byte) { return this._i2cBus.writeByte(this._i2cAddress, register, byte); } readWord(register) { return this._i2cBus.readWord(this._i2cAddress, register). then(word => (word >> 8) + ((word & 0xff) << 8)); } writeWord(register, word) { return this._i2cBus.writeWord( this._i2cAddress, register, (word >> 8) + ((word & 0xff) << 8) ); } softReset() { return this.configuration(0, SHUTDOWN_BIT). then(_ => this.configuration(0, ALERT_ENABLED_BIT)). then(_ => this.configuration(0, ALERT_MODE_BIT)). // TODO - can this be removed? then(_ => this.lowerAlertTemperature(0)). then(_ => this.upperAlertTemperature(0)). then(_ => this.criticalTemperature(0)). then(_ => this.resolution(Mcp9808.RESOLUTION_1_16)). then(_ => this.configuration( INTERRUPT_CLEAR_BIT, CONFIGURATION_BITS ^ INTERRUPT_CLEAR_BIT ) ); } configuration(bitsToSet, bitsToReset) { let releaseConfigRegLock = null; // To modify bits in the configuration register it's necessary to read // the register, modify the required bits and write back to the // register. In order to prevent parallel asynchronous operations that // are modifying the configuration register from stepping on each other // here a mutex is needed. return new Promise((resolve, reject) => { configRegLock(release => { releaseConfigRegLock = release; resolve(); }); }). then(_ => this.readWord(CONFIGURATION_REG)). then(config => { config |= bitsToSet; config &= (~bitsToReset & 0xffff); return this.writeWord(CONFIGURATION_REG, config); }). then(_ => releaseConfigRegLock()). catch(err => { if (releaseConfigRegLock !== null) { releaseConfigRegLock(); } return Promise.reject(err); }); } writeTemperature(tempRegister, temp) { return Promise.resolve().then(_ => { let rawTemp = temp < 0 ? temp + 256 : temp; rawTemp = (Math.round(rawTemp * 4) << 2); if (temp < 0) { rawTemp |= 0x1000; } return this.writeWord(tempRegister, rawTemp); }); } temperature() { return this.readWord(TEMP_REG).then(rawTemp => { let temp = (rawTemp & 0x0fff) / 16; if (rawTemp & 0x1000) { temp -= 256; } return { celsius: temp, belowLowerLimit: !!(rawTemp & 0x2000), aboveUpperLimit: !!(rawTemp & 0x4000), critical: !!(rawTemp & 0x8000), rawTemp: rawTemp }; }); } lowerAlertTemperature(temp) { return this.writeTemperature(LOWER_ALERT_TEMP_REG, temp); } upperAlertTemperature(temp) { return this.writeTemperature(UPPER_ALERT_TEMP_REG, temp); } criticalTemperature(temp) { return this.writeTemperature(CRITICAL_TEMP_REG, temp); } manufacturerId() { return this.readWord(MANUFACTURER_ID_REG); } deviceId() { return this.readWord(DEVICE_ID_REVISION_REG).then(word => word >> 8); } resolution(val) { return this.writeByte(RESOLUTION_REG, val); } hysteresis(val) { return this.configuration( (val << 9) & HYSTERESIS_BITS, (~val << 9) & HYSTERESIS_BITS ); } enableAlerts() { // Don't set alertEnabled and alertMode at the same time. If done, there // will be no interrupt if the application is started and the current // temperature is > upperAlertTemperature and < criticalTemperature. return this.configuration(ALERT_ENABLED_BIT, 0). then(_ => this.configuration(ALERT_MODE_BIT, 0)); } } class Mcp9808 extends EventEmitter { static get RESOLUTION_1_2() {return 0;} static get RESOLUTION_1_4() {return 1;} static get RESOLUTION_1_8() {return 2;} static get RESOLUTION_1_16() {return 3;} static get HYSTERESIS_0() {return 0;} static get HYSTERESIS_1_5() {return 1;} static get HYSTERESIS_3() {return 2;} static get HYSTERESIS_6() {return 3;} constructor(i2cMcp9808, alertGpio) { super(); this._i2cMcp9808 = i2cMcp9808; this._alertGpio = alertGpio; } static open(options) { let i2cMcp9808; let tempSensor; return Promise.resolve().then(_ => { options = options || {}; let errMsg = validateOpenOptions(options); if (errMsg) { return Promise.reject(new Error(errMsg)); } return i2c.openPromisified(options.i2cBusNumber === undefined ? DEFAULT_I2C_BUS : options.i2cBusNumber); }).then(i2cBus => { const i2cAddress = options.i2cAddress === undefined ? DEFAULT_I2C_ADDRESS : options.i2cAddress; i2cMcp9808 = new I2cMcp9808(i2cBus, i2cAddress); let alertGpio = null; if (options.alertGpioNumber !== undefined) { alertGpio = new Gpio(options.alertGpioNumber, 'in', 'both'); } tempSensor = new Mcp9808(i2cMcp9808, alertGpio); if (alertGpio !== null) { watchAlertGpio(alertGpio, tempSensor, i2cMcp9808); } }).then(_ => i2cMcp9808.manufacturerId() ).then(manufacturerId => { if (manufacturerId !== MANUFACTURER_ID) { return Promise.reject(new Error( 'Expected manufacturer ID to be 0x' + MANUFACTURER_ID.toString(16) + '. Got 0x' + manufacturerId.toString(16) + '. MCP9808 sensor not found.' )); } return i2cMcp9808.deviceId(); }).then(deviceId => { if (deviceId !== DEVICE_ID) { return Promise.reject(new Error( 'Expected device ID to be 0x' + DEVICE_ID.toString(16) + '. Got 0x' + deviceId.toString(16) + '. MCP9808 sensor not found.' )); } return i2cMcp9808.softReset(); }).then(_ => { if (options.lowerAlertTemperature !== undefined) { return Promise.all([ i2cMcp9808.lowerAlertTemperature(options.lowerAlertTemperature), i2cMcp9808.upperAlertTemperature(options.upperAlertTemperature), i2cMcp9808.criticalTemperature(options.criticalTemperature) ]); } }).then(_ => { if (options.hysteresis !== undefined) { return i2cMcp9808.hysteresis(options.hysteresis); } }).then(_ => { if (options.resolution !== undefined) { return i2cMcp9808.resolution(options.resolution); } }).then(_ => tempSensor); } close() { return Promise.resolve().then(_ => { if (this._alertGpio !== null) { this._alertGpio.unexport(); } return this._i2cMcp9808.close(); }); } enableAlerts() { return this._i2cMcp9808.enableAlerts(); } temperature() { return this._i2cMcp9808.temperature(); } } module.exports = Mcp9808;