Skip to content

Workflow Diagrams

acdcnow edited this page Sep 21, 2026 · 2 revisions

Workflow Diagrams

Applies to integration 1.2.x · branch HA2026_09_dev · commit bd6c124

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.


1. Repository architecture (GitDiagram)

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 for HA2026_09_dev. The component topology is identical in 1.2.x; the only additions on HA2026_09_dev are the leaf nodes diagnostics.py and brand/, plus a new wertetyp field 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
Loading

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__.py owns the entry lifecycle, coordinator.py owns 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.

2. Initial setup workflow

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
Loading

3. Periodic update workflow

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
Loading

contracts and stats are the coordinator steps described in SDD §5.2.


4. Provider login workflows

4.1 Wiener Netze (OpenID Connect + PKCE)

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
Loading

4.2 Netz Niederösterreich (cookie session with legacy fallback)

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
Loading

5. Entity creation workflow

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
Loading

6. Effect of a wertetyp value on the sensor

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
Loading

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.


7. Diagrams for the archived 1.1.8 line

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.