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>
111 lines
3.9 KiB
C++
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();
|
|
}
|