# 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).