diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e9dc20a..8896d6a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Next -- **Added** experimental ESPHome external component `ct002` to run the CT002/CT003 emulator directly on an ESP32. See `esphome.example.yaml`. +- **Added** experimental ESPHome external component `ct002` to run the CT002/CT003 emulator directly on an ESP32. See `esphome.example.yaml` and the per-meter grid-power sensor reference in `docs/esphome-powermeters.md` (which also lists meters not yet supported on the ESP, e.g. Enphase Envoy and the SMA Energy Meter). Per-source `config.ini` documentation moved out of the README into `docs/powermeters.md`. - **Added** Modbus UDP support via a `TRANSPORT = TCP|UDP` option in the `[MODBUS]` section (defaults to `TCP`). - **Fixed** Home Assistant powermeter timing out on startup with "Timeout waiting for Home Assistant state" when the configured sensor hasn't changed value since AstraMeter started. - **Fixed** `DEVICE_TYPE = shellypro3em_new` (and `shellypro3em_old`) generating an invalid `device-N` source id that the B2500 rejected, so the CT was never detected. These variants now default to a proper `shellypro3em-*` id like the combined `shellypro3em` type ([#389](https://github.com/tomquist/astrameter/issues/389)). diff --git a/README.md b/README.md index 9d1efebc..a960a72b 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ ct002: power_sensor_l1: grid_l1 ``` -Everything else is optional. See **[`esphome.example.yaml`](esphome.example.yaml)** for the complete, annotated config — three-phase sensors, the cross-phase filter pipeline (Hampel / smoothing / deadband / PID), balancer and saturation tuning, and the two optional sub-blocks below — with every knob shown at its default. +Everything else is optional. See **[`esphome.example.yaml`](esphome.example.yaml)** for the complete, annotated config — three-phase sensors, the cross-phase filter pipeline (Hampel / smoothing / deadband / PID), balancer and saturation tuning, and the two optional sub-blocks below — with every knob shown at its default. For the grid-power `sensor:` configuration per meter type (and which meters aren't supported on the ESP yet), see **[docs/esphome-powermeters.md](docs/esphome-powermeters.md)**. Two optional sub-blocks nest under the same `ct002:` key: @@ -191,7 +191,7 @@ For details on the CT002/CT003 UDP protocol used by Marstek storage systems, see ## Configuration -Configuration is managed via `config.ini`. Each powermeter type has specific settings. +Configuration is managed via `config.ini`. Each powermeter type has specific settings — see the per-source reference in **[docs/powermeters.md](docs/powermeters.md)** (and **[docs/esphome-powermeters.md](docs/esphome-powermeters.md)** for the ESPHome external component). ### General Configuration @@ -459,422 +459,23 @@ PID_OUTPUT_MAX = 800 PID_MODE = bias ``` -### Shelly +### Powermeter sources -#### Shelly 1PM -```ini -[SHELLY] -TYPE = 1PM -IP = 192.168.1.100 -USER = username -PASS = password -METER_INDEX = meter1 -``` - -#### Shelly Plus 1PM -```ini -[SHELLY] -TYPE = PLUS1PM -IP = 192.168.1.100 -USER = username -PASS = password -METER_INDEX = meter1 -``` - -#### Shelly EM -```ini -[SHELLY] -TYPE = EM -IP = 192.168.1.100 -USER = username -PASS = password -METER_INDEX = meter1 -``` - -#### Shelly 3EM -```ini -[SHELLY] -TYPE = 3EM -IP = 192.168.1.100 -USER = username -PASS = password -METER_INDEX = meter1 -``` - -#### Shelly 3EM Pro -```ini -[SHELLY] -TYPE = 3EMPro -IP = 192.168.1.100 -USER = username -PASS = password -METER_INDEX = meter1 -``` - -### Tasmota - -```ini -[TASMOTA] -IP = 192.168.1.101 -USER = tasmota_user -PASS = tasmota_pass -JSON_STATUS = StatusSNS -JSON_PAYLOAD_MQTT_PREFIX = SML -JSON_POWER_MQTT_LABEL = Power -JSON_POWER_INPUT_MQTT_LABEL = Power1 -JSON_POWER_OUTPUT_MQTT_LABEL = Power2 -JSON_POWER_CALCULATE = True -``` - -For 3-phase meters, use comma-separated labels: - -```ini -[TASMOTA] -IP = 192.168.1.101 -JSON_STATUS = StatusSNS -JSON_PAYLOAD_MQTT_PREFIX = eBZ -JSON_POWER_MQTT_LABEL = Power_L1,Power_L2,Power_L3 -``` - -For 3-phase with `JSON_POWER_CALCULATE`, provide matching comma-separated -input and output labels (counts must be equal): - -```ini -[TASMOTA] -IP = 192.168.1.101 -JSON_STATUS = StatusSNS -JSON_PAYLOAD_MQTT_PREFIX = SML -JSON_POWER_INPUT_MQTT_LABEL = Power_In_L1,Power_In_L2,Power_In_L3 -JSON_POWER_OUTPUT_MQTT_LABEL = Power_Out_L1,Power_Out_L2,Power_Out_L3 -JSON_POWER_CALCULATE = True -``` - -### Shrdzm - -```ini -[SHRDZM] -IP = 192.168.1.102 -USER = shrdzm_user -PASS = shrdzm_pass -``` - -### Emlog - -```ini -[EMLOG] -IP = 192.168.1.103 -METER_INDEX = 0 -JSON_POWER_CALCULATE = True -``` - -### IoBroker - -```ini -[IOBROKER] -IP = 192.168.1.104 -PORT = 8087 -CURRENT_POWER_ALIAS = Alias.0.power -POWER_CALCULATE = True -POWER_INPUT_ALIAS = Alias.0.power_in -POWER_OUTPUT_ALIAS = Alias.0.power_out -``` - -### HomeAssistant -```ini -[HOMEASSISTANT] -IP = 192.168.1.105 -PORT = 8123 -# Use HTTPS - if empty False is Fallback -HTTPS = ""|True|False -ACCESSTOKEN = YOUR_ACCESS_TOKEN -# The entity or entities (comma-separated for 3-phase) that provide current power -CURRENT_POWER_ENTITY = ""|sensor.current_power|sensor.phase1,sensor.phase2,sensor.phase3 -# If False or Empty the power is not calculated - if empty False is Fallback -POWER_CALCULATE = ""|True|False -# The entity ID or IDs (comma-separated for 3-phase) that provide power input -POWER_INPUT_ALIAS = ""|sensor.power_input|sensor.power_in_1,sensor.power_in_2,sensor.power_in_3 -# The entity ID or IDs (comma-separated for 3-phase) that provide power output -POWER_OUTPUT_ALIAS = ""|sensor.power_output|sensor.power_out_1,sensor.power_out_2,sensor.power_out_3 -# Is a Path Prefix needed? -API_PATH_PREFIX = ""|/core -# Per-powermeter throttling override (recommended: 2-3 seconds for HomeAssistant) -THROTTLE_INTERVAL = 2 -``` - -Example: Variant 1 with a single combined input & output sensor -```ini -[HOMEASSISTANT] -IP = 192.168.1.105 -PORT = 8123 -HTTPS = True -ACCESSTOKEN = YOUR_ACCESS_TOKEN -CURRENT_POWER_ENTITY = sensor.current_power -``` - -Example: Variant 2 with separate input & output sensors -```ini -[HOMEASSISTANT] -IP = 192.168.1.105 -PORT = 8123 -HTTPS = True -ACCESSTOKEN = YOUR_ACCESS_TOKEN -POWER_CALCULATE = True -POWER_INPUT_ALIAS = sensor.power_input -POWER_OUTPUT_ALIAS = sensor.power_output -``` - -Example: Variant 3 with three-phase power monitoring -```ini -[HOMEASSISTANT] -IP = 192.168.1.105 -PORT = 8123 -HTTPS = True -ACCESSTOKEN = YOUR_ACCESS_TOKEN -CURRENT_POWER_ENTITY = sensor.phase1,sensor.phase2,sensor.phase3 -``` - -Example: Variant 4 with three-phase power calculation -```ini -[HOMEASSISTANT] -IP = 192.168.1.105 -PORT = 8123 -HTTPS = True -ACCESSTOKEN = YOUR_ACCESS_TOKEN -POWER_CALCULATE = True -POWER_INPUT_ALIAS = sensor.power_in_1,sensor.power_in_2,sensor.power_in_3 -POWER_OUTPUT_ALIAS = sensor.power_out_1,sensor.power_out_2,sensor.power_out_3 -# Per-powermeter throttling override (recommended: 2-3 seconds for HomeAssistant) -# THROTTLE_INTERVAL = 2 -``` - -### VZLogger - -```ini -[VZLOGGER] -IP = 192.168.1.106 -PORT = 8080 -UUID = your-uuid -``` - -For 3-phase meters, provide comma-separated UUIDs (one per phase); phases are -fetched in parallel: - -```ini -[VZLOGGER] -IP = 192.168.1.106 -PORT = 8080 -UUID = uuid-l1, uuid-l2, uuid-l3 -``` - -### ESPHome - -```ini -[ESPHOME] -IP = 192.168.1.107 -PORT = 6052 -DOMAIN = your_domain -ID = your_id -``` - -### AMIS Reader - -```ini -[AMIS_READER] -IP = 192.168.1.108 -``` - -### Modbus TCP/UDP - -```ini -[MODBUS] -HOST = 192.168.1.100 -PORT = 502 -UNIT_ID = 1 -ADDRESS = 0 -COUNT = 1 -DATA_TYPE = UINT16 -BYTE_ORDER = BIG -WORD_ORDER = BIG -REGISTER_TYPE = HOLDING # or INPUT -TRANSPORT = TCP # or UDP -``` - -### MQTT - -```ini -[MQTT] -BROKER = broker.example.com -PORT = 1883 -TOPIC = home/powermeter -JSON_PATH = $.path.to.value (Optional for JSON payloads) -USERNAME = mqtt_user (Optional) -PASSWORD = mqtt_pass (Optional) -# Optional: connect over TLS (mqtts://) — default false -# TLS = false -# Per-powermeter throttling override -# THROTTLE_INTERVAL = 2 -``` - -Instead of `BROKER`/`PORT`/`USERNAME`/`PASSWORD`/`TLS`, you can provide a single `URI` of the form `mqtt[s]://[user[:pass]@]host[:port]` (use `mqtts://` for TLS; credentials and port are optional). When `URI` is set, the individual `BROKER`/`PORT`/`USERNAME`/`PASSWORD`/`TLS` fields are ignored. - -```ini -[MQTT] -URI = mqtts://user:pass@broker.example.com:8883 -TOPIC = home/powermeter -``` - -The `JSON_PATH` option is used to extract the power value from a JSON payload. The path must be a [valid JSONPath expression](https://goessner.net/articles/JsonPath/). -If the payload is a simple integer value, you can omit this option. - -Both `JSON_PATH` and `JSON_PATHS` are parsed with the [`jsonpath-ng` extended syntax](https://github.com/h2non/jsonpath-ng#extensions), so you can chain extensions like `` `split(...)` `` or `` `sub(/regex/, replacement)` `` to massage a payload value before it's converted to a float — for instance `$.state.`split( , 0, -1)`` or `$.state.`sub(/[^0-9.\-]+$/, )`` to strip a unit suffix like `"331.74 W"`. See the [JSON HTTP](#json-http) section below for more examples. - -#### Multi-phase MQTT - -For three-phase setups, there are two options: - -**Option 1: Multiple topics** — one topic per phase, each publishing a plain numeric value (or JSON with the same path): - -```ini -[MQTT] -BROKER = broker.example.com -TOPICS = home/power/l1, home/power/l2, home/power/l3 -``` - -**Option 2: Single topic with multiple JSON paths** — one topic publishing a JSON message containing all phases: - -```ini -[MQTT] -BROKER = broker.example.com -TOPIC = home/powermeter -JSON_PATHS = $.phases[0].power, $.phases[1].power, $.phases[2].power -``` - -`TOPICS` takes precedence over `TOPIC`, and `JSON_PATHS` takes precedence over `JSON_PATH`. You can combine `TOPICS` with `JSON_PATH` (same path applied to each topic) or with `JSON_PATHS` (one path per topic, counts must match). - -### JSON HTTP - -```ini -[JSON_HTTP] -URL = http://example.com/api -# Comma separated JSON paths - single path for 1-phase or three for 3-phase -JSON_PATHS = $.power -USERNAME = user (Optional) -PASSWORD = pass (Optional) -# Additional headers separated by ';' using 'Key: Value' -HEADERS = Authorization: Bearer token -``` - -`JSON_PATHS` is parsed with the [`jsonpath-ng` extended syntax](https://github.com/h2non/jsonpath-ng#extensions), so you can chain extensions like `` `split(...)` `` or `` `sub(/regex/, replacement)` `` to massage the value before it's converted to a float. For example, an openHAB `Number:Power` item returns `"331.74 W"` — strip the unit with either of: - -```ini -JSON_PATHS = $.state.`split( , 0, -1)` -JSON_PATHS = $.state.`sub(/[^0-9.\-]+$/, )` -``` - -### TQ Energy Manager - -```ini -[TQ_EM] -IP = 192.168.1.100 -#PASSWORD = pass -#TIMEOUT = 5.0 (Optional) -``` - -### HomeWizard - -Reads a [HomeWizard](https://www.homewizard.com/) P1 dongle (or compatible device) over the local **WebSocket** API (`wss://`). Obtain a token once via `POST /api/user` while confirming on the device; see the [HomeWizard API docs](https://api-documentation.homewizard.com/docs/v2/). - -```ini -[HOMEWIZARD] -IP = 192.168.1.110 -TOKEN = YOUR_32_CHAR_HEX_TOKEN -SERIAL = your_device_serial -# Optional: disable TLS certificate verification on a trusted LAN if verification fails (default True) -# VERIFY_SSL = True -# THROTTLE_INTERVAL = 0 -``` - -### Enphase Envoy (IQ Gateway) - -Reads grid power from an [Enphase IQ Gateway / Envoy](https://enphase.com/installers/microinverters/iq-gateway) over the local HTTPS API (`/production.json?details=1`). The reading comes from the `net-consumption` measurement (positive = grid import, negative = export). Per-phase readings are reported automatically when the gateway exposes them; otherwise the aggregate single-phase value is used. Requires consumption CTs installed on the Envoy. - -```ini -[ENVOY] -HOST = 192.168.1.120 -# Option A: pre-obtained long-lived JWT (recommended) -TOKEN = eyJ... -# Option B: let AstraMeter fetch and refresh tokens via the Enphase Enlighten cloud -# USERNAME = you@example.com -# PASSWORD = your-enphase-password -# SERIAL = 123456789012 -# Envoy ships a self-signed certificate; verification is disabled by default. -# VERIFY_SSL = False -``` - -**Token acquisition.** Generate a long-lived (~1 year) static token at . Alternatively, configure `USERNAME`/`PASSWORD`/`SERIAL` and AstraMeter will fetch a token on first use and refresh it automatically when the Envoy returns 401. - -**TLS.** `VERIFY_SSL` defaults to `False` because Enphase does not publish a CA bundle for the IQ Gateway's self-signed certificate. This option **only affects the local Envoy connection** — Enphase Enlighten cloud requests (login and token endpoints) always verify TLS using the system trust store, regardless of this setting. - -**MFA.** The auto-fetch flow does not support Enlighten accounts with multi-factor authentication enabled. Those users must supply a static `TOKEN`. - -**CT direction.** If your readings have the wrong sign (export shows as import or vice versa), one or more CTs are mounted backwards. Flip them in software with the global `POWER_MULTIPLIER = -1` (or per-phase, e.g. `POWER_MULTIPLIER = 1, -1, 1`). - -### SMA Energy Meter - -Reads an [SMA Energy Meter](https://www.sma.de/) (EM 1.0/2.0) or Sunny Home Manager via the **Speedwire** multicast protocol (UDP). The listener joins the default multicast group and reports per-phase active power (L1, L2, L3). Use `SERIAL_NUMBER = 0` to auto-detect the first meter seen on the network, or set the device serial to pin a specific unit. Like other UDP-based features, this requires the host to receive multicast traffic (use Docker host networking or equivalent). - -```ini -[SMA_ENERGY_METER] -MULTICAST_GROUP = 239.12.255.254 -PORT = 9522 -SERIAL_NUMBER = 0 -# INTERFACE = 192.168.1.10 -# THROTTLE_INTERVAL = 0 -``` - -### Modbus - -```ini -[MODBUS] -HOST = -PORT = -UNIT_ID = -ADDRESS = -COUNT = -DATA_TYPE = UINT16 -BYTE_ORDER = BIG -WORD_ORDER = BIG -REGISTER_TYPE = HOLDING -TRANSPORT = TCP -``` - -`TRANSPORT` selects the Modbus transport: `TCP` (default) or `UDP`. - -### Script - -You can also use a custom script to get the power values. The script should output at most 3 integer values, separated by a line break. -```ini -[SCRIPT] -COMMAND = /path/to/your/script.sh -``` - -### SML - -```ini -[SML] -SERIAL = /dev/ttyUSB0 -# Optional: override default OBIS hex registers (12 hex digits each; defaults match common German eHZ meters) -#OBIS_POWER_CURRENT = 0100100700ff -#OBIS_POWER_L1 = 0100240700ff -#OBIS_POWER_L2 = 0100380700ff -#OBIS_POWER_L3 = 01004c0700ff -``` - -Read from a powermeter that is connected via USB and that transmits SML (Smart Message Language) data via an IR head. **`SERIAL` is required**: local device path to the serial interface (e.g. `/dev/ttyUSB0` on Linux). +The per-source configuration for every supported meter lives in dedicated +reference docs — find your meter and copy the matching section: -**Multi-phase:** If the meter exposes per-phase instantaneous active power for L1–L3 (`Summenwirkleistung` / default OBIS above), those three values are used automatically. Otherwise the aggregate instantaneous power register (`aktuelle Wirkleistung` / `OBIS_POWER_CURRENT`) is used as a single reading. When both are present in the same SML list, per-phase values take precedence. +- **[docs/powermeters.md](docs/powermeters.md)** — `config.ini` sections for the + Python add-on / Docker / direct install (Shelly, Tasmota, Shrdzm, Emlog, + IoBroker, HomeAssistant, VZLogger, ESPHome, AMIS Reader, Modbus, MQTT, JSON + HTTP, TQ Energy Manager, HomeWizard, Enphase Envoy, SMA Energy Meter, Script, + SML). +- **[docs/esphome-powermeters.md](docs/esphome-powermeters.md)** — the equivalent + grid-power `sensor:` configuration when running the + [ESPHome external component](#esphome-external-component-run-on-an-esp32) on an + ESP32, including which meters aren't supported on the ESP yet. -**OBIS overrides:** Only needed if your meter uses different register addresses; values must be exactly 12 hexadecimal characters (lowercase or uppercase). +The value transformation, PID controller, and per-powermeter options +(throttling, smoothing, deadband, Hampel) documented above apply to every source. ### Multiple Powermeters diff --git a/docs/esphome-powermeters.md b/docs/esphome-powermeters.md new file mode 100644 index 00000000..407850f3 --- /dev/null +++ b/docs/esphome-powermeters.md @@ -0,0 +1,820 @@ +# Powermeter Configuration Reference (ESPHome external component) + +When you run AstraMeter as the [ESPHome external +component](../README.md#esphome-external-component-run-on-an-esp32) on an ESP32, +the `ct002:` block does **not** talk to your meter directly. Instead it consumes +**any ESPHome `sensor`** that reports grid power in watts. So "configuring a +powermeter" here means: *give ESPHome a sensor that reads your meter, then point +`ct002:` at it.* + +## How a reading reaches the emulator + +There is no "powermeter" object in the ESPHome component — the integration is a +plain **id reference**. Every example below publishes a watts value into a sensor +whose `id` is `grid_l1` (and `grid_l2` / `grid_l3` for the other phases). The +`ct002:` block names those ids in its `power_sensor_l*` keys. That id match is +the entire wiring; whenever the sensor publishes a new value, the emulator picks +it up on its next Marstek CT002 poll — you never call `ct002:` directly. + +**Each section below is a complete, copy-pasteable config** for one meter — from +`external_components:` through `ct002:`. To keep them focused, every example +**omits the `wifi:`, `api:`, `ota:`, and board (`esp32:`) blocks** — add those +for your hardware (see [`esphome.example.yaml`](../esphome.example.yaml) for a +full board config). What's shown is complete for the meter → emulator wiring. + +Per-phase calibration/throttling (`offset:`, `multiply:`, `throttle:`) goes in +`filters:` **on the sensor**, not in `ct002:` — see the +[main README note](../README.md#esphome-external-component-run-on-an-esp32). +Running the Python add-on instead? See [powermeters.md](powermeters.md). + +> The polling/lambda examples are **illustrative**. ESPHome's `http_request`, +> `json`, and lambda APIs differ slightly between releases — check the linked +> component docs for the exact syntax on your version. + +## Support legend + +| Tier | Meaning | +|------|---------| +| 🟢 **Native** | A built-in ESPHome component reads this exact source. | +| 🔵 **Generic** | No device-specific component, but ESPHome's built-in `http_request`+`json` or `mqtt_subscribe` reads it with a small lambda. | +| 🟠 **Alternate** | The exact API the Python class uses has no ESPHome port, but the *same device* also speaks a protocol ESPHome reads natively (Modbus/MQTT/P1). | +| 🔴 **Not yet available** | No practical way to read this on an ESP32 today. Documented so we know what to build. | + +## Contents + +- [Shelly](#shelly) — 🔵 Generic (or 🟢 native if you flash the Shelly) +- [Tasmota](#tasmota) — 🔵 Generic (or 🟢 native if you flash the device) +- [Shrdzm](#shrdzm) — 🔵 Generic +- [Emlog](#emlog) — 🔵 Generic +- [IoBroker](#iobroker) — 🔵 Generic +- [HomeAssistant](#homeassistant) — 🟢 Native +- [VZLogger](#vzlogger) — 🔵 Generic (or 🟢 native by reading the meter directly) +- [ESPHome](#esphome) — 🟢 Native (it's already ESPHome) +- [AMIS Reader](#amis-reader) — 🔵 Generic +- [Modbus](#modbus) — 🟢 Native (RS485 serial; see TCP caveat) +- [MQTT](#mqtt) — 🟢 Native +- [JSON HTTP](#json-http) — 🟢 Native (generic `http_request`) +- [SML](#sml) — 🟢 Native +- [TQ Energy Manager](#tq-energy-manager) — 🟠 Alternate (Modbus/MQTT) +- [HomeWizard](#homewizard) — 🟠 Alternate (local v1 HTTP, or native P1) +- [Enphase Envoy (IQ Gateway)](#enphase-envoy-iq-gateway) — 🔴 Not yet available +- [SMA Energy Meter](#sma-energy-meter) — 🔴 Not yet available + +> **Script** (the Python `[SCRIPT]` source) has no ESPHome equivalent by design — +> an ESP32 can't run a host shell command — so it is intentionally omitted here. +> +> **The 🔵 generic HTTP sections** all share the same shape: a `template` sensor +> named `grid_l1` holds the value, an `interval:` polls the URL, and a lambda +> parses the JSON body with the built-in +> [`json::parse_json`](https://esphome.io/components/json/) helper and publishes +> into `grid_l1`. Only the URL and the lambda field differ between them. The +> [`http_request`](https://esphome.io/components/http_request/) and +> [`json`](https://esphome.io/components/json/) components are built in — no +> extra external component needed. + +## Shelly + +**Tier: 🔵 Generic** (poll over the network) — or **🟢 Native** if the Shelly is +ESP32-based and you flash ESPHome onto it. + +Most Shelly devices are reachable over HTTP from the ct002 ESP32. Gen2/Gen3/Pro +expose an RPC API; Gen1 a REST `/status`. Single-phase (Shelly Plus 1PM / Pro +family, RPC `apower`): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.100/rpc/Switch.GetStatus?id=0 + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["apower"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +Three-phase (Shelly 3EM Pro, RPC `EM.GetStatus`) — three template sensors, one +poll, all three phases on `ct002:`: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + - platform: template + id: grid_l2 + unit_of_measurement: W + device_class: power + - platform: template + id: grid_l3 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.100/rpc/EM.GetStatus?id=0 + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["a_act_power"]); + id(grid_l2).publish_state(root["b_act_power"]); + id(grid_l3).publish_state(root["c_act_power"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 + power_sensor_l2: grid_l2 + power_sensor_l3: grid_l3 +``` + +(This splits `EM.GetStatus` into per-phase readings; the Python `[SHELLY]` +`3EMPro` source instead reads the aggregate `total_act_power` from the same +response. Both are valid — use whichever your setup needs.) + +Gen1 (Shelly 1PM/EM/3EM) expose `http:///status` with a `meters[]` / +`emeters[]` array — point the lambda at `root["emeters"][0]["power"]` etc. + +**Native alternative:** Shelly hardware is ESP-based, so you can flash ESPHome +directly onto it and read its onboard energy chip (BL0942 / ADE7953 / ADE7880) +as a native sensor. If that Shelly is ESP32-based (e.g. Shelly Pro 3EM) it can +even run the `ct002:` component itself. See +[devices.esphome.io](https://devices.esphome.io/) for per-model configs. + +## Tasmota + +**Tier: 🔵 Generic** — or **🟢 Native** if you flash ESPHome onto the device. + +Tasmota answers `GET /cm?cmnd=status%2010` with sensor JSON nested under +`StatusSNS`. Adapt the prefix/label to your meter (here `SML`/`Power`): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.101/cm?cmnd=status%2010 + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["StatusSNS"]["SML"]["Power"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +**Native alternative:** the device is ESP-based — flashing ESPHome lets you read +the underlying energy-monitor chip (CSE7766 / HLW8012 / BL0942 / ADE7953) +directly as a native sensor. + +## Shrdzm + +**Tier: 🔵 Generic.** The SHRDZM module serves `GET /getLastData?user=…&password=…` +returning OBIS keys; grid power is `1.7.0` (import) minus `2.7.0` (export): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.102/getLastData?user=USER&password=PASS + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + float in = root["1.7.0"]; + float out = root["2.7.0"]; + id(grid_l1).publish_state(in - out); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +## Emlog + +**Tier: 🔵 Generic.** EmLog serves +`GET /pages/getinformation.php?heute&meterindex=` with `Leistung170` +(import) and `Leistung270` (export): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.103/pages/getinformation.php?heute&meterindex=0 + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + float in = root["Leistung170"]; + float out = root["Leistung270"]; + id(grid_l1).publish_state(in - out); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +## IoBroker + +**Tier: 🔵 Generic.** With ioBroker's simpleAPI adapter, `GET /getBulk/` +returns a JSON array (`GET /getPlainValue/` returns a bare number): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.104:8087/getBulk/Alias.0.power + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonArray arr) -> bool { + id(grid_l1).publish_state(arr[0]["val"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +**Alternative:** if you run ioBroker's MQTT adapter, publish the state to a topic +and read it with the native [`mqtt_subscribe`](#mqtt) sensor instead (simpler and +push-based). + +## HomeAssistant + +**Tier: 🟢 Native.** Use the built-in +[`homeassistant`](https://esphome.io/components/sensor/homeassistant/) sensor +platform — the ESP subscribes to a HA entity over the native API (so the `api:` +block, normally part of the omitted boilerplate, is shown here because it's +required for this source): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +api: # native API link to Home Assistant is required for this source + +sensor: + - platform: homeassistant + id: grid_l1 + entity_id: sensor.grid_power + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +For three-phase, add `grid_l2` / `grid_l3` sensors pointing at the per-phase +entities and set `power_sensor_l2` / `power_sensor_l3`. This is the same data +path the Python `[HOMEASSISTANT]` source uses, but pushed to the ESP instead of +polled from a server. + +## VZLogger + +**Tier: 🔵 Generic** — or **🟢 Native** by reading the meter directly. + +vzlogger's HTTP interface serves `GET /` with the latest tuple at +`data[0].tuples[0][1]`: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.106:8080/your-uuid + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["data"][0]["tuples"][0][1]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +**Native alternative:** vzlogger itself just reads a physical meter (usually +SML or DLMS/D0 over an IR head). You can skip vzlogger entirely and read that +meter directly on the ESP with the native [`sml`](#sml) component (or +[`dsmr`](https://esphome.io/components/sensor/dsmr/) for P1/D0), removing the +middleware. + +## ESPHome + +**Tier: 🟢 Native.** The Python `[ESPHOME]` source polls another ESPHome +device's web-server REST API. On the ESP32 there's no bridge to build — if your +grid-power source is already an ESPHome device, either define that meter's sensor +in the **same** YAML as `ct002:` (any native chip / Modbus / pulse-counter sensor +with `id: grid_l1`), or import another ESPHome node's entity via Home Assistant. +The latter, complete: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +api: # required to import the other node's entity from Home Assistant + +sensor: + - platform: homeassistant # the other ESPHome node's entity, via HA + id: grid_l1 + entity_id: sensor.other_esphome_grid_power + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +You can also subscribe over [MQTT](#mqtt) if both nodes share a broker. + +## AMIS Reader + +**Tier: 🔵 Generic.** The AMIS reader serves `GET /rest` with a `saldo` field +(signed grid power): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.108/rest + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["saldo"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +## Modbus + +**Tier: 🟢 Native** — with one important caveat (see below). Use the built-in +[`modbus_controller`](https://esphome.io/components/sensor/modbus_controller/) +sensor over an RS485 transceiver wired to the ESP: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +uart: + id: mod_uart + tx_pin: GPIO17 + rx_pin: GPIO16 + baud_rate: 9600 + stop_bits: 1 + +modbus: + id: modbus1 + uart_id: mod_uart + +modbus_controller: + - id: meter + address: 1 # Modbus unit / slave id + modbus_id: modbus1 + update_interval: 1s + +sensor: + - platform: modbus_controller + modbus_controller_id: meter + id: grid_l1 + register_type: holding # or read (input) + address: 0 + value_type: U_WORD # S_DWORD / U_DWORD / FP32 / … to match your meter + unit_of_measurement: W + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +> **Modbus-TCP caveat.** ESPHome's `modbus_controller` is a **serial (RS485)** +> master — it does not open a raw Modbus-TCP socket the way the Python +> `[MODBUS]` source does with `TRANSPORT = TCP`. To read a network Modbus-TCP +> meter from the ESP you need either a wired RS485 connection to the meter, or a +> Modbus-TCP↔RTU gateway. Map `DATA_TYPE`/`BYTE_ORDER`/`WORD_ORDER` from your +> Python config onto `value_type` and the register's byte/word order. + +## MQTT + +**Tier: 🟢 Native.** For a plain numeric payload, use +[`mqtt_subscribe`](https://esphome.io/components/sensor/mqtt_subscribe/): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +mqtt: + broker: 192.168.1.10 + port: 1883 + +sensor: + - platform: mqtt_subscribe + id: grid_l1 + topic: home/powermeter + unit_of_measurement: W + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +For a **JSON** payload (the Python `JSON_PATH` case), `mqtt_subscribe` only +handles bare floats, so extract the field with `on_json_message` into a template +sensor: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +mqtt: + broker: 192.168.1.10 + port: 1883 + on_json_message: + topic: home/powermeter + then: + - sensor.template.publish: + id: grid_l1 + state: !lambda 'return x["path"]["to"]["value"];' + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +For three-phase, subscribe to three topics (or read three fields) into +`grid_l1/2/3` and set `power_sensor_l2` / `power_sensor_l3`. + +## JSON HTTP + +**Tier: 🟢 Native** (generic `http_request`). Point the URL at your endpoint and +set the lambda to your JSON field. Headers and basic auth are supported on the +`http_request.get` action: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://example.com/api + headers: + Authorization: Bearer token + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["power"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +## SML + +**Tier: 🟢 Native.** Smart meters that emit SML over an IR head map directly to +ESPHome's built-in [`sml`](https://esphome.io/components/sml/) component (the +ESP-side equivalent of the Python `[SML]` source). Wire a photo-transistor to a +UART RX pin, then select the OBIS register: + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +uart: + id: uart_bus + rx_pin: GPIO16 + baud_rate: 9600 + data_bits: 8 + parity: NONE + stop_bits: 1 + +sml: + id: mysml + uart_id: uart_bus + +sensor: + - platform: sml + id: grid_l1 + sml_id: mysml + obis_code: "1-0:16.7.0" # aggregate active power (Python OBIS_POWER_CURRENT) + unit_of_measurement: W + # per-phase instead: 1-0:36.7.0 (L1), 1-0:56.7.0 (L2), 1-0:76.7.0 (L3) + # → add grid_l2 / grid_l3 sensors and set power_sensor_l2 / l3 below + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +The default `obis_code` here matches the Python source's default +`OBIS_POWER_CURRENT` (`0100100700ff` → `1-0:16.7.0`); the per-phase codes match +its `OBIS_POWER_L1/L2/L3` defaults. + +## TQ Energy Manager + +**Tier: 🟠 Alternate.** The Python `[TQ_EM]` source talks to the device's +proprietary session/login JSON API (`/start.php` + `/mum-webservice/data.php`), +which has no ESPHome port. However, the TQ Energy Manager (EM420 and similar) +also exposes **Modbus RTU/TCP and MQTT** — read it through one of those instead. + +Via Modbus (RS485 to the EM; use the active-power register from the TQ Modbus +register map — `address` / `value_type` below are placeholders): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +uart: + id: mod_uart + tx_pin: GPIO17 + rx_pin: GPIO16 + baud_rate: 9600 + stop_bits: 1 + +modbus: + id: modbus1 + uart_id: mod_uart + +modbus_controller: + - id: tq_em + address: 1 + modbus_id: modbus1 + update_interval: 1s + +sensor: + - platform: modbus_controller + modbus_controller_id: tq_em + id: grid_l1 + register_type: holding + address: 0 # ← set to the TQ active-power register + value_type: S_DWORD # ← match the register's type + unit_of_measurement: W + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +Alternatively enable the EM's MQTT export and use the [`mqtt_subscribe`](#mqtt) +config above with the EM's power topic. + +## HomeWizard + +**Tier: 🟠 Alternate.** The Python `[HOMEWIZARD]` source uses the v2 WebSocket +API (TLS + token), which has no ESPHome component. Easiest ESP path: enable +*Local API* in the HomeWizard app and poll the **v1 HTTP API** at +`GET /api/v1/data`; grid power is `active_power_w` (and `active_power_l1_w` … +`_l3_w` for three-phase): + +```yaml +external_components: + - source: github://tomquist/astrameter@develop + components: [ct002] + +http_request: + useragent: esphome/astrameter + timeout: 5s + +sensor: + - platform: template + id: grid_l1 + unit_of_measurement: W + device_class: power + +interval: + - interval: 1s + then: + - http_request.get: + url: http://192.168.1.110/api/v1/data + capture_response: true + on_response: + then: + - lambda: |- + json::parse_json(body, [](JsonObject root) -> bool { + id(grid_l1).publish_state(root["active_power_w"]); + return true; + }); + +ct002: + id: ct002_main + power_sensor_l1: grid_l1 +``` + +**Native alternative:** the HomeWizard dongle just reads your smart meter's P1 +telegram. With your own P1-reader hardware you can skip the dongle and use +ESPHome's native [`dsmr`](https://esphome.io/components/sensor/dsmr/) component +(or [`sml`](#sml) for SML meters) on the ESP. + +## Enphase Envoy (IQ Gateway) + +**Tier: 🔴 Not yet available.** There is currently **no ESPHome component** for +the Enphase Envoy / IQ Gateway, so there is no config to copy yet. The Python +`[ENVOY]` source reads the local `/production.json?details=1` endpoint, which +requires: + +- **HTTPS to a self-signed certificate** on the gateway, and +- a **JWT bearer token** — either a long-lived static token or one fetched and + refreshed via the Enphase Enlighten **cloud** (login → entrez token endpoint), + including transparent re-auth on HTTP 401. + +ESPHome's stock `http_request` can't comfortably do the cloud token exchange and +refresh loop, so this needs a **purpose-built external component**. + +*To implement:* an external component that (1) holds a static JWT or performs the +Enlighten login + entrez token fetch over TLS, (2) GETs `production.json` from +the local gateway (TLS, self-signed), (3) parses the `consumption[] → +net-consumption` entry (per-phase `lines[].wNow`, else aggregate `wNow`), and +(4) re-auths on 401. References: the AstraMeter Python implementation in +`src/astrameter/powermeter/envoy.py`, plus existing ESP32 work in +[collin80/Envoy](https://github.com/collin80/Envoy) and +[Matthew1471/Enphase-API](https://github.com/Matthew1471/Enphase-API). + +## SMA Energy Meter + +**Tier: 🔴 Not yet available.** There is currently **no ESPHome component** for +the SMA **Speedwire** protocol, so there is no config to copy yet. The Python +`[SMA_ENERGY_METER]` source joins the `239.12.255.254:9522` UDP multicast group +and decodes SMA's binary OBIS channel stream (validating the `SMA\0` magic, +protocol id `0x6069`, SUSY/serial, then reading the active-power channels and +dividing the raw value by 10). + +*To implement:* an external component that joins the multicast group on the ESP's +network interface and parses the Speedwire datagram (per-phase +`L1/L2/L3 = power_plus − power_minus`, else total). References: the AstraMeter +Python implementation in `src/astrameter/powermeter/sma_energy_meter.py`, and the +protocol as implemented by [sma2mqtt](https://github.com/vindolin/sma2mqtt) and +[SMA-Speedwire](https://github.com/J0B10/SMA-Speedwire). diff --git a/docs/powermeters.md b/docs/powermeters.md new file mode 100644 index 00000000..3e2fa024 --- /dev/null +++ b/docs/powermeters.md @@ -0,0 +1,439 @@ +# Powermeter Configuration Reference (Python add-on / Docker / direct install) + +This is the per-source reference for the Python AstraMeter (Home Assistant +add-on, Docker, or direct install). **Find your meter in the list below and copy +the matching `config.ini` section.** + +These sections only cover the *source* of the grid-power reading. Options that +apply to **any** powermeter — throttling, wait-for-fresh-push, EMA smoothing, +deadband, Hampel outlier rejection — plus [Value +Transformation](../README.md#value-transformation), the [PID +Controller](../README.md#pid-controller), and running [Multiple +Powermeters](../README.md#multiple-powermeters) are documented in the main +[README](../README.md#configuration). + +> **Running on an ESP32 instead of the Python add-on?** See +> [esphome-powermeters.md](esphome-powermeters.md) for the equivalent +> grid-power sensor configuration for the ESPHome external component. + +## Contents + +- [Shelly](#shelly) +- [Tasmota](#tasmota) +- [Shrdzm](#shrdzm) +- [Emlog](#emlog) +- [IoBroker](#iobroker) +- [HomeAssistant](#homeassistant) +- [VZLogger](#vzlogger) +- [ESPHome](#esphome) +- [AMIS Reader](#amis-reader) +- [Modbus (TCP/UDP)](#modbus-tcpudp) +- [MQTT](#mqtt) +- [JSON HTTP](#json-http) +- [TQ Energy Manager](#tq-energy-manager) +- [HomeWizard](#homewizard) +- [Enphase Envoy (IQ Gateway)](#enphase-envoy-iq-gateway) +- [SMA Energy Meter](#sma-energy-meter) +- [Script](#script) +- [SML](#sml) + +## Shelly + +### Shelly 1PM +```ini +[SHELLY] +TYPE = 1PM +IP = 192.168.1.100 +USER = username +PASS = password +METER_INDEX = meter1 +``` + +### Shelly Plus 1PM +```ini +[SHELLY] +TYPE = PLUS1PM +IP = 192.168.1.100 +USER = username +PASS = password +METER_INDEX = meter1 +``` + +### Shelly EM +```ini +[SHELLY] +TYPE = EM +IP = 192.168.1.100 +USER = username +PASS = password +METER_INDEX = meter1 +``` + +### Shelly 3EM +```ini +[SHELLY] +TYPE = 3EM +IP = 192.168.1.100 +USER = username +PASS = password +METER_INDEX = meter1 +``` + +### Shelly 3EM Pro +```ini +[SHELLY] +TYPE = 3EMPro +IP = 192.168.1.100 +USER = username +PASS = password +METER_INDEX = meter1 +``` + +## Tasmota + +```ini +[TASMOTA] +IP = 192.168.1.101 +USER = tasmota_user +PASS = tasmota_pass +JSON_STATUS = StatusSNS +JSON_PAYLOAD_MQTT_PREFIX = SML +JSON_POWER_MQTT_LABEL = Power +JSON_POWER_INPUT_MQTT_LABEL = Power1 +JSON_POWER_OUTPUT_MQTT_LABEL = Power2 +JSON_POWER_CALCULATE = True +``` + +For 3-phase meters, use comma-separated labels: + +```ini +[TASMOTA] +IP = 192.168.1.101 +JSON_STATUS = StatusSNS +JSON_PAYLOAD_MQTT_PREFIX = eBZ +JSON_POWER_MQTT_LABEL = Power_L1,Power_L2,Power_L3 +``` + +For 3-phase with `JSON_POWER_CALCULATE`, provide matching comma-separated +input and output labels (counts must be equal): + +```ini +[TASMOTA] +IP = 192.168.1.101 +JSON_STATUS = StatusSNS +JSON_PAYLOAD_MQTT_PREFIX = SML +JSON_POWER_INPUT_MQTT_LABEL = Power_In_L1,Power_In_L2,Power_In_L3 +JSON_POWER_OUTPUT_MQTT_LABEL = Power_Out_L1,Power_Out_L2,Power_Out_L3 +JSON_POWER_CALCULATE = True +``` + +## Shrdzm + +```ini +[SHRDZM] +IP = 192.168.1.102 +USER = shrdzm_user +PASS = shrdzm_pass +``` + +## Emlog + +```ini +[EMLOG] +IP = 192.168.1.103 +METER_INDEX = 0 +JSON_POWER_CALCULATE = True +``` + +## IoBroker + +```ini +[IOBROKER] +IP = 192.168.1.104 +PORT = 8087 +CURRENT_POWER_ALIAS = Alias.0.power +POWER_CALCULATE = True +POWER_INPUT_ALIAS = Alias.0.power_in +POWER_OUTPUT_ALIAS = Alias.0.power_out +``` + +## HomeAssistant +```ini +[HOMEASSISTANT] +IP = 192.168.1.105 +PORT = 8123 +# Use HTTPS - if empty False is Fallback +HTTPS = ""|True|False +ACCESSTOKEN = YOUR_ACCESS_TOKEN +# The entity or entities (comma-separated for 3-phase) that provide current power +CURRENT_POWER_ENTITY = ""|sensor.current_power|sensor.phase1,sensor.phase2,sensor.phase3 +# If False or Empty the power is not calculated - if empty False is Fallback +POWER_CALCULATE = ""|True|False +# The entity ID or IDs (comma-separated for 3-phase) that provide power input +POWER_INPUT_ALIAS = ""|sensor.power_input|sensor.power_in_1,sensor.power_in_2,sensor.power_in_3 +# The entity ID or IDs (comma-separated for 3-phase) that provide power output +POWER_OUTPUT_ALIAS = ""|sensor.power_output|sensor.power_out_1,sensor.power_out_2,sensor.power_out_3 +# Is a Path Prefix needed? +API_PATH_PREFIX = ""|/core +# Per-powermeter throttling override (recommended: 2-3 seconds for HomeAssistant) +THROTTLE_INTERVAL = 2 +``` + +Example: Variant 1 with a single combined input & output sensor +```ini +[HOMEASSISTANT] +IP = 192.168.1.105 +PORT = 8123 +HTTPS = True +ACCESSTOKEN = YOUR_ACCESS_TOKEN +CURRENT_POWER_ENTITY = sensor.current_power +``` + +Example: Variant 2 with separate input & output sensors +```ini +[HOMEASSISTANT] +IP = 192.168.1.105 +PORT = 8123 +HTTPS = True +ACCESSTOKEN = YOUR_ACCESS_TOKEN +POWER_CALCULATE = True +POWER_INPUT_ALIAS = sensor.power_input +POWER_OUTPUT_ALIAS = sensor.power_output +``` + +Example: Variant 3 with three-phase power monitoring +```ini +[HOMEASSISTANT] +IP = 192.168.1.105 +PORT = 8123 +HTTPS = True +ACCESSTOKEN = YOUR_ACCESS_TOKEN +CURRENT_POWER_ENTITY = sensor.phase1,sensor.phase2,sensor.phase3 +``` + +Example: Variant 4 with three-phase power calculation +```ini +[HOMEASSISTANT] +IP = 192.168.1.105 +PORT = 8123 +HTTPS = True +ACCESSTOKEN = YOUR_ACCESS_TOKEN +POWER_CALCULATE = True +POWER_INPUT_ALIAS = sensor.power_in_1,sensor.power_in_2,sensor.power_in_3 +POWER_OUTPUT_ALIAS = sensor.power_out_1,sensor.power_out_2,sensor.power_out_3 +# Per-powermeter throttling override (recommended: 2-3 seconds for HomeAssistant) +# THROTTLE_INTERVAL = 2 +``` + +## VZLogger + +```ini +[VZLOGGER] +IP = 192.168.1.106 +PORT = 8080 +UUID = your-uuid +``` + +For 3-phase meters, provide comma-separated UUIDs (one per phase); phases are +fetched in parallel: + +```ini +[VZLOGGER] +IP = 192.168.1.106 +PORT = 8080 +UUID = uuid-l1, uuid-l2, uuid-l3 +``` + +## ESPHome + +```ini +[ESPHOME] +IP = 192.168.1.107 +PORT = 6052 +DOMAIN = your_domain +ID = your_id +``` + +## AMIS Reader + +```ini +[AMIS_READER] +IP = 192.168.1.108 +``` + +## Modbus (TCP/UDP) + +```ini +[MODBUS] +HOST = 192.168.1.100 +PORT = 502 +UNIT_ID = 1 +ADDRESS = 0 +COUNT = 1 +DATA_TYPE = UINT16 +BYTE_ORDER = BIG +WORD_ORDER = BIG +REGISTER_TYPE = HOLDING # or INPUT +TRANSPORT = TCP # or UDP +``` + +`TRANSPORT` selects the Modbus transport: `TCP` (default) or `UDP`. + +## MQTT + +```ini +[MQTT] +BROKER = broker.example.com +PORT = 1883 +TOPIC = home/powermeter +JSON_PATH = $.path.to.value (Optional for JSON payloads) +USERNAME = mqtt_user (Optional) +PASSWORD = mqtt_pass (Optional) +# Optional: connect over TLS (mqtts://) — default false +# TLS = false +# Per-powermeter throttling override +# THROTTLE_INTERVAL = 2 +``` + +Instead of `BROKER`/`PORT`/`USERNAME`/`PASSWORD`/`TLS`, you can provide a single `URI` of the form `mqtt[s]://[user[:pass]@]host[:port]` (use `mqtts://` for TLS; credentials and port are optional). When `URI` is set, the individual `BROKER`/`PORT`/`USERNAME`/`PASSWORD`/`TLS` fields are ignored. + +```ini +[MQTT] +URI = mqtts://user:pass@broker.example.com:8883 +TOPIC = home/powermeter +``` + +The `JSON_PATH` option is used to extract the power value from a JSON payload. The path must be a [valid JSONPath expression](https://goessner.net/articles/JsonPath/). +If the payload is a simple integer value, you can omit this option. + +Both `JSON_PATH` and `JSON_PATHS` are parsed with the [`jsonpath-ng` extended syntax](https://github.com/h2non/jsonpath-ng#extensions), so you can chain extensions like `` `split(...)` `` or `` `sub(/regex/, replacement)` `` to massage a payload value before it's converted to a float — for instance `` `$.state.`split( , 0, -1)` `` or `` `$.state.`sub(/[^0-9.\-]+$/, )` `` to strip a unit suffix like `"331.74 W"`. See the [JSON HTTP](#json-http) section below for more examples. + +### Multi-phase MQTT + +For three-phase setups, there are two options: + +**Option 1: Multiple topics** — one topic per phase, each publishing a plain numeric value (or JSON with the same path): + +```ini +[MQTT] +BROKER = broker.example.com +TOPICS = home/power/l1, home/power/l2, home/power/l3 +``` + +**Option 2: Single topic with multiple JSON paths** — one topic publishing a JSON message containing all phases: + +```ini +[MQTT] +BROKER = broker.example.com +TOPIC = home/powermeter +JSON_PATHS = $.phases[0].power, $.phases[1].power, $.phases[2].power +``` + +`TOPICS` takes precedence over `TOPIC`, and `JSON_PATHS` takes precedence over `JSON_PATH`. You can combine `TOPICS` with `JSON_PATH` (same path applied to each topic) or with `JSON_PATHS` (one path per topic, counts must match). + +## JSON HTTP + +```ini +[JSON_HTTP] +URL = http://example.com/api +# Comma separated JSON paths - single path for 1-phase or three for 3-phase +JSON_PATHS = $.power +USERNAME = user (Optional) +PASSWORD = pass (Optional) +# Additional headers separated by ';' using 'Key: Value' +HEADERS = Authorization: Bearer token +``` + +`JSON_PATHS` is parsed with the [`jsonpath-ng` extended syntax](https://github.com/h2non/jsonpath-ng#extensions), so you can chain extensions like `` `split(...)` `` or `` `sub(/regex/, replacement)` `` to massage the value before it's converted to a float. For example, an openHAB `Number:Power` item returns `"331.74 W"` — strip the unit with either of: + +```ini +JSON_PATHS = $.state.`split( , 0, -1)` +JSON_PATHS = $.state.`sub(/[^0-9.\-]+$/, )` +``` + +## TQ Energy Manager + +```ini +[TQ_EM] +IP = 192.168.1.100 +#PASSWORD = pass +#TIMEOUT = 5.0 (Optional) +``` + +## HomeWizard + +Reads a [HomeWizard](https://www.homewizard.com/) P1 dongle (or compatible device) over the local **WebSocket** API (`wss://`). Obtain a token once via `POST /api/user` while confirming on the device; see the [HomeWizard API docs](https://api-documentation.homewizard.com/docs/v2/). + +```ini +[HOMEWIZARD] +IP = 192.168.1.110 +TOKEN = YOUR_32_CHAR_HEX_TOKEN +SERIAL = your_device_serial +# Optional: disable TLS certificate verification on a trusted LAN if verification fails (default True) +# VERIFY_SSL = True +# THROTTLE_INTERVAL = 0 +``` + +## Enphase Envoy (IQ Gateway) + +Reads grid power from an [Enphase IQ Gateway / Envoy](https://enphase.com/installers/microinverters/iq-gateway) over the local HTTPS API (`/production.json?details=1`). The reading comes from the `net-consumption` measurement (positive = grid import, negative = export). Per-phase readings are reported automatically when the gateway exposes them; otherwise the aggregate single-phase value is used. Requires consumption CTs installed on the Envoy. + +```ini +[ENVOY] +HOST = 192.168.1.120 +# Option A: pre-obtained long-lived JWT (recommended) +TOKEN = eyJ... +# Option B: let AstraMeter fetch and refresh tokens via the Enphase Enlighten cloud +# USERNAME = you@example.com +# PASSWORD = your-enphase-password +# SERIAL = 123456789012 +# Envoy ships a self-signed certificate; verification is disabled by default. +# VERIFY_SSL = False +``` + +**Token acquisition.** Generate a long-lived (~1 year) static token at . Alternatively, configure `USERNAME`/`PASSWORD`/`SERIAL` and AstraMeter will fetch a token on first use and refresh it automatically when the Envoy returns 401. + +**TLS.** `VERIFY_SSL` defaults to `False` because Enphase does not publish a CA bundle for the IQ Gateway's self-signed certificate. This option **only affects the local Envoy connection** — Enphase Enlighten cloud requests (login and token endpoints) always verify TLS using the system trust store, regardless of this setting. + +**MFA.** The auto-fetch flow does not support Enlighten accounts with multi-factor authentication enabled. Those users must supply a static `TOKEN`. + +**CT direction.** If your readings have the wrong sign (export shows as import or vice versa), one or more CTs are mounted backwards. Flip them in software with the global `POWER_MULTIPLIER = -1` (or per-phase, e.g. `POWER_MULTIPLIER = 1, -1, 1`). + +## SMA Energy Meter + +Reads an [SMA Energy Meter](https://www.sma.de/) (EM 1.0/2.0) or Sunny Home Manager via the **Speedwire** multicast protocol (UDP). The listener joins the default multicast group and reports per-phase active power (L1, L2, L3). Use `SERIAL_NUMBER = 0` to auto-detect the first meter seen on the network, or set the device serial to pin a specific unit. Like other UDP-based features, this requires the host to receive multicast traffic (use Docker host networking or equivalent). + +```ini +[SMA_ENERGY_METER] +MULTICAST_GROUP = 239.12.255.254 +PORT = 9522 +SERIAL_NUMBER = 0 +# INTERFACE = 192.168.1.10 +# THROTTLE_INTERVAL = 0 +``` + +## Script + +You can also use a custom script to get the power values. The script should output at most 3 integer values, separated by a line break. +```ini +[SCRIPT] +COMMAND = /path/to/your/script.sh +``` + +## SML + +```ini +[SML] +SERIAL = /dev/ttyUSB0 +# Optional: override default OBIS hex registers (12 hex digits each; defaults match common German eHZ meters) +#OBIS_POWER_CURRENT = 0100100700ff +#OBIS_POWER_L1 = 0100240700ff +#OBIS_POWER_L2 = 0100380700ff +#OBIS_POWER_L3 = 01004c0700ff +``` + +Read from a powermeter that is connected via USB and that transmits SML (Smart Message Language) data via an IR head. **`SERIAL` is required**: local device path to the serial interface (e.g. `/dev/ttyUSB0` on Linux). + +**Multi-phase:** If the meter exposes per-phase instantaneous active power for L1–L3 (`Summenwirkleistung` / default OBIS above), those three values are used automatically. Otherwise the aggregate instantaneous power register (`aktuelle Wirkleistung` / `OBIS_POWER_CURRENT`) is used as a single reading. When both are present in the same SML list, per-phase values take precedence. + +**OBIS overrides:** Only needed if your meter uses different register addresses; values must be exactly 12 hexadecimal characters (lowercase or uppercase).