Power Management#

Power management estimates whether field IoT nodes can survive the planned deployment. The pycsamt.iot.power helpers combine battery capacity, reserve, active/sleep duty cycle, regulator losses, telemetry windows, edge-processing overhead, auxiliary loads, and optional solar harvesting.

The examples below use synthetic L18-style field nodes. That is the right level for this page because power budgeting depends on device operations, not EDI impedance files. The three scenarios represent a solar-assisted node, a marginal node with high telemetry demand, and a critical node with small battery capacity and no harvesting.

Build Device Power Profiles#

Use pycsamt.iot.DevicePowerProfile when several field nodes share the same recorder hardware. The profile stores hardware draw; each pycsamt.iot.EnergyConfig stores deployment-specific conditions.

 1from pycsamt.iot import DevicePowerProfile
 2
 3profile = DevicePowerProfile(
 4    "amt-recorder",
 5    active_power_w=1.6,
 6    sleep_power_w=0.12,
 7    telemetry_power_w=3.0,
 8    edge_power_w=0.35,
 9)
10
11configs = [
12    profile.apply(
13        battery_wh=160.0,
14        duty_cycle=0.35,
15        solar_wh_per_day=38.0,
16        charge_efficiency=0.85,
17        reserve_fraction=0.20,
18        regulator_efficiency=0.88,
19        telemetry_seconds_per_day=420.0,
20        edge_duty_cycle=0.35,
21        auxiliary_wh_per_day=1.5,
22        min_runtime_days=7.0,
23        device_id="l18-node-01",
24    ),
25    profile.apply(
26        battery_wh=95.0,
27        duty_cycle=0.55,
28        solar_wh_per_day=8.0,
29        charge_efficiency=0.80,
30        reserve_fraction=0.20,
31        regulator_efficiency=0.85,
32        telemetry_seconds_per_day=900.0,
33        edge_duty_cycle=0.55,
34        auxiliary_wh_per_day=2.0,
35        min_runtime_days=7.0,
36        device_id="l18-node-02",
37    ),
38    profile.apply(
39        battery_wh=48.0,
40        duty_cycle=0.85,
41        solar_wh_per_day=0.0,
42        reserve_fraction=0.15,
43        regulator_efficiency=0.82,
44        telemetry_seconds_per_day=1500.0,
45        edge_duty_cycle=0.80,
46        auxiliary_wh_per_day=3.0,
47        min_runtime_days=7.0,
48        device_id="l18-node-03",
49    ),
50]

Estimate One Device#

Use pycsamt.iot.estimate_energy_budget() for a single device. The estimate reports daily load, daily harvest, net daily draw, runtime, state, and machine-readable issues.

 1from pycsamt.iot import estimate_energy_budget, power_summary_table
 2
 3estimate = estimate_energy_budget(configs[1])
 4table = power_summary_table(estimate, device_ids=[configs[1].device_id])
 5print(
 6    table[
 7        [
 8            "device_id",
 9            "state",
10            "runtime_days",
11            "load_wh_per_day",
12            "harvest_wh_per_day",
13            "net_wh_per_day",
14            "energy_margin_wh_per_day",
15            "issues",
16        ]
17    ].to_string(index=False)
18)

Output:

  device_id    state  runtime_days  load_wh_per_day  harvest_wh_per_day  net_wh_per_day  energy_margin_wh_per_day                                     issues
l18-node-02 critical       2.77963        33.741765                 6.4       27.341765                -27.341765 daily_energy_deficit;runtime_below_minimum

Estimate A Deployment#

Use pycsamt.iot.estimate_deployment_energy() when several nodes must be compared. runtime_days is infinite when daily harvest is greater than or equal to daily load.

 1from pycsamt.iot import estimate_deployment_energy
 2
 3deployment = estimate_deployment_energy(configs)
 4print(
 5    deployment[
 6        [
 7            "device_id",
 8            "state",
 9            "runtime_days",
10            "autonomy_days_no_harvest",
11            "load_wh_per_day",
12            "harvest_wh_per_day",
13            "net_wh_per_day",
14            "issues",
15        ]
16    ].copy().round(
17        {
18            "runtime_days": 2,
19            "autonomy_days_no_harvest": 2,
20            "load_wh_per_day": 2,
21            "harvest_wh_per_day": 2,
22            "net_wh_per_day": 2,
23        }
24    ).to_string(index=False)
25)

Output:

  device_id      state  runtime_days  autonomy_days_no_harvest  load_wh_per_day  harvest_wh_per_day  net_wh_per_day                                     issues
l18-node-01 sustaining           inf                      5.77            22.19                32.3          -10.11
l18-node-02   critical          2.78                      2.25            33.74                 6.4           27.34 daily_energy_deficit;runtime_below_minimum
l18-node-03   critical          0.80                      0.80            51.30                 0.0           51.30 daily_energy_deficit;runtime_below_minimum

Encode Power Telemetry#

An EnergyEstimate can be encoded as a power packet and added to a pycsamt.iot.FieldSession. This keeps power evidence next to edge QC, synchronisation, and station metadata.

 1from pycsamt.iot import DeviceConfig, FieldSession
 2
 3devices = [
 4    DeviceConfig(
 5        cfg.device_id,
 6        station=f"00{i}A",
 7        channels=["ex", "ey", "hx", "hy"],
 8    )
 9    for i, cfg in enumerate(configs, start=1)
10]
11session = FieldSession("WILLY-L18-POWER-DEMO", devices=devices)
12
13for idx, (device, cfg) in enumerate(zip(devices, configs)):
14    packet = estimate_energy_budget(cfg).to_packet(
15        device,
16        timestamp=1_700_000_000.0 + 60.0 * idx,
17        survey_id=session.survey_id,
18    )
19    session.add_packet(packet)
20
21packet = session.packets[1]
22print(f"topic: {packet.topic}")
23print(f"state: {packet.payload['state']}")
24print(f"runtime_days: {packet.payload['runtime_days']:.2f}")
25print(f"payload keys: {', '.join(sorted(packet.payload))}")

Output:

topic: pycsamt/WILLY-L18-POWER-DEMO/002A/l18-node-02/power
state: critical
runtime_days: 2.78
payload keys: autonomy_days_no_harvest, auxiliary_wh_per_day, average_power_w, edge_wh_per_day, energy_margin_wh_per_day, harvest_wh_per_day, issues, load_wh_per_day, net_wh_per_day, reserve_wh, runtime_days, runtime_hours, state, telemetry_wh_per_day, usable_battery_wh

Plot Power Budgets#

The plotting helper summarises daily load and harvest, runtime, no-harvest autonomy, daily load components, and state counts.

 1from pathlib import Path
 2
 3from pycsamt.iot import plot_power_budget
 4
 5out_dir = Path("docs/source/images/user_guide/iot")
 6out_dir.mkdir(parents=True, exist_ok=True)
 7
 8plot_power_budget(
 9    configs,
10    figsize=(10.8, 7.2),
11    title="L18 IoT power budget scenarios",
12    output_path=(
13        out_dir / "user-guide-iot-power-management-01.png"
14    ).as_posix(),
15    close=True,
16)
../../_images/user-guide-iot-power-management-01.png

Field Interpretation#

l18-node-01 is sustaining because harvested energy exceeds daily load. Its no-harvest autonomy is still finite, so the deployment remains exposed to cloudy weather or panel failure. l18-node-02 and l18-node-03 both have a daily energy deficit and fall below the seven-day minimum runtime. The third node is the highest-risk case because it has small battery capacity, high duty cycle, long telemetry windows, and no harvest.

In field planning, revise the critical nodes before deployment: reduce duty cycle, shorten telemetry windows, add battery capacity, add solar harvesting, or lower auxiliary load. Record the final budget in the acquisition manifest so runtime assumptions remain auditable.