feat: docs-as-code — générateur, literate-nav, badges, CI Gitea Actions

- PORTING_STATUS.yaml : source de vérité canal APT + placement nav
- scripts/gen_device_reference.py : génération matrice + fiches + SUMMARY.md
  depuis integrationplugin*.json + meta.json ; nightly sans JSON = invisible
- mkdocs.yml : plugin literate-nav, nav 6 sections, Appareils via SUMMARY.md
- .gitea/workflows/docs.yml : CI complet — fetch JSON (branche auto-détectée),
  génération, build --strict, check idempotence, rsync deploy
- Badges HTML (stable/testing/nightly + consumer/community + ok/part/road)
- Fiches appareils : Eastron, ABB B2x, ABB Terra, Keba, Waveshare
- requirements.txt : mkdocs-material, mkdocs-literate-nav, PyYAML

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-02 08:53:54 +02:00
parent 394d8bc54a
commit 9eee067829
25 changed files with 1434 additions and 84 deletions

94
.gitea/workflows/docs.yml Normal file
View File

@ -0,0 +1,94 @@
name: Build & Deploy docs
on:
push:
branches: [main]
schedule:
- cron: '0 3 * * *' # mise à jour nocturne (meta.json des repos plugins)
workflow_dispatch:
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: pip install -r requirements.txt
# ── Récupération des JSON depuis les 5 repos drivers ─────────────────
- name: Fetch plugin JSON files
env:
GITEA_TOKEN: ${{ secrets.MKDOCS_TOKEN }}
run: |
GITEA_BASE="https://git.etm-powersync.fr"
AUTH_BASE="https://pakutz79:${GITEA_TOKEN}@git.etm-powersync.fr"
mkdir -p .plugins-src
for repo in etm-powersync-plugins etm-powersync-plugins-modbus \
nymea-plugins nymea-plugins-modbus nymea-generic; do
# Branche par défaut via API Gitea (pas de hardcoding main/master)
BRANCH=$(curl -sf \
-H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_BASE}/api/v1/repos/ETM-Schurig/${repo}" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('default_branch','main'))" \
2>/dev/null) || BRANCH="main"
echo "→ ${repo} (branche: ${BRANCH})"
git clone --depth 1 --branch "${BRANCH}" \
"${AUTH_BASE}/ETM-Schurig/${repo}.git" \
".plugins-src/${repo}" \
|| echo "WARNING: ${repo} introuvable ou inaccessible — ignoré"
done
# ── Génération de la doc ──────────────────────────────────────────────
- name: Generate device reference + SUMMARY.md
run: |
python3 scripts/gen_device_reference.py \
--src .plugins-src \
--docs docs \
--lang fr
# ── Build MkDocs ──────────────────────────────────────────────────────
- name: MkDocs build --strict
run: mkdocs build --strict
# ── Vérification idempotence ──────────────────────────────────────────
- name: Check generated content is up-to-date
run: |
python3 scripts/gen_device_reference.py \
--src .plugins-src \
--docs docs \
--lang fr \
--check
# ── SSH ───────────────────────────────────────────────────────────────
- name: Setup SSH deploy key
env:
SSH_KEY: ${{ secrets.DOCS_DEPLOY_SSH_KEY }}
DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
run: |
mkdir -p ~/.ssh
printf '%s\n' "${SSH_KEY}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${DEPLOY_HOST}" >> ~/.ssh/known_hosts
# ── Déploiement ───────────────────────────────────────────────────────
- name: Deploy via rsync
env:
DEPLOY_USER: ${{ secrets.DOCS_DEPLOY_USER }}
DEPLOY_HOST: ${{ secrets.DOCS_DEPLOY_HOST }}
DEPLOY_PATH: ${{ secrets.DOCS_DEPLOY_PATH }}
run: |
rsync -az --delete \
-e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=yes" \
site/ \
"${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}"

3
.gitignore vendored
View File

@ -1 +1,4 @@
.venv/ .venv/
site/
.plugins-src/
.claude/

63
PORTING_STATUS.yaml Normal file
View File

@ -0,0 +1,63 @@
# Source de vérité : canal APT + placement nav.
# title/tagline/stability/icon → meta.json du repo plugin.
# slug : nom de fichier de la fiche (défaut = plugin).
- repo: etm-powersync-plugins-modbus
plugin: eastron
name: Eastron SDM
channel: stable
category: compteur
- repo: etm-powersync-plugins-modbus
plugin: abbb2x
slug: abb-b2x
name: Compteur ABB B2x
channel: testing
category: compteur
- repo: etm-powersync-plugins-modbus
plugin: waveshare-relay-d8
slug: waveshare
name: Waveshare relais
channel: testing
category: smartdevice
- repo: etm-powersync-plugins-modbus
plugin: abbterra
slug: abb-terra
name: Borne ABB Terra AC
channel: testing
category: irve
- repo: nymea-plugins
plugin: keba
name: Keba
channel: nightly
category: irve
- repo: nymea-plugins
plugin: fronius
name: Fronius
channel: nightly
category: onduleur
- repo: nymea-plugins
plugin: daikinairco
name: Daikin
channel: nightly
category: hvac
subcategory: climatisation
- repo: nymea-plugins
plugin: sgready
name: SG-Ready
channel: nightly
category: hvac
subcategory: pac
- repo: nymea-plugins
plugin: simpleheatpump
name: SimpleHeatpump
channel: nightly
category: hvac
subcategory: pac

15
docs/appareils/SUMMARY.md Normal file
View File

@ -0,0 +1,15 @@
* [Compatibilité](compatibilite.md)
* [Compteurs](compteurs/index.md)
* [Eastron SDM](compteurs/eastron.md)
* [Compteur ABB B2x](compteurs/abb-b2x.md)
* [Bornes de recharge](bornes/index.md)
* [Borne ABB Terra AC](bornes/abb-terra.md)
* [Keba](bornes/keba.md)
* [SmartDevices](smart/index.md)
* [Waveshare relais](smart/waveshare.md)
* [HVAC](hvac/index.md)
* [Daikin](hvac/daikinairco.md)
* [SG-Ready](hvac/sgready.md)
* [SimpleHeatpump](hvac/simpleheatpump.md)
* [Onduleurs / PV](onduleurs/index.md)
* [Fronius](onduleurs/fronius.md)

View File

@ -1,3 +0,0 @@
# Bornes de recharge
> Stub — détail des bornes supportées, registres Modbus, particularités d'intégration.

View File

@ -0,0 +1,104 @@
# Borne ABB Terra AC
<span class="badge testing">TESTING</span> <span class="badge consumer">CONSUMER</span>
La borne de recharge ABB Terra AC existe en deux variantes de communication :
**Modbus TCP** (réseau Ethernet/LAN) et **Modbus RTU** (bus RS485).
## 1. Choix de la variante
- **TCP** : la borne est sur le réseau local, repérée par son adresse IP /
nom d'hôte. Ajout par découverte réseau.
- **RTU** : la borne est sur un bus RS485, repérée par son adresse esclave.
## 2. Raccordement
- TCP : câble Ethernet vers le réseau du hub.
- RTU : A↔A, B↔B, masse, terminaison 120 Ω.
## 3. Ajout dans PowerSync
Variante TCP : **découverte automatique** sur le réseau, ou ajout **manuel**
(IP, port). Variante RTU : ajout via le maître RTU + adresse esclave.
Voir [Ajouter un appareil](../../installation/application.md).
## 4. Vérification
`connected`, `pluggedIn` puis `charging` reflètent l'état de la session ;
`maxChargingCurrent` reflète la consigne de courant.
---
## Référence
<!-- BEGIN GENERATED: integrationpluginabbterra.json -->
**Fabricant :** ABB
**Plugin :** `AbbTerra`
#### Modèles pris en charge
| Modèle | Rôle | Transport | Ajout | Grandeurs |
| --- | --- | --- | --- | --- |
| **Terra AC Charger (TCP)** | Borne de recharge | Modbus TCP | Découverte automatique / Ajout manuel | 17 |
| **Terra AC Charger (RTU)** | Borne de recharge | Modbus RTU | Découverte automatique / Ajout manuel | 17 |
#### Détail par modèle
??? abstract "Terra AC Charger (TCP) — `terraAcTcp`"
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `macAddress` | MAC address | QString | — | — | oui |
| `address` | Host address | QString | — | — | non |
| `hostName` | Host name | QString | — | — | non |
| `port` | Port | uint | — | `502` | non |
| `slaveId` | Slave ID | uint | 1255 | `1` | non |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `pluggedIn` | Plugged in | bool | — |
| `charging` | Charging | bool | — |
| `power` | Charging enabled | bool | — |
| `maxChargingCurrent` | Maximum charging current | double | Ampere |
| `phaseCount` | Phase count | uint | — |
| `currentPower` | Active power | double | Watt |
| `currentPhase1` | Current phase 1 | double | Ampere |
| `currentPhase2` | Current phase 2 | double | Ampere |
| `currentPhase3` | Current phase 3 | double | Ampere |
| `voltagePhase1` | Voltage phase 1 | double | Volt |
| `voltagePhase2` | Voltage phase 2 | double | Volt |
| `voltagePhase3` | Voltage phase 3 | double | Volt |
| `sessionEnergy` | Session energy | double | KiloWattHour |
| `firmwareVersion` | Firmware version | QString | — |
| `serialNumber` | Serial number | QString | — |
| `errorCode` | Error code | uint | — |
??? abstract "Terra AC Charger (RTU) — `terraAcRtu`"
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `rtuMaster` | Modbus RTU master | QString | — | — | non |
| `slaveId` | Modbus slave ID | uint | 1247 | `1` | non |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `pluggedIn` | Plugged in | bool | — |
| `charging` | Charging | bool | — |
| `power` | Charging enabled | bool | — |
| `maxChargingCurrent` | Maximum charging current | double | Ampere |
| `phaseCount` | Phase count | uint | — |
| `currentPower` | Active power | double | Watt |
| `currentPhase1` | Current phase 1 | double | Ampere |
| `currentPhase2` | Current phase 2 | double | Ampere |
| `currentPhase3` | Current phase 3 | double | Ampere |
| `voltagePhase1` | Voltage phase 1 | double | Volt |
| `voltagePhase2` | Voltage phase 2 | double | Volt |
| `voltagePhase3` | Voltage phase 3 | double | Volt |
| `sessionEnergy` | Session energy | double | KiloWattHour |
| `firmwareVersion` | Firmware version | QString | — |
| `serialNumber` | Serial number | QString | — |
| `errorCode` | Error code | uint | — |
<!-- END GENERATED -->

View File

@ -0,0 +1,8 @@
# Bornes de recharge
Bornes EVSE intégrées dans ETM PowerSync pour la recharge pilotée des véhicules électriques.
| Appareil | Protocole | Canal | Stabilité |
|---|---|---|---|
| [Borne ABB Terra AC](abb-terra.md) | Modbus TCP / RTU | <span class="badge testing">TESTING</span> | <span class="badge consumer">CONSUMER</span> |
| [Keba](keba.md) | Modbus TCP | <span class="badge nightly">NIGHTLY</span> | <span class="badge community">COMMUNITY</span> |

View File

@ -0,0 +1,61 @@
# Keba
<span class="badge nightly">NIGHTLY</span> <span class="badge community">COMMUNITY</span>
Bornes de recharge Keba (séries P30, P31), communication **Modbus TCP** via réseau local.
## 1. Matériel requis
- Borne Keba P30 ou P31
- Connexion Ethernet vers le réseau du hub
## 2. Activation Modbus TCP
Modbus TCP est **désactivé par défaut** sur les bornes Keba.
Activez-le dans l'interface web de la borne (port `502`).
!!! warning "Activation Modbus"
Sans cette étape, la borne ne répondra pas à PowerSync.
## 3. Ajout dans PowerSync
Ajout par **découverte automatique** sur le réseau, ou **manuel** (IP, port 502).
Voir [Ajouter un appareil](../../installation/application.md).
## 4. Vérification
`connected`, `pluggedIn` puis `charging` reflètent l'état de la session.
---
## Référence
<!-- BEGIN GENERATED: integrationpluginkeba.json -->
**Fabricant :** Keba
**Plugin :** `keba`
#### Modèles pris en charge
| Modèle | Rôle | Transport | Ajout | Grandeurs |
| --- | --- | --- | --- | --- |
| **Keba P30 / P31** | Borne de recharge | Modbus TCP | Découverte automatique / Ajout manuel | 6 |
#### Détail par modèle
??? abstract "Keba P30 / P31 — `kebaEVCharger`"
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `macAddress` | MAC address | QString | — | — | oui |
| `address` | Host address | QString | — | — | non |
| `port` | Port | uint | — | `502` | non |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `pluggedIn` | Plugged in | bool | — |
| `charging` | Charging | bool | — |
| `maxChargingCurrent` | Maximum charging current | double | Ampere |
| `currentPower` | Active power | double | Watt |
| `sessionEnergy` | Session energy | double | KiloWattHour |
<!-- END GENERATED -->

View File

@ -1,39 +1,47 @@
# Compatibilité # Compatibilité
Liste de référence des équipements. **Source de vérité** : générée à partir du suivi de Liste de référence des équipements supportés par ETM PowerSync. Générée depuis
portage (`PORTING_STATUS`). Statuts : [Supporté]{.badge .ok} · `PORTING_STATUS.yaml` — ne pas modifier manuellement entre les marqueurs.
[Partiel]{.badge .part} · [Roadmap]{.badge .road}.
!!! warning "À automatiser" | Badge | Signification |
Cette page sera générée depuis `PORTING_STATUS` pour éviter la double saisie. |---|---|
Le tableau ci-dessous est un point de départ manuel. | <span class="badge stable">STABLE</span> | Disponible sur le dépôt APT **stable** — prêt pour la production |
| <span class="badge testing">TESTING</span> | Disponible sur le dépôt APT **testing** — fonctionnel, en cours d'épreuve |
## Bornes de recharge (EVSE) | <span class="badge nightly">NIGHTLY</span> | Portage en cours — non disponible en production |
| Marque / Modèle | Protocole | Statut |
|---|---|---|
| Keba | Modbus TCP | [Partiel]{.badge .part} |
<!-- BEGIN GENERATED: __matrix__ -->
## Compteurs ## Compteurs
| Marque / Modèle | Protocole | Statut | | Marque / Modèle | Protocole | Canal | Stabilité |
|---|---|---| |---|---|---|---|
| Eastron SDM | Modbus RTU | [Supporté]{.badge .ok} | | Eastron SDM | Modbus RTU | <span class="badge stable">STABLE</span> | <span class="badge consumer">CONSUMER</span> |
| Waveshare (relais) | Modbus | [Supporté]{.badge .ok} | | Compteur ABB B2x | Modbus RTU | <span class="badge testing">TESTING</span> | <span class="badge consumer">CONSUMER</span> |
## Bornes de recharge
| Marque / Modèle | Protocole | Canal | Stabilité |
|---|---|---|---|
| Borne ABB Terra AC | Modbus TCP | <span class="badge testing">TESTING</span> | <span class="badge consumer">CONSUMER</span> |
| Keba | Modbus TCP | <span class="badge nightly">NIGHTLY</span> | <span class="badge consumer">CONSUMER</span> |
## SmartDevices
| Marque / Modèle | Protocole | Canal | Stabilité |
|---|---|---|---|
| Waveshare relais | Modbus RTU | <span class="badge testing">TESTING</span> | <span class="badge consumer">CONSUMER</span> |
## HVAC
| Marque / Modèle | Protocole | Canal | Stabilité |
|---|---|---|---|
| Daikin | — | <span class="badge nightly">NIGHTLY</span> | <span class="badge consumer">CONSUMER</span> |
| SG-Ready | — | <span class="badge nightly">NIGHTLY</span> | <span class="badge consumer">CONSUMER</span> |
| SimpleHeatpump | — | <span class="badge nightly">NIGHTLY</span> | <span class="badge consumer">CONSUMER</span> |
## Onduleurs / PV ## Onduleurs / PV
| Marque / Modèle | Protocole | Statut | | Marque / Modèle | Protocole | Canal | Stabilité |
|---|---|---| |---|---|---|---|
| SMA | Modbus / SunSpec | [Partiel]{.badge .part} | | Fronius | Modbus TCP | <span class="badge nightly">NIGHTLY</span> | <span class="badge consumer">CONSUMER</span> |
| Fronius | Modbus / SunSpec | [Roadmap]{.badge .road} |
| Huawei | Modbus | [Roadmap]{.badge .road} |
| SolarEdge | Modbus | [Roadmap]{.badge .road} |
## Batteries / ESS <!-- END GENERATED -->
| Marque / Modèle | Protocole | Statut |
|---|---|---|
| Victron | Modbus / MQTT | [Roadmap]{.badge .road} |
> Stub — compléter à partir du suivi de portage réel.

View File

@ -1,3 +0,0 @@
# Compteurs
> Stub — compteurs d'énergie supportés (Eastron SDM, etc.), câblage, configuration Modbus.

View File

@ -0,0 +1,71 @@
# Compteur ABB B2x
<span class="badge testing">TESTING</span> <span class="badge consumer">CONSUMER</span>
Le compteur d'énergie ABB B2x communique en **Modbus RTU** sur le bus RS485 du
hub. Mesure triphasée (tensions, courants et puissances par phase).
## 1. Matériel requis
- Compteur ABB B2x
- Adaptateur USB↔RS485 côté hub
- Câble bus 2 fils (A/B) + masse
## 2. Raccordement RS485
A↔A, B↔B, masse commune, terminaison **120 Ω** en bout de bus.
## 3. Adressage Modbus
Adresse esclave unique sur le bus (`1``254`, défaut `1`).
## 4. Ajout dans PowerSync
Ajout par **découverte automatique** ou **manuel** (saisie de l'adresse esclave).
Voir [Ajouter un appareil](../../installation/application.md).
---
## Référence
<!-- BEGIN GENERATED: integrationpluginabbb2x.json -->
**Fabricant :** ABB
**Plugin :** `AbbB2x`
#### Modèles pris en charge
| Modèle | Rôle | Transport | Ajout | Grandeurs |
| --- | --- | --- | --- | --- |
| **ABB B2x energy meter** | Compteur d'énergie | Modbus RTU | Découverte automatique / Ajout manuel | 14 |
#### Détail par modèle
??? abstract "ABB B2x energy meter — `abbB2x`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | 1254 | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | 1254 | `1` | oui |
| `modbusMasterUuid` | Modbus RTU master | QString | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
| `voltagePhaseA` | Voltage phase A | double | Volt |
| `voltagePhaseB` | Voltage phase B | double | Volt |
| `voltagePhaseC` | Voltage phase C | double | Volt |
| `currentPhaseA` | Current phase A | double | Ampere |
| `currentPhaseB` | Current phase B | double | Ampere |
| `currentPhaseC` | Current phase C | double | Ampere |
| `currentPowerPhaseA` | Current power phase A | double | Watt |
| `currentPowerPhaseB` | Current power phase B | double | Watt |
| `currentPowerPhaseC` | Current power phase C | double | Watt |
<!-- END GENERATED -->

View File

@ -0,0 +1,407 @@
# Compteurs Eastron (SDM)
<span class="badge stable">STABLE</span> <span class="badge consumer">CONSUMER</span>
Les compteurs Eastron de la série SDM (SDM72, SDM120, SDM220, SDM230, SDM630)
communiquent en **Modbus RTU** sur le bus RS485 du hub. Selon le modèle, ils
mesurent un raccordement monophasé ou triphasé et peuvent être affectés à trois
rôles : compteur général, compteur de consommation ou compteur de production.
## 1. Matériel requis
- Le compteur Eastron (modèle selon le besoin de mesure)
- Un adaptateur USB↔RS485 côté hub
- Câble bus 2 fils torsadés (A/B) + masse
## 2. Raccordement RS485
Relier A↔A, B↔B entre l'adaptateur et le compteur, masse commune. Placer une
résistance de terminaison **120 Ω** à chaque extrémité du bus.
!!! warning "À valider sur votre banc"
Vitesse (baudrate) et parité par défaut du compteur : à confirmer dans le
menu de l'appareil avant mise en service.
## 3. Adressage Modbus
Chaque appareil du bus doit avoir une **adresse esclave unique** (réglée via le
menu du compteur). L'adresse par défaut est `1`. Si plusieurs Eastron partagent
le bus, attribuez `1`, `2`, `3`, …
## 4. Ajout et configuration dans l'application
L'ajout se fait depuis l'application, en mode installateur — voir
[Ajouter un appareil](../../installation/application.md). Pour l'Eastron :
découverte sur le bus, sélection du modèle, puis choix du **rôle** (compteur
général / consommation / production).
## 5. Vérification
Une fois ajouté, l'état `connected` passe à vrai et les grandeurs (`currentPower`,
etc.) se mettent à jour. En cas d'absence de données : vérifier câblage A/B,
adresse esclave et terminaison.
---
## Référence {#reference}
<!-- BEGIN GENERATED: integrationplugineastron.json -->
**Fabricant :** Eastron
**Plugin :** `eastron`
#### Modèles pris en charge
| Modèle | Rôle | Transport | Ajout | Grandeurs |
| --- | --- | --- | --- | --- |
| **SDM630 — Energy Meter** | Compteur d'énergie | Modbus RTU | Découverte automatique | 20 |
| **SDM630 — Consumer Meter** | Compteur de consommation | Modbus RTU | Découverte automatique | 4 |
| **SDM630 — Producer Meter** | Compteur de production | Modbus RTU | Découverte automatique | 4 |
| **SDM72 — Energy Meter** | Compteur d'énergie | Modbus RTU | Découverte automatique | 14 |
| **SDM72 — Consumer Meter** | Compteur de consommation | Modbus RTU | Découverte automatique | 4 |
| **SDM72 — Producer Meter** | Compteur de production | Modbus RTU | Découverte automatique | 4 |
| **SDM120 — Energy Meter** | Compteur d'énergie | Modbus RTU | Découverte automatique | 7 |
| **SDM120 — Consumer Meter** | Compteur de consommation | Modbus RTU | Découverte automatique | 4 |
| **SDM120 — Producer Meter** | Compteur de production | Modbus RTU | Découverte automatique | 4 |
| **SDM220 — Energy Meter** | Compteur d'énergie | Modbus RTU | Découverte automatique | 7 |
| **SDM220 — Consumer Meter** | Compteur de consommation | Modbus RTU | Découverte automatique | 4 |
| **SDM220 — Producer Meter** | Compteur de production | Modbus RTU | Découverte automatique | 4 |
| **SDM230 — Energy Meter** | Compteur d'énergie | Modbus RTU | Découverte automatique | 7 |
| **SDM230 — Consumer Meter** | Compteur de consommation | Modbus RTU | Découverte automatique | 4 |
| **SDM230 — Producer Meter** | Compteur de production | Modbus RTU | Découverte automatique | 4 |
#### Détail par modèle
??? abstract "SDM630 — Energy Meter — `sdm630`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `voltagePhaseA` | Voltage phase A | double | Volt |
| `voltagePhaseB` | Voltage phase B | double | Volt |
| `voltagePhaseC` | Voltage phase C | double | Volt |
| `currentPhaseA` | Current phase A | double | Ampere |
| `currentPhaseB` | Current phase B | double | Ampere |
| `currentPhaseC` | Current phase C | double | Ampere |
| `currentPower` | Current power | double | Watt |
| `currentPowerPhaseA` | Current power phase A | double | Watt |
| `currentPowerPhaseB` | Current power phase B | double | Watt |
| `currentPowerPhaseC` | Current power phase C | double | Watt |
| `frequency` | Frequency | double | Hertz |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `energyConsumedPhaseA` | Energy consumed phase A | double | KiloWattHour |
| `energyConsumedPhaseB` | Energy consumed phase B | double | KiloWattHour |
| `energyConsumedPhaseC` | Energy consumed phase C | double | KiloWattHour |
| `energyProducedPhaseA` | Energy produced phase A | double | KiloWattHour |
| `energyProducedPhaseB` | Energy produced phase B | double | KiloWattHour |
| `energyProducedPhaseC` | Energy produced phase C | double | KiloWattHour |
??? abstract "SDM630 — Consumer Meter — `sdm630Consumer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM630 — Producer Meter — `sdm630Producer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM72 — Energy Meter — `sdm72`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `voltagePhaseA` | Voltage phase A | double | Volt |
| `voltagePhaseB` | Voltage phase B | double | Volt |
| `voltagePhaseC` | Voltage phase C | double | Volt |
| `currentPhaseA` | Current phase A | double | Ampere |
| `currentPhaseB` | Current phase B | double | Ampere |
| `currentPhaseC` | Current phase C | double | Ampere |
| `currentPower` | Current power | double | Watt |
| `currentPowerPhaseA` | Current power phase A | double | Watt |
| `currentPowerPhaseB` | Current power phase B | double | Watt |
| `currentPowerPhaseC` | Current power phase C | double | Watt |
| `frequency` | Frequency | double | Hertz |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
??? abstract "SDM72 — Consumer Meter — `sdm72Consumer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM72 — Producer Meter — `sdm72Producer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM120 — Energy Meter — `sdm120`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `voltagePhaseA` | Voltage | double | Volt |
| `currentPhaseA` | Current | double | Ampere |
| `currentPower` | Current power | double | Watt |
| `frequency` | Frequency | double | Hertz |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
??? abstract "SDM120 — Consumer Meter — `sdm120Consumer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM120 — Producer Meter — `sdm120Producer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM220 — Energy Meter — `sdm220`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `voltagePhaseA` | Voltage | double | Volt |
| `currentPhaseA` | Current | double | Ampere |
| `currentPower` | Current power | double | Watt |
| `frequency` | Frequency | double | Hertz |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
??? abstract "SDM220 — Consumer Meter — `sdm220Consumer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM220 — Producer Meter — `sdm220Producer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM230 — Energy Meter — `sdm230`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `voltagePhaseA` | Voltage | double | Volt |
| `currentPhaseA` | Current | double | Ampere |
| `currentPower` | Current power | double | Watt |
| `frequency` | Frequency | double | Hertz |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
??? abstract "SDM230 — Consumer Meter — `sdm230Consumer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyConsumed` | Total energy consumed | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
??? abstract "SDM230 — Producer Meter — `sdm230Producer`"
_Paramètres de découverte :_
| Clé | Libellé | Type | Plage | Défaut |
| --- | --- | --- | --- | --- |
| `slaveAddress` | Slave address | int | — | `1` |
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `slaveAddress` | Modbus slave address | uint | — | `1` | non |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `currentPower` | Current power | double | Watt |
| `totalEnergyProduced` | Total energy produced | double | KiloWattHour |
| `frequency` | Frequency | double | Hertz |
<!-- END GENERATED -->

View File

@ -0,0 +1,8 @@
# Compteurs
Compteurs d'énergie supportés par ETM PowerSync via Modbus RTU.
| Appareil | Protocole | Canal | Stabilité |
|---|---|---|---|
| [Eastron SDM](eastron.md) | Modbus RTU | <span class="badge stable">STABLE</span> | <span class="badge consumer">CONSUMER</span> |
| [Compteur ABB B2x](abb-b2x.md) | Modbus RTU | <span class="badge testing">TESTING</span> | <span class="badge consumer">CONSUMER</span> |

View File

@ -1,10 +1,9 @@
# Appareils # Appareils
ETM PowerSync communique avec les équipements via Modbus (TCP/RTU), MQTT et protocoles ETM PowerSync communique avec les équipements via Modbus (TCP/RTU) et protocoles
propriétaires, grâce aux plugins du dépôt propriétaires, grâce aux plugins embarqués.
[powersync-plugins](https://github.com/etmschurig/powersync-plugins).
- [Compatibilité](compatibilite.md) — liste de référence filtrable - [Compatibilité](compatibilite.md) — matrice complète avec canaux APT
- [Bornes de recharge](bornes.md) - [Compteurs](compteurs/index.md) — Eastron SDM, ABB B2x
- [Compteurs](compteurs.md) - [Bornes de recharge](bornes/index.md) — ABB Terra AC, Keba
- [Onduleurs / PV](onduleurs.md) - [SmartDevices](smart/index.md) — Waveshare

View File

@ -1,3 +0,0 @@
# Onduleurs / PV
> Stub — onduleurs et données PV (SunSpec/Modbus), lecture de production.

View File

@ -0,0 +1,7 @@
# SmartDevices
Modules de pilotage de charges supportés par ETM PowerSync.
| Appareil | Protocole | Canal | Stabilité |
|---|---|---|---|
| [Waveshare relais](waveshare.md) | Modbus RTU | <span class="badge testing">TESTING</span> | <span class="badge consumer">CONSUMER</span> |

View File

@ -0,0 +1,64 @@
# Waveshare relais
<span class="badge testing">TESTING</span> <span class="badge consumer">CONSUMER</span>
Module de relais Waveshare (modèle 8 canaux RS485), pilotage via **Modbus RTU**.
## 1. Matériel requis
- Module Waveshare 8-Channel Relay (RS485)
- Adaptateur USB↔RS485 côté hub
- Alimentation 12 V DC + câble bus (A/B)
## 2. Raccordement RS485
A↔A, B↔B, masse commune, terminaison **120 Ω** en bout de bus.
Alimentation 12 V DC sur les bornes VCC/GND du module.
## 3. Adressage Modbus
Adresse par défaut : **1**. Configurable via l'utilitaire Waveshare ou par
commande Modbus de changement d'adresse.
Paramètres : **9600 bps, 8N1**.
## 4. Ajout dans PowerSync
Ajout manuel (port série, adresse esclave).
Voir [Ajouter un appareil](../../installation/application.md).
---
## Référence
<!-- BEGIN GENERATED: integrationpluginwaveshare-relay-d8.json -->
**Fabricant :** Waveshare
**Plugin :** `waveshare-relay-d8`
#### Modèles pris en charge
| Modèle | Rôle | Transport | Ajout | Grandeurs |
| --- | --- | --- | --- | --- |
| **Waveshare 8-Channel Relay (RS485)** | — | Modbus RTU | Ajout manuel | 9 |
#### Détail par modèle
??? abstract "Waveshare 8-Channel Relay (RS485) — `waveshareRelayD8`"
_Réglages :_
| Clé | Libellé | Type | Plage | Défaut | Lecture seule |
| --- | --- | --- | --- | --- | --- |
| `modbusMasterUuid` | Modbus RTU master | QUuid | — | — | oui |
| `slaveAddress` | Slave address | uint | 1247 | `1` | non |
_Grandeurs mesurées :_
| Clé | Grandeur | Type | Unité |
| --- | --- | --- | --- |
| `connected` | Connected | bool | — |
| `relay1` | Relay 1 | bool | — |
| `relay2` | Relay 2 | bool | — |
| `relay3` | Relay 3 | bool | — |
| `relay4` | Relay 4 | bool | — |
| `relay5` | Relay 5 | bool | — |
| `relay6` | Relay 6 | bool | — |
| `relay7` | Relay 7 | bool | — |
| `relay8` | Relay 8 | bool | — |
<!-- END GENERATED -->

View File

@ -1,8 +0,0 @@
# API REST / MQTT [Supporté]{.badge .ok}
ETM PowerSync expose l'API JSON-RPC de nymea, permettant l'intégration domotique
(Home Assistant, etc.). Vos données restent accessibles, en local.
Voir [Intégrations](../integrations/rest-api.md) pour les détails.
> Stub — documenter les endpoints principaux.

View File

@ -4,14 +4,14 @@ Vue d'ensemble des fonctions du HEMS et de leur état réel.
| Fonction | État | | Fonction | État |
|---|---| |---|---|
| [Surplus solaire](surplus-solaire.md) | [Partiel]{.badge .part} | | [Surplus solaire](surplus-solaire.md) | <span class="badge testing">EN COURS</span> |
| [Délestage / Load management](delestage.md) | [Partiel]{.badge .part} | | [Délestage / Load management](delestage.md) | <span class="badge testing">EN COURS</span> |
| [Gestion batterie](gestion-batterie.md) | [Partiel]{.badge .part} | | [Gestion batterie](gestion-batterie.md) | <span class="badge testing">EN COURS</span> |
| [API REST / MQTT](api.md) | [Supporté]{.badge .ok} | | [API REST](../integrations/rest-api.md) / [MQTT](../integrations/mqtt.md) | <span class="badge stable">DISPONIBLE</span> |
| Tarifs dynamiques (FR) | [Roadmap]{.badge .road} | | Tarifs dynamiques (FR) | <span class="badge nightly">ROADMAP</span> |
| Optimisation CO₂ (RTE) | [Roadmap]{.badge .road} | | Optimisation CO₂ (RTE) | <span class="badge nightly">ROADMAP</span> |
| Planificateur de charge | [Roadmap]{.badge .road} | | Planificateur de charge | <span class="badge nightly">ROADMAP</span> |
| Pompe à chaleur | [Roadmap]{.badge .road} | | Pompe à chaleur | <span class="badge nightly">ROADMAP</span> |
!!! note !!! note
Les fonctions en *Roadmap* ne sont pas encore disponibles. Elles sont listées par Les fonctions en *Roadmap* ne sont pas encore disponibles. Elles sont listées par

View File

@ -8,9 +8,9 @@ Bâti sur [nymea.io](https://nymea.io), selon une architecture **Open Core** : u
libre (GPL-3.0) et une couche d'optimisation propriétaire. libre (GPL-3.0) et une couche d'optimisation propriétaire.
!!! info "Statuts honnêtes" !!! info "Statuts honnêtes"
Chaque fonction affiche son état réel : [:material-circle:{.ok} **Supporté**]{.badge .ok} Chaque fonction affiche son état réel : <span class="badge stable">STABLE</span>
en production · [:material-circle:{.part} **Partiel**]{.badge .part} en industrialisation · en production · <span class="badge testing">EN COURS</span> en industrialisation ·
[:material-circle:{.road} **Roadmap**]{.badge .road} planifié. On ne documente pas une promesse. <span class="badge nightly">ROADMAP</span> planifié. On ne documente pas une promesse.
## Par où commencer ## Par où commencer

View File

@ -0,0 +1,33 @@
# L'application PowerSync
Guide d'utilisation de l'application ETM PowerSync pour ajouter et configurer des appareils.
## Accéder à l'interface
L'interface est accessible depuis un navigateur sur le réseau local :
```
http://<ip-du-hub>
```
!!! note "Capture"
*Placeholder — capture du menu principal de l'application à ajouter.*
## Ajouter un appareil
1. Ouvrir l'application et naviguer vers **Appareils** → **Ajouter un appareil**
2. Sélectionner le type d'appareil dans la liste
3. Renseigner les paramètres de connexion (adresse IP ou port série, adresse Modbus)
4. Valider — l'appareil apparaît dans le tableau de bord si la connexion est établie
!!! note "Capture"
*Placeholder — capture de l'écran de configuration d'un appareil
(`app-config-thing.png`) à remplacer quand l'app sera prête.*
## Tableau de bord
Le tableau de bord affiche en temps réel les puissances, l'état des bornes et le bilan
énergétique du site.
!!! note "Capture"
*Placeholder — capture du tableau de bord à ajouter.*

View File

@ -11,9 +11,21 @@
} }
[data-md-color-scheme="slate"] .md-typeset a { color: var(--md-primary-fg-color); } [data-md-color-scheme="slate"] .md-typeset a { color: var(--md-primary-fg-color); }
/* badges de statut : {.badge .ok} / {.badge .part} / {.badge .road} */ /* badges — 3 familles : canal APT · stabilité plugin · maturité fonctionnalité */
.badge{font-family:"IBM Plex Mono",monospace;font-size:.7rem;letter-spacing:.06em; .badge{font-family:"IBM Plex Mono",monospace;font-size:.7rem;letter-spacing:.06em;
text-transform:uppercase;padding:2px 8px;border-radius:20px;font-weight:600;white-space:nowrap} text-transform:uppercase;padding:2px 8px;border-radius:20px;font-weight:600;white-space:nowrap;
.badge.ok{color:#3fd18a;background:rgba(63,209,138,.12);border:1px solid rgba(63,209,138,.3)} display:inline-block;vertical-align:middle;line-height:1.4}
.badge.part{color:#fec113;background:rgba(254,193,19,.1);border:1px solid rgba(254,193,19,.3)}
.badge.road{color:#8fa9b5;background:rgba(143,169,181,.1);border:1px solid rgba(143,169,181,.3)} /* canal APT */
.badge.stable {color:#3fd18a;background:rgba(63,209,138,.12);border:1px solid rgba(63,209,138,.3)}
.badge.testing {color:#fec113;background:rgba(254,193,19,.1);border:1px solid rgba(254,193,19,.3)}
.badge.nightly {color:#8fa9b5;background:rgba(143,169,181,.1);border:1px solid rgba(143,169,181,.3)}
/* stabilité plugin (meta.json) */
.badge.consumer {color:#31a3dd;background:rgba(49,163,221,.1);border:1px solid rgba(49,163,221,.3)}
.badge.community {color:#a78bfa;background:rgba(167,139,250,.1);border:1px solid rgba(167,139,250,.3)}
/* maturité fonctionnalités (à la main) */
.badge.ok {color:#3fd18a;background:rgba(63,209,138,.12);border:1px solid rgba(63,209,138,.3)}
.badge.part {color:#fec113;background:rgba(254,193,19,.1);border:1px solid rgba(254,193,19,.3)}
.badge.road {color:#8fa9b5;background:rgba(143,169,181,.1);border:1px solid rgba(143,169,181,.3)}

View File

@ -22,8 +22,7 @@ theme:
primary: custom # couleurs réelles définies dans stylesheets/extra.css primary: custom # couleurs réelles définies dans stylesheets/extra.css
accent: custom accent: custom
features: features:
- navigation.sections - navigation.indexes
- navigation.tabs
- navigation.top - navigation.top
- navigation.instant - navigation.instant
- navigation.footer - navigation.footer
@ -47,6 +46,7 @@ markdown_extensions:
- pymdownx.highlight: - pymdownx.highlight:
anchor_linenums: true anchor_linenums: true
- pymdownx.superfences - pymdownx.superfences
- pymdownx.details
- pymdownx.inlinehilite - pymdownx.inlinehilite
- pymdownx.tabbed: - pymdownx.tabbed:
alternate_style: true alternate_style: true
@ -57,6 +57,8 @@ markdown_extensions:
plugins: plugins:
- search: - search:
lang: fr lang: fr
- literate-nav:
nav_file: SUMMARY.md
nav: nav:
- Accueil: index.md - Accueil: index.md
@ -64,22 +66,18 @@ nav:
- installation/index.md - installation/index.md
- Dépôt APT: installation/depot-apt.md - Dépôt APT: installation/depot-apt.md
- Configuration: installation/configuration.md - Configuration: installation/configuration.md
- L'application: installation/application.md
- Appareils: appareils/
- Fonctionnalités: - Fonctionnalités:
- fonctionnalites/index.md - fonctionnalites/index.md
- Surplus solaire: fonctionnalites/surplus-solaire.md - Surplus solaire: fonctionnalites/surplus-solaire.md
- Délestage / Load management: fonctionnalites/delestage.md - Délestage: fonctionnalites/delestage.md
- Gestion batterie: fonctionnalites/gestion-batterie.md - Gestion batterie: fonctionnalites/gestion-batterie.md
- API REST / MQTT: fonctionnalites/api.md - Tarifs dynamiques: tarifs.md
- Appareils: - Référence:
- appareils/index.md - reference.md
- Compatibilité: appareils/compatibilite.md
- Bornes de recharge: appareils/bornes.md
- Compteurs: appareils/compteurs.md
- Onduleurs / PV: appareils/onduleurs.md
- Intégrations:
- API REST: integrations/rest-api.md - API REST: integrations/rest-api.md
- MQTT: integrations/mqtt.md - MQTT: integrations/mqtt.md
- Tarifs: tarifs.md - Aide:
- Référence: reference.md - Dépannage: depannage.md
- Dépannage: depannage.md - FAQ: faq.md
- FAQ: faq.md

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
mkdocs-material>=9.5
mkdocs-literate-nav>=0.6
PyYAML>=6.0

View File

@ -0,0 +1,409 @@
#!/usr/bin/env python3
"""
gen_device_reference.py génère la matrice de compatibilité, les sections de
référence des appareils et le fichier de nav literate-nav (SUMMARY.md) depuis
PORTING_STATUS.yaml + integrationplugin*.json + meta.json.
Principe docs-as-code : ne remplace que le contenu entre marqueurs existants.
<!-- BEGIN GENERATED: integrationplugineastron.json -->
... contenu régénéré ...
<!-- END GENERATED -->
Marqueur spécial pour la matrice de compatibilité :
<!-- BEGIN GENERATED: __matrix__ -->
Usage :
python3 scripts/gen_device_reference.py --src .plugins-src --docs docs --lang fr
python3 scripts/gen_device_reference.py --src .plugins-src --docs docs --lang fr --check
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
try:
import yaml
except ImportError:
sys.exit("PyYAML est requis : pip install PyYAML")
INTERFACE_ROLE = {
"energymeter": "Compteur d'énergie",
"smartmeterconsumer": "Compteur de consommation",
"smartmeterproducer": "Compteur de production",
"evcharger": "Borne de recharge",
"heatpump": "Pompe à chaleur",
"smartmeter": "Compteur intelligent",
}
TECH_INTERFACES = {"connectable", "networkdevice"}
CREATE_METHOD = {
"discovery": "Découverte automatique",
"user": "Ajout manuel",
"auto": "Automatique",
}
CATEGORY_LABELS = {
"compteur": "Compteurs",
"irve": "Bornes de recharge",
"smartdevice": "SmartDevices",
"hvac": "HVAC",
"onduleur": "Onduleurs / PV",
"batterie": "Batteries / ESS",
"tarif": "Tarifs & prévisions",
}
CATEGORY_FOLDER = {
"compteur": "compteurs",
"irve": "bornes",
"smartdevice": "smart",
"hvac": "hvac",
"onduleur": "onduleurs",
"batterie": "batteries",
"tarif": "tarifs",
}
CATEGORY_ORDER = ["compteur", "irve", "smartdevice", "hvac", "onduleur", "batterie", "tarif"]
CHANNEL_BADGES = {
"stable": '<span class="badge stable">STABLE</span>',
"testing": '<span class="badge testing">TESTING</span>',
"nightly": '<span class="badge nightly">NIGHTLY</span>',
}
STABILITY_BADGES = {
"consumer": '<span class="badge consumer">CONSUMER</span>',
"community": '<span class="badge community">COMMUNITY</span>',
}
MARKER_RE = re.compile(
r"(<!-- BEGIN GENERATED: (?P<key>[^\s]+) -->)(?P<body>.*?)(<!-- END GENERATED -->)",
re.DOTALL,
)
# ── Chargement ────────────────────────────────────────────────────────────────
def load_porting_status(root: Path) -> list:
path = root / "PORTING_STATUS.yaml"
if not path.exists():
sys.exit(f"ERREUR : PORTING_STATUS.yaml introuvable à {path}")
with open(path) as f:
return yaml.safe_load(f)
def load_plugins(src: Path) -> dict:
"""Charge tous les integrationplugin*.json trouvés (récursivement)."""
plugins: dict = {}
for f in sorted(src.rglob("integrationplugin*.json")):
if f.name not in plugins:
plugins[f.name] = json.loads(f.read_text(encoding="utf-8"))
return plugins
def load_meta(src: Path, plugin: str) -> dict:
"""Charge meta.json depuis le même dossier que integrationplugin<plugin>.json."""
for f in sorted(src.rglob(f"integrationplugin{plugin}.json")):
meta_path = f.parent / "meta.json"
if meta_path.exists():
return json.loads(meta_path.read_text(encoding="utf-8"))
return {}
return {}
# ── Libellé d'un appareil ────────────────────────────────────────────────────
def entry_name(e: dict, meta: dict) -> str:
"""Titre : PORTING_STATUS.name > meta.json.title > plugin (fallback dernier recours)."""
return e.get("name") or meta.get("title") or e.get("plugin", "?")
# ── Helpers JSON nymea ────────────────────────────────────────────────────────
def transport_of(tc: dict) -> str:
params = {p["name"] for p in tc.get("paramTypes", [])} | \
{p["name"] for p in tc.get("discoveryParamTypes", [])}
ifaces = set(tc.get("interfaces", []))
if "networkdevice" in ifaces or {"hostName", "address", "port", "macAddress"} & params:
return "Modbus TCP"
if {"modbusMasterUuid", "rtuMaster"} & params:
return "Modbus RTU"
return ""
def roles(tc: dict) -> str:
r = [INTERFACE_ROLE.get(i, i) for i in tc.get("interfaces", []) if i not in TECH_INTERFACES]
return ", ".join(r) if r else ""
def add_method(tc: dict) -> str:
return " / ".join(CREATE_METHOD.get(m, m) for m in tc.get("createMethods", [])) or ""
def resolve_protocol(plugin_data: dict | None) -> str:
if not plugin_data:
return ""
for v in plugin_data.get("vendors", []):
for tc in v.get("thingClasses", []):
t = transport_of(tc)
if t != "":
return t
return ""
# ── Rendu Markdown ────────────────────────────────────────────────────────────
def fmt_range(p: dict) -> str:
lo, hi = p.get("minValue"), p.get("maxValue")
if lo is not None and hi is not None:
return f"{lo}{hi}"
return ""
def fmt_default(p: dict) -> str:
d = p.get("defaultValue", "")
if d == "" or d is None:
return ""
return f"`{d}`"
def md_table(headers: list, rows: list) -> str:
out = ["| " + " | ".join(headers) + " |",
"| " + " | ".join("---" for _ in headers) + " |"]
for r in rows:
out.append("| " + " | ".join(str(c) for c in r) + " |")
return "\n".join(out)
def render_params(tc: dict) -> list:
lines = []
disc = tc.get("discoveryParamTypes", [])
if disc:
rows = [[f"`{p['name']}`", p.get("displayName", ""), p.get("type", ""),
fmt_range(p), fmt_default(p)] for p in disc]
lines.append("_Paramètres de découverte :_")
lines.append(md_table(["Clé", "Libellé", "Type", "Plage", "Défaut"], rows))
lines.append("")
settings = tc.get("paramTypes", [])
if settings:
rows = [[f"`{p['name']}`", p.get("displayName", ""), p.get("type", ""),
fmt_range(p), fmt_default(p),
"oui" if p.get("readOnly") else "non"] for p in settings]
lines.append("_Réglages :_")
lines.append(md_table(["Clé", "Libellé", "Type", "Plage", "Défaut", "Lecture seule"], rows))
lines.append("")
return lines
def render_states(tc: dict) -> list:
states = tc.get("stateTypes", [])
if not states:
return []
rows = [[f"`{s['name']}`", s.get("displayName", ""), s.get("type", ""),
s.get("unit") or ""] for s in states]
return [md_table(["Clé", "Grandeur", "Type", "Unité"], rows), ""]
def render_plugin(plugin: dict) -> str:
out = []
vendors = plugin.get("vendors", [])
vname = ", ".join(v.get("displayName", v.get("name", "")) for v in vendors)
out.append(f"**Fabricant :** {vname} ")
out.append(f"**Plugin :** `{plugin.get('name', '')}`")
out.append("")
tcs = [(v, tc) for v in vendors for tc in v.get("thingClasses", [])]
rows = [[f"**{tc.get('displayName', tc.get('name'))}**",
roles(tc), transport_of(tc), add_method(tc),
str(len(tc.get("stateTypes", [])))]
for _, tc in tcs]
out.append("#### Modèles pris en charge")
out.append(md_table(["Modèle", "Rôle", "Transport", "Ajout", "Grandeurs"], rows))
out.append("")
out.append("#### Détail par modèle")
for _, tc in tcs:
title = tc.get("displayName", tc.get("name"))
out.append(f'??? abstract "{title} — `{tc.get("name")}`"')
body = []
body += render_params(tc)
if tc.get("stateTypes"):
body.append("_Grandeurs mesurées :_")
body += render_states(tc)
for ln in ("\n".join(body)).splitlines():
out.append(" " + ln if ln else "")
out.append("")
return "\n".join(out).rstrip() + "\n"
def render_matrix(entries: list, plugins: dict, src: Path) -> str:
by_cat: dict = {}
for e in entries:
by_cat.setdefault(e.get("category", "autre"), []).append(e)
lines = []
ordered_cats = [c for c in CATEGORY_ORDER if c in by_cat]
ordered_cats += [c for c in by_cat if c not in CATEGORY_ORDER]
for cat in ordered_cats:
label = CATEGORY_LABELS.get(cat, cat.capitalize())
cat_rows = []
for e in by_cat[cat]:
fname = f"integrationplugin{e['plugin']}.json" if e.get("plugin") else None
plugin_data = plugins.get(fname) if fname else None
# nightly sans JSON → absent de la matrice et du nav
if e["channel"] == "nightly" and (not fname or fname not in plugins):
continue
meta = load_meta(src, e["plugin"]) if e.get("plugin") else {}
name = entry_name(e, meta)
protocol = resolve_protocol(plugin_data)
channel_badge = CHANNEL_BADGES.get(e["channel"], e["channel"])
stability = meta.get("stability", "")
stability_badge = STABILITY_BADGES.get(stability, "") if stability else ""
cat_rows.append(f"| {name} | {protocol} | {channel_badge} | {stability_badge} |")
if not cat_rows:
continue
lines.append(f"## {label}\n")
lines.append("| Marque / Modèle | Protocole | Canal | Stabilité |")
lines.append("|---|---|---|---|")
lines.extend(cat_rows)
lines.append("")
return "\n".join(lines)
# ── SUMMARY.md pour mkdocs-literate-nav ──────────────────────────────────────
def generate_summary(entries: list, docs: Path, src: Path, plugins: dict) -> None:
"""Écrit docs/appareils/SUMMARY.md (nav literate-nav)."""
by_cat: dict = {}
for e in entries:
by_cat.setdefault(e.get("category", "autre"), []).append(e)
lines = ["* [Compatibilité](compatibilite.md)"]
ordered_cats = [c for c in CATEGORY_ORDER if c in by_cat]
ordered_cats += [c for c in by_cat if c not in CATEGORY_ORDER]
for cat in ordered_cats:
label = CATEGORY_LABELS.get(cat, cat.capitalize())
folder = CATEGORY_FOLDER.get(cat, cat)
cat_entries = []
for e in by_cat[cat]:
if not e.get("plugin"):
continue
fname = f"integrationplugin{e['plugin']}.json"
# nightly sans JSON → pas de fiche, pas d'entrée nav
if e["channel"] == "nightly" and fname not in plugins:
continue
meta = load_meta(src, e["plugin"])
name = entry_name(e, meta)
slug = e.get("slug") or e["plugin"]
cat_entries.append(f" * [{name}]({folder}/{slug}.md)")
if cat_entries:
lines.append(f"* [{label}]({folder}/index.md)")
lines.extend(cat_entries)
summary_path = docs / "appareils" / "SUMMARY.md"
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"SUMMARY.md → {summary_path.relative_to(docs.parent)}")
# ── Validation ────────────────────────────────────────────────────────────────
def validate_entries(entries: list, plugins: dict, src: Path) -> None:
errors = []
for e in entries:
if not e.get("plugin"):
continue
fname = f"integrationplugin{e['plugin']}.json"
if e["channel"] != "nightly":
# JSON obligatoire pour stable / testing
if fname not in plugins:
errors.append(
f" BLOQUANT : {fname} introuvable dans --src "
f"(channel={e['channel']}, plugin={e['plugin']})"
)
# Libellé obligatoire
meta = load_meta(src, e["plugin"])
if not e.get("name") and not meta.get("title"):
errors.append(
f" BLOQUANT : libellé manquant pour plugin={e['plugin']} "
f"(ni PORTING_STATUS.name ni meta.json.title)"
)
if errors:
sys.exit("ERREURS BLOQUANTES :\n" + "\n".join(errors))
# ── Traitement des fichiers MD ────────────────────────────────────────────────
def process(docs: Path, entries: list, plugins: dict, src: Path, check: bool) -> int:
entry_by_plugin = {e["plugin"]: e for e in entries if e.get("plugin")}
changed = []
for md in sorted(docs.rglob("*.md")):
text = md.read_text(encoding="utf-8")
if "<!-- BEGIN GENERATED:" not in text:
continue
def repl(m: re.Match) -> str:
key = m.group("key")
if key == "__matrix__":
gen = render_matrix(entries, plugins, src)
elif key in plugins:
gen = render_plugin(plugins[key])
else:
print(f" ! {md.name}: clé '{key}' absente de --src", file=sys.stderr)
return m.group(0)
return f"{m.group(1)}\n{gen}\n{m.group(4)}"
new = MARKER_RE.sub(repl, text)
if new != text:
changed.append(md)
if not check:
md.write_text(new, encoding="utf-8")
if check:
if changed:
print("Doc PAS à jour :", ", ".join(str(c) for c in changed))
return 1
print("Doc à jour.")
return 0
print(f"Régénéré : {len(changed)} page(s).")
for c in changed:
print(f" - {c.relative_to(docs)}")
return 0
# ── Point d'entrée ────────────────────────────────────────────────────────────
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--src", default=".plugins-src", type=Path,
help="Dossier source des JSON (plat ou avec sous-dossiers repos)")
ap.add_argument("--docs", default="docs", type=Path)
ap.add_argument("--lang", default="fr", help="Conservé pour compatibilité CI")
ap.add_argument("--check", action="store_true",
help="CI : exit 1 si la doc n'est pas à jour")
a = ap.parse_args()
src = a.src
entries = load_porting_status(Path.cwd())
plugins = load_plugins(src)
if not plugins:
print(f"Aucun integrationplugin*.json trouvé dans {src}", file=sys.stderr)
return 2
print(f"{len(plugins)} plugin(s) chargé(s) : {', '.join(plugins)}")
validate_entries(entries, plugins, src)
generate_summary(entries, a.docs, src, plugins)
return process(a.docs, entries, plugins, src, a.check)
if __name__ == "__main__":
raise SystemExit(main())