UNPKG

johnny-five

Version:

The JavaScript Robotics and Hardware Programming Framework. Use with: Arduino (all models), Electric Imp, Beagle Bone, Intel Galileo & Edison, Linino One, Pinoccio, pcDuino3, Raspberry Pi, Particle/Spark Core & Photon, Tessel 2, TI Launchpad and more!

1,735 lines (1,471 loc) 78.6 kB
const Board = require("./board"); const Emitter = require("./mixins/emitter"); const Fn = require("./fn"); const priv = new Map(); const activeDrivers = new Map(); const { int16, uint16, uint24, s32, u32, } = Fn; const ACCELEROMETER = "accelerometer"; const ALTIMETER = "altimeter"; const BAROMETER = "barometer"; const GYRO = "gyro"; const HYGROMETER = "hygrometer"; const MAGNETOMETER = "magnetometer"; const ORIENTATION = "orientation"; const THERMOMETER = "thermometer"; function Components(controller, options) { const state = priv.get(this); const descriptors = Object.create(null); for (const component of this.components) { // TODO: Can this be put inside the get accessor? // - Lazy init? state[component] = new Components[component]( Object.assign({ controller: options.controller || controller, freq: options.freq, board: this.board, }, options) ); descriptors[component] = { get() { return state[component]; } }; // TODO: Get rid of this trash if (backwardCompatibilityGarbageHacks[component]) { descriptors[backwardCompatibilityGarbageHacks[component]] = descriptors[component]; } } Object.defineProperties(this, descriptors); } Components.accelerometer = require("./accelerometer"); Components.altimeter = require("./altimeter"); Components.barometer = require("./barometer"); Components.gyro = require("./gyro"); Components.hygrometer = require("./hygrometer"); Components.magnetometer = require("./compass"); Components.orientation = require("./orientation"); Components.thermometer = require("./thermometer"); const backwardCompatibilityGarbageHacks = { thermometer: "temperature", }; const Drivers = { SHT31D: { ADDRESSES: { value: [0x44] }, REGISTER: { value: { // Table 13 SOFT_RESET: 0x30A2, // Table 8 MEASURE_HIGH_REPEATABILITY: 0x2400, } }, initialize: { value(board, options) { const READLENGTH = 6; const io = board.io; const address = Drivers.addressResolver(this, options); io.i2cConfig(options); io.i2cWrite(address, [ // Page 12, Table 13 this.REGISTER.SOFT_RESET >> 8, this.REGISTER.SOFT_RESET & 0xFF, ]); const computed = { temperature: null, humidity: null, }; // temp msb, temp lsb, temp CRC, humidity msb, humidity lsb, humidity CRC const readCycle = () => { // Page 10, Table 8 // Send high repeatability measurement command io.i2cWrite(address, [ this.REGISTER.MEASURE_HIGH_REPEATABILITY >> 8, this.REGISTER.MEASURE_HIGH_REPEATABILITY & 0xFF, ]); setTimeout(() => { io.i2cReadOnce(address, READLENGTH, data => { computed.temperature = uint16(data[0], data[1]); computed.humidity = uint16(data[3], data[4]); this.emit("data", computed); readCycle(); }); }, 16); }; readCycle(); } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.SHT31D, options); return `sht-31d-${address}`; } } }, HTU21D: { ADDRESSES: { value: [0x40] }, REGISTER: { value: { HUMIDITY: 0xE5, TEMPERATURE: 0xE3, SOFT_RESET: 0xFE, } }, initialize: { value(board, options) { const io = board.io; const address = Drivers.addressResolver(this, options); // The "no hold" measurement requires waiting // _at least_ 22ms between register write and // register read. Delay is measured in μs: // 22ms = 22000μs; recommend 50ms = 50000μs options.delay = 50000; io.i2cConfig(options); io.i2cWrite(address, this.REGISTER.SOFT_RESET); const computed = { temperature: null, humidity: null, }; let cycle = 0; const readCycle = () => { // Despite the registers being back to back, the HTU21D // does not like when 5 bytes are requested, so we put // the two data sources on their own read channels. const isTemperatureCycle = cycle === 0; const register = isTemperatureCycle ? this.REGISTER.TEMPERATURE : this.REGISTER.HUMIDITY; io.i2cReadOnce(address, register, 2, data => { if (isTemperatureCycle) { computed.temperature = uint16(data[0], data[1]); } else { computed.humidity = uint16(data[0], data[1]); } if (++cycle === 2) { cycle = 0; this.emit("data", computed); } readCycle(); }); }; readCycle(); } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.HTU21D, options); return `htu-s1d-${address}`; } } }, HIH6130: { ADDRESSES: { value: [0x27] }, initialize: { value(board, options) { const io = board.io; const address = Drivers.addressResolver(this, options); io.i2cConfig(options); const computed = { humidity: null, temperature: null, }; let delay = 36.65; const measureCycle = () => { // The most common use cases involve continuous // sampling of sensor data, so that's what this // controller-driver will provide. io.i2cWrite(address, 0xA0, [0x00, 0x00]); setTimeout(() => { io.i2cWrite(address, 0x80, [0x00, 0x00]); io.i2cReadOnce(address, 4, data => { // Page 2, Figure 4. // Humidity and Temperature Data Fetch, Four Byte Data Read // B7:6 Contain status bits const status = data[0] >> 6; // Mask out B7:6 status bits from H MSB computed.humidity = int16(data[0] & 0x3F, data[1]); // Shift off B1:0 (which are empty) computed.temperature = int16(data[2], data[3] >> 2); // Page 3, 2.6 Status Bits // // 0 0 Normal // 0 1 Stale // 1 0 Command Mode // 1 1 Diagnostic Condition // // When the two status bits read "01", "stale" data is // indicated. This means that the data that already // exists in the sensor's output buffer has already // been fetched by the Master, and has not yet been // updated with the next data from the current measurement // cycle. This can happen when the Master polls the // data quicker than the sensor can update the output buffer. if (status === 0) { delay--; } if (status === 1) { delay++; } this.emit("data", computed); measureCycle(); }); // Page 3 // 3.0 Measurement Cycle // The measurement cycle duration is typically // 36.65 ms for temperature and humidity readings. }, delay); }; measureCycle(); } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.HIH6130, options); return `hih6130-${address}`; } } }, DHT_I2C_NANO_BACKPACK: { ADDRESSES: { value: [0x0A] }, REGISTER: { value: { READ: 0x00, } }, initialize: { value(board, options) { const io = board.io; // Correspond to firmware variables const dhtPin = 2; let dhtType = 11; const address = Drivers.addressResolver(this, options); io.i2cConfig(options); const dhtVariantExec = /(\d{2})/.exec(options.controller); const dhtVariant = dhtVariantExec && dhtVariantExec.length && dhtVariantExec[0]; if (dhtVariant) { dhtType = +dhtVariant; if (Number.isNaN(dhtType)) { dhtType = 11; } } const computed = { temperature: null, humidity: null, }; io.i2cWrite(address, [dhtPin, dhtType]); io.i2cRead(address, 4, data => { computed.humidity = int16(data[0], data[1]); computed.temperature = int16(data[2], data[3]); this.emit("data", computed); }); } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.DHT_I2C_NANO_BACKPACK, options); return `dht_i2c_nano_backpack-${address}`; } } }, MPU6050: { ADDRESSES: { value: [0x68, 0x69] }, REGISTER: { value: { SETUP: [0x6B, 0x00], READ: 0x3B } }, initialize: { value(board, options) { const READLENGTH = 14; const io = board.io; const address = Drivers.addressResolver(this, options); const computed = { accelerometer: {}, temperature: {}, gyro: {} }; io.i2cConfig(options); io.i2cWrite(address, this.REGISTER.SETUP); io.i2cRead(address, this.REGISTER.READ, READLENGTH, data => { computed.accelerometer = { x: int16(data[0], data[1]), y: int16(data[2], data[3]), z: int16(data[4], data[5]) }; computed.temperature = int16(data[6], data[7]); computed.gyro = { x: int16(data[8], data[9]), y: int16(data[10], data[11]), z: int16(data[12], data[13]) }; this.emit("data", computed); }); }, }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.MPU6050, options); return `mpu-6050-${address}`; } } }, BNO055: { ADDRESSES: { value: [0x28, 0x29] }, REGISTER: { value: { // // 4.2.1 Register map Page 0 // READ: { /* All motion data is in the following order: X LSB X MSB Y LSB Y MSB Z LSB Z MSB The quarternion data is WXYZ W LSB W MSB X LSB X MSB Y LSB Y MSB Z LSB Z MSB */ // m/s^2 by default ACCEL: 0x08, // X LSB // ? by default MAG: 0x0E, // X LSB // dps by default GYRO: 0x14, // X LSB //euler angles - degrees EULER: 0x1A, // heading LSB //quarternion QUARTERNION: 0x20, // W LSB // °C by default TEMP: 0x34, }, LENGTH: { ACCEL: 6, MAG: 6, GYRO: 6, EULER: 6, QUARTERNION: 8, TEMP: 1, }, OPR_MODE_ADDR: 0x3D, OPR_MODES: { CONFIG: 0x00, ACCONLY: 0x01, MAGONLY: 0x02, GYRONLY: 0x03, ACCMAG: 0x04, ACCGYRO: 0x05, MAGGYRO: 0x06, AMG: 0x07, IMUPLUS: 0x08, COMPASS: 0x09, M4G: 0x0A, NDOF_FMC_OFF: 0x0B, NDOF: 0x0C, }, PWR_MODE_ADDR: 0x3E, PWR_MODES: { NORMAL: 0x00, LOW: 0x01, SUSPEND: 0x02, }, PAGE_ID_ADDR: 0x07, PAGE_STATES: { ZERO: 0x00, }, CALIBRATION: 0x35, SYS_TRIGGER: 0x3F, UNIT_SEL_ADDR: 0x3B, AXIS_MAP_CONFIG_ADDR: 0x41, AXIS_MAP_SIGN_ADDR: 0x42, } }, initialize: { value(board, options) { const io = board.io; // Page 67 4.3.54 // a value for what we use to consider the system calibrated, // 0xC0 represents the just fusion algorithm/system const calibrationMask = options.calibrationMask || 0xC0; const address = Drivers.addressResolver(this, options); const computed = { accelerometer: { x: null, y: null, z: null, }, gyro: { x: null, y: null, z: null, }, magnetometer: { x: null, y: null, z: null, }, orientation: { euler: { heading: null, roll: null, pitch: null, }, quarternion: { w: null, x: null, y: null, z: null, }, }, temperature: null, calibration: null, }; io.i2cConfig(options); // Put chip into CONFIG operation mode io.i2cWriteReg(address, this.REGISTER.OPR_MODE_ADDR, this.REGISTER.OPR_MODES.CONFIG); // Set register page to 0 io.i2cWriteReg(address, this.REGISTER.PAGE_ID_ADDR, this.REGISTER.PAGE_STATES.ZERO); // Page 70, 4.3.63 SYS_TRIGGER // // RST_SYS (Set to reset system) // // B7 B6 B5 B4 B3 B2 B1 B0 // 0 0 1 0 0 0 0 0 // io.i2cWriteReg(address, this.REGISTER.SYS_TRIGGER, 0x20); const por = new Promise(resolve => { setTimeout(() => { // Normal power mode io.i2cWriteReg(address, this.REGISTER.PWR_MODE_ADDR, this.REGISTER.PWR_MODES.NORMAL); // Page 70, 4.3.63 SYS_TRIGGER // // CLK_SEL: // // B7 B6 B5 B4 B3 B2 B1 B0 // 0 0 0 0 0 0 0 0 // //io.i2cWriteReg(address, this.REGISTER.SYS_TRIGGER, 0x00); // do we want to enable an external crystal?? io.i2cWriteReg(address, this.REGISTER.SYS_TRIGGER, options.enableExternalCrystal ? 0x80 : 0x00); // AF Page 24 3.4, Axis remap // // AXIS_MAP_CONFIG: // // B7 B6 B5 B4 B3 B2 B1 B0 // 0 0 0 0 0 0 0 0 // - - z z y y x x // // x axis = 00, y axis = 01, z axis = 10 // // see also the defaults starting on Page 50 // const axisMap = options.axisMap || 0x24; io.i2cWriteReg(address, this.REGISTER.AXIS_MAP_CONFIG_ADDR, axisMap); // AF Page 24 3.4, Axis remap // // AXIS_MAP_CONFIG: // // B7 B6 B5 B4 B3 B2 B1 B0 // 0 0 0 0 0 0 0 0 // - - - - - x y z // // 0 = positive, 1 = negative // const axisSign = options.axisSign || 0x00; io.i2cWriteReg(address, this.REGISTER.AXIS_MAP_SIGN_ADDR, axisSign); // Set operational mode to "nine degrees of freedom" setTimeout(() => { io.i2cWriteReg(address, this.REGISTER.OPR_MODE_ADDR, this.REGISTER.OPR_MODES.NDOF); resolve(); }, 10); // Page 13, 1.2, OPERATING CONDITIONS BNO055 // From reset to config mode }, 650); }); por.then(() => new Promise(resolve => { const readCalibration = () => { io.i2cReadOnce(address, this.REGISTER.CALIBRATION, 1, data => { const calibration = data[0]; const didCalibrationChange = computed.calibration !== calibration; computed.calibration = calibration; // It is useful, possibly to know when the calibration state changes // some of the calibrations are a little picky to get right, so emitting // the calibration state as it changes is useful. if (didCalibrationChange) { this.emit("calibration", computed.calibration); } if ((calibration & calibrationMask) === calibrationMask) { // Emit the calibration state so we can work out in our userspace if // we are good to go, and for when we are performing the calibration steps // let everyone know we are calibrated. this.emit("calibrated"); resolve(); } else { readCalibration(); } }); }; readCalibration(); })).then(() => { // Temperature requires no calibration, begin reading immediately // here we read out temp, and the calibration state since they are back to back // and the device can, has been observed to go out of calibration and we may want to check io.i2cRead(address, this.REGISTER.READ.TEMP, 2, data => { computed.temperature = data[0]; const didCalibrationChange = computed.calibration !== data[1]; computed.calibration = data[1]; this.emit("data", computed); if (didCalibrationChange) { this.emit("calibration", computed.calibration); } }); // ACCEL, MAG and GYRO are 6 bytes each => 18 bytes total io.i2cRead(address, this.REGISTER.READ.ACCEL, 18, data => { computed.accelerometer = { x: int16(data[1], data[0]), y: int16(data[3], data[2]), z: int16(data[5], data[4]) }; computed.magnetometer = { x: int16(data[7], data[6]), y: int16(data[9], data[8]), z: int16(data[11], data[10]) }; computed.gyro = { x: int16(data[13], data[12]), y: int16(data[15], data[14]), z: int16(data[17], data[16]) }; this.emit("data", computed); }); // Moved the ndof/quarternions to their own read.. bytes go missing, lots of 32 byte buffers everywhere io.i2cRead(address, this.REGISTER.READ.EULER, 14, data => { // raw euler computed.orientation.euler = { heading: int16(data[1], data[0]), roll: int16(data[3], data[2]), pitch: int16(data[5], data[4]) }; // scaled quarternion - unitless computed.orientation.quarternion = { w: int16(data[7], data[6]), x: int16(data[9], data[8]), y: int16(data[11], data[10]), z: int16(data[13], data[12]) }; this.emit("data", computed); }); }); }, }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.BNO055, options); return `bno055-${address}`; } } }, MPL115A2: { ADDRESSES: { value: [0x60] }, REGISTER: { value: { // Page 5 // Table 2. Device Memory Map COEFFICIENTS: 0x04, PADC_MSB: 0x00, CONVERT: 0x12, } }, initialize: { value(board, options) { const io = board.io; const address = Drivers.addressResolver(this, options); io.i2cConfig(options); const computed = { pressure: null, temperature: null, }; const cof = { a0: null, b1: null, b2: null, c12: null }; const handler = data => { // Page 5 // 3.1 Pressure, Temperature and Coefficient Bit-Width Specifications const Padc = uint16(data[0], data[1]) >> 6; const Tadc = uint16(data[2], data[3]) >> 6; // Page 6 // 3.2 Compensation computed.pressure = cof.a0 + (cof.b1 + cof.c12 * Tadc) * Padc + cof.b2 * Tadc; computed.temperature = Tadc; this.emit("data", computed); readCycle(); }; var readCycle = () => { io.i2cWriteReg(address, this.REGISTER.CONVERT, 0x00); // Page 5 // Table 2. Device Memory Map // Starting from PADC_MSB, read 4 bytes: // // Padc_MSB // Padc_LSB // Tadc_MSB // Tadc_LSB // io.i2cReadOnce(address, this.REGISTER.PADC_MSB, 4, handler); // TODO: User specified "frequency" needs to be applied here. }; const pCoefficients = new Promise(resolve => { io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 8, data => { const A0 = int16(data[0], data[1]); const B1 = int16(data[2], data[3]); const B2 = int16(data[4], data[5]); const C12 = int16(data[6], data[7]) >> 2; // Source: // https://github.com/adafruit/Adafruit_MPL115A2 // a0 is the pressure offset coefficient // b1 is the pressure sensitivity coefficient // b2 is the temperature coefficient of offset (TCO) // c12 is the temperature coefficient of sensitivity (TCS) cof.a0 = A0 / 8; cof.b1 = B1 / 8192; cof.b2 = B2 / 16384; cof.c12 = C12 / 4194304; resolve(); }); }); pCoefficients.then(readCycle); }, }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.MPL115A2, options); return `mpl115a2-${address}`; } } }, // Based off of the AdaFruit Arduino library for this chip // https://github.com/adafruit/Adafruit_MPL3115A2_Library MPL3115A2: { ADDRESSES: { value: [0x60] }, REGISTER: { // Page 18 // 13 Register descriptions value: { STATUS: 0x00, PRESSURE: 0x01, CONFIG: 0x13, BAR_IN_MSB: 0x14, BAR_IN_LSB: 0x15, CONTROL: 0x26, } }, MASK: { value: { STATUS: { PRESSURE_DATA_READ: 0x04 }, CONTROL: { SBYB: 0x01, OS128: 0x38, ALTIMETER: 0x80, PRESSURE: 0x00 }, CONFIG: { TDEFE: 0x01, PDEFE: 0x02, DREM: 0x04 } } }, initialize: { value(board, options) { const READLENGTH = 6; const io = board.io; let isPressure = false; let elevation = null; let offset = 0; const address = Drivers.addressResolver(this, options); // See http://www.henrylahr.com/?p=99 for implementation approach // let altNow = 0; const computed = { pressure: 0, altitude: 0, temperature: 0 }; if (typeof options.elevation !== "undefined") { elevation = options.elevation; } if (elevation !== null && elevation <= 0) { offset = Math.abs(elevation) + 1; elevation = 1; } const waitForReady = next => { io.i2cReadOnce(address, this.REGISTER.STATUS, 1, data => { if (data[0] & this.MASK.STATUS.PRESSURE_DATA_READ) { next(); } else { setTimeout(() => { waitForReady(next); }, 100); } }); }; const readValues = () => { const modeMask = isPressure ? this.MASK.CONTROL.PRESSURE : this.MASK.CONTROL.ALTIMETER; const mode = this.MASK.CONTROL.SBYB | this.MASK.CONTROL.OS128 | modeMask; io.i2cWrite(address, this.REGISTER.CONTROL, mode); waitForReady(() => { io.i2cReadOnce(address, this.REGISTER.PRESSURE, READLENGTH, data => { const value = uint24(data[1], data[2], data[3]) >> 4; const temperature = uint16(data[4], data[5]) >> 4; let altVal; computed.temperature = temperature; if (isPressure) { computed.pressure = value; this.emit("data", computed); } else { const m = data[1]; const c = data[2]; const l = data[3]; const fl = (l >> 4) / 16; altVal = (m << 8 | c) + fl; altNow = (altNow * 3 + altVal) / 4; computed.altitude = altNow - offset; } isPressure = !isPressure; readValues(); }); }); }; const reads = []; const calibrate = () => { // Clear Oversampling and OST io.i2cWrite(address, this.REGISTER.CONTROL, 0x3B); io.i2cWrite(address, this.REGISTER.CONTROL, 0x39); setTimeout(() => { io.i2cReadOnce(address, this.REGISTER.PRESSURE, READLENGTH, data => { const m = data[1]; const c = data[2]; const l = data[3]; const fl = (l >> 4) / 4; reads.push((m << 10 | c << 2) + fl); if (reads.length === 4) { const curpress = (reads[0] + reads[1] + reads[2] + reads[3]) / 4; const seapress = curpress / ((1 - elevation * 0.0000225577) ** 5.255); // Update Barometric input for Altitude io.i2cWrite(address, this.REGISTER.BAR_IN_MSB, (seapress / 2) >> 8); io.i2cWrite(address, this.REGISTER.BAR_IN_LSB, (seapress / 2) & 0xFF); // Get into Altitude mode // One shot & OST bit io.i2cWrite(address, this.REGISTER.CONTROL, 0xBB); io.i2cWrite(address, this.REGISTER.CONTROL, 0xB9); setTimeout(() => { io.i2cReadOnce(address, this.REGISTER.PRESSURE, READLENGTH, data => { const m = data[1]; const c = data[2]; const l = data[3]; const fl = (l >> 4) / 16; altNow = (m << 8 | c) + fl; readValues(false); }); }, 550); } else { calibrate(); } }); }, 500); }; io.i2cConfig( Object.assign(options, { settings: { stopTX: true } }) ); // configure the chip // Set Altitude Offset. io.i2cWriteReg(address, 0x2D, 0x00); io.i2cWriteReg(address, this.REGISTER.BAR_IN_MSB, 0); io.i2cWriteReg(address, this.REGISTER.BAR_IN_LSB, 0); io.i2cWriteReg(address, this.REGISTER.CONFIG, this.MASK.CONFIG.TDEFE | this.MASK.CONFIG.PDEFE | this.MASK.CONFIG.DREM); if (elevation !== null) { calibrate(); } else { readValues(); } } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.MPL3115A2, options); return `mpl3115a2-${address}`; } } }, BMP180: { ADDRESSES: { value: [0x77] }, REGISTER: { value: { COEFFICIENTS: 0xAA, READ: 0x00, READ_START: 0xF4, READ_RESULT: 0xF6, } }, initialize: { value(board, options) { const io = board.io; let elevation = null; let offset = 0; if (typeof options.elevation !== "undefined") { elevation = options.elevation; } if ((elevation != null && elevation <= 0) || elevation == null) { offset = Math.abs(elevation) + 1; elevation = 1; } const address = Drivers.addressResolver(this, options); /** * Table 1: Operating conditions, output signal and mechanical characteristics * * Pressure Conversion Delay (ms) * * [ * 5, LOW * 8, STANDARD * 14, HIGH * 26, ULTRA * ] * * These numbers are derived from rounding the Max column of * Table 1, for the Conversion Time entries. */ const mode = options.mode || 3; const kpDelay = [5, 8, 14, 26][mode]; const oss = Fn.constrain(mode, 0, 3); const cof = { a1: null, a2: null, a3: null, a4: null, a5: null, a6: null, b1: null, b2: null, b5: null, mb: null, mc: null, md: null, }; io.i2cConfig(options); const pCoefficients = new Promise(resolve => { io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 22, data => { // BMP085 // Page 12 // 3.4 Calibration Coefficients // // BMP180 // Page 13 // 3.4 Calibration Coefficients // cof.a1 = int16(data[0], data[1]); cof.a2 = int16(data[2], data[3]); cof.a3 = int16(data[4], data[5]); cof.a4 = uint16(data[6], data[7]); cof.a5 = uint16(data[8], data[9]); cof.a6 = uint16(data[10], data[11]); cof.b1 = int16(data[12], data[13]); cof.b2 = int16(data[14], data[15]); cof.mb = int16(data[16], data[17]); cof.mc = int16(data[18], data[19]); cof.md = int16(data[20], data[21]); resolve(); }); }); pCoefficients.then(() => { // BMP085 // Pages 10, 11 // 3.3 Measurement of pressure and temperature // Pages 12, 13, 14 // 3.5 Calculating pressure and temperature // // BMP180 // Pages 11, 12 // 3.3 Measurement of pressure and temperature // Pages 13, 14, 15, 16 // 3.5 Calculating pressure and temperature // const computed = { altitude: null, pressure: null, temperature: null, }; let cycle = 0; // BMP180 // Pages 11, 15 // 3.3 Measurement of pressure and temperature // 3.5 Calculating pressure and temperature const readCycle = () => { // cycle 0: temperature // cycle 1: pressure const isTemperatureCycle = cycle === 0; const component = isTemperatureCycle ? 0x2E : 0x34 + (oss << 6); const numBytes = isTemperatureCycle ? 2 : 3; const delay = isTemperatureCycle ? 5 : kpDelay; io.i2cWriteReg(address, this.REGISTER.READ_START, component); // Once the READ_START register is set, // delay the READ_RESULT request based on the // mode value provided by the user, or default. setTimeout(() => { io.i2cReadOnce(address, this.REGISTER.READ_RESULT, numBytes, data => { let compensated; let uncompensated; let x1; let x2; let x3; let b3; let b4; let b6; let b7; let b6s; let bx; if (isTemperatureCycle) { // TEMPERATURE uncompensated = int16(data[0], data[1]); // Compute the true temperature x1 = ((uncompensated - cof.a6) * cof.a5) >> 15; x2 = ((cof.mc << 11) / (x1 + cof.md)) >> 0; // Compute b5, which is used by the pressure cycle cof.b5 = (x1 + x2) | 0; // Steps of 0.1°C computed.temperature = ((cof.b5 + 8) >> 4) / 10; } else { // PRESSURE uncompensated = uint24(data[0], data[1], data[2]) >> (8 - oss); b6 = cof.b5 - 4000; b6s = b6 * b6; bx = b6s >> 12; // Intermediary x1 & x2 to calculate x3 for b3 x1 = (cof.b2 * bx) >> 11; x2 = (cof.a2 * b6) >> 11; x3 = x1 + x2; b3 = ((((cof.a1 * 4 + x3) << oss) + 2) / 4) >> 0; // Intermediary x1 & x2 to calculate x3 for b4 x1 = (cof.a3 * b6) >> 13; x2 = (cof.b1 * bx) >> 16; x3 = ((x1 + x2) + 2) >> 2; b4 = (cof.a4 * (x3 + 32768)) >> 15; b7 = (uncompensated - b3) * (50000 >> oss); if (b7 < Fn.POW_2_31) { compensated = (b7 * 2) / b4; } else { compensated = (b7 / b4) * 2; } compensated >>= 0; x1 = (compensated >> 8) * (compensated >> 8); x1 = (x1 * 3038) >> 16; x2 = (-7357 * compensated) >> 16; compensated += (x1 + x2 + 3791) >> 4; // Steps of 1Pa (= 0.01hPa = 0.01mbar) (=> 0.001kPa) computed.pressure = compensated; // 3.7 Calculating pressure at sea level const seapress = compensated / ((1 - elevation * 0.0000225577) ** 5.255); const altitude = 44330 * (1 - compensated / seapress ** (1 / 5.255)); // Page 3 (of BMP280 Datasheet) // ...relative accuracy is ±0.12 hPa, which is equivalent to // ±1 m difference in altitude. computed.altitude = Math.round(altitude - offset); } if (++cycle === 2) { cycle = 0; this.emit("data", computed); } readCycle(); }); }, delay); }; // Kick off "read loop" // readCycle(); }); } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.BMP180, options); return `bmp180-${address}`; } } }, BMP280: { ADDRESSES: { value: [0x77] }, REGISTER: { value: { COEFFICIENTS: 0x88, CONFIG: 0xF5, MEASURE: 0xF4, // 0xF7, 0xF8, 0xF9 // MSB, LSB, XLSB PRESSURE: 0xF7, // 0xFA, 0xFB, 0xFC // MSB, LSB, XLSB TEMPERATURE: 0xFA, RESET: 0xE0, } }, initialize: { value(board, options) { const io = board.io; let elevation = null; let offset = 0; if (typeof options.elevation !== "undefined") { elevation = options.elevation; } if ((elevation != null && elevation <= 0) || elevation == null) { offset = Math.abs(elevation) + 1; elevation = 1; } const address = Drivers.addressResolver(this, options); const dig = { T1: null, T2: null, T3: null, P1: null, P2: null, P3: null, P4: null, P5: null, P6: null, P7: null, P8: null, P9: null, }; io.i2cConfig(options); // Page. 24 // 4.3.2 Register 0xE0 "reset" io.i2cWrite(address, this.REGISTER.RESET, 0xB6); const pCoefficients = new Promise(resolve => { io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 24, data => { // Page 21, Table 17 // Compensation parameter storage, naming and data type // These are received LSB FIRST // dig.T1 = uint16(data[1], data[0]); dig.T2 = int16(data[3], data[2]); dig.T3 = int16(data[5], data[4]); dig.P1 = uint16(data[7], data[6]); dig.P2 = int16(data[9], data[8]); dig.P3 = int16(data[11], data[10]); dig.P4 = int16(data[13], data[12]); dig.P5 = int16(data[15], data[14]); dig.P6 = int16(data[17], data[16]); dig.P7 = int16(data[19], data[18]); dig.P8 = int16(data[21], data[20]); dig.P9 = int16(data[23], data[22]); resolve(); }); }); pCoefficients.then(() => { /* CTRL_MEAS bits | DATA LSB | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | - | - | - | - | - | - | - | - | | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | */ io.i2cWrite(address, this.REGISTER.MEASURE, 0x3F); const computed = { altitude: null, pressure: null, temperature: null, }; // // Page 12 // 3.3.1 Pressure measurement // // Page 13 // 3.3.2 Temperature measurement // io.i2cRead(address, this.REGISTER.PRESSURE, 6, data => { let compensated = 0; // Page 45 // "Returns temperature in DegC, double precision. Output value of // '51.23' equals 51.23 DegC. t_fine carries fine temperature as global value" let fine; // var1, var2 // // Expect: // // int32 // let v1; let v2; // Page 44 // "Both pressure and temperature values are expected to be // received in 20 bit format, positive, stored in a 32 bit signed integer. " // // V = int32(uint24(m, l, xl)) // V >> 4; // // Page 45 let P = s32(uint24(data[0], data[1], data[2])); let T = s32(uint24(data[3], data[4], data[5])); P >>= 4; T >>= 4; // TEMPERATURE // Page 45 // bmp280_compensate_T_int32 // var1 = ((((adc_T>>3) – ((BMP280_S32_t)dig_T1<<1))) * // ((BMP280_S32_t)dig_T2)) >> 11; // var2 = (((((adc_T>>4) – ((BMP280_S32_t)dig_T1)) * // ((adc_T>>4) – ((BMP280_S32_t)dig_T1))) >> 12) * // ((BMP280_S32_t)dig_T3)) >> 14; // // const adc16 = T >> 4; const adc16subT1 = adc16 - dig.T1; v1 = (((T >> 3) - (dig.T1 << 1)) * dig.T2) >> 11; v2 = (((adc16subT1 * adc16subT1) >> 12) * dig.T3) >> 14; // t_fine = var1 + var2; fine = v1 + v2; // Page 7, 8 // Table 2: Parameter specification // // // Temperature 0.01 °C // // As toFixed(2) // // C = +(((t_fine * 5 + 128) >> 8) / 100).toFixed(resolution) // computed.temperature = ((fine * 5 + 128) >> 8) / 100; v1 = undefined; v2 = undefined; // PRESSURE // Page 46 // bmp280_compensate_P_int32 // // Every single seemingly arbitrary magic number comes from the datasheet. // Datasheets are evidently written by people that don't care about // anyone else actually understanding how a thing works. // // var1 = (((BMP280_S32_t)t_fine)>>1) – (BMP280_S32_t)64000; v1 = s32(fine >> 1) - 64000; // var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((BMP280_S32_t)dig_P6); v2 = (((v1 >> 2) * (v1 >> 2)) >> 11) * s32(dig.P6); // var2 = var2 + ((var1*((BMP280_S32_t)dig_P5))<<1); v2 += (v1 * s32(dig.P5)) << 1; // var2 = (var2>>2)+(((BMP280_S32_t)dig_P4)<<16); v2 = (v2 >> 2) + (s32(dig.P4) << 16); // var1 = (((dig_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + // ((((BMP280_S32_t)dig_P2) * var1)>>1))>>18; v1 = (((dig.P3 * (((v1 >> 2) * (v1 >> 2)) >> 13)) >> 3) + ((s32(dig.P2) * v1) >> 1)) >> 18; // var1 =((((32768+var1))*((BMP280_S32_t)dig_P1))>>15); v1 = (((Fn.POW_2_15 + v1) * s32(dig.P1)) >> 15); if (v1 === 0) { // Prevent division by zero return 0; } // p = (((BMP280_U32_t)(((BMP280_S32_t)1048576)-adc_P)-(var2>>12)))*3125; compensated = u32((s32(Fn.POW_2_20) - P) - (v2 >> 12)) * 3125; if (compensated < Fn.POW_2_31) { // p = (p << 1) / ((BMP280_U32_t)var1); compensated = ((compensated << 1) >>> 0) / u32(v1); } else { // p = (p / (BMP280_U32_t)var1) * 2; compensated = ((compensated / u32(v1)) >>> 0) * 2; } compensated = u32(compensated) >>> 0; // var1 = (((BMP280_S32_t)dig_P9) * ((BMP280_S32_t)(((p>>3) * (p>>3))>>13)))>>12; const compshift3r = compensated >> 3; v1 = (s32(dig.P9) * s32(((compshift3r * compshift3r) >> 13))) >> 12; // var2 = (((BMP280_S32_t)(p>>2)) * ((BMP280_S32_t)dig_P8))>>13; v2 = (s32(compensated >> 2) * s32(dig.P8)) >> 13; // p = (BMP280_U32_t)((BMP280_S32_t)p + ((var1 + var2 + dig_P7) >> 4)); compensated = u32(s32(compensated) + ((v1 + v2 + dig.P7) >> 4)); // Steps of 1Pa (= 0.01hPa = 0.01mbar) (=> 0.001kPa) computed.pressure = compensated; // Calculating pressure at sea level (copied from BMP180) const seapress = compensated / ((1 - elevation * 0.0000225577) ** 5.255); const altitude = 44330 * (1 - compensated / seapress ** (1 / 5.255)); // Page 3 // ...relative accuracy is ±0.12 hPa, which is equivalent to // ±1 m difference in altitude. computed.altitude = Math.round(altitude - offset); this.emit("data", computed); }); }); } }, identifier: { value(options) { const address = Drivers.addressResolver(Drivers.BMP280, options); return `bmp280-${address}`; } } }, BME280: { ADDRESSES: { value: [0x77] }, REGISTER: { value: { COEFFICIENTS_TP: 0x88, COEFFICIENTS_H: 0xE1, CONFIG: 0xF5, MEASURE_H: 0xF2, MEASURE_TP: 0xF4, PRESSURE: 0xF7, // 0xF7, 0xF8, 0xF9 // MSB, LSB, XLSB TEMPERATURE: 0xFA, // 0xFA, 0xFB, 0xFC // MSB, LSB, XLSB HUMIDITY: 0xFD, // 0xFD, 0xFE // MSB, LSB RESET: 0xE0, } }, initialize: { value(board, options) { const io = board.io; let elevation = null; let offset = 0; if (typeof options.elevation !== "undefined") { elevation = options.elevation; } if ((elevation != null && elevation <= 0) || elevation == null) { offset = Math.abs(elevation) + 1; elevation = 1; } const address = Drivers.addressResolver(this, options); const dig = { T1: null, T2: null, T3: null, P1: null, P2: null, P3: null, P4: null, P5: null, P6: null, P7: null, P8: null, P9: null, H1: null, H2: null, H3: null, H4: null, H5: null, H6: null, }; io.i2cConfig(options); // Page. 24 // 4.3.2 Register 0xE0 "reset" io.i2cWrite(address, this.REGISTER.RESET, 0xB6); const pCoefficients = new Promise(resolveCoeffs => { // Page 22, // Table 16: Compensation parameter storage, naming and data type // These are received LSB FIRST // // The H register is not contiguous! Promise.all([ new Promise(resolve => { io.i2cReadOnce(address, 0x88, 24, data => { dig.T1 = uint16(data[1], data[0]); dig.T2 = int16(data[3], data[2]); dig.T3 = int16(data[5], data[4]); dig.P1 = uint16(data[7], data[6]); dig.P2 = int16(data[9], data[8]); dig.P3 = int16(data[11], data[10]); dig.P4 = int16(data[13], data[12]); dig.P5 = int16(data[15], data[14]); dig.P6 = int16(data[17], data[16]); dig.P7 = int16(data[19], data[18]); dig.P8 = s32(int16(data[21], data[20])); dig.P9 = s32(int16(data[23], data[22])); resolve(); }); }), new Promise(resolve => { io.i2cReadOnce(address, 0xA1, 1, data => { dig.H1 = Fn.u8(data[0]); resolve(); }); }), new Promise(resolve => { io.i2cReadOnce(address, 0xE1, 8, data => { /* 0xE1 => data[0] 0xE2 => data[1] 0xE3 => data[2] 0xE4 => data[3] 0xE5 => data[4] 0xE6 => data[5] 0xE7 => data[6] */ // 0xE2 0xE1 // H2 [15:8] [7:0] dig.H2 = s32(int16(data[1], data[0])); // 0xE3 dig.H3 = s32(data[2]); // Special Bit arrangements for H4 & H5 // // 0xE5 0xE4 // H4 [3:0] [11:4] signed short // 0xE6 0xE5 // H5 [11:4] [3:0] signed short dig.H4 = s32((data[3] << 4) | (data[4] & 0xF)); dig.H5 = s32((data[5] << 4) | (data[4] >> 4)); // 0xE7 dig.H6 = Fn.s8(data[6]); resolve(); }); }) ]).then(resolveCoeffs); }); pCoefficients.then(() => { /* Table 19: Register 0xF2 "ctrl_hum" Bit 2, 1, 0 Controls oversampling of humidity osrs_h[2:0] Humidity oversampling 000 Skipped (output set to 0x8000) 001 oversampling ×1 010 oversampling ×2 011 oversampling ×4 100 oversampling ×8 101, others oversampling ×16 | | | HUM | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | - | - | - | - | - | - | - | - | | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | */ io.i2cWrite(address, this.REGISTER.MEASURE_H, 0x05); /* Table 22: Register 0xF4 "ctrl_meas" Bit 7, 6, 5 Controls oversampling of temperature data Bit 4, 3, 2 Controls oversampling of pressure data Bit 1, 0 Controls the sensor mode of the device osrs_h[2:0] Humidity oversampling 000 Skipped (output set to 0x8000) 001 oversampling ×1 010 oversampling ×2 011 oversampling ×4 100 oversampling ×8 101, others oversampling ×16 000 Skipped (output set to 0x80000) 001 oversampling ×1 010 oversampling ×2 011 oversampling ×4 100 oversampling ×8 101, others oversampling ×16 00 Sleep mode 01 and 10 Forced mode 11 Normal mode | TEMP | PRES | Mode | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | - | - | - | - | - | - | - | - | | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | */ io.i2cWrite(address, this.REGISTER.MEASURE_TP, 0xB7); const computed = { altitude: null, pressure: null, humidity: null, temperature: null, }; // // Page 12 // 3.3.1 Pressure measurement // // Page 13 // 3.3.2 Temperature measurement // const standby = Date.now(); io.i2cRead(address, this.REGISTER.PRESSURE, 8, data => { // // Response time to complete 63% of a step is 1 second. // Don't emit a reading until a complete step has occurred. // This will be ~1587ms // (1 / 63 * 100) * 1000 = 1587.3015873015872ms // if ((standby + 1587) > Date.now()) { if (!process.env.IS_TEST_MODE) { if ((standby + 1000) > Date.now()) { return; } } let compensated = 0; // Page 45 // "Returns temperature in DegC, double precision. Output value of // '51.23' equals 51.23 DegC. t_fine carries fine temperature as global value" let fine; // var1, var2 // // Expect: // // int32 // let v1; let v2; let vx; // Page 50 // "Both pressure and temperature values are expected to be // received in 20 bit format, positive, stored in a 32 bit signed integer. " // // V = int32(uint24(m, l, xl)) // V >> 4; // // Page 50 let P = s32(uint24(data[0], data[1], data[2])); let T = s32(uint24(data[3], data[4], data[5])); const H = s32(uint16(data[6], data[7])); P >>= 4; T