pamoja.profile

Idiomatic device-profile facade.

A profile is a named, pre-wired bundle: a control policy, a publish topic, and a power schedule. Instantiate one rather than choosing algorithms and tuning constants by hand.

A Profile is the manifest, which loads from and saves to JSON so it ships as a file. A Controller is the decision logic that manifest describes: hand it a reading and it says what the output should do and whether the reading crossed a threshold worth raising. The presentation a dashboard reads travels inside the manifest JSON.

 1"""Idiomatic device-profile facade.
 2
 3A profile is a named, pre-wired bundle: a control policy, a publish topic, and a
 4power schedule. Instantiate one rather than choosing algorithms and tuning
 5constants by hand.
 6
 7A :class:`Profile` is the manifest, which loads from and saves to JSON so it
 8ships as a file. A :class:`Controller` is the decision logic that manifest
 9describes: hand it a reading and it says what the output should do and whether
10the reading crossed a threshold worth raising. The presentation a dashboard
11reads travels inside the manifest JSON.
12"""
13
14from __future__ import annotations
15
16import enum
17
18from pamoja._native import (
19    AlertReport,
20    ControlPolicy,
21    Controller,
22    PowerScheduleSpec,
23    Profile,
24    Reaction,
25)
26
27__all__ = [
28    "AlertKind",
29    "AlertReport",
30    "ControlKind",
31    "ControlPolicy",
32    "Controller",
33    "PowerScheduleSpec",
34    "Profile",
35    "Reaction",
36]
37
38
39class ControlKind(str, enum.Enum):
40    """Which control policy a profile applies to each reading."""
41
42    #: Hold a reading near a setpoint by switching an output on and off.
43    SETPOINT = "Setpoint"
44    #: Watch a falling level and warn before it reaches empty.
45    LEVEL = "Level"
46    #: Warn when a reading changes faster than a limit.
47    SURGE = "Surge"
48    #: Report readings only, with no output and no alerts.
49    MONITOR = "Monitor"
50
51
52class AlertKind(str, enum.Enum):
53    """Which threshold a reading crossed."""
54
55    #: A controlled reading drifted outside its safe band.
56    OUT_OF_RANGE = "OutOfRange"
57    #: A falling level will reach empty within a few more samples.
58    RUNNING_OUT = "RunningOut"
59    #: A reading is changing faster than its safe rate.
60    CHANGING_FAST = "ChangingFast"
class AlertKind(builtins.str, enum.Enum):
53class AlertKind(str, enum.Enum):
54    """Which threshold a reading crossed."""
55
56    #: A controlled reading drifted outside its safe band.
57    OUT_OF_RANGE = "OutOfRange"
58    #: A falling level will reach empty within a few more samples.
59    RUNNING_OUT = "RunningOut"
60    #: A reading is changing faster than its safe rate.
61    CHANGING_FAST = "ChangingFast"

Which threshold a reading crossed.

OUT_OF_RANGE = <AlertKind.OUT_OF_RANGE: 'OutOfRange'>
RUNNING_OUT = <AlertKind.RUNNING_OUT: 'RunningOut'>
CHANGING_FAST = <AlertKind.CHANGING_FAST: 'ChangingFast'>
class AlertReport:

An alert a reading raised.

Only the attribute belonging to kind is set; the rest are None.

reading

The offending reading, for an out-of-range alert.

kind

Which threshold the reading crossed: OutOfRange, RunningOut, or ChangingFast.

samples

The estimated samples until empty, for a running-out alert.

rate

The change since the previous sample, for a changing-fast alert.

class ControlKind(builtins.str, enum.Enum):
40class ControlKind(str, enum.Enum):
41    """Which control policy a profile applies to each reading."""
42
43    #: Hold a reading near a setpoint by switching an output on and off.
44    SETPOINT = "Setpoint"
45    #: Watch a falling level and warn before it reaches empty.
46    LEVEL = "Level"
47    #: Warn when a reading changes faster than a limit.
48    SURGE = "Surge"
49    #: Report readings only, with no output and no alerts.
50    MONITOR = "Monitor"

Which control policy a profile applies to each reading.

SETPOINT = <ControlKind.SETPOINT: 'Setpoint'>
LEVEL = <ControlKind.LEVEL: 'Level'>
SURGE = <ControlKind.SURGE: 'Surge'>
MONITOR = <ControlKind.MONITOR: 'Monitor'>
class ControlPolicy:

A profile's control policy.

Only the attributes belonging to kind are set; the rest are None.

hysteresis

Half the deadband width, for a setpoint policy.

kind

Which policy this describes: Setpoint, Level, Surge, or Monitor.

safe_band

How far the reading may stray before an alert, for a setpoint policy.

empty

The level treated as empty, for a level policy.

limit

The largest safe change per sample, for a surge policy.

cooling

Whether the output cools rather than heats, for a setpoint policy.

setpoint

The target reading, for a setpoint policy.

warn_within

How many samples ahead to warn, for a level policy.

rising

Whether a rise rather than a fall is watched, for a surge policy.

class Controller:

The decision logic a profile assembles.

A controller carries state between readings, because a level estimate and a rate of change both need the previous sample, so evaluate readings through one controller in the order they were taken.

def setpoint(setpoint, hysteresis, cooling, safe_band):

Holds a reading near a setpoint by switching an output on and off.

def level(empty, warn_within):

Warns before a falling level reaches empty.

def surge(rising, limit):

Warns when a reading changes faster than a limit.

def monitor():

Reports readings without judging them.

def evaluate(self, /, reading):

Decides what one reading calls for.

class PowerScheduleSpec:

How often a node samples as its battery drains, in whole seconds.

saver_below

Enter the saver cadence below this state of charge.

active_secs

Seconds between samples at a healthy charge.

saver_secs

Seconds between samples while conserving.

critical_secs

Seconds between samples when critically low.

critical_below

Enter the critical cadence below this state of charge.

class Profile:

A named, ready-to-run node assembled from pamoja capabilities.

def vaccine_fridge_monitor():

A cold-chain fridge monitor, which holds 5 C and flags an excursion.

def irrigation_node():

An irrigation node, which opens a valve as soil moisture falls.

def well_level():

A well-level monitor, which warns before a tank runs dry.

def flood_sensor():

A flood sensor, which warns when a level rises too fast.

def from_json(manifest):

Loads a profile from its JSON manifest.

Raises ValueError if the manifest is malformed.

def to_json(self, /):

Serializes this profile to its JSON manifest.

def controller(self, /):

Builds the decision logic this profile describes.

control

The control policy applied to each reading.

topic

The topic each reading is published to.

power

The sampling schedule kept as the battery drains.

name

The profile's stable, human-readable name.

class Reaction:

What a controller decided about one reading.

alert

The alert the reading raised, or None if it crossed nothing.

actuator

The setting the output should take, or None when the profile observes rather than controls.