Skip to content

Add get_mode_settings(), set_mode_reserve(); fix get_mode()Add tou reserve methods#37

Open
npdsomerhayes wants to merge 3 commits into
richo:mainfrom
npdsomerhayes:add-tou-reserve-methods
Open

Add get_mode_settings(), set_mode_reserve(); fix get_mode()Add tou reserve methods#37
npdsomerhayes wants to merge 3 commits into
richo:mainfrom
npdsomerhayes:add-tou-reserve-methods

Conversation

@npdsomerhayes

Copy link
Copy Markdown

Summary

Fixes the long-standing broken get_mode() and adds two new methods built
on a previously undocumented API endpoint discovered via MITM interception
of the FranklinWH mobile app.

get_mode() had a TODO comment acknowledging it was wrong:

  • runingMode from _switch_status() is not a stable mode identifier —
    it appears to be a rolling/schedule ID that changes between polls even
    when the mode hasn't changed
  • touMinSoc / selfMinSoc / backupMaxSoc do not reflect the
    user-visible battery reserve percentage
  • The result was that get_mode() raised KeyError

New endpoint: getGatewayTouListV2

POST /hes-gateway/terminal/tou/getGatewayTouListV2?showType=1

Returns all three operating modes with their reserve SOC percentages and
identifies the currently active mode via currendId (sic — API typo):

{
  "result": {
    "currendId": 83132,
    "list": [
      {"id": 83132, "workMode": 1, "name": "Free power",        "soc": 10.0, "editSocFlag": true},
      {"id": 79680, "workMode": 2, "name": "Self-Consumption",  "soc": 10.0, "editSocFlag": true},
      {"id": 77244, "workMode": 3, "name": "Emergency Backup",  "soc": 100.0,"editSocFlag": false}
    ]
  }
}

workMode 1/2/3 uses the same encoding as Mode.workMode. The id values
are installation-specific and differ from the hardcoded values in the Mode
factory methods (9322/9323/9324), which are correct for updateTouMode and
unrelated to these schedule-entry IDs.

New endpoint: updateSocV2

POST /hes-gateway/terminal/tou/updateSocV2?workMode=2&electricityType=1&soc=15

Updates the reserve SOC for a single mode without switching to that mode.
electricityType=1 is always required (purpose unknown, discovered via MITM).
Emergency Backup always has editSocFlag: false; the server rejects writes.

Changes

New: get_mode_settings() -> ModeSettings

Returns a ModeSettings dataclass (consistent with the existing
ExportSettings pattern) containing all mode configurations and the active
mode. Convenience properties: .current_work_mode and .reserves.

New: set_mode_reserve(work_mode, soc)

Updates one mode's reserve without switching modes. Validates inputs before
the API call and raises InvalidDataException on API failure.

Fixed: get_mode()

Rewritten to use get_mode_settings(). Preserves the existing
(mode_name, soc) return signature. Now raises InvalidDataException
(consistent with the rest of the library) instead of KeyError.

New: WORK_MODE_MAP, ModeInfo, ModeSettings exported from __init__

Tested

bin/test-tou-reserve.py (included) exercises all three methods against a
real gateway. Confirmed working.

── get_mode_settings() ─────────────────────────────────────────
  current_mode_id  : 83132
  current_work_mode: 1 (time_of_use)
  reserves         : {1: 10.0, 2: 10.0, 3: 100.0}
  modes:
    workMode=1  id= 83132  soc= 10.0%  editable   name='Free power' (user-defined tariff label) ← active
    workMode=2  id= 79680  soc= 10.0%  editable   name='Self-Consumption'
    workMode=3  id= 77244  soc=100.0%  read-only  name='Emergency Backup'

Note: name is the user's custom tariff profile label as set in the FranklinWH app — it is not a fixed mode identifier. The example above shows "Free power" which is the name of this user's electricity plan. Use workMode (1/2/3) to identify the mode type reliably.

get_mode()
  mode_name: 'time_of_use'
  soc      : 10.0%

── set_mode_reserve(work_mode=1, soc=10) [time_of_use, no-op] 
 Success — value written back unchanged
 Confirmed reserve still 10.0%

── All tests passed ────────────────────────────────────────────

npdsomerhayes and others added 3 commits May 24, 2026 15:29
get_mode() had a known-broken TODO: it used runingMode from _switch_status()
which is an unreliable rolling ID, not a stable mode identifier. The
touMinSoc/selfMinSoc/backupMaxSoc fields it read are also incorrect — they
do not reflect the user-visible battery reserve percentages.

This commit fixes all of that by introducing two new methods built on a
newly discovered API endpoint (getGatewayTouListV2), found via MITM
interception of the FranklinWH mobile app.

New: get_mode_settings() -> ModeSettings
  POST /hes-gateway/terminal/tou/getGatewayTouListV2
  Returns a ModeSettings dataclass listing all three operating modes with
  their installation-specific ids, names, reserve SOC percentages, and
  which mode is currently active (via current_mode_id / current_work_mode).
  ModeSettings.reserves provides a convenient workMode→soc dict.

New: set_mode_reserve(work_mode, soc)
  POST /hes-gateway/terminal/tou/updateSocV2
  Updates the reserve SOC for a single mode WITHOUT switching to that mode.
  Validates work_mode against WORK_MODE_MAP and soc against 0-100 before
  making the API call.

Fixed: get_mode() rewritten to use get_mode_settings()
  Preserves the existing (mode_name, soc) return signature for backward
  compatibility. Now raises InvalidDataException (consistent with the rest
  of the library) instead of RuntimeError or KeyError.

New: WORK_MODE_MAP {1: TOU, 2: Self-Consumption, 3: Emergency Backup}
  Maps the workMode integers returned by getGatewayTouListV2 and
  getDeviceCompositeInfo to the existing MODE_* constants.

New: ModeInfo and ModeSettings dataclasses exported from __init__.py

Notes on the API:
- currendId in the API response is a known typo (not currENTId); preserved as-is
- electricityType=1 is a required parameter for updateSocV2 (purpose unknown)
- Emergency Backup always has editSocFlag=false; the server rejects SOC writes
- The mode ids returned by getGatewayTouListV2 (e.g. 83132, 79680, 77244) are
  installation-specific and differ from the hardcoded values in Mode factory
  methods (9322/9323/9324) used by updateTouMode — both sets are correct for
  their respective endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The getGatewayTouListV2 API returns soc as a float (e.g. 10.0, 100.0).
ModeInfo.soc was declared as int which would cause a type mismatch.
Updated to float and aligned the ModeSettings.reserves return annotation.
get_mode() docstring return type updated from tuple[str, int] to
tuple[str, float] accordingly.

Also adds bin/test-tou-reserve.py — a standalone script that exercises
get_mode_settings(), get_mode(), and set_mode_reserve() against a real
gateway without requiring a Home Assistant installation.  Tested
successfully against gateway 10060006A02F24170129.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fier

The 'name' field returned by getGatewayTouListV2 is whatever the user
named their electricity plan in the FranklinWH app — it is not a fixed
or predictable mode label. Only work_mode (1/2/3) reliably identifies
the mode type.

Updated ModeInfo docstring and test script output accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant