Add AGENTS.md

This commit is contained in:
Patrick Schurig 2026-06-13 14:10:51 +02:00
parent 4a2e5ce9e1
commit 577535d2b2

389
eastron/AGENTS.md Normal file
View File

@ -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 = 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).