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

    Module @pamoja/hal

    Ergonomic facade over the generated bus binding.

    A driver is a conversation with a part, and a bus is what carries it. An I2cBus is one bus that the program and every driver on it share, with one of three things on the other end: the kernel's adapter on a Linux board, simulated parts that answer from their registers, or a script of the transfers a driver is expected to make. A driver runs the same way over all three, so a program is written and tested with nothing plugged in and then pointed at /dev/i2c-1. A SerialPort is the same idea for a UART, and the Delay types pace a driver that waits between pin changes.

    @pamoja/hal

    The embedded-hal traits every driver takes, a bit-banged 1-Wire bus, simulated parts and scripted buses that stand in for hardware, the Linux backends over i2c-dev, spidev, and the GPIO character device, one I2C bus and one serial port a program and its drivers share, and delays that sleep or only count. 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/hal
    

    This pulls in @pamoja/native, the compiled engine. 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/hal.ts:

    import { I2cBus, I2cPart, I2cStep } from '@pamoja/hal'
    import { Bme280, type Bme280Measurement, bme280 } from '@pamoja/sensors'

    const BME280 = bme280.addressPrimary
    const x = (code: number): string => `x${bme280.oversamplingFactor(code)}`
    const shown = (reading: Bme280Measurement): string =>
    `${reading.celsius.toFixed(2)} C, ${reading.hectopascals.toFixed(2)} hPa, ` +
    `${reading.relativeHumidityPercent.toFixed(2)} %`

    async function main() {
    // A bus with one part on it: a BME280 that is not there. It holds a real part's
    // calibration and one measurement that part took, and it answers from its registers, so
    // the driver runs its whole datasheet sequence against it. On a Raspberry Pi the bus is
    // I2cBus.open('/dev/i2c-1') and nothing after this line changes.
    const bus = I2cBus.simulated([bme280.sim.part(BME280)])
    const sensor = new Bme280(bus, BME280)

    // Reset, identify, calibrate, configure. The datasheet wants ctrl_hum written before
    // ctrl_meas, and the part left asleep until a measurement is forced. The part keeps what
    // the driver wrote, so the configuration reads back off the bus.
    await sensor.init()
    const part = bus.part(BME280)
    if (!(part instanceof I2cPart)) throw new Error('no part with byte-wide registers at the address')
    const humidity = bme280.ctrlHumFromBits(part.register(bme280.register.ctrlHum))
    const ctrl = bme280.ctrlMeasFromBits(part.register(bme280.register.ctrlMeas))
    const asleep = ctrl.mode === bme280.mode.sleep
    console.log(
    `configured humidity ${x(humidity)}, temperature ${x(ctrl.temperature)}, ` +
    `pressure ${x(ctrl.pressure)}, asleep: ${asleep}`,
    )

    // One forced measurement. The driver waits the datasheet's longest measurement time for
    // these settings before it reads, and a simulated bus counts that wait rather than
    // sleeping through it.
    const reading = await sensor.measure()
    console.log(`measured ${shown(reading)}`)
    const waitedMs = (bus.waitedMicros / 1000).toFixed(2)
    console.log(`waited ${waitedMs} ms across ${bus.transfers} transfers`)

    // A part reports whatever it is asked to. Putting one in the first one's place is how a
    // program meets a reading it would otherwise wait on the weather for, here a cold store
    // at four degrees, and the driver carries on without noticing.
    bus.attach(bme280.sim.reporting(BME280, 4.0, 1013.25, 80.0))
    const cold = await sensor.measure()
    console.log(`cold store ${shown(cold)}`)

    // Nothing answers at the part's other address, and the driver says so rather than
    // returning a reading.
    try {
    await new Bme280(bus, bme280.addressSecondary).init()
    console.log('absent a part answered')
    } catch (error) {
    console.log(`absent ${(error as Error).message}`)
    }

    // The other half of the bus layer. A script plays one conversation and refuses anything
    // else, which proves a driver follows the datasheet rather than merely working: the
    // reset, the status once the calibration has loaded, the chip id, the two calibration
    // blocks, the three configuration writes in the order the part requires, then one forced
    // measurement.
    const { register, oversampling, mode, sim } = bme280
    const settings = { temperature: oversampling.x1, pressure: oversampling.x1, mode: mode.sleep }
    const forced = { ...settings, mode: mode.forced }
    const resetState = { standby: bme280.standby.ms0_5, filter: bme280.filter.off, spi3Wire: false }
    const script = I2cBus.scripted([
    I2cStep.write(BME280, Buffer.from([register.reset, bme280.resetWord])),
    I2cStep.writeRead(BME280, Buffer.from([register.status]), Buffer.from([sim.statusIdle])),
    I2cStep.writeRead(BME280, Buffer.from([register.chipId]), Buffer.from([bme280.chipId])),
    I2cStep.writeRead(BME280, Buffer.from([register.calibTempPress]), sim.calibration()),
    I2cStep.writeRead(BME280, Buffer.from([register.calibHumidity]), sim.calibrationHumidity()),
    I2cStep.write(BME280, Buffer.from([register.config, bme280.configBits(resetState)])),
    I2cStep.write(BME280, Buffer.from([register.ctrlHum, bme280.ctrlHumBits(oversampling.x1)])),
    I2cStep.write(BME280, Buffer.from([register.ctrlMeas, bme280.ctrlMeasBits(settings)])),
    I2cStep.write(BME280, Buffer.from([register.ctrlMeas, bme280.ctrlMeasBits(forced)])),
    I2cStep.writeRead(BME280, Buffer.from([register.status]), Buffer.from([sim.statusIdle])),
    I2cStep.writeRead(BME280, Buffer.from([register.data]), sim.burst()),
    ])
    const checked = await new Bme280(script, BME280).measure()
    const left = script.remaining ?? 0
    console.log(
    `datasheet ${checked.celsius.toFixed(2)} C after ${script.transfers} transfers, ` +
    `${left} steps left`,
    )

    return { humidity, asleep, reading, cold, waited: bus.waitedMicros, checked, script }
    }

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

    MIT

    CommandPart
    DelayLog
    I2cBus
    I2cPart
    I2cStep
    SerialPort
    SerialStep
    SleepDelay
    WordPart
    Delay
    SerialSettings
    I2cBusKind
    I2cFault
    Parity
    SerialPortKind
    SimulatedPart
    I2cBusKind
    I2cFault
    Parity
    SerialPortKind