// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-plugins. * * nymea-plugins is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-plugins is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-plugins. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginmecelectronics.h" #include "plugininfo.h" #include #include #include #include #include #include #include #include IntegrationPluginMecMeter::IntegrationPluginMecMeter() { } IntegrationPluginMecMeter::~IntegrationPluginMecMeter() { } void IntegrationPluginMecMeter::init() { m_zeroConf = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp"); connect(m_zeroConf, &ZeroConfServiceBrowser::serviceEntryAdded, this, [=](const ZeroConfServiceEntry &entry){ if (myThings().findByParams({Param(mecMeterThingIdParamTypeId, entry.name())})) { pluginStorage()->beginGroup(entry.name()); pluginStorage()->setValue("cachedAddress", entry.hostAddress().toString()); pluginStorage()->endGroup(); } }); } void IntegrationPluginMecMeter::discoverThings(ThingDiscoveryInfo *info) { foreach (const ZeroConfServiceEntry &entry, m_zeroConf->serviceEntries()) { if (entry.protocol() != QAbstractSocket::IPv4Protocol) { continue; } qCDebug(dcMecElectronics()) << "zeroconf entry:" << entry; if (QRegularExpression("mec[A-Z0-9]{12}").match(entry.name()).hasMatch()) { qCDebug(dcMecElectronics()) << "Found mec meter!"; ThingDescriptor descriptor(mecMeterThingClassId, entry.name(), entry.hostAddress().toString()); descriptor.setParams({Param(mecMeterThingIdParamTypeId, entry.name())}); info->addThingDescriptor(descriptor); } } info->finish(Thing::ThingErrorNoError); } void IntegrationPluginMecMeter::startPairing(ThingPairingInfo *info) { info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter your login credentials for the mecMeter.")); } void IntegrationPluginMecMeter::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) { QString meterId = info->params().paramValue(mecMeterThingIdParamTypeId).toString(); QNetworkRequest request = composeRequest(meterId, username, secret); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, info, [=](){ if (reply->error() != QNetworkReply::NoError) { qCWarning(dcMecElectronics()) << "Error connecting to mecmeter:" << reply->error() << reply->errorString(); // Device responds with InternalServerError on wrong login if (reply->error() == QNetworkReply::InternalServerError) { info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("The login credentials are not valid.")); return; } info->finish(Thing::ThingErrorHardwareFailure); return; } pluginStorage()->beginGroup(meterId); pluginStorage()->setValue("username", username); pluginStorage()->setValue("password", secret); pluginStorage()->endGroup(); info->finish(Thing::ThingErrorNoError); }); } void IntegrationPluginMecMeter::setupThing(ThingSetupInfo *info) { info->finish(Thing::ThingErrorNoError); } void IntegrationPluginMecMeter::postSetupThing(Thing *thing) { Q_UNUSED(thing) if (!m_timer) { m_timer = hardwareManager()->pluginTimerManager()->registerTimer(1); connect(m_timer, &PluginTimer::timeout, this, [this](){ foreach (Thing *thing, myThings()) { refresh(thing); } }); } } void IntegrationPluginMecMeter::thingRemoved(Thing *thing) { Q_UNUSED(thing) if (myThings().isEmpty() && m_timer) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_timer); m_timer = nullptr; } } void IntegrationPluginMecMeter::refresh(Thing *thing) { QString meterId = thing->paramValue(mecMeterThingIdParamTypeId).toString(); pluginStorage()->beginGroup(meterId); QString username = pluginStorage()->value("username").toString(); QString password = pluginStorage()->value("password").toString(); pluginStorage()->endGroup(); QNetworkRequest request = composeRequest(thing->paramValue(mecMeterThingIdParamTypeId).toString(), username, password); QNetworkReply *reply = hardwareManager()->networkManager()->get(request); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, thing, [thing, reply](){ if (reply->error() != QNetworkReply::NoError) { qCWarning(dcMecElectronics()) << "Failed to refresh meter data. The reply returned with error" << reply->errorString(); thing->setStateValue(mecMeterConnectedStateTypeId, false); return; } QByteArray data = reply->readAll(); QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcMecElectronics()) << "Failed to parse meter data" << data << ":" << error.errorString(); return; } thing->setStateValue(mecMeterConnectedStateTypeId, true); QVariantMap dataMap = jsonDoc.toVariant().toMap(); qCDebug(dcMecElectronics()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); /* { "EFAA": 0, "EFAB": 0, "EFAC": 0, "EFAF": 0, "EFAH": 0, "EFAT": 0, "EFBF": 0, "EFBH": 0, "EFCF": 0, "EFCH": 0, "EFRA": 0, "EFRB": 0, "EFRC": 0.026041666666666668, "EFRT": 0.026041666666666668, "EFSA": 0, "EFSB": 0, "EFSC": 1193.7760416666667, "EFST": 841.9010416666667, "EFTF": 0, "EFTH": 0, "EMT": 1.9661458333333335, "ERA1": 0, "ERA2": 0, "ERA3": 0, "ERA4": 0, "ERAA": 0, "ERAB": 0, "ERAC": 11.848958333333334, "ERAF": 0, "ERAH": 0, "ERAT": 11.848958333333334, "ERB1": 0, "ERB2": 0, "ERB3": 0, "ERB4": 0, "ERBF": 0, "ERBH": 0, "ERC1": 0, "ERC2": 0.026041666666666668, "ERC3": 1.7447916666666667, "ERC4": 0.9895833333333334, "ERCF": 11.822916666666668, "ERCH": 0, "ERRA": 0, "ERRB": 0, "ERRC": 2.734375, "ERRT": 2.734375, "ERSA": 0, "ERSB": 0, "ERSC": 2341.588541666667, "ERST": 2693.463541666667, "ERT1": 0, "ERT2": 0.026041666666666668, "ERT3": 2.265625, "ERT4": 0.46875, "ERTF": 11.822916666666668, "ERTH": 0, "ESA": 0, "ESB": 0, "ESC": 3535.3645833333335, "EST": 3535.3645833333335, "EVT": 2355.7552083333335, "F": 49.99, "IA": 0.007428385416666667, "IAA": 0, "IAB": 0, "IAC": 30.200000000000003, "IADC": -6.40869140625e-05, "IB": 0.03445963541666667, "IBDC": -0.00044403076171874997, "IC": 0.012877604166666667, "ICDC": 0.000157928466796875, "IN": 3.429166666666667, "IN0": 0.034, "PA": 0, "PAF": 0, "PAH": 0, "PB": 0, "PBF": 0, "PBH": 0, "PC": -0.01953125, "PCF": -0.016276041666666668, "PCH": 0, "PFA": 0, "PFB": 0, "PFC": -0.008, "PFT": -0.007, "PT": -0.013020833333333334, "PTF": -0.013020833333333334, "PTH": 0, "QA": 0, "QB": 0, "QC": -0.009765625, "QT": 0, "SA": 0, "SAMPLES": 13385877, "SB": 0, "SC": 2.98828125, "ST": 2.9817708333333335, "STATUS": 372, "T": 36, "THIA": 0, "THIB": 0, "THIC": 40.81666666666667, "THUA": 0, "THUB": 0, "THUC": 2.5250000000000004, "TIME": 4283480878, "UAA": 0, "UAB": 0, "UAC": 0, "VA": 0, "VAB": 0, "VB": 0, "VBC": 231.947734375, "VC": 231.947734375, "VCA": 231.947734375, "VPT": 154.63182291666666, "VT": 77.31591145833333 } */ // Total energy / power thing->setStateValue(mecMeterTotalEnergyConsumedStateTypeId, 0.001 * qRound(dataMap.value("EFAT").toDouble())); thing->setStateValue(mecMeterTotalEnergyProducedStateTypeId, 0.001 * qRound(dataMap.value("ERAT").toDouble())); thing->setStateValue(mecMeterCurrentPowerStateTypeId, dataMap.value("PT").toDouble()); // thing->setStateValue(mecMeterTotalForwardeReactiveEnergyStateTypeId, dataMap.value("EFRT").toDouble()); // Voltage thing->setStateValue(mecMeterVoltagePhaseAStateTypeId, dataMap.value("VA").toDouble()); thing->setStateValue(mecMeterVoltagePhaseBStateTypeId, dataMap.value("VB").toDouble()); thing->setStateValue(mecMeterVoltagePhaseCStateTypeId, dataMap.value("VC").toDouble()); // Current thing->setStateValue(mecMeterCurrentPhaseAStateTypeId, dataMap.value("IA").toDouble()); thing->setStateValue(mecMeterCurrentPhaseBStateTypeId, dataMap.value("IB").toDouble()); thing->setStateValue(mecMeterCurrentPhaseCStateTypeId, dataMap.value("IC").toDouble()); // Power thing->setStateValue(mecMeterCurrentPowerPhaseAStateTypeId, dataMap.value("PA").toDouble()); thing->setStateValue(mecMeterCurrentPowerPhaseBStateTypeId, dataMap.value("PB").toDouble()); thing->setStateValue(mecMeterCurrentPowerPhaseCStateTypeId, dataMap.value("PC").toDouble()); // Frequency // thing->setStateValue(mecMeterFrequencyStateTypeId, dataMap.value("F").toDouble()); // Energy consumed thing->setStateValue(mecMeterEnergyConsumedPhaseAStateTypeId, 0.001 * qRound(dataMap.value("EFAA").toDouble())); thing->setStateValue(mecMeterEnergyConsumedPhaseBStateTypeId, 0.001 * qRound(dataMap.value("EFAB").toDouble())); thing->setStateValue(mecMeterEnergyConsumedPhaseCStateTypeId, 0.001 * qRound(dataMap.value("EFAC").toDouble())); // Energy produced thing->setStateValue(mecMeterEnergyProducedPhaseAStateTypeId, 0.001 * qRound(dataMap.value("ERAA").toDouble() / 1000.0)); thing->setStateValue(mecMeterEnergyProducedPhaseBStateTypeId, 0.001 * qRound(dataMap.value("ERAB").toDouble() / 1000.0)); thing->setStateValue(mecMeterEnergyProducedPhaseCStateTypeId, 0.001 * qRound(dataMap.value("ERAC").toDouble() / 1000.0)); }); } QNetworkRequest IntegrationPluginMecMeter::composeRequest(const QString &meterId, const QString &username, const QString &password) { QHostAddress address; foreach (const ZeroConfServiceEntry &entry, m_zeroConf->serviceEntries()) { if (entry.protocol() == QAbstractSocket::IPv4Protocol && entry.name() == meterId) { address = entry.hostAddress(); break; } } if (address.isNull()) { pluginStorage()->beginGroup(meterId); address = QHostAddress(pluginStorage()->value("cachedAddress").toString()); pluginStorage()->endGroup(); } if (address.isNull()) { qCWarning(dcMecElectronics()) << "Error finding mecMeter device in the network"; return QNetworkRequest(); } QUrl url; url.setScheme("http"); url.setHost(address.toString()); url.setPath("/wizard/public/api/measurements"); QNetworkRequest request(url); QString concatenated = username + ":" + password; QByteArray data = concatenated.toLocal8Bit().toBase64(); QString headerData = "Basic " + data; request.setRawHeader("Authorization", headerData.toLocal8Bit()); return request; }