From 577535d2b2b38d682b143cc6d9068628355bf50d Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 13 Jun 2026 14:10:51 +0200 Subject: [PATCH] Add AGENTS.md --- eastron/AGENTS.md | 389 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100644 eastron/AGENTS.md diff --git a/eastron/AGENTS.md b/eastron/AGENTS.md new file mode 100644 index 0000000..aec86f6 --- /dev/null +++ b/eastron/AGENTS.md @@ -0,0 +1,389 @@ +# 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 = import−export) ; 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 (1–4 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 `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 `-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).