pamoja.power

Idiomatic power-scheduling facade.

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.

 1"""Idiomatic power-scheduling facade.
 2
 3A node on a battery and a panel has to decide how often to do anything at all. A
 4duty cycle says how the time splits between working and sleeping; a power plan
 5says how that split should change as the charge falls, so a node that would
 6otherwise go dark in a cloudy week keeps reporting, less often.
 7"""
 8
 9from __future__ import annotations
10
11import enum
12
13from pamoja._native import DutyCycle, PowerPlan
14
15__all__ = [
16    "DutyCycle",
17    "PowerMode",
18    "PowerPlan",
19    "duty_cycle",
20    "power_plan",
21]
22
23
24class PowerMode(str, enum.Enum):
25    """What a node should be doing at the current state of charge."""
26
27    #: Full duty, because the charge is healthy.
28    ACTIVE = "Active"
29    #: Reduced duty, to conserve charge.
30    SAVER = "Saver"
31    #: Minimum duty, to stay alive as long as possible.
32    CRITICAL = "Critical"
33
34
35def duty_cycle(active_us: int, sleep_us: int) -> DutyCycle:
36    """Split a period between working and sleeping.
37
38    :param active_us: How long the node works each period, in microseconds.
39    :param sleep_us: How long it sleeps each period, in microseconds.
40    :returns: The duty cycle.
41    """
42    return DutyCycle(active_us, sleep_us)
43
44
45def power_plan(active_us: int, saver_us: int, critical_us: int) -> PowerPlan:
46    """Describe how a work interval stretches as the charge falls.
47
48    The defaults enter :attr:`PowerMode.SAVER` below 50% charge and
49    :attr:`PowerMode.CRITICAL` below 20%; move them with
50    :meth:`PowerPlan.with_thresholds`.
51
52    :param active_us: The interval at a healthy charge, in microseconds.
53    :param saver_us: The longer interval used to conserve, in microseconds.
54    :param critical_us: The longest interval, in microseconds.
55    :returns: The power plan.
56    """
57    return PowerPlan(active_us, saver_us, critical_us)
class DutyCycle:

The split between the time a node works and the time it sleeps.

def from_fraction(period_us, fraction):

Creates a duty cycle that spends fraction of period_us awake.

sleep_us

How long it sleeps each period, in microseconds.

period_us

The whole period, awake plus asleep, in microseconds.

fraction

The share of the period spent awake, from 0 through 1.

active_us

How long the node stays awake each period, in microseconds.

class PowerMode(builtins.str, enum.Enum):
25class PowerMode(str, enum.Enum):
26    """What a node should be doing at the current state of charge."""
27
28    #: Full duty, because the charge is healthy.
29    ACTIVE = "Active"
30    #: Reduced duty, to conserve charge.
31    SAVER = "Saver"
32    #: Minimum duty, to stay alive as long as possible.
33    CRITICAL = "Critical"

What a node should be doing at the current state of charge.

ACTIVE = <PowerMode.ACTIVE: 'Active'>
SAVER = <PowerMode.SAVER: 'Saver'>
CRITICAL = <PowerMode.CRITICAL: 'Critical'>
class PowerPlan:

The work intervals a node uses in each mode, and where the modes change.

def with_thresholds(self, /, saver_below, critical_below):

Returns a copy of this plan with the state-of-charge thresholds moved.

def mode(self, /, soc):

Returns the mode this plan calls for at a state of charge, by name.

def mode_while_charging(self, /, soc, charging):

Returns the mode, eased one step toward full duty while charging.

def interval_for_us(self, /, mode):

Returns the work interval for a named mode, in microseconds.

def interval_us(self, /, soc):

Returns the work interval at a state of charge, in microseconds.

critical_below

The charge below which the plan enters critical mode.

saver_below

The charge below which the plan enters saver mode.

def duty_cycle(active_us: int, sleep_us: int) -> DutyCycle:
36def duty_cycle(active_us: int, sleep_us: int) -> DutyCycle:
37    """Split a period between working and sleeping.
38
39    :param active_us: How long the node works each period, in microseconds.
40    :param sleep_us: How long it sleeps each period, in microseconds.
41    :returns: The duty cycle.
42    """
43    return DutyCycle(active_us, sleep_us)

Split a period between working and sleeping.

Parameters
  • active_us: How long the node works each period, in microseconds.
  • sleep_us: How long it sleeps each period, in microseconds. :returns: The duty cycle.
def power_plan(active_us: int, saver_us: int, critical_us: int) -> PowerPlan:
46def power_plan(active_us: int, saver_us: int, critical_us: int) -> PowerPlan:
47    """Describe how a work interval stretches as the charge falls.
48
49    The defaults enter :attr:`PowerMode.SAVER` below 50% charge and
50    :attr:`PowerMode.CRITICAL` below 20%; move them with
51    :meth:`PowerPlan.with_thresholds`.
52
53    :param active_us: The interval at a healthy charge, in microseconds.
54    :param saver_us: The longer interval used to conserve, in microseconds.
55    :param critical_us: The longest interval, in microseconds.
56    :returns: The power plan.
57    """
58    return PowerPlan(active_us, saver_us, critical_us)

Describe how a work interval stretches as the charge falls.

The defaults enter PowerMode.SAVER below 50% charge and PowerMode.CRITICAL below 20%; move them with PowerPlan.with_thresholds().

Parameters
  • active_us: The interval at a healthy charge, in microseconds.
  • saver_us: The longer interval used to conserve, in microseconds.
  • critical_us: The longest interval, in microseconds. :returns: The power plan.