pamoja for TypeScript - v0.2.0
    Preparing search index...

    Module @pamoja/sensors

    Ergonomic facade over the generated sensor-driver binding.

    These are eleven parts a field node is likely to have wired to it. Each has its decode half, turning the register bytes a bus driver read into the physical reading the manufacturer's datasheet says they mean, and each I2C part has a driver, which runs the datasheet's whole conversation over an I2cBus from @pamoja/hal, and a simulated part that answers it with nothing plugged in. The DS18B20 is read through the files the Linux kernel's 1-Wire driver serves.

    @pamoja/sensors

    Datasheet-anchored drivers for eleven parts, from every language: the BME280, BMP280, DS18B20, HDC1080, INA219, INA226, ADS1115, OPT3001, SCD4x, SHT3x, and TMP117. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.

    read the guide documentation API reference

    npm install @pamoja/sensors
    

    This pulls in @pamoja/native, the compiled engine, and @pamoja/hal. npm install pamoja is the whole framework in one package.

    The test that runs in CI, spliced here as it ran.

    From bindings/node/guides/sensors.ts:

    import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
    import { tmpdir } from 'node:os'
    import { join } from 'node:path'
    import { I2cBus } from '@pamoja/hal'
    import {
    Ads1115,
    Bmp280,
    Ds18b20Thermometer,
    Hdc1080,
    Ina219,
    Ina226,
    Opt3001,
    Scd4x,
    Sht3x,
    Tmp117,
    ads1115,
    bmp280,
    ds18b20,
    hdc1080,
    ina219,
    ina226,
    opt3001,
    scd4x,
    sht3x,
    tmp117,
    } from '@pamoja/sensors'

    // The address plan. Every part answers at the address its pins choose, and two parts on one
    // address garble each other, so a bench of nine is planned around the two that cannot move:
    // the HDC1080 and the SCD41 have one address each.
    const air = sht3x.addressA
    const pressure = bmp280.addressSecondary
    const light = opt3001.addressScl
    const soil = ads1115.addressSda
    const enclosure = tmp117.address.add0Vplus
    const panel = ina219.address(ina219.pin.ground, ina219.pin.supply)
    const battery = ina226.address(ina226.pin.supply, ina226.pin.supply)

    async function main() {
    // The bench with nothing plugged in: each part answers the way its datasheet says, with the
    // reading it is given here, a warm and humid afternoon. On a Raspberry Pi the bus is
    // I2cBus.open('/dev/i2c-1') and nothing after this statement changes.
    const bus = I2cBus.simulated([
    sht3x.sim.reporting(air, 24.1, 62.0),
    bmp280.sim.reporting(pressure, 24.1, 1003.2),
    scd4x.sim.reporting(1_180, 24.1, 62.0),
    opt3001.sim.reporting(light, 4_200),
    ads1115.sim.reporting(soil, ads1115.pga.fsr4_096, 2.35),
    tmp117.sim.reporting(enclosure, 31.25),
    hdc1080.sim.reporting(31.25, 38.0),
    ina219.sim.reporting(panel, 100, 3_200_000, 18_400, 1_250_000),
    ina226.sim.reporting(battery, 2, 20_000_000, 12_800_000, -350_000),
    ])

    // One driver per part. Each holds its own share of the bus, runs its datasheet's whole
    // conversation on the first measurement, and resolves once the part has answered.
    const airNow = await new Sht3x(bus, air).measure()
    console.log(`air ${airNow.celsius.toFixed(2)} C, ${airNow.relativeHumidity.toFixed(2)} %`)

    const weather = await new Bmp280(bus, pressure).measure()
    console.log(`pressure ${weather.hectopascals.toFixed(1)} hPa`)

    const co2 = await new Scd4x(bus).measure()
    console.log(`co2 ${co2.co2Ppm} ppm`)

    const sun = await new Opt3001(bus, light).measure()
    console.log(`light ${sun.lux.toFixed(0)} lux`)

    // A capacitive probe's voltage falls as the soil wets and nears 3 V in dry soil, past the
    // ADS1115's default range of 2.048 V, so the driver is given the 4.096 V range.
    const probe = await new Ads1115(bus, soil, {
    mux: ads1115.mux.ain0Gnd,
    pga: ads1115.pga.fsr4_096,
    }).sample()
    console.log(`soil ${probe.volts.toFixed(3)} V`)

    const box = await new Tmp117(bus, enclosure).measure()
    const boxAir = await new Hdc1080(bus).measure()
    console.log(`enclosure ${box.celsius.toFixed(2)} C, ${boxAir.relativeHumidity.toFixed(1)} %`)

    // Two current monitors, each calibrated for its own shunt. The fans and the pump draw more
    // than the panel gives, so the battery makes up the rest and its current reads negative:
    // current through a shunt is signed by its direction.
    const charge = await new Ina219(bus, panel, {
    shuntMilliohms: 100,
    maxMicroamps: 3_200_000,
    }).measure()
    console.log(
    `panel ${(charge.busMillivolts / 1e3).toFixed(2)} V, ` +
    `${(charge.currentMicroamps / 1e6).toFixed(2)} A, ` +
    `${(charge.powerMicrowatts / 1e6).toFixed(2)} W`,
    )
    const drain = await new Ina226(bus, battery, {
    shuntMilliohms: 2,
    maxMicroamps: 20_000_000,
    }).measure()
    console.log(
    `battery ${drain.busVolts.toFixed(2)} V, ${drain.currentAmps.toFixed(2)} A, ` +
    `${drain.powerWatts.toFixed(2)} W`,
    )

    // Every driver waited as its datasheet asks, the OPT3001's 800 ms integration and the
    // SCD41's command times most of all. The simulated bus counted the waits and slept none.
    console.log(`waited ${(bus.waitedMicros / 1e6).toFixed(2)} s across ${bus.transfers} transfers`)

    // The same probe read at the default range. Past 2.048 V the converter pins at its top code,
    // and the sample says so rather than passing the edge of the range off as a reading.
    bus.attach(ads1115.sim.reporting(soil, ads1115.pga.fsr2_048, 2.35))
    const pinned = await new Ads1115(bus, soil, { mux: ads1115.mux.ain0Gnd }).sample()
    console.log(`default gain ${pinned.volts.toFixed(3)} V, clipped: ${pinned.clipped}`)

    // A driver aimed at the wrong address meets whatever answers there. The TMP117 reads its
    // device id before anything else, so pointed at the soil probe's converter it refuses
    // rather than reporting that part's registers as a temperature.
    try {
    await new Tmp117(bus, soil).init()
    console.log('wrong part accepted, which should never happen')
    } catch (error) {
    console.log(`wrong part ${(error as Error).message}`)
    }

    // A DS18B20 on a lead into a pot, read the way a Linux board reads one: the kernel's 1-Wire
    // driver serves each probe as a file under /sys/bus/w1/devices. The program writes that
    // file itself, with the text the kernel prints, so it runs anywhere.
    const devices = join(tmpdir(), `pamoja-bench-${process.pid}`)
    const directory = join(devices, `${ds18b20.familyCode.toString(16)}-000005e2fdc3`)
    mkdirSync(directory, { recursive: true })
    const scratchpad = ds18b20.buildScratchpad(19.5, 12, 30, 5)
    writeFileSync(join(directory, 'w1_slave'), ds18b20.w1SlaveText(scratchpad))
    const found = []
    for (const thermometer of Ds18b20Thermometer.discover(devices)) {
    const reading = await thermometer.read()
    console.log(
    `soil probe ${reading.celsius.toFixed(4)} C, ` +
    `alarms at ${reading.alarmLow} and ${reading.alarmHigh} C`,
    )
    found.push(reading)
    }
    rmSync(devices, { recursive: true })

    return { airNow, weather, co2, sun, probe, box, boxAir, charge, drain, pinned, found }
    }

    main()
    Language Package Reference
    Rust pamoja-sensors reference, docs.rs, install
    TypeScript @pamoja/sensors reference, install
    Python pamoja-sensors reference, install
    C# Pamoja.Sensors reference, install

    MIT

    Ads1115
    Bme280
    Bme280Calibration
    Bmp280
    Bmp280Calibration
    Ds18b20Thermometer
    Hdc1080
    Ina219
    Ina226
    Opt3001
    Scd4x
    Sht3x
    Tmp117
    Ads1115Config
    Ads1115Sample
    Ads1115Settings
    Bme280Config
    Bme280CtrlMeas
    Bme280Measurement
    Bme280Settings
    Bmp280Config
    Bmp280CtrlMeas
    Bmp280RawMeasurement
    Bmp280Reading
    Bmp280Settings
    Ds18b20Reading
    Hdc1080Config
    Hdc1080Measurement
    Hdc1080Settings
    Ina219Config
    Ina219Reading
    Ina219Settings
    Ina226Config
    Ina226DieIdFields
    Ina226MaskEnable
    Ina226Reading
    Ina226Settings
    Opt3001Config
    Opt3001Reading
    Opt3001Settings
    Scd4xMeasurement
    Sht3xMeasurement
    Sht3xSettings
    Sht3xStatus
    Tmp117Alerts
    Tmp117Config
    Tmp117Reading
    Tmp117Settings
    Ina226AlertFunction
    Sht3xRate
    Sht3xRepeatability
    ads1115
    bme280
    bmp280
    ds18b20
    hdc1080
    ina219
    ina226
    Ina226AlertFunction
    opt3001
    scd4x
    sht3x
    Sht3xRate
    Sht3xRepeatability
    tmp117