CLI Command Reference¶
Live output captured: 2026-03-22 19:25–19:28 AEDT
Device: aGate X (FW V10R01B04D00, SoC 77%, evening, no solar)
Connection: WiFi to 192.168.0.110:502
Quick Reference¶
CLI="python3 tools/franklinwh_cli.py -i YOUR_AGATE_IP"
$CLI --status # Compact status (default)
$CLI --status --verbose # Full verbose status with debug
$CLI --charge 3000 # Charge battery at 3000W
$CLI --discharge 2000 # Discharge battery at 2000W
$CLI --charge 0 # Force standby (battery idle, grid powers home)
$CLI --stop # Release control → aGate native mode resumes
$CLI --healthcheck # System health check
$CLI --monitor # Interactive TUI dashboard
1. Status — Idle (aGate Native Mode)¶
Battery discharging 600W to serve 565W home load — normal Self-Consumption behavior.
⚡ FranklinWH aGate | SoC: 77% | Self-Consumption | Reserve: 12%
──────────────────────────────────────────────────────
Solar: 0W idle Battery: ↑ DISCHARGING 600W
Home: 565W consuming Grid: ~0W (Grid Following)
──────────────────────────────────────────────────────
LocRemCtl: Local Available: 10.5/13.6 kWh
Derived: Local (aGate, Self-Consumption)
Key observations:
- Derived: Local — aGate is controlling the battery natively
- Grid: ~0W (Grid Following) — grid balanced, aGate managing load
- Battery discharges just enough to cover home load (self-consumption)
2. Charge at 3000W¶
Pre-command state assessment:
CURRENT SYSTEM STATE
Battery: SoC: 77.0% | Target: 100.0% | ETA: +36min
Activity: DISCHARGING (600W, aGate native)
Home Load: 558W
Grid: -2W (balanced)
WSetEna: 0
OnGridMode: Self-Consumption
Result: SUCCESS - Command Sent: 3000.0W (-60.0% of 5000W)
✅ Command will PERSIST until you run --stop
Status during charge (30s later):
⚡ FranklinWH aGate | SoC: 77% | Self-Consumption | Reserve: 12%
──────────────────────────────────────────────────────
Solar: 0W idle Battery: ↓ CHARGING 3000W
Home: 582W consuming Grid: ← 3587W importing
──────────────────────────────────────────────────────
LocRemCtl: Local Available: 10.5/13.6 kWh
Derived: Remote (Modbus WSetPct=-60.0%)
Key observations:
- Derived: Remote (Modbus WSetPct=-60.0%) — Modbus has taken control
- Grid imports 3587W to charge battery (3000W) + serve home (582W)
- LocRemCtl: Local — aGate SunSpec register still reports Local (LocRemCtl Paradox)
3. Stop (Release Control)¶
Resetting control state to idle...
Before reset: WSetEna=1, WSetPct=-600, WSet=0
✓ Reset successful: WSetEna=0, WSetPct=0
✓ Control released
aGate immediately resumes native Self-Consumption mode.
4. Discharge at 2000W¶
Result: SUCCESS - Command Sent: -2000.0W (40.0% of 5000W)
✅ Command will PERSIST until you run --stop
Status during discharge:
⚡ FranklinWH aGate | SoC: 77% | Self-Consumption | Reserve: 12%
──────────────────────────────────────────────────────
Solar: 0W idle Battery: ↑ DISCHARGING 2000W
Home: 540W consuming Grid: → 1469W exporting
──────────────────────────────────────────────────────
LocRemCtl: Local Available: 10.5/13.6 kWh
Derived: Remote (Modbus WSetPct=40.0%)
Key observations: - Battery discharges 2000W → 540W powers home, 1469W exported to grid - This is a Remote Control scenario — excess power feeds the grid
5. Standby (Force Battery Idle)¶
Forces WSetPct=0 — battery neither charges nor discharges. Grid must supply all home load.
Status during standby:
⚡ FranklinWH aGate | SoC: 77% | Self-Consumption | Reserve: 12%
──────────────────────────────────────────────────────
Solar: 0W idle Battery: IDLE
Home: 551W consuming Grid: ← 575W importing
──────────────────────────────────────────────────────
LocRemCtl: Local Available: 10.5/13.6 kWh
Derived: Remote (Modbus WSetPct=0.0%)
Key observations:
- Battery is IDLE — neither charging nor discharging
- Grid imports 575W to power home loads entirely
- Derived: Remote (Modbus WSetPct=0.0%) — Modbus still has control, commanding zero power
- This is useful for TOU strategies: hold battery during off-peak, discharge during peak
6. Healthcheck¶
HEALTH CHECK: HEALTHY
DEVICE:
Manufacturer: FranklinWH Technologies Co., Ltd
Model: aGate X
Serial: 10060006A02F********
Firmware: V10R01B04D00
Checks:
✓ connection: OK
✓ model_704: OK
✓ model_713: OK
✓ model_701: OK
✓ zombie_state: OK (not in zombie state)
soc: 77.0
✓ soc_safe: OK
grid_voltage: 241.8
grid_frequency: 50.0
ac_type: Single-Phase (230V Nominal)
grid_connection: Connected
grid_mode: Grid Following
inverter_state: Standby
ongrid_mode_name: Self-Consumption
self_reserve_pct: 12
ℹ extension_readonly: Ongrid Mode, Self Reserve, Tou Reserve
Recommendations:
• ℹ Extension registers read-only (requires installer unlock)
Command Lifecycle Summary¶
┌─────────┐ --charge N ┌──────────┐ --stop ┌─────────┐
│ IDLE │ ───────────────→ │ CHARGING │ ──────────→ │ IDLE │
│ (aGate) │ --discharge N │ │ │ (aGate) │
│ Local │ ───────────────→ │DISCHARGING│──────────→ │ Local │
│ │ --charge 0 │ │ │ │
│ │ ───────────────→ │ STANDBY │ ──────────→ │ │
└─────────┘ └──────────┘ └─────────┘
Derived: Derived: Derived:
Local (aGate, Remote (Modbus Local (aGate,
Self-Consumption) WSetPct=X%) Self-Consumption)
[!WARNING] Commands persist indefinitely. The aGate has no hardware timeout (PICS Issue 4). Always use
--stopto release control, or--revert Nfor automatic software timeout.[!CAUTION] WiFi can cause orphaned control. During this test,
--stoptimed out due to WiFi packet loss, leaving the aGate in Remote Control. The library detected this on next connect (Active VPP state detected: WSetEna=1). Use--stopagain or the library will auto-release withauto_release_orphan=True.
7. Max Charge / Max Discharge¶
$CLI --max-charge # Uses WChaRteMaxRtg from M702 nameplate
$CLI --max-discharge # Uses WDisChaRteMaxRtg from M702 nameplate
Max charge (5000W):
Using max charge rate: 5000W (from M702 nameplate)
Result: SUCCESS - Command Sent: 5000W (-100.0% of 5000W)
Battery: ↓ CHARGING 5000W Grid: ← 5603W importing
Derived: Remote (Modbus WSetPct=-100.0%)
Max discharge (5000W):
Using max discharge rate: 5000W (from M702 nameplate)
Result: SUCCESS - Command Sent: -5000W (100.0% of 5000W)
Battery: ↑ DISCHARGING 5000W Grid: → 4443W exporting
Derived: Remote (Modbus WSetPct=100.0%)
8. Standby (Explicit Flag)¶
Result: SUCCESS - Command Sent: 0W (0% of 5000W)
Battery: IDLE Grid: ← 546W importing
Derived: Remote (Modbus WSetPct=0.0%)
9. Diagnostic Commands¶
Check Alarms¶
System Alarms (Model 701): 0x00000000 ✓ None active
DC Port Alarms (Model 714): 0x00000000 ✓ None active
✓ No blocking alarms - operation permitted
Check SPAN Panel¶
aGate IP: 192.168.0.110
Scanning: 192.168.0.0/24 for SPAN panels (port 80)...
— No SPAN panels found on 192.168.0.0/24
→ Extension registers will be READ-ONLY
Test Extension Write¶
✗ Ongrid Mode (15507): READ-ONLY (Write rejected (needs unlock?))
✗ Self Reserve (15508): READ-ONLY (Write rejected (needs unlock?))
✗ Tou Reserve (15509): READ-ONLY (Write rejected (needs unlock?))
Summary: 0/3 registers writable
Dry Run¶
No command sent to aGate — simulation only.10. Software Auto-Revert¶
Software timeout set: 30s
Result: SUCCESS - Command Sent: 2000.0W (-40.0% of 5000W) [timeout: 30s]
⏱️ Auto-revert in 30s (software timer)
[!WARNING]
--revertrequires the CLI process to stay running. In fire-and-forget mode (no--loop), the CLI exits immediately after sending the command and the timer is lost. Use--revert N --loopfor reliable auto-revert.
Comprehensive Switch Test Matrix¶
Tested: 2026-03-22 19:45–19:49 AEDT | SoC: 76% | Device: aGate X (V10R01B04D00)
| Switch | Result | Notes |
|---|---|---|
--status |
✅ | Compact summary with LocRemCtl + Derived |
--status --detail |
✅ | Full verbose output with register sources, lifetime energy (M701/M714) |
--status -v |
✅ | Debug logging (SunSpec scan, model discovery) |
--status -q |
✅ | Quiet mode — suppresses debug, shows compact |
--healthcheck |
✅ | All checks passed: HEALTHY |
--check-alarms |
✅ | System + DC port alarms: none active |
--check-span |
✅ | Network scan: no SPAN panel found |
--test-extension-write |
✅ | 0/3 writable (expected without SPAN unlock) |
--charge 3000 |
✅ | 3000W charge, grid imports 3587W |
--discharge 2000 |
✅ | 2000W discharge, grid exports 1469W |
--stop |
✅ | WSetEna=0, control released |
--max-charge |
✅ | 5000W (from M702), grid imports 5603W |
--max-discharge |
✅ | 5000W (from M702), grid exports 4443W |
--standby |
✅ | Battery IDLE, grid imports 546W |
--dry-run |
✅ | Simulation only, no command sent |
--revert 20 |
✅ | Countdown timer, auto-released after 20s (DEF-004 FIXED) |
--target-soc-auto --loop (charge) |
✅ | Charge 75→76%, auto-stopped at 125s (DEF-005 FIXED) |
--target-soc-auto --loop (discharge) |
✅ | Discharge 75→74%, auto-stopped at ~150s (DEF-005 FIXED) |
--clear-alarms |
✅ | Alarm reset via raw Modbus TCP FC06 (DEF-006 FIXED) |
--show-schedule |
⚠️ | Schedule requires version field (validation works correctly) |
--validate-schedule |
⚠️ | Same validation requirement (correct behavior) |
Not Tested (Special Conditions Required)¶
| Switch | Reason |
|---|---|
--monitor |
Interactive TUI — test manually |
--mode * |
Virtual modes — not properly implemented |
--off-grid-permitted |
Requires off-grid condition |
--force |
Safety override — test manually with caution |
--reset-on-start |
Startup flag — tested implicitly via --stop |
--soc-ramp-window |
Requires virtual mode loop |
--duration |
Same mechanism as --revert |
--power |
Legacy alias for --charge/--discharge |
--theme |
TUI visual only |
Bugs Found and Fixed During Testing¶
DEF-004: --revert N Timer Lost Without --loop — ✅ FIXED¶
Root cause: CLI exited immediately after sending command, timer was garbage collected.
Fix: --revert N now runs a visible countdown in the CLI process, then calls reset_control_state() on expiry. Ctrl+C during countdown releases control early.
Re-test: --charge 2000 --revert 20 — command sent, 20s countdown displayed, auto-reverted.
DEF-005: --target-soc-auto Validation Uses Wrong Comparison — ✅ FIXED¶
Root cause: 1% tolerance in SoC comparison (current_soc <= target_soc + 1.0) meant ±1% targets always failed.
Fix: Removed tolerance — exact comparison now: current_soc <= target_soc (discharge) and current_soc >= target_soc (charge).
Re-test: Charge 75→76% auto-stopped at 125s. Discharge 75→74% auto-stopped at ~150s. Both showed 🎯 TARGET REACHED!.
DEF-006: --clear-alarms SunSpec API Error — ✅ FIXED¶
Root cause: Used self.dev.write_register() which doesn't exist on sunspec2 client.
Fix: Replaced with raw Modbus TCP FC06 single register write (same pattern as extension register probing).
Re-test: --clear-alarms → ✓ Alarm reset command sent.
Virtual Modes (Not Included)¶
[!IMPORTANT] Virtual modes (
--mode self_consumption,--mode peak_shave, etc.) are not covered in this reference.These modes use software-calculated power targets within Remote Control (
WSetEna=1). They are experimental and not properly implemented — the aGate's native modes (Self-Consumption, TOU, Emergency Backup) handle these scenarios better.Future: Virtual modes may be removed entirely, pending FranklinWH enabling: 1. LocRemCtl write access — allowing proper Local/Remote handoff per SunSpec spec 2. Extension register write access — enabling direct mode/reserve control via Modbus
Until then, the recommended approach is direct power commands (
--charge,--discharge,--stop) for Remote Control (mobile app: "VPP Mode"), and the FranklinWH mobile app for mode/reserve changes.
Raw test output: /tmp/cli_lifecycle_output.txt, /tmp/cli_switch_test.txt
Test scripts: /tmp/cli_lifecycle_test.sh, /tmp/cli_switch_test.sh
Last updated: 2026-03-22