etm-powersync-plugins-modbus/v2c/v2ctcpdiscovery.cpp
Patrick Schurig 2ac00e1330 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>
2026-06-12 15:20:11 +02:00

111 lines
3.9 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#include "v2ctcpdiscovery.h"
#include "extern-plugininfo.h"
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
#include <QTime>
#include <QUrl>
V2cTcpDiscovery::V2cTcpDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery,
QObject *parent)
: QObject(parent),
m_networkDeviceDiscovery(networkDeviceDiscovery)
{}
void V2cTcpDiscovery::startDiscovery()
{
qCInfo(dcV2C()) << "Discovery: searching for V2C Trydan chargers on the network...";
m_startTime = QDateTime::currentDateTime();
m_pendingProbes = 0;
m_scanFinished = false;
m_results.clear();
m_networkDeviceInfos.clear();
NetworkDeviceDiscoveryReply *reply = m_networkDeviceDiscovery->discover();
connect(reply, &NetworkDeviceDiscoveryReply::hostAddressDiscovered,
this, &V2cTcpDiscovery::probeAddress);
connect(reply, &NetworkDeviceDiscoveryReply::finished,
reply, &NetworkDeviceDiscoveryReply::deleteLater);
connect(reply, &NetworkDeviceDiscoveryReply::finished, this, [this, reply]() {
m_networkDeviceInfos = reply->networkDeviceInfos();
m_scanFinished = true;
// Give outstanding HTTP probes 3 extra seconds to complete.
QTimer::singleShot(3000, this, &V2cTcpDiscovery::finishDiscovery);
});
}
QList<V2cTcpDiscovery::Result> V2cTcpDiscovery::results() const
{
return m_results;
}
void V2cTcpDiscovery::probeAddress(const QHostAddress &address)
{
// The V2C Trydan HTTP API: GET /RealTimeData returns JSON with a "Paused"
// field (and ChargeState, ChargePower, etc.). The presence of "Paused"
// is a reliable Trydan fingerprint; it does not appear on generic devices.
// cf. evcc charger/trydan.go realTimeData struct and AGENTS.md § HTTP.
QUrl url;
url.setScheme(QStringLiteral("http"));
url.setHost(address.toString());
url.setPath(QStringLiteral("/RealTimeData"));
QNetworkRequest request(url);
request.setTransferTimeout(3000);
m_pendingProbes++;
QNetworkReply *reply = m_nam.get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply, address]() {
reply->deleteLater();
m_pendingProbes--;
if (reply->error() == QNetworkReply::NoError) {
const QByteArray data = reply->readAll();
const QJsonObject json = QJsonDocument::fromJson(data).object();
// "Paused" is the HTTP-API name for PauseState — note it differs from
// the Modbus register name (cf. AGENTS.md § HTTP).
if (json.contains(QStringLiteral("Paused"))) {
Result result;
result.ipAddress = address.toString();
result.networkDeviceInfo = m_networkDeviceInfos.get(address);
if (result.networkDeviceInfo.address().isNull()) {
NetworkDeviceInfo info;
info.setAddress(address);
result.networkDeviceInfo = info;
}
qCDebug(dcV2C()) << "Discovery: found V2C Trydan at" << address.toString();
m_results.append(result);
}
}
// Finish early if the scan is already done and this was the last probe.
if (m_scanFinished && m_pendingProbes == 0)
finishDiscovery();
});
}
void V2cTcpDiscovery::finishDiscovery()
{
// Guard against double-emission (can be called by the timer and the last probe).
if (m_finished)
return;
m_finished = true;
const qint64 ms = QDateTime::currentMSecsSinceEpoch() - m_startTime.toMSecsSinceEpoch();
qCInfo(dcV2C()) << "Discovery: finished in"
<< QTime::fromMSecsSinceStartOfDay(static_cast<int>(ms)).toString("mm:ss.zzz")
<< "with" << m_results.count() << "V2C Trydan(s) found.";
emit discoveryFinished();
}