Repository navigation
Workflow Diagrams
Applies to integration 1.2.x · branch
HA2026_09_dev· commitbd6c124
This page holds the visual documentation: the repository architecture map produced with GitDiagram plus the hand-maintained workflow and sequence diagrams. The design rationale behind them lives in the Architecture Design Document and the Software Design Document.
Generated with GitDiagram — 17 components, 23 connections.
Provenance note. GitDiagram indexes the repository default branch, i.e.
main(v1.1.8) — verified: its?branch=parameter is ignored and the page stays on "Loading diagram", so the map above cannot be regenerated forHA2026_09_dev. The component topology is identical in 1.2.x; the only additions onHA2026_09_devare the leaf nodesdiagnostics.pyandbrand/, plus a newwertetypfield in the reading contract. Those deltas are listed in ADD §8 and are drawn in the hand-maintained diagrams of this page.
flowchart TD
subgraph group_configuration["Configuration"]
node_config_flow["Config Flow<br/>[config_flow.py]"]
node_options_flow["Options Flow<br/>[config_flow.py]"]
end
subgraph group_orchestration["Polling Orchestration"]
node_integration_setup["Integration Setup<br/>[__init__.py]"]
node_coordinator["Data Coordinator<br/>[coordinator.py]"]
end
subgraph group_providers["Provider Clients"]
node_client_factory["Client Factory<br/>[client.py]"]
node_client_base["Client Interface<br/>[base.py]"]
node_wn_client["Wiener Netze Client<br/>[client_wn.py]"]
node_noe_client["Netz NÖ Client<br/>[client_noe.py]"]
end
subgraph group_presentation["Home Assistant Entities"]
node_sensor_platform["Sensor Platform<br/>[sensor.py]"]
node_reading_sensor["OBIS Reading Sensors<br/>[sensor.py]"]
node_statistics_sensor["Statistics Sensors<br/>[sensor.py]"]
node_diagnostic_sensor["Diagnostic Sensors<br/>[sensor.py]"]
node_meter_devices["Meter Devices<br/>[sensor.py]"]
end
node_ha_user(("Home Assistant User"))
node_ha_runtime(("Home Assistant Runtime"))
node_wn_portal{{"Wiener Netze APIs"}}
node_noe_portal{{"Netz NÖ APIs"}}
node_ha_user -->|"configures"| node_config_flow
node_config_flow -->|"creates client"| node_client_factory
node_config_flow -->|"logs in"| node_wn_client
node_config_flow -->|"logs in"| node_noe_client
node_config_flow -->|"creates entry"| node_integration_setup
node_ha_user -->|"sets interval"| node_options_flow
node_options_flow -->|"triggers reload"| node_integration_setup
node_ha_runtime -->|"loads integration"| node_integration_setup
node_integration_setup -->|"initializes"| node_coordinator
node_coordinator -->|"selects client"| node_client_factory
node_client_factory -->|"dispatches Wiener"| node_wn_client
node_client_factory -->|"dispatches NÖ"| node_noe_client
node_wn_client -->|"authenticates and queries"| node_wn_portal
node_noe_client -->|"authenticates and queries"| node_noe_portal
node_coordinator -->|"uses interface"| node_client_base
node_coordinator -->|"polls meters and data"| node_wn_client
node_coordinator -->|"polls meters and data"| node_noe_client
node_coordinator -->|"publishes meter data"| node_sensor_platform
node_sensor_platform -->|"creates readings"| node_reading_sensor
node_sensor_platform -->|"creates statistics"| node_statistics_sensor
node_sensor_platform -->|"creates diagnostics"| node_diagnostic_sensor
node_sensor_platform -->|"groups by meter"| node_meter_devices
node_ha_runtime -->|"registers sensors"| node_sensor_platform
classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_config_flow,node_options_flow,node_ha_user toneBlue
class node_integration_setup,node_coordinator toneAmber
class node_client_factory,node_client_base,node_wn_client,node_noe_client,node_wn_portal,node_noe_portal toneMint
class node_sensor_platform,node_reading_sensor,node_statistics_sensor,node_diagnostic_sensor,node_meter_devices toneRose
class node_ha_runtime toneIndigo
How to read it
- Configuration (blue) — the user-facing flows. They never touch a portal directly; they go through the factory.
-
Polling Orchestration (amber) —
__init__.pyowns the entry lifecycle,coordinator.pyowns everything that happens on a timer. - Provider Clients (green) — the factory dispatches to one adapter, the adapters are the only components that speak HTTP to a portal.
- Home Assistant Entities (rose) — one platform file that materialises readings, statistics, diagnostics and the per-meter devices.
sequenceDiagram
autonumber
actor U as User
participant CF as config_flow.py
participant CL as Provider client
participant P as Portal
participant INI as __init__.py
participant CO as coordinator.py
participant SE as sensor.py
U->>CF: Add integration, choose provider
CF->>U: Credentials form
U->>CF: username + password
CF->>CF: unique_id + duplicate check (before any I/O)
CF->>CL: get_client(provider, user, pass)
CF->>CL: login() in executor
CL->>P: POST login
P-->>CL: session / token
CF->>CL: zaehlpunkte() in executor
CL->>P: GET metering points
P-->>CL: contracts
alt login failed
CF-->>U: invalid_auth
else unreachable / non-JSON / no account
CF-->>U: cannot_connect
else no contracts
CF-->>U: no_contracts
else success
CF->>INI: async_create_entry(provider, username, password)
INI->>CO: construct coordinator
INI->>CO: async_config_entry_first_refresh()
CO->>CL: login, zaehlpunkte, consumptions, historical_data
INI->>INI: entry.runtime_data = coordinator
INI->>SE: async_forward_entry_setups
SE->>SE: build entities + devices from coordinator.data
end
flowchart TD
timer(["Scan interval elapsed<br/>default 360 min"]) --> logged{"logged in and<br/>session valid?"}
logged -->|"no"| login["login() in executor"]
logged -->|"yes"| contracts
login --> contracts["zaehlpunkte() in executor"]
contracts --> stats["consumptions() in executor"]
stats --> statsok{"provider has<br/>statistics?"}
statsok -->|"no (Netz NOE)"| loop
statsok -->|"yes"| st["normalise statistics"] --> loop
loop["for each metering point"] --> read["historical_data(zp) in executor"]
read --> readok{"ok?"}
readok -->|"no"| warn["LOGGER.warning<br/>keep going"] --> next
readok -->|"yes"| store["readings -> blob"] --> next
next{"more meters?"} -->|"yes"| read
next -->|"no"| match["_match_stats()<br/>assign statistics per meter"]
match --> publish["publish blob to sensors"]
publish --> state["entities write state"]
classDef fail fill:#ffe4e6,stroke:#e11d48,color:#881337
classDef okc fill:#dcfce7,stroke:#16a34a,color:#14532d
class warn fail
contractsandstatsare the coordinator steps described in SDD §5.2.
sequenceDiagram
autonumber
participant CL as WienerNetzeClient
participant KC as log.wien (Keycloak)
participant API as api.wstw.at
CL->>CL: generate code_verifier / code_challenge (S256)
CL->>KC: GET /auth?client_id=wn-smartmeter&code_challenge=...
KC-->>CL: login form (HTML)
CL->>CL: lxml XPath //form/@action
CL->>KC: POST username
KC-->>CL: password form (HTML)
CL->>KC: POST password (no redirects)
KC-->>CL: 302 Location with ?code=... in the fragment
CL->>KC: POST /token (authorization_code + code_verifier)
KC-->>CL: access_token, expires_in
CL->>API: GET app-config.json (Bearer)
API-->>CL: b2cApiKey, b2bApiKey
CL->>CL: _access_token_expiration = now + expires_in
flowchart TD
start(["login()"]) --> post["POST /orchestration/Authentication/Login<br/>{user, pwd}"]
post --> body{"body is JSON?"}
body -->|"no (HTML error page)"| cerr["SmartmeterConnectionError<br/>HTTP status + snippet"]
body -->|"yes"| st{"status code"}
st -->|"401 / 403"| lerr["SmartmeterLoginError"]
st -->|"other non-200"| cerr
st -->|"200"| succ{"success == false?"}
succ -->|"yes"| lerr
succ -->|"no"| ok["session established"]
ok --> zp(["zaehlpunkte()"])
zp --> modern["GetMeteringPointsByBusinesspartnerId?context=5"]
modern --> good{"list with items?"}
good -->|"yes"| norm["normalise"]
good -->|"no / error / empty"| legacy["GetAccountIdByBussinespartnerId?context=1<br/>+ GetMeteringPointByAccountId"]
legacy --> norm
norm --> empty{"any metering point?"}
empty -->|"no"| qerr["SmartmeterQueryError"]
empty -->|"yes"| out["[{'zaehlpunkte': [...]}]"]
classDef fail fill:#ffe4e6,stroke:#e11d48,color:#881337
classDef okc fill:#dcfce7,stroke:#16a34a,color:#14532d
class cerr,lerr,qerr fail
class ok,out okc
flowchart TD
data[("coordinator.data")] --> each["for each zaehlpunktnummer"]
each --> r{"readings"}
r -->|"one per OBIS code"| s1["AustriaSmartMeterSensor<br/>unit Wh, state class from wertetyp"]
each --> i{"info"}
i --> d1["Diagnostic: metering point id"]
i --> d2["Diagnostic: customer id"]
i --> d3["Diagnostic: market ready / active"]
i --> d4["Diagnostic: smart meter type / consumption visible"]
i --> d5["Diagnostic: facility type"]
i --> d6["Diagnostics: address + address fields"]
each --> st{"stats"}
st -->|"consumptionYesterday"| s2["AustriaSmartMeterStatistic"]
st -->|"consumptionDayBeforeYesterday"| s3["AustriaSmartMeterStatistic"]
s1 --> dev["shared DeviceInfo per metering point"]
d1 --> dev
d6 --> dev
s2 --> dev
dev --> reg["Home Assistant entity + device registry"]
classDef present fill:#ffe4e6,stroke:#e11d48,color:#881337
classDef diag fill:#dbeafe,stroke:#2563eb,color:#172554
class s1,s2,s3 present
class d1,d2,d3,d4,d5,d6 diag
flowchart LR
reading["reading from adapter"] --> known{"obisCode in OBIS_NAMES<br/>or unit in kWh/Wh?"}
known -->|"no"| plain["no device class,<br/>no unit"]
known -->|"yes"| cls["device_class = energy<br/>unit = Wh"]
cls --> wt{"wertetyp in<br/>DAY / CONSUMPTION /<br/>QUARTER_HOUR?"}
wt -->|"yes"| total["state_class = total<br/>(resets every period)"]
wt -->|"no"| inc["state_class = total_increasing<br/>(cumulative meter reading)"]
classDef period fill:#fef3c7,stroke:#d97706,color:#78350f
classDef cum fill:#dcfce7,stroke:#16a34a,color:#14532d
class total period
class inc cum
This is the mechanism that lets Wiener Netze (total_increasing, meter readings) and
Netz NÖ (total, per-day consumption) coexist in one platform file —
see ADD AD-5/AD-6.
The pre-2026.9 design had no local brand images and no diagnostics module, and
hass.data[DOMAIN][entry_id] played the role that entry.runtime_data plays now. The
corresponding documentation is preserved at
Archived 1.1.8 design documentation.
🟢 Current — 1.2.x
(branch HA2026_09_dev, HA 2026.9+)
🗄️ Archived — 1.1.8
(branch main)
🔗 Links