Patrick Schurig 577535d2b2 Add AGENTS.md
2026-06-13 14:10:51 +02:00

390 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AGENTS.md — plugin Eastron (compteurs SDM)
> Repo : `etm-powersync-plugins-modbus` · Dossier : `eastron/` · Branche : `main`
> Compteurs d'énergie **Eastron SDM** (SDM72, SDM120, SDM220, SDM230, SDM630).
> Comm : **Modbus RTU / RS485 uniquement** (pas de variante TCP dans ce plugin).
> Statut cible : **Supporté / prod** — SDM72 (principal) + SDM120 (PV) en prod sur `etm-nymea-dev`.
> Paquet : `eastron 1.15.0+etm3` (amd64 + cross arm64, canaux nightly/testing/stable).
> Plugin id : `2078c2fc-c4cd-46bb-95ab-e9a55ef0a281` · Vendor `eastron` `33529c0d-67e6-441e-b0bb-6e76e9d4bc1c`.
>
> **Nature du plugin (à comprendre AVANT toute modif)** : c'est un plugin **généré par le
> modbus-tool de nymea**, pas un plugin écrit à la main. La carte de registres et le décodage
> sont **déclaratifs** (`*-registers.json`) ; le `.cpp`/`.h` est en grande partie du code
> généré + un peu de glue (discovery, setup, câblage des states). On édite le JSON, **pas** les
> classes de connexion générées.
>
> **Sources de vérité (lues juin 2026, commit `b1da668`)** :
> - `sdm72-registers.json` — carte SDM72 (triphasé). ✅ lue intégralement.
> - `sdm120-registers.json` — carte SDM120 (monophasé). ✅ lue intégralement.
> - `integrationplugineastron.json` — ThingClasses, interfaces, params, states. ✅ lue
> (tronquée en toute fin sur `sdm230Producer`, mais les 5 familles de modèles sont vues).
> - **Protocoles constructeur (datasheets Eastron, lus juin 2026)** : SDM120-Modbus V2.4,
> SDM230-Modbus V1.2, SDM72D-M-2 V1.1.1 (2023). ✅ croisés avec les `*-registers.json` —
> adresses, FC04, endianness confirmées (voir §4).
> - **Non encore relus dans ce brief** : `integrationplugineastron.cpp`/`.h` (discovery + glue),
> `eastron.pro`, `meta.json`, `sdm630-registers.json`, `sdm220-registers.json`,
> `sdm230-registers.json` (contenu JSON ; les adresses SDM230 sont confirmées par le
> datasheet). → voir « À vérifier dans le code » en fin de fichier.
---
## 1. RÔLE
Exposer des compteurs Eastron SDM lus en Modbus RTU comme Things nymea implémentant les
interfaces énergie (`energymeter` / `smartmeterconsumer` / `smartmeterproducer`), consommées par
le moteur `energy-etm` (`EnergyArbitrator`) pour le bilan de puissance/énergie (point de
livraison, production PV).
**Read-only.** Un compteur ne se pilote pas : aucune action, aucun registre en écriture, aucun
`paramRegisters` d'écriture. Tout est `inputRegister` `access: RO`. Toute tentative d'ajouter une
écriture est hors périmètre (et faux pour ce matériel).
## 2. ARCHITECTURE — la séparation à ne jamais oublier
Le point central de ce plugin (équivalent de la « règle d'or transport » du brief V2C, mais
inversé) : **le plugin ne possède PAS le port série.**
```
nymead
└─ ModbusRtuMaster (objet nymea, configuré HORS plugin)
• /dev/ttyUSB0 ← le port physique appartient au master, pas au plugin
• 9600 8N1, timeout, retries ← configurés sur le MASTER
• un seul master = un seul maître sur le bus RS485
└─ Thing « SDM72 » (slaveAddress 1) ─┐ partagent le même
└─ Thing « SDM120 » (slaveAddress 2) ─┘ master via modbusMasterUuid
```
- Chaque ThingClass a deux `paramTypes` clés :
- `slaveAddress` (`uint`, défaut 1) — l'unit ID Modbus du compteur sur le bus.
- `modbusMasterUuid` (`QUuid`, **readOnly**) — pointe vers le `ModbusRtuMaster` nymea qui
porte le port série. Renseigné par le mécanisme de setup/discovery, jamais saisi à la main.
- 🔴 **Conséquence directe** : le baud (9600), la parité (N), le timeout (500 ms) et les retries
(3) **ne sont PAS dans ce plugin**. Ils vivent dans la config du `ModbusRtuMaster` (côté
nymea-app → Modbus RTU, ou config nymead). Chercher un bug de baud/timeout dans `eastron/` est
une perte de temps : c'est au niveau du master. *(Localisation exacte du réglage à confirmer
selon la version de nymea-app, mais ce n'est pas dans ce dossier.)*
- 🔴 **Bus mono-maître** : tous les SDM partagent le même master = le même `/dev/ttyUSB0`.
nymead est le **seul** maître. Voir §7 (discipline de bus) — c'est la cause racine n°1 des
timeouts intermittents.
## 3. THINGCLASSES, RÔLES ÉNERGIE & CONVENTION ETM
### 3.1 Matrice complète du plugin (ce qui existe)
5 modèles × 3 variantes de rôle = 15 ThingClasses (toutes en `createMethods: ["discovery"]`,
toutes avec `connectable` + une interface énergie selon la variante) :
| Modèle | « — Energy Meter » | « — Producer Meter » | « — Consumer Meter » |
|---|---|---|---|
| **SDM72** (3φ) | `sdm72` (`energymeter`) | `sdm72Producer` (`smartmeterproducer`) | `sdm72Consumer` (`smartmeterconsumer`) |
| **SDM120** (1φ) | `sdm120` (`energymeter`) | `sdm120Producer` (`smartmeterproducer`) | `sdm120Consumer` (`smartmeterconsumer`) |
| **SDM220** (1φ) | `sdm220` (`energymeter`) | `sdm220Producer` (`smartmeterproducer`) | `sdm220Consumer` (`smartmeterconsumer`) |
| **SDM230** (1φ) | `sdm230` (`energymeter`) | `sdm230Producer` (`smartmeterproducer`) | `sdm230Consumer` (`smartmeterconsumer`) |
| **SDM630** (3φ) | `sdm630` (`energymeter`) | `sdm630Producer` (`smartmeterproducer`) | `sdm630Consumer` (`smartmeterconsumer`) |
States par variante : **Energy Meter** = riche (tension/courant/puissance ± par phase +
`totalEnergyConsumed` + `totalEnergyProduced`) ; **Producer** = `currentPower`,
`totalEnergyProduced`, `frequency` ; **Consumer** = `currentPower`, `totalEnergyConsumed`,
`frequency`. Toutes ont `connected` (`bool`, `cached:false`).
### 3.2 Comment nymea attribue le rôle (source `energymanagerimpl.cpp`)
🔴 **Le rôle énergie d'un compteur = son INTERFACE, lue statiquement par l'`EnergyManager`.** Il
n'y a aucun paramètre runtime « rôle » côté plugin. La logique du manager :
- **Root meter (grid)** : `setRootMeter()` **refuse** un thing qui n'a pas l'interface
`energymeter`. Le compteur réseau *doit* être un `energymeter`, et il n'y en a **qu'un**. Son
`currentPower` signé (+ = consommé / = produit) et ses deux totaux donnent l'échange réseau
dans les deux sens → **un seul thing couvre import ET export**.
- **Producteurs** : production = `configuredThings().filterByInterface("smartmeterproducer")`.
Seuls les things `smartmeterproducer` comptent comme production (onduleur/PV).
- **Consommateurs** : `filterByInterface("smartmeterconsumer")` (charge dédiée, PAC…).
- ⚠️ **Gotcha auto-root** : si aucun root meter n'est défini, le manager **promeut
automatiquement le premier `energymeter` qui apparaît**. Ne pas laisser traîner un `energymeter`
parasite qui raflerait le rôle de root à la place du bon compteur.
🔴 **Pourquoi on ne peut PAS fusionner en une seule ThingClass par modèle** : un thing portant à
la fois `energymeter` + `smartmeterproducer` + `smartmeterconsumer` serait ramassé par **les deux**
boucles `filterByInterface`**double comptage** du bilan (compté en prod ET en conso). Le modèle
interface-driven impose donc une ThingClass par rôle. C'est une limite de l'**energy experience**,
pas du plugin `eastron`.
### 3.3 Le rôle dépend de l'INSTALLATION, pas du modèle (convention ETM)
Le même modèle sert à des rôles différents selon le chantier (ex. onduleur 3 kW → SDM120,
onduleur 10 kW → SDM230 ; en triphasé SDM72/SDM630 en compteur réseau, onduleur ou conso PAC).
La matrice modèle×rôle est donc **irréductible** : aucune variante n'est « jamais utilisée », **ne
pas élaguer** (on casserait des cas réels). Couverture ETM des (modèle × rôle) effectivement
déployés :
| Modèle | Root meter (`energymeter`) | Producteur (`smartmeterproducer`) | Consommateur (`smartmeterconsumer`) |
|---|:---:|:---:|:---:|
| **SDM72** (3φ) | ✅ | ✅ | ✅ |
| **SDM630** (3φ) | ✅ | ✅¹ | ✅¹ |
| **SDM230** (1φ) | ✅ | ✅ | ✅ |
| **SDM120** (1φ) | —² | ✅ | ✅ |
| **SDM220** (1φ) | —³ | —³ | —³ |
¹ SDM630 en producteur/consommateur = gros onduleur ou PAC **triphasés** (selon la remarque
triphasé : SDM72/SDM630 couvrent les trois rôles en 3φ).
² SDM120 n'est pas déployé comme compteur réseau (trop petit / dédié branche). La ThingClass
`sdm120` (`energymeter`) **existe** et serait techniquement acceptée comme root par nymea, mais
hors convention ETM — et attention au gotcha auto-root.
³ SDM220 : présent dans le plugin, **hors convention de déploiement ETM listée** (à traiter comme
les autres 1φ si un cas se présente).
### 3.4 Convention `displayName` par fonction (action court terme, ZÉRO risque)
La liste « ajouter un thing » montre les ThingClasses par `displayName`. Renommer **uniquement le
`displayName`** (jamais l'`id` ni le `name`) ne casse **aucun** thing appairé en prod — c'est
l'action sûre et disponible tout de suite pour rendre la liste lisible (l'installateur choisit par
**fonction**, pas par « Producer/Consumer/Energy Meter »). Convention ETM proposée :
| ThingClass (`name`) | `displayName` proposé |
|---|---|
| `sdm72` / `sdm230` / `sdm630` | **Compteur réseau — Eastron SDMxx (Nφ)** |
| `sdm72Producer` / `sdm230Producer` / `sdm120Producer` / `sdm630Producer` | **Onduleur / PV — Eastron SDMxx (Nφ)** |
| `sdm72Consumer` / `sdm230Consumer` / `sdm120Consumer` / `sdm630Consumer` | **Consommateur (PAC/charge) — Eastron SDMxx (Nφ)** |
Édition à faire dans `integrationplugineastron.json` (champ `displayName` de chaque ThingClass).
Regrouper les libellés par rôle aide à scanner les 15 entrées en 3 blocs. Ça **ne réduit pas** le
nombre d'entrées (15 restent 15) — ça les rend intentionnelles.
### 3.5 Le vrai fix de fond (hors périmètre plugin — feature request nymea)
Réduire à « 1 entrée par modèle, rôle choisi après » suppose de modifier
`nymea-experience-plugin-energy`, **pas** ce plugin. **Demande à porter à l'équipe nymea** :
> Permettre d'assigner le rôle énergie (root/grid, producteur, consommateur) à un seul thing
> `energymeter` bidirectionnel depuis l'energy experience — sur le modèle de `setRootMeter()` déjà
> existant, étendu à producteur/consommateur — au lieu d'imposer une ThingClass par interface.
> Motivation : le rôle dépend de l'installation, pas du modèle ; la matrice modèle×rôle explose la
> liste d'ajout côté installateur. C'est additif → ne casse rien des setups existants.
Si nymea suit, la matrice eastron se simplifie d'elle-même (une ThingClass `energymeter` par
modèle, rôle réglé en réglages énergie). **Pas pour aujourd'hui ni le mois prochain.**
### 3.6 Config prod ETM (`etm-nymea-dev`, 192.168.1.120)
- **SDM72** = compteur **principal / réseau**, unit ID **1** → ThingClass `sdm72` (`energymeter`),
désigné **root meter** dans les réglages énergie.
- **SDM120** = compteur **PV**, unit ID **2** → ThingClass `sdm120Producer` (`smartmeterproducer`).
*(Un `sdm120` energymeter non-root ne serait PAS agrégé en production — il faut bien la variante
Producer.)*
- ⚠️ Vérifier sur le Thing réel que ces variantes sont bien celles instanciées (convention ETM
« pas de valeur inventée »), via `nymea-app` ou la conf nymead.
## 4. CARTES DE REGISTRES (confirmées depuis les `*-registers.json`)
Communes aux deux : `protocol: "RTU"`, `endianness: "BigEndian"`, `type: "float"` (32 bits, 2
registres), `registerType: "inputRegister"` (FC04), `access: "RO"`,
`errorLimitUntilNotReachable: 15`. Adresses en **base 0** (registre Modbus, pas l'offset 3xxxx).
**✅ Adresses, FC04 et word order Big-endian (MSR first) confirmés contre les protocoles
constructeur** (SDM72D-M-2 V1.1.1, SDM120 V2.4, SDM230 V1.2).
> 🔎 **`totalEnergyConsumed`/`totalEnergyProduced` = Import/Export, PAS le « Total » du compteur.**
> Le plugin lit `Import active energy` (reg 72) et `Export active energy` (reg 74), et **non** le
> registre « Total active energy » (reg 342). C'est volontaire et correct : sur SDM120/SDM230 le
> « total » dépend du réglage interne *Measurement mode* (mode 2 = import+export par défaut, mode 1
> = import seul, mode 3 = importexport) ; sur SDM72 le « total » = import+export. Lire import et
> export séparément contourne cette ambiguïté de mode. **Ne pas « simplifier » en lisant reg 342.**
### SDM72 — `className: "Sdm72"` · `checkReachableRegister: "totalCurrentPower"`
| addr | id | unité | groupé en block ? |
|---|---|---|---|
| 52 | `totalCurrentPower` (puissance système totale, 30053) | W | non (registre seul, sonde reachable) |
| 0 / 2 / 4 | `voltagePhaseA/B/C` (30001/03/05) | V | block `phaseVoltageAndCurrent` |
| 6 / 8 / 10 | `currentPhaseA/B/C` (30007/09/11) | A | block `phaseVoltageAndCurrent` |
| 12 / 14 / 16 | `powerPhaseA/B/C` (30013/15/17) | W | block `phasePower` |
| 70 | `frequency` (30071) | Hz | block `frequencyAndTotalEnergy` |
| 72 | `totalEnergyConsumed` = Import active energy (30073) | kWh | block `frequencyAndTotalEnergy` |
| 74 | `totalEnergyProduced` = Export active energy (30075) | kWh | block `frequencyAndTotalEnergy` |
### SDM120 — `className: "Sdm120"` · `checkReachableRegister: "activePower"`
| addr | id | unité | groupé en block ? |
|---|---|---|---|
| 0 | `voltage` (30001) | V | non |
| 6 | `current` (30007) | A | non |
| 12 | `activePower` (30013) | W | non (sonde reachable) |
| 70 | `frequency` (30071) | Hz | block `frequencyAndTotalEnergy` |
| 72 | `totalEnergyConsumed` = Import active energy (30073) | kWh | block `frequencyAndTotalEnergy` |
| 74 | `totalEnergyProduced` = Export active energy (30075) | kWh | block `frequencyAndTotalEnergy` |
### SDM230 — `className: "Sdm230"` (JSON non relu, **adresses confirmées par datasheet V1.2**)
Layout **identique au SDM120** (monophasé) : voltage(0), current(6), activePower(12),
frequency(70), import→`totalEnergyConsumed`(72), export→`totalEnergyProduced`(74). Le
`sdm230-registers.json` doit donc être la copie du SDM120 avec `className: "Sdm230"` — à vérifier
sur le fichier réel.
> ⚠️ **Block reads = OK ici** (contrairement au piège V2C). Les `blocks` regroupent des registres
> **contigus et non chevauchants** (le SDM expose des floats sur des adresses pairement espacées).
> Le modbus-tool lit chaque block en une transaction groupée — comportement voulu et sûr sur SDM.
> Ne PAS « casser » les blocks en lectures individuelles pour imiter V2C : c'est inutile et ça
> multiplie les transactions sur un bus déjà partagé.
>
> 📏 **Limite de transaction par modèle** (datasheets) : SDM72 = **30 paramètres / 60 registres**
> max par requête ; SDM120 et SDM230 = **40 / 80**. Les blocks actuels sont très en dessous, mais
> à respecter si on ajoute des registres à un block (un block trop large lit aussi des registres
> réservés inutiles).
## 4bis. PARAMÈTRES SÉRIE DU COMPTEUR (réglés sur le compteur, vus dans les datasheets)
Confirmé par les manuels (utile au commissioning ; rappel : ces valeurs sont côté **compteur**,
le `ModbusRtuMaster` nymea doit matcher — cf. §2) :
- **Baud supportés** : SDM72D-M-2 = 1200/2400/4800/9600/**19200** (défaut **9600**) ;
SDM120 & SDM230 = 1200/2400/4800/9600 (défaut **2400**, **pas de 19200**).
- 🔴 **Cap bus = 9600** : un SDM120/SDM230 ne peut pas dépasser 9600. Le bus partagé est donc
plafonné à 9600 tant qu'un de ces modèles y est. Inutile d'espérer accélérer le bus au-delà.
- 🔧 **Commissioning** : un SDM120/SDM230 sorti d'usine est à **2400** → le passer à **9600** (et
régler son adresse Modbus unique) via le menu façade avant de le brancher au bus prod.
- **Parité/stop** : défaut **None / 1 stop**. Le bus prod est en **9600 8N1** → cohérent.
- **Word order** : MSR first (Big-endian) par défaut sur les trois ; SDM72 l'auto-détecte et ne
l'expose pas au menu. Le plugin suppose `BigEndian` → ne pas changer ce réglage sur le compteur.
## 5. SÉMANTIQUE DE REACHABILITY (lien direct avec les timeouts)
`errorLimitUntilNotReachable: 15` + `checkReachableRegister` définissent comment le plugin gère
les erreurs Modbus :
- Le générateur lit périodiquement le `checkReachableRegister` (`totalCurrentPower` pour SDM72,
`activePower` pour SDM120) pour juger si le device répond.
- Il faut **15 erreurs consécutives** (timeouts/exceptions) avant que le Thing bascule
`connected = false`. En dessous, les valeurs continuent d'être servies (d'où « parfois ça
répond, parfois rafales » sans que le Thing tombe forcément).
- 🔎 **Implication debug** : des rafales < 15 sont absorbées silencieusement côté state
`connected`, mais polluent les logs (`SdmXXModbusRtuConnection: ... TimeoutError`) et trouent la
mesure. Le seuil masque le symptôme, il ne le corrige pas. La cause se traite au niveau bus
7), pas en montant ce seuil.
## 6. DISCOVERY
- `createMethods: ["discovery"]`, `discoveryParamTypes: [ slaveAddress (int, défaut 1) ]`.
- La discovery sonde le bus (via le `ModbusRtuMaster` courant) à l'adresse demandée et tente de
lire le `checkReachableRegister` ; si ça répond, le device est proposé.
- La discovery **émet du trafic sur le bus partagé** : lancer une discovery pendant que les
SDM prod tournent ajoute des transactions. Pas bloquant, mais à savoir pour le diagnostic.
- *(Détail d'implémentation discovery choix du master, gestion multi-master, dédoublonnage
à lire dans `integrationplugineastron.cpp`, non couvert ici.)*
## 7. DISCIPLINE DE BUS RS485 — la cause racine n°1 des timeouts (OPÉRATIONNEL)
Cette section encode une leçon de prod (incident timeouts SDM apparu pendant une session de debug
v2c/Trydan avec `mbpoll` sur `/dev/ttyUSB0`). À lire avant tout diagnostic de timeout.
1. **Un seul maître.** Le RS485 est mono-maître ; nymead l'est. **Ne jamais lancer `mbpoll` /
`modpoll` sur `/dev/ttyUSB0` pendant que nymead tourne** deux maîtres collisions de trames
timeouts intermittents par rafales. Pour sonder au `mbpoll`, **arrêter nymead d'abord**
(`sudo systemctl stop nymead`), puis le relancer.
2. **Pas de process série résiduel.** Après une session de debug, vérifier qu'aucun `mbpoll`
zombie ne tient le port : `pgrep -a mbpoll ; sudo lsof /dev/ttyUSB0` (doit ne montrer que
nymead).
3. **Épingler le port par identité**, pas par `ttyUSBn` : le `ModbusRtuMaster` doit pointer un
`/dev/serial/by-id/usb-Silicon_Labs_CP2102...-if00-port0` (cp210x), sinon renommage possible
au reboot (ttyUSB0 ttyUSB1) si un 2e adaptateur est branché.
4. **Stabilité cp210x** : surveiller `dmesg | grep -i cp210x` (disconnect/reset) un adaptateur
qui décroche donne aussi des rafales, indépendamment du bus.
5. **Charge VM** : sous forte charge CPU, nymead peut ne pas tenir la cadence du master pseudo-
timeouts. Contributeur, rarement cause unique. **Mécanisme** (confirmé par le protocole Eastron,
§3.3) : une trame RTU doit être un **flux continu** ; un silence > **1,5 temps-caractère**
(≈ 1,6 ms à 9600 8N1) en milieu de trame fait **jeter** la trame par le SDM → pas de réponse →
timeout côté master. Le *latency timer* du cp210x (défaut **16 ms**) combiné au jitter
d'ordonnancement de la VM peut fragmenter une trame au-delà de ce seuil. Pistes : baisser le
latency timer du cp210x (`/sys/bus/usb-serial/devices/ttyUSB0/latency_timer`), réduire la charge,
ou prioriser nymead. À traiter seulement si le bus est propre par ailleurs (14 ci-dessus).
6. **Réglages bus** (au niveau master, pas plugin) : A/B non inversés, GND commun adaptateur↔SDM,
borniers serrés, terminaison 120 Ω peu critique sur bus court 2 nœuds à 9600. Topologie en
**daisy-chain**, pas d'étoile ni de stub (réflexions → corruption ; protocole §2.2).
## 8. CLASSES GÉNÉRÉES & LECTURE DES LOGS
Le modbus-tool génère, à partir du `className`, une classe de connexion `<ClassName>ModbusRtuConnection` :
- `Sdm72``Sdm72ModbusRtuConnection`
- `Sdm120``Sdm120ModbusRtuConnection`
D'où les lignes de log `Sdm72ModbusRtuConnection: ModbusRtu reply error ... TimeoutError`. Un log
préfixé par une de ces classes = couche transport générée, pas la glue du plugin. Utile pour
router un bug : préfixe `SdmXXModbusRtuConnection` → registre/transport/bus ; messages du plugin
`eastron` lui-même → setup/discovery/câblage des states.
## 9. POUR ÉTENDRE (ajouter un modèle ou un registre)
Workflow déclaratif, dans l'ordre :
1. Ajouter/éditer `<modele>-registers.json` (className unique, registres `inputRegister` float
Big-endian, choisir un `checkReachableRegister` qui répond toujours, garder les blocks
contigus). Réutiliser les conventions SDM72/SDM120 ci-dessus.
2. Ajouter la/les ThingClass(es) dans `integrationplugineastron.json` (3 variantes de rôle si
pertinent), avec les `paramTypes` `slaveAddress` + `modbusMasterUuid` (readOnly) et le state
`connected` (`cached: false`).
3. **Générer de nouveaux UUID** pour chaque nouvel id (plugin/thingclass/param/state). Ne jamais
réutiliser un UUID existant.
4. Régénérer via le modbus-tool nymea (cible du `.pro`), recompiler amd64 puis cross arm64.
5. Tester en mono-maître (nymead arrêté, `mbpoll -1 ...`) que le device répond, puis via nymead.
6. Mettre à jour `PORTING_STATUS*` / doc catalogue (badge canal/origine/stabilité) si applicable.
## 10. INTERDIT
- **Éditer les classes de connexion générées** (`SdmXXModbusRtuConnection`) à la main — elles
sont régénérées, toute modif sera écrasée. Corriger dans les `*-registers.json`.
- **Mettre la config série (baud/parité/timeout/retries) dans le plugin** — elle appartient au
`ModbusRtuMaster` nymea (§2).
- **Ajouter de l'écriture** (action/registre `access: WO/RW`) — un SDM est read-only.
- **Changer/réutiliser les UUID** de ThingClasses ou de plugin publiés (casse l'appairage des
Things existants en prod).
- **Casser les blocks en lectures individuelles** pour copier le pattern V2C — non pertinent ici
(registres contigus, pas de chevauchement firmware).
- **Lancer `mbpoll` sur le bus pendant que nymead tourne** (§7).
- **Monter `errorLimitUntilNotReachable` pour « régler » des timeouts** — masque, ne corrige pas.
- **Toucher aux autres plugins du repo** (`abbterra`, `abbb2x`, `waveshare-relay-d8`, …).
## 11. BUILD / DEPLOY
- Paquet `eastron` versionné `1.15.0+etm3`. Cross-compilation arm64 via le container
`build-cross-arm64` (Debian trixie amd64 + `crossbuild-essential-arm64`).
- Publié sur les canaux APT `powersync-nightly` / `powersync-testing` / `powersync-stable`
(reprepro, GPG `77033A6E…9D1B4986`). Recette complète dans `DEPLOY.md` du projet.
- Déployé sur le RPi `hems` (192.168.1.75, canal `powersync-testing`) et en prod sur
`etm-nymea-dev` (192.168.1.120).
## 12. DEFINITION OF DONE (pour une modif de ce plugin)
1. Le plugin compile (amd64 + cross arm64) et reste cohérent avec le `.pro`.
2. Toute nouvelle valeur passe par un `*-registers.json` (déclaratif), pas par du code à la main.
3. Reachability conservée : `checkReachableRegister` valide, `errorLimitUntilNotReachable` inchangé
sans justification.
4. UUID neufs pour tout nouvel élément ; UUID existants intacts.
5. Testé d'abord en **mono-maître** (nymead arrêté) au `mbpoll -1`, puis intégré via nymead, sur
le bus réel.
6. États visibles et cohérents dans `nymea-app` (valeurs qui évoluent, `connected=true` stable).
7. `PORTING_STATUS*` / doc catalogue à jour si le périmètre modèle change.
## 13. À VÉRIFIER DANS LE CODE (non lu dans ce brief)
Honnêteté sur le périmètre lu : ce brief s'appuie sur les 2 cartes de registres (SDM72/SDM120) +
le JSON du plugin + les 3 protocoles constructeur (SDM72/SDM120/SDM230). Restent à confirmer en
lisant le dépôt :
- `integrationplugineastron.cpp`/`.h` : logique de discovery, sélection du `ModbusRtuMaster`,
câblage des states, gestion de la reconnexion.
- `eastron.pro` : invocation exacte du modbus-tool, liste des `*-registers.json` compilés.
- `meta.json` : packaging (nom, deps, version).
- `sdm230-registers.json` : **adresses confirmées par le datasheet V1.2** (= SDM120) ; reste à
vérifier que le fichier JSON existe bien et porte `className: "Sdm230"`.
- `sdm220-registers.json`, `sdm630-registers.json` : **non vérifiés** — ni le JSON ni le datasheet
fournis (SDM630 = 3φ riche type SDM72, SDM220 = 1φ type SDM120/230). Vérifier adresses,
`checkReachableRegister` et blocks avant de s'y fier.
- Variante réellement instanciée pour le SDM72 (principal) et le SDM120 (PV) sur `etm-nymea-dev`
(cf. §3) — à lire sur le Thing réel, pas à supposer.
## 14. RÉFÉRENCES
- Source plugin : `git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus`, dossier
`eastron/` (commit `b1da668`).
- Cartes registres : `eastron/sdm72-registers.json`, `eastron/sdm120-registers.json` (+ 630/220/230).
- ThingClasses/interfaces/states : `eastron/integrationplugineastron.json`.
- Modèle de structure dans le repo : `abbterra` (evcharger Modbus TCP), `eastron` lui-même
(pattern registres RTU déclaratif) — cités comme références dans le brief V2C.
- Datasheets/protocoles constructeur : Eastron SDM72D-M-2 User Manual V1.1.1 (2023, meter code
`00 89`), SDM120-Modbus Protocol V2.4, SDM230-Modbus User Manual + Protocol V1.2. Registres
input float IEEE754 Big-endian (MSR first), FC04 ; limites de transaction 30 (SDM72) / 40
(SDM120/230) paramètres ; baud max 9600 (SDM120/230) vs 19200 (SDM72).