Skip to main content

pamoja-power

Duty cycling and an energy-aware governor that stretches work as the battery drains and holds its mode against a wandering charge. One capability of pamoja, one memory-safe Rust core with bindings for TypeScript, Python, and C#.

read the guide documentation API reference

Install

pip install pamoja-power
from pamoja import power

This pulls in pamoja-native, the compiled engine. pip install pamoja is the whole framework in one package.

Example

The script the test suite runs, spliced here as it ran.

From bindings/python/guides/power.py:

from pamoja.power import DutyCycle, PowerMode, power_plan

# 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.
plan = power_plan(60_000_000, 600_000_000, 3_600_000_000)

# The default thresholds enter saver mode below 50% charge and critical below 20%.
for charge in (0.80, 0.35, 0.12):
    every = plan.interval_us(charge) // 1_000_000
    print(f"at {charge * 100:.0f}% charge: {plan.mode(charge)}, sampling every {every}s")

# A panel that is delivering buys back one mode. The interval for a charge knows nothing of
# the panel, so the cadence comes from the mode the panel bought.
charging = plan.mode_while_charging(0.12, True)
every = plan.interval_for_us(charging) // 1_000_000
print(f"at 12% charge while charging: {charging}, sampling every {every}s")

# A charge worked out from a fuel gauge that did not answer is not a number. The plan takes
# it as critical, so a node that cannot tell what it has left does the least until it can.
unknown = float("nan")
every = plan.interval_us(unknown) // 1_000_000
print(f"with no reading from the gauge: {plan.mode(unknown)}, sampling every {every}s")

# The thresholds say how long the battery must carry the node without sun. Winter nights
# are long, so a winter plan starts saving sooner and goes critical sooner.
winter = plan.with_thresholds(0.70, 0.30)
saver, critical = winter.saver_below * 100, winter.critical_below * 100
print(f"the winter plan saves below {saver:.0f}% and goes critical below {critical:.0f}%")
cold, mild = winter.mode(0.60), plan.mode(0.60)
print(f"at 60% charge: {cold} in winter, {mild} by default")

# A fuel gauge wanders a point or two between readings, so a charge sitting at a threshold
# would change the cadence on every cycle. `next_mode` takes the mode the node is in: it
# drops as soon as the charge falls below a threshold, and climbs back only once the charge
# is the plan's hysteresis margin clear of it.
wandering = (0.49, 0.51, 0.50, 0.53, 0.48, 0.52)


def walk(governor):
    mode = PowerMode.ACTIVE
    modes = []
    for charge in wandering:
        mode = governor.next_mode(mode, charge)
        modes.append(mode)
    return ", ".join(modes)


flapping = walk(plan.with_hysteresis(0))
print(f"a charge wandering around 50% with no margin: {flapping}")
margin = plan.hysteresis * 100
settled = walk(plan)
print(f"and with the {margin:.0f} point margin: {settled}")
back = plan.next_mode(PowerMode.SAVER, 0.56)
print(f"at 56% the charge has cleared the margin: {back}")

# 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.
awake_us = 2_000_000
healthy = DutyCycle(awake_us, plan.interval_us(0.80) - awake_us)
flat = DutyCycle(awake_us, plan.interval_us(0.12) - awake_us)
print(f"awake {healthy.fraction * 100:.2f}% of the time when healthy")
print(f"awake {flat.fraction * 100:.3f}% of the time when flat")

# A node that lives on its panel can stay awake for the share of the time the harvest pays
# for. Asleep it draws next to nothing, so that share is the harvest over what it draws
# awake, and the duty cycle turns it into time.
minute_us = 60_000_000
awake_mw = 120
cloudy = DutyCycle.from_fraction(minute_us, 12 / awake_mw)
print(f"a 12 mW harvest pays for {cloudy.active_us // 1000}ms awake in each minute")

# The share is clamped, so a harvest above the draw keeps the node awake throughout, and a
# harvest the meter could not read keeps it asleep until one can be.
sunny = DutyCycle.from_fraction(minute_us, 150 / awake_mw)
unread = DutyCycle.from_fraction(minute_us, float("nan"))
print(f"a 150 mW harvest keeps it awake all {sunny.active_us // 1000}ms")
print(f"an unread harvest keeps it asleep all {unread.sleep_us // 1000}ms")

The same capability in every language

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

Documentation

License

MIT

Release files for pamoja-power 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pamoja-power 0.2.0
File Size Uploaded
pamoja_power-0.2.0.tar.gz 4.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pamoja-power 0.2.0
File Interpreter ABI Platform
pamoja_power-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 10.2 kB

Release files / pamoja_power-0.2.0.tar.gz

Download URL pamoja_power-0.2.0.tar.gz
Size 4.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f6239cb1da83837441d707ce1fdb1820d9fe61aab20133116340dccbd414f931
BLAKE2b-256 checksum
How to use checksums
a0760991433e45ffc4a9ff3d6556a0eede48770cb8a199e7eabf85d17c5a8848
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release files / pamoja_power-0.2.0-py3-none-any.whl

Download URL pamoja_power-0.2.0-py3-none-any.whl
Size 5.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
22d209e31d65460c41ef018c2ddee71d2cc1a4b71ccdcc85ce861d32909263e1
BLAKE2b-256 checksum
How to use checksums
29c97ba289bfd1c9d8359386192caead97971514087c504cd912ab363e7cb494
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.18

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page