- Date: 2026-05-15 local
- Hardware: SimpleAir RevE powered through the Joulescope JS220 current path from a 3.7 V bench source. The JS220 voltage sense can still report the bench source when the target current path is switched off.
- Instrument: Joulescope JS220
u/js220/005920, firmware 1.3.0, FPGA 1.3.3, MCP driver 2.1.0 - Firmware revision:
82f8ae0 - Scope: current draw and power during cold boot, first SEN66 duty-cycle measurement, post-boot nominal operation, no-RTT power-audit firmware variants, and retained RAM firmware-event tracing
- Status: Measured and complete with explicit physical limitations
The current live board has a low post-boot nominal draw around 1.22-1.28 mA / 4.52-4.74 mW at 3.701 V when the SEN66 rail is off. The first SEN66 duty-cycle window dominates boot energy: repeated cold boots show a 45.2-49 s high-power window averaging roughly 74-87 mA, with 1 s bin peaks of 115.7-122.0 mA, a resumed 0.252 s-bin peak of about 131 mA, and a restored-normal 0.1 s-bin boot peak of 134.70 mA. A retained RAM event trace now pins the boot high-current plateau to the SEN66 duty manager: the first scheduled measurement powers BUCK1/SEN66 on at 9.406 s firmware uptime, starts measurement at 9.578 s, reads at 54.630 s, finishes stop at 55.867 s, and powers the rail off at 55.917 s. Later complete scheduled SEN66 events were captured directly: one measured 46 s at 85.34 mA average with a 127.93 mA 1 s peak, a repeat 300 s / 0.1 s-bin capture measured 45.6 s at 87.96 mA average with a 133.10 mA 0.1 s-bin peak, and a no-reset fully booted 300 s / 0.1 s-bin capture measured 45.1 s at 88.30 mA average with a 132.65 mA 0.1 s-bin peak. Contiguous full-duty-period validations captured one scheduled SEN66 event each and averaged 5.559 mA / 20.549 mW over 929.1 s and 5.542 mA / 20.486 mW over 929.6 s, essentially matching the stitched forecast. A newer 2000 s normal-firmware validation captured the boot event plus two later scheduled SEN66 events; the scheduled events were nearly identical and project a steady 900 s schedule at 5.765 mA / 21.308 mW. Across a 15 minute steady duty cycle, the best current live-board model is now about 5.6-5.8 mA average.
The major power consumer is SEN66 on BUCK1. Everything else visible in the captures is comparatively small: boot/init before SEN66 wake averaged about 2.1-2.2 mA, and post-SEN66 steady operation settles to about 1.2-1.3 mA with small periodic BLE/log/scheduler bursts.
Continuation measurements on 2026-05-16 added three more fully booted nominal windows, three short complete scheduled non-boot SEN66 duty-cycle captures, two 930 s whole-duty-period validations, a 2000 s normal-firmware multi-duty validation, a post-event rail-off check, 100 ms-bin nominal profiles, repeated JS220 target-power-cycle boot captures after correcting the JS220 current-path interpretation, a no-RTT/no-log power-audit firmware pass, and a RAM-traced boot capture. A final current-path sanity check measured 0.0 mA with JS220 target power off while the voltage sense still reported the upstream ~3.7 V source, then restored target power and captured another normal SEN66-dominated boot. After the measurement-only images, default normal firmware was rebuilt warning-clean, flashed, current-path power-cycled, and captured again at 45.15 mA over 90 s after the no-display-driver pass, 44.93 mA over 90 s after the no-PMIC-stack pass, 44.86 mA over 90 s after the no-PMIC-charger pass, 45.10 mA over 90 s after the PMIC child-driver split pass, 45.44 mA over 90 s after the PMIC/I2C22 split, 45.17 mA over 90 s after the I2C22 pinctrl follow-up pass, 45.42 mA over 89.5 s after the register-dump pass, and 45.79 mA over 90 s after the 600 s BLE A/B pass. A corrected direct-backend helper pass then kept JS220 target power off during measurement, verified 0.0 mA / 0.0 mAh over a 10 s current-only validation, captured a 120 s / 0.1 s current-path power-cycle boot with a 45.1 s SEN66 span averaging 86.03 mA, captured an adjacent fully booted 180 s / 0.1 s nominal window averaging 1.259 mA, and captured a following fully booted 929.6 s / 0.5 s duty-period window averaging 5.542 mA with a 45.0 s SEN66 span averaging 89.37 mA. A repeatability artifact now combines seven scheduled SEN66 events and two full-duty windows: incremental SEN66 event cost spans 1.090-1.132 mAh with a 1.110 mAh mean, and the two full-duty windows average 5.551 mA with only 0.012 mA sample standard deviation. Post-boot idle sanity checks stayed in the expected low-milliamp class at 1.271 mA, 1.182 mA, 1.265 mA, 1.271 mA, 1.182 mA, 1.268 mA, 1.273 mA, 1.272 mA, and 1.259 mA. The fully booted SEN66-off baseline is repeatable at 1.18-1.28 mA, while the 300 s / 0.1 s-bin, corrected 930 s / 0.5 s-bin, 2000 s, and resumed MCP captures show the sub-2 mA idle bins centered around 1.08-1.24 mA between bursts. A 10 Ah / 3.7 V LiPo forecast at 27 C now lands at ~54-61 days for the current 15 minute SEN66 schedule under the 80-90% usable capacity and 3%/month self-discharge planning case; conservative aged-pack/event-margin cases remain about 45-50 days.
After the external debug probe/SWD ribbon was physically detached by the operator, the hosted Joulescope MCP path was used directly for an absolute detached-current pass on the current restored-normal bench image. The MCP target-power-off check measured 0.0 mA / 0.0 mAh over 9.5 s while the voltage sense still reported the upstream bench source, so the current-path disconnect gate passed by current only. A following 3000 ms JS220 current-path power cycle and 120 s / 0.1 s detached boot capture averaged 34.324 mA / 126.852 mW and consumed 1.153 mAh. The adjacent fully booted 180 s / 0.1 s detached nominal window averaged 1.266 mA / 4.679 mW. Four sequential MCP-safe 295 s / 0.5 s detached duty/tail windows then measured 1.254 mA, 1.260 mA, 14.487 mA, and 1.265 mA; the third segment contained a scheduled SEN66 event, and the idle segments plus event segment project a 5.595 mA 900 s duty-cycle current. This closes the debug-probe-detached absolute-current check for the current bench image, but it was not GPI-marker synchronized and no fresh release image was flashed immediately before that detached pass. The remaining wired JS220 GPI marker step cannot be completed in the current bench setup, so this report is closed as complete with explicit limitations rather than as exact hardware-edge synchronized attribution.
A follow-up crash-triage check was run after a no-blink bench observation. The board was not electrically dead and was not stuck in reset: it drew 1.652 mA before an explicit JS220 current-path power cycle, then after a 3000 ms current-path disconnect/reconnect entered normal boot, reached the SEN66 duty window, averaged 89.45 mA during a 19.5 s SEN66-on follow-up, and returned to 1.263 mA / 4.670 mW post-SEN66 idle. RTT capture also showed a normal boot: LED task initialized, LED pattern set to 100 ms on / 3000 ms off, PMIC initialized, BLE advertising started, SEN66 detected as SEN66 serial 3D01952DB4236A40, and SEN66 warmup started. No fault log was observed. The no-blink observation is therefore not supported by the electrical/RTT evidence from this check; the active battery-mode heartbeat is short and sparse enough to be easy to miss visually.
After cleaning the base/release Kconfig split, a warning-clean layered release build was flashed and measured with the debug probe still attached. A JS220 target-power-off validation measured 0.0 mA through the current path, then the release boot capture measured 32.98 mA / 121.91 mW over 120 s. The SEN66-class portion was 45 s from 11-56 s, averaging 84.88 mA with a 127.11 mA 1 s-bin peak and 1.061 mAh / 3.922 mWh inside the SEN66-class bins. The following 20 s SEN66-off idle capture measured 1.229 mA / 4.543 mW. This confirms the warning-clean release candidate behaves like the earlier debug/no-output measurement family; it does not reveal a large hidden release-only saving. It is still probe-attached evidence, not a debug-probe-detached absolute floor.
The board was then restored to the default debug-capable firmware so the bench is not left on a silent no-output release image. A short 15 s JS220 sanity capture after a 3000 ms current-path power cycle showed the expected SEN66 onset in bins 11-14, with a 90.99 mA 1 s-bin max.
A final high-resolution restored-normal boot run used the direct JS220 backend at a requested 0.1 s interval after another 3000 ms current-path power cycle. After filtering the direct backend's overlapping first statistics artifact, the 120.0 s capture averaged 33.69 mA / 124.53 mW, and the SEN66-class span was 45.2 s from 10.1-55.3 s, averaging 86.92 mA / 321.28 mW with 1.0913 mAh / 4.0338 mWh in the span. The maximum 0.1 s bin was 134.70 mA, with a maximum instantaneous current inside a statistics bin of 258.75 mA. This aligns the boot SEN66 peak with the independent scheduled-event 0.1 s capture and confirms the lower release 120 s average was mostly the longer low-current tail, not a different high-load mechanism.
After correcting the capture helper so target-power-off validation does not auto-restore the JS220 current range, a 5 s off check showed one first-bin residual artifact followed by zero-current bins, and a 10 s repeat measured 0.0 mA / 0.0 mAh with current_range_mode=off before and after the capture. A following 3000 ms JS220 current-path cycle produced another normal 120 s / 0.1 s boot: 33.35 mA / 123.26 mW over the analyzed window, with a 45.1 s SEN66-class span from 10.1-55.2 s, 86.03 mA average, and 133.84 mA maximum 0.1 s bin. The adjacent fully booted 180 s / 0.1 s nominal window, without another reset, averaged 1.259 mA / 4.653 mW, had a 1.077 mA median, and had no bins above 40 mA.
Further A/B firmware cuts isolated the largest visible non-SEN66 terms. Disabling the UI task reduced no-SEN6X steady draw by about 0.144 mA; disabling the LED task reduced it by about 0.102 mA. A focused UI/EPD retained-trace pass, with SEN6X, sensor task, LED task, and BLE disabled, observed display writes at 5.155-6.415 s, 63.015-64.956 s, and 183.096-185.036 s firmware uptime. The first two refreshes were captured electrically in a 100 s JS220 window; each visible EPD write cluster was only roughly 7-10 mJ above local idle in 0.25 s bins. A follow-up UI-no-display-write variant kept the UI task, frame buffer, cache processing, and RAM renderer alive but skipped hardware display_write(). That measured 0.897 mA / 3.318 mW, essentially identical to the stripped no-UI/no-LED/no-BLE floor. A deeper no-display-driver cut then disabled the EPD device, SPI20, and display subsystem entirely and linked a UI API stub; two adjacent 30 s captures measured 0.8610 mA and 0.8620 mA. Disabling the SHT45/DPS368 devicetree nodes and I2C21 on top of that did not move the floor: three adjacent 30 s captures averaged 0.8611 mA / 3.1830 mW. Therefore the UI task and renderer are not the material cost; the display-driver/SPI20 init and retained hardware state are worth only about 0.031-0.034 mA on the stripped no-BLE floor, and display writes/post-write behavior explain the rest of the UI/EPD-visible term. Removing the nPM13xx software device stack plus I2C22 was not a saving: with the same stripped build plus nPM13xx regulators, charger, GPIO, LED, fuel gauge, PMIC node, and I2C22 disabled, three adjacent 30 s captures averaged 1.1693 mA / 4.3228 mW, about +0.308 mA above the no-env-I2C floor. A focused exact-baseline repeat with the PMIC device stack active but application PMIC manager skipped measured 0.8609 mA / 3.1824 mW. Disabling PMIC children one at a time stayed near that exact floor: charger/fuel-gauge 0.8666 mA, LED child 0.8602 mA, GPIO child 0.8619 mA, and regulator child 0.8627 mA. PMIC-parent-MFD-only averaged 0.8692 mA / 3.2123 mW, and PMIC-node-disabled with I2C22 kept enabled averaged 0.8696 mA / 3.2135 mW. A follow-up I2C22 sleep-pinctrl image, with TWIM22 still enabled but i2c22_sleep used as the default pinctrl state, averaged 1.1699 mA / 4.3252 mW. An I2C22-disabled image that parked P1.11/P1.15 as GPIO inputs with pull-ups still averaged 1.1660 mA / 4.3104 mW. The +0.308 mA whole-stack increase is therefore not caused by the PMIC parent MFD or child drivers, and GPIO pull-ups alone do not recreate the low-current class. A final-idle TWIM22/GPIO register dump did not expose the mechanism: default-pinctrl and sleep-pinctrl images had identical visible TWIM22/P1.11/P1.15 register state after boot. A focused LED-disabled split, with the LED task still running but the visible indicator off, measured 0.897 mA / 3.316 mW on the stripped no-BLE floor, only about +0.004-0.005 mA above the no-LED-task floor. The earlier 0.102 mA LED delta is therefore dominated by the visible GPIO LED duty cycle. BLE was not visible in the first no-SEN6X 100 s pair, but repeated stripped-floor A/B windows bound BLE idle advertising/controller overhead at about +0.011 to +0.015 mA over 100 s-class windows. A paired 30 s / 100 ms-bin capture measured 0.945 mA BLE-on versus 0.894 mA BLE-off, or +0.050 mA in that short adjacent window, with no large radio spike visible at 100 ms resolution. The strongest BLE average-current isolation is now the paired 600 s stripped-floor run with display driver, environmental I2C, sensor task, UI, LED task, and SEN6X removed: 0.8705 mA / 3.2181 mW BLE-on versus 0.8601 mA / 3.1794 mW BLE-off, or +0.0105 mA / +0.0387 mW average. With SEN6X, UI, and LED tasks all disabled while BLE stayed enabled, steady draw reached 0.906 mA / 3.35 mW in the earlier longer windows. Slowing the nPM1300/fuel-gauge work item from 1 Hz to 60 s did not materially reduce that floor (0.903 mA / 3.34 mW), and skipping the application PMIC manager entirely measured 0.8919-0.8931 mA in the earlier display-driver-present branch, so the remaining sub-milliamp baseline is not explained by PMIC manager init/status reads or fuel-gauge scheduling. A System OFF measurement build also stayed at 0.894-0.895 mA, with no visible current collapse after the sys_poweroff() attempt under this bench setup.
This audit did not use live RTT during the validated captures, because attached debug sessions and logging can pollute absolute power results. Attribution below is based on the measured power signature, firmware schedule in main.c, sensor_task.c, sen6x_duty_manager.c, Kconfig defaults, and the retained RAM power-event trace dumped after the measurement window.
Measurements used the Joulescope MCP server and the same joulescope_mcp.service.Js220Service backend directly when the active Codex MCP transport timed out:
| Step | Tool path | Parameters |
|---|---|---|
| Device discovery | MCP list_devices / direct service list_devices() |
found u/js220/005920 |
| Power status | MCP target_power_status / direct service target_power_status() |
target power on, current range auto |
| Power cycle | MCP cycle_target_power |
off_ms=2000 or 3000, on_mode=auto, settle_ms=0 |
| Energy capture | MCP measure_energy / direct service measure_energy() |
3.70 V observed while the current path was enabled, autorange, 0.5 s, 1 s, and 0.1 s requested intervals; 5 s was requested for two continuation calls but the MCP returned shorter effective intervals |
| 300 s scheduled-event captures | direct joulescope_mcp.service.Js220Service.measure_energy() |
300.0 s, 0.1 s bins, full artifacts saved under docs/research/power/ after the active MCP transport closed; one repeat was no-reset fully booted and derived per-bin current from returned charge/energy fields |
| 930 s whole-duty-period capture | direct joulescope_mcp.service.Js220Service.measure_energy() |
930.0 s requested, 929.1 s actual, 1 s bins, full compact artifacts saved under docs/research/power/ after the active MCP transport closed |
| 2000 s normal multi-duty capture | direct joulescope_mcp.service.Js220Service.measure_energy() |
2000.0 s, 1 s bins, normal restored firmware, JS220 current-path power-cycled before capture; boot event plus two scheduled SEN66 events detected |
| 600 s BLE stripped-floor A/B capture | direct joulescope_mcp.service.Js220Service.measure_energy() |
Paired BLE-on/BLE-off images, 600.0 s each, 1 s bins, JS220 current-path power-cycled before each capture |
| Crash-triage power-cycle check | direct joulescope_mcp.service.Js220Service.measure_energy() plus probe-rs attach RTT |
Confirmed current-path power cycle with off_ms=3000, normal SEN66 high-load entry, low-milliamp post-SEN66 idle, and boot logs showing LED/PMIC/BLE/SEN66 startup |
| Marker check | MCP read_gpi / direct service read_gpi() |
all GPI pins low; no firmware event marker wired |
| Resumed-process MCP reachability check | MCP list_devices, target_power_status, read_gpi, and measure_energy |
The resumed Codex process reached JS220 u/js220/005920, target power was on/auto, GPI was 0x00000000, and a short 5 s / 0.1 s MCP energy check measured 1.174 mA average |
| Near-5-minute MCP timeout validation | MCP measure_energy |
295.0 s requested with 0.5 s bins; completed in 295.064 s with 294.6 s actual duration, 590 intervals, and 1.263 mA / 4.668 mW average |
| Near-5-minute MCP scheduled SEN66 capture | MCP measure_energy |
A second 295.0 s / 0.5 s MCP window caught a scheduled SEN66 event: 46.0 s, 1.1060 mAh event charge, 86.56 mA event average, 130.97 mA peak 0.5 s bin, and 1.0899 mAh incremental charge |
| Debug-probe-detached MCP capture | MCP set_target_power, cycle_target_power, and measure_energy |
Operator detached the external debug probe/SWD ribbon first. Target-power-off measured 0.0 mA by current-only validation; detached boot averaged 34.324 mA over 120.9 s, detached nominal averaged 1.266 mA over 180 s, and sequential 295 s duty windows caught a scheduled SEN66 event with a 5.595 mA 900 s projection. |
| Host-side physical blocker check | probe-rs list, direct JS220 target-power/GPI status, serial device listing |
J-Link USB-enumerated, JS220 target power on/auto, JS220 GPI 0x00000000; no target attach, reset, halt, or RTT performed |
| GPIO marker instrumentation build check | west build -p -d build-power-marker-sensorio2 ... -DEXTRA_CONF_FILE="prj.power_audit_event_trace.conf;prj.power_audit_marker_sensorio2.conf" -DDTC_OVERLAY_FILE=boards/power_audit_marker_sensorio2.overlay |
CONFIG_SIMPLEAIR_POWER_EVENT_TRACE=y plus CONFIG_SIMPLEAIR_POWER_EVENT_GPIO_MARKER=y linked with a measurement-only power-marker0 alias on SENSOR_IO2 / J7.5 / P0.04 after disabling the optional mmwave_mcu_in node; no marker capture taken yet |
| Retained event trace | probe-rs read from power_event_trace |
measurement-only .noinit RAM trace; boot/SEN66 trace captured 66 events and focused UI/EPD trace captured 23 events, both with no overflow and dumped after JS220 captures without resetting the target |
Limitations:
| Limitation | Impact |
|---|---|
| No JS220 GPI marker capture has been taken | Electrical phases now have retained firmware timestamps and a build-checked power-marker0 GPIO marker path. The checked-in SENSOR_IO2 marker image is only valid after confirming the J7 daughter/radar path is absent, unpowered, or intentionally disconnected; no safe pad has been physically wired to JS220 GPI and no current capture contains that edge. The operator confirmed this final physical step cannot be completed in the current setup, so the audit treats it as an explicit limitation rather than remaining open work. |
| No live RTT capture during measurement | Avoids probe pollution, but prevents exact log-to-current correlation. |
| Host-side physical blocker check does not prove ribbon state | probe-rs list still sees the J-Link over USB, but host enumeration alone does not prove whether the SWD ribbon is physically attached to the SimpleAir target. The check intentionally did not attach, reset, halt, or read target state. |
| Early captures used the debug-capable configuration | CONFIG_SIMPLEAIR_ENABLE_DEBUG=y, RTT logging and console are enabled in app/prj.conf; those numbers are for the debug-capable build, not a release floor. |
cycle_target_power returns just after power restore |
The first few milliseconds after power reapplication may be missed by the measurement call. |
| Earlier long-capture MCP transport instability | tool_timeout_sec = 300 is set in /Users/juanqui/.codex/config.toml. Earlier in this Codex process, the hosted MCP transport returned Transport closed, so long 300 s, 600 s, 930 s, and 2000 s captures below used the same local joulescope_mcp backend directly from the shell. After tool rediscovery in the resumed process, MCP list_devices, target_power_status, read_gpi, a short 5 s / 0.1 s measure_energy call, a near-5-minute nominal 295 s / 0.5 s measure_energy call, and a near-5-minute 295 s / 0.5 s scheduled SEN66 capture succeeded. All tables use completed captures only. In one no-reset 300 s capture, the direct backend returned per-bin charge/energy rather than explicit per-bin current/power, so the saved CSV derives current and power from charge, energy, and bin duration. |
| Detached MCP pass was not marker-synchronized | The external debug probe was detached for the MCP pass, but no firmware GPIO was wired to JS220 GPI. The run also used the current bench image rather than flashing a fresh release image immediately before capture. Treat it as the detached absolute-current check for the restored-normal image, not as a final release-floor or exact function-edge capture. |
| JS220 target-power-off voltage is not a DUT-rail proof | With JS220 target power set to off, the DUT load current path is open and measured current falls to approximately 0.0 mA, while the voltage sense can still report about 3.7 V from the upstream bench source. Voltage alone must not be interpreted as target current flow, DUT power delivery, DUT rail voltage, or evidence of a bypass supply. |
Precondition for any next JS220 run: ensure the DUT load current flows through the JS220 current path, with no battery or USB target-power source bypassing it. The validation check is current-path continuity: target-power-off should report approximately 0 mA through the JS220. The voltage sense may still show the upstream 3.7 V source after the load-current disconnect; that is expected for this setup and is not, by itself, evidence that the target is still powered.
The reusable capture helper scripts/capture_js220_power.py --target-power-off now writes a current_path_validation object into its JSON output. That gate is intentionally current-only: it passes on near-zero measured JS220 current and records voltage_used_for_validation=false because the JS220 can still sense the upstream bench source with the DUT current path open.
Current-path validation after the correction:
| Step | Window | Average current | Average voltage | Interpretation |
|---|---|---|---|---|
| Pre-disconnect normal idle | 30 s | 1.2267 mA | 3.7007 V | Fully booted, SEN66-off nominal state. |
| JS220 target power off | 5 s | 0.0000 mA | 3.7007 V | DUT load current path open; voltage sense still sees the upstream bench source. |
| Current path restored | 90 s | 42.9368 mA | 3.6990 V | Normal boot capture with first SEN66 high-current window reproduced. |
| JS220 target power off before trace boot | 5.5 s | 0.0000 mA | voltage sense still ~3.7006 V after first-bin artifact | DUT load current path open; voltage is not DUT-power evidence. |
| Current path restored, trace boot | 95 s | 42.0891 mA | 3.6989 V | RAM-traced normal boot; first SEN66 high-current window reproduced. |
| Corrected helper target power off, first 5 s | 5 s | 0.0711 mA | not returned by direct backend | Current range stayed off before and after capture; first bin had a 0.355 mA residual artifact and the remaining bins were 0.0 mA. |
| Corrected helper target power off, 10 s repeat | 10 s | 0.0000 mA | not returned by direct backend | Current range stayed off before and after capture; current-only validation passed with 0.0 mAh measured charge. |
| Corrected helper current-path restored | 120 s | 33.3482 mA | not returned by direct backend | Normal 0.1 s-bin boot after 3000 ms JS220 current-path power cycle; SEN66 span was 45.1 s at 86.03 mA average with a 133.84 mA max bin. |
| Corrected helper post-boot nominal | 180 s | 1.2587 mA | not returned by direct backend | Fully booted no-reset SEN66-off nominal window; median 1.077 mA and no bins above 40 mA. |
| Final restore after short BLE A/B work | 9.1 s | 79.2667 mA | first-bin artifact; min/max 3.655-3.735 V | Default normal firmware was rebuilt warning-clean, flashed, reset, and immediately showed the expected SEN66 boot load class. |
| Final restore after no-display-driver work | 90 s | 45.1462 mA | 3.6988 V | Default normal firmware rebuilt warning-clean, flashed, reset, then current-path power-cycled; normal SEN66 boot load reproduced. |
| Post-final-restore idle sanity | 5 s | 1.2709 mA | 3.7007 V | Same restored normal firmware after the boot window; SEN66-off idle returned to the expected nominal class. |
| Final restore after no-PMIC-stack work | 90 s | 44.9311 mA | 3.6988 V | Default normal firmware rebuilt warning-clean, flashed, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the PMIC-stack isolation image. |
| Post-no-PMIC-stack-restore idle sanity | 5 s | 1.1823 mA | 3.7007 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Final restore after no-PMIC-charger work | 90 s | 44.8646 mA | 3.6988 V | Default normal firmware rebuilt warning-clean, flashed with verify, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the charger-disabled isolation image. |
| Post-no-PMIC-charger-restore idle sanity | 5 s | 1.2653 mA | 3.7006 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Final restore after PMIC child-driver split work | 90 s | 45.1027 mA | 3.6987 V | Default normal firmware rebuilt warning-clean, flashed with verify, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the regulator-disabled isolation image. |
| Post-PMIC-child-split restore idle sanity | 5 s | 1.2710 mA | 3.7006 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Final restore after PMIC/I2C22 split work | 90 s | 45.4364 mA | 3.6986 V | Default normal firmware rebuilt warning-clean, flashed with verify, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the PMIC-node/I2C22 isolation image. |
| Post-PMIC/I2C22-split restore idle sanity | 5 s | 1.1824 mA | 3.7005 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Final restore after I2C22 pinctrl follow-up work | 90 s | 45.1717 mA | 3.6988 V | Default normal firmware rebuilt warning-clean, flashed with verify, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the I2C22 sleep-pinctrl/GPIO-pull-up images. |
| Post-I2C22-pinctrl-restore idle sanity | 5 s | 1.2679 mA | 3.7007 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Final restore after I2C22 register-dump work | 89.5 s | 45.4150 mA | voltage average field affected by first-bin artifact; min/max normal | Default normal firmware rebuilt warning-clean, flashed with verify, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the register-dump images. |
| Post-register-dump restore idle sanity | 5 s | 1.2730 mA | 3.7007 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Final restore after 600 s BLE A/B work | 90 s | 45.7905 mA | 3.6989 V average; min/max affected by first-bin artifact | Default normal firmware rebuilt warning-clean, flashed with verify, reset, then current-path power-cycled; normal SEN66 boot load reproduced after the long stripped BLE images. |
| Post-600 s BLE A/B restore idle sanity | 5 s | 1.2719 mA | 3.7008 V | Same restored normal firmware after the boot window; SEN66-off idle stayed in the expected nominal class. |
| Crash-triage pre-cycle idle | 3.5 s | 1.6520 mA | voltage average field affected by first-bin artifact; normal per-bin voltage after artifact | Board drew current before the cycle, so it was not electrically dead. |
| Crash-triage current-path restored | 12 s | 9.9775 mA | 3.7003 V | Normal early boot followed by SEN66 load onset near 10 s after current-path restore. |
| Crash-triage SEN66 high-load follow-up | 19.5 s | 89.4469 mA | voltage average field affected by first-bin artifact; normal per-bin voltage after artifact | Expected SEN66-on boot duty window; confirms firmware reached SEN66 duty manager. |
| Crash-triage post-SEN66 idle | 10 s | 1.2633 mA | 3.7007 V | Returned to normal low-milliamp SEN66-off idle after the boot duty window. |
| Warning-clean release target power off | 2.1 s actual | 0.0000 mA | voltage sense not DUT-power evidence while current path is off | DUT load current path open before the release boot capture. |
| Warning-clean release boot | 120 s | 32.9822 mA | 3.6994 V average; min/max affected by SEN66 pulse and range artifacts | Release candidate flashed with verify, JS220 current-path power-cycled, then booted into the normal SEN66 load window. |
| Warning-clean release post-boot idle | 20 s | 1.2288 mA | 3.7007 V | Same release image after the boot SEN66 window; SEN66-off idle stayed in the expected nominal class. |
| Default restore after release | 15 s | 19.5959 mA | 3.6977 V average; min/max affected by SEN66 pulse | Default debug-capable firmware restored after release measurement; SEN66 onset appeared in bins 11-14. |
| Restored-normal 0.1 s boot repeat | 120 s | 33.6909 mA | 3.6994 V mean of bins; min/max normal SEN66 sag/pulse | Default debug-capable firmware, JS220 current-path power-cycled, captured at 0.1 s bins; SEN66 span was 45.2 s at 86.92 mA with 134.70 mA max 0.1 s bin. |
| Detached MCP target power off | 9.5 s actual | 0.0000 mA | voltage sense still reported upstream source | External debug probe/SWD ribbon detached by operator; current-only validation passed with 0.0 mAh measured charge. |
| Detached MCP boot | 120.9 s actual | 34.3241 mA | 3.6716 V average | Current bench image, 3000 ms JS220 current-path power cycle, hosted MCP measurement at 0.1 s requested bins. |
| Detached MCP post-boot nominal | 180 s | 1.2657 mA | 3.7006 V | Adjacent fully booted SEN66-off window with debug probe detached. |
| Detached MCP duty/tail aggregate | 1179.6 s | 4.5675 mA | voltage arrays omitted from aggregate | Four sequential 295 s MCP windows to stay below the 300 s tool timeout; segment averages were 1.254, 1.260, 14.487, and 1.265 mA, with the scheduled SEN66 event in segment 3. |
| No-reset fully booted 300 s repeat | 300 s | 14.3322 mA | direct backend voltage not returned for this pass | Fully booted normal firmware without intentional power cycle; one scheduled SEN66 event appeared from 147.4-192.5 s, while the median 0.1 s bin was 1.080 mA. |
| Subsystem | Source | Active behavior |
|---|---|---|
| RTT/logging/debug | app/prj.conf:17-24, app/prj.conf:151-152 |
RTT backend and debug features enabled. |
| Display | app/prj.conf:65-68 |
Display and EPD driver enabled. |
| PMIC/fuel gauge | app/prj.conf:128-146 |
nPM13xx regulators, charger, LED driver, and Nordic fuel gauge enabled. |
| Sensors | app/prj.conf:35-52, app/prj.conf:178-187 |
SHT45 and DPS368 enabled; SGP41/STC31 disabled; SEN6X enabled with duty cycling and no forced recalibration. |
| BLE | app/prj.conf:189-225 |
BLE peripheral enabled, one connection, bonding/settings enabled. |
| LoRa | app/prj.conf:83-100, boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:196-203 |
LoRa disabled on RevE. |
| Sensor poll loop | app/src/tasks/sensor_task.h:6-8 |
120 s periodic SHT45/DPS368/PMIC sensor loop. |
| SEN66 interval | drivers/sensor/sen6x/Kconfig:110-126 |
900 s off interval, 45 s warmup. |
| Power-audit overlay | app/prj.power_audit.conf |
Disables debug flag, logging, RTT/UART console, printk, boot banners, and enables device power-management options for a measurement-only no-output build. |
| Layered release overlay | app/prj.release.conf |
Now disables the debug flag, logging, RTT/UART console, printk, boot banners, and asserts when merged with app/prj.conf; device runtime PM is enabled, while system CONFIG_PM remains unavailable on this target. |
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Average voltage |
|---|---|---|---|---|---|---|---|
| Live pre-check | 5 s | 0.5 s | 0.001756 mAh | 0.006490 mWh | 1.264 mA | 4.673 mW | 3.701 V |
| Boot 1 | 95 s | 0.5 s | 1.027285 mAh | 3.797284 mWh | 38.929 mA | 143.897 mW | 3.699 V |
| Boot 2 | 69.5 s | 1 s | 0.991895 mAh | 3.666537 mWh | 51.379 mA | 189.921 mW | 3.70 V stable after first-bin artifact |
| Boot 3 | 70 s | 1 s | 0.954640 mAh | 3.528923 mWh | 49.096 mA | 181.487 mW | 3.699 V |
| Boot 4 | 80.75 s | 0.252 s | 1.100707 mAh | 4.068462 mWh | 49.072 mA | 181.380 mW | 3.70 V stable after first-bin artifact |
| Boot 5 | 79.25 s | 0.991 s | 1.026116 mAh | 3.793014 mWh | 46.612 mA | 172.301 mW | 3.70 V stable after first-bin artifact |
| Boot 6 | 80.00 s | 1 s | 1.009216 mAh | 3.730691 mWh | 45.415 mA | 167.881 mW | 3.699 V |
| Boot 7 | 90.00 s | 0.5 s | 1.073420 mAh | 3.967729 mWh | 42.937 mA | 158.709 mW | 3.699 V |
| Boot 8, RAM-traced no-output | 95.00 s | 0.5 s | 1.110685 mAh | 4.105226 mWh | 42.089 mA | 155.566 mW | 3.699 V |
| Boot 9, final restored normal firmware | 90.00 s | 1 s | 1.128654 mAh | 4.171664 mWh | 45.146 mA | 166.867 mW | 3.699 V |
| Boot 10, final restored normal firmware after no-PMIC-stack work | 90.00 s | 1 s | 1.123278 mAh | 4.151700 mWh | 44.931 mA | 166.068 mW | 3.699 V |
| Boot 11, final restored normal firmware after no-PMIC-charger work | 90.00 s | 1 s | 1.121615 mAh | 4.145633 mWh | 44.865 mA | 165.825 mW | 3.699 V |
| Boot 12, final restored normal firmware after PMIC child-driver split work | 90.00 s | 1 s | 1.127567 mAh | 4.167475 mWh | 45.103 mA | 166.699 mW | 3.699 V |
| Boot 13, final restored normal firmware after PMIC/I2C22 split work | 90.00 s | 1 s stats | 1.135910 mAh | 4.198262 mWh | 45.436 mA | 167.930 mW | 3.699 V |
| Boot 14, final restored normal firmware after I2C22 pinctrl follow-up work | 90.00 s | 1 s stats | 1.129293 mAh | 4.173930 mWh | 45.172 mA | 166.957 mW | 3.699 V |
| Boot 15, final restored normal firmware after I2C22 register-dump work | 89.50 s | 0.994 s stats | 1.129068 mAh | 4.173104 mWh | 45.415 mA | 167.857 mW | voltage average affected by first-bin artifact; min/max normal |
| Boot 16, final restored normal firmware after 600 s BLE A/B work | 90.00 s | 1 s | 1.144761 mAh | 4.231273 mWh | 45.790 mA | 169.251 mW | 3.699 V |
| Boot 17, warning-clean release candidate | 120.00 s | 1.000 s | 1.099407 mAh | 4.063705 mWh | 32.982 mA | 121.911 mW | Probe-attached release measurement after verified flash and JS220 current-path power cycle; longer post-SEN66 idle tail lowers the 120 s average. |
| Boot 18, restored normal firmware 0.1 s repeat | 120.00 s | 0.100 s filtered | 1.123030 mAh | 4.150999 mWh | 33.691 mA | 124.530 mW | Direct backend returned an overlapping first statistics artifact for sub-second intervals; table uses non-overlapping 0.1 s bins. |
| Boot 19, corrected helper power-cycle 0.1 s repeat | 119.90 s analyzed | 0.100 s filtered | 1.110811 mAh | 4.105893 mWh | 33.348 mA | 123.265 mW | Direct backend after corrected target-power-off validation; SEN66 span was 45.1 s at 86.03 mA with 133.84 mA max 0.1 s bin. |
Boot 2, Boot 3, Boot 5, and Boot 6 are useful for 1 s phase segmentation because they include the full first SEN66 power window. Boot 18 and Boot 19 are the best electrical boot peak-current captures because they cover the complete boot SEN66 span at 0.1 s bins.
| Phase | Observed time from restore | Measured current | Attribution basis |
|---|---|---|---|
| Early boot and app init | firmware uptime 0.939-3.127 s in the trace; JS220 roughly 0-8/9 s before the large SEN66 sample window | 2.1-2.2 mA average before SEN66; first seconds include small startup spikes | main() initializes identity, LED blink/window, PMIC, settings, UI, sensor task, and comm manager before entering the main loop (app/src/main.c:107-387). Trace markers show LED init 0.939-2.941 s, PMIC init 2.941-3.043 s, settings 3.046-3.063 s, SEN6X settings 3.085-3.104 s, sensor task init at 3.104 s, BLE init 3.104-3.127 s, and boot complete at 3.127 s. |
| SHT45/DPS368 first poll | firmware uptime 3.129-3.195 s | too small to isolate in 0.5 s JS220 bins | Retained trace marks SHT45 fetch 3.129-3.139 s and DPS368 fetch 3.139-3.195 s. |
| SEN66 first probe | firmware uptime 4.105-4.406 s | small transient only | Duty manager powers BUCK1/SEN66 on for reprobe at 4.105 s, reprobes at 4.255-4.327 s, then stops/powers off by 4.406 s. |
| SEN66 first scheduled duty wake | firmware uptime 9.406 s power-on; JS220 high-current bins begin around the same 8-9 s region after power restore | current jumps above 50 mA | Duty manager starts 1 s after init, probes SEN66, enters calibrated state, powers it off, then schedules first measurement 5 s later (app/src/tasks/sen6x_duty_manager.c:497-522, app/src/tasks/sen6x_duty_manager.c:417-438). |
| SEN66 warmup and measurement | firmware uptime 9.406-55.917 s powered interval; measurement command starts 9.578 s; read starts 54.630 s | Boot 2: 77.31 mA avg, 121.96 mA 1 s max. Boot 3: 73.81 mA avg, 115.74 mA 1 s max. Boot 8: whole 95 s window averaged 42.09 mA, with the same SEN66 plateau signature. | Firmware waits 45 s after powering SEN66 and starting measurement, then reads data, saves VOC state, stops measurement, and powers BUCK1 off (app/src/tasks/sen6x_duty_manager.c:441-484). The trace shows read 54.630-54.674 s, callback at 54.674 s, stop 54.767-55.867 s, and BUCK1/SEN66 power-off 55.867-55.917 s. |
| Post-SEN66 settling | after firmware uptime 55.917 s | drops from 19-28 mA bins to ~3-4 mA bins, then steady baseline | stop_measurement() and power_off_sensor() include the SEN66 stop path and a 50 ms post-fall I2C bus settle (app/src/tasks/sen6x_duty_manager.c:232-243, app/src/tasks/sen6x_duty_manager.c:131-159). |
| Fully booted nominal | after SEN66 off | 1.234 mA average over 90 s | Measured directly after Boot 3. Includes BLE advertising/scheduler bursts, PMIC/fuel gauge activity, idle tasks, and no SEN66 high-power window. |
| Finding | Evidence |
|---|---|
| SEN66 dominates boot energy. | The high-power duty window contributes about 0.943-0.981 mAh per boot-cycle capture, versus the post-boot baseline at 1.234 mA. |
| The measured SEN66 window duration matches firmware configuration. | Repeated boot captures show about 45.6-49 s of elevated current; Kconfig default warmup is 45 s and firmware adds read/stop/poweroff overhead (drivers/sensor/sen6x/Kconfig:119-126). |
| Rail cutting is working. | Current falls back to the 1.2-1.3 mA nominal baseline after the measured high-power window. Firmware disables BUCK1 through pmic_manager_set_sen6x_power(false) (app/src/tasks/pmic_manager.c:587-590) and the board maps BUCK1 to SEN6X (boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:221-229). |
| Continuous SEN66 operation would be untenable for battery operation. | In-repo SEN66 notes cite 90 mA typical continuous measurement and 300-350 mA peak pulses (docs/devices/sen6x.md:488-503). The measured duty window is below the 90 mA continuous typical once averaged over 1 s bins, but still dominates the budget. |
| Current duty-cycle average is still large. | The first complete scheduled SEN66 event projects to 5.56 mA average every 900 s using the measured nominal baseline. A later 300 s / 0.1 s-bin repeat event projects 5.50 mA using sub-2 mA idle bins, or about 5.67 mA with the more conservative 1.2646 mA nominal baseline. A no-reset fully booted 300 s / 0.1 s-bin repeat event projects 5.46 mA using its sub-2 mA idle bins. Whole-duty-period captures measured 5.559 mA over 929.1 s and 5.542 mA over 929.6 s directly. A 2000 s normal-firmware capture found two repeated scheduled events averaging 1.1323 mAh incremental over idle and projecting 5.765 mA for the steady 900 s schedule. |
SEN66 15 minute duty-cycle estimate:
| Basis | High window | High current | Baseline | Estimated 900 s average |
|---|---|---|---|---|
| Boot 2 | 45.67 s | 77.31 mA | 1.234 mA | 5.09 mA |
| Boot 3 | 46.00 s | 73.81 mA | 1.234 mA | 4.94 mA |
These boot-only estimates are kept as reproducibility checks. They have been superseded for forecasting by the complete scheduled 900 s event captures later in this audit. The first complete scheduled event measured an 85.34 mA high-load average and projects to 5.56 mA; the later 0.1 s-bin repeat measured 87.96 mA over the main event segment and stays within the same forecast band.
Additional resumed power-cycle checks after correcting the JS220 current-path interpretation:
| Run | Effective interval | Full-window average | Estimated high window | Peak interval current | Notes |
|---|---|---|---|---|---|
| Boot 4 | 0.252 s | 49.07 mA | ~49 s at ~80 mA | ~131 mA over 0.252 s | Best sub-second peak visibility; total charge 1.1007 mAh. |
| Boot 5 | 0.991 s | 46.61 mA | ~45.6 s at ~80 mA | ~119 mA over 0.991 s | Full current-path power cycle; post-window bins return to ~1.1-1.6 mA. |
| Boot 6 | 1.000 s | 45.41 mA | ~47 s at ~76 mA | ~117 mA over 1.000 s | Full current-path power cycle; post-window bins return to ~1.1-1.6 mA. |
| Boot 7 | 0.500 s | 42.94 mA | ~44.5 s above ~50 mA | ~126 mA over 0.500 s | Captured immediately after explicit JS220 target-power-off validation and target-power restore; post-window bins return to ~1.1-1.6 mA. |
| Boot 8, RAM-traced no-output | 0.500 s | 42.09 mA | firmware trace: 46.51 s SEN66 powered interval | max bin not separately extracted; voltage min/max showed SEN66 pulse sag | Captured after explicit JS220 target-power-off validation. Trace confirms SEN66 power-on at 9.406 s and rail-off at 55.917 s firmware uptime. |
| Boot 9, final restored normal firmware | 1.000 s | 45.15 mA | ~46 s at high load from the 1 s charge bins | ~131 mA equivalent in the largest 1 s charge bin | Captured after rebuilding and flashing default firmware over the no-display-driver measurement image; post-window 5 s sanity returned to 1.271 mA. |
| Boot 11, final restored normal firmware after no-PMIC-charger work | 1.000 s | 44.86 mA | ~46 s at high load from the 1 s charge bins | high-current class reproduced; peak not separately reduced from the compact output | Captured after rebuilding and flashing default firmware over the charger-disabled measurement image; post-window 5 s sanity returned to 1.265 mA. |
| Boot 12, final restored normal firmware after PMIC child-driver split work | 1.000 s | 45.10 mA | ~46 s at high load from the 1 s charge bins | high-current class reproduced; voltage min/max showed normal SEN66 pulse sag | Captured after rebuilding and flashing default firmware over the regulator-disabled measurement image; post-window 5 s sanity returned to 1.271 mA. |
| Boot 13, final restored normal firmware after PMIC/I2C22 split work | 1.000 s | 45.44 mA | ~46 s at high load from the 1 s charge bins | high-current class reproduced; voltage min/max showed normal SEN66 pulse sag | Captured after rebuilding and flashing default firmware over the PMIC-node/I2C22 measurement image; post-window 5 s sanity returned to 1.182 mA. |
| Boot 14, final restored normal firmware after I2C22 pinctrl follow-up work | 1.000 s | 45.17 mA | ~46 s at high load from the 1 s charge bins | high-current class reproduced; voltage min/max showed normal SEN66 pulse sag | Captured after rebuilding and flashing default firmware over the I2C22 sleep-pinctrl/GPIO-pull-up measurement images; post-window 5 s sanity returned to 1.268 mA. |
| Boot 15, final restored normal firmware after I2C22 register-dump work | 0.994 s | 45.42 mA | ~46 s at high load from the 0.994 s charge bins | high-current class reproduced; voltage min/max showed normal SEN66 pulse sag | Captured after rebuilding and flashing default firmware over the I2C22 register-dump images; post-window 5 s sanity returned to 1.273 mA. |
| Boot 16, final restored normal firmware after 600 s BLE A/B work | 1.000 s | 45.79 mA | ~46 s at high load from the 1 s charge bins | 133.21 mA 1 s-bin peak; voltage min/max showed normal SEN66 pulse sag plus first-bin artifact | Captured after rebuilding and flashing default firmware over the long stripped BLE measurement images; post-window 5 s sanity returned to 1.272 mA. |
| Boot 17, warning-clean release candidate | 1.000 s | 32.98 mA over 120 s full window | 45 SEN66-class bins from 11-56 s | 127.11 mA 1 s-bin peak; SEN66-class bins averaged 84.88 mA and consumed 1.0610 mAh / 3.9219 mWh | Captured after verified flashing of build-release-layered/zephyr/zephyr.signed.hex; post-window 20 s idle measured 1.229 mA. |
| Boot 18, restored normal firmware 0.1 s repeat | 0.100 s filtered | 33.69 mA over 120 s full window | 45.2 s at high load from 10.1-55.3 s | 134.70 mA 0.1 s-bin peak; 258.75 mA max instantaneous current inside a stats bin; SEN66-class span averaged 86.92 mA and consumed 1.0913 mAh / 4.0338 mWh | Captured with the direct JS220 backend after a 3000 ms current-path power cycle; non-overlapping 0.1 s bins exclude the backend's first overlapping statistics artifact. |
The resumed cycles, retained trace, and 0.1 s boot repeat support the earlier conclusion: boot energy variation is mostly how the first SEN66 duty window aligns with the capture window and binning, not a different dominant subsystem.
Crash-triage RTT after the no-blink observation showed normal application execution, not a hard fault or boot stall. The captured boot logs included Starting 2-second rapid LED blink for SWD programming window, LED hardware initialized, LED task initialized, LED pattern updated: 1, On: 100 ms, Off: 3000 ms, LED: BATTERY mode (slow heartbeat), PMIC init success, BLE advertising start, SEN66 product-name/serial detection, and SEN66 warmup start. The LED path in the active image was therefore configured, but the steady battery-mode heartbeat is only a 100 ms pulse every 3 s.
| Subsystem | Measured/observed contribution | Notes |
|---|---|---|
| SHT45 | About +0.0049 mA upper-bound at a 1 Hz forced-read cadence | Enabled on I2C21 at 0x44 (boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:154-159). Initial and periodic reads occur in sensor_task_thread() (app/src/tasks/sensor_task.c:474-512). A measurement-only SHT45 probe task at 1 Hz measured 0.900 mA against a 0.895 mA stripped baseline. That includes probe-thread wake overhead, so the sensor read itself is at or below about 18 uJ/read in this setup. |
| DPS368 | About +0.0554 mA upper-bound at a 1 Hz forced-read cadence | Enabled on I2C21 at 0x77 (boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:161-165). Reads occur after SHT45 (app/src/tasks/sensor_task.c:515-540). A measurement-only DPS368 probe task at 1 Hz measured 0.950 mA against a 0.895 mA stripped baseline. That includes probe-thread wake overhead, so the pressure/temperature read path is at or below about 205 uJ/read in this setup. |
| SGP41 | Off in this build | CONFIG_SGP41=n (app/prj.conf:47-49) and DT node disabled (boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:174-179). |
| STC31 | Off in this build | CONFIG_STC31=n (app/prj.conf:51-52) and DT node disabled (boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:181-185). |
| LoRa/SX1262 | Off in this build | LoRa Kconfig is commented out and SPI21 is disabled on RevE (app/prj.conf:83-100, boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi:196-203). |
| BLE | Best long-window stripped-floor average is +0.0105 mA / +0.0387 mW; short windows ranged from +0.011 to +0.015 mA over 100 s-class runs and +0.050 mA in one paired 30 s / 100 ms-bin run | BLE enable and advertising are initialized through comm manager and BLE manager (app/src/main.c:358-374, app/src/tasks/comm_manager.c:147-226, app/src/tasks/ble_manager.c:433-514). The first no-SEN6X/no-BLE pair was inconclusive because UI/LED/sensor task variance masked the radio term. Repeated no-SEN6X/no-sensor/no-UI/no-LED/FG60 captures measured BLE-enabled windows at 0.900-0.917 mA and BLE-disabled windows at 0.8920-0.8927 mA. The resulting 100 s-class average delta is small: about +0.015 mA including the first post-flash BLE-on window, or about +0.011 mA using the two later BLE-on windows. A paired 30 s / 100 ms-bin run measured 0.945 mA with BLE enabled and 0.894 mA with BLE disabled; the 100 ms bins stayed in the sub-1 mA class and did not expose a large discrete advertising spike. The cleaner 600 s deep-stripped pair measured 0.8705 mA BLE-on and 0.8601 mA BLE-off, confirming about +10.5 uA average BLE/controller/advertising overhead in this build. Exact radio-event energy still needs a marker or radio trace. |
| PMIC/fuel gauge | Application PMIC manager and individual PMIC child drivers are not measurable contributors on the stripped floor; the earlier whole-stack increase tracks the I2C22/TWIM22 configuration path, not PMIC MFD/child software | PMIC status is sampled during boot and sensor loop (app/src/main.c:249-273, app/src/tasks/sensor_task.c:453-462). The recurring fuel-gauge tick cadence was isolated separately below; skipping pmic_manager_init() and its boot status read measured 0.8919-0.8931 mA in the earlier display-driver-present branch, and 0.8609 mA with display/env I2C also removed. Disabling only individual PMIC child areas measured near that exact baseline: charger/fuel gauge 0.8666 mA, LED 0.8602 mA, GPIO 0.8619 mA, and regulators 0.8627 mA. PMIC-parent-MFD-only averaged 0.8692 mA, and PMIC-node-disabled with I2C22 kept active averaged 0.8696 mA. Forcing I2C22 to use sleep pinctrl while still enabled averaged 1.1699 mA, and parking P1.11/P1.15 as GPIO inputs with pull-ups while I2C22 was disabled averaged 1.1660 mA. Final-idle register dumps did not explain the default-vs-sleep split because the visible TWIM22/P1.11/P1.15 state was identical after boot; the remaining mechanism needs init-time or lower-level power-domain instrumentation. PMIC always-on/regulator hardware baseline is still not separately isolated from the MCU/platform floor. |
| UI/EPD | UI task/render-only overhead is near the stripped floor; display hardware writes/post-write state are the visible cost | UI task is initialized at boot (app/src/main.c:314-336). A focused no-SEN6X/no-sensor/no-LED/no-BLE/FG60 trace measured display writes at 5.155-6.415 s, 63.015-64.956 s, and 183.096-185.036 s firmware uptime (app/src/tasks/ui_task.c:520-555). The first 100 s JS220 window captured the first two write clusters. A follow-up build that kept UI/rendering active but skipped display_write() measured 0.897 mA, essentially the same as the 0.892-0.893 mA stripped no-UI/no-LED/no-BLE floor. Disabling the display subsystem, EPD node, and SPI20 entirely lowered the stripped no-BLE floor further to 0.8615 mA mean, so display-driver/SPI20 init state accounts for only about 0.031-0.034 mA. The older no-SEN6X/no-UI A/B reduction is real, but it is not caused by RAM rendering or the 100 ms UI loop alone. |
| LED task / visible LED | About 0.102 mA in no-SEN6X A/B, dominated by the visible GPIO LED duty cycle | Disabling led_task_init() in the no-SEN6X power-audit build reduced steady current from 1.266 mA to 1.164 mA. A stripped split with the LED task still running but led_config.enabled=false measured 0.897 mA versus 0.892-0.893 mA for the no-LED-task no-BLE floor. That leaves only about +0.004-0.005 mA for LED task overhead in that branch; the visible LED dominates the earlier 0.102 mA delta. |
| nPM1300/fuel gauge | Recurring 1 Hz fuel-gauge tick not measurable as average-current overhead on the stripped floor | pmic_manager schedules fuel_gauge_update() with CONFIG_SIMPLEAIR_MEASURE_FG_PERIOD_S (app/src/tasks/pmic_manager.c:69-80, app/src/tasks/pmic_manager.c:271-282). Slowing that cadence from 1 s to 60 s changed the no-SEN6X/no-UI/no-LED steady floor from 0.906 mA to 0.903 mA, which is within repeatability. |
| System OFF attempt | 0.894-0.895 mA under current bench setup | A measurement-only build with CONFIG_SIMPLEAIR_MEASURE_POWEROFF_AT_BOOT=y calls sys_poweroff() after 30 s. JS220 measured 0.895 mA over the boot-to-off window and 0.894 mA over the following 100 s. No visible step occurred at the expected 30 s point. Treat this as a bench-observed floor, not as an nRF54L15 silicon System OFF current. |
Measured after the first SEN66 duty window had completed:
| Duration | Total charge | Total energy | Average current | Average power | Voltage |
|---|---|---|---|---|---|
| 90 s | 0.030850 mAh | 0.114050 mWh | 1.234 mA | 4.562 mW | 3.701 V |
Nominal 1 s bins ranged from 1.084 mA to 1.543 mA. The repeating pattern is consistent with low-duty BLE advertising and periodic firmware wakeups, not SEN66 operation.
Additional measurements were taken on 2026-05-16, after the Codex MCP config had been changed for future 5 minute Joulescope calls. The running Codex process still behaved best with sub-120 s measurement calls, so the extended coverage uses consecutive short windows.
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Average voltage |
|---|---|---|---|---|---|---|---|
| Nominal A | 95 s | 1 s | 0.033385 mAh | 0.123428 mWh | 1.265 mA | 4.677 mW | 3.701 V |
| Nominal B | 95 s | 1 s | 0.033390 mAh | 0.123444 mWh | 1.265 mA | 4.678 mW | 3.701 V |
| Nominal C | 95 s | 1 s | 0.033337 mAh | 0.123251 mWh | 1.263 mA | 4.671 mW | 3.701 V |
| Current-path pre-disconnect idle | 30 s | 0.5 s | 0.010223 mAh | 0.037793 mWh | 1.227 mA | 4.535 mW | 3.701 V |
| Post-restore normal firmware idle | 100 s | 0.5 s | 0.035322 mAh | 0.130579 mWh | 1.272 mA | 4.701 mW | 3.701 V |
| Post-final-restore nominal A | 30 s | 1 s stats | 0.010312 mAh | 0.038123 mWh | 1.237 mA | 4.575 mW | 3.701 V |
| Post-final-restore nominal B | 30 s | 1 s stats | 0.010319 mAh | 0.038148 mWh | 1.238 mA | 4.578 mW | 3.701 V |
| Post-final-restore nominal C | 30 s | 1 s stats | 0.010311 mAh | 0.038117 mWh | 1.237 mA | 4.574 mW | 3.701 V |
| Post-PMIC/I2C22-split restore idle sanity | 5 s | 1 s stats | 0.001642 mAh | 0.006071 mWh | 1.182 mA | 4.371 mW | 3.701 V |
| 300 s scheduled-event repeat, whole window | 300 s | 0.1 s stats | 1.204827 mAh | 4.453252 mWh | 14.458 mA | 53.439 mW | 3.700 V |
| No-reset fully booted 300 s repeat, whole window | 300 s | 0.1 s stats | 1.197930 mAh | 4.427811 mWh | 14.332 mA | 52.975 mW | not returned by direct backend |
| Corrected helper post-boot nominal | 180 s | 0.1 s stats | 0.062936 mAh | 0.232671 mWh | 1.259 mA | 4.653 mW | not returned by direct backend |
Mean of the original three continuation windows: 1.2646 mA / 4.675 mW. The three final post-restore 30 s statistics windows averaged 1.2377 mA / 4.5755 mW over 90 s, with 1 s bins alternating between roughly 1.09 mA and 1.53 mA and sub-second current peaks up to about 90 mA within individual bins. Including the earlier 90 s post-boot capture gives 1.2569 mA. The corrected-helper 180 s post-boot window averaged 1.2587 mA, matching that nominal class while providing 0.1 s burst detail with no SEN66 event. For forecasts below, 1.2646 mA is used as the current debug-build SEN66-off baseline because it is slightly conservative and came from longer 95 s windows.
The post-restore normal firmware idle rows were captured after returning from measurement images to the normal signed app image. They confirm the board was not left in measurement-only firmware state and that the normal firmware returned to the same 1.2-1.3 mA SEN66-off baseline used for the forecast. A requested 280 s measure_energy call and shorter 110 s/90 s follow-ups timed out at the live session's 120 s tool-call boundary; the Codex config contains tool_timeout_sec = 300, but the running process still required sub-120 s calls. The final repeated nominal windows therefore used capture_statistics at 1 Hz.
Additional post-event rail-off checks:
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Average voltage |
|---|---|---|---|---|---|---|---|
| Pre-event nominal slice | 40 s | 1 s | 0.013697 mAh | 0.050638 mWh | 1.233 mA | 4.557 mW | 3.701 V |
| Post-event nominal slice | 40 s | 1 s | 0.013574 mAh | 0.050186 mWh | 1.222 mA | 4.517 mW | 3.701 V |
| Post-cycle nominal slice | 60 s | 1 s | 0.020655 mAh | 0.076363 mWh | 1.239 mA | 4.582 mW | 3.701 V |
| Long nominal sensor-loop slice | 110.5 s | 0.502 s | 0.038426 mAh | 0.142063 mWh | 1.252 mA | 4.628 mW | 3.70 V stable after first-bin artifact |
The post-event slice confirms BUCK1/SEN66 returned to the same low-current state after the scheduled duty window.
A 20 s nominal capture requested interval_s=0.1; the MCP returned 200 bins at 0.1045 s effective interval. The total was 0.006799 mAh / 0.025135 mWh, or 1.171 mA / 4.329 mW average. The first average-voltage bin was an artifact, but min/max and the remaining voltage bins stayed around the same 3.70 V rail.
At 100 ms resolution, the low bins are about 1.02-1.07 mA, while recurring short bursts reach about 3.7 mA when averaged over one 104.5 ms bin. Those bursts are consistent with BLE advertising and periodic firmware wakeups. They are much smaller than the SEN66 fan/laser load and explain why 1 s nominal bins alternate between roughly 1.08 mA and 1.54 mA.
The 110.5 s nominal capture at 0.502 s bins averaged 1.252 mA / 4.628 mW. Its largest half-second bin was about 3.75 mA, and the only multi-bin burst cluster was still below the SEN66-off milliwatt scale. That capture likely includes one sensor_task 120 s poll window, so SHT45, DPS368, PMIC status, BLE advertising, and scheduler wakeups remain in the low single-milliamp burst class unless A/B isolation proves otherwise.
After the active Codex MCP transport timed out on long captures, the same local joulescope_mcp.service.Js220Service backend was run directly from the shell for one completed 300 s capture at 0.1 s bins. Target power was verified on/auto immediately before the run. Compact artifacts are saved in:
| Artifact | Contents |
|---|---|
docs/research/power/2026-05-16-nominal-300s-0p1s-summary.json |
Whole-window totals, percentiles, and detected high-current segments. |
docs/research/power/2026-05-16-nominal-300s-0p1s-event-analysis.json |
Idle/event split and 900 s projection from this capture. |
docs/research/power/2026-05-16-nominal-300s-0p1s-samples.csv |
3000 per-bin rows with current, power, voltage, charge, and energy. |
The 300 s window includes one complete scheduled SEN66 event:
| Metric | Value |
|---|---|
| Whole-window charge / energy | 1.204827 mAh / 4.453252 mWh |
| Whole-window average | 14.458 mA / 53.439 mW |
| Sub-2 mA idle bins | 235.0 s at 1.0966 mA / 4.0540 mW |
| Main high-current segment, >=5 mA | 45.6 s at 87.96 mA / 325.13 mW |
| Main high-current segment, >=25 mA | 45.1 s at 88.77 mA |
| Highest 0.1 s bin | 133.10 mA / 491.71 mW |
| Incremental charge above sub-2 mA idle | 1.1003 mAh |
| Incremental energy above sub-2 mA idle | 4.0669 mWh |
| 900 s average using this event and sub-2 mA idle bins | 5.50 mA / 20.32 mW |
The repeat event is slightly higher than the first complete scheduled capture, but it does not materially move the forecast. Using the repeat event with the more conservative 1.2646 mA nominal baseline projects about 5.67 mA for the 900 s schedule, versus 5.56 mA from the first complete event. The field forecast below already covers this spread through the conservative self-discharge, usable-capacity, and aged-pack cases.
A later no-reset fully booted 300 s capture was taken without intentionally power-cycling the board, after a 3 s spot read had shown 1.762 mA. It caught another complete scheduled SEN66 event, proving that the spot read was not a new steady floor:
| Artifact | Contents |
|---|---|
docs/research/power/2026-05-16-fully-booted-nominal-300s-0p1s-summary.json |
No-reset fully booted 300 s totals, derived per-bin statistics, and detected high-current clusters. |
docs/research/power/2026-05-16-fully-booted-nominal-300s-0p1s-samples.csv |
2991 retained rows; current and power are derived from per-bin charge/energy returned by the direct backend. |
| Metric | Value |
|---|---|
| Whole-window charge / energy | 1.197930 mAh / 4.427811 mWh |
| Whole-window average | 14.332 mA / 52.975 mW |
| Median 0.1 s bin | 1.080 mA |
| Sub-2 mA bins | 237.5 s at 1.091 mA / 4.034 mW |
| Main high-current segment, >=40 mA | 45.1 s at 88.30 mA / 326.35 mW |
| Highest 0.1 s bin | 132.65 mA |
| Incremental charge above sub-2 mA idle | 1.0925 mAh |
| Incremental energy above sub-2 mA idle | 4.0379 mWh |
| 900 s average using this event and sub-2 mA idle bins | 5.46 mA / 20.19 mW |
This no-reset repeat aligns with the earlier scheduled-event captures and strengthens the conclusion that the normal fully booted non-SEN66 baseline is near 1.1-1.3 mA, while the 15 minute average is dominated by the SEN66 duty window.
A direct-service 930 s capture at 1 s bins was run to validate the stitched event-plus-baseline forecast against one contiguous duty-period-scale window. The JS220 returned 929.1 s actual duration and captured one scheduled SEN66 event from 603.1 s to 649.1 s.
| Artifact | Contents |
|---|---|
docs/research/power/2026-05-16-long-930s-1s-summary.json |
Whole-window totals, percentiles, and detected high-current segments. |
docs/research/power/2026-05-16-long-930s-1s-event-analysis.json |
Idle/event split and projection for the long-window capture. |
docs/research/power/2026-05-16-long-930s-1s-samples.csv |
930 per-bin rows with current, power, voltage, charge, and energy. |
| Metric | Value |
|---|---|
| Actual capture duration | 929.1 s |
| Whole-window charge / energy | 1.434772 mAh / 5.303288 mWh |
| Whole-window average | 5.559 mA / 20.549 mW |
| Idle bins below 2 mA | 871 s at 1.2386 mA / 4.5790 mW |
| Main SEN66 segment, >=5 mA | 46.0 s at 88.06 mA / 325.46 mW |
| Main SEN66 segment, >=50 mA | 44.0 s at 90.22 mA |
| Highest 1 s bin | 128.96 mA / 476.45 mW |
| Incremental charge above sub-2 mA idle | 1.1093 mAh |
| Projection from event plus idle bins | 5.68 mA / 20.98 mW |
The whole-window measured average, 5.559 mA, is a strong single-window validation of the current 15 minute schedule forecast. Its voltage average includes a first-bin statistics artifact, but median and p01/p99 average-voltage bins stayed around 3.70 V; the current/charge/energy totals are the values used here.
A second corrected-helper 930 s capture was then run after a current-only JS220 target-power-off validation, a restored normal 0.1 s boot, and an adjacent 180 s post-boot nominal capture. It was fully booted and did not intentionally reset the board before measurement. It requested 930.0 s at 0.5 s bins and returned 929.6 s of analyzed data.
| Artifact | Contents |
|---|---|
docs/research/power/2026-05-16-corrected-duty-period-930s-0p5s.json |
Corrected-helper full duty-period totals, percentiles, and detected high-current segments. |
docs/research/power/2026-05-16-corrected-duty-period-930s-0p5s.csv |
1860 per-bin rows for the corrected 0.5 s duty-period capture. |
| Metric | Value |
|---|---|
| Actual capture duration | 929.6 s |
| Whole-window charge / energy | 1.431189 mAh / 5.290039 mWh |
| Whole-window average | 5.542 mA / 20.486 mW |
| Median 0.5 s bin | 1.092 mA |
| Main SEN66 segment, >=40 mA | 45.0 s at 89.37 mA |
| Highest 0.5 s bin | 132.84 mA |
| Bins below 1.2 mA | 706.0 s |
This second duty-period-scale result independently reproduces the earlier 5.559 mA whole-window validation. It also aligns with the 2000 s repeated-event model, while landing slightly lower because it captures one event and a long fully booted idle interval instead of boot plus two later events.
The file pair 2026-05-16-sen66-repeatability-forecast.json and .csv combines seven scheduled non-boot SEN66 events and the two full-duty windows into one deterministic repeatability summary. It is regenerated by scripts/analyze_sen66_repeatability.py from saved artifacts only: the two 300 s / 0.1 s event captures, the original 929.1 s validation, the corrected 929.6 s validation, the two non-boot scheduled events from the 2000 s normal capture, and the later MCP-hosted 295 s / 0.5 s scheduled-event summary.
| Metric | Value |
|---|---|
| Scheduled SEN66 events included | 7 |
| Scheduled event incremental charge above idle | 1.090-1.132 mAh, mean 1.110 mAh |
| Scheduled event duration | 45.6-47.0 s, mean 46.20 s |
| Scheduled event average current | 86.33-89.85 mA, mean 87.67 mA |
| Scheduled event peak bin current | 128.96-136.23 mA, mean 132.27 mA |
| Full-duty-window averages | 5.542 and 5.559 mA, mean 5.551 mA |
The repeatability artifact also gives a compact runtime sensitivity using measured event min/mean/max rather than one point estimate. Under the 15 minute schedule, 10 Ah nominal pack, 27 C, and 3%/month self-discharge, the event-spread-only range is 60.4-62.1 days at 90% usable capacity and 53.7-55.2 days at 80% usable capacity. That narrow spread means product-level runtime uncertainty is dominated more by usable capacity, self-discharge, debug/probe-detached baseline, and future sleep-state behavior than by SEN66 event-to-event repeatability.
A longer direct-service capture was run after restoring the default normal firmware, flashing with verify, and power-cycling through the JS220 current path. This 2000 s window intentionally includes boot plus two later scheduled SEN66 events, so the whole-window average includes extra boot energy and should not be used as the steady schedule current. The parsed scheduled events excluding boot are the better forecast input.
| Artifact | Contents |
|---|---|
docs/research/power/2026-05-16-long-2000s-normal-1s-summary.json |
2000 s normal-firmware whole-window totals and percentiles. |
docs/research/power/2026-05-16-long-2000s-normal-1s-event-analysis.json |
Boot/scheduled event split, idle baseline, and steady 900 s projection. |
docs/research/power/2026-05-16-long-2000s-normal-1s-non-sen-burst-analysis.json |
Non-SEN66 idle/burst classification and cadence correlation. |
docs/research/power/2026-05-16-long-2000s-normal-1s-samples.csv |
2000 per-bin rows with current, power, voltage, charge, and energy. |
docs/research/power/2026-05-16-battery-forecast-multi-cycle-update.json |
Forecast scenarios derived from the two repeated scheduled events in the 2000 s capture. |
docs/research/power/2026-05-16-sen66-repeatability-forecast.json |
Combined repeatability and runtime-sensitivity summary across seven scheduled SEN66 events and two full-duty windows. |
docs/research/power/2026-05-16-sen66-repeatability-forecast.csv |
CSV export of the repeatability artifact's deterministic event-min/mean/max forecast rows. |
| Metric | Value |
|---|---|
| Actual capture duration | 2000.0 s |
| Whole-window charge / energy | 4.058020 mAh / 14.999692 mWh |
| Whole-window average, includes boot | 7.304 mA / 26.999 mW |
| Idle bins below 2 mA | 1828 s at 1.2355 mA / 4.5675 mW |
| Boot SEN66 segment, >=5 mA | 47 s at 84.95 mA, 128.54 mA max |
| Scheduled SEN66 event 1, >=5 mA | 46 s at 89.85 mA, 131.17 mA max |
| Scheduled SEN66 event 2, >=5 mA | 47 s at 87.96 mA, 136.23 mA max |
| Mean scheduled incremental charge above idle | 1.1323 mAh |
| Mean scheduled incremental energy above idle | 4.1851 mWh |
| Projection from repeated scheduled events plus idle bins | 5.765 mA / 21.308 mW |
The two non-boot scheduled events are very close: 1.13233 mAh and 1.13224 mAh incremental above idle. This is the strongest repeatability check so far for the natural 900 s SEN66 cadence. It shifts the measured steady schedule estimate upward slightly versus the 929.1 s single-event whole-window average, but keeps the practical conclusion unchanged: the 15 minute policy is a roughly 5.6-5.8 mA load before self-discharge.
The same 2000 s capture also improves the non-SEN66 attribution. Excluding the three SEN66 plateaus, normal firmware averaged 1.263 mA / 4.668 mW over 1860 s. About 64.5% of non-SEN time was below 1.2 mA and centered at 1.093 mA. About 30.3% of non-SEN time sat in 1.4-2.0 mA one-second bins; their median inter-bin gap was 3 s, consistent with small periodic scheduler/BLE/PMIC/UI wakeups, but not separable without markers. The larger non-SEN 2-5 mA clusters were only 1.7% of non-SEN time and, after boot, occurred with a 120 s median start-to-start gap. That matches both SENSOR_POLL_INTERVAL_MS = 120000 (app/src/tasks/sensor_task.h:8) and UI_UPDATE_MAX_INTERVAL_MS = 120000 (app/src/ui/ui_config.h:56-57). Treat those 120 s clusters as combined sensor poll plus UI/display/PMIC/communication work unless a GPIO marker is added; prior A/B work already shows SHT45/DPS368 transaction energy alone is small.
The scheduled non-boot SEN66 event was first caught in three contiguous captures. The first two MCP calls returned early when requested with 5 s intervals; their returned actual_duration_s and per-interval charge are used as the source of truth. This made the event capture a strong electrical lower-bound.
| Segment | Actual duration | Average current | Average power | Interpretation |
|---|---|---|---|---|
| Event start | 15.50 s total; last 3.26 s high-load | 14.19 mA over whole segment | 52.47 mW | Nominal bins, then SEN66 load begins. |
| Event body | 12.89 s | 91.97 mA | 339.94 mW | SEN66 active high-current body. |
| Event tail | 79.81 s total; first ~15 s high-load | 15.87 mA over whole segment | 58.67 mW | SEN66 continues, then BUCK1 shuts off and baseline resumes. |
Classifying only the high-load bins gives:
| Metric | Value |
|---|---|
| Captured high-load duration | 31.12 s |
| Captured high-load charge | 0.7150 mAh |
| Captured high-load energy | 2.6431 mWh |
| Captured high-load average current | 82.72 mA |
| Incremental charge above nominal baseline | 0.7041 mAh |
| 900 s average using this lower-bound event | 4.08 mA |
| 900 s average using earlier full boot-equivalent event | 4.94-5.09 mA |
That lower-bound capture has now been superseded by a complete 60 s window that included baseline before the event, a full 46 s high-current region, and baseline after the event:
| Metric | Value |
|---|---|
| Complete event capture duration | 60 s |
| Capture average | 65.785 mA / 243.159 mW |
| High-load bins | 46 bins, starting at bin 11 and ending at bin 56 |
| High-load charge | 1.0905 mAh |
| High-load energy | 4.0307 mWh |
| High-load average current | 85.34 mA |
| High-load average power | 315.44 mW |
| Highest 1 s bin | 127.93 mA |
| Incremental charge above 1.2646 mA baseline | 1.0743 mAh |
| 900 s average using complete scheduled event | 5.56 mA |
The complete scheduled capture matches the configured 45 s SEN66 warmup plus stop/read/poweroff overhead (drivers/sensor/sen6x/Kconfig:119-126, app/src/tasks/sen6x_duty_manager.c:441-484). The later 300 s / 0.1 s-bin repeat capture independently reproduced a 45.6 s SEN66 event with 1.1003 mAh incremental charge above sub-2 mA idle, the 929.1 s whole-duty-period validation measured 5.559 mA directly, the 2000 s multi-duty validation repeated two natural scheduled events at 1.1323 mAh incremental each, and the later MCP-hosted 295 s / 0.5 s event capture reproduced the same band at 1.0899 mAh incremental. Together, those captures are the best empirical basis for the 15 minute duty-cycle forecast. A GPIO marker is still recommended for exact code-phase timestamping, but the current forecast no longer relies on the earlier partial scheduled event.
Release-power measurement is still not complete. Three release-style build paths were checked:
| Build command | Result | Power-audit implication |
|---|---|---|
west build -p -b simpleair_reve/nrf54l15/cpuapp app -DBOARD_ROOT=/Users/juanqui/NCS/simpleair-ncs --no-sysbuild -DCONF_FILE=prj.release.conf |
Failed to link. Because only prj.release.conf was merged, base features from prj.conf were absent; linker failures included settings, hwinfo, UI, sensor device ordinals, and regulator APIs. |
The README/rules shortcut is not a valid standalone release build command in this checkout. |
west build -p -d build-release-merged-conf -b simpleair_reve/nrf54l15/cpuapp app -DBOARD_ROOT=/Users/juanqui/NCS/simpleair-ncs --no-sysbuild '-DCONF_FILE=prj.conf;prj.release.conf' |
Built and linked warning-clean after app/prj.release.conf and base Kconfig cleanup. Final .config disables the application debug flag, logging, RTT, console, printk, early console, boot banners, and asserts, and enables device runtime PM. |
Equivalent to the layered release command for configuration results; valid release-power candidate pending fresh flash and measurement. |
west build -p -d build-release-layered -b simpleair_reve/nrf54l15/cpuapp app -DBOARD_ROOT=/Users/juanqui/NCS/simpleair-ncs --no-sysbuild -DEXTRA_CONF_FILE=prj.release.conf |
Built and linked warning-clean. This is the correct merge mechanism for base prj.conf plus the release overlay in the current west invocation. Final .config has CONFIG_SIMPLEAIR_ENABLE_DEBUG=n, CONFIG_LOG=n, CONFIG_USE_SEGGER_RTT=n, CONFIG_CONSOLE=n, CONFIG_PRINTK=n, CONFIG_EARLY_CONSOLE=n, CONFIG_BOOT_BANNER=n, CONFIG_NCS_BOOT_BANNER=n, CONFIG_ASSERT=n, and device runtime PM enabled. CONFIG_PM remains n because system PM is unavailable on this target. |
This is now a valid release-power candidate and has one probe-attached JS220 boot/idle measurement; debug-probe-detached measurement remains the absolute-current gap. |
Before the later app/prj.release.conf cleanup above, an earlier merged release-style artifact was flashed with:
probe-rs download --probe 1366:1020:000801051672 --chip nRF54L15 --protocol swd --verify --disable-double-buffering build/zephyr/zephyr.elf
JS220 measurements after a target power cycle:
| Window | Interval | Total charge | Total energy | Average current | Average power | Interpretation |
|---|---|---|---|---|---|---|
| 69.1 s | ~0.987 s | 0.079590 mAh | 0.294231 mWh | 4.147 mA | 15.329 mW | Flat draw; no expected SEN66 boot duty signature. |
| 20.9 s | 0.1045 s | 0.022729 mAh | 0.084025 mWh | 3.915 mA | 14.473 mW | Flat 100 ms bins around 3.9 mA. |
This result is not used in the battery-life forecast. The missing SEN66 boot signature means the measurement did not verify that the normal application path was running. It is still useful as a historical build/configuration finding: the pre-cleanup release setup needed correction before a release-power floor could be measured honestly. The corrected layered release build now resolves to a no-output .config and has been flashed and measured with the debug probe attached; the remaining release-power gap is a fresh release-image detached capture, preferably with a wired JS220 GPI marker.
After this attempt, the default debug-capable firmware was rebuilt cleanly and an initial raw-app build/zephyr/zephyr.elf restore was attempted. The board later appeared crashed/not blinking. The successful recovery path was to restore the full MCUboot layout without chip erase:
probe-rs download --probe 1366:1020:000801051672 --chip nRF54L15 --protocol swd --speed 1000 --verify --disable-double-buffering build-mcuboot/zephyr/zephyr.elf
probe-rs download --probe 1366:1020:000801051672 --chip nRF54L15 --protocol swd --speed 1000 --verify --disable-double-buffering --binary-format hex build/zephyr/zephyr.signed.hex
probe-rs reset --probe 1366:1020:000801051672 --chip nRF54L15 --protocol swd --speed 1000
RTT then confirmed a healthy boot: main, LED task, PMIC, SHT45, DPS368, BLE advertising, SEN66 reprobe, first SEN66 45 s warmup, measurement, BUCK1 shutdown, and slow-advertising transition all completed. However, immediate post-flash JS220 captures remained flat around 3.86-3.91 mA and did not reproduce the earlier SEN66 high-current signature.
Those flat readings remain excluded from the battery-life model because they lacked the expected SEN66 boot duty signature and were taken around a flash/recovery/debug-probe sequence. The JS220-off observation was later reinterpreted correctly: 0.0 mA through the JS220 current path confirms the target current path is open. The voltage sense can still report the upstream 3.7 V source and therefore does not prove that the DUT is powered or that another supply path is powering it. The main forecast continues to use the completed scheduled-event capture, which had a clear baseline before and after the SEN66 load.
A measurement-only overlay was added at app/prj.power_audit.conf and built with:
west build -p -b simpleair_reve/nrf54l15/cpuapp app -DBOARD_ROOT=/Users/juanqui/NCS/simpleair-ncs --no-sysbuild -DEXTRA_CONF_FILE=prj.power_audit.conf
The build succeeded and final .config confirmed CONFIG_SIMPLEAIR_ENABLE_DEBUG, CONFIG_LOG, CONFIG_CONSOLE, CONFIG_USE_SEGGER_RTT, CONFIG_PRINTK, CONFIG_EARLY_CONSOLE, CONFIG_BOOT_BANNER, and CONFIG_NCS_BOOT_BANNER were all off. A 2026-05-16 revalidation produced build-power-audit/zephyr/zephyr.elf and zephyr.signed.hex with the same final no-output configuration and CONFIG_PM_DEVICE* enabled. After moving RTT buffer sizing and the fuel-gauge warning log-level preference from unconditional prj.conf assignments into gated app/Kconfig defaults, this power-audit build is now warning-clean while default debug-capable builds retain the same effective RTT/log settings. The signed image was flashed with MCUboot layout intact:
probe-rs download --probe 1366:1020:000801051672 --chip nRF54L15 --protocol swd --speed 1000 --verify --disable-double-buffering --binary-format hex build/zephyr/zephyr.signed.hex
probe-rs reset --probe 1366:1020:000801051672 --chip nRF54L15 --protocol swd --speed 1000
JS220 measurements after full target-power cycles:
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Interpretation |
|---|---|---|---|---|---|---|---|
| PA Boot 1 | 79.5 s | 0.994 s | 1.099357 mAh | 4.063548 mWh | 49.782 mA | 184.010 mW | Clear boot SEN66 signature; about 46-49 s high-current region, peak about 132 mA over one returned interval. |
| PA Boot 2 | 80.0 s | 1.000 s | 1.058346 mAh | 3.912158 mWh | 47.626 mA | 176.047 mW | Clear boot SEN66 signature; high-current region begins around 8 s, peak about 125 mA over one returned interval. |
No-RTT nominal measurements:
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Average voltage |
|---|---|---|---|---|---|---|---|
| PA Nominal 1 | 95 s | 1 s | 0.033827 mAh | 0.125062 mWh | 1.282 mA | 4.739 mW | 3.701 V |
| PA Nominal 2 | 100 s | 0.5 s | 0.035388 mAh | 0.130830 mWh | 1.274 mA | 4.710 mW | 3.701 V |
This is essentially the same current class as the debug-capable nominal captures. The current evidence does not support treating logging/RTT as a dominant power term in the idle/SEN66-off budget. The no-output 100 s capture had low half-second bins around 1.08-1.12 mA, regular short bursts around 2.0 mA, and one larger burst cluster peaking around 4.8 mA over a 0.5 s bin. That still leaves ordinary SHT45/DPS368/PMIC/BLE/scheduler activity far below the SEN66 load class. The no-output power-audit boot captures also reproduce the same SEN66-dominated boot signature as the earlier debug-capable captures.
Additional measurement overlays were added:
| Overlay | Purpose |
|---|---|
app/prj.power_audit_no_sen6x.conf + app/boards/power_audit_no_sen6x.overlay |
Disables CONFIG_SEN6X, CONFIG_SEN6X_DUTY_CYCLE_ENABLED, and the sen6x@6b devicetree node to remove SEN66 driver, settings, and duty-manager activity. |
app/prj.power_audit_no_sensor_task.conf |
Skips sensor_task_init() to remove the periodic application sensor task after SEN6X is already disabled. |
app/prj.power_audit_no_ui_task.conf |
Skips ui_task_init() to isolate UI/EPD task overhead after SEN6X is already disabled. |
app/prj.power_audit_ui_no_display_writes.conf |
Keeps the UI task, frame buffer, cache processing, and RAM renderer active while skipping hardware display_write(), to split UI software overhead from display hardware write activity. |
app/prj.power_audit_no_display_driver.conf + app/boards/power_audit_no_display_driver.overlay |
Disables CONFIG_DISPLAY, CONFIG_EPD, CONFIG_EPD_3IN52, the epd0 devicetree node, and spi20, then links a UI API stub to isolate display-driver/SPI20 init state from the rest of the stripped floor. |
app/prj.power_audit_no_env_i2c.conf + app/boards/power_audit_no_env_i2c.overlay |
Disables the SHT45 and DPS368 drivers, the sht45 and dps368 devicetree nodes, and I2C21 to isolate the environmental-sensor driver/bus init state after the display stack is already removed. |
app/prj.power_audit_no_led_task.conf |
Skips led_task_init() to isolate status LED task overhead after SEN6X is already disabled. |
app/prj.power_audit_led_disabled.conf |
Keeps led_task running but initializes the visible GPIO indicator disabled, to split physical LED current from LED task/PMIC-poll overhead. |
app/prj.power_audit_no_ble.conf |
Disables CONFIG_SIMPLEAIR_COMM_BLE_ENABLED and Zephyr Bluetooth to isolate BLE idle/advertising overhead after SEN6X is already disabled. |
app/prj.power_audit_fg_60s.conf |
Changes CONFIG_SIMPLEAIR_MEASURE_FG_PERIOD_S from 1 s to 60 s to isolate periodic nPM1300/fuel-gauge tick cost on stripped-down floors. |
app/prj.power_audit_no_pmic_manager.conf |
Skips pmic_manager_init() and the boot PMIC status read to bound application PMIC manager overhead on the stripped floor. Measurements require a full JS220 current-path power cycle first, because normal pmic_manager_init() is the path that forces retained BUCK1/SEN6X power off after MCU reset. |
app/prj.power_audit_no_pmic_stack.conf + app/boards/power_audit_no_pmic_stack.overlay |
Disables nPM13xx MFD/regulator/charger/GPIO/LED/fuel-gauge Kconfig, the PMIC devicetree node, PMIC subnodes, and I2C22. Follow-up splits show this is a combined PMIC/I2C22 cut, not a pure PMIC software-stack test. |
app/prj.power_audit_no_pmic_charger.conf + app/boards/power_audit_no_pmic_charger.overlay |
Disables only the nPM13xx charger child and Nordic fuel gauge while keeping the PMIC MFD/regulator/GPIO/LED stack active, to split charger/ADC/fuel-gauge init from the rest of the PMIC stack. |
app/prj.power_audit_no_pmic_led.conf + app/boards/power_audit_no_pmic_led.overlay |
Disables only the nPM13xx LED child while keeping PMIC MFD/regulator/GPIO/charger/fuel-gauge support active. |
app/prj.power_audit_no_pmic_gpio.conf + app/boards/power_audit_no_pmic_gpio.overlay |
Disables only the nPM13xx GPIO child while keeping PMIC MFD/regulator/LED/charger/fuel-gauge support active. |
app/prj.power_audit_no_pmic_regulators.conf + app/boards/power_audit_no_pmic_regulators.overlay |
Disables only the nPM13xx regulator child while keeping PMIC MFD/GPIO/LED/charger/fuel-gauge support active. |
app/prj.power_audit_pmic_mfd_only.conf + app/boards/power_audit_pmic_mfd_only.overlay |
Keeps the nPM13xx parent MFD active while disabling charger/fuel gauge, LED, GPIO, and regulator child drivers, to split parent PMIC initialization from child-device initialization. |
app/prj.power_audit_no_pmic_node_keep_i2c.conf + app/boards/power_audit_no_pmic_node_keep_i2c.overlay |
Disables the PMIC parent node and child drivers while keeping I2C22 active, to split PMIC-node removal from I2C22 removal. |
app/boards/power_audit_i2c22_sleep_pinctrl.overlay |
Keeps I2C22 active with PMIC clients disabled, but applies i2c22_sleep as both default and sleep pinctrl, to split TWIM22 device presence from normal default pinctrl state. |
app/prj.power_audit_i2c22_gpio_pullups.conf + app/boards/power_audit_no_pmic_stack.overlay |
Keeps the I2C22-disabled no-PMIC-stack cut, then parks P1.11/P1.15 as GPIO inputs with pull-ups in main() to test whether simple GPIO pull-ups recreate the low-current floor. |
app/prj.power_audit_poweroff.conf |
Enables CONFIG_SIMPLEAIR_MEASURE_POWEROFF_AT_BOOT, causing main() to wait 30 s and call sys_poweroff() for a bench-observed System OFF floor attempt. |
app/prj.power_audit_sht45_probe.conf |
Enables a 1 Hz SHT45-only measurement task with the normal sensor task disabled. |
app/prj.power_audit_dps368_probe.conf |
Enables a 1 Hz DPS368-only measurement task with the normal sensor task disabled. |
app/prj.power_audit_event_trace.conf |
Enables CONFIG_SIMPLEAIR_POWER_EVENT_TRACE, a measurement-only retained RAM event buffer used to correlate boot and SEN66 firmware phases with JS220 current captures after the run. |
All variants in this A/B pass built and linked at the time they were measured; no C warnings remained after guarding SEN6X-only locals and BLE-only status locals in sensor_task.c. The old inherited Kconfig assignment warnings were later removed at the base config layer by moving RTT/log preferences into gated app/Kconfig defaults; the primary prj.power_audit.conf, prj.release.conf, and default builds have since been revalidated warning-clean.
No-SEN6X measurements:
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Interpretation |
|---|---|---|---|---|---|---|---|
| No-SEN6X boot | 79.5 s | 0.994 s | 0.030108 mAh | 0.111311 mWh | 1.363 mA | 5.041 mW | Full current-path power cycle; the 45-49 s SEN66 high-current plateau disappeared. |
| No-SEN6X nominal | 100.5 s | 0.5025 s | 0.035353 mAh | 0.130700 mWh | 1.266 mA | 4.682 mW | Essentially identical to normal no-output SEN66-off nominal. |
No-SEN6X plus no-sensor-task measurements:
| Run | Window | Interval | Total charge | Total energy | Average current | Average power | Interpretation |
|---|---|---|---|---|---|---|---|
| No-SEN6X/no-sensor boot | 79.5 s | 0.994 s | 0.030857 mAh | 0.114078 mWh | 1.397 mA | 5.166 mW | Full current-path power cycle; no SEN66 plateau. Startup alignment/noise dominates the small difference versus No-SEN6X boot. |
| No-SEN6X/no-sensor nominal | 100.5 s | 0.5025 s | 0.035259 mAh | 0.130353 mWh | 1.263 mA | 4.669 mW | Removing the whole application sensor task reduced nominal current by only about 0.003 mA versus No-SEN6X nominal. |
No-SEN6X plus UI/LED/BLE isolation measurements:
| Variant | Window | Interval | Total charge | Total energy | Average current | Average power | Delta vs No-SEN6X nominal |
|---|---|---|---|---|---|---|---|
| No-SEN6X/no-UI nominal | 100 s | 0.5 s | 0.031168 mAh | 0.115224 mWh | 1.122 mA | 4.148 mW | -0.144 mA |
| No-SEN6X/no-LED nominal | 100 s | 0.5 s | 0.032337 mAh | 0.119546 mWh | 1.164 mA | 4.304 mW | -0.102 mA |
| No-SEN6X/no-BLE boot-window | 100 s | 0.5 s | 0.036941 mAh | 0.136571 mWh | 1.330 mA | 4.917 mW | not comparable; includes startup bins |
| No-SEN6X/no-BLE nominal | 100 s | 0.5 s | 0.035321 mAh | 0.130582 mWh | 1.272 mA | 4.701 mW | +0.005 mA |
| No-SEN6X/no-UI/no-LED boot-window | 100 s | 0.5 s | 0.027628 mAh | 0.102135 mWh | 0.995 mA | 3.677 mW | not comparable; includes startup bins |
| No-SEN6X/no-UI/no-LED nominal | 100 s | 0.5 s | 0.025154 mAh | 0.092989 mWh | 0.906 mA | 3.348 mW | -0.361 mA |
| No-SEN6X/no-UI/no-LED/FG60 boot-window | 100 s | 0.5 s | 0.026592 mAh | 0.098306 mWh | 0.957 mA | 3.539 mW | not comparable; includes startup bins |
| No-SEN6X/no-UI/no-LED/FG60 nominal | 100 s | 0.5 s | 0.025075 mAh | 0.092700 mWh | 0.903 mA | 3.337 mW | -0.003 mA versus 1 Hz stripped floor |
| No-SEN6X/no-sensor/no-LED/FG60/no-BLE, UI task running but no display writes | 100 s | 0.5 s | 0.024930 mAh | 0.092159 mWh | 0.897 mA | 3.318 mW | UI task, frame buffer, cache, and RAM renderer active; hardware display_write() skipped |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60, BLE on A | 100 s | 0.5 s | 0.025461 mAh | 0.094119 mWh | 0.917 mA | 3.388 mW | repeated stripped BLE-on floor; first post-flash window |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60, BLE on B | 99.5 s | 0.995 s | 0.025036 mAh | 0.092550 mWh | 0.906 mA | 3.349 mW | repeated stripped BLE-on floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60, BLE on C | 100 s | 1.0 s | 0.025000 mAh | 0.092416 mWh | 0.900 mA | 3.327 mW | repeated stripped BLE-on floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60, BLE on 100 ms profile | 30.4 s | 0.101 s | 0.007978 mAh | 0.029492 mWh | 0.945 mA | 3.493 mW | paired short-window profile; average voltage field had a first-bin artifact, but voltage min/max stayed normal |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE A | 100 s | 1.0 s | 0.024790 mAh | 0.091640 mWh | 0.892 mA | 3.299 mW | repeated stripped BLE-off floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE B | 100 s | 1.0 s | 0.024796 mAh | 0.091664 mWh | 0.893 mA | 3.300 mW | repeated stripped BLE-off floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE C | 100 s | 1.0 s | 0.024778 mAh | 0.091597 mWh | 0.892 mA | 3.297 mW | repeated stripped BLE-off floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE 100 ms profile | 30.0 s | 0.100 s | 0.007454 mAh | 0.027555 mWh | 0.894 mA | 3.307 mW | paired short-window profile; adjacent delta versus BLE-on profile was +0.050 mA |
| No-SEN6X/no-sensor/no-UI/FG60/no-BLE, LED task running but indicator disabled | 100 s | 0.5 s | 0.024915 mAh | 0.092105 mWh | 0.897 mA | 3.316 mW | isolates LED task with visible GPIO LED off; only +0.004-0.005 mA versus no-LED-task no-BLE floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-PMIC-manager A | 100 s | 1.0 s | 0.024808 mAh | 0.091708 mWh | 0.893 mA | 3.301 mW | full current-path power cycle before capture; app PMIC manager skipped |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-PMIC-manager B | 100 s | 1.0 s | 0.024800 mAh | 0.091677 mWh | 0.893 mA | 3.300 mW | app PMIC manager skipped |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-PMIC-manager C | 100 s | 1.0 s | 0.024776 mAh | 0.091587 mWh | 0.892 mA | 3.297 mW | app PMIC manager skipped |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver A | 30 s | 1.0 s | 0.007175 mAh | 0.026524 mWh | 0.861 mA | 3.183 mW | display subsystem, EPD node, and SPI20 disabled; UI API stub linked |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver B | 30 s | 1.0 s | 0.007183 mAh | 0.026553 mWh | 0.862 mA | 3.186 mW | adjacent repeat of no-display-driver stripped floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C A | 30 s | 1.0 s | 0.007179 mAh | 0.026537 mWh | 0.861 mA | 3.184 mW | SHT45, DPS368, and I2C21 disabled on top of no-display-driver stripped floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C B | 30 s | 1.0 s | 0.007171 mAh | 0.026510 mWh | 0.861 mA | 3.181 mW | adjacent repeat; indistinguishable from no-display-driver floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C C | 30 s | 1.0 s | 0.007176 mAh | 0.026529 mWh | 0.861 mA | 3.183 mW | adjacent repeat; mean 0.8611 mA / 3.1830 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/BLE-on/no-display-driver/no-env-I2C long | 600 s | 1.0 s | 0.145089 mAh | 0.536350 mWh | 0.871 mA | 3.218 mW | long deep-stripped BLE-on floor; 600 bins, power-cycled before capture |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C long | 600 s | 1.0 s | 0.143342 mAh | 0.529896 mWh | 0.860 mA | 3.179 mW | long deep-stripped BLE-off floor; paired delta is +0.0105 mA / +0.0387 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-manager exact A | 30 s | 1.0 s | 0.007174 mAh | 0.026520 mWh | 0.861 mA | 3.182 mW | PMIC device stack active; app PMIC manager skipped; exact baseline for PMIC subdriver splits |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-manager exact B | 30 s | 1.0 s | 0.007175 mAh | 0.026522 mWh | 0.861 mA | 3.183 mW | adjacent repeat; mean baseline 0.8609 mA / 3.1824 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-manager exact C | 30 s | 1.0 s | 0.007173 mAh | 0.026518 mWh | 0.861 mA | 3.182 mW | adjacent repeat; indistinguishable from no-env-I2C floor |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-stack A | 30 s | 1.0 s | 0.009749 mAh | 0.036042 mWh | 1.170 mA | 4.325 mW | nPM13xx software device stack and I2C22 disabled; current increased |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-stack B | 30 s | 1.0 s | 0.009746 mAh | 0.036029 mWh | 1.170 mA | 4.324 mW | adjacent repeat; same higher-current class |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-stack C | 30 s | 1.0 s | 0.009738 mAh | 0.035999 mWh | 1.169 mA | 4.320 mW | adjacent repeat; mean 1.1693 mA / 4.3228 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-charger A | 30 s | 1.0 s | 0.007218 mAh | 0.026675 mWh | 0.866 mA | 3.201 mW | charger child and Nordic fuel gauge disabled; PMIC MFD/regulator/GPIO/LED stack still active |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-charger B | 30 s | 1.0 s | 0.007223 mAh | 0.026694 mWh | 0.867 mA | 3.203 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-charger C | 30 s | 1.0 s | 0.007225 mAh | 0.026701 mWh | 0.867 mA | 3.204 mW | adjacent repeat; mean 0.8666 mA / 3.2028 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-LED A | 30 s | 1.0 s | 0.007179 mAh | 0.026538 mWh | 0.861 mA | 3.185 mW | nPM13xx LED child disabled; PMIC MFD/regulator/GPIO/charger/fuel-gauge stack still active |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-LED B | 30 s | 1.0 s | 0.007163 mAh | 0.026479 mWh | 0.860 mA | 3.177 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-LED C | 30 s | 1.0 s | 0.007163 mAh | 0.026479 mWh | 0.860 mA | 3.177 mW | adjacent repeat; mean 0.8602 mA / 3.1798 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-GPIO A | 30 s | 1.0 s | 0.007181 mAh | 0.026547 mWh | 0.862 mA | 3.186 mW | nPM13xx GPIO child disabled; PMIC MFD/regulator/LED/charger/fuel-gauge stack still active |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-GPIO B | 30 s | 1.0 s | 0.007184 mAh | 0.026556 mWh | 0.862 mA | 3.187 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-GPIO C | 30 s | 1.0 s | 0.007184 mAh | 0.026556 mWh | 0.862 mA | 3.187 mW | adjacent repeat; mean 0.8619 mA / 3.1863 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-regulators A | 30 s | 1.0 s | 0.007185 mAh | 0.026562 mWh | 0.862 mA | 3.187 mW | nPM13xx regulator child disabled; PMIC MFD/GPIO/LED/charger/fuel-gauge stack still active |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-regulators B | 30 s | 1.0 s | 0.007193 mAh | 0.026590 mWh | 0.863 mA | 3.191 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-regulators C | 30 s | 1.0 s | 0.007190 mAh | 0.026579 mWh | 0.863 mA | 3.189 mW | adjacent repeat; mean 0.8627 mA / 3.1892 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/PMIC-parent-MFD-only A | 30 s | 1.0 s stats | 0.007240 mAh | 0.026755 mWh | 0.869 mA | 3.211 mW | PMIC parent MFD active; charger/fuel gauge, LED, GPIO, and regulator children disabled |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/PMIC-parent-MFD-only B | 30 s | 1.0 s stats | 0.007241 mAh | 0.026760 mWh | 0.869 mA | 3.211 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/PMIC-parent-MFD-only C | 30 s | 1.0 s stats | 0.007250 mAh | 0.026792 mWh | 0.870 mA | 3.215 mW | adjacent repeat; mean 0.8692 mA / 3.2123 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-node-keep-I2C22 A | 30 s | 1.0 s stats | 0.007247 mAh | 0.026782 mWh | 0.870 mA | 3.214 mW | PMIC parent and children disabled; I2C22 left enabled |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-node-keep-I2C22 B | 30 s | 1.0 s stats | 0.007251 mAh | 0.026794 mWh | 0.870 mA | 3.215 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/no-PMIC-node-keep-I2C22 C | 30 s | 1.0 s stats | 0.007242 mAh | 0.026762 mWh | 0.869 mA | 3.211 mW | adjacent repeat; mean 0.8696 mA / 3.2135 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/I2C22-sleep-pinctrl A | 30 s | 1.0 s stats | 0.009752 mAh | 0.036053 mWh | 1.170 mA | 4.326 mW | PMIC parent and children disabled; I2C22 enabled but default pinctrl forced to i2c22_sleep |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/I2C22-sleep-pinctrl B | 30 s | 1.0 s stats | 0.009756 mAh | 0.036066 mWh | 1.171 mA | 4.328 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/I2C22-sleep-pinctrl C | 30 s | 1.0 s stats | 0.009741 mAh | 0.036010 mWh | 1.169 mA | 4.321 mW | adjacent repeat; mean 1.1699 mA / 4.3252 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/I2C22-disabled-GPIO-pullups A | 30 s | 1.0 s stats | 0.009726 mAh | 0.035957 mWh | 1.167 mA | 4.315 mW | I2C22 disabled; P1.11/P1.15 parked as GPIO inputs with pull-ups in main() |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/I2C22-disabled-GPIO-pullups B | 30 s | 1.0 s stats | 0.009712 mAh | 0.035906 mWh | 1.165 mA | 4.309 mW | adjacent repeat |
| No-SEN6X/no-sensor/no-UI/no-LED/FG60/no-BLE/no-display-driver/no-env-I2C/I2C22-disabled-GPIO-pullups C | 30 s | 1.0 s stats | 0.009710 mAh | 0.035898 mWh | 1.165 mA | 4.308 mW | adjacent repeat; mean 1.1660 mA / 4.3104 mW |
| No-SEN6X/no-sensor/no-UI/no-LED/no-BLE/FG60 baseline | 100 s | 0.5 s | 0.024863 mAh | 0.091910 mWh | 0.895 mA | 3.309 mW | reference floor for one-sensor probe variants |
| Same baseline + SHT45 1 Hz probe | 100 s | 0.5 s | 0.024999 mAh | 0.092414 mWh | 0.900 mA | 3.327 mW | +0.0049 mA upper-bound; about 18 uJ/read at 1 Hz |
| Same baseline + DPS368 1 Hz probe | 100 s | 0.5 s | 0.026401 mAh | 0.097597 mWh | 0.950 mA | 3.513 mW | +0.0554 mA upper-bound; about 205 uJ/read at 1 Hz |
| No-SEN6X/no-sensor/no-UI/no-LED/no-BLE/FG60/System-OFF attempt | 100 s | 0.5 s | 0.024863 mAh | 0.091912 mWh | 0.895 mA | 3.309 mW | no visible step after expected sys_poweroff() point |
| Same System-OFF attempt, post-off follow-up | 100 s | 0.5 s | 0.024831 mAh | 0.091792 mWh | 0.894 mA | 3.305 mW | stable bench floor after the first window |
This A/B pass confirms the earlier attribution. SEN66's cost is its powered measurement window, not dormant code. With SEN66 disabled, SHT45 and DPS368 are now separately bounded by 1 Hz forced-read probe variants. The SHT45 path is effectively in the noise floor at about +0.0049 mA, while DPS368 is visible but still small at about +0.0554 mA. PMIC status polling from sensor_task and sensor-update dispatch remain below the current JS220 bench setup's practical isolation floor over 100 s windows. The largest measured non-SEN66 terms are display hardware write/post-write behavior and the visible GPIO LED indication. The UI split is important: keeping UI task/render/cache active while skipping display_write() measured 0.897 mA, near the stripped no-UI/no-LED/no-BLE floor. Disabling the display driver stack and SPI20 pushed that stripped floor to a 0.8615 mA mean over two adjacent windows, about 0.031-0.034 mA below the display-driver-present stripped floor. Disabling SHT45, DPS368, and I2C21 on top of that measured 0.8611 mA, which is only -0.0004 mA from the no-display-driver mean and is below practical significance here. Removing the whole nPM13xx software stack plus I2C22 did the opposite of a saving: the mean rose to 1.1693 mA, about +0.308 mA above the no-env-I2C floor. An exact baseline with PMIC device stack active but application PMIC manager skipped measured 0.8609 mA. Disabling PMIC child areas one at a time did not recreate the no-PMIC-stack increase: charger/fuel gauge averaged 0.8666 mA, LED averaged 0.8602 mA, GPIO averaged 0.8619 mA, and regulators averaged 0.8627 mA. Parent-MFD-only averaged 0.8692 mA, and disabling the PMIC node while keeping I2C22 enabled averaged 0.8696 mA. I2C22 sleep-pinctrl and disabled-GPIO-pull-up follow-ups both stayed high at 1.1699 mA and 1.1660 mA, respectively. Therefore the +0.308 mA effect is not any measured PMIC child driver, not the PMIC parent MFD, and not fixed by simple GPIO pull-ups. A final-idle TWIM22/GPIO register dump did not expose the mechanism because default-pinctrl and sleep-pinctrl images had identical visible TWIM22/P1.11/P1.15 register state after boot. The LED split is similar: keeping led_task alive with the indicator disabled measured 0.897 mA against the 0.892-0.893 mA no-LED-task floor, so physical LED duty dominates the earlier 0.102 mA no-LED delta. BLE idle/advertising was only isolated after stripping the sensor, UI, LED, and fast fuel-gauge work: BLE-on mean was 0.907 mA across three 100 s-class windows, BLE-off mean was 0.892 mA, so the observed average delta is about +0.015 mA. Excluding the first post-flash BLE-on window gives about +0.011 mA. The adjacent 30 s / 100 ms-bin BLE pair produced a larger +0.050 mA delta, with BLE-on bins roughly 0.88-0.98 mA and BLE-off bins roughly 0.89-0.91 mA. The deeper 600 s pair, with display driver and environmental I2C also removed, measured 0.8705 mA BLE-on and 0.8601 mA BLE-off, so the best average BLE overhead estimate is +0.0105 mA. The short 30 s result remains useful as a warning that radio/controller timing can change small-window averages, but the 600 s pair should be used for battery forecasts. Exact radio-event energy still needs a firmware marker or controller/radio trace. Slowing the fuel-gauge work item from 1 Hz to 60 s did not materially reduce the stripped floor, and skipping pmic_manager_init() also stayed at the same floor class. The next target is therefore below application PMIC manager cadence: platform sleep-state residency, always-on silicon, I2C22 TWIM/pinctrl init, bench wiring, and debug-probe effects.
I2C22 register follow-up used probe-rs read after flashing each measurement image, JS220 current-path power-cycling, and allowing a 30 s settle. Nordic's MDK maps TWIM22 ENABLE to 0x500c8500, FREQUENCY to 0x500c8524, PSEL.SCL to 0x500c8600, PSEL.SDA to 0x500c8604, and GPIO1 PIN_CNF[n] to 0x500d8280 + 4n. The raw logs are retained as /tmp/sa_i2c22_regs_default_pinctrl.log, /tmp/sa_i2c22_regs_sleep_pinctrl.log, and /tmp/sa_i2c22_regs_gpio_pullups.log.
| Register/state | Default pinctrl, I2C22 enabled | Sleep pinctrl as default, I2C22 enabled | I2C22 disabled + GPIO pull-ups | Interpretation |
|---|---|---|---|---|
TWIM22 ENABLE |
0x00000000 |
0x00000000 |
0x00000000 |
All three images have TWIM22 disabled at final idle. |
TWIM22 FREQUENCY |
0x06186000 |
0x06186000 |
0x04000000 |
Enabled-device images program 400 kHz; disabled image remains at reset/default 250 kHz. |
TWIM22 PSEL.SCL |
0x0000002b |
0x0000002b |
0xffffffff |
Enabled-device images keep SCL connected to P1.11 even while idle. Disabled image disconnects PSEL. |
TWIM22 PSEL.SDA |
0x0000002f |
0x0000002f |
0xffffffff |
Enabled-device images keep SDA connected to P1.15 even while idle. Disabled image disconnects PSEL. |
GPIO1 PIN_CNF[11] |
0x00000802 |
0x00000802 |
0x0000000c |
Enabled-device images leave input disconnected with DRIVE1 disconnected; GPIO-pull-up image uses input + pull-up. |
GPIO1 PIN_CNF[15] |
0x00000802 |
0x00000802 |
0x0000000c |
Same state as P1.11. |
This dump narrows but does not close the I2C22 mechanism. The power measurements still say that normal I2C22/default-pinctrl initialization is materially different from using sleep pinctrl as default or disabling I2C22 plus GPIO pull-ups. However, after boot and debug access, the visible TWIM22/GPIO registers for the default-pinctrl and sleep-pinctrl images are identical. The remaining mechanism is therefore probably an initialization-time sequence, a lower-level pin-control or power-domain latch not reflected in these registers, or a debug-probe perturbation. Treat the register dump as a negative result for simple ENABLE/PSEL/GPIO-CNF explanations, not as proof that the earlier +0.308 mA current split was invalid.
The System OFF attempt is intentionally treated as inconclusive for silicon-level current. The image contained CONFIG_POWEROFF=y and CONFIG_SIMPLEAIR_MEASURE_POWEROFF_AT_BOOT=y, and the code path calls sys_poweroff() after 30 s, but the JS220 current did not visibly collapse. The result is still a valid bench floor for this wiring and probe state: about 0.894 mA. It is not evidence that the nRF54L15 itself consumes 0.894 mA in System OFF.
A focused display-refresh variant was built with SEN6X, the sensor task, LED task, and BLE disabled, fuel-gauge cadence slowed to 60 s, UI enabled, and retained event tracing enabled:
west build -p -b simpleair_reve/nrf54l15/cpuapp app -DBOARD_ROOT=/Users/juanqui/NCS/simpleair-ncs --no-sysbuild -DEXTRA_CONF_FILE='prj.power_audit.conf;prj.power_audit_no_sen6x.conf;prj.power_audit_no_sensor_task.conf;prj.power_audit_no_led_task.conf;prj.power_audit_no_ble.conf;prj.power_audit_fg_60s.conf;prj.power_audit_event_trace.conf' -DEXTRA_DTC_OVERLAY_FILE=boards/power_audit_no_sen6x.overlay
JS220 completed two contiguous windows after a fresh current-path power cycle:
| Window | Interval | Total charge | Total energy | Average current | Average power | Average voltage | Interpretation |
|---|---|---|---|---|---|---|---|
| 100 s | 0.25 s | 0.039505 mAh | 0.146076 mWh | 1.422 mA | 5.259 mW | 3.7007 V | Captured boot plus first two UI refreshes. |
| 70 s | 0.25 s | 0.021961 mAh | 0.081191 mWh | 1.129 mA | 4.176 mW | 3.7007 V | Captured the following idle interval; the next forced UI refresh occurred just after this window. |
The retained RAM trace then reported:
| Event | Firmware uptime | Duration |
|---|---|---|
| UI render 1 | 5.092-6.415 s | 1.323 s |
| Display write 1 | 5.155-6.415 s | 1.260 s |
| UI render 2 | 62.951-64.956 s | 2.005 s |
| Display write 2 | 63.015-64.956 s | 1.941 s |
| UI render 3 | 183.031-185.036 s | 2.005 s |
| Display write 3 | 183.096-185.036 s | 1.940 s |
The first 100 s electrical window shows two small current clusters aligned with the first two display writes. With 0.25 s bins and indirect JS220-to-firmware alignment, the observed clusters are approximately 2.5-2.6 mA average while active and roughly 7-10 mJ above local idle per display write. Amortized over a 120 s forced-refresh interval, that pulse energy is only about 0.02-0.03 mA equivalent at 3.7 V. This explains why disabling the UI task saved far more current than the display-write pulse energy itself: the measurable UI/EPD cost is dominated by steady display-stack/task/device-state overhead and wake behavior, not by the infrequent e-paper refresh pulse alone.
Battery assumptions:
| Assumption | Value | Rationale |
|---|---|---|
| Pack | 10 Ah LiPo, 3.7 V nominal | Matches requested battery and app/src/battery_model.inc:19, whose model name is MakerFocus10Ah. |
| Nominal stored energy | 37 Wh | 10 Ah x 3.7 V nominal. |
| Temperature | 27 C room temperature | Matches the battery model temperature table at app/src/battery_model.inc:7; no cold-temperature derate applied. |
| Usable capacity range | 80-90% field planning, 100% theoretical | Accounts for cutoff reserve, cell age, capacity tolerance, wiring/connector loss, and avoiding full depletion. |
| Self-discharge range | 2-5%/month sensitivity; 3%/month middle case | Real LiPo self-discharge depends on cell age and storage voltage; multi-month forecasts must include it. |
| Load voltage | JS220 observed ~3.70 V while current path was enabled | The board was measured at the same nominal voltage as the requested LiPo assumption. With the current path disabled, voltage sense can still observe the upstream bench source and is not used as a DUT-power or DUT-rail validation. |
Runtime formula:
runtime_hours = usable_capacity_mAh / (measured_load_mA + self_discharge_equivalent_mA)
self_discharge_equivalent_mA = 10000 mAh * monthly_self_discharge_fraction / (30 days * 24 h/day)
Self-discharge equivalent current for a 10 Ah pack:
| Monthly self-discharge | Equivalent continuous loss |
|---|---|
| 2%/month | 0.278 mA |
| 3%/month | 0.417 mA |
| 5%/month | 0.694 mA |
Machine-readable forecast artifacts were generated from the measured model:
| Artifact | Contents |
|---|---|
docs/research/power/2026-05-16-battery-forecast-summary.json |
Handpicked planning scenarios using the 929.1 s measured schedule, hourly/6 h alternatives, and stress cases. |
docs/research/power/2026-05-16-battery-forecast-matrix.json |
Full forecast matrix for baseline model, SEN66 interval, usable capacity, self-discharge, and event-energy margin. |
docs/research/power/2026-05-16-battery-forecast-matrix.csv |
Same matrix as CSV; 2640 rows. |
docs/research/power/2026-05-16-battery-forecast-monte-carlo.json |
100,000-iteration probabilistic runtime forecast at 27 C. |
docs/research/power/2026-05-16-battery-forecast-monte-carlo-sample.csv |
First 2,000 Monte Carlo draws for spot-checking. |
docs/research/power/2026-05-16-battery-forecast-multi-cycle-update.json |
Four planning scenarios using the repeated scheduled-event estimate from the 2000 s normal-firmware capture. |
docs/research/power/2026-05-16-real-world-battery-forecast-2000s.json |
Canonical 2000 s repeated-event real-world forecast with pack cases, margins, reverse targets, and boot penalty rows. |
docs/research/power/2026-05-16-real-world-battery-forecast-2000s.csv |
165-row CSV matrix from the 2000 s real-world forecast artifact. |
docs/research/power/2026-05-16-sen66-target-interval-requirements.json |
Compact target-runtime interval requirements derived from the 2000 s repeated-event forecast basis. |
docs/research/power/2026-05-16-sen66-target-interval-requirements.csv |
90-row CSV matrix of required SEN66 interval by baseline, pack case, and target runtime. |
docs/research/power/2026-05-16-sen66-repeatability-forecast.json |
Repeatability summary and runtime sensitivity across seven scheduled SEN66 events and two full-duty windows. |
docs/research/power/2026-05-16-sen66-repeatability-forecast.csv |
84-row CSV matrix using measured SEN66 event min/mean/max, 10 Ah pack cases, self-discharge, and interval sweeps. |
docs/research/power/2026-05-16-release-baseline-forecast-sensitivity.json |
Sensitivity check that substitutes the warning-clean release post-boot idle current into the 2000 s repeated-event forecast basis. |
docs/research/power/2026-05-16-release-baseline-forecast-sensitivity.csv |
CSV rows for the release-baseline sensitivity cases. |
The first measured complete SEN66 event adds 1.0743 mAh above the nominal baseline each time it runs. The later 300 s / 0.1 s-bin repeat event adds 1.1003 mAh above its sub-2 mA idle baseline, or projects to about 5.67 mA at the 900 s schedule when paired with the conservative 1.2646 mA nominal baseline. The 929.1 s contiguous validation measured 5.559 mA directly with one scheduled event. The 2000 s normal-firmware validation then captured two natural scheduled events after boot at 1.1323 mAh mean incremental charge above idle, projecting 5.765 mA at the 900 s schedule. The interval sweep and daily budget below now use the 2000 s repeated-event basis.
average_load_mA = baseline_mA + (1.1323 mAh / interval_hours)
This table compares the directly measured 5.559 mA single-event full-duty-period average with the newer 5.765 mA repeated-event steady projection from the 2000 s capture. The 2000 s whole-window average, 7.304 mA, includes boot energy and is not the right steady-state battery-life input.
| Scenario | Load before self-discharge | Total equivalent load | Runtime |
|---|---|---|---|
| 929.1 s validation, fresh theoretical 10 Ah pack, no self-discharge | 5.559 mA | 5.559 mA | 74.9 d |
| 929.1 s validation, 90% usable, 3%/month self-discharge | 5.559 mA | 5.976 mA | 62.8 d |
| 929.1 s validation, 80% usable, 3%/month self-discharge | 5.559 mA | 5.976 mA | 55.8 d |
| 2000 s repeated-event projection, fresh theoretical 10 Ah pack, no self-discharge | 5.765 mA | 5.765 mA | 72.3 d |
| 2000 s repeated-event projection, 90% usable, 3%/month self-discharge | 5.765 mA | 6.181 mA | 60.7 d |
| 2000 s repeated-event projection, 80% usable, 3%/month self-discharge | 5.765 mA | 6.181 mA | 53.9 d |
| 2000 s repeated-event projection, 70% usable, 5%/month self-discharge | 5.765 mA | 6.459 mA | 45.2 d |
| Conservative nominal model, 80% usable, 5%/month, +10% SEN66 event margin | 5.992 mA | 6.686 mA | 49.9 d |
Interpretation: the best repeated-event forecast is 53.9-60.7 days for the current schedule under the 80-90% usable capacity and 3%/month self-discharge planning case. The earlier single-event validation gave 55.8-62.8 days. A more conservative field commitment, allowing aged-pack behavior and event-energy margin, remains about 45-50 days.
The newer 2026-05-16-real-world-battery-forecast-2000s.* artifacts use a single consistent basis from the 2000 s normal-firmware capture: 1.263 mA normal non-SEN average plus 1.1323 mAh incremental SEN66 cost per scheduled event. This is slightly more conservative than the earlier 5.765 mA repeated-event projection because it keeps the 2-5 mA non-SEN clusters in the baseline instead of using only the sub-2 mA idle bins.
| Scenario | SEN66 interval | Total equivalent load | Runtime |
|---|---|---|---|
| Fresh best practical case, 95% usable, 2%/month self-discharge | 15 min | 6.070 mA | 65.2 d |
| Field planning newer pack, 90% usable, 3%/month self-discharge | 15 min | 6.209 mA | 60.4 d |
| Field planning nominal, 80% usable, 3%/month self-discharge | 15 min | 6.209 mA | 53.7 d |
| Field margin, 80% usable, 3%/month, +5% baseline and +10% SEN66 event margin | 15 min | 6.725 mA | 49.6 d |
| Aged conservative pack, 70% usable, 5%/month, +10% baseline and +15% SEN66 event margin | 15 min | 7.292 mA | 40.0 d |
| Field planning nominal, 80% usable, 3%/month self-discharge | 1 h | 2.812 mA | 118.6 d |
| Field planning nominal, 80% usable, 3%/month self-discharge | 6 h | 1.868 mA | 178.4 d |
| Field planning nominal, 80% usable, 3%/month self-discharge | 24 h | 1.727 mA | 193.1 d |
| Field planning nominal, 80% usable, 3%/month self-discharge | SEN66 off | 1.679 mA | 198.5 d |
The warning-clean release idle measurement was also run through the same 2000 s repeated-event forecast basis as a sensitivity check. Substituting the release post-boot idle current of 1.229 mA for the normal non-SEN baseline of 1.263 mA changes the 15 minute load before self-discharge from 5.792 mA to 5.758 mA. That moves the practical 90% / 3% case from 60.40 d to 60.73 d, the 80% / 3% case from 53.69 d to 53.98 d, and the field-margin case from 49.57 d to 49.83 d. The release cleanup is therefore valuable for correctness and product configuration, but it does not materially change the battery-life conclusion under the current SEN66 schedule.
The reverse target rows in the same artifact show that with current normal firmware, 80% usable capacity, and 3%/month self-discharge, 120 days requires spacing SEN66 events to at least 1.03 h. 180 days requires at least 6.57 h. 270 days and 365 days are impossible from the current baseline plus self-discharge alone, even if SEN66 is disabled, unless the non-SEN baseline is reduced below the measured 1.263 mA class.
A deterministic matrix is useful for design trades, but field planning also needs uncertainty. The Monte Carlo forecast uses 100,000 iterations with seed 20260516 and the following room-temperature assumptions:
| Variable | Distribution |
|---|---|
| Usable capacity | Triangular, 70-95%, mode 85%. |
| Self-discharge | Triangular, 2-5%/month, mode 3%/month. |
| Idle current | Triangular, 1.09-1.28 mA, mode 1.2386 mA. |
| SEN66 event cost | Truncated normal from the three measured complete-event costs, mean 1.1093 mAh, sigma floored at 0.02 mAh, bounds 1.04-1.18 mAh. |
| Integrated current-schedule load | Truncated normal around the measured 5.559 mA full-period average with 3% sigma; the newer 2000 s repeated-event projection is 5.765 mA and sits inside the conservative/current-schedule margin band. |
Runtime percentiles:
| Scenario | P10 | P50 | P90 | Mean |
|---|---|---|---|---|
| Current 15 min schedule, measured integrated load | 52.4 d | 57.8 d | 62.9 d | 57.7 d |
| Current 15 min schedule, +10% SEN66 event margin | 48.3 d | 53.2 d | 57.5 d | 53.1 d |
| Hourly SEN66, measured event model | 113.4 d | 125.5 d | 136.8 d | 125.3 d |
| 6 h SEN66, measured event model | 168.4 d | 188.0 d | 207.8 d | 188.1 d |
| SEN66 off, measured idle model | 186.3 d | 209.0 d | 232.1 d | 209.1 d |
Interpretation: under the current 15 minute SEN66 schedule, a practical field expectation is about 7-8.5 weeks median. The original Monte Carlo used the 5.559 mA single-event integrated load and produced a ~52 day P10 measured-load case and ~48 day P10 with a 10% SEN66 event-energy margin. The newer 5.765 mA repeated-event projection shifts the deterministic middle case to 53.9-60.7 days for 80-90% usable capacity at 3%/month self-discharge, which is still inside the same planning band. Hourly SEN66 roughly doubles the median runtime to ~125 days, while a 6 h schedule reaches ~188 days median before deeper platform sleep work becomes the limit.
This table uses the current measured normal non-SEN baseline from the 2000 s capture, the repeated scheduled SEN66 event cost, 80-90% usable pack capacity, and the middle 3%/month self-discharge planning case. It shows the diminishing return of stretching the SEN66 interval once the baseline load and calendar self-discharge dominate.
| SEN66 interval | Average load before self-discharge | Runtime, 80-90% usable with 3%/month self-discharge |
|---|---|---|
| 15 min | 5.792 mA | 53.7-60.4 d |
| 30 min | 3.527 mA | 84.5-95.1 d |
| 1 h | 2.395 mA | 118.6-133.4 d |
| 2 h | 1.829 mA | 148.4-167.0 d |
| 3 h | 1.640 mA | 162.1-182.3 d |
| 6 h | 1.451 mA | 178.4-200.7 d |
| 12 h | 1.357 mA | 187.9-211.4 d |
| 24 h | 1.310 mA | 193.1-217.2 d |
The same interval sweep with the deep-stripped no-display/no-env-I2C floor substituted as the baseline:
| SEN66 interval | Average load before self-discharge | Runtime, 80-90% usable with 3%/month self-discharge |
|---|---|---|
| 15 min | 5.390 mA | 57.4-64.6 d |
| 30 min | 3.126 mA | 94.1-105.9 d |
| 1 h | 1.993 mA | 138.3-155.6 d |
| 2 h | 1.427 mA | 180.8-203.4 d |
| 3 h | 1.239 mA | 201.4-226.6 d |
| 6 h | 1.050 mA | 227.3-255.7 d |
| 12 h | 0.955 mA | 242.9-273.3 d |
| 24 h | 0.908 mA | 251.6-283.0 d |
The daily mAh budget is a more useful planning view than average current alone. At the current 15 minute SEN66 policy, the baseline firmware consumes about 30.31 mAh/day, while SEN66 scheduled measurements consume about 108.70 mAh/day using the repeated-event model. With a 3%/month self-discharge planning assumption, the 10 Ah pack loses another 10.00 mAh/day to calendar effects.
Current firmware baseline, 3%/month self-discharge, 80-90% usable capacity:
| SEN66 interval | Samples/day | Baseline mAh/day | SEN66 mAh/day | Load mAh/day before self-discharge | Load plus self-discharge | Runtime |
|---|---|---|---|---|---|---|
| 15 min | 96.000 | 30.31 | 108.70 | 139.01 | 149.01 | 53.7-60.4 d |
| 30 min | 48.000 | 30.31 | 54.35 | 84.66 | 94.66 | 84.5-95.1 d |
| 1 h | 24.000 | 30.31 | 27.17 | 57.48 | 67.48 | 118.6-133.4 d |
| 2 h | 12.000 | 30.31 | 13.59 | 43.89 | 53.89 | 148.4-167.0 d |
| 3 h | 8.000 | 30.31 | 9.06 | 39.36 | 49.36 | 162.1-182.3 d |
| 6 h | 4.000 | 30.31 | 4.53 | 34.84 | 44.84 | 178.4-200.7 d |
| 12 h | 2.000 | 30.31 | 2.26 | 32.57 | 42.57 | 187.9-211.4 d |
| 24 h | 1.000 | 30.31 | 1.13 | 31.44 | 41.44 | 193.1-217.2 d |
| 48 h | 0.500 | 30.31 | 0.57 | 30.87 | 40.87 | 195.7-220.2 d |
| manual weekly | 0.143 | 30.31 | 0.16 | 30.47 | 40.47 | 197.7-222.4 d |
| SEN66 off | 0.000 | 30.31 | 0.00 | 30.31 | 40.31 | 198.5-223.3 d |
Deep-stripped no-display/no-env-I2C baseline, 3%/month self-discharge, 80-90% usable capacity:
| SEN66 interval | Samples/day | Baseline mAh/day | SEN66 mAh/day | Load mAh/day before self-discharge | Load plus self-discharge | Runtime |
|---|---|---|---|---|---|---|
| 15 min | 96.000 | 20.67 | 108.70 | 129.37 | 139.37 | 57.4-64.6 d |
| 30 min | 48.000 | 20.67 | 54.35 | 75.02 | 85.02 | 94.1-105.9 d |
| 1 h | 24.000 | 20.67 | 27.17 | 47.84 | 57.84 | 138.3-155.6 d |
| 2 h | 12.000 | 20.67 | 13.59 | 34.25 | 44.25 | 180.8-203.4 d |
| 3 h | 8.000 | 20.67 | 9.06 | 29.72 | 39.72 | 201.4-226.6 d |
| 6 h | 4.000 | 20.67 | 4.53 | 25.20 | 35.20 | 227.3-255.7 d |
| 12 h | 2.000 | 20.67 | 2.26 | 22.93 | 32.93 | 242.9-273.3 d |
| 24 h | 1.000 | 20.67 | 1.13 | 21.80 | 31.80 | 251.6-283.0 d |
| 48 h | 0.500 | 20.67 | 0.57 | 21.23 | 31.23 | 256.1-288.2 d |
| manual weekly | 0.143 | 20.67 | 0.16 | 20.83 | 30.83 | 259.5-291.9 d |
| SEN66 off | 0.000 | 20.67 | 0.00 | 20.67 | 30.67 | 260.9-293.5 d |
This table answers the inverse question: how far apart SEN66 measurements must be spaced to hit a target runtime. It now uses the stronger 2000 s repeated-event model: 1.1323 mAh mean incremental SEN66 event cost above idle, 3%/month self-discharge for the field-planning cases, and measured current baselines. "Impossible" means the baseline firmware plus self-discharge already exceeds the daily budget before adding any SEN66 measurements. The full 90-row matrix is saved in docs/research/power/2026-05-16-sen66-target-interval-requirements.json and .csv.
Current normal firmware baseline, 2000 s non-SEN average:
| Target runtime | 90% usable, 3%/month | 80% usable, 3%/month | 80% usable, 3%/month, +5% load/+10% event margin |
|---|---|---|---|
| 90 d | SEN66 interval >= 0.46 h | SEN66 interval >= 0.56 h | SEN66 interval >= 0.64 h |
| 120 d | SEN66 interval >= 0.78 h | SEN66 interval >= 1.03 h | SEN66 interval >= 1.20 h |
| 180 d | SEN66 interval >= 2.80 h | SEN66 interval >= 6.57 h | SEN66 interval >= 11.40 h |
| 270 d | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge |
| 365 d | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge |
Deep-stripped optimization reference baseline:
| Target runtime | 90% usable, 3%/month | 80% usable, 3%/month | 80% usable, 3%/month, +5% load/+10% event margin |
|---|---|---|---|
| 90 d | SEN66 interval >= 0.39 h | SEN66 interval >= 0.47 h | SEN66 interval >= 0.52 h |
| 120 d | SEN66 interval >= 0.61 h | SEN66 interval >= 0.75 h | SEN66 interval >= 0.85 h |
| 180 d | SEN66 interval >= 1.41 h | SEN66 interval >= 1.97 h | SEN66 interval >= 2.35 h |
| 270 d | SEN66 interval >= 10.19 h | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge |
| 365 d | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge | Impossible from baseline + self-discharge |
For a 365 day target at 3%/month self-discharge, the maximum firmware baseline before any SEN66 sampling is only 0.497 mA at 80% usable capacity, 0.611 mA at 90% usable capacity, or 0.725 mA at theoretical 100% usable capacity. The measured optimized floor is still about 0.906 mA, so year-class operation on this 10 Ah pack requires both a lower baseline floor and much less frequent SEN66 use, plus a pack with verified low self-discharge and usable-capacity behavior.
Interpretation: with the current 15 minute policy, SEN66 dominates and UI/LED cleanup only buys days. At hourly or slower SEN66 intervals, baseline cleanup matters more; at 12-24 h intervals, self-discharge becomes large enough that pack chemistry and age limit the forecast even if firmware current is improved further.
| Scenario | Average load | 100% pack | 90% usable | 80% usable |
|---|---|---|---|---|
| SEN66 off / nominal firmware only | 1.265 mA | 329.5 d | 296.5 d | 263.6 d |
| SEN66 off / measured no-SEN6X no-UI no-LED floor | 0.906 mA | 459.9 d | 413.9 d | 367.9 d |
| SEN66 every 15 min, complete scheduled capture | 5.562 mA | 74.9 d | 67.4 d | 59.9 d |
| SEN66 every 15 min with measured no-UI/no-LED floor | 5.203 mA | 80.1 d | 72.1 d | 64.1 d |
| SEN66 hourly, using complete event energy | 2.339 mA | 178.1 d | 160.3 d | 142.5 d |
| SEN66 hourly with measured no-UI/no-LED floor | 1.980 mA | 210.4 d | 189.4 d | 168.3 d |
| SEN66 every 6 h, using complete event energy | 1.444 mA | 288.6 d | 259.8 d | 230.9 d |
| SEN66 every 6 h with measured no-UI/no-LED floor | 1.085 mA | 384.0 d | 345.6 d | 307.2 d |
| SEN66 continuous at 90 mA typical plus baseline | 91.265 mA | 4.6 d | 4.1 d | 3.7 d |
For field planning at room temperature, use the 80-90% capacity columns plus a self-discharge sensitivity. The middle 3%/month case is a reasonable planning line unless the exact pack datasheet says otherwise.
| Scenario | 2%/month, 80-90% usable | 3%/month, 80-90% usable | 5%/month, 80-90% usable |
|---|---|---|---|
| SEN66 off / nominal firmware only | 216-243 d | 198-223 d | 170-191 d |
| SEN66 off / measured no-SEN6X no-UI no-LED floor | 282-317 d | 252-284 d | 208-234 d |
| SEN66 every 15 min, complete scheduled capture | 57-64 d | 56-63 d | 53-60 d |
| SEN66 every 15 min with measured no-UI/no-LED floor | 61-68 d | 59-67 d | 57-64 d |
| SEN66 hourly, using complete event energy | 127-143 d | 121-136 d | 110-124 d |
| SEN66 every 6 h, using complete event energy | 194-218 d | 179-202 d | 156-175 d |
| SEN66 continuous at 90 mA typical plus baseline | 3.6-4.1 d | 3.6-4.1 d | 3.6-4.1 d |
Recommended forecast for the live debug-capable firmware with current 15 minute SEN66 schedule:
| Planning posture | Expected life |
|---|---|
| Optimistic electrical case, fresh pack | ~60-75 d |
| Realistic middle case, 3%/month self-discharge, 80-90% usable | ~56-63 d |
| Conservative field commitment until detached release-power and GPIO-marked data exists | ~53-60 d |
| Conservative aged-pack stress case, 80% usable, 5%/month self-discharge, 10% SEN66 event margin | ~50 d |
| Same stress case with only 70% usable capacity | ~44 d |
Measured UI/LED elimination would only move the 15 minute SEN66 schedule from about 56-63 d to about 59-67 d in the middle 3%/month self-discharge case. The same optimization matters much more when SEN66 is sampled hourly or less often, because baseline current becomes a larger fraction of the pack budget.
Boot energy is negligible against a 10 Ah pack: the measured cold-boot captures were about 0.95-1.14 mAh, or roughly 0.01% of pack capacity. Sustained duty schedule, not boot, determines service life.
Objective restated as deliverables:
| Deliverable | Success criterion | Current evidence | Status |
|---|---|---|---|
| Power-cycle the device and measure the entire boot cycle | Multiple JS220 current-path power cycles with boot windows long enough to include the first SEN66 duty event. | Nineteen cold-boot captures are tabulated above, including final restored-normal-firmware verification after the measurement-only images, a warning-clean release boot, two restored-normal 0.1 s-bin boot repeats, and one debug-probe-detached MCP boot on the current bench image. No-output power-audit boots also reproduced the same SEN66 signature. | Complete electrically; first milliseconds after JS220 power restore may still be missed. |
| Identify power consumed by different boot functions | Attribute large boot phases to early app init, SEN66 warmup/measurement, stop/poweroff, and fully booted nominal state. | Boot phase table maps current regions to main.c, sensor_task.c, sen6x_duty_manager.c, PMIC BUCK1 control, nominal post-SEN66 captures, and the retained RAM event trace. |
Complete at boot/SEN66 phase level; exact tiny-function energy still requires hardware-synchronized markers and faster bins. |
| Run multiple measurements for confidence | Repeat cold boot, nominal, scheduled SEN66, and A/B isolation measurements. | Nineteen boot captures, multiple 30-300 s nominal/event windows plus a corrected-helper 180 s / 0.1 s nominal window, one detached MCP 180 s nominal window, three short complete scheduled SEN66 event captures, two 930 s whole-duty-period validations, one 2000 s normal-firmware multi-duty validation with two repeated scheduled events, one detached MCP segmented duty/tail pass with a scheduled SEN66 event, no-output boot/nominal pairs, explicit target-power-off current-path validation, a retained event-traced boot, 600 s paired BLE stripped-floor captures, and A/B variants for SEN6X, sensor task, UI, display driver/SPI20, environmental I2C, LED, BLE, PMIC manager, PMIC stack, PMIC charger/fuel-gauge, PMIC LED/GPIO/regulator children, PMIC parent/node with I2C22 split, I2C22 pinctrl/GPIO-pull-up state, fuel-gauge cadence, and System OFF attempt. | Complete for average-current forecasting. |
| Study nominal fully booted power | Measure post-SEN66 idle with SEN66 rail off and repeat enough to get a stable baseline. | Continuation nominal A/B/C averaged 1.2646 mA; no-output nominal was 1.274-1.282 mA; stripped no-SEN6X/no-UI/no-LED floor reached 0.903-0.906 mA. A focused UI-enabled stripped variant measured 1.129 mA over the post-boot idle-follow-up window. The corrected-helper 180 s / 0.1 s nominal window averaged 1.259 mA with a 1.077 mA median, and the detached MCP 180 s nominal window averaged 1.266 mA. The first 300 s / 0.1 s-bin capture showed 235 s of sub-2 mA idle bins averaging 1.0966 mA between bursts and the scheduled SEN66 event. The no-reset fully booted 300 s / 0.1 s-bin repeat showed a 1.080 mA median bin and 237.5 s of sub-2 mA bins averaging 1.091 mA. The corrected 929.6 s / 0.5 s duty-period window had a 1.092 mA median and 706.0 s below 1.2 mA. The detached MCP idle duty segments averaged 1.259 mA. The 2000 s normal-firmware capture, excluding SEN66 plateaus, averaged 1.263 mA; 64.5% of non-SEN time was below 1.2 mA and centered near 1.093 mA. | Complete for current firmware and tested measurement variants. |
| Trace every sensor and major operation | Account for SEN66, SHT45, DPS368, SGP41, STC31, PMIC/fuel-gauge, UI/EPD, LED, BLE, and disabled sensors. | SEN66 was measured directly and dominates. SHT45 and DPS368 now have one-sensor 1 Hz probe measurements, with upper-bound deltas of +0.0049 mA and +0.0554 mA respectively; disabling their devicetree nodes plus I2C21 after the display-driver cut did not move the stripped floor. SGP41 and STC31 are off in this build. PMIC/fuel-gauge tick cadence was A/B tested at 1 s versus 60 s, and the application PMIC manager was separately skipped at the stripped floor with no measurable reduction. The 2000 s normal-firmware non-SEN burst analysis found 2-5 mA clusters at a 120 s cadence matching the sensor-task poll interval and UI maximum refresh interval, but those remain combined sensor/UI/PMIC/comm work without markers. The earlier whole nPM13xx/I2C22 stack cut raised current by about +0.308 mA, but follow-up splits showed individual PMIC children, the PMIC parent MFD, and the PMIC node itself are not the cause when I2C22 remains active. I2C22 sleep-pinctrl and I2C22-disabled/GPIO-pull-up variants stayed in the same high-current class, while final-idle TWIM22/GPIO register dumps showed default-pinctrl and sleep-pinctrl images converged to the same visible register state. UI/EPD now has A/B isolation, retained display-write timing, a UI-render-without-display-write split showing the software UI path is near the stripped floor, and a no-display-driver/SPI20 split measuring a 0.8615 mA deeper stripped floor. LED has both no-task and task-running/indicator-off split measurements, showing visible LED duty dominates. BLE was isolated at the stripped floor at about +0.011 to +0.015 mA average across 100 s-class windows, with a paired 30 s / 100 ms-bin run showing +0.050 mA and a paired 600 s deep-stripped run showing +0.0105 mA average. | Partial: exact 120 s cluster split, TWIM22/pinctrl mechanism, and BLE radio-event energy still cannot be separated without event markers or deeper rail instrumentation. |
| Especially characterize SEN66 | Capture boot and scheduled SEN66 duty windows, peaks, duration, duty-cycle projection, and battery impact. | Complete scheduled event 1 measured 46 s at 85.34 mA average with 127.93 mA 1 s peak and projects to 5.56 mA average at the 900 s interval. Complete scheduled event 2 measured 45.6 s at 87.96 mA average with 133.10 mA 0.1 s-bin peak and projects to the same 5.5-5.7 mA schedule band depending on baseline choice. Complete scheduled event 3, captured no-reset and fully booted, measured 45.1 s at 88.30 mA average with a 132.65 mA 0.1 s-bin peak and projects to 5.46 mA using its sub-2 mA idle bins. The 929.1 s validation measured 5.559 mA across one full duty-period-scale window; the corrected 929.6 s validation measured 5.542 mA with a 45.0 s SEN66 span at 89.37 mA and a 132.84 mA peak. The 2000 s normal-firmware capture repeated two non-boot scheduled events at 46-47 s, 87.96-89.85 mA, 131.17-136.23 mA 1 s-bin max, and 1.1323 mAh mean incremental charge above idle, projecting 5.765 mA. Multiple boot captures reproduce the same class of high-current event. Retained trace shows the boot SEN66 powered interval from 9.406 s to 55.917 s firmware uptime. | Complete electrically and code-phase traced for boot; a JS220 GPI edge would improve absolute time alignment. |
| Forecast 10 Ah / 3.7 V LiPo at 27 C under real-world conditions | Include measured loads, usable-capacity derating, self-discharge sensitivity, and alternative SEN66 schedules. | Forecast tables include 100/90/80/70% usable capacity coverage, 0/2/3/5% per month self-discharge in the machine-readable matrix, current 15 minute schedule, hourly, 6 h, continuous SEN66, optimized baseline scenarios, daily mAh budgets, conservative aged-pack stress cases, a 2640-row JSON/CSV deterministic forecast matrix, a 100,000-iteration Monte Carlo forecast generated from the measured 929.1 s validation plus event/baseline uncertainty, a multi-cycle update artifact using the 2000 s repeated-event projection, a 2000 s real-world forecast matrix that keeps the normal non-SEN baseline, pack cases, load/event margins, reverse targets, and boot penalty rows together, a repeatability forecast across seven scheduled SEN66 events and two full-duty windows, and a compact SEN66 target-interval requirement matrix for 60-365 day product targets. | Complete as a planning forecast; pack-specific cutoff behavior and cell datasheet curves would refine it. |
| Produce a power consumption audit report | Keep a dated report under docs/audit/ with methodology, data, attribution, findings, recommendations, and sources. |
This file contains methodology, raw tables, A/B isolation, forecasts, findings, recommendations, source references, and final normal-firmware restoration evidence. | Complete as a living audit artifact. |
Prompt-to-artifact checklist:
| Prompt requirement / follow-up | Concrete artifact or command evidence | Coverage |
|---|---|---|
| Use the Joulescope JS220 path | JS220 device u/js220/005920 appears in saved summaries under docs/research/power/; current-path sanity rows show target-power-off current at 0.0 mA; resumed MCP status, near-5-minute MCP measurement artifacts, and the detached MCP summary show the hosted MCP path is now usable. |
Complete for measurements; earlier long captures used the same local joulescope_mcp.service.Js220Service backend directly after the hosted MCP transport closed, while resumed MCP checks and the detached MCP pass now pass. |
| Set Joulescope MCP timeout to 5 minutes | /Users/juanqui/.codex/config.toml contains tool_timeout_sec = 300 under [mcp_servers.joulescope-js220]; 2026-05-16-mcp-timeout-295s-nominal-summary.json records a 295 s MCP measure_energy call completing in 295.064 s. |
Complete for config and live near-timeout validation. |
| Use smaller floating-point intervals such as 0.1 s | 2026-05-16-nominal-300s-0p1s-*, 2026-05-16-fully-booted-nominal-300s-0p1s-*, 2026-05-16-normal-boot-120s-0p1s-*, 2026-05-16-corrected-powercycle-boot-120s-0p1s.*, 2026-05-16-corrected-postboot-nominal-180s-0p1s.*, and BLE 100 ms artifacts use sub-second requested intervals. |
Complete for boot, scheduled SEN66, nominal, and BLE stripped-floor windows. |
| Debug the apparent no-blink crash before more power work | Crash-triage rows show pre-cycle current, JS220 current-path power cycle, SEN66 high-load follow-up, and RTT evidence of normal LED task, PMIC, BLE, and SEN66 initialization. | Complete for the observed bench state; no fault log was seen. |
| Try power cycles | Multiple JS220 cycle_target_power runs are reflected in boot rows, crash-triage, release, restored-normal, and detached MCP captures. |
Complete electrically through JS220 current path. |
| Correctly interpret JS220 disconnect behavior | Methodology and current-path sanity rows state that voltage sense may still show ~3.7 V while target current flow is disconnected; disconnect validation uses measured current, not voltage. | Complete in measurement method and report. |
| Measure full boot cycle | Cold-boot table includes 19 boot captures from 69.5-120 s, including no-output, release, RAM-traced, 0.1 s restored-normal, and detached MCP variants. | Complete for average and SEN66 boot-energy attribution. |
| Measure nominal fully booted power | Nominal windows, detached MCP nominal and idle segments, post-SEN66 idle checks, 2000 s non-SEN analysis, no-output idle, release idle, and stripped-floor variants are tabulated. | Complete for measured firmware variants. |
| Identify function-level boot consumers | Boot phase table, retained RAM event trace, SEN66 duty-manager source references, and A/B variants identify SEN66 as dominant and split UI, LED, BLE, PMIC, sensor task, display, I2C, and fuel-gauge effects. | Complete for major functions; tiny-operation attribution remains bounded because hard timestamp alignment would need a GPIO/GPI marker that cannot be captured in the current setup. |
| Especially characterize SEN66/SEN66 boot and duty cycle | Boot 18 and Boot 19 0.1 s captures, three complete scheduled captures, 929.1 s validation, 2000 s repeated scheduled events, a resumed-MCP 295 s scheduled-event capture, and retained boot trace cover duration, peaks, charge, energy, and forecast impact. | Complete electrically; hardware marker would improve absolute time alignment. |
| Forecast a 10 Ah / 3.7 V LiPo at 27 C under real-world conditions | 2026-05-16-battery-forecast-*, 2026-05-16-real-world-battery-forecast-2000s.*, 2026-05-16-sen66-repeatability-forecast.*, 2026-05-16-sen66-target-interval-requirements.*, and 2026-05-16-release-baseline-forecast-sensitivity.* cover usable capacity, self-discharge, margins, schedule alternatives, reverse targets, Monte Carlo, event repeatability, and release sensitivity. |
Complete as planning forecast; pack-specific cutoff and datasheet behavior would refine it. |
| Write a power consumption audit report | docs/audit/2026-05-15-power-consumption-audit.md contains methodology, measurements, attribution, forecasts, findings, gaps, recommendations, and artifact index. |
Complete with explicit physical limitations; exact marker-synchronized completion is not claimed. |
Evidence manifest check:
| Manifest | Result | Interpretation |
|---|---|---|
docs/research/power/2026-05-16-power-audit-evidence-manifest.json |
67 non-manifest artifacts checked; 44 JSON and 23 CSV; 0 parse errors; 0 missing report references. | The saved evidence set referenced by this report exists locally and is machine-parseable. |
docs/research/power/2026-05-16-power-audit-evidence-manifest.csv |
One row per non-manifest artifact with path, extension, size, SHA-256, report-reference flag, parse status, and row count when applicable. | Use this CSV for quick review of row counts and integrity hashes. |
Verifier command:
python3 scripts/verify_power_audit_evidence.pyCompletion gate command:
python3 scripts/check_power_audit_completion.py
python3 scripts/check_power_audit_completion.py --output docs/research/power/2026-05-16-power-audit-completion-status.jsonAs of this report revision, the completion gate returns complete=true,
complete_with_explicit_limitations=true, and
exact_marker_synchronized_completion=false. The debug-probe-detached absolute
capture now has a non-template detached MCP artifact for the current bench image.
The wired JS220 GPI marker check remains unpassed, but it is waived for this
audit because the operator confirmed the final physical wiring/capture step
cannot be completed in the current bench setup. That waiver is recorded in
docs/research/power/2026-05-16-gpi-marker-physical-limitation.json. A real
GPI pass still requires explicit marker transition evidence such as
gpi_marker_transitions_detected=true, gpi_marker_validation.passed=true, or a
non-empty gpi_transitions list. The reusable capture helper supports
--poll-gpi-interval <seconds>; the detached/GPI plan uses 0.02 s GPI polling
for boot, nominal, and duty-period captures if the physical setup becomes
available later.
Uncovered requirements before claiming exact marker-synchronized completion:
| Gap | Why it matters | Next evidence required |
|---|---|---|
| No hardware-synchronized firmware marker was captured alongside JS220 samples. | Retained RAM trace identifies firmware phases, but JS220 bins and firmware uptime are aligned indirectly rather than by a shared edge. CONFIG_SIMPLEAIR_POWER_EVENT_GPIO_MARKER now build-checks the firmware side of a power-marker0 GPIO marker, but no physical JS220 GPI capture exists. |
Waived for this audit because the physical step cannot be completed. If the setup changes, follow ../research/2026-05-16-power-detached-gpi-runbook.md: pick and wire a safe marker pad, enable power-marker0, then repeat boot and nominal captures. |
| Release/System OFF floors still need detached-marker follow-up. | The current restored-normal image now has a debug-probe-detached MCP capture, but no GPIO marker was wired and no fresh release or System OFF image was flashed immediately before that detached pass. | Treat release/System OFF floors as bounded, not exact marker-aligned results. If the setup changes, wire a safe JS220 GPI marker and repeat release idle/System OFF or other final floor captures with current-path validation by current, not voltage. |
| Exact I2C22 TWIM/default-pinctrl mechanism, PMIC/regulator hardware baseline, and exact BLE radio-event costs remain below the current instrumentation resolution. | They matter once SEN66 duty factor is reduced to hourly or multi-hour intervals. | Use TWIM22 register/pinctrl-state dumps, PMIC rail-state instrumentation, BLE event markers, and 100 ms or faster captures with debug probe detached. |
| Severity | Finding | Evidence | Recommendation |
|---|---|---|---|
| Major | SEN66 duty cycle is the dominant battery-life cost. | Complete scheduled capture 1 measured 85.34 mA for 46 s per 900 s, projecting to 5.56 mA average including the measured baseline. Complete scheduled capture 2 measured 87.96 mA for 45.6 s with a 133.10 mA 0.1 s-bin peak, keeping the projected 900 s schedule in the same 5.5-5.7 mA band. Contiguous 929.1 s and 929.6 s validations measured 5.559 mA and 5.542 mA directly. The 2000 s normal-firmware validation repeated two non-boot scheduled events and projects 5.765 mA steady current before self-discharge. | Decide whether 15 minute SEN66 sampling is a product requirement. If year-class battery life is required, the SEN66 schedule needs a radically lower duty factor or event-driven/manual sampling. |
| Major | The current measurement can claim boot/SEN66 code-phase timing, but not exact hardware-edge synchronized sub-0.5 s energy per tiny function. | RAM trace captured 66 firmware events with no overflow; JS220 GPI pins were all low, so firmware uptime and JS220 bins are not edge-synchronized. A measurement-only power-marker0 GPIO marker path now compiles warning-clean, but it has not been wired or captured, and the operator confirmed this final physical step cannot be completed in the current setup. |
Use the current report for major-function attribution. If line-level energy attribution becomes a product requirement, repeat boot captures only after a safe marker pad can be wired to JS220 GPI. |
| Major | Debug-capable captures are not a release-power floor, but no-RTT cleanup did not materially change the idle budget. | app/prj.power_audit.conf disabled debug/log/RTT/console/printk/banner output and measured 1.282 mA nominal, close to the 1.2646 mA debug-capable nominal mean. |
Keep using measured current instead of assuming a large release-build savings. Continue release cleanup for correctness, but focus battery-life optimization on SEN66 duty factor and sleep behavior. |
| Major | Release and power-audit builds now resolve to no-output, warning-clean configurations; probe-attached release current matches the existing model. | After updating app/prj.release.conf, moving RTT/log defaults into gated app/Kconfig defaults, and removing unconditional RTT/log assignments from app/prj.conf, both build-release-layered and build-power-audit link warning-clean. Final .config has CONFIG_SIMPLEAIR_ENABLE_DEBUG=n, CONFIG_LOG=n, CONFIG_USE_SEGGER_RTT=n, CONFIG_CONSOLE=n, CONFIG_PRINTK=n, CONFIG_EARLY_CONSOLE=n, CONFIG_BOOT_BANNER=n, CONFIG_NCS_BOOT_BANNER=n, CONFIG_ASSERT=n, and device runtime PM enabled. CONFIG_PM still resolves to n because system PM is unavailable on this target. The warning-clean release candidate then measured 32.98 mA over a 120 s boot window, 84.88 mA across 45 SEN66-class seconds, and 1.229 mA in a post-boot idle window. |
Repeat the release measurement with the debug probe physically detached for absolute-current confidence; otherwise keep focusing optimization effort on SEN66 duty factor and sleep-state residency, not logging. |
| Major | Current 15 minute SEN66 schedule is incompatible with multi-month battery life on 10 Ah unless about 2 months is acceptable. | The 2000 s repeated-event projection gives 53.9-60.7 days for 80-90% usable capacity at 3%/month self-discharge. The stricter 2000 s real-world matrix, using the normal non-SEN baseline, gives 53.7-60.4 days for the same planning case, 49.6 days with modest load/event margin, and 40.0 days for an aged conservative pack case. | Move SEN66 to hourly, multi-hour, explicit user-requested, charger-only, or burst-per-day policy if the product target is closer to 6-12 months. |
| Minor | SEN6X disabled A/B confirms that SEN66 boot energy is entirely the powered duty window. | No-SEN6X boot measured 0.0301 mAh over 79.5 s, versus 1.058-1.099 mAh in no-output normal boot captures with the SEN66 plateau. | Use the no-SEN6X overlay as the reference floor for future non-SEN subsystem A/B work. |
| Minor | SHT45 and DPS368 are small relative to the baseline floor. | One-sensor 1 Hz probe variants measured SHT45 at +0.0049 mA and DPS368 at +0.0554 mA above the stripped baseline; the older whole sensor-task A/B was only +0.003 mA. | Treat these as upper bounds because each includes probe-thread wake overhead. For exact transaction energy, add GPIO markers around each fetch and capture at 100 ms or faster. |
| Minor | Environmental sensor driver and I2C21 init state are not a measurable contributor after the display stack is removed. | With display driver/SPI20 already disabled, disabling SHT45, DPS368, and I2C21 measured 0.8611 mA mean, essentially identical to the 0.8615 mA no-display-driver mean. | Do not chase SHT45/DPS368 driver registration as a baseline-power optimization until larger terms are exhausted. Keep exact transaction-energy work focused on marked fetch windows. |
| Minor | Normal-firmware non-SEN wakeups have two visible cadence classes. | In the 2000 s normal trace with SEN66 plateaus removed, 64.5% of non-SEN time was below 1.2 mA and centered near 1.093 mA. 1.4-2.0 mA one-second bins had a median 3 s gap. Larger 2-5 mA non-SEN clusters were only 1.7% of non-SEN time and, after boot, repeated with a 120 s median gap matching SENSOR_POLL_INTERVAL_MS and UI_UPDATE_MAX_INTERVAL_MS. |
Use GPIO markers around sensor poll, UI render/write, PMIC status read, and BLE/controller events before trying to optimize the 120 s clusters; timing alone cannot split them. |
| Minor | Display hardware write/post-write behavior and visible LED indication are the largest measured non-SEN66 overheads. | No-SEN6X/no-UI measured 1.122 mA; no-SEN6X/no-LED measured 1.164 mA; no-SEN6X/no-UI/no-LED measured 0.906 mA steady. Focused UI/EPD trace showed display writes around 1.3-2.0 s long but only about 7-10 mJ above local idle per write, while UI-render-without-display-write measured 0.897 mA, close to the stripped floor. Disabling the display driver, EPD node, and SPI20 measured 0.8610-0.8620 mA, about 0.031-0.034 mA below the display-driver-present stripped no-BLE floor. LED-task-running/indicator-off measured 0.897 mA versus the 0.892-0.893 mA no-LED-task no-BLE floor, so visible LED duty dominates the 0.102 mA LED delta. | Make LED indications sparse or charger-only for battery operation. Audit EPD/display-driver post-write state and consider stronger panel parking after refreshes. |
| Minor | BLE idle advertising is measurable only after stripping larger firmware wakeups, and the long-window average is small. | The first no-SEN6X/no-BLE pair was inconclusive, but repeated no-SEN6X/no-sensor/no-UI/no-LED/FG60 100 s-class windows measured BLE on at 0.900-0.917 mA and BLE off at 0.8920-0.8927 mA, for about +0.011 to +0.015 mA average BLE overhead. A paired 30 s / 100 ms-bin capture measured 0.945 mA BLE-on and 0.894 mA BLE-off, or +0.050 mA. The cleaner 600 s deep-stripped pair measured 0.8705 mA BLE-on and 0.8601 mA BLE-off, or +0.0105 mA / +0.0387 mW average. | Use the 600 s pair for average battery forecasts. Use GPIO markers or radio event tracing if BLE radio-event energy must be separated from scheduler and platform wakeups. |
| Minor | Fuel-gauge cadence is not the cause of the remaining stripped floor. | No-SEN6X/no-UI/no-LED measured 0.906 mA with 1 Hz fuel-gauge cadence; the same floor with 60 s fuel-gauge cadence measured 0.903 mA. | Stop spending power-optimization effort on fuel-gauge tick cadence until sleep-state residency and regulator/radio baseline have been measured directly. |
| Minor | Application PMIC manager init/status work is not the cause of the remaining stripped floor. | With no-SEN6X/no-sensor/no-UI/no-LED/no-BLE/FG60, skipping pmic_manager_init() measured 0.8919-0.8931 mA, essentially identical to the 0.8920-0.8927 mA stripped no-BLE floor. |
Treat the remaining PMIC question as hardware/regulator quiescent current and rail state, not application PMIC polling. Measure with rail-state instrumentation and debug probe detached. |
| Minor | The +0.308 mA no-PMIC-stack result depends on the I2C22/TWIM22 configuration path, not PMIC parent or child driver overhead, but the visible final idle registers do not explain it. | Disabling the PMIC node, I2C22, and nPM13xx regulator/charger/GPIO/LED/fuel-gauge drivers raised the stripped floor to 1.1693 mA, about +0.308 mA above the no-env-I2C stripped floor. Repeating the exact floor with PMIC stack active but application PMIC manager skipped measured 0.8609 mA. Individual child-disabled splits stayed near that baseline: charger/fuel gauge 0.8666 mA, LED 0.8602 mA, GPIO 0.8619 mA, and regulators 0.8627 mA. PMIC-parent-MFD-only averaged 0.8692 mA, and PMIC-node-disabled with I2C22 left enabled averaged 0.8696 mA. Forcing I2C22 to use sleep pinctrl while enabled averaged 1.1699 mA, and disabling I2C22 while parking P1.11/P1.15 as GPIO pull-up inputs averaged 1.1660 mA. Register dumps after boot showed default-pinctrl and sleep-pinctrl images both idle with ENABLE=0, PSEL.SCL=0x2b, PSEL.SDA=0x2f, and P1.11/P1.15 PIN_CNF=0x802; the disabled/GPIO-pull-up image had disconnected TWIM PSELs and PIN_CNF=0x0c. |
Do not disable I2C22 as a power optimization. Next, instrument pinctrl/TWIM init and PM-device runtime transitions, then repeat with debug probe detached and current-path validation by current, not voltage. |
| Minor | System OFF did not reduce bench current below the stripped idle floor. | The poweroff build measured 0.895 mA over the boot-to-off window and 0.894 mA in the following window. | Add a GPIO marker immediately before sys_poweroff(), detach SWD, and separately inspect PMIC rail state before treating this as a real System OFF silicon floor. |
| Minor | First boot sample windows are reproducible. | Nineteen cold-boot captures now land around 0.95-1.14 mAh total boot charge, with 0.5-1 s-bin SEN66 peaks around 116-133 mA, one 0.252 s-bin peak around 131 mA, and restored-normal 0.1 s-bin peaks of 134.70 mA and 133.84 mA. | Use GPIO-marked captures after instrumentation to get code-phase confidence intervals. |
| Minor | Later 900 s duty-cycle behavior is now repeat-captured electrically, while boot SEN66 behavior is code-phase traced. | Continuation measurements captured three short complete scheduled SEN66 high-load events, two 930 s whole-duty-period validations, and one 2000 s normal-firmware validation with two repeated scheduled events at 957-1003 s and 1903-1950 s. All match the configured warmup duration and forecast band; retained boot trace shows SEN66 power-on, measurement start, read, stop, and power-off timing. | Repeat a scheduled non-boot SEN66 event with retained trace or GPIO markers only if scheduled-event timing needs the same code-phase proof as boot. |
Detailed execution checklist: ../research/2026-05-16-power-detached-gpi-runbook.md.
- Restore the measurement precondition: the DUT load current must flow through the JS220 current path, no battery or USB target-power path may bypass the meter, and the debug probe must be detached for absolute-current captures. Use measured current through the JS220, not sensed voltage, as the disconnect validation.
- For hard clock alignment, pick a safe externally wired pad, define it as the
power-marker0devicetree alias in a measurement overlay, and enableCONFIG_SIMPLEAIR_POWER_EVENT_GPIO_MARKER=ywithCONFIG_SIMPLEAIR_POWER_EVENT_TRACE=y. The retained RAM trace already covers firmware phases; the GPIO marker path now drives a JS220 GPI edge while selected high-value spans are active. A measurement-onlySENSOR_IO2/ J7.5 / P0.04 marker overlay now builds asbuild-power-marker-sensorio2, but it may only be flashed after confirming the J7 daughter/radar path is absent, unpowered, or intentionally disconnected. The detached/GPI runbook includes the RevE marker-pad shortlist:P0.01is the best electrical candidate only if the no-connect module pad is physically accessible,SENSOR_IO2is the best accessible candidate under the J7 precondition, andP2.10or other J7SENSOR_IO*pins are weaker output-only fallbacks. - Build a release-power measurement variant with RTT/logging disabled and no debug probe attached.
- Repeat this audit sequence:
- 5 s live sanity capture.
- 5 cold boots at 70-90 s, 0.5-1 s bins.
- 5 post-boot nominal captures at 90 s.
- One direct 900 s duty-cycle capture with debug probe physically detached and a GPIO marker if absolute release-power confidence is required. Attached-probe 929.1 s and 929.6 s validations are now complete.
- Continue A/B captures from the no-SEN6X floor with GPIO-marked TWIM22/pinctrl init transitions, PMIC rail-state variants, and GPIO-marked BLE radio-event windows to assign exact deltas to the remaining sub-milliamp functions. The first TWIM22 final-idle register dump did not expose the current mechanism.
| Source | Lines | Use |
|---|---|---|
app/src/main.c |
111-164 | Measurement-only I2C22 GPIO pull-up parking variant. |
app/src/main.c |
147-387 | Boot/init sequence. |
app/Kconfig |
75-84 | Measurement-only I2C22 GPIO pull-up Kconfig option. |
app/src/tasks/sen6x_duty_manager.c |
101-159, 217-243, 324-484, 497-522 | SEN66 rail control, measurement state machine, boot scheduling. |
app/src/tasks/pmic_manager.c |
520-590 | BUCK1 regulator control path. |
app/src/tasks/sensor_task.c |
125-162, 301-405, 407-540 | Sensor initialization, SEN6X guard cleanup, and SHT45/DPS368 polling. |
app/src/tasks/comm_manager.c |
147-226 | BLE advertising state transition. |
app/src/tasks/ble_manager.c |
433-514 | BLE enable and fast advertising. |
scripts/capture_js220_power.py |
n/a | Reusable direct-backend JS220 capture helper for detached-probe and GPI-marker passes. |
scripts/analyze_sen66_repeatability.py |
n/a | Regenerates the SEN66 repeatability and runtime-sensitivity JSON/CSV artifacts from saved measurements. |
scripts/run_detached_power_capture_plan.py |
n/a | Plans or executes the detached-probe capture sequence with a current-only target-power-off gate before boot capture. |
scripts/check_power_audit_completion.py |
n/a | Checks report/artifact completion status and names the remaining physical blockers. |
scripts/verify_power_audit_evidence.py |
n/a | Regenerates and verifies the power-audit evidence manifest. |
docs/research/power/2026-05-16-nominal-300s-0p1s-summary.json |
n/a | 300 s / 0.1 s-bin JS220 scheduled-event repeat summary. |
docs/research/power/2026-05-16-nominal-300s-0p1s-event-analysis.json |
n/a | Idle/event split and 900 s projection for the repeated scheduled SEN66 capture. |
docs/research/power/2026-05-16-nominal-300s-0p1s-samples.csv |
n/a | Compact per-bin current, power, voltage, charge, and energy data for the repeated scheduled SEN66 capture. |
docs/research/power/2026-05-16-fully-booted-nominal-300s-0p1s-summary.json |
n/a | No-reset fully booted 300 s capture at 0.1 s bins, with another scheduled SEN66 event. |
docs/research/power/2026-05-16-fully-booted-nominal-300s-0p1s-samples.csv |
n/a | Per-bin charge/energy rows with derived current and power for the no-reset fully booted 300 s capture. |
docs/research/power/2026-05-16-long-930s-1s-summary.json |
n/a | 929.1 s whole-duty-period JS220 validation summary. |
docs/research/power/2026-05-16-long-930s-1s-event-analysis.json |
n/a | Idle/event split and projection for the whole-duty-period validation. |
docs/research/power/2026-05-16-long-930s-1s-samples.csv |
n/a | Compact per-bin current, power, voltage, charge, and energy data for the whole-duty-period validation. |
docs/research/power/2026-05-16-long-2000s-normal-1s-summary.json |
n/a | 2000 s normal-firmware multi-duty JS220 validation summary. |
docs/research/power/2026-05-16-long-2000s-normal-1s-event-analysis.json |
n/a | Boot/scheduled event split and steady 900 s projection for the 2000 s normal capture. |
docs/research/power/2026-05-16-long-2000s-normal-1s-non-sen-burst-analysis.json |
n/a | Non-SEN66 idle/burst classification and cadence correlation from the 2000 s normal capture. |
docs/research/power/2026-05-16-long-2000s-normal-1s-samples.csv |
n/a | 2000 per-bin rows for the normal-firmware multi-duty validation. |
docs/research/power/2026-05-16-battery-forecast-summary.json |
n/a | Handpicked 10 Ah / 3.7 V LiPo forecast scenarios at 27 C. |
docs/research/power/2026-05-16-battery-forecast-matrix.json |
n/a | Full forecast matrix over baseline model, SEN66 interval, usable capacity, self-discharge, and event margin. |
docs/research/power/2026-05-16-battery-forecast-matrix.csv |
n/a | CSV export of the 2640-row battery forecast matrix. |
docs/research/power/2026-05-16-battery-forecast-monte-carlo.json |
n/a | 100,000-iteration probabilistic runtime forecast at 27 C. |
docs/research/power/2026-05-16-battery-forecast-monte-carlo-sample.csv |
n/a | 2,000-row sample from the Monte Carlo inputs and outputs. |
docs/research/power/2026-05-16-battery-forecast-multi-cycle-update.json |
n/a | Forecast update using the 2000 s repeated scheduled-event projection. |
docs/research/power/2026-05-16-real-world-battery-forecast-2000s.json |
n/a | 2000 s repeated-event real-world forecast with pack cases, margins, reverse targets, and boot penalty rows. |
docs/research/power/2026-05-16-real-world-battery-forecast-2000s.csv |
n/a | 165-row CSV matrix from the 2000 s real-world forecast artifact. |
docs/research/power/2026-05-16-sen66-target-interval-requirements.json |
n/a | Compact SEN66 interval requirements for target runtimes from 60-365 days. |
docs/research/power/2026-05-16-sen66-target-interval-requirements.csv |
n/a | 90-row CSV matrix of required SEN66 intervals by baseline, pack case, and target runtime. |
docs/research/power/2026-05-16-sen66-repeatability-forecast.json |
n/a | Repeatability summary and runtime sensitivity across seven scheduled SEN66 events and two full-duty windows. |
docs/research/power/2026-05-16-sen66-repeatability-forecast.csv |
n/a | 84-row CSV matrix using measured SEN66 event min/mean/max, 10 Ah pack cases, self-discharge, and interval sweeps. |
docs/research/power/2026-05-16-release-baseline-forecast-sensitivity.json |
n/a | Release-idle sensitivity check using the 2000 s repeated scheduled-event forecast basis. |
docs/research/power/2026-05-16-release-baseline-forecast-sensitivity.csv |
n/a | CSV export of the release-idle sensitivity rows. |
docs/research/power/2026-05-16-host-physical-blocker-check.json |
n/a | Non-invasive host-side check showing J-Link USB enumeration, JS220 target-power state, and all-low GPI marker state. |
docs/research/power/2026-05-16-joulescope-mcp-transport-diagnostic.json |
n/a | Diagnostic showing the standalone Joulescope MCP stdio client works while the current Codex-hosted MCP tool transport returns Transport closed. |
docs/research/power/2026-05-16-joulescope-mcp-resumed-status.json |
n/a | Resumed-process MCP reachability check showing JS220 status, all-low GPI state, and a short 5 s / 0.1 s MCP energy measurement. |
docs/research/power/2026-05-16-mcp-timeout-295s-nominal-summary.json |
n/a | Near-5-minute MCP timeout validation and fully booted nominal-current check: 295 s requested, 294.6 s actual, 1.263 mA average. |
docs/research/power/2026-05-16-mcp-scheduled-sen66-event-295s-summary.json |
n/a | Near-5-minute MCP-hosted scheduled SEN66 event capture: 295 s requested, 46.0 s event, 1.0899 mAh incremental charge, 86.56 mA event average. |
docs/research/power/2026-05-16-detached-release-mcp-summary.json |
n/a | Debug-probe-detached MCP summary on the current restored-normal bench image: current-only target-power-off validation, 120.9 s boot, 180 s nominal, and four 295 s duty/tail segments with one scheduled SEN66 event. |
docs/research/power/2026-05-16-marker-sensorio2-build-validation.json |
n/a | Build-only validation for the measurement-only JS220 GPI marker overlay on SENSOR_IO2 / J7.5 / P0.04; this is not a physical marker capture. |
docs/research/power/2026-05-16-detached-template-capture-plan.json |
n/a | Dry-run detached-probe capture plan generated by scripts/run_detached_power_capture_plan.py. |
docs/research/power/2026-05-16-detached-template-gpi-capture-plan.json |
n/a | Dry-run detached-probe plus JS220 GPI-poll capture plan generated with --require-gpi-marker --gpi-poll-interval 0.02. |
docs/research/power/2026-05-16-detached-release-capture-plan.json |
n/a | Direct-helper detached capture plan written before the helper discovered that the hosted MCP server owned the JS220; the actual detached measurements were taken through 2026-05-16-detached-release-mcp-summary.json. |
docs/research/power/2026-05-16-power-audit-completion-status.json |
n/a | Machine-readable completion-gate result; currently incomplete because the wired-GPI marker capture is still missing. |
docs/research/power/2026-05-16-power-audit-build-config-audit.json |
n/a | Revalidated warning-clean power-audit build command and final no-output .config checks. |
docs/research/power/2026-05-16-release-layered-build-config-audit.json |
n/a | Revalidated warning-clean layered release build after app/prj.release.conf and base Kconfig cleanup, with final .config checks. |
docs/research/power/2026-05-16-ble-on-stripped-600s-1s-summary.json |
n/a | 600 s deep-stripped BLE-on JS220 summary. |
docs/research/power/2026-05-16-ble-on-stripped-600s-1s-samples.csv |
n/a | 600 per-bin rows for the deep-stripped BLE-on capture. |
docs/research/power/2026-05-16-ble-off-stripped-600s-1s-summary.json |
n/a | 600 s deep-stripped BLE-off JS220 summary. |
docs/research/power/2026-05-16-ble-off-stripped-600s-1s-samples.csv |
n/a | 600 per-bin rows for the deep-stripped BLE-off capture. |
docs/research/power/2026-05-16-crash-triage-power-cycle-summary.json |
n/a | Crash-triage power-cycle and RTT summary after the no-blink bench observation. |
docs/research/power/2026-05-16-final-restore-after-ble-long-90s-summary.json |
n/a | Default-firmware final restore boot sanity capture after the long BLE A/B work. |
docs/research/power/2026-05-16-final-restore-after-ble-long-90s-samples.csv |
n/a | 90 per-bin rows for the final restored-normal boot sanity capture. |
docs/research/power/2026-05-16-final-restore-after-ble-long-idle-5s-summary.json |
n/a | Post-restore SEN66-off idle sanity check. |
docs/research/power/2026-05-16-release-layered-off-3s-summary.json |
n/a | Warning-clean release candidate target-power-off current-path validation. |
docs/research/power/2026-05-16-release-layered-off-3s-samples.csv |
n/a | Per-bin rows for the release target-power-off validation. |
docs/research/power/2026-05-16-release-layered-boot-120s-summary.json |
n/a | Warning-clean release candidate boot capture after verified flash and JS220 current-path power cycle. |
docs/research/power/2026-05-16-release-layered-boot-120s-samples.csv |
n/a | 120 per-bin rows for the release boot capture. |
docs/research/power/2026-05-16-release-layered-postboot-idle-20s-summary.json |
n/a | Warning-clean release candidate SEN66-off post-boot idle capture. |
docs/research/power/2026-05-16-release-layered-postboot-idle-20s-samples.csv |
n/a | 20 per-bin rows for the release post-boot idle capture. |
docs/research/power/2026-05-16-normal-boot-120s-0p1s-summary.json |
n/a | Restored-normal current-path power-cycle boot capture at 0.1 s bins with SEN66 span analysis. |
docs/research/power/2026-05-16-normal-boot-120s-0p1s-samples.csv |
n/a | Non-overlapping 0.1 s-bin rows for the restored-normal boot repeat. |
docs/research/power/2026-05-16-default-restore-after-release-15s-summary.json |
n/a | Short JS220 sanity capture after restoring default debug-capable firmware over the release image. |
docs/research/power/2026-05-16-default-restore-after-release-15s-samples.csv |
n/a | 15 per-bin rows for the default restore sanity capture. |
docs/research/power/2026-05-16-corrected-target-power-off-5s.json |
n/a | Corrected helper target-power-off validation with first-bin residual artifact and current range held off. |
docs/research/power/2026-05-16-corrected-target-power-off-5s.csv |
n/a | Five per-bin rows for the corrected helper target-power-off validation. |
docs/research/power/2026-05-16-corrected-target-power-off-10s.json |
n/a | Corrected helper target-power-off repeat passing the current-only validation at 0.0 mA / 0.0 mAh. |
docs/research/power/2026-05-16-corrected-target-power-off-10s.csv |
n/a | Ten per-bin rows for the corrected helper target-power-off repeat. |
docs/research/power/2026-05-16-corrected-powercycle-boot-120s-0p1s.json |
n/a | Corrected helper 120 s / 0.1 s current-path power-cycle boot summary with SEN66 span analysis. |
docs/research/power/2026-05-16-corrected-powercycle-boot-120s-0p1s.csv |
n/a | 1200 per-bin rows for the corrected helper current-path power-cycle boot capture. |
docs/research/power/2026-05-16-corrected-postboot-nominal-180s-0p1s.json |
n/a | Corrected helper fully booted 180 s / 0.1 s post-SEN66 nominal summary. |
docs/research/power/2026-05-16-corrected-postboot-nominal-180s-0p1s.csv |
n/a | 1800 per-bin rows for the corrected helper post-boot nominal capture. |
docs/research/power/2026-05-16-corrected-duty-period-930s-0p5s.json |
n/a | Corrected helper fully booted 930 s / 0.5 s duty-period summary with one scheduled SEN66 event. |
docs/research/power/2026-05-16-corrected-duty-period-930s-0p5s.csv |
n/a | 1860 per-bin rows for the corrected helper duty-period capture. |
docs/research/power/2026-05-16-power-audit-evidence-manifest.json |
n/a | Machine-readable evidence manifest for power-audit artifacts, parse status, row counts, and SHA-256 digests. |
docs/research/power/2026-05-16-power-audit-evidence-manifest.csv |
n/a | CSV export of the evidence manifest. |
docs/research/2026-05-16-power-detached-gpi-runbook.md |
n/a | Runbook for the remaining debug-probe-detached and JS220 GPI/GPIO-marker measurement passes. |
docs/research/2026-05-16-power-audit-completion-audit.md |
n/a | Prompt-to-artifact completion audit showing which user requirements are covered and which physical blockers remain. |
app/prj.conf |
17-24, 35-52, 65-68, 128-152, 178-225 | Active feature set. |
app/prj.release.conf |
6-43 | Release-mode power-management, logging, console, banner, printk, and assertion differences. |
app/prj.power_audit.conf |
1-38 | Measurement-only no-output overlay. |
app/prj.power_audit_no_sen6x.conf |
1-10 | No-SEN6X A/B Kconfig overlay. |
app/prj.power_audit_no_sensor_task.conf |
1-7 | No-sensor-task A/B Kconfig overlay. |
app/prj.power_audit_no_ui_task.conf |
1-7 | No-UI-task A/B Kconfig overlay. |
app/prj.power_audit_no_display_driver.conf |
1-9 | No-display-driver A/B Kconfig overlay. |
app/prj.power_audit_no_env_i2c.conf |
1-8 | No-environmental-I2C A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_stack.conf |
1-13 | No-PMIC-stack A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_charger.conf |
1-7 | No-PMIC-charger A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_led.conf |
1-6 | No-PMIC-LED-child A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_gpio.conf |
1-6 | No-PMIC-GPIO-child A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_regulators.conf |
1-6 | No-PMIC-regulator-child A/B Kconfig overlay. |
app/prj.power_audit_pmic_mfd_only.conf |
1-10 | PMIC-parent-MFD-only A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_node_keep_i2c.conf |
1-10 | No-PMIC-node with I2C22 retained A/B Kconfig overlay. |
app/prj.power_audit_i2c22_gpio_pullups.conf |
1-6 | I2C22-disabled GPIO-pull-up A/B Kconfig overlay. |
app/prj.power_audit_no_led_task.conf |
1-7 | No-LED-task A/B Kconfig overlay. |
app/prj.power_audit_no_ble.conf |
1-13 | No-BLE A/B Kconfig overlay. |
app/prj.power_audit_fg_60s.conf |
1-7 | Fuel-gauge 60 s cadence A/B Kconfig overlay. |
app/prj.power_audit_no_pmic_manager.conf |
1-9 | No-PMIC-manager A/B Kconfig overlay. |
app/prj.power_audit_poweroff.conf |
1-8 | System OFF attempt A/B Kconfig overlay. |
app/prj.power_audit_sht45_probe.conf |
1-9 | SHT45-only 1 Hz probe Kconfig overlay. |
app/prj.power_audit_dps368_probe.conf |
1-9 | DPS368-only 1 Hz probe Kconfig overlay. |
app/prj.power_audit_marker_sensorio2.conf |
1-2 | Measurement-only power-event trace plus JS220 GPI marker Kconfig overlay for the checked-in SENSOR_IO2 marker build. |
app/Kconfig |
219-240 | Retained event trace and optional power-marker0 JS220 GPI marker Kconfig. |
app/src/tasks/power_event_trace.c |
1-160 | Retained event trace buffer and optional GPIO marker span driver. |
app/src/tasks/sensor_power_probe.c |
1-75 | Measurement-only one-sensor probe task. |
app/boards/power_audit_no_sen6x.overlay |
1-11 | No-SEN6X A/B devicetree overlay. |
app/boards/power_audit_no_display_driver.overlay |
1-9 | No-display-driver A/B devicetree overlay. |
app/boards/power_audit_no_env_i2c.overlay |
1-18 | No-environmental-I2C devicetree overlay. |
app/boards/power_audit_no_pmic_stack.overlay |
1-30 | No-PMIC-stack devicetree overlay. |
app/boards/power_audit_no_pmic_charger.overlay |
1-5 | No-PMIC-charger devicetree overlay. |
app/boards/power_audit_no_pmic_led.overlay |
1-5 | No-PMIC-LED-child devicetree overlay. |
app/boards/power_audit_no_pmic_gpio.overlay |
1-5 | No-PMIC-GPIO-child devicetree overlay. |
app/boards/power_audit_no_pmic_regulators.overlay |
1-5 | No-PMIC-regulator-child devicetree overlay. |
app/boards/power_audit_pmic_mfd_only.overlay |
1-17 | PMIC-parent-MFD-only devicetree overlay. |
app/boards/power_audit_no_pmic_node_keep_i2c.overlay |
1-23 | No-PMIC-node with I2C22 retained devicetree overlay. |
app/boards/power_audit_i2c22_sleep_pinctrl.overlay |
1-34 | I2C22 enabled with sleep pinctrl as default devicetree overlay. |
app/boards/power_audit_marker_sensorio2.overlay |
1-20 | Measurement-only power-marker0 overlay on SENSOR_IO2 / J7.5 / P0.04, with optional mmwave_mcu_in disabled. |
app/src/tasks/pmic_manager.c |
130-252 | Guarded PMIC child-device access for child-disabled measurement builds. |
app/src/tasks/ui_task_stub.c |
1-49 | Measurement-only UI API stub for builds without a display driver. |
app/src/battery_model.inc |
7, 19 | 27 C battery model and MakerFocus10Ah model identity. |
drivers/sensor/sen6x/Kconfig |
110-126 | SEN66 interval and warmup defaults. |
../modules/hal/nordic/nrfx/bsp/stable/mdk/nrf54l15_types.h |
37147-37205, 38029-38052, 9959-9976, 12202-12272 | TWIM22 and GPIO1 register offsets and field decoding for the I2C22 register follow-up. |
../zephyr/dts/vendor/nordic/nrf54l_05_10_15.dtsi |
389-397, 550-557 | I2C22 and GPIO1 peripheral base offsets under peripheral@50000000. |
boards/ne0xlabs/simpleair_reve/simpleair_reve_common.dtsi |
144-194, 205-277 | Sensor bus and power rail mapping. |
docs/devices/sen6x.md |
484-555 | SEN66 power table and duty-cycle notes. |