abbterra: retire les registres WO pour build sur generateur 1.15 (lecture seule)

- abbterra-registers.json : suppression des 4 registres WO
  (chargingCurrentLimitCommand, socketLockCommand, startStopChargingSession,
  communicationTimeoutCommand) incompatibles avec le generateur libnymea-modbus 1.15
- integrationpluginabbterra.cpp/.h : suppression de executeAction + applyTimeoutSetting
- integrationpluginabbterra.json : power + maxChargingCurrent passes en lecture seule
  (writable/displayNameAction retires)
La borne sera reconnue et lira les mesures ; le pilotage sera rebranche
quand le generateur 1.16 sera disponible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-01 08:29:36 +02:00
parent c26b0336bd
commit 3cbce56454
5 changed files with 5 additions and 334 deletions

View File

@ -191,53 +191,5 @@
}
]
}
],
"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"
}
]
}

View File

@ -133,93 +133,7 @@ void IntegrationPluginAbbterra::thingRemoved(Thing *thing)
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;
}
}
// Read-only mode: WO registers removed for compatibility with libnymea-modbus 1.15
info->finish(Thing::ThingErrorUnsupportedFeature);
}
@ -280,7 +194,6 @@ void IntegrationPluginAbbterra::setupTcpThing(ThingSetupInfo *info)
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) {
@ -308,11 +221,6 @@ void IntegrationPluginAbbterra::setupTcpThing(ThingSetupInfo *info)
updateThing(thing, connection);
});
connect(thing, &Thing::settingChanged, connection, [this, thing, connection](const ParamTypeId &paramTypeId, const QVariant &) {
if (paramTypeId == terraAcTcpSettingsCommunicationTimeoutParamTypeId) {
applyTimeoutSetting(thing, connection);
}
});
}
void IntegrationPluginAbbterra::setupRtuThing(ThingSetupInfo *info)
@ -356,7 +264,6 @@ void IntegrationPluginAbbterra::setupRtuThing(ThingSetupInfo *info)
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) {
@ -384,33 +291,8 @@ void IntegrationPluginAbbterra::setupRtuThing(ThingSetupInfo *info)
updateThing(thing, connection);
});
connect(thing, &Thing::settingChanged, connection, [this, thing, connection](const ParamTypeId &paramTypeId, 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)
{

View File

@ -30,8 +30,6 @@ public slots:
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);

View File

@ -107,23 +107,19 @@
"id": "207e2074-0147-4617-9a8b-3f326dcd6a0b",
"name": "power",
"displayName": "Charging enabled",
"displayNameAction": "Set charging enabled",
"type": "bool",
"defaultValue": true,
"writable": true
"defaultValue": 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
"defaultValue": 6
},
{
"id": "0764bce9-fd26-4da8-8d92-f6a5ce73e81e",
@ -294,23 +290,19 @@
"id": "e35fd4fa-bf5a-45a1-8a39-f0d3d9efa4c6",
"name": "power",
"displayName": "Charging enabled",
"displayNameAction": "Set charging enabled",
"type": "bool",
"defaultValue": true,
"writable": true
"defaultValue": 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
"defaultValue": 6
},
{
"id": "cd1add95-18d9-46b5-a3d5-f0f29d5160c9",

View File

@ -1,153 +0,0 @@
# 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.