Run status and operating mode — the field guide¶
Written 2026-10-12. Every mapping below is taken from source or from a live read on the reference aGate (192.168.0.110), not from recollection. Where something is not yet observed, it says so.
This exists because the same question was answered wrongly three times in one session: does the local API see VPP mode? The answer is yes — but not in the field everyone reaches for first.
1. There are TWO fields, and they answer different questions¶
The single most common mistake is treating "run status" as one thing. It is two.
| Question | Cloud field | Local field | Values |
|---|---|---|---|
| What is the battery physically doing? | runtimeData.run_status |
1301 run_status |
RUN_STATUS table (§2) |
| Which programme is in control? | runtimeData.mode (tou_mode) |
1707 ibg_run_status.ibgRunStatus |
programme id; 9 = VPP |
| What is the programme called? | runtimeData.name |
name (both frames) |
e.g. Self-Consumption; empty during VPP |
The FranklinWH Cloud cookbook states this explicitly: the RUN_STATUS value table is
"keyed by runtimeData.run_status, not runtimeData.mode", while the VPP rule in
effective_mode is "RUN_STATUS[9] = "VPP mode" if tou_mode == 9 and name is
empty".
So run_status == 9 is not how you detect VPP, on either channel. run_status stays
at the battery direction. VPP shows up in the programme field.
2. RUN_STATUS — what the battery is doing¶
Identical in both libraries: franklinwh_cloud.const.modes.RUN_STATUS and the local
catalog.run_status_desc().
| Value | Label | Note |
|---|---|---|
| 0 | Standby | inactive / idle |
| 1 | Charging | |
| 2 | Discharging | |
| 3 | Unknown 3 | reserved, never observed |
| 4 | Unknown 4 | reserved, never observed |
| 5 | Off-Grid Standby | island mode, battery idle |
| 6 | Off-Grid Charging | island mode, solar charging |
| 7 | Off-Grid Discharging | island mode, battery powering home |
| 8 | Debug Mode | FranklinWH remote support session |
| 9 | VPP mode | defined in the table, but see §4 — the local 1301 does not emit it |
Cloud-side distribution over 60 days / 12,962 samples (docs/cli-energy.md in
franklinwh-cloud): 0 no flow 100%, 1 charging 95%, 2 discharging 98%. No sighting
of 3, 4 or 9 in that sample.
3. Operating mode (work mode) — and the three numbering schemes¶
Three systems number the same three modes differently. This is a standing source of error; always say which scheme a number belongs to.
| Mode | Cloud workMode |
Modbus oldIndex |
Local 1726 |
|---|---|---|---|
| Time-of-Use | 1 | 3 | by scheduling_type |
| Self-Consumption | 2 | 2 | by scheduling_type |
| Emergency Backup | 3 | 1 | by scheduling_type |
Cloud ↔ Modbus is not identity: CLOUD_TO_MODBUS_MODE maps 1→3, 2→2, 3→1. The local
library carries both in OPERATING_MODES
({'Emergency Backup': {'modbus': 1, 'cloud': 3}, 'Self-Consumption': {'modbus': 2,
'cloud': 2}, 'Time-of-Use': {'modbus': 3, 'cloud': 1}}).
Naming: the local broker labels TOU with a site-specific tariff name (e.g.
"Ausgrid EA11 TOU"). catalog.mode_label() maps scheduling_type to the generic
"Time-of-Use" for display parity with the app. Note the cloud spells it
"Time-Of-Use" (capital O) and the local library "Time-of-Use" — do not compare these
strings across channels.
4. VPP mode — measured, 2026-10-12¶
A Modbus force is VPP mode. Per the vendor reference
(david2069.github.io/franklinwh-modbus/VPP_MODE_REFERENCE/): "When any remote
client takes direct control of the aGate — whether via Modbus TCP or the FranklinWH
Cloud API — the mobile app displays the operating mode as VPP Mode."
Live readings, with a Modbus force discharge active and the mobile app showing VPP Mode:
| Field | Not forced | During VPP |
|---|---|---|
1301 run_status |
2 | 2 — unchanged |
1301 / 1707 name |
Self-Consumption |
"" (empty) |
1707 ibgRunStatus |
85232 | 9 |
Modbus M704 wset_enabled |
false | true |
So, on the local channel:
which is the cloud's own rule (tou_mode == 9, name empty) applied to the local
equivalent field. No Modbus is required to detect VPP mode.
Why 1301 looked like a dead end¶
run_status reports the battery's direction and keeps doing so under VPP — the battery
is still discharging, so 2 is correct, not a bug. The code in
cloud_compat.py that maps a local run_status == 9 to "VPP mode" is therefore
unreachable in practice: the field it reads never carries 9. It is harmless but
misleading, and should be re-pointed at ibgRunStatus.
Not yet established¶
- What
ibgRunStatusis when not in VPP. One observation:85232. The catalogue annotates the field "(programme id)", so it is probably a programme identifier rather than a small enum — but a single sample is not a mapping. Do not writeibgRunStatus != 9 → no programme; test for== 9only. - Whether a cloud-originated VPP (a utility/aggregator dispatch) also sets
ibgRunStatusto 9. Only a Modbus-originated force has been observed. The vendor doc says the app shows VPP Mode for both, which makes it likely, but likely is not measured. - Whether
nameis empty for every VPP source, or only this one.
4a. A US-centric platform, and an international one coming¶
Worth stating because it decides how to shape code now, not later.
The current aGate is US electrical-standards centric. Its native service layout is
split-phase, which is why the firmware reports the service as two halves even on an AU
single-phase 240 V site, and why gridVol1 + gridVol2 sum to the service voltage
here rather than describing two real conductors.
FranklinWH have stated an international platform is coming, with native single-phase and three-phase support in addition to split/two-phase — required for the European market, Germany in particular.
What that means for this code:
- Never assume two. Per-line voltages are exposed as a list
(
line_voltages_v), not a pair.half_voltage_v/half_voltage_2_v/leg_voltage_vare kept for existing callers but bake in "exactly two" and will be wrong on three-phase hardware. Prefer the list. - The service voltage stays the primary figure.
gridLineVolis one reading of the thing itself, and it is what grid-compliance curves are quoted against. It does not change shape when the topology does — summing N halves would. ac_typemust stay a real enum, not a boolean.single | split | threealready exists on the meter model; nothing should collapse it to "is it three-phase or not".- Phase-dependent labelling belongs behind
ac_type, which is whyL1/L2and "legs" are conditional rather than removed — on genuinely split-phase hardware they are correct, and on three-phase hardware the correct labels are different again. M701 ACTypegets more valuable, not less. It already distinguishes0 SINGLE_PHASE / 1 SPLIT_PHASE / 2 THREE_PHASE, which is exactly the axis the new platform moves along.
What NOT to do: build speculative three-phase handling now. Nobody has one, the
field semantics are unconfirmed (RESEARCH-GRID-PHASE-ENUM), and guessing is how the
"legs" defect happened. The goal here is only to avoid baking in assumptions that would
have to be unpicked — a list instead of a pair, an enum instead of a boolean.
5. Rules of thumb¶
- To show the user what the battery is doing:
RUN_STATUS[1301.run_status]. - To detect VPP:
1707.ibgRunStatus == 9. Neverrun_status == 9. - To show the dominant mode label, follow the cloud's
effective_modepriority: programme name if non-empty →"VPP mode"if the programme id is 9 and the name is empty → the work-mode label. vpp_activein this codebase is not VPP — it is the Modbus M704wset_enabledforce bit, set by any controller including our own schedules. It is a force signal. The UI labels it "Force dispatch" for that reason.- Never compare mode strings across channels (§3, the
Time-Of-Use/Time-of-Usedifference).
6. Sources¶
franklinwh_cloud/const/modes.py—RUN_STATUS,OPERATING_MODES,CLOUD_TO_MODBUS_MODEfranklinwh_direct_connect_api—catalog.run_status_desc(),OPERATING_MODES,WORK_MODE_NAMES,mode_label()- API Cookbook → Operating Mode & Run Status — Field Guide (david2069.github.io/franklinwh-cloud)
- VPP Mode Reference (david2069.github.io/franklinwh-modbus)
- Live reads on 192.168.0.110, 2026-10-12, recorded in §4