Ajout plugin ABB Terra AC (vendoring upstream experimental-silo)
- abbterra/ copié depuis nymea-plugins-modbus experimental-silo (a652793) - paquet nymea-plugin-abbterra (nom upstream, vendoring tracé dans VENDORED.md) - 3 paquets binaires => .install par paquet - abbterra ajouté à FORKED_PLUGINS du mirror (exclusion upstream) - changelog 1.15.0+etm3
This commit is contained in:
parent
da2ce5253d
commit
c26b0336bd
98
abbb2x/.claude/REPRISE-ABB-atelier.md
Normal file
98
abbb2x/.claude/REPRISE-ABB-atelier.md
Normal file
@ -0,0 +1,98 @@
|
||||
# REPRISE — Test atelier : borne ABB Terra AC + compteur ABB B2x (B21/B23/B24)
|
||||
|
||||
## Contexte
|
||||
Je teste EN ATELIER une borne ABB Terra AC + un compteur ABB B2x (en stock) avant
|
||||
une visite client. Objectif : faire reconnaître les deux par nymea, depuis MES
|
||||
propres paquets Debian, et valider les mesures (surtout le scaling du compteur).
|
||||
|
||||
On a rôdé tout le pipeline modbus aujourd'hui (plugin Eastron SDM construit et
|
||||
fonctionnel). Les deux plugins ABB suivent EXACTEMENT le même chemin.
|
||||
|
||||
## Infrastructure (rappel)
|
||||
- VM build : etm-powersync-dev, conteneur LXC `build-1-15` (libnymea-dev 1.15.0)
|
||||
- déjà installés dans le conteneur : libnymea-modbus-dev, qt6-serialport-dev,
|
||||
qt6-serialbus-dev, nymea-dev-tools, qt6-base-dev(-tools)
|
||||
- Edge de test : ssh etm@192.168.1.120 (etm-nymea-dev), nymead actif
|
||||
- Dépôt APT : reprepro sur /mnt/builddisk/apt-repo ; publish-to-repo.sh ; clé GPG ETM
|
||||
- canal de travail : powersync-testing ; edge branché dessus
|
||||
- Repo modbus : git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus
|
||||
- chemin local VM : ~/projects/etm-powersync/etm/etm-powersync-plugins-modbus
|
||||
- contient déjà : eastron/ (fonctionne), modbus.pri recâblé sur paquets système
|
||||
(PKGCONFIG += nymea-modbus + include(/usr/include/nymea-modbus/modbus-tool.pri))
|
||||
|
||||
## Règles de packaging (acquises aujourd'hui, à respecter)
|
||||
- 1 seul paquet binaire par repo modbus actuel → PAS de fichier debian/*.install
|
||||
(qmake6 installe la .so, debhelper la ramasse). Le .install ne sert qu'en multi-binaire.
|
||||
- debian/control Build-Depends modbus : debhelper, pkg-config, libnymea-dev,
|
||||
nymea-dev-tools:native, libnymea-modbus-dev, qt6-base-dev, qt6-base-dev-tools,
|
||||
qt6-serialport-dev, qt6-serialbus-dev
|
||||
- debian/rules : nettoyer autogenerated/ + moc_* + *plugininfo.h au clean
|
||||
- changelog : format strict (ligne vide avant le " --"), version +etm1 (plugins maison)
|
||||
- .pro racine : PLUGIN_DIRS sans backslash après la dernière entrée
|
||||
- nom paquet plugin maison = powersync-plugin-XXX (pas de Provides/Replaces si code original)
|
||||
- Le conteneur CLONE depuis Gitea → toujours git push AVANT de builder dans le conteneur
|
||||
|
||||
## Le pipeline qui marche (référence eastron)
|
||||
git push, puis dans le conteneur :
|
||||
cd /root && rm -rf <repo> && git clone <url> && cd <repo>
|
||||
qmake6 && make -j$(nproc) # valider compil + génération modbus AVANT debian/
|
||||
dpkg-buildpackage -b -us -uc
|
||||
Puis vérif (dpkg-deb -c → la .so présente), lxc file pull, reprepro includedeb
|
||||
powersync-testing, publish-to-repo.sh, install sur .120, restart nymead.
|
||||
|
||||
## TÂCHE 1 — Borne ABB Terra AC (code upstream, faible risque)
|
||||
Le plugin existe DÉJÀ : nymea-plugins-modbus, branche experimental-silo, dossier abbterra/
|
||||
(deux ThingClass : terraAcTcp {address, port=502, slaveId=1} et terraAcRtu {rtuMaster, slaveId=1}).
|
||||
À faire :
|
||||
1. Copier abbterra/ depuis l'upstream experimental-silo dans MON repo modbus.
|
||||
2. Ajouter "abbterra" à PLUGIN_DIRS (sans casser le backslash).
|
||||
3. Builder via le pipeline ci-dessus → powersync-plugin-abbterra_1.15.0+etm1.
|
||||
4. Import testing + install .120.
|
||||
À CONFIRMER en atelier : la borne en stock est-elle en Modbus TCP ou RTU ?
|
||||
(détermine quelle ThingClass utiliser à la config nymea:app)
|
||||
|
||||
## TÂCHE 2 — Compteur ABB B2x (plugin NEUF, à valider sur matériel)
|
||||
J'ai déjà un abbb2x-registers.json prêt (registres extraits/vérifiés des manuels
|
||||
B21 + B23/B24 — même mapping pour les 3 modèles ; puissances en int32 SIGNÉ pour
|
||||
gérer l'injection PV ; énergie import/export en uint64 size 4 ; scaling via "unit").
|
||||
Reste à créer le plugin autour :
|
||||
1. Créer le dossier b2x/ (ou abbb2x/) dans MON repo modbus avec :
|
||||
- abbb2x-registers.json (je l'ai)
|
||||
- eastron.pro → adapter en abbb2x.pro (MODBUS_CONNECTIONS += abbb2x-registers.json ;
|
||||
include(../modbus.pri))
|
||||
- integrationpluginabbb2x.cpp/.h/.json → ADAPTER depuis integrationplugineastron.*
|
||||
(même type d'appareil = compteur ; mapper les registres générés vers l'interface
|
||||
nymea smartmeter : currentPower, voltage/current/power par phase, énergie conso/prod).
|
||||
Vendor = "ABB". Nouveau pluginId/thingClassId/vendorId (générer des UUID, NE PAS
|
||||
réutiliser ceux d'eastron).
|
||||
2. Ajouter "abbb2x" à PLUGIN_DIRS.
|
||||
3. Builder → powersync-plugin-abbb2x_1.15.0+etm1, import, install .120.
|
||||
|
||||
POINTS À VALIDER FACE AU COMPTEUR (typiques d'un plugin neuf) :
|
||||
- SCALING PUISSANCE : manuel dit "Signed, 0.01 W". Si la puissance lue est ×100 trop
|
||||
grande, le scaling réel est 1W → corriger le "unit" dans le registers.json.
|
||||
- Comment le pipeline applique le "unit" (générateur vs code .cpp) : vérifier dans
|
||||
integrationpluginabbterra.cpp comment ABB applique le scaling (grep unit/toDouble/setVoltage).
|
||||
- checkReachableRegister = voltagePhaseA : confirmer qu'il répond à l'init.
|
||||
- Sur un compteur monophasé (B21), L2/L3 doivent rester à 0/invalide — normal.
|
||||
|
||||
## Config Modbus RTU (si transport série)
|
||||
- Vérifier l'adaptateur série sur .120 : ls -l /dev/ttyUSB* ; dmesg | grep -iE "ttyUSB|ftdi|ch341|cp210"
|
||||
- nymead doit avoir accès au port : user nymea dans le groupe dialout (sudo usermod -aG dialout nymea + restart)
|
||||
- Paramètres ligne ABB B2x par défaut : à confirmer sur l'écran du compteur (souvent 9600 8N1)
|
||||
- Adresses esclaves : si borne RTU + compteur RTU sur le MÊME bus → slaveId DISTINCTS
|
||||
- Dans nymea:app : si RTU, créer d'abord le "Modbus RTU master" (l'adaptateur), puis les things
|
||||
|
||||
## À ne pas oublier (notes de fond, PAS pour aujourd'hui)
|
||||
- Modélisation interfaces compteur : prévoir au ThingClass un param "role"
|
||||
(producteur/consommateur/raccordement) + un param "phase" (L1/L2/L3 pour mono) —
|
||||
donnée d'installation, inerte aujourd'hui, prérequis du DÉLESTAGE futur.
|
||||
- Délestage : stratégie 2 niveaux (N1 = phases du compteur principal ; N2 = sous-compteurs
|
||||
par circuit). nymea-energy-plugin-nymea (chargebyte, GPL) contient déjà de la logique de
|
||||
délestage → l'INVESTIGUER avant de coder. Le délestage ira dans powersync-energy-plugin-etm
|
||||
(Community, rule-based, GPL) ; l'optimizer reste propriétaire (œuvre séparée via socket).
|
||||
|
||||
## Mon environnement
|
||||
Je travaille en français. Je préfère : affichage complet des fichiers (cat -n) plutôt que
|
||||
diffs, logs complets avant tout commit, pas de fallback silencieux. Va étape par étape,
|
||||
attends ma sortie de commande avant de passer à la suivante.
|
||||
153
abbterra/.claude/REPRISE-ABB-terraac.md
Normal file
153
abbterra/.claude/REPRISE-ABB-terraac.md
Normal file
@ -0,0 +1,153 @@
|
||||
# REPRISE — TÂCHE 1 : intégrer & builder ABB Terra AC (vendoring upstream)
|
||||
|
||||
## Contexte
|
||||
Je prépare une visite client (borne ABB Terra AC + compteur ABB B23). Test EN ATELIER
|
||||
d'abord. Le plugin **abbterra** se trouve DÉJÀ dans MON repo
|
||||
`etm-powersync-plugins-modbus` (dossier `abbterra/`) — il a été copié depuis l'upstream
|
||||
nymea-plugins-modbus, branche `experimental-silo`. Le compteur B2x (TÂCHE 2) est déjà
|
||||
intégré et prêt (abbb2x ajouté à PLUGIN_DIRS, debian/control + changelog faits).
|
||||
|
||||
But de cette tâche : packager et déployer la borne, proprement, en décidant comment on
|
||||
gère le fait que ce code est upstream-mais-pas-encore-release.
|
||||
|
||||
## Décision de fond (déjà tranchée — à appliquer, pas à rediscuter)
|
||||
abbterra N'EST PAS un fork divergent : je ne modifie pas le code, je le **builde en avance**
|
||||
parce que nymea ne l'a pas encore publié dans son dépôt apt (il n'est que dans la branche
|
||||
experimental-silo). C'est du **vendoring temporaire**. Tout nymea est GPLv3 → redistribution
|
||||
et build anticipé explicitement permis, aucune contrainte juridique.
|
||||
|
||||
Approche retenue (cohérente avec l'archi existante du mirror) :
|
||||
1. **Nom du paquet = `nymea-plugin-abbterra`** (nom UPSTREAM, PAS de préfixe powersync-).
|
||||
2. **PAS de Provides/Replaces/Conflicts** (rien ne le concurrence : le mirror l'exclura).
|
||||
3. **Ajouter `abbterra` à `FORKED_PLUGINS`** dans `/mnt/builddisk/sync-nymea-mirror.sh`.
|
||||
→ empêche le mirror de réimporter une version upstream concurrente sous le même nom.
|
||||
`FORKED_PLUGINS` couvre désormais DEUX cas : forks divergents (keba) ET builds
|
||||
anticipés de code upstream non encore release (abbterra). Mettre à jour son commentaire.
|
||||
4. **Tracer le vendoring** : créer `abbterra/VENDORED.md` (voir contenu plus bas).
|
||||
|
||||
Pourquoi ce choix : transition douce. Le jour où nymea release abbterra dans master/stable,
|
||||
il suffira de (a) supprimer le dossier abbterra/ de mon repo, (b) retirer `abbterra` de
|
||||
FORKED_PLUGINS, (c) relancer le sync — le mirror tirera alors la version OFFICIELLE sous
|
||||
le MÊME nom, donc l'edge bascule dessus sans réinstall ni reconfig des things. Un seul nom
|
||||
de paquet existe à tout instant, jamais de doublon.
|
||||
|
||||
## Infrastructure (rappel)
|
||||
- VM build : etm-powersync-dev ; conteneur LXC `build-1-15` (libnymea-dev 1.15.0,
|
||||
libnymea-modbus-dev, qt6-serialport-dev, qt6-serialbus-dev, nymea-dev-tools, python3).
|
||||
- Edge test : ssh etm@192.168.1.120, nymead actif, canal powersync-testing.
|
||||
- Dépôt APT : reprepro /mnt/builddisk/apt-repo ; publish-to-repo.sh ; clé GPG ETM.
|
||||
- Mirror : /mnt/builddisk/sync-nymea-mirror.sh (sélection auto depuis index upstream
|
||||
moins FORKED_PLUGINS ; keba déjà dedans).
|
||||
- Repo modbus : git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus
|
||||
local : ~/projects/etm-powersync/etm/etm-powersync-plugins-modbus
|
||||
- contient déjà : eastron/ (OK, en prod), abbb2x/ (prêt), abbterra/ (à packager).
|
||||
- modbus.pri recâblé sur paquets système (PKGCONFIG nymea-modbus + modbus-tool.pri).
|
||||
- Le conteneur CLONE depuis Gitea → TOUJOURS git push avant de builder.
|
||||
|
||||
## Règles de packaging (acquises, à respecter)
|
||||
- .pro racine : PLUGIN_DIRS une entrée par ligne, PAS de backslash après la dernière ;
|
||||
pas de SUBDIRS local ni de .depends vers libnymea-modbus (lib système).
|
||||
- Multi-binaire : le repo aura maintenant 3 paquets (eastron + abbb2x + abbterra) dans
|
||||
le MÊME debian/ → IL FAUT un `debian/<paquet>.install` par paquet (nom EXACT du Package:),
|
||||
sinon dh_install ne route pas les .so → paquet vide. Vérifier que les .install existent
|
||||
pour les 3 : powersync-plugin-eastron.install, powersync-plugin-abbb2x.install,
|
||||
nymea-plugin-abbterra.install. Chacun contient la ligne :
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationplugin<nom>.so
|
||||
- debian/control Build-Depends modbus : debhelper, pkg-config, libnymea-dev,
|
||||
nymea-dev-tools:native, libnymea-modbus-dev, qt6-base-dev, qt6-base-dev-tools,
|
||||
qt6-serialport-dev, qt6-serialbus-dev.
|
||||
- changelog : format strict (ligne vide avant le " --"). Bumper la source en +etm3
|
||||
(etm2 = ajout abbb2x déjà fait).
|
||||
- rules : nettoyer autogenerated/ + moc_* + *plugininfo.h au dh_auto_clean.
|
||||
|
||||
## ÉTAPES
|
||||
|
||||
### 1. Vérifier l'état d'abbterra dans le repo
|
||||
ls -la ~/projects/.../etm-powersync-plugins-modbus/abbterra/
|
||||
grep -n 'PLUGIN_DIRS\|abbterra\|abbb2x\|eastron' etm-powersync-plugins-modbus.pro
|
||||
- abbterra présent ? abbterra dans PLUGIN_DIRS ? (l'ajouter si absent, sans casser le backslash)
|
||||
|
||||
### 2. debian/ — ajouter le paquet nymea-plugin-abbterra
|
||||
- debian/control : nouveau stanza `Package: nymea-plugin-abbterra`
|
||||
Architecture: any
|
||||
Section: libs
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
(PAS de Provides/Replaces/Conflicts)
|
||||
Description: ABB Terra AC charging station (Modbus TCP/RTU) — vendored from
|
||||
nymea-plugins-modbus experimental-silo, pending upstream release.
|
||||
- debian/nymea-plugin-abbterra.install :
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationpluginabbterra.so
|
||||
- debian/changelog : nouvelle entrée en tête, version 1.15.0+etm3 (ligne vide avant " --").
|
||||
- Vérifier que les .install des 3 paquets existent (cf. règles multi-binaire).
|
||||
|
||||
### 3. Tracer le vendoring — créer abbterra/VENDORED.md
|
||||
Contenu :
|
||||
----------------------------------------------------------------
|
||||
# Vendoring — abbterra
|
||||
Copié depuis : nymea/nymea-plugins-modbus @ branche experimental-silo
|
||||
Commit source : a652793 ("Add new plugin for ABB Terra AC Charger")
|
||||
Date copie : 2026-06-01
|
||||
Raison : plugin présent upstream mais PAS encore publié dans le dépôt apt nymea.
|
||||
Build anticipé ETM en attendant la release master/stable.
|
||||
Nom paquet : nymea-plugin-abbterra (nom upstream conservé)
|
||||
Exclu du mirror via FORKED_PLUGINS dans sync-nymea-mirror.sh.
|
||||
SORTIE (quand nymea release abbterra dans master/stable) :
|
||||
1) supprimer le dossier abbterra/ de ce repo
|
||||
2) retirer "abbterra" de FORKED_PLUGINS
|
||||
3) relancer sync-nymea-mirror.sh → le mirror tire la version officielle (même nom)
|
||||
----------------------------------------------------------------
|
||||
|
||||
### 4. Mirror — exclure abbterra
|
||||
Dans /mnt/builddisk/sync-nymea-mirror.sh, dans FORKED_PLUGINS :
|
||||
FORKED_PLUGINS=(
|
||||
"keba"
|
||||
"abbterra"
|
||||
)
|
||||
Mettre à jour le commentaire au-dessus pour préciser les 2 cas (fork divergent + vendoring).
|
||||
|
||||
### 5. Build (pipeline validé)
|
||||
git add -A && git commit -m "..." && git push
|
||||
sudo lxc exec build-1-15 -- bash -c '
|
||||
set -e
|
||||
cd /root && rm -rf etm-powersync-plugins-modbus
|
||||
git clone https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus.git
|
||||
cd etm-powersync-plugins-modbus
|
||||
echo "=== plugins ===" && grep -A6 PLUGIN_DIRS etm-powersync-plugins-modbus.pro
|
||||
echo "=== installs ===" && ls debian/*.install
|
||||
echo "=== packages ===" && grep -c "^Package:" debian/control
|
||||
chmod +x debian/rules
|
||||
# build local d abord pour valider compil + generation, PUIS le .deb :
|
||||
qmake6 && make -j$(nproc) 2>&1 | tail -30
|
||||
'
|
||||
- Vérifier dans la sortie : abbterra ET abbb2x ET eastron compilent, .so produites.
|
||||
- Si abbterra casse sur des getters/connexion : c est du code upstream testé, donc
|
||||
plutot un souci d intégration (PLUGIN_DIRS, modbus.pri) qu un bug — lire l erreur.
|
||||
- Puis : dpkg-buildpackage -b -us -uc 2>&1 | tail -30
|
||||
|
||||
### 6. Vérifier les .deb (anti-paquet-vide)
|
||||
Pour chacun des 3 : dpkg-deb -c <deb> | grep '\.so' → une .so au bon chemin.
|
||||
Pour abbterra : dpkg-deb -f nymea-plugin-abbterra_*.deb Package Depends
|
||||
(Depends doit inclure libqt6serialbus/serialport via shlibs).
|
||||
|
||||
### 7. Import + déploiement
|
||||
lxc file pull des .deb vers /mnt/builddisk
|
||||
reprepro -b /mnt/builddisk/apt-repo includedeb powersync-testing <deb> (les 3 + dbgsym)
|
||||
/mnt/builddisk/publish-to-repo.sh
|
||||
reprepro -b /mnt/builddisk/apt-repo list powersync-testing | grep -iE 'abbterra|abbb2x'
|
||||
ssh etm@192.168.1.120 'sudo apt update && sudo apt install -y nymea-plugin-abbterra powersync-plugin-abbb2x && sudo systemctl restart nymead'
|
||||
|
||||
## Config en atelier (après install)
|
||||
- Borne ABB Terra : ThingClass terraAcTcp (address, port 502, slaveId 1) OU terraAcRtu
|
||||
(rtuMaster, slaveId 1). CONFIRMER sur le matériel : TCP (réseau) ou RTU (RS-485) ?
|
||||
- Compteur ABB B2x : RTU (rtuMaster + slaveAddress). Si borne RTU + compteur RTU sur le
|
||||
MÊME bus → slaveId DISTINCTS.
|
||||
- RTU : créer d'abord le "Modbus RTU master" (adaptateur /dev/ttyUSB*) dans nymea:app,
|
||||
vérifier droits (user nymead dans groupe dialout), puis ajouter les things.
|
||||
- B2x = code NEUF : valider scaling (puissance signée +import/-export ; si ×100 trop grand
|
||||
passer /100 à /1 dans le .cpp ; vérifier noms getters générés dans
|
||||
abbb2x/autogenerated/abbb2xmodbusrtuconnection.h).
|
||||
|
||||
## Préférences de travail
|
||||
Français. Affichage complet des fichiers (cat -n) plutôt que diffs ; logs complets avant
|
||||
commit ; pas de fallback silencieux. Étape par étape : attendre ma sortie de commande avant
|
||||
de continuer. Toujours vérifier le contenu d'un .deb (dpkg-deb -c) avant import.
|
||||
19
abbterra/README.md
Normal file
19
abbterra/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# ABB Terra AC
|
||||
|
||||
This plugin integrates ABB Terra AC chargers via Modbus TCP and Modbus RTU.
|
||||
|
||||
Implemented features:
|
||||
|
||||
- network discovery for Modbus TCP chargers
|
||||
- Modbus RTU discovery using nymea's managed RTU hardware resource
|
||||
- connection state
|
||||
- plugged-in and charging state detection
|
||||
- charging enable/disable via current-limit control
|
||||
- writable maximum charging current
|
||||
- active power and session energy
|
||||
- firmware version, serial number, and error code
|
||||
- communication timeout setting
|
||||
|
||||
The register model is based on:
|
||||
|
||||
- `ABB_Terra_AC_Charger_ModbusCommunication_v1.7.pdf`
|
||||
12
abbterra/VENDORED.md
Normal file
12
abbterra/VENDORED.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Vendoring — abbterra
|
||||
Copié depuis : nymea/nymea-plugins-modbus @ branche experimental-silo
|
||||
Commit source : a652793 ("Add new plugin for ABB Terra AC Charger")
|
||||
Date copie : 2026-06-01
|
||||
Raison : plugin présent upstream mais PAS encore publié dans le dépôt apt nymea.
|
||||
Build anticipé ETM en attendant la release master/stable.
|
||||
Nom paquet : nymea-plugin-abbterra (nom upstream conservé)
|
||||
Exclu du mirror via FORKED_PLUGINS dans sync-nymea-mirror.sh.
|
||||
SORTIE (quand nymea release abbterra dans master/stable) :
|
||||
1) supprimer le dossier abbterra/ de ce repo
|
||||
2) retirer "abbterra" de FORKED_PLUGINS
|
||||
3) relancer sync-nymea-mirror.sh → le mirror tire la version officielle (même nom)
|
||||
243
abbterra/abbterra-registers.json
Normal file
243
abbterra/abbterra-registers.json
Normal file
@ -0,0 +1,243 @@
|
||||
{
|
||||
"className": "AbbTerra",
|
||||
"protocol": "BOTH",
|
||||
"endianness": "BigEndian",
|
||||
"stringEndianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 3,
|
||||
"checkReachableRegister": "serialNumber",
|
||||
"queuedRequests": true,
|
||||
"queuedRequestsDelay": 50,
|
||||
"blocks": [
|
||||
{
|
||||
"id": "deviceInfo",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "serialNumber",
|
||||
"address": 16384,
|
||||
"size": 4,
|
||||
"type": "uint64",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Product serial number",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "firmwareVersionRaw",
|
||||
"address": 16388,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Firmware version",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "userSettableMaxCurrent",
|
||||
"address": 16390,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Maximum user settable charging current",
|
||||
"defaultValue": "32000",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "errorCode",
|
||||
"address": 16392,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Last error code",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "socketLockState",
|
||||
"address": 16394,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Socket and cable lock state",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "chargingStateRaw",
|
||||
"address": 16396,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Charging state",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "chargingCurrentLimit",
|
||||
"address": 16398,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current charging current limit",
|
||||
"defaultValue": "6000",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentL1",
|
||||
"address": 16400,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current L1",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentL2",
|
||||
"address": 16402,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current L2",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentL3",
|
||||
"address": 16404,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "mA",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current L3",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "voltageL1",
|
||||
"address": 16406,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "0.1V",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Voltage L1",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "voltageL2",
|
||||
"address": 16408,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "0.1V",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Voltage L2",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "voltageL3",
|
||||
"address": 16410,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "0.1V",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Voltage L3",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "activePower",
|
||||
"address": 16412,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "W",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Measured active power",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "sessionEnergy",
|
||||
"address": 16414,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "Wh",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Delivered energy of the current session",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "communicationTimeoutReadback",
|
||||
"address": 16416,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "s",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Communication timeout",
|
||||
"defaultValue": "60",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"registers": [
|
||||
{
|
||||
"id": "chargingCurrentLimitCommand",
|
||||
"address": 16640,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"unit": "mA",
|
||||
"readSchedule": "",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Set charging current limit",
|
||||
"defaultValue": "6000",
|
||||
"access": "WO"
|
||||
},
|
||||
{
|
||||
"id": "socketLockCommand",
|
||||
"address": 16642,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Socket lock control",
|
||||
"defaultValue": "0",
|
||||
"access": "WO"
|
||||
},
|
||||
{
|
||||
"id": "startStopChargingSession",
|
||||
"address": 16645,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"readSchedule": "",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Start or stop charging session",
|
||||
"defaultValue": "0",
|
||||
"access": "WO"
|
||||
},
|
||||
{
|
||||
"id": "communicationTimeoutCommand",
|
||||
"address": 16646,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"unit": "s",
|
||||
"readSchedule": "",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Set communication timeout",
|
||||
"defaultValue": "60",
|
||||
"access": "WO"
|
||||
}
|
||||
]
|
||||
}
|
||||
20
abbterra/abbterra.pro
Normal file
20
abbterra/abbterra.pro
Normal file
@ -0,0 +1,20 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
MODBUS_CONNECTIONS += abbterra-registers.json
|
||||
#MODBUS_TOOLS_CONFIG += VERBOSE
|
||||
include(../modbus.pri)
|
||||
|
||||
TARGET = $$qtLibraryTarget(nymea_integrationpluginabbterra)
|
||||
|
||||
OTHER_FILES += abbterra-registers.json
|
||||
|
||||
SOURCES += \
|
||||
abbterrartudiscovery.cpp \
|
||||
abbterratcpdiscovery.cpp \
|
||||
integrationpluginabbterra.cpp
|
||||
|
||||
HEADERS += \
|
||||
abbterrartudiscovery.h \
|
||||
abbterratcpdiscovery.h \
|
||||
abbterrautils.h \
|
||||
integrationpluginabbterra.h
|
||||
76
abbterra/abbterrartudiscovery.cpp
Normal file
76
abbterra/abbterrartudiscovery.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "abbterrartudiscovery.h"
|
||||
#include "abbterrautils.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
AbbTerraRtuDiscovery::AbbTerraRtuDiscovery(ModbusRtuHardwareResource *modbusRtuResource, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_modbusRtuResource(modbusRtuResource)
|
||||
{
|
||||
}
|
||||
|
||||
void AbbTerraRtuDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcAbbTerra()) << "Discovery: Searching for ABB Terra AC chargers on Modbus RTU...";
|
||||
|
||||
m_candidateMasters.clear();
|
||||
m_results.clear();
|
||||
m_masterIndex = 0;
|
||||
m_slaveId = 1;
|
||||
|
||||
foreach (ModbusRtuMaster *master, m_modbusRtuResource->modbusRtuMasters()) {
|
||||
if (master->connected()) {
|
||||
m_candidateMasters.append(master);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_candidateMasters.isEmpty()) {
|
||||
qCWarning(dcAbbTerra()) << "No connected Modbus RTU master available for ABB Terra AC discovery.";
|
||||
emit discoveryFinished(false);
|
||||
return;
|
||||
}
|
||||
|
||||
scanNext();
|
||||
}
|
||||
|
||||
QList<AbbTerraRtuDiscovery::Result> AbbTerraRtuDiscovery::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
void AbbTerraRtuDiscovery::scanNext()
|
||||
{
|
||||
if (m_masterIndex >= m_candidateMasters.count()) {
|
||||
emit discoveryFinished(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_slaveId > 247) {
|
||||
m_masterIndex++;
|
||||
m_slaveId = 1;
|
||||
scanNext();
|
||||
return;
|
||||
}
|
||||
|
||||
ModbusRtuMaster *master = m_candidateMasters.at(m_masterIndex);
|
||||
const quint16 currentSlaveId = m_slaveId++;
|
||||
|
||||
ModbusRtuReply *reply = master->readHoldingRegister(currentSlaveId, 0x4000, 8);
|
||||
connect(reply, &ModbusRtuReply::finished, this, [this, master, currentSlaveId, reply]() {
|
||||
if (reply->error() == ModbusRtuReply::NoError) {
|
||||
const AbbTerraUtils::DeviceInfo deviceInfo = AbbTerraUtils::deviceInfoFromRegisters(reply->result());
|
||||
if (deviceInfo.valid) {
|
||||
Result result;
|
||||
result.modbusRtuMasterId = master->modbusUuid();
|
||||
result.slaveId = currentSlaveId;
|
||||
result.serialNumber = deviceInfo.serialNumber;
|
||||
result.productName = deviceInfo.productName;
|
||||
result.firmwareVersion = deviceInfo.firmwareVersion;
|
||||
m_results.append(result);
|
||||
}
|
||||
}
|
||||
|
||||
scanNext();
|
||||
});
|
||||
}
|
||||
41
abbterra/abbterrartudiscovery.h
Normal file
41
abbterra/abbterrartudiscovery.h
Normal file
@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef ABBTERRARTUDISCOVERY_H
|
||||
#define ABBTERRARTUDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <hardware/modbus/modbusrtuhardwareresource.h>
|
||||
|
||||
class AbbTerraRtuDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
QUuid modbusRtuMasterId;
|
||||
quint16 slaveId;
|
||||
QString serialNumber;
|
||||
QString productName;
|
||||
QString firmwareVersion;
|
||||
};
|
||||
|
||||
explicit AbbTerraRtuDiscovery(ModbusRtuHardwareResource *modbusRtuResource, QObject *parent = nullptr);
|
||||
|
||||
void startDiscovery();
|
||||
QList<Result> results() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished(bool modbusRtuMasterAvailable);
|
||||
|
||||
private:
|
||||
void scanNext();
|
||||
|
||||
private:
|
||||
ModbusRtuHardwareResource *m_modbusRtuResource = nullptr;
|
||||
QList<ModbusRtuMaster *> m_candidateMasters;
|
||||
QList<Result> m_results;
|
||||
int m_masterIndex = 0;
|
||||
quint16 m_slaveId = 1;
|
||||
};
|
||||
|
||||
#endif // ABBTERRARTUDISCOVERY_H
|
||||
127
abbterra/abbterratcpdiscovery.cpp
Normal file
127
abbterra/abbterratcpdiscovery.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "abbterratcpdiscovery.h"
|
||||
#include "abbterrautils.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
#include <utility>
|
||||
|
||||
AbbTerraTcpDiscovery::AbbTerraTcpDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_networkDeviceDiscovery(networkDeviceDiscovery)
|
||||
{
|
||||
}
|
||||
|
||||
void AbbTerraTcpDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcAbbTerra()) << "Discovery: Starting to search for ABB Terra AC chargers on the network...";
|
||||
|
||||
m_startDateTime = QDateTime::currentDateTime();
|
||||
m_networkDeviceInfos.clear();
|
||||
m_temporaryResults.clear();
|
||||
m_results.clear();
|
||||
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered, this, &AbbTerraTcpDiscovery::checkNetworkDevice);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [this, discoveryReply]() {
|
||||
m_networkDeviceInfos = discoveryReply->networkDeviceInfos();
|
||||
QTimer::singleShot(3000, this, &AbbTerraTcpDiscovery::finishDiscovery);
|
||||
});
|
||||
}
|
||||
|
||||
QList<AbbTerraTcpDiscovery::Result> AbbTerraTcpDiscovery::results() const
|
||||
{
|
||||
return m_results;
|
||||
}
|
||||
|
||||
void AbbTerraTcpDiscovery::checkNetworkDevice(const QHostAddress &address)
|
||||
{
|
||||
AbbTerraModbusTcpConnection *connection = new AbbTerraModbusTcpConnection(address, 502, 1, this);
|
||||
m_connections.append(connection);
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::reachableChanged, this, [this, connection](bool reachable) {
|
||||
if (!reachable) {
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
connection->initialize();
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::initializationFinished, this, [this, connection, address](bool success) {
|
||||
if (!success) {
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
const AbbTerraUtils::DeviceInfo deviceInfo = AbbTerraUtils::deviceInfoFromValues(connection->serialNumber(),
|
||||
connection->firmwareVersionRaw(),
|
||||
connection->userSettableMaxCurrent());
|
||||
if (deviceInfo.valid) {
|
||||
Result result;
|
||||
result.serialNumber = deviceInfo.serialNumber;
|
||||
result.productName = deviceInfo.productName;
|
||||
result.firmwareVersion = deviceInfo.firmwareVersion;
|
||||
result.networkDeviceInfo = m_networkDeviceInfos.get(address);
|
||||
if (result.networkDeviceInfo.address().isNull()) {
|
||||
NetworkDeviceInfo info;
|
||||
info.setAddress(address);
|
||||
result.networkDeviceInfo = info;
|
||||
}
|
||||
m_temporaryResults.append(result);
|
||||
}
|
||||
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionErrorOccurred, this, [this, connection](QModbusDevice::Error error) {
|
||||
if (error != QModbusDevice::NoError) {
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::checkReachabilityFailed, this, [this, connection]() {
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void AbbTerraTcpDiscovery::cleanupConnection(AbbTerraModbusTcpConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void AbbTerraTcpDiscovery::finishDiscovery()
|
||||
{
|
||||
foreach (const Result &result, m_temporaryResults) {
|
||||
bool known = false;
|
||||
foreach (const Result &existing, m_results) {
|
||||
if (existing.serialNumber == result.serialNumber || existing.networkDeviceInfo.address() == result.networkDeviceInfo.address()) {
|
||||
known = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!known) {
|
||||
qCDebug(dcAbbTerra()) << "Discovery: Found" << result.productName << result.networkDeviceInfo;
|
||||
m_results.append(result);
|
||||
}
|
||||
}
|
||||
|
||||
const QList<AbbTerraModbusTcpConnection *> leftoverConnections = m_connections;
|
||||
foreach (AbbTerraModbusTcpConnection *connection, leftoverConnections) {
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
|
||||
const qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
qCInfo(dcAbbTerra()) << "Discovery: Finished ABB Terra AC network discovery in"
|
||||
<< QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz")
|
||||
<< "with" << m_results.count() << "result(s).";
|
||||
emit discoveryFinished();
|
||||
}
|
||||
46
abbterra/abbterratcpdiscovery.h
Normal file
46
abbterra/abbterratcpdiscovery.h
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef ABBTERRATCPDISCOVERY_H
|
||||
#define ABBTERRATCPDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "abbterramodbustcpconnection.h"
|
||||
|
||||
class AbbTerraTcpDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
QString serialNumber;
|
||||
QString productName;
|
||||
QString firmwareVersion;
|
||||
};
|
||||
|
||||
explicit AbbTerraTcpDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
|
||||
|
||||
void startDiscovery();
|
||||
QList<Result> results() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
void checkNetworkDevice(const QHostAddress &address);
|
||||
void cleanupConnection(AbbTerraModbusTcpConnection *connection);
|
||||
void finishDiscovery();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
QDateTime m_startDateTime;
|
||||
NetworkDeviceInfos m_networkDeviceInfos;
|
||||
QList<AbbTerraModbusTcpConnection *> m_connections;
|
||||
QList<Result> m_results;
|
||||
QList<Result> m_temporaryResults;
|
||||
};
|
||||
|
||||
#endif // ABBTERRATCPDISCOVERY_H
|
||||
145
abbterra/abbterrautils.h
Normal file
145
abbterra/abbterrautils.h
Normal file
@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef ABBTERRAUTILS_H
|
||||
#define ABBTERRAUTILS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include <modbusdatautils.h>
|
||||
|
||||
namespace AbbTerraUtils {
|
||||
|
||||
struct DeviceInfo {
|
||||
bool valid = false;
|
||||
QString serialNumber;
|
||||
QString productName;
|
||||
QString firmwareVersion;
|
||||
double maxChargingCurrent = 32.0;
|
||||
};
|
||||
|
||||
inline QString connectorTypeName(quint8 connectorType)
|
||||
{
|
||||
switch (connectorType) {
|
||||
case 'G':
|
||||
return QStringLiteral("Cable");
|
||||
case 'P':
|
||||
return QStringLiteral("Outlet");
|
||||
case 'S':
|
||||
return QStringLiteral("Socket");
|
||||
case 'T':
|
||||
return QStringLiteral("Socket");
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
inline int ratedPowerFromCode(quint8 ratedPowerCode)
|
||||
{
|
||||
switch (ratedPowerCode) {
|
||||
case 7:
|
||||
return 7;
|
||||
case 11:
|
||||
return 11;
|
||||
case 22:
|
||||
return 22;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
inline DeviceInfo deviceInfoFromValues(quint64 serialNumberRaw, quint32 firmwareVersionRaw, quint32 maxCurrentRaw)
|
||||
{
|
||||
DeviceInfo deviceInfo;
|
||||
|
||||
const quint8 connectorType = static_cast<quint8>((serialNumberRaw >> 56) & 0xff);
|
||||
const quint8 ratedPowerCode = static_cast<quint8>((serialNumberRaw >> 48) & 0xff);
|
||||
const quint8 plantId = static_cast<quint8>((serialNumberRaw >> 40) & 0xff);
|
||||
const quint8 productionWeek = static_cast<quint8>((serialNumberRaw >> 24) & 0xff);
|
||||
const quint8 productionYear = static_cast<quint8>((serialNumberRaw >> 16) & 0xff);
|
||||
const quint8 uniqueHigh = static_cast<quint8>((serialNumberRaw >> 8) & 0xff);
|
||||
const quint8 uniqueLow = static_cast<quint8>(serialNumberRaw & 0xff);
|
||||
const int ratedPower = ratedPowerFromCode(ratedPowerCode);
|
||||
const QString connectorName = connectorTypeName(connectorType);
|
||||
|
||||
if (ratedPower <= 0 || connectorName.isEmpty() || productionWeek == 0 || productionWeek > 53) {
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
deviceInfo.valid = true;
|
||||
deviceInfo.serialNumber = QStringLiteral("TACW%1-%2-%3%4-%5%6%7")
|
||||
.arg(ratedPower)
|
||||
.arg(plantId)
|
||||
.arg(productionWeek, 2, 10, QLatin1Char('0'))
|
||||
.arg(productionYear, 2, 10, QLatin1Char('0'))
|
||||
.arg(QChar(static_cast<char>(connectorType)))
|
||||
.arg(uniqueHigh, 2, 10, QLatin1Char('0'))
|
||||
.arg(uniqueLow, 2, 10, QLatin1Char('0'));
|
||||
deviceInfo.productName = QStringLiteral("ABB Terra AC %1 kW %2").arg(ratedPower).arg(connectorName);
|
||||
deviceInfo.firmwareVersion = QStringLiteral("%1.%2.%3")
|
||||
.arg((firmwareVersionRaw >> 24) & 0xff)
|
||||
.arg((firmwareVersionRaw >> 16) & 0xff)
|
||||
.arg((firmwareVersionRaw >> 8) & 0xff);
|
||||
if (maxCurrentRaw >= 6000 && maxCurrentRaw <= 32000) {
|
||||
deviceInfo.maxChargingCurrent = maxCurrentRaw / 1000.0;
|
||||
}
|
||||
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
inline DeviceInfo deviceInfoFromRegisters(const QVector<quint16> ®isters)
|
||||
{
|
||||
if (registers.count() < 8) {
|
||||
return DeviceInfo();
|
||||
}
|
||||
|
||||
return deviceInfoFromValues(
|
||||
ModbusDataUtils::convertToUInt64(registers.mid(0, 4), ModbusDataUtils::ByteOrderBigEndian),
|
||||
ModbusDataUtils::convertToUInt32(registers.mid(4, 2), ModbusDataUtils::ByteOrderBigEndian),
|
||||
ModbusDataUtils::convertToUInt32(registers.mid(6, 2), ModbusDataUtils::ByteOrderBigEndian)
|
||||
);
|
||||
}
|
||||
|
||||
inline quint8 chargingStateCode(quint32 chargingStateRaw)
|
||||
{
|
||||
return static_cast<quint8>((chargingStateRaw >> 8) & 0x7f);
|
||||
}
|
||||
|
||||
inline bool chargingLimitedByCar(quint32 chargingStateRaw)
|
||||
{
|
||||
return ((chargingStateRaw >> 15) & 0x1) == 0x1;
|
||||
}
|
||||
|
||||
inline bool isVehiclePluggedIn(quint32 chargingStateRaw, quint32 socketLockState)
|
||||
{
|
||||
const quint8 chargingState = chargingStateCode(chargingStateRaw);
|
||||
if (chargingState >= 3 && chargingState <= 6) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return socketLockState == 0x0101 || socketLockState == 0x0111;
|
||||
}
|
||||
|
||||
inline bool isCharging(quint32 chargingStateRaw, quint32 activePower)
|
||||
{
|
||||
return chargingStateCode(chargingStateRaw) == 6 || activePower > 100;
|
||||
}
|
||||
|
||||
inline uint phaseCount(quint32 voltageL1, quint32 voltageL2, quint32 voltageL3)
|
||||
{
|
||||
uint phases = 0;
|
||||
if (voltageL1 > 1000) {
|
||||
phases++;
|
||||
}
|
||||
if (voltageL2 > 1000) {
|
||||
phases++;
|
||||
}
|
||||
if (voltageL3 > 1000) {
|
||||
phases++;
|
||||
}
|
||||
return qMax(phases, 1u);
|
||||
}
|
||||
|
||||
} // namespace AbbTerraUtils
|
||||
|
||||
#endif // ABBTERRAUTILS_H
|
||||
BIN
abbterra/docs/ABB_Terra_AC_Charger_ModbusCommunication_v1.7.pdf
Normal file
BIN
abbterra/docs/ABB_Terra_AC_Charger_ModbusCommunication_v1.7.pdf
Normal file
Binary file not shown.
483
abbterra/integrationpluginabbterra.cpp
Normal file
483
abbterra/integrationpluginabbterra.cpp
Normal file
@ -0,0 +1,483 @@
|
||||
#include "integrationpluginabbterra.h"
|
||||
#include "abbterrartudiscovery.h"
|
||||
#include "abbterratcpdiscovery.h"
|
||||
#include "abbterrautils.h"
|
||||
|
||||
#include "integrations/thing.h"
|
||||
#include "integrations/thingactioninfo.h"
|
||||
#include "integrations/thingdescriptor.h"
|
||||
#include "integrations/thingdiscoveryinfo.h"
|
||||
#include "integrations/thingsetupinfo.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QtMath>
|
||||
#include <utility>
|
||||
|
||||
#include <hardwaremanager.h>
|
||||
#include <hardware/modbus/modbusrtuhardwareresource.h>
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
IntegrationPluginAbbterra::IntegrationPluginAbbterra()
|
||||
{
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == terraAcTcpThingClassId) {
|
||||
AbbTerraTcpDiscovery *discovery = new AbbTerraTcpDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
|
||||
connect(discovery, &AbbTerraTcpDiscovery::discoveryFinished, info, [this, info, discovery]() {
|
||||
foreach (const AbbTerraTcpDiscovery::Result &result, discovery->results()) {
|
||||
ThingDescriptor descriptor(terraAcTcpThingClassId, result.productName, result.serialNumber);
|
||||
ParamList params;
|
||||
params.append(Param(terraAcTcpThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress()));
|
||||
params.append(Param(terraAcTcpThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName()));
|
||||
params.append(Param(terraAcTcpThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress()));
|
||||
params.append(Param(terraAcTcpThingPortParamTypeId, 502));
|
||||
params.append(Param(terraAcTcpThingSlaveIdParamTypeId, 1));
|
||||
descriptor.setParams(params);
|
||||
|
||||
if (Thing *existingThing = myThings().findByParams(params)) {
|
||||
descriptor.setThingId(existingThing->id());
|
||||
}
|
||||
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
discovery->startDiscovery();
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->thingClassId() == terraAcRtuThingClassId) {
|
||||
AbbTerraRtuDiscovery *discovery = new AbbTerraRtuDiscovery(hardwareManager()->modbusRtuResource(), info);
|
||||
connect(discovery, &AbbTerraRtuDiscovery::discoveryFinished, info, [this, info, discovery](bool modbusRtuMasterAvailable) {
|
||||
if (!modbusRtuMasterAvailable) {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No connected Modbus RTU master available."));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const AbbTerraRtuDiscovery::Result &result, discovery->results()) {
|
||||
ThingDescriptor descriptor(terraAcRtuThingClassId, result.productName, QStringLiteral("Slave ID: %1").arg(result.slaveId));
|
||||
ParamList params{
|
||||
Param(terraAcRtuThingRtuMasterParamTypeId, result.modbusRtuMasterId),
|
||||
Param(terraAcRtuThingSlaveIdParamTypeId, result.slaveId)
|
||||
};
|
||||
descriptor.setParams(params);
|
||||
|
||||
if (Thing *existingThing = myThings().findByParams(params)) {
|
||||
descriptor.setThingId(existingThing->id());
|
||||
}
|
||||
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
discovery->startDiscovery();
|
||||
return;
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
if (info->thing()->thingClassId() == terraAcTcpThingClassId) {
|
||||
setupTcpThing(info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->thing()->thingClassId() == terraAcRtuThingClassId) {
|
||||
setupRtuThing(info);
|
||||
return;
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::postSetupThing(Thing *thing)
|
||||
{
|
||||
Q_UNUSED(thing)
|
||||
|
||||
if (m_pluginTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(30);
|
||||
connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() {
|
||||
foreach (AbbTerraModbusTcpConnection *connection, m_tcpConnections) {
|
||||
connection->update();
|
||||
}
|
||||
foreach (AbbTerraModbusRtuConnection *connection, m_rtuConnections) {
|
||||
connection->update();
|
||||
}
|
||||
});
|
||||
m_pluginTimer->start();
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::thingRemoved(Thing *thing)
|
||||
{
|
||||
delete m_tcpConnections.take(thing);
|
||||
delete m_rtuConnections.take(thing);
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
|
||||
if (myThings().isEmpty() && m_pluginTimer) {
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
||||
m_pluginTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
if (thing->thingClassId() == terraAcTcpThingClassId) {
|
||||
AbbTerraModbusTcpConnection *connection = m_tcpConnections.value(thing);
|
||||
if (!connection || !connection->reachable()) {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The charging station is not reachable."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->action().actionTypeId() == terraAcTcpPowerActionTypeId) {
|
||||
const bool power = info->action().paramValue(terraAcTcpPowerActionPowerParamTypeId).toBool();
|
||||
const quint32 currentMilliAmps = power ? static_cast<quint32>(qRound(thing->stateValue(terraAcTcpMaxChargingCurrentStateTypeId).toDouble() * 1000.0)) : 0;
|
||||
QModbusReply *reply = connection->setChargingCurrentLimitCommand(currentMilliAmps);
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, info, [info, thing, connection, reply, power]() {
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
thing->setStateValue(terraAcTcpPowerStateTypeId, power);
|
||||
connection->update();
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->action().actionTypeId() == terraAcTcpMaxChargingCurrentActionTypeId) {
|
||||
const double current = info->action().paramValue(terraAcTcpMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble();
|
||||
QModbusReply *reply = connection->setChargingCurrentLimitCommand(static_cast<quint32>(qRound(current * 1000.0)));
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, info, [info, thing, connection, reply, current]() {
|
||||
if (reply->error() == QModbusDevice::NoError) {
|
||||
thing->setStateValue(terraAcTcpMaxChargingCurrentStateTypeId, current);
|
||||
thing->setStateValue(terraAcTcpPowerStateTypeId, current >= 6.0);
|
||||
connection->update();
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
return;
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == terraAcRtuThingClassId) {
|
||||
AbbTerraModbusRtuConnection *connection = m_rtuConnections.value(thing);
|
||||
if (!connection || !connection->reachable()) {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The charging station is not reachable."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->action().actionTypeId() == terraAcRtuPowerActionTypeId) {
|
||||
const bool power = info->action().paramValue(terraAcRtuPowerActionPowerParamTypeId).toBool();
|
||||
const quint32 currentMilliAmps = power ? static_cast<quint32>(qRound(thing->stateValue(terraAcRtuMaxChargingCurrentStateTypeId).toDouble() * 1000.0)) : 0;
|
||||
ModbusRtuReply *reply = connection->setChargingCurrentLimitCommand(currentMilliAmps);
|
||||
connect(reply, &ModbusRtuReply::finished, info, [info, thing, connection, reply, power]() {
|
||||
if (reply->error() == ModbusRtuReply::NoError) {
|
||||
thing->setStateValue(terraAcRtuPowerStateTypeId, power);
|
||||
connection->update();
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->action().actionTypeId() == terraAcRtuMaxChargingCurrentActionTypeId) {
|
||||
const double current = info->action().paramValue(terraAcRtuMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toDouble();
|
||||
ModbusRtuReply *reply = connection->setChargingCurrentLimitCommand(static_cast<quint32>(qRound(current * 1000.0)));
|
||||
connect(reply, &ModbusRtuReply::finished, info, [info, thing, connection, reply, current]() {
|
||||
if (reply->error() == ModbusRtuReply::NoError) {
|
||||
thing->setStateValue(terraAcRtuMaxChargingCurrentStateTypeId, current);
|
||||
thing->setStateValue(terraAcRtuPowerStateTypeId, current >= 6.0);
|
||||
connection->update();
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
} else {
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature);
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::setupTcpThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
if (m_tcpConnections.contains(thing)) {
|
||||
m_tcpConnections.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
|
||||
if (!monitor) {
|
||||
monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing);
|
||||
if (!monitor) {
|
||||
info->finish(Thing::ThingErrorInvalidParameter);
|
||||
return;
|
||||
}
|
||||
m_monitors.insert(thing, monitor);
|
||||
}
|
||||
|
||||
const quint16 port = static_cast<quint16>(thing->paramValue(terraAcTcpThingPortParamTypeId).toUInt());
|
||||
const quint16 slaveId = static_cast<quint16>(thing->paramValue(terraAcTcpThingSlaveIdParamTypeId).toUInt());
|
||||
AbbTerraModbusTcpConnection *connection = new AbbTerraModbusTcpConnection(monitor->networkDeviceInfo().address(), port, slaveId, thing);
|
||||
|
||||
connect(info, &ThingSetupInfo::aborted, connection, &AbbTerraModbusTcpConnection::deleteLater);
|
||||
connect(info, &ThingSetupInfo::aborted, monitor, [this, thing]() {
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
});
|
||||
|
||||
connect(monitor, &NetworkDeviceMonitor::networkDeviceInfoChanged, connection, [connection](const NetworkDeviceInfo &networkDeviceInfo) {
|
||||
connection->modbusTcpMaster()->setHostAddress(networkDeviceInfo.address());
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::reachableChanged, thing, [this, thing, connection](bool reachable) {
|
||||
if (reachable) {
|
||||
connection->initialize();
|
||||
} else {
|
||||
setDisconnectedState(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::initializationFinished, thing, [this, thing, connection](bool success) {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const AbbTerraUtils::DeviceInfo deviceInfo = AbbTerraUtils::deviceInfoFromValues(connection->serialNumber(),
|
||||
connection->firmwareVersionRaw(),
|
||||
connection->userSettableMaxCurrent());
|
||||
if (!deviceInfo.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
thing->setStateValue(terraAcTcpConnectedStateTypeId, true);
|
||||
thing->setStateValue(terraAcTcpFirmwareVersionStateTypeId, deviceInfo.firmwareVersion);
|
||||
thing->setStateValue(terraAcTcpSerialNumberStateTypeId, deviceInfo.serialNumber);
|
||||
thing->setStateMinMaxValues(terraAcTcpMaxChargingCurrentStateTypeId, 6.0, deviceInfo.maxChargingCurrent);
|
||||
applyTimeoutSetting(thing, connection);
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::initializationFinished, info, [this, info, thing, connection](bool success) {
|
||||
if (!success) {
|
||||
connection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the charger."));
|
||||
return;
|
||||
}
|
||||
|
||||
const AbbTerraUtils::DeviceInfo deviceInfo = AbbTerraUtils::deviceInfoFromValues(connection->serialNumber(),
|
||||
connection->firmwareVersionRaw(),
|
||||
connection->userSettableMaxCurrent());
|
||||
if (!deviceInfo.valid) {
|
||||
connection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The device does not match the ABB Terra AC Modbus register map."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_tcpConnections.insert(thing, connection);
|
||||
connection->update();
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusTcpConnection::updateFinished, thing, [this, thing, connection]() {
|
||||
updateThing(thing, connection);
|
||||
});
|
||||
|
||||
connect(thing, &Thing::settingChanged, connection, [this, thing, connection](const ParamTypeId ¶mTypeId, const QVariant &) {
|
||||
if (paramTypeId == terraAcTcpSettingsCommunicationTimeoutParamTypeId) {
|
||||
applyTimeoutSetting(thing, connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::setupRtuThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
if (m_rtuConnections.contains(thing)) {
|
||||
m_rtuConnections.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
ModbusRtuMaster *master = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(thing->paramValue(terraAcRtuThingRtuMasterParamTypeId).toUuid());
|
||||
if (!master) {
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The Modbus RTU connection is not available."));
|
||||
return;
|
||||
}
|
||||
|
||||
const quint16 slaveId = static_cast<quint16>(thing->paramValue(terraAcRtuThingSlaveIdParamTypeId).toUInt());
|
||||
AbbTerraModbusRtuConnection *connection = new AbbTerraModbusRtuConnection(master, slaveId, thing);
|
||||
|
||||
connect(connection, &AbbTerraModbusRtuConnection::reachableChanged, thing, [this, thing, connection](bool reachable) {
|
||||
if (reachable) {
|
||||
connection->initialize();
|
||||
} else {
|
||||
setDisconnectedState(thing);
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusRtuConnection::initializationFinished, thing, [this, thing, connection](bool success) {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const AbbTerraUtils::DeviceInfo deviceInfo = AbbTerraUtils::deviceInfoFromValues(connection->serialNumber(),
|
||||
connection->firmwareVersionRaw(),
|
||||
connection->userSettableMaxCurrent());
|
||||
if (!deviceInfo.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
thing->setStateValue(terraAcRtuConnectedStateTypeId, true);
|
||||
thing->setStateValue(terraAcRtuFirmwareVersionStateTypeId, deviceInfo.firmwareVersion);
|
||||
thing->setStateValue(terraAcRtuSerialNumberStateTypeId, deviceInfo.serialNumber);
|
||||
thing->setStateMinMaxValues(terraAcRtuMaxChargingCurrentStateTypeId, 6.0, deviceInfo.maxChargingCurrent);
|
||||
applyTimeoutSetting(thing, connection);
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusRtuConnection::initializationFinished, info, [this, info, thing, connection](bool success) {
|
||||
if (!success) {
|
||||
connection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the charger."));
|
||||
return;
|
||||
}
|
||||
|
||||
const AbbTerraUtils::DeviceInfo deviceInfo = AbbTerraUtils::deviceInfoFromValues(connection->serialNumber(),
|
||||
connection->firmwareVersionRaw(),
|
||||
connection->userSettableMaxCurrent());
|
||||
if (!deviceInfo.valid) {
|
||||
connection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The device does not match the ABB Terra AC Modbus register map."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_rtuConnections.insert(thing, connection);
|
||||
connection->update();
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
connect(connection, &AbbTerraModbusRtuConnection::updateFinished, thing, [this, thing, connection]() {
|
||||
updateThing(thing, connection);
|
||||
});
|
||||
|
||||
connect(thing, &Thing::settingChanged, connection, [this, thing, connection](const ParamTypeId ¶mTypeId, const QVariant &) {
|
||||
if (paramTypeId == terraAcRtuSettingsCommunicationTimeoutParamTypeId) {
|
||||
applyTimeoutSetting(thing, connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::applyTimeoutSetting(Thing *thing, AbbTerraModbusTcpConnection *connection)
|
||||
{
|
||||
QModbusReply *reply = connection->setCommunicationTimeoutCommand(static_cast<quint16>(thing->setting(terraAcTcpSettingsCommunicationTimeoutParamTypeId).toUInt()));
|
||||
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
|
||||
connect(reply, &QModbusReply::finished, connection, [connection, reply]() {
|
||||
if (reply->error() != QModbusDevice::NoError && connection->reachable()) {
|
||||
connection->updateCommunicationTimeoutReadback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::applyTimeoutSetting(Thing *thing, AbbTerraModbusRtuConnection *connection)
|
||||
{
|
||||
ModbusRtuReply *reply = connection->setCommunicationTimeoutCommand(static_cast<quint16>(thing->setting(terraAcRtuSettingsCommunicationTimeoutParamTypeId).toUInt()));
|
||||
connect(reply, &ModbusRtuReply::finished, connection, [connection, reply]() {
|
||||
if (reply->error() != ModbusRtuReply::NoError && connection->reachable()) {
|
||||
connection->updateCommunicationTimeoutReadback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::updateThing(Thing *thing, AbbTerraModbusTcpConnection *connection)
|
||||
{
|
||||
thing->setStateValue(terraAcTcpConnectedStateTypeId, connection->reachable());
|
||||
thing->setStateValue(terraAcTcpPluggedInStateTypeId, AbbTerraUtils::isVehiclePluggedIn(connection->chargingStateRaw(), connection->socketLockState()));
|
||||
thing->setStateValue(terraAcTcpChargingStateTypeId, AbbTerraUtils::isCharging(connection->chargingStateRaw(), connection->activePower()));
|
||||
thing->setStateValue(terraAcTcpPowerStateTypeId, connection->chargingCurrentLimit() >= 6000);
|
||||
thing->setStateValue(terraAcTcpMaxChargingCurrentStateTypeId, connection->chargingCurrentLimit() / 1000.0);
|
||||
thing->setStateValue(terraAcTcpPhaseCountStateTypeId, AbbTerraUtils::phaseCount(connection->voltageL1(), connection->voltageL2(), connection->voltageL3()));
|
||||
thing->setStateValue(terraAcTcpCurrentPowerStateTypeId, static_cast<double>(connection->activePower()));
|
||||
thing->setStateValue("currentPhase1", connection->currentL1() / 1000.0);
|
||||
thing->setStateValue("currentPhase2", connection->currentL2() / 1000.0);
|
||||
thing->setStateValue("currentPhase3", connection->currentL3() / 1000.0);
|
||||
thing->setStateValue("voltagePhase1", connection->voltageL1() / 10.0);
|
||||
thing->setStateValue("voltagePhase2", connection->voltageL2() / 10.0);
|
||||
thing->setStateValue("voltagePhase3", connection->voltageL3() / 10.0);
|
||||
thing->setStateValue(terraAcTcpSessionEnergyStateTypeId, connection->sessionEnergy() / 1000.0);
|
||||
thing->setStateValue(terraAcTcpErrorCodeStateTypeId, connection->errorCode());
|
||||
thing->setSettingValue(terraAcTcpSettingsCommunicationTimeoutParamTypeId, connection->communicationTimeoutReadback());
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::updateThing(Thing *thing, AbbTerraModbusRtuConnection *connection)
|
||||
{
|
||||
thing->setStateValue(terraAcRtuConnectedStateTypeId, connection->reachable());
|
||||
thing->setStateValue(terraAcRtuPluggedInStateTypeId, AbbTerraUtils::isVehiclePluggedIn(connection->chargingStateRaw(), connection->socketLockState()));
|
||||
thing->setStateValue(terraAcRtuChargingStateTypeId, AbbTerraUtils::isCharging(connection->chargingStateRaw(), connection->activePower()));
|
||||
thing->setStateValue(terraAcRtuPowerStateTypeId, connection->chargingCurrentLimit() >= 6000);
|
||||
thing->setStateValue(terraAcRtuMaxChargingCurrentStateTypeId, connection->chargingCurrentLimit() / 1000.0);
|
||||
thing->setStateValue(terraAcRtuPhaseCountStateTypeId, AbbTerraUtils::phaseCount(connection->voltageL1(), connection->voltageL2(), connection->voltageL3()));
|
||||
thing->setStateValue(terraAcRtuCurrentPowerStateTypeId, static_cast<double>(connection->activePower()));
|
||||
thing->setStateValue("currentPhase1", connection->currentL1() / 1000.0);
|
||||
thing->setStateValue("currentPhase2", connection->currentL2() / 1000.0);
|
||||
thing->setStateValue("currentPhase3", connection->currentL3() / 1000.0);
|
||||
thing->setStateValue("voltagePhase1", connection->voltageL1() / 10.0);
|
||||
thing->setStateValue("voltagePhase2", connection->voltageL2() / 10.0);
|
||||
thing->setStateValue("voltagePhase3", connection->voltageL3() / 10.0);
|
||||
thing->setStateValue(terraAcRtuSessionEnergyStateTypeId, connection->sessionEnergy() / 1000.0);
|
||||
thing->setStateValue(terraAcRtuErrorCodeStateTypeId, connection->errorCode());
|
||||
thing->setSettingValue(terraAcRtuSettingsCommunicationTimeoutParamTypeId, connection->communicationTimeoutReadback());
|
||||
}
|
||||
|
||||
void IntegrationPluginAbbterra::setDisconnectedState(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == terraAcTcpThingClassId) {
|
||||
thing->setStateValue(terraAcTcpConnectedStateTypeId, false);
|
||||
thing->setStateValue(terraAcTcpChargingStateTypeId, false);
|
||||
thing->setStateValue(terraAcTcpPluggedInStateTypeId, false);
|
||||
thing->setStateValue(terraAcTcpCurrentPowerStateTypeId, 0);
|
||||
thing->setStateValue("currentPhase1", 0);
|
||||
thing->setStateValue("currentPhase2", 0);
|
||||
thing->setStateValue("currentPhase3", 0);
|
||||
thing->setStateValue("voltagePhase1", 0);
|
||||
thing->setStateValue("voltagePhase2", 0);
|
||||
thing->setStateValue("voltagePhase3", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == terraAcRtuThingClassId) {
|
||||
thing->setStateValue(terraAcRtuConnectedStateTypeId, false);
|
||||
thing->setStateValue(terraAcRtuChargingStateTypeId, false);
|
||||
thing->setStateValue(terraAcRtuPluggedInStateTypeId, false);
|
||||
thing->setStateValue(terraAcRtuCurrentPowerStateTypeId, 0);
|
||||
thing->setStateValue("currentPhase1", 0);
|
||||
thing->setStateValue("currentPhase2", 0);
|
||||
thing->setStateValue("currentPhase3", 0);
|
||||
thing->setStateValue("voltagePhase1", 0);
|
||||
thing->setStateValue("voltagePhase2", 0);
|
||||
thing->setStateValue("voltagePhase3", 0);
|
||||
}
|
||||
}
|
||||
46
abbterra/integrationpluginabbterra.h
Normal file
46
abbterra/integrationpluginabbterra.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef INTEGRATIONPLUGINABBTERRA_H
|
||||
#define INTEGRATIONPLUGINABBTERRA_H
|
||||
|
||||
#include <plugintimer.h>
|
||||
#include "integrations/integrationplugin.h"
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
#include "abbterramodbusrtuconnection.h"
|
||||
#include "abbterramodbustcpconnection.h"
|
||||
|
||||
class IntegrationPluginAbbterra : public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginabbterra.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
explicit IntegrationPluginAbbterra();
|
||||
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
public slots:
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
|
||||
private:
|
||||
void setupTcpThing(ThingSetupInfo *info);
|
||||
void setupRtuThing(ThingSetupInfo *info);
|
||||
void applyTimeoutSetting(Thing *thing, AbbTerraModbusTcpConnection *connection);
|
||||
void applyTimeoutSetting(Thing *thing, AbbTerraModbusRtuConnection *connection);
|
||||
void updateThing(Thing *thing, AbbTerraModbusTcpConnection *connection);
|
||||
void updateThing(Thing *thing, AbbTerraModbusRtuConnection *connection);
|
||||
void setDisconnectedState(Thing *thing);
|
||||
|
||||
private:
|
||||
PluginTimer *m_pluginTimer = nullptr;
|
||||
QHash<Thing *, AbbTerraModbusTcpConnection *> m_tcpConnections;
|
||||
QHash<Thing *, AbbTerraModbusRtuConnection *> m_rtuConnections;
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINABBTERRA_H
|
||||
416
abbterra/integrationpluginabbterra.json
Normal file
416
abbterra/integrationpluginabbterra.json
Normal file
@ -0,0 +1,416 @@
|
||||
{
|
||||
"name": "AbbTerra",
|
||||
"displayName": "ABB Terra AC",
|
||||
"id": "d7f1cb28-b18b-449e-8cd2-1d99b9d8f681",
|
||||
"paramTypes": [],
|
||||
"vendors": [
|
||||
{
|
||||
"id": "0369ebad-5186-437d-b520-041a0b9b7582",
|
||||
"name": "abb",
|
||||
"displayName": "ABB",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "93ad828a-9a7a-4fca-be3f-88641317845f",
|
||||
"name": "terraAcTcp",
|
||||
"displayName": "Terra AC Charger (TCP)",
|
||||
"interfaces": [
|
||||
"evcharger",
|
||||
"connectable",
|
||||
"networkdevice"
|
||||
],
|
||||
"createMethods": [
|
||||
"discovery",
|
||||
"user"
|
||||
],
|
||||
"discoveryType": "weak",
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "9b3332aa-6c26-4399-b3dc-d7fdbe3f4420",
|
||||
"name": "macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": "",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "962ef9d9-30a2-4636-8ff2-2a28742277f4",
|
||||
"name": "address",
|
||||
"displayName": "Host address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "f9e6fed4-2a18-4f23-b70e-78919d4b01f8",
|
||||
"name": "hostName",
|
||||
"displayName": "Host name",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "153258dd-e81b-48e5-9ba7-46d59e854d85",
|
||||
"name": "port",
|
||||
"displayName": "Port",
|
||||
"type": "uint",
|
||||
"defaultValue": 502
|
||||
},
|
||||
{
|
||||
"id": "cc4c66c1-185f-4c97-88a0-2bc66319d157",
|
||||
"name": "slaveId",
|
||||
"displayName": "Slave ID",
|
||||
"type": "uint",
|
||||
"defaultValue": 1,
|
||||
"minValue": 1,
|
||||
"maxValue": 255
|
||||
}
|
||||
],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "5a0e678a-ffc8-443a-9ae7-bb2243b0ec4b",
|
||||
"name": "communicationTimeout",
|
||||
"displayName": "Communication timeout",
|
||||
"type": "uint",
|
||||
"unit": "Seconds",
|
||||
"defaultValue": 60,
|
||||
"minValue": 10,
|
||||
"maxValue": 65535
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "21e1a2bb-0e05-462c-9909-4f9a0eada438",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "2fb30a0b-dc5a-4831-b5c6-de653c0c9ee1",
|
||||
"name": "pluggedIn",
|
||||
"displayName": "Plugged in",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "0c685ced-27fa-46ba-8c0b-c49af82054f0",
|
||||
"name": "charging",
|
||||
"displayName": "Charging",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "207e2074-0147-4617-9a8b-3f326dcd6a0b",
|
||||
"name": "power",
|
||||
"displayName": "Charging enabled",
|
||||
"displayNameAction": "Set charging enabled",
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "e3d27f8a-73d0-493a-b99a-29e7dc184485",
|
||||
"name": "maxChargingCurrent",
|
||||
"displayName": "Maximum charging current",
|
||||
"displayNameAction": "Set maximum charging current",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"minValue": 6,
|
||||
"maxValue": 32,
|
||||
"stepSize": 0.1,
|
||||
"defaultValue": 6,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "0764bce9-fd26-4da8-8d92-f6a5ce73e81e",
|
||||
"name": "phaseCount",
|
||||
"displayName": "Phase count",
|
||||
"type": "uint",
|
||||
"minValue": 1,
|
||||
"maxValue": 3,
|
||||
"defaultValue": 1
|
||||
},
|
||||
{
|
||||
"id": "59b486a3-aa44-4a94-8478-1077af124441",
|
||||
"name": "currentPower",
|
||||
"displayName": "Active power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "953ce672-a9a2-4cd6-bf87-ccfe720c7595",
|
||||
"name": "currentPhase1",
|
||||
"displayName": "Current phase 1",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "e4f97cde-191c-4c84-9668-169c5e648338",
|
||||
"name": "currentPhase2",
|
||||
"displayName": "Current phase 2",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "c1cead50-9894-4b12-9211-c5bc84109b1d",
|
||||
"name": "currentPhase3",
|
||||
"displayName": "Current phase 3",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "bcff4c2d-5567-40ff-adef-5b47dc5ace76",
|
||||
"name": "voltagePhase1",
|
||||
"displayName": "Voltage phase 1",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "db7af25a-cb7b-4640-9515-a7af345bd770",
|
||||
"name": "voltagePhase2",
|
||||
"displayName": "Voltage phase 2",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "fe67e1bf-6871-45a6-a253-819cb70c5ec4",
|
||||
"name": "voltagePhase3",
|
||||
"displayName": "Voltage phase 3",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "be2cc28e-79c2-4eaf-91ad-8131ac1f87e2",
|
||||
"name": "sessionEnergy",
|
||||
"displayName": "Session energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "6d9b38a7-c6eb-47bc-8929-5a875d0fd5d0",
|
||||
"name": "firmwareVersion",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "69c9f4e2-ec8c-4d65-9cf4-ce8b7a3f8c3b",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "ce98bfda-b27a-4f69-bd4e-49eb3e722245",
|
||||
"name": "errorCode",
|
||||
"displayName": "Error code",
|
||||
"type": "uint",
|
||||
"defaultValue": 0
|
||||
}
|
||||
],
|
||||
"actionTypes": []
|
||||
},
|
||||
{
|
||||
"id": "a385801e-600c-4e27-ad73-5184a7516860",
|
||||
"name": "terraAcRtu",
|
||||
"displayName": "Terra AC Charger (RTU)",
|
||||
"interfaces": [
|
||||
"evcharger",
|
||||
"connectable"
|
||||
],
|
||||
"createMethods": [
|
||||
"discovery",
|
||||
"user"
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "deb19a6b-33b3-417e-abf1-a1fb18585fe4",
|
||||
"name": "rtuMaster",
|
||||
"displayName": "Modbus RTU master",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "23dbb93d-6605-435c-9a41-b8a8e8242ea0",
|
||||
"name": "slaveId",
|
||||
"displayName": "Modbus slave ID",
|
||||
"type": "uint",
|
||||
"defaultValue": 1,
|
||||
"minValue": 1,
|
||||
"maxValue": 247
|
||||
}
|
||||
],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "c4e0a515-270e-403e-8453-02002859938e",
|
||||
"name": "communicationTimeout",
|
||||
"displayName": "Communication timeout",
|
||||
"type": "uint",
|
||||
"unit": "Seconds",
|
||||
"defaultValue": 60,
|
||||
"minValue": 10,
|
||||
"maxValue": 65535
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "c43068ca-dddc-4c43-9149-68927afae5e7",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "14fcf045-52d7-4f47-b64e-2bf3f87c242f",
|
||||
"name": "pluggedIn",
|
||||
"displayName": "Plugged in",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "64708d42-bbfe-44a5-82ae-c8e66e25c5a5",
|
||||
"name": "charging",
|
||||
"displayName": "Charging",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "e35fd4fa-bf5a-45a1-8a39-f0d3d9efa4c6",
|
||||
"name": "power",
|
||||
"displayName": "Charging enabled",
|
||||
"displayNameAction": "Set charging enabled",
|
||||
"type": "bool",
|
||||
"defaultValue": true,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "ea933a77-a098-4303-bbdb-15c72dfd3634",
|
||||
"name": "maxChargingCurrent",
|
||||
"displayName": "Maximum charging current",
|
||||
"displayNameAction": "Set maximum charging current",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"minValue": 6,
|
||||
"maxValue": 32,
|
||||
"stepSize": 0.1,
|
||||
"defaultValue": 6,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "cd1add95-18d9-46b5-a3d5-f0f29d5160c9",
|
||||
"name": "phaseCount",
|
||||
"displayName": "Phase count",
|
||||
"type": "uint",
|
||||
"minValue": 1,
|
||||
"maxValue": 3,
|
||||
"defaultValue": 1
|
||||
},
|
||||
{
|
||||
"id": "152a58cb-0bf1-4d4a-aac2-233d3c68f81b",
|
||||
"name": "currentPower",
|
||||
"displayName": "Active power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "a8e2e2cf-b2ca-48c5-a702-9226062c43e3",
|
||||
"name": "currentPhase1",
|
||||
"displayName": "Current phase 1",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "210ff017-ea75-4705-9f23-f73e324601ef",
|
||||
"name": "currentPhase2",
|
||||
"displayName": "Current phase 2",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "3a5d6ce5-d3ed-4ba5-996f-15a2b3124faf",
|
||||
"name": "currentPhase3",
|
||||
"displayName": "Current phase 3",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "52ebc781-a527-4641-b768-3a0bd0209f96",
|
||||
"name": "voltagePhase1",
|
||||
"displayName": "Voltage phase 1",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "bb513653-70ca-4ee1-a913-aab3d2881708",
|
||||
"name": "voltagePhase2",
|
||||
"displayName": "Voltage phase 2",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "c84a5e75-121d-4af8-a203-23f586613cf2",
|
||||
"name": "voltagePhase3",
|
||||
"displayName": "Voltage phase 3",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "7a9a6ec3-7572-42d8-833d-5b434ca95765",
|
||||
"name": "sessionEnergy",
|
||||
"displayName": "Session energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "071d7408-fa96-49da-b56b-f5564bee9b5d",
|
||||
"name": "firmwareVersion",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "c95ea476-4ce3-4c04-bc23-238bdee496ee",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "1f7d2791-c23e-47fe-8512-850ea25e09b3",
|
||||
"name": "errorCode",
|
||||
"displayName": "Error code",
|
||||
"type": "uint",
|
||||
"defaultValue": 0
|
||||
}
|
||||
],
|
||||
"actionTypes": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
13
abbterra/meta.json
Normal file
13
abbterra/meta.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "ABB Terra AC",
|
||||
"tagline": "Connect ABB Terra AC chargers over Modbus TCP or Modbus RTU.",
|
||||
"stability": "consumer",
|
||||
"icon": "",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"energy"
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,296 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>AbbTerra</name>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="80"/>
|
||||
<source>ABB</source>
|
||||
<extracomment>The name of the vendor ({0369ebad-5186-437d-b520-041a0b9b7582})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="83"/>
|
||||
<source>ABB Terra AC</source>
|
||||
<extracomment>The name of the plugin AbbTerra ({d7f1cb28-b18b-449e-8cd2-1d99b9d8f681})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="86"/>
|
||||
<location filename="../plugininfo.h" line="89"/>
|
||||
<source>Active power</source>
|
||||
<extracomment>The name of the StateType ({152a58cb-0bf1-4d4a-aac2-233d3c68f81b}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({59b486a3-aa44-4a94-8478-1077af124441}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="92"/>
|
||||
<location filename="../plugininfo.h" line="95"/>
|
||||
<source>Charging</source>
|
||||
<extracomment>The name of the StateType ({64708d42-bbfe-44a5-82ae-c8e66e25c5a5}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({0c685ced-27fa-46ba-8c0b-c49af82054f0}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="98"/>
|
||||
<location filename="../plugininfo.h" line="101"/>
|
||||
<location filename="../plugininfo.h" line="104"/>
|
||||
<location filename="../plugininfo.h" line="107"/>
|
||||
<source>Charging enabled</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcRtu, ActionType: power, ID: {e35fd4fa-bf5a-45a1-8a39-f0d3d9efa4c6})
|
||||
----------
|
||||
The name of the StateType ({e35fd4fa-bf5a-45a1-8a39-f0d3d9efa4c6}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the ParamType (ThingClass: terraAcTcp, ActionType: power, ID: {207e2074-0147-4617-9a8b-3f326dcd6a0b})
|
||||
----------
|
||||
The name of the StateType ({207e2074-0147-4617-9a8b-3f326dcd6a0b}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="110"/>
|
||||
<location filename="../plugininfo.h" line="113"/>
|
||||
<source>Communication timeout</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcRtu, Type: settings, ID: {c4e0a515-270e-403e-8453-02002859938e})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: terraAcTcp, Type: settings, ID: {5a0e678a-ffc8-443a-9ae7-bb2243b0ec4b})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="116"/>
|
||||
<location filename="../plugininfo.h" line="119"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({c43068ca-dddc-4c43-9149-68927afae5e7}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({21e1a2bb-0e05-462c-9909-4f9a0eada438}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="122"/>
|
||||
<location filename="../plugininfo.h" line="125"/>
|
||||
<source>Current phase 1</source>
|
||||
<extracomment>The name of the StateType ({a8e2e2cf-b2ca-48c5-a702-9226062c43e3}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({953ce672-a9a2-4cd6-bf87-ccfe720c7595}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="128"/>
|
||||
<location filename="../plugininfo.h" line="131"/>
|
||||
<source>Current phase 2</source>
|
||||
<extracomment>The name of the StateType ({210ff017-ea75-4705-9f23-f73e324601ef}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({e4f97cde-191c-4c84-9668-169c5e648338}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="134"/>
|
||||
<location filename="../plugininfo.h" line="137"/>
|
||||
<source>Current phase 3</source>
|
||||
<extracomment>The name of the StateType ({3a5d6ce5-d3ed-4ba5-996f-15a2b3124faf}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({c1cead50-9894-4b12-9211-c5bc84109b1d}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="140"/>
|
||||
<location filename="../plugininfo.h" line="143"/>
|
||||
<source>Error code</source>
|
||||
<extracomment>The name of the StateType ({1f7d2791-c23e-47fe-8512-850ea25e09b3}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({ce98bfda-b27a-4f69-bd4e-49eb3e722245}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="146"/>
|
||||
<location filename="../plugininfo.h" line="149"/>
|
||||
<source>Firmware version</source>
|
||||
<extracomment>The name of the StateType ({071d7408-fa96-49da-b56b-f5564bee9b5d}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({6d9b38a7-c6eb-47bc-8929-5a875d0fd5d0}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="152"/>
|
||||
<source>Host address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcTcp, Type: thing, ID: {962ef9d9-30a2-4636-8ff2-2a28742277f4})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="155"/>
|
||||
<source>Host name</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcTcp, Type: thing, ID: {f9e6fed4-2a18-4f23-b70e-78919d4b01f8})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="158"/>
|
||||
<source>MAC address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcTcp, Type: thing, ID: {9b3332aa-6c26-4399-b3dc-d7fdbe3f4420})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="161"/>
|
||||
<location filename="../plugininfo.h" line="164"/>
|
||||
<location filename="../plugininfo.h" line="167"/>
|
||||
<location filename="../plugininfo.h" line="170"/>
|
||||
<source>Maximum charging current</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcRtu, ActionType: maxChargingCurrent, ID: {ea933a77-a098-4303-bbdb-15c72dfd3634})
|
||||
----------
|
||||
The name of the StateType ({ea933a77-a098-4303-bbdb-15c72dfd3634}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the ParamType (ThingClass: terraAcTcp, ActionType: maxChargingCurrent, ID: {e3d27f8a-73d0-493a-b99a-29e7dc184485})
|
||||
----------
|
||||
The name of the StateType ({e3d27f8a-73d0-493a-b99a-29e7dc184485}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="173"/>
|
||||
<source>Modbus RTU master</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcRtu, Type: thing, ID: {deb19a6b-33b3-417e-abf1-a1fb18585fe4})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="176"/>
|
||||
<source>Modbus slave ID</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcRtu, Type: thing, ID: {23dbb93d-6605-435c-9a41-b8a8e8242ea0})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="179"/>
|
||||
<location filename="../plugininfo.h" line="182"/>
|
||||
<source>Phase count</source>
|
||||
<extracomment>The name of the StateType ({cd1add95-18d9-46b5-a3d5-f0f29d5160c9}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({0764bce9-fd26-4da8-8d92-f6a5ce73e81e}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="185"/>
|
||||
<location filename="../plugininfo.h" line="188"/>
|
||||
<source>Plugged in</source>
|
||||
<extracomment>The name of the StateType ({14fcf045-52d7-4f47-b64e-2bf3f87c242f}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({2fb30a0b-dc5a-4831-b5c6-de653c0c9ee1}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="191"/>
|
||||
<source>Port</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcTcp, Type: thing, ID: {153258dd-e81b-48e5-9ba7-46d59e854d85})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="194"/>
|
||||
<location filename="../plugininfo.h" line="197"/>
|
||||
<source>Serial number</source>
|
||||
<extracomment>The name of the StateType ({c95ea476-4ce3-4c04-bc23-238bdee496ee}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({69c9f4e2-ec8c-4d65-9cf4-ce8b7a3f8c3b}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="200"/>
|
||||
<location filename="../plugininfo.h" line="203"/>
|
||||
<source>Session energy</source>
|
||||
<extracomment>The name of the StateType ({7a9a6ec3-7572-42d8-833d-5b434ca95765}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({be2cc28e-79c2-4eaf-91ad-8131ac1f87e2}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="206"/>
|
||||
<location filename="../plugininfo.h" line="209"/>
|
||||
<source>Set charging enabled</source>
|
||||
<extracomment>The name of the ActionType ({e35fd4fa-bf5a-45a1-8a39-f0d3d9efa4c6}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the ActionType ({207e2074-0147-4617-9a8b-3f326dcd6a0b}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="212"/>
|
||||
<location filename="../plugininfo.h" line="215"/>
|
||||
<source>Set maximum charging current</source>
|
||||
<extracomment>The name of the ActionType ({ea933a77-a098-4303-bbdb-15c72dfd3634}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the ActionType ({e3d27f8a-73d0-493a-b99a-29e7dc184485}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="218"/>
|
||||
<source>Slave ID</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: terraAcTcp, Type: thing, ID: {cc4c66c1-185f-4c97-88a0-2bc66319d157})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="221"/>
|
||||
<source>Terra AC Charger (RTU)</source>
|
||||
<extracomment>The name of the ThingClass ({a385801e-600c-4e27-ad73-5184a7516860})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="224"/>
|
||||
<source>Terra AC Charger (TCP)</source>
|
||||
<extracomment>The name of the ThingClass ({93ad828a-9a7a-4fca-be3f-88641317845f})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="227"/>
|
||||
<location filename="../plugininfo.h" line="230"/>
|
||||
<source>Voltage phase 1</source>
|
||||
<extracomment>The name of the StateType ({52ebc781-a527-4641-b768-3a0bd0209f96}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({bcff4c2d-5567-40ff-adef-5b47dc5ace76}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="233"/>
|
||||
<location filename="../plugininfo.h" line="236"/>
|
||||
<source>Voltage phase 2</source>
|
||||
<extracomment>The name of the StateType ({bb513653-70ca-4ee1-a913-aab3d2881708}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({db7af25a-cb7b-4640-9515-a7af345bd770}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../plugininfo.h" line="239"/>
|
||||
<location filename="../plugininfo.h" line="242"/>
|
||||
<source>Voltage phase 3</source>
|
||||
<extracomment>The name of the StateType ({c84a5e75-121d-4af8-a203-23f586613cf2}) of ThingClass terraAcRtu
|
||||
----------
|
||||
The name of the StateType ({fe67e1bf-6871-45a6-a253-819cb70c5ec4}) of ThingClass terraAcTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IntegrationPluginAbbterra</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="57"/>
|
||||
<source>No connected Modbus RTU master available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="141"/>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="186"/>
|
||||
<source>The charging station is not reachable.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="289"/>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="365"/>
|
||||
<source>Could not initialize the communication with the charger.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="298"/>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="374"/>
|
||||
<source>The device does not match the ABB Terra AC Modbus register map.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginabbterra.cpp" line="328"/>
|
||||
<source>The Modbus RTU connection is not available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
153
abbterraac/.claude/REPRISE-ABB-terraac.md
Normal file
153
abbterraac/.claude/REPRISE-ABB-terraac.md
Normal file
@ -0,0 +1,153 @@
|
||||
# REPRISE — TÂCHE 1 : intégrer & builder ABB Terra AC (vendoring upstream)
|
||||
|
||||
## Contexte
|
||||
Je prépare une visite client (borne ABB Terra AC + compteur ABB B23). Test EN ATELIER
|
||||
d'abord. Le plugin **abbterra** se trouve DÉJÀ dans MON repo
|
||||
`etm-powersync-plugins-modbus` (dossier `abbterra/`) — il a été copié depuis l'upstream
|
||||
nymea-plugins-modbus, branche `experimental-silo`. Le compteur B2x (TÂCHE 2) est déjà
|
||||
intégré et prêt (abbb2x ajouté à PLUGIN_DIRS, debian/control + changelog faits).
|
||||
|
||||
But de cette tâche : packager et déployer la borne, proprement, en décidant comment on
|
||||
gère le fait que ce code est upstream-mais-pas-encore-release.
|
||||
|
||||
## Décision de fond (déjà tranchée — à appliquer, pas à rediscuter)
|
||||
abbterra N'EST PAS un fork divergent : je ne modifie pas le code, je le **builde en avance**
|
||||
parce que nymea ne l'a pas encore publié dans son dépôt apt (il n'est que dans la branche
|
||||
experimental-silo). C'est du **vendoring temporaire**. Tout nymea est GPLv3 → redistribution
|
||||
et build anticipé explicitement permis, aucune contrainte juridique.
|
||||
|
||||
Approche retenue (cohérente avec l'archi existante du mirror) :
|
||||
1. **Nom du paquet = `nymea-plugin-abbterra`** (nom UPSTREAM, PAS de préfixe powersync-).
|
||||
2. **PAS de Provides/Replaces/Conflicts** (rien ne le concurrence : le mirror l'exclura).
|
||||
3. **Ajouter `abbterra` à `FORKED_PLUGINS`** dans `/mnt/builddisk/sync-nymea-mirror.sh`.
|
||||
→ empêche le mirror de réimporter une version upstream concurrente sous le même nom.
|
||||
`FORKED_PLUGINS` couvre désormais DEUX cas : forks divergents (keba) ET builds
|
||||
anticipés de code upstream non encore release (abbterra). Mettre à jour son commentaire.
|
||||
4. **Tracer le vendoring** : créer `abbterra/VENDORED.md` (voir contenu plus bas).
|
||||
|
||||
Pourquoi ce choix : transition douce. Le jour où nymea release abbterra dans master/stable,
|
||||
il suffira de (a) supprimer le dossier abbterra/ de mon repo, (b) retirer `abbterra` de
|
||||
FORKED_PLUGINS, (c) relancer le sync — le mirror tirera alors la version OFFICIELLE sous
|
||||
le MÊME nom, donc l'edge bascule dessus sans réinstall ni reconfig des things. Un seul nom
|
||||
de paquet existe à tout instant, jamais de doublon.
|
||||
|
||||
## Infrastructure (rappel)
|
||||
- VM build : etm-powersync-dev ; conteneur LXC `build-1-15` (libnymea-dev 1.15.0,
|
||||
libnymea-modbus-dev, qt6-serialport-dev, qt6-serialbus-dev, nymea-dev-tools, python3).
|
||||
- Edge test : ssh etm@192.168.1.120, nymead actif, canal powersync-testing.
|
||||
- Dépôt APT : reprepro /mnt/builddisk/apt-repo ; publish-to-repo.sh ; clé GPG ETM.
|
||||
- Mirror : /mnt/builddisk/sync-nymea-mirror.sh (sélection auto depuis index upstream
|
||||
moins FORKED_PLUGINS ; keba déjà dedans).
|
||||
- Repo modbus : git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus
|
||||
local : ~/projects/etm-powersync/etm/etm-powersync-plugins-modbus
|
||||
- contient déjà : eastron/ (OK, en prod), abbb2x/ (prêt), abbterra/ (à packager).
|
||||
- modbus.pri recâblé sur paquets système (PKGCONFIG nymea-modbus + modbus-tool.pri).
|
||||
- Le conteneur CLONE depuis Gitea → TOUJOURS git push avant de builder.
|
||||
|
||||
## Règles de packaging (acquises, à respecter)
|
||||
- .pro racine : PLUGIN_DIRS une entrée par ligne, PAS de backslash après la dernière ;
|
||||
pas de SUBDIRS local ni de .depends vers libnymea-modbus (lib système).
|
||||
- Multi-binaire : le repo aura maintenant 3 paquets (eastron + abbb2x + abbterra) dans
|
||||
le MÊME debian/ → IL FAUT un `debian/<paquet>.install` par paquet (nom EXACT du Package:),
|
||||
sinon dh_install ne route pas les .so → paquet vide. Vérifier que les .install existent
|
||||
pour les 3 : powersync-plugin-eastron.install, powersync-plugin-abbb2x.install,
|
||||
nymea-plugin-abbterra.install. Chacun contient la ligne :
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationplugin<nom>.so
|
||||
- debian/control Build-Depends modbus : debhelper, pkg-config, libnymea-dev,
|
||||
nymea-dev-tools:native, libnymea-modbus-dev, qt6-base-dev, qt6-base-dev-tools,
|
||||
qt6-serialport-dev, qt6-serialbus-dev.
|
||||
- changelog : format strict (ligne vide avant le " --"). Bumper la source en +etm3
|
||||
(etm2 = ajout abbb2x déjà fait).
|
||||
- rules : nettoyer autogenerated/ + moc_* + *plugininfo.h au dh_auto_clean.
|
||||
|
||||
## ÉTAPES
|
||||
|
||||
### 1. Vérifier l'état d'abbterra dans le repo
|
||||
ls -la ~/projects/.../etm-powersync-plugins-modbus/abbterra/
|
||||
grep -n 'PLUGIN_DIRS\|abbterra\|abbb2x\|eastron' etm-powersync-plugins-modbus.pro
|
||||
- abbterra présent ? abbterra dans PLUGIN_DIRS ? (l'ajouter si absent, sans casser le backslash)
|
||||
|
||||
### 2. debian/ — ajouter le paquet nymea-plugin-abbterra
|
||||
- debian/control : nouveau stanza `Package: nymea-plugin-abbterra`
|
||||
Architecture: any
|
||||
Section: libs
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
(PAS de Provides/Replaces/Conflicts)
|
||||
Description: ABB Terra AC charging station (Modbus TCP/RTU) — vendored from
|
||||
nymea-plugins-modbus experimental-silo, pending upstream release.
|
||||
- debian/nymea-plugin-abbterra.install :
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationpluginabbterra.so
|
||||
- debian/changelog : nouvelle entrée en tête, version 1.15.0+etm3 (ligne vide avant " --").
|
||||
- Vérifier que les .install des 3 paquets existent (cf. règles multi-binaire).
|
||||
|
||||
### 3. Tracer le vendoring — créer abbterra/VENDORED.md
|
||||
Contenu :
|
||||
----------------------------------------------------------------
|
||||
# Vendoring — abbterra
|
||||
Copié depuis : nymea/nymea-plugins-modbus @ branche experimental-silo
|
||||
Commit source : a652793 ("Add new plugin for ABB Terra AC Charger")
|
||||
Date copie : 2026-06-01
|
||||
Raison : plugin présent upstream mais PAS encore publié dans le dépôt apt nymea.
|
||||
Build anticipé ETM en attendant la release master/stable.
|
||||
Nom paquet : nymea-plugin-abbterra (nom upstream conservé)
|
||||
Exclu du mirror via FORKED_PLUGINS dans sync-nymea-mirror.sh.
|
||||
SORTIE (quand nymea release abbterra dans master/stable) :
|
||||
1) supprimer le dossier abbterra/ de ce repo
|
||||
2) retirer "abbterra" de FORKED_PLUGINS
|
||||
3) relancer sync-nymea-mirror.sh → le mirror tire la version officielle (même nom)
|
||||
----------------------------------------------------------------
|
||||
|
||||
### 4. Mirror — exclure abbterra
|
||||
Dans /mnt/builddisk/sync-nymea-mirror.sh, dans FORKED_PLUGINS :
|
||||
FORKED_PLUGINS=(
|
||||
"keba"
|
||||
"abbterra"
|
||||
)
|
||||
Mettre à jour le commentaire au-dessus pour préciser les 2 cas (fork divergent + vendoring).
|
||||
|
||||
### 5. Build (pipeline validé)
|
||||
git add -A && git commit -m "..." && git push
|
||||
sudo lxc exec build-1-15 -- bash -c '
|
||||
set -e
|
||||
cd /root && rm -rf etm-powersync-plugins-modbus
|
||||
git clone https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus.git
|
||||
cd etm-powersync-plugins-modbus
|
||||
echo "=== plugins ===" && grep -A6 PLUGIN_DIRS etm-powersync-plugins-modbus.pro
|
||||
echo "=== installs ===" && ls debian/*.install
|
||||
echo "=== packages ===" && grep -c "^Package:" debian/control
|
||||
chmod +x debian/rules
|
||||
# build local d abord pour valider compil + generation, PUIS le .deb :
|
||||
qmake6 && make -j$(nproc) 2>&1 | tail -30
|
||||
'
|
||||
- Vérifier dans la sortie : abbterra ET abbb2x ET eastron compilent, .so produites.
|
||||
- Si abbterra casse sur des getters/connexion : c est du code upstream testé, donc
|
||||
plutot un souci d intégration (PLUGIN_DIRS, modbus.pri) qu un bug — lire l erreur.
|
||||
- Puis : dpkg-buildpackage -b -us -uc 2>&1 | tail -30
|
||||
|
||||
### 6. Vérifier les .deb (anti-paquet-vide)
|
||||
Pour chacun des 3 : dpkg-deb -c <deb> | grep '\.so' → une .so au bon chemin.
|
||||
Pour abbterra : dpkg-deb -f nymea-plugin-abbterra_*.deb Package Depends
|
||||
(Depends doit inclure libqt6serialbus/serialport via shlibs).
|
||||
|
||||
### 7. Import + déploiement
|
||||
lxc file pull des .deb vers /mnt/builddisk
|
||||
reprepro -b /mnt/builddisk/apt-repo includedeb powersync-testing <deb> (les 3 + dbgsym)
|
||||
/mnt/builddisk/publish-to-repo.sh
|
||||
reprepro -b /mnt/builddisk/apt-repo list powersync-testing | grep -iE 'abbterra|abbb2x'
|
||||
ssh etm@192.168.1.120 'sudo apt update && sudo apt install -y nymea-plugin-abbterra powersync-plugin-abbb2x && sudo systemctl restart nymead'
|
||||
|
||||
## Config en atelier (après install)
|
||||
- Borne ABB Terra : ThingClass terraAcTcp (address, port 502, slaveId 1) OU terraAcRtu
|
||||
(rtuMaster, slaveId 1). CONFIRMER sur le matériel : TCP (réseau) ou RTU (RS-485) ?
|
||||
- Compteur ABB B2x : RTU (rtuMaster + slaveAddress). Si borne RTU + compteur RTU sur le
|
||||
MÊME bus → slaveId DISTINCTS.
|
||||
- RTU : créer d'abord le "Modbus RTU master" (adaptateur /dev/ttyUSB*) dans nymea:app,
|
||||
vérifier droits (user nymead dans groupe dialout), puis ajouter les things.
|
||||
- B2x = code NEUF : valider scaling (puissance signée +import/-export ; si ×100 trop grand
|
||||
passer /100 à /1 dans le .cpp ; vérifier noms getters générés dans
|
||||
abbb2x/autogenerated/abbb2xmodbusrtuconnection.h).
|
||||
|
||||
## Préférences de travail
|
||||
Français. Affichage complet des fichiers (cat -n) plutôt que diffs ; logs complets avant
|
||||
commit ; pas de fallback silencieux. Étape par étape : attendre ma sortie de commande avant
|
||||
de continuer. Toujours vérifier le contenu d'un .deb (dpkg-deb -c) avant import.
|
||||
17
debian/changelog
vendored
17
debian/changelog
vendored
@ -1,3 +1,20 @@
|
||||
etm-powersync-plugins-modbus (1.15.0+etm3) trixie; urgency=medium
|
||||
|
||||
* Ajout plugin ABB Terra AC (borne de charge, Modbus TCP/RTU) : vendoring
|
||||
depuis nymea-plugins-modbus experimental-silo (commit a652793), en
|
||||
attente de la release master/stable upstream. Nom paquet nymea-plugin-abbterra
|
||||
(nom upstream conserve pour transition sans reinstall).
|
||||
|
||||
-- ETM-Schurig SARL <contact@etm-schurig.eu> Sun, 01 Jun 2026 10:00:00 +0200
|
||||
|
||||
etm-powersync-plugins-modbus (1.15.0+etm2) trixie; urgency=medium
|
||||
|
||||
* Ajout plugin ABB B2x (B21/B23/B24, Modbus RTU) : tensions, courants,
|
||||
puissances par phase (int32 signe pour injection PV), frequence,
|
||||
energie import/export (uint64 x4 registres, 0.01 kWh).
|
||||
|
||||
-- ETM-Schurig SARL <contact@etm-schurig.eu> Sun, 01 Jun 2026 09:00:00 +0200
|
||||
|
||||
etm-powersync-plugins-modbus (1.15.0+etm1) trixie; urgency=medium
|
||||
|
||||
* Initial ETM packaging of the eastron plugin (Eastron SDM72/120/630, Modbus RTU).
|
||||
|
||||
18
debian/control
vendored
18
debian/control
vendored
@ -23,3 +23,21 @@ Depends: ${shlibs:Depends},
|
||||
Description: PowerSync integration plugin for Eastron SDM energy meters (Modbus RTU)
|
||||
nymea integration plugin for Eastron SDM72 / SDM120 / SDM630 energy meters
|
||||
over Modbus RTU, for use by the ETM PowerSync home energy management system.
|
||||
|
||||
Package: powersync-plugin-abbb2x
|
||||
Architecture: any
|
||||
Section: libs
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Description: PowerSync integration plugin for ABB B2x energy meters (Modbus RTU)
|
||||
nymea integration plugin for ABB B21 / B23 / B24 energy meters
|
||||
over Modbus RTU, for use by the ETM PowerSync home energy management system.
|
||||
|
||||
Package: nymea-plugin-abbterra
|
||||
Architecture: any
|
||||
Section: libs
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Description: ABB Terra AC charging station (Modbus TCP/RTU) - vendored from upstream
|
||||
nymea integration plugin for ABB Terra AC wallbox over Modbus TCP or RTU.
|
||||
Vendored from nymea-plugins-modbus experimental-silo, pending upstream release.
|
||||
|
||||
1
debian/nymea-plugin-abbterra.install
vendored
Normal file
1
debian/nymea-plugin-abbterra.install
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationpluginabbterra.so
|
||||
1
debian/powersync-plugin-abbb2x.install
vendored
Normal file
1
debian/powersync-plugin-abbb2x.install
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationpluginabbb2x.so
|
||||
1
debian/powersync-plugin-eastron.install
vendored
Normal file
1
debian/powersync-plugin-eastron.install
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/*/nymea/plugins/libnymea_integrationplugineastron.so
|
||||
@ -4,7 +4,9 @@ TEMPLATE = subdirs
|
||||
# dependency on the libs will be defined
|
||||
|
||||
PLUGIN_DIRS = \
|
||||
eastron
|
||||
eastron \
|
||||
abbb2x \
|
||||
abbterra
|
||||
|
||||
|
||||
message(============================================)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user