Duty cycling and an energy-aware governor that stretches work as the battery drains. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.
npm install @pamoja/power
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/power.ts:
import { DutyCycle, PowerMode, PowerPlan } from '@pamoja/power'
// A solar node samples every minute while the charge is healthy, stretches to ten minutes
// to conserve, and to an hour once the battery is nearly flat. Durations cross the binding
// as microseconds.
const plan = new PowerPlan(60_000_000, 600_000_000, 3_600_000_000)
// The default thresholds enter saver mode below 50% charge and critical below 20%.
for (const charge of [0.8, 0.35, 0.12]) {
const every = plan.intervalUs(charge) / 1_000_000
console.log(
`at ${(charge * 100).toFixed(0)}% charge: ${plan.mode(charge)}, sampling every ${every}s`,
)
}
// A panel that is delivering buys back one mode, so the same flat battery keeps reporting
// on the ten-minute saver cadence while the sun is on it.
const charging = plan.modeWhileCharging(0.12, true)
console.log(`the same flat battery, while charging: ${charging}`)
// The work is the same two seconds whichever mode the node is in; stretching the cycle is
// what saves the energy. The duty fraction is the proxy for average draw, so the hourly
// cadence costs a sixtieth of what the one-minute cadence does.
const awakeUs = 2_000_000
const healthy = new DutyCycle(awakeUs, plan.intervalUs(0.8) - awakeUs)
const flat = new DutyCycle(awakeUs, plan.intervalUs(0.12) - awakeUs)
console.log(`awake ${(healthy.fraction * 100).toFixed(2)}% of the time when healthy`)
console.log(`awake ${(flat.fraction * 100).toFixed(3)}% of the time when flat`)
// Stating the budget as a fraction instead gives the awake time directly.
const quarter = DutyCycle.fromFraction(1_000_000, 0.25)
console.log(`a quarter-duty second is ${quarter.activeUs / 1000}ms awake`)
| Language | Package | Reference |
|---|---|---|
| Rust | pamoja-power |
reference, docs.rs, install |
| TypeScript | @pamoja/power |
reference, install |
| Python | pamoja-power |
reference, install |
| C# | Pamoja.Power |
reference, install |
@pamoja/power reference, every class, function, and type this package exports.MIT
Ergonomic facade over the generated power binding.
A node on a battery and a panel has to decide how often to do anything at all. A duty cycle says how the time splits between working and sleeping; a power plan says how that split should change as the charge falls, so a node that would otherwise go dark in a cloudy week keeps reporting, less often.
The power mode is re-exported as a runtime PowerMode object, because the generated enum is types-only.