fix(v2c): correct logging category dcV2C + debian packaging + modbus.pri path

Build fixes found during first local compilation (qmake6 + g++ -Werror):

1. Logging category: nymea-plugininfocompiler generates 'dcV2C' (uppercase C)
   from the JSON vendor name "V2C". All three source files used 'dcV2c'
   (lowercase c) — corrected to 'dcV2C' in integrationpluginv2c.cpp,
   trydanmodbustcpmaster.cpp, v2ctcpdiscovery.cpp.

2. modbus.pri: hardcoded include(/usr/include/nymea-modbus/modbus-tool.pri)
   fails when libnymea-modbus-dev is not system-installed. Changed to a
   conditional: checks $$[QT_INSTALL_PREFIX] first, then NYMEA_MODBUS_PATH
   override, with a graceful fallback (warning + manual C++17 CONFIG flag).
   The v2c plugin has MODBUS_CONNECTIONS empty so the tool loop is a no-op
   either way; the fallback is safe.

3. debian/: add powersync-plugin-v2c package entry (control, .install,
   changelog entry 1.15.0+etm4).

Build verified: libnymea_integrationpluginv2c.so links against libnymea.so.1,
libnymea-modbus.so.1, Qt6Network/SerialBus/Core, 0 compiler warnings
(with -Werror -Wall -Wextra -std=c++17).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig 2026-06-12 15:20:11 +02:00
parent f0be2d00a2
commit 2ac00e1330
7 changed files with 51 additions and 14 deletions

13
debian/changelog vendored
View File

@ -1,3 +1,16 @@
etm-powersync-plugins-modbus (1.15.0+etm4) trixie; urgency=medium
* Ajout plugin V2C Trydan (borne de charge EV, Modbus TCP) : interface
evcharger complète — charge enable/disable (PauseState+Lock miroir),
consigne courant (Intensity), telemetrie (ChargePower, HousePower,
PowerFV, ChargeState IEC 61851). Gestion conflit Dynamic/PauseDynamic
sans ecriture du registre Dynamic (evite la coupure de telemetrie,
cf. evcc issue #28047). Decouverte LAN via HTTP /RealTimeData.
Transport abstrait (TCP Etape 1, RTU Etape 2 prevu).
Statut : Partiel — non teste sur borne reelle.
-- ETM-Schurig SARL <contact@etm-schurig.eu> Thu, 12 Jun 2026 12:00:00 +0200
etm-powersync-plugins-modbus (1.15.0+etm3) trixie; urgency=medium etm-powersync-plugins-modbus (1.15.0+etm3) trixie; urgency=medium
* Ajout plugin ABB Terra AC (borne de charge, Modbus TCP/RTU) : vendoring * Ajout plugin ABB Terra AC (borne de charge, Modbus TCP/RTU) : vendoring

12
debian/control vendored
View File

@ -41,3 +41,15 @@ Depends: ${shlibs:Depends},
Description: ABB Terra AC charging station (Modbus TCP/RTU) - vendored from upstream 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. nymea integration plugin for ABB Terra AC wallbox over Modbus TCP or RTU.
Vendored from nymea-plugins-modbus experimental-silo, pending upstream release. Vendored from nymea-plugins-modbus experimental-silo, pending upstream release.
Package: powersync-plugin-v2c
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
Description: PowerSync integration plugin for V2C Trydan EV charger (Modbus TCP)
nymea integration plugin for the V2C Trydan / Trydan Pro EV charger over
Modbus TCP (port 502), for use by the ETM PowerSync home energy management
system. Implements the evcharger interface: charge enable/disable, current
setpoint, plugged-in / charging state, house and solar power telemetry.
Status: Partial — not yet validated on real hardware.

1
debian/powersync-plugin-v2c.install vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/*/nymea/plugins/libnymea_integrationpluginv2c.so

View File

@ -1,11 +1,22 @@
QT += network serialport serialbus QT += network serialport serialbus
# libnymea-modbus depuis le paquet systeme (libnymea-modbus-dev), plus les sources locales # libnymea-modbus depuis le paquet systeme (libnymea-modbus-dev) ou via NYMEA_MODBUS_PATH.
CONFIG += link_pkgconfig CONFIG += link_pkgconfig
PKGCONFIG += nymea-modbus PKGCONFIG += nymea-modbus
OTHER_FILES += $${MODBUS_CONNECTIONS} OTHER_FILES += $${MODBUS_CONNECTIONS}
# Outil de generation des connexions modbus (sdm*-registers.json -> .cpp/.h), # Locate modbus-tool.pri: system install first, then NYMEA_MODBUS_PATH override
# fourni par libnymea-modbus-dev # (for builds against a local libnymea-modbus source tree).
include(/usr/include/nymea-modbus/modbus-tool.pri) # When MODBUS_CONNECTIONS is empty (e.g. v2c plugin), the tool's for-loop is a
# no-op; we still need the file for the C++ standard CONFIG lines below.
MODBUS_TOOL_PRI = $$[QT_INSTALL_PREFIX]/include/nymea-modbus/modbus-tool.pri
!isEmpty(NYMEA_MODBUS_PATH): MODBUS_TOOL_PRI = $$NYMEA_MODBUS_PATH/modbus-tool.pri
exists($$MODBUS_TOOL_PRI) {
include($$MODBUS_TOOL_PRI)
} else {
warning("modbus-tool.pri not found at $$MODBUS_TOOL_PRI — MODBUS_CONNECTIONS code-gen disabled")
greaterThan(QT_MAJOR_VERSION, 5): CONFIG *= c++17
lessThan(QT_MAJOR_VERSION, 6): CONFIG *= c++11
}

View File

@ -279,7 +279,7 @@ void IntegrationPluginV2c::updateThingStates(Thing *thing, TrydanModbusMaster *m
thing->setStateValue(trydanOptimizerSuspendedStateTypeId, master->pauseDynamic() == 1); thing->setStateValue(trydanOptimizerSuspendedStateTypeId, master->pauseDynamic() == 1);
if (conflict) { if (conflict) {
qCDebug(dcV2c()) << thing->name() qCDebug(dcV2C()) << thing->name()
<< ": V2C internal optimizer is active (Dynamic==1). " << ": V2C internal optimizer is active (Dynamic==1). "
"HEMS will suppress it via PauseDynamic if suspendInternalOptimizer==true."; "HEMS will suppress it via PauseDynamic if suspendInternalOptimizer==true.";
} }
@ -302,7 +302,7 @@ void IntegrationPluginV2c::handleDynamicConflict(Thing *thing, TrydanModbusMaste
const bool suspendAllowed = thing->setting(trydanSettingsSuspendInternalOptimizerParamTypeId).toBool(); const bool suspendAllowed = thing->setting(trydanSettingsSuspendInternalOptimizerParamTypeId).toBool();
if (!suspendAllowed) { if (!suspendAllowed) {
qCWarning(dcV2c()) << thing->name() qCWarning(dcV2C()) << thing->name()
<< ": V2C internal optimizer active but suspendInternalOptimizer==false. " << ": V2C internal optimizer active but suspendInternalOptimizer==false. "
"HEMS and V2C PID may fight — expect oscillations."; "HEMS and V2C PID may fight — expect oscillations.";
return; return;
@ -314,7 +314,7 @@ void IntegrationPluginV2c::handleDynamicConflict(Thing *thing, TrydanModbusMaste
// Only write on transition to avoid unnecessary flash wear. // Only write on transition to avoid unnecessary flash wear.
if (master->pauseDynamic() != desired) { if (master->pauseDynamic() != desired) {
qCDebug(dcV2c()) << thing->name() qCDebug(dcV2C()) << thing->name()
<< ": writing PauseDynamic =" << desired << ": writing PauseDynamic =" << desired
<< (hemsInControl ? "(HEMS taking control)" : "(releasing to V2C PID)"); << (hemsInControl ? "(HEMS taking control)" : "(releasing to V2C PID)");
master->writePauseDynamic(static_cast<quint16>(desired)); master->writePauseDynamic(static_cast<quint16>(desired));

View File

@ -200,7 +200,7 @@ void TrydanModbusTcpMaster::doNextRead(QList<ReadStep> steps,
static_cast<int>(m_slaveId)); static_cast<int>(m_slaveId));
if (!reply) { if (!reply) {
qCWarning(dcV2c()) << "TrydanModbusTcpMaster: sendReadRequest returned null for reg" << Qt::hex << address; qCWarning(dcV2C()) << "TrydanModbusTcpMaster: sendReadRequest returned null for reg" << Qt::hex << address;
onDone(false); onDone(false);
return; return;
} }
@ -210,7 +210,7 @@ void TrydanModbusTcpMaster::doNextRead(QList<ReadStep> steps,
reply->deleteLater(); reply->deleteLater();
if (reply->error() != QModbusDevice::NoError) { if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcV2c()) << "TrydanModbusTcpMaster: read error for reg" qCWarning(dcV2C()) << "TrydanModbusTcpMaster: read error for reg"
<< Qt::hex << steps.at(index).first << Qt::hex << steps.at(index).first
<< ":" << reply->errorString(); << ":" << reply->errorString();
callback(false, 0, 0); callback(false, 0, 0);
@ -233,7 +233,7 @@ void TrydanModbusTcpMaster::writeFC6(quint16 address, quint16 value,
QModbusReply *reply = m_modbusTcpMaster->sendWriteRequest(unit, static_cast<int>(m_slaveId)); QModbusReply *reply = m_modbusTcpMaster->sendWriteRequest(unit, static_cast<int>(m_slaveId));
if (!reply) { if (!reply) {
qCWarning(dcV2c()) << "TrydanModbusTcpMaster: sendWriteRequest returned null for reg" << Qt::hex << address; qCWarning(dcV2C()) << "TrydanModbusTcpMaster: sendWriteRequest returned null for reg" << Qt::hex << address;
callback(address, false); callback(address, false);
return; return;
} }
@ -261,7 +261,7 @@ void TrydanModbusTcpMaster::handleError()
{ {
m_errorCount++; m_errorCount++;
if (m_errorCount >= k_errorLimit) { if (m_errorCount >= k_errorLimit) {
qCWarning(dcV2c()) << "TrydanModbusTcpMaster: error limit reached, marking unreachable"; qCWarning(dcV2C()) << "TrydanModbusTcpMaster: error limit reached, marking unreachable";
setReachable(false); setReachable(false);
} }
} }

View File

@ -19,7 +19,7 @@ V2cTcpDiscovery::V2cTcpDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery,
void V2cTcpDiscovery::startDiscovery() void V2cTcpDiscovery::startDiscovery()
{ {
qCInfo(dcV2c()) << "Discovery: searching for V2C Trydan chargers on the network..."; qCInfo(dcV2C()) << "Discovery: searching for V2C Trydan chargers on the network...";
m_startTime = QDateTime::currentDateTime(); m_startTime = QDateTime::currentDateTime();
m_pendingProbes = 0; m_pendingProbes = 0;
m_scanFinished = false; m_scanFinished = false;
@ -84,7 +84,7 @@ void V2cTcpDiscovery::probeAddress(const QHostAddress &address)
info.setAddress(address); info.setAddress(address);
result.networkDeviceInfo = info; result.networkDeviceInfo = info;
} }
qCDebug(dcV2c()) << "Discovery: found V2C Trydan at" << address.toString(); qCDebug(dcV2C()) << "Discovery: found V2C Trydan at" << address.toString();
m_results.append(result); m_results.append(result);
} }
} }
@ -103,7 +103,7 @@ void V2cTcpDiscovery::finishDiscovery()
m_finished = true; m_finished = true;
const qint64 ms = QDateTime::currentMSecsSinceEpoch() - m_startTime.toMSecsSinceEpoch(); const qint64 ms = QDateTime::currentMSecsSinceEpoch() - m_startTime.toMSecsSinceEpoch();
qCInfo(dcV2c()) << "Discovery: finished in" qCInfo(dcV2C()) << "Discovery: finished in"
<< QTime::fromMSecsSinceStartOfDay(static_cast<int>(ms)).toString("mm:ss.zzz") << QTime::fromMSecsSinceStartOfDay(static_cast<int>(ms)).toString("mm:ss.zzz")
<< "with" << m_results.count() << "V2C Trydan(s) found."; << "with" << m_results.count() << "V2C Trydan(s) found.";
emit discoveryFinished(); emit discoveryFinished();