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,701 lines (1,429 loc) 78.5 kB
var Board = require("./board"); var Emitter = require("events").EventEmitter; var util = require("util"); var Fn = require("./fn"); var int16 = Fn.int16; var uint16 = Fn.uint16; var uint24 = Fn.uint24; var priv = new Map(); var activeDrivers = new Map(); // TODO: make real const var ACCELEROMETER = "accelerometer"; var ALTIMETER = "altimeter"; var BAROMETER = "barometer"; var GYRO = "gyro"; var HYGROMETER = "hygrometer"; var MAGNETOMETER = "magnetometer"; var ORIENTATION = "orientation"; var THERMOMETER = "thermometer"; function Components(controller, options) { var state = priv.get(this); var descriptors = Object.create(null); this.components.forEach(function(component) { // 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: function() { return state[component]; } }; 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"); var backwardCompatibilityGarbageHacks = { thermometer: "temperature", }; var Drivers = { // https://cdn-shop.adafruit.com/product-files/2857/Sensirion_Humidity_SHT3x_Datasheet_digital-767294.pdf SHT31D: { ADDRESSES: { value: [0x44] }, REGISTER: { value: { // Values are in 16-bit form // to coincide with datasheet // // Table 13 SOFT_RESET: 0x30A2, // Table 8 MEASURE_HIGH_REPEATABILITY: 0x2400, } }, initialize: { value: function(board, opts) { var READLENGTH = 6; var io = board.io; var address = opts.address || this.ADDRESSES[0]; opts.address = address; io.i2cConfig(opts); // Soft Reset io.i2cWrite(address, [ // Page 12 // Table 13 // See diagram this.REGISTER.SOFT_RESET >> 8, this.REGISTER.SOFT_RESET & 0xFF, ]); // Page 10 // Table 8 // Send high repeatability measurement command io.i2cWrite(address, [ this.REGISTER.MEASURE_HIGH_REPEATABILITY >> 8, this.REGISTER.MEASURE_HIGH_REPEATABILITY & 0xFF, ]); var computed = { temperature: null, humidity: null, }; // temp msb, temp lsb, temp CRC, humidity msb, humidity lsb, humidity CRC io.i2cRead(address, READLENGTH, function(data) { computed.temperature = int16(data[0], data[1]); computed.humidity = int16(data[3], data[4]); this.emit("data", computed); }.bind(this)); } }, identifier: { value: function(opts) { var address = opts.address || Drivers.SHT31D.ADDRESSES.value[0]; return "sht-31d-" + address; } } }, // https://www.adafruit.com/datasheets/1899_HTU21D.pdf HTU21D: { ADDRESSES: { value: [0x40] }, REGISTER: { value: { HUMIDITY: 0xE5, TEMPERATURE: 0xE3, SOFT_RESET: 0xFE, } }, initialize: { value: function(board, opts) { var io = board.io; var address = opts.address || this.ADDRESSES[0]; opts.address = address; // 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 opts.delay = 50000; io.i2cConfig(opts); io.i2cWrite(address, this.REGISTER.SOFT_RESET); var computed = { temperature: null, humidity: null, }; var cycle = 0; var readCycle = function() { // 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. var isTemperatureCycle = cycle === 0; var register = isTemperatureCycle ? this.REGISTER.TEMPERATURE : this.REGISTER.HUMIDITY; io.i2cReadOnce(address, register, 2, function(data) { if (isTemperatureCycle) { computed.temperature = int16(data[0], data[1]); } else { computed.humidity = int16(data[0], data[1]); } if (++cycle === 2) { cycle = 0; this.emit("data", computed); } readCycle(); }.bind(this)); }.bind(this); readCycle(); } }, identifier: { value: function(opts) { var address = opts.address || Drivers.HTU21D.ADDRESSES.value[0]; return "htu-s1d-" + address; } } }, // http://www.phanderson.com/arduino/I2CCommunications.pdf // http://www.phanderson.com/arduino/CommandModeInstructions.pdf // http://cdn.sparkfun.com/datasheets/Prototyping/1443945.pdf HIH6130: { ADDRESSES: { value: [0x27] }, initialize: { value: function(board, opts) { var io = board.io; var address = opts.address || this.ADDRESSES[0]; opts.address = address; io.i2cConfig(opts); var computed = { humidity: null, temperature: null, }; var delay = 36.65; var measureCycle = function() { // 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(function() { io.i2cWrite(address, 0x80, [0x00, 0x00]); io.i2cReadOnce(address, 4, function(data) { // Page 2 // Figure 4. Humidity and Temperature Data Fetch, Four Byte Data Read // B7:6 Contain status bits var 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(); }.bind(this)); // Page 3 // 3.0 Measurement Cycle // The measurement cycle duration is typically // 36.65 ms for temperature and humidity readings. }.bind(this), delay); }.bind(this); measureCycle(); } }, identifier: { value: function(opts) { var address = opts.address || Drivers.HIH6130.ADDRESSES.value[0]; return "hih6130-" + address; } } }, DHT_I2C_NANO_BACKPACK: { ADDRESSES: { value: [0x0A] }, REGISTER: { value: { READ: 0x00, } }, initialize: { value: function(board, opts) { var io = board.io; var address = opts.address || this.ADDRESSES[0]; // Correspond to firmware variables var dhtPin = 2; var dhtType = 11; opts.address = address; io.i2cConfig(opts); var dhtVariantMatch = /(\d{2})/.exec(opts.controller)[0]; if (dhtVariantMatch) { dhtType = +dhtVariantMatch; if (Number.isNaN(dhtType)) { dhtType = 11; } } var computed = { temperature: null, humidity: null, }; io.i2cWrite(address, [dhtPin, dhtType]); io.i2cRead(address, 4, function(data) { computed.humidity = int16(data[0], data[1]); computed.temperature = int16(data[2], data[3]); this.emit("data", computed); }.bind(this)); } }, identifier: { value: function(opts) { var address = opts.address || Drivers.DHT_I2C_NANO_BACKPACK.ADDRESSES.value[0]; return "dht_i2c_nano_backpack-" + address; } } }, // Based on the example code from // http://playground.arduino.cc/Main/MPU-6050 // http://www.invensense.com/mems/gyro/mpu6050.html MPU6050: { ADDRESSES: { value: [0x68, 0x69] }, REGISTER: { value: { SETUP: [0x6B, 0x00], // += 250 READ: 0x3B } }, initialize: { value: function(board, opts) { var READLENGTH = 14; var io = board.io; var address = opts.address || this.ADDRESSES[0]; opts.address = address; var computed = { accelerometer: {}, temperature: {}, gyro: {} }; io.i2cConfig(opts); io.i2cWrite(address, this.REGISTER.SETUP); io.i2cRead(address, this.REGISTER.READ, READLENGTH, function(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); }.bind(this)); }, }, identifier: { value: function(opts) { var address = opts.address || Drivers.MPU6050.ADDRESSES.value[0]; 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: function(board, opts) { // Datasheet references: // // QS: https://ae-bst.resource.bosch.com/media/products/dokumente/bno055/BST-BNO055-AN007-00_Quick_Start_Guide.pdf // // AF: https://www.adafruit.com/datasheets/BST_BNO055_DS000_12.pdf // var io = board.io; var address = opts.address || this.ADDRESSES[0]; // AF. Page 67 4.3.54 //a value for what we use to consider the system calibrated, 0xC0 represents the just fusion algorithm/system var calibrationMask = opts.calibrationMask || 0xC0; opts.address = address; var 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(opts); // 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); // AF 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); var por = new Promise(function(resolve) { setTimeout(function() { // Normal power mode io.i2cWriteReg(address, this.REGISTER.PWR_MODE_ADDR, this.REGISTER.PWR_MODES.NORMAL); // AF 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, opts.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 AF Page 50 // var axisMap = opts.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 // var axisSign = opts.axisSign || 0x00; io.i2cWriteReg(address, this.REGISTER.AXIS_MAP_SIGN_ADDR, axisSign); // Set operational mode to "nine degrees of freedom" setTimeout(function() { io.i2cWriteReg(address, this.REGISTER.OPR_MODE_ADDR, this.REGISTER.OPR_MODES.NDOF); resolve(); }.bind(this), 10); // OPERATING CONDITIONS BNO055 // AF Page 13, 1.2, OPERATING CONDITIONS BNO055 // From reset to config mode }.bind(this), 650); }.bind(this)); por.then(function() { return new Promise(function(resolve) { var readCalibration = function() { io.i2cReadOnce(address, this.REGISTER.CALIBRATION, 1, function(data) { var calibration = data[0]; var 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. // grab the calibration 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(); } }.bind(this)); }.bind(this); readCalibration(); }.bind(this)); }.bind(this)).then(function() { // 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, function(data) { computed.temperature = data[0]; var didCalibrationChange = computed.calibration !== data[1]; computed.calibration = data[1]; this.emit("data", computed); if (didCalibrationChange) { this.emit("calibration", computed.calibration); } }.bind(this)); // ACCEL, MAG and GYRO are 6 bytes each => 18 bytes total io.i2cRead(address, this.REGISTER.READ.ACCEL, 18, function(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); }.bind(this)); // 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, function(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); }.bind(this)); }.bind(this)); }, }, identifier: { value: function(opts) { var address = opts.address || Drivers.BNO055.ADDRESSES.value[0]; 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: function(board, opts) { /* Datasheet Reference: http://cache.freescale.com/files/sensors/doc/data_sheet/MPL115A2.pdf */ var io = board.io; var address = opts.address || this.ADDRESSES[0]; opts.address = address; io.i2cConfig(opts); var computed = { pressure: null, temperature: null, }; var cof = { a0: null, b1: null, b2: null, c12: null }; var handler = function(data) { // Page 5 // 3.1 Pressure, Temperature and Coefficient Bit-Width Specifications var Padc = uint16(data[0], data[1]) >> 6; var 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(); }.bind(this); var readCycle = function() { 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. }.bind(this); var pCoefficients = new Promise(function(resolve) { io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 8, function(data) { var A0 = int16(data[0], data[1]); var B1 = int16(data[2], data[3]); var B2 = int16(data[4], data[5]); var 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(); }.bind(this)); }.bind(this)); pCoefficients.then(readCycle); }, }, identifier: { value: function(opts) { var address = opts.address || Drivers.MPL115A2.ADDRESSES.value[0]; 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: function(board, opts) { var READLENGTH = 6; var io = board.io; var address = opts.address || this.ADDRESSES[0]; var isPressure = false; var elevation = null; var offset = 0; opts.address = address; // See http://www.henrylahr.com/?p=99 for implementation approach // var altNow = 0; var computed = { pressure: 0, altitude: 0, temperature: 0 }; if (typeof opts.elevation !== "undefined") { elevation = opts.elevation; } if (elevation !== null && elevation <= 0) { offset = Math.abs(elevation) + 1; elevation = 1; } var waitForReady = function(next) { io.i2cReadOnce(address, this.REGISTER.STATUS, 1, function(data) { if (data[0] & this.MASK.STATUS.PRESSURE_DATA_READ) { next(); } else { setTimeout(function() { waitForReady(next); }, 100); } }.bind(this)); }.bind(this); var readValues = function() { var modeMask = isPressure ? this.MASK.CONTROL.PRESSURE : this.MASK.CONTROL.ALTIMETER; var mode = this.MASK.CONTROL.SBYB | this.MASK.CONTROL.OS128 | modeMask; io.i2cWrite(address, this.REGISTER.CONTROL, mode); waitForReady(function() { io.i2cReadOnce(address, this.REGISTER.PRESSURE, READLENGTH, function(data) { var value = uint24(data[1], data[2], data[3]) >> 4; var temperature = uint16(data[4], data[5]) >> 4; var altVal; computed.temperature = temperature; if (isPressure) { computed.pressure = value; this.emit("data", computed); } else { var m = data[1]; var c = data[2]; var l = data[3]; var fl = (l >> 4) / 16; altVal = (m << 8 | c) + fl; altNow = (altNow * 3 + altVal) / 4; computed.altitude = altNow - offset; } isPressure = !isPressure; readValues(); }.bind(this)); }.bind(this)); }.bind(this); var reads = []; var calibrate = function() { // Clear Oversampling and OST io.i2cWrite(address, this.REGISTER.CONTROL, 0x3B); io.i2cWrite(address, this.REGISTER.CONTROL, 0x39); setTimeout(function() { io.i2cReadOnce(address, this.REGISTER.PRESSURE, READLENGTH, function(data) { var m = data[1]; var c = data[2]; var l = data[3]; var fl = (l >> 4) / 4; reads.push((m << 10 | c << 2) + fl); if (reads.length === 4) { var curpress = (reads[0] + reads[1] + reads[2] + reads[3]) / 4; var seapress = curpress / Math.pow(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(function() { io.i2cReadOnce(address, this.REGISTER.PRESSURE, READLENGTH, function(data) { var m = data[1]; var c = data[2]; var l = data[3]; var fl = (l >> 4) / 16; altNow = (m << 8 | c) + fl; readValues(false); }); }.bind(this), 550); } else { calibrate(); } }.bind(this)); }.bind(this), 500); }.bind(this); io.i2cConfig( Object.assign(opts, { 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: function(opts) { var address = opts.address || Drivers.MPL3115A2.ADDRESSES.value[0]; return "mpl3115a2-" + address; } } }, BMP180: { ADDRESSES: { value: [0x77] }, REGISTER: { value: { COEFFICIENTS: 0xAA, READ: 0x00, READ_START: 0xF4, READ_RESULT: 0xF6, } }, initialize: { value: function(board, opts) { var io = board.io; var address = opts.address || this.ADDRESSES[0]; var elevation = null; var offset = 0; if (typeof opts.elevation !== "undefined") { elevation = opts.elevation; } if ((elevation != null && elevation <= 0) || elevation == null) { offset = Math.abs(elevation) + 1; elevation = 1; } opts.address = address; /** * https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf * 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. */ var mode = opts.mode || 3; var kpDelay = [5, 8, 14, 26][mode]; var oss = Fn.constrain(mode, 0, 3); var 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(opts); var pCoefficients = new Promise(function(resolve) { io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 22, function(data) { // https://www.sparkfun.com/datasheets/Components/General/BST-BMP085-DS000-05.pdf // Page 12 // 3.4 Calibration Coefficients // // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf // 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(); }); }.bind(this)); pCoefficients.then(function() { // https://www.sparkfun.com/datasheets/Components/General/BST-BMP085-DS000-05.pdf // Pages 10, 11 // 3.3 Measurement of pressure and temperature // Pages 12, 13, 14 // 3.5 Calculating pressure and temperature // // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf // Pages 11, 12 // 3.3 Measurement of pressure and temperature // Pages 13, 14, 15, 16 // 3.5 Calculating pressure and temperature // var computed = { altitude: null, pressure: null, temperature: null, }; var cycle = 0; // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf // Pages 11, 15 // 3.3 Measurement of pressure and temperature // 3.5 Calculating pressure and temperature var readCycle = function() { // cycle 0: temperature // cycle 1: pressure var isTemperatureCycle = cycle === 0; var component = isTemperatureCycle ? 0x2E : 0x34 + (oss << 6); var numBytes = isTemperatureCycle ? 2 : 3; var 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(function() { io.i2cReadOnce(address, this.REGISTER.READ_RESULT, numBytes, function(data) { var compensated, uncompensated; var x1, x2, x3, b3, b4, b6, b7, b6s, 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 var seapress = compensated / Math.pow(1 - elevation * 0.0000225577, 5.255); var altitude = 44330 * (1 - Math.pow(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(); }.bind(this)); }.bind(this), delay); }.bind(this); // Kick off "read loop" // readCycle(); }.bind(this)); } }, identifier: { value: function(opts) { var address = opts.address || Drivers.BMP180.ADDRESSES.value[0]; 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: function(board, opts) { var io = board.io; var address = opts.address || this.ADDRESSES[0]; var elevation = null; var offset = 0; if (typeof opts.elevation !== "undefined") { elevation = opts.elevation; } if ((elevation != null && elevation <= 0) || elevation == null) { offset = Math.abs(elevation) + 1; elevation = 1; } opts.address = address; /** * All page numbers refer to pages in: * https://cdn-shop.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf * */ var 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(opts); // Page. 24 // 4.3.2 Register 0xE0 "reset" io.i2cWrite(address, this.REGISTER.RESET, 0xB6); var pCoefficients = new Promise(function(resolve) { io.i2cReadOnce(address, this.REGISTER.COEFFICIENTS, 24, function(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(); }); }.bind(this)); pCoefficients.then(function() { /* 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); var 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, function(data) { var 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" var fine; // var1, var2 // // Expect: // // int32 // var v1, 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 var P = Fn.s32(uint24(data[0], data[1], data[2])); var T = Fn.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; // // var adc16 = T >> 4; var 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 = Fn.s32(fine >> 1) - 64000; // var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((BMP280_S32_t)dig_P6); v2 = (((v1 >> 2) * (v1 >> 2)) >> 11) * Fn.s32(dig.P6); // var2 = var2 + ((var1*((BMP280_S32_t)dig_P5))<<1); v2 += (v1 * Fn.s32(dig.P5)) << 1; // var2 = (var2>>2)+(((BMP280_S32_t)dig_P4)<<16); v2 = (v2 >> 2) + (Fn.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) + ((Fn.s32(dig.P2) * v1) >> 1)) >> 18; // var1 =((((32768+var1))*((BMP280_S32_t)dig_P1))>>15); v1 = (((Fn.POW_2_15 + v1) * Fn.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 = Fn.u32((Fn.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) / Fn.u32(v1); } else { // p = (p / (BMP280_U32_t)var1) * 2; compensated = ((compensated / Fn.u32(v1)) >>> 0) * 2; } compensated = Fn.u32(compensated) >>> 0; // var1 = (((BMP280_S32_t)dig_P9) * ((BMP280_S32_t)(((p>>3) * (p>>3))>>13)))>>12; var compshift3r = compensated >> 3; v1 = (Fn.s32(dig.P9) * Fn.s32(((compshift3r * compshift3r) >> 13))) >> 12; // var2 = (((BMP280_S32_t)(p>>2)) * ((BMP280_S32_t)dig_P8))>>13; v2 = (Fn.s32(compensated >> 2) * Fn.s32(dig.P8)) >> 13; // p = (BMP280_U32_t)((BMP280_S32_t)p + ((var1 + var2 + dig_P7) >> 4)); compensated = Fn.u32(Fn.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) var seapress = compensated / Math.pow(1 - elevation * 0.0000225577, 5.255); var altitude = 44330 * (1 - Math.pow(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); }.bind(this)); }.bind(this)); } }, identifier: { value: function(opts) { var address = opts.address || Drivers.BMP280.ADDRESSES.value[0]; return "bmp280-" + address; } } }, BME280: { ADDRESSES: { value: [0x77] }, REGISTER: { value: { COEFFICIENTS_TP: 0x88, COEFFICIENTS_H: 0xE1, CONFIG: 0xF5, MEASURE_H: 0xF2, MEASURE_TP: 0xF4, // 0xF7, 0xF8, 0xF9 // MSB, LSB, XLSB PRESSURE: 0xF7, // 0xFA, 0xFB, 0xFC // MSB, LSB, XLSB TEMPERATURE: 0xFA, // 0xFD, 0xFE // MSB, LSB RESET: 0xE0, } }, initialize: { value: function(board, opts) { var io = board.io; var address = opts.address || this.ADDRESSES[0]; var elevation = null; var offset = 0; if (typeof opts.elevation !== "undefined") { elevation = opts.elevation; } if ((elevation != null && elevation <= 0) || elevation == null) { offset = Math.abs(elevation) + 1; elevation = 1; } opts.address = address; /** * All page numbers refer to pages in: * https://cdn.sparkfun.com/assets/learn_tutorials/4/1/9/BST-BME280_DS001-10.pdf * */ var 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(opts); // Page. 24 // 4.3.2 Register 0xE0 "reset" io.i2cWrite(address, this.REGISTER.RESET, 0xB6); var pCoefficients = new Promise(function(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(function(resolve) { io.i2cReadOnce(address, 0x88, 24, function(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 = Fn.s32(int16(data[21], data[20])); dig.P9 = Fn.s32(int16(data[23], data[22])); resolve(); }); }), new Promise(function(resolve) { io.i2cReadOnce(address, 0xA1, 1, function(data) { dig.H1 = Fn.u8(data[0]); resolve(); }); }), new Promise(function(resolve) { io.i2cReadOnce(address, 0xE1, 8, function(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 = Fn.s32(int16(data[1], data[0])); // 0xE3 dig.H3 = Fn.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 = Fn.s32((data[3] << 4) | (data[4] & 0xF)); dig.H5 = Fn.s32((data[5] << 4) | (data[4] >> 4)); // 0xE7 dig.H6 = Fn.s8(data[6]); resolve(); }); }) ]).then(resolveCoeffs); }.bind(this)); pCoefficients.then(function() { /* 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 0xF2 "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