Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/en/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ A **proxy** just need 1 **power meter** package
Get power data from Home Assistant sensor
* [Shelly EM](power_meter_shelly_em.md)
Get power data from a shelly EM
* [Shelly EM3 Pro / Pro 3EM](power_meter_shelly_em3.md)
Get 3-phase power data from a Shelly EM3 Pro / Pro 3EM
* [Proxy client](power_meter_proxy_client.md)
Get power data from outside from solar router

Expand Down
52 changes: 52 additions & 0 deletions docs/en/power_meter_shelly_em3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Shelly EM3 Pro / Pro 3EM Power Meter

This power meter is designed to get power consumption directly from a Shelly EM3 Pro / Pro 3EM three-phase energy meter over HTTP (Gen2/Gen3 "RPC" API).

To use this package, add the following lines to your configuration file:

```yaml linenums="1"
packages:
power_meter:
url: https://github.com/hacf-fr/Solar-Router-for-ESPHome/
file: solar_router/power_meter_shelly_em3.yaml
vars:
power_meter_ip_address: "192.168.1.21"
```

This package needs to know the IP address of the Shelly EM3 Pro / Pro 3EM. This IP address has to be defined by `power_meter_ip_address` into `vars` section as shown above.

!!! note "Per-phase sensors"
The package exposes the three per-phase active powers as internal sensors (for tuning). Set `show_phase_power: "True"` in `vars` to make them visible in Home Assistant.

!!! note "HTTP Authentication Header"
This power meter allow to define HTTP Authentication Header with the variable `power_meter_auth_header`.
This variable can be set in `vars` section.

!!! note "EM component id"
If the EM component id of the meter differs from the default, set `power_meter_emeter_id` in `vars`.

## Three-phase sum

On a three-phase contract the meter adds the three phases and bills only the **net** value. When one phase produces more (photovoltaic) than the other two consume, that's the right time to divert energy.

This power meter therefore uses the *arithmetic sum* of the three phase active powers as the grid-exchange signal:

```
S_grid = a_act_power + b_act_power + c_act_power == total_act_power
```

* `+` sign : power is taken from the grid
* `-` sign : power is pushed back to the grid

With routing on the 3-phase sum, the solar router diverts energy only when the whole installation is in surplus, never importing from the grid to run the load.

This package is activated/deactivated with the variable `power_meter_activated`. By default, a power meter is deactivated at startup. The activation switch in home assistant determines if the power meter should be started or not.

This power meter can be use in a proxy (a Solar Router only using a power meter). If this power meter is used in a proxy, it is required to activate it at startup by setting `power_meter_activated_at_start` to `1` in your yaml in the `vars` section defining the power meter configuration :

```yaml linenums="1"
power_meter_activated_at_start: "1"
```

!!! warning "Network dependency"
This power meter require the network to gather information about energy exchanged with the grid.
2 changes: 2 additions & 0 deletions docs/fr/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Un **proxy** n'a besoin que d'un package **power meter**
Pour obtenir les données de puissance du capteur Home Assistant
* [Shelly EM](power_meter_shelly_em.md)
Pour obtenir les données de puissance d'un Shelly EM
* [Shelly EM3 Pro / Pro 3EM](power_meter_shelly_em3.md)
Pour obtenir les données de puissance triphasées d'un Shelly EM3 Pro / Pro 3EM
* [Client Proxy](power_meter_proxy_client.md)
Pour obtenir les données de puissance de l'extérieur du routeur solaire

Expand Down
52 changes: 52 additions & 0 deletions docs/fr/power_meter_shelly_em3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Power meter Shelly EM3 Pro / Pro 3EM

Ce *power meter* est conçu pour obtenir la consommation d'énergie directement à partir d'un compteur d'énergie triphasé Shelly EM3 Pro / Pro 3EM via HTTP (API Gen2/Gen3 "RPC").

Pour utiliser ce package, ajoutez les lignes suivantes à votre fichier de configuration :

```yaml linenums="1"
packages:
power_meter:
url: https://github.com/hacf-fr/Solar-Router-for-ESPHome/
file: solar_router/power_meter_shelly_em3.yaml
vars:
power_meter_ip_address: "192.168.1.21"
```

Ce package doit connaître l'adresse IP du Shelly EM3 Pro / Pro 3EM. L'adresse IP doit être définie par `power_meter_ip_address` dans la section `vars` de votre configuration, comme dans l'exemple ci-dessus.

!!! note "Capteurs par phase"
Le package expose les trois puissances actives par phase en tant que capteurs internes (pour le réglage). Définissez `show_phase_power: "True"` dans `vars` pour les rendre visibles dans Home Assistant.

!!! note "En-tête d'authentification HTTP"
Ce *power meter* permet de définir l'en-tête d'authentification HTTP avec la variable `power_meter_auth_header`.
Cette variable peut être définie dans la section `vars`.

!!! note "Identifiant du composant EM"
Si l'identifiant du composant EM du compteur diffère de la valeur par défaut, définissez `power_meter_emeter_id` dans `vars`.

## Somme triphasée

Sur un abonnement triphasé, le compteur additionne les trois phases et ne facture que la valeur **nette**. Lorsqu'une phase produit plus (photovoltaïque) que les deux autres ne consomment, c'est le bon moment pour détourner l'énergie.

Ce *power meter* utilise donc la *somme arithmétique* des trois puissances actives par phase comme signal d'échange avec le réseau :

```
S_grid = a_act_power + b_act_power + c_act_power == total_act_power
```

* signe `+` : l'énergie est prélevée du réseau
* signe `-` : l'énergie est réinjectée sur le réseau

Avec le détournement sur la somme triphasée, le routeur solaire ne détourne l'énergie que lorsque toute l'installation est en surplus, sans jamais prélever du réseau pour alimenter la charge.

Ce package est activé/désactivé à l'aide d'une variable globale `power_meter_activated`. Par défaut, un compteur de puissance est désactivé au démarrage. L'interrupteur d'activation dans home assistant détermine si le compteur de puissance doit être démarré ou non.

Si ce compteur de puissance est utilisé à l'intérieur d'un proxy, il est nécessaire de l'activer au démarrage en définissant `power_meter_activated_at_start` à `1` dans votre fichier de configuration yaml, comme dans l'exemple ci-dessous :

```yaml linenums="1"
power_meter_activated_at_start: "1"
```

!!! warning "Dépendance réseau"
Ce *power meter* nécessite le réseau pour recueillir des informations sur l'énergie échangée avec le réseau électrique.
70 changes: 70 additions & 0 deletions esp32-em3-router.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ----------------------------------------------------------------------------------------------------
# ESPHome configuration - This part depends on your hardware target
# ----------------------------------------------------------------------------------------------------
# Example: ESP32-based solar router using a 3-phase Shelly EM3 Pro / Pro 3EM as
# the power meter (progressive triac dimmer + 1-dimmer engine).
# ----------------------------------------------------------------------------------------------------

esphome:
name: em3-router
friendly_name: EM3 Router
min_version: 2025.5.0

esp32:
board: esp32dev
framework:
type: esp-idf

# Enable logging
logger:
baud_rate: 115200
level: INFO
logs:
component: ERROR
light: ERROR

# Enable Home Assistant API
api:
encryption:
key: !secret api_encryption_key

# Enable over-the-air updates
ota:
- platform: esphome
password: !secret solar_router_ota_password

# WiFi connection
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
reboot_timeout: 24h

# Activate web interface
web_server:
port: 80

# Define watchdog time (it should be greater than timeout)
http_request:
watchdog_timeout: 12s

# ----------------------------------------------------------------------------------------------------
# Customisation
# ----------------------------------------------------------------------------------------------------
packages:
solar_router:
url: https://github.com/hacf-fr/Solar-Router-for-ESPHome/
ref: main
refresh: 1d
files:
- path: solar_router/common.yaml
- path: solar_router/power_meter_shelly_em3.yaml
vars:
power_meter_ip_address: "192.168.1.42"
- path: solar_router/regulator_triac.yaml
vars:
regulator_gate_pin: GPIO17
regulator_zero_crossing_pin: GPIO23
- path: solar_router/engine_1dimmer.yaml
vars:
green_led_pin: GPIO18
yellow_led_pin: GPIO19
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ nav:
- JSY-MK-194T: power_meter_jsy-mk-194t.md
- Proxy client: power_meter_proxy_client.md
- Shelly EM: power_meter_shelly_em.md
- Shelly EM3 Pro / Pro 3EM: power_meter_shelly_em3.md
- Regulator:
- Triac: regulator_triac.md
- Solid State Relay: regulator_solid_state_relay.md
Expand Down
170 changes: 170 additions & 0 deletions solar_router/power_meter_shelly_em3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<<: !include power_meter_common.yaml

esphome:
min_version: 2025.5.0

# ----------------------------------------------------------------------------------------------------
# Shelly EM3 Pro / Pro 3EM power meter (Gen2/Gen3 "RPC" API)
# ----------------------------------------------------------------------------------------------------
# Polls the Shelly three-phase energy meter over HTTP (`EM.GetStatus`) and uses
# the *arithmetic sum* of the three phase active powers as the grid-exchange
# signal:
#
# S_grid = a_act_power + b_act_power + c_act_power == total_act_power
#
# This matches the way a three-phase contract is billed (net metering):
# the three phases are added up and only the net value is billed, so an export
# of -1200 W on phase C fully offsets a (+500 W) + (+800 W) import on A+B.
#
# + sign : power is taken from the grid
# - sign : power is pushed back to the grid
#
# With routing on the 3-phase sum, the router diverts energy only when the
# *whole installation* is in surplus, never importing "from the front door" to
# run the load. Phase-balance/per-phase safety is naturally satisfied because
# simultaneous phase imbalances cannot make the sum go negative.
#
# Package variables (vars):
# power_meter_ip_address (required) IP / hostname of the Shelly EM3 Pro.
# power_meter_emeter_id (optional) EM component id, default "0".
# power_meter_auth_header (optional) HTTP Authorization header (Basic auth).
# show_phase_power (optional) expose per-phase W sensors, default "False".

substitutions:
# Same defaults as power_meter_common.yaml - re-declared here because a
# substitution block overriding the merged one replaces it entirely.
power_meter_activated_at_start: "0"
power_sign: "1"
# The Shelly EM3 only exposes a NET power (total_act_power): the per-phase
# "consumption" template below is never written in this package, so it stayed
# stuck at "unknown" in HA. Hide it instead of shipping dead weight.
consumption_sensor_internal: "true"
# This package specifics
power_meter_emeter_id: "0"
power_meter_auth_header: ""
show_phase_power: "False"

http_request:
id: em3_http_request
useragent: esphome/device
timeout: 5s
verify_ssl: False

script:
# Poll the Em3 meter and publish the grid-exchange power (total_act_power)
- id: power_meter_source
mode: single
then:
- if:
condition:
lambda: 'return network::is_connected();'
then:
- http_request.get:
url: 'http://${power_meter_ip_address}/rpc/EM.GetStatus?id=${power_meter_emeter_id}'
request_headers:
Content-Type: application/json
Authorization: ${power_meter_auth_header}
capture_response: true
max_response_buffer_size: 8192
on_response:
then:
- logger.log:
format: 'EM3 response status: %d, duration: %u ms'
args:
- response->status_code
- response->duration_ms
- lambda: |-
if (response->status_code != 200) {
ESP_LOGW("em3", "EM3 request failed with status: %d", response->status_code);
id(real_power).publish_state(NAN);
} else {
bool parse_success = json::parse_json(body, [](JsonObject root) -> bool {
if (!root["total_act_power"].is<double>()) {
ESP_LOGW("em3", "EM3 response missing 'total_act_power'");
return false;
}
id(real_power).publish_state(root["total_act_power"].as<float>() * ${power_sign});

if (root["a_act_power"].is<double>()) {
id(em3_phase_a_power).publish_state(root["a_act_power"].as<float>());
}
if (root["b_act_power"].is<double>()) {
id(em3_phase_b_power).publish_state(root["b_act_power"].as<float>());
}
if (root["c_act_power"].is<double>()) {
id(em3_phase_c_power).publish_state(root["c_act_power"].as<float>());
}
return true;
});

if (!parse_success) {
id(real_power).publish_state(NAN);
}
}
on_error:
then:
- logger.log:
format: 'EM3 request failed or timeout'
level: WARN
- lambda: |-
id(real_power).publish_state(NAN);

# Sense the per-phase active powers for debugging / tuning (hidden by default)
sensor:
# Re-declared from power_meter_common.yaml: the '<<' merge only keeps the
# FIRST definition of a key, and this file defines its own 'sensor:' section.
# The engine depends on these two template sensors.
- id: real_power
platform: template
name: "Real Power"
device_class: "power"
unit_of_measurement: "W"
update_interval: 1s

- id: consumption
platform: template
name: "Consumption"
device_class: "power"
unit_of_measurement: "W"
update_interval: 1s
internal: ${consumption_sensor_internal}

- platform: template
id: em3_phase_a_power
name: "EM3 Phase A Active Power"
device_class: "power"
unit_of_measurement: "W"
internal: ${show_phase_power}
update_interval: never
- platform: template
id: em3_phase_b_power
name: "EM3 Phase B Active Power"
device_class: "power"
unit_of_measurement: "W"
internal: ${show_phase_power}
update_interval: never
- platform: template
id: em3_phase_c_power
name: "EM3 Phase C Active Power"
device_class: "power"
unit_of_measurement: "W"
internal: ${show_phase_power}
update_interval: never

time:
# Poll cadence note:
# Polling every second (/1) opens/closes an esp_http_client connection every
# second; over hours this can provoke a stale-transport use-after-free inside
# ESP-IDF's tcp_transport (LoadProhibited in base_poll_read / transport_ssl.c,
# "CRASH DETECTED ON PREVIOUS BOOT"). We therefore poll every 2 seconds (/2):
# ~2x less connection churn, which sacrifices ~1s of reaction time. Revisit
# if a future ESPHome release fixes the http_request/IDF transport reuse race.
- platform: sntp
on_time:
- seconds: /2
then:
- if:
condition:
- lambda: return id(power_meter_activated) != 0;
then:
- script.execute: power_meter_source