/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2025, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "integrationpluginsenec.h" #include "senecdiscovery.h" #include "plugininfo.h" #include #include #include #include "senecconnection.h" IntegrationPluginSenec::IntegrationPluginSenec() { // Testing the convert methods // QString rawValue = "fl_42A2E5E4"; // 81.449 // float value = SenecStorageLan::parseFloat(rawValue); // qCWarning(dcSenec()) << rawValue << value; // QString rawValue = "st_foobar"; // foobar // QString value = SenecStorageLan::parseString(rawValue); // qCWarning(dcSenec()) << rawValue << value; // QString rawValue = "u8_64"; // 100 // quint8 value = SenecStorageLan::parseUInt8(rawValue); // qCWarning(dcSenec()) << rawValue << value; } IntegrationPluginSenec::~IntegrationPluginSenec() { } void IntegrationPluginSenec::discoverThings(ThingDiscoveryInfo *info) { if (info->thingClassId() == senecStorageLanThingClassId) { if (!hardwareManager()->networkDeviceDiscovery()->available()) { qCWarning(dcSenec()) << "Failed to discover network devices. The network device discovery is not available."; info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discover devices in your network.")); return; } qCInfo(dcSenec()) << "Starting network discovery..."; SenecDiscovery *discovery = new SenecDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info); connect(discovery, &SenecDiscovery::discoveryFinished, info, [=](){ ThingDescriptors descriptors; qCInfo(dcSenec()) << "Discovery finished. Found" << discovery->results().count() << "devices"; foreach (const SenecDiscovery::Result &result, discovery->results()) { qCInfo(dcSenec()) << "Discovered device on" << result.networkDeviceInfo; QString title = "SENEC connection (" + result.deviceId + ")"; QString description = result.networkDeviceInfo.address().toString(); ThingDescriptor descriptor(senecStorageLanThingClassId, title, description); ParamList params; params << Param(senecStorageLanThingMacAddressParamTypeId, result.networkDeviceInfo.thingParamValueMacAddress()); params << Param(senecStorageLanThingHostNameParamTypeId, result.networkDeviceInfo.thingParamValueHostName()); params << Param(senecStorageLanThingAddressParamTypeId, result.networkDeviceInfo.thingParamValueAddress()); descriptor.setParams(params); // Check if we already have set up this device Thing *existingThing = myThings().findByParams(params); if (existingThing) { qCDebug(dcSenec()) << "This thing already exists in the system:" << result.networkDeviceInfo; descriptor.setThingId(existingThing->id()); } info->addThingDescriptor(descriptor); } info->finish(Thing::ThingErrorNoError); }); discovery->startDiscovery(); } } void IntegrationPluginSenec::startPairing(ThingPairingInfo *info) { if (info->thingClassId() == senecConnectionThingClassId) { qCDebug(dcSenec()) << "Start pairing process ..."; SenecConnection *connection = new SenecConnection(hardwareManager()->networkManager(), this); //m_unfinishedTadoAccounts.insert(info->thingId(), tado); connect(info, &ThingPairingInfo::aborted, this, [connection]() { qCWarning(dcSenec()) << "Thing pairing has been aborted, cleaning up..."; //m_unfinishedTadoAccounts.remove(info->thingId()); connection->deleteLater(); }); connect(connection, &SenecConnection::initFinished, info, [info, connection] (bool success) { if (!success) { info->finish(Thing::ThingErrorAuthenticationFailure); return; } qCDebug(dcSenec()) << "Senec server is reachable. Starting the OpenID auth process" << connection->authEndpoint().toString(QUrl::FullyEncoded); info->setOAuthUrl(connection->authEndpoint()); info->finish(Thing::ThingErrorNoError); }); connection->initialize(); } } void IntegrationPluginSenec::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) { if (info->thingClassId() == senecAccountThingClassId) { qCDebug(dcSenec()) << "Start logging in" << username << secret.left(2) + QString(secret.length() - 2, '*'); QVariantMap requestMap; requestMap.insert("username", username); requestMap.insert("password", secret); QNetworkRequest request(SenecAccount::loginUrl()); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Indented)); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [reply, info, username, this] { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // Check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcSenec()) << "Login request finished with error. Status:" << status << "Error:" << reply->errorString(); info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Username or password is invalid.")); return; } // Note: as of now (API 4.4.3) the login seems to return a static token, which does not require any refresh. // Not as bad as saving user and password on the device, but almost ... // https://documenter.getpostman.com/view/932140/2s9YXib2td QByteArray responseData = reply->readAll(); QJsonParseError jsonError; QVariantMap responseMap = QJsonDocument::fromJson(responseData, &jsonError).toVariant().toMap(); if (jsonError.error != QJsonParseError::NoError) { qCWarning(dcSenec()) << "Login request finished successfully, but the response contains invalid JSON object:" << responseData; info->finish(Thing::ThingErrorAuthenticationFailure); return; } if (!responseMap.contains("token") || !responseMap.contains("refreshToken")) { qCWarning(dcSenec()) << "Login request finished successfully, but the response JSON does not contain the expected properties" << qUtf8Printable(responseData); info->finish(Thing::ThingErrorHardwareFailure); return; } QString token = responseMap.value("token").toString(); QString refreshToken = responseMap.value("refreshToken").toString(); pluginStorage()->beginGroup(info->thingId().toString()); pluginStorage()->setValue("username", username); pluginStorage()->setValue("token", token); pluginStorage()->setValue("refreshToken", refreshToken); pluginStorage()->endGroup(); info->finish(Thing::ThingErrorNoError); }); } } void IntegrationPluginSenec::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); qCDebug(dcSenec()) << "Setting up" << thing->name() << thing->params(); if (thing->thingClassId() == senecAccountThingClassId) { // Load login information pluginStorage()->beginGroup(thing->id().toString()); QString token = pluginStorage()->value("token").toString(); QString refreshToken = pluginStorage()->value("refreshToken").toString(); QString username = pluginStorage()->value("username").toString(); pluginStorage()->endGroup(); SenecAccount *account = new SenecAccount(hardwareManager()->networkManager(), username, token, refreshToken, this); m_accounts.insert(thing, account); info->finish(Thing::ThingErrorNoError); thing->setStateValue(senecAccountUserDisplayNameStateTypeId, username); } if (thing->thingClassId() == senecStorageLanThingClassId) { // Handle reconfigure if (m_monitors.contains(thing)) { qCDebug(dcSenec()) << "Unregister existing monitor and recreate a new one..."; hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); } NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(thing); m_monitors.insert(thing, monitor); SenecStorageLan *storage = new SenecStorageLan(hardwareManager()->networkManager(), monitor->networkDeviceInfo().address(), this); m_storages.insert(thing, storage); connect(monitor, &NetworkDeviceMonitor::reachableChanged, storage, [monitor, storage](bool reachable){ if (reachable) { storage->setAddress(monitor->networkDeviceInfo().address()); storage->initialize(); } }); connect(storage, &SenecStorageLan::availableChanged, thing, [thing, this](bool available){ thing->setStateValue(senecStorageLanConnectedStateTypeId, available); foreach (Thing *child, myThings().filterByParentId(thing->id())) { child->setStateValue("connected", available); } }); connect(storage, &SenecStorageLan::updatedFinished, thing, [storage, thing, this](bool success){ thing->setStateValue(senecStorageLanConnectedStateTypeId, storage->available()); foreach (Thing *child, myThings().filterByParentId(thing->id())) { child->setStateValue("connected", storage->available()); } if (!success) return; thing->setStateValue(senecStorageLanCapacityStateTypeId, storage->capacity()); thing->setStateValue(senecStorageLanBatteryLevelStateTypeId, storage->batteryLevel()); thing->setStateValue(senecStorageLanBatteryCriticalStateTypeId, storage->batteryLevel() < 10.0); thing->setStateValue(senecStorageLanCurrentPowerStateTypeId, storage->batteryPower()); if (storage->batteryPower() > 0) { thing->setStateValue(senecStorageLanChargingStateStateTypeId, "charging"); } else if (storage->batteryPower() < 0) { thing->setStateValue(senecStorageLanChargingStateStateTypeId, "discharging"); } else { thing->setStateValue(senecStorageLanChargingStateStateTypeId, "idle"); } // Check if we have a meter Thing *meterThing = nullptr; Things meterThings = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()); if (!meterThings.isEmpty()) meterThing = meterThings.first(); // If so, update if (meterThing) { meterThing->setStateValue(senecMeterCurrentPowerStateTypeId, storage->gridPower()); meterThing->setStateValue(senecMeterConnectedStateTypeId, true); } // Check if we have an inverter Thing *inverterThing = nullptr; Things inverterThings = myThings().filterByThingClassId(senecInverterThingClassId).filterByParentId(thing->id()); if (!inverterThings.isEmpty()) { inverterThing = inverterThings.first(); } // If so, update if (inverterThing) { inverterThing->setStateValue(senecInverterCurrentPowerStateTypeId, storage->inverterPower()); inverterThing->setStateValue(senecInverterConnectedStateTypeId, true); } }); connect(thing, &Thing::settingChanged, this, [this, thing](const ParamTypeId ¶mTypeId, const QVariant &value){ if (paramTypeId == senecStorageLanSettingsAddMeterParamTypeId) { if (value.toBool()) { // Check if we have to add the meter if (myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()).isEmpty()) { qCDebug(dcSenec()) << "Add meter for" << thing->name(); emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(senecMeterThingClassId, "SENEC Meter", QString(), thing->id())); } } else { // Check if we have to remove the meter Things existingMeters = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()); if (!existingMeters.isEmpty()) { qCDebug(dcSenec()) << "Remove meter thing for" << thing->name(); emit autoThingDisappeared(existingMeters.takeFirst()->id()); } } } else if (paramTypeId == senecStorageLanSettingsAddMeterParamTypeId) { if (value.toBool()) { // Check if we have to add the meter if (myThings().filterByThingClassId(senecInverterThingClassId).filterByParentId(thing->id()).isEmpty()) { qCDebug(dcSenec()) << "Add inverter for" << thing->name(); emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(senecInverterThingClassId, "SENEC Inverter", QString(), thing->id())); } } else { // Check if we have to remove the meter Things existingInverters = myThings().filterByThingClassId(senecInverterThingClassId).filterByParentId(thing->id()); if (!existingInverters.isEmpty()) { qCDebug(dcSenec()) << "Remove inverter thing for" << thing->name(); emit autoThingDisappeared(existingInverters.takeFirst()->id()); } } } }); info->finish(Thing::ThingErrorNoError); storage->initialize(); } else if (thing->thingClassId() == senecStorageThingClassId) { connect(thing, &Thing::settingChanged, this, [this, thing](const ParamTypeId ¶mTypeId, const QVariant &value){ if (paramTypeId == senecStorageSettingsCapacityParamTypeId) { thing->setStateValue(senecStorageCapacityStateTypeId, value.toDouble()); } else if (paramTypeId == senecStorageSettingsAddMeterParamTypeId) { if (value.toBool()) { // Check if we have to add the meter if (myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()).isEmpty()) { qCDebug(dcSenec()) << "Add meter for" << thing->name(); emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(senecMeterThingClassId, "SENEC Meter", QString(), thing->id())); } } else { // Check if we have to remove the meter Things existingMeters = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()); if (!existingMeters.isEmpty()) { qCDebug(dcSenec()) << "Remove meter thing for" << thing->name(); emit autoThingDisappeared(existingMeters.takeFirst()->id()); } } } else if (paramTypeId == senecStorageSettingsAddInverterParamTypeId) { if (value.toBool()) { // Check if we have to add the meter if (myThings().filterByThingClassId(senecInverterThingClassId).filterByParentId(thing->id()).isEmpty()) { qCDebug(dcSenec()) << "Add inverter for" << thing->name(); emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(senecInverterThingClassId, "SENEC Inverter", QString(), thing->id())); } } else { // Check if we have to remove the meter Things existingInverters = myThings().filterByThingClassId(senecInverterThingClassId).filterByParentId(thing->id()); if (!existingInverters.isEmpty()) { qCDebug(dcSenec()) << "Remove inverter thing for" << thing->name(); emit autoThingDisappeared(existingInverters.takeFirst()->id()); } } } }); info->finish(Thing::ThingErrorNoError); } else if (thing->thingClassId() == senecMeterThingClassId) { info->finish(Thing::ThingErrorNoError); } else if (thing->thingClassId() == senecInverterThingClassId) { info->finish(Thing::ThingErrorNoError); } } void IntegrationPluginSenec::postSetupThing(Thing *thing) { if (thing->thingClassId() == senecAccountThingClassId) { // Search for now things, first poll the SenecAccount *account = m_accounts.value(thing); // Check installation, create things if not already created QNetworkReply *reply = account->getSystems(); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, account, [reply, thing, this] { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // Check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcSenec()) << "Systems request finished with error. Status:" << status << "Error:" << reply->errorString(); if (status == 401) { qCWarning(dcSenec()) << "Authentication error, reconfigure and re-login should fix this problem."; thing->setStateValue(senecAccountLoggedInStateTypeId, false); } thing->setStateValue(senecAccountConnectedStateTypeId, false); return; } QByteArray responseData = reply->readAll(); QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError); QVariant responseVariant = jsonDoc.toVariant(); if (jsonError.error != QJsonParseError::NoError) { qCWarning(dcSenec()) << "Systems request finished successfully, but the response contains invalid JSON object:" << responseData; thing->setStateValue(senecAccountConnectedStateTypeId, false); return; } qCDebug(dcSenec()) << "Systems request finished successfully" << qUtf8Printable(jsonDoc.toJson()); thing->setStateValue(senecAccountLoggedInStateTypeId, true); thing->setStateValue(senecAccountConnectedStateTypeId, true); ThingDescriptors descriptors; foreach (const QVariant &installation, responseVariant.toList()) { QVariantMap installationMap = installation.toMap(); // Note: for now we only support V4 systems if (!installationMap.value("systemType").toString().toLower().contains("v4")) continue; // This is a V4 storage, let's check if we already created one QString id = installationMap.value("id").toString(); Things existingThings = myThings().filterByThingClassId(senecStorageThingClassId).filterByParam(senecStorageThingIdParamTypeId, id); if (existingThings.isEmpty()) { qCDebug(dcSenec()) << "Creating new storage for" << id; ThingDescriptor descriptor(senecStorageThingClassId, "SENEC.Home P4", id); descriptor.setParentId(thing->id()); ParamList params; params.append(Param(senecStorageThingIdParamTypeId, id)); descriptor.setParams(params); descriptors.append(descriptor); } else { qCDebug(dcSenec()) << "Thing for storage" << id << "already created."; } } if (!descriptors.isEmpty()) { qCDebug(dcSenec()) << "Adding" << descriptors.count() << "new SENEC.Home" << (descriptors.count() > 1 ? "storages" : "storage"); emit autoThingsAppeared(descriptors); } }); } else if (thing->thingClassId() == senecStorageThingClassId) { thing->setStateValue(senecStorageCapacityStateTypeId, thing->setting(senecStorageSettingsCapacityParamTypeId).toDouble()); SenecAccount *account = m_accounts.value(myThings().findById(thing->parentId())); QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString(); QNetworkReply *reply = account->getTechnicalData(id); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, account, [reply, thing, this] { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // Check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcSenec()) << "Technical data request finished with error. Status:" << status << "Error:" << reply->errorString(); thing->setStateValue(senecStorageConnectedStateTypeId, false); return; } QByteArray responseData = reply->readAll(); QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError); //QVariantMap responseMap = jsonDoc.toVariant().toMap(); if (jsonError.error != QJsonParseError::NoError) { qCWarning(dcSenec()) << "Technical data request finished successfully, but the response contains invalid JSON object:" << responseData; return; } qCDebug(dcSenec()) << "Technical data request finished successfully" << qUtf8Printable(jsonDoc.toJson()); thing->setStateValue(senecStorageConnectedStateTypeId, true); refresh(thing); }); } // Create the refresh timer if not already set up if (!m_refreshTimer) { qCDebug(dcSenec()) << "Starting refresh timer ..."; m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(5); connect(m_refreshTimer, &PluginTimer::timeout, this, [this](){ refresh(); }); m_refreshTimer->start(); } } void IntegrationPluginSenec::thingRemoved(Thing *thing) { if (thing->thingClassId() == senecAccountThingClassId) { if (m_accounts.contains(thing)) m_accounts.take(thing)->deleteLater(); // Wipe any stored login information pluginStorage()->beginGroup(thing->id().toString()); pluginStorage()->remove(""); pluginStorage()->endGroup(); } if (thing->thingClassId() == senecStorageLanThingClassId) { hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing)); m_storages.take(thing)->deleteLater(); } if (myThings().isEmpty()) { qCDebug(dcSenec()) << "Stopping refresh timer"; hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); m_refreshTimer = nullptr; } } void IntegrationPluginSenec::executeAction(ThingActionInfo *info) { info->finish(Thing::ThingErrorNoError); } void IntegrationPluginSenec::refresh(Thing *thing) { // If no thing given, refresh all storages recursive if (!thing) { foreach (Thing *storageThing, myThings().filterByThingClassId(senecStorageThingClassId)) { refresh(storageThing); } foreach (Thing *storageLanThing, myThings().filterByThingClassId(senecStorageLanThingClassId)) { m_storages.value(storageLanThing)->update(); } return; } Thing *parentThing = myThings().findById(thing->parentId()); SenecAccount *account = m_accounts.value(parentThing); QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString(); QNetworkReply *reply = account->getDashboard(id); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, account, [reply, thing, this] { // Check if we have a meter Thing *meterThing = nullptr; Things meterThings = myThings().filterByThingClassId(senecMeterThingClassId).filterByParentId(thing->id()); if (!meterThings.isEmpty()) { meterThing = meterThings.first(); } int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); // Check HTTP status code if (status != 200 || reply->error() != QNetworkReply::NoError) { qCWarning(dcSenec()) << "Dashboard request finished with error. Status:" << status << "Error:" << reply->errorString(); thing->setStateValue(senecStorageConnectedStateTypeId, false); if (meterThing) { meterThing->setStateValue(senecMeterConnectedStateTypeId, false); } return; } QByteArray responseData = reply->readAll(); QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError); QVariantMap responseMap = jsonDoc.toVariant().toMap(); if (jsonError.error != QJsonParseError::NoError) { qCWarning(dcSenec()) << "Dashboard request finished successfully, but the response contains invalid JSON object:" << responseData; return; } qCDebug(dcSenec()) << "Dashboard request finished successfully" << qUtf8Printable(jsonDoc.toJson()); thing->setStateValue(senecStorageConnectedStateTypeId, true); QVariantMap currentDataMap = responseMap.value("currently").toMap(); float batteryCharge = qRound(currentDataMap.value("batteryChargeInW").toFloat() * 100) / 100.0; float batteryDischarge = qRound(currentDataMap.value("batteryDischargeInW").toFloat() * 100) / 100.0; int batteryLevel = currentDataMap.value("batteryLevelInPercent").toInt(); // qCDebug(dcSenec()) << "charge:" << batteryCharge << "W" << "discharge:" << batteryDischarge << "W" << "level" << batteryLevel << "%"; // Note: there are some situations where the battery is charging and discharging at the same time. // In that case we use the bigger power. Maybe we cloudl als sum them up, that should be tested... float currentPower = 0; if (batteryCharge != 0 && batteryDischarge != 0) { if (batteryCharge > batteryDischarge) { currentPower = batteryCharge; } else { currentPower = -batteryDischarge; } } else if (batteryCharge != 0) { currentPower = batteryCharge; } else if (batteryDischarge != 0) { currentPower = -batteryDischarge; } if (currentPower > 0) { thing->setStateValue(senecStorageChargingStateStateTypeId, "charging"); } else if (currentPower < 0) { thing->setStateValue(senecStorageChargingStateStateTypeId, "discharging"); } else { thing->setStateValue(senecStorageChargingStateStateTypeId, "idle"); } thing->setStateValue(senecStorageCurrentPowerStateTypeId, currentPower); thing->setStateValue(senecStorageBatteryLevelStateTypeId, batteryLevel); thing->setStateValue(senecStorageBatteryCriticalStateTypeId, batteryLevel < 10); // Check if we have a meter if (meterThing) { float gridConsume = qRound(currentDataMap.value("gridDrawInW").toFloat() * 100) / 100.0; float gridReturn = qRound(currentDataMap.value("gridFeedInInW").toFloat() * 100) / 100.0; qCDebug(dcSenec()) << "Grid power: consume" << gridConsume << "W" << "return:" << gridReturn << "W"; double currentPower = 0; if (gridConsume != 0) { currentPower = gridConsume; } else if (gridReturn != 0) { currentPower = -gridReturn; } meterThing->setStateValue(senecMeterCurrentPowerStateTypeId, currentPower); meterThing->setStateValue(senecMeterConnectedStateTypeId, true); } }); }