Merge PR #761: Shelly: Add support for Gen3 hardware and fix #739

This commit is contained in:
jenkins 2025-03-30 20:36:41 +02:00
commit 3345e1d9e1
2 changed files with 153 additions and 120 deletions

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2020, nymea GmbH * Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -32,24 +32,24 @@
#include "plugininfo.h" #include "plugininfo.h"
#include "shellyjsonrpcclient.h" #include "shellyjsonrpcclient.h"
#include <QColor>
#include <QUrlQuery> #include <QUrlQuery>
#include <QNetworkReply>
#include <QHostAddress> #include <QHostAddress>
#include <QJsonDocument> #include <QJsonDocument>
#include <QColor> #include <QNetworkReply>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QRegularExpression>
#include "hardwaremanager.h" #include <hardwaremanager.h>
#include "network/networkaccessmanager.h" #include <network/networkaccessmanager.h>
#include "network/mqtt/mqttprovider.h" #include <network/mqtt/mqttprovider.h>
#include "network/mqtt/mqttchannel.h" #include <network/mqtt/mqttchannel.h>
#include <plugintimer.h>
#include "plugintimer.h" #include <qmath.h>
#include "qmath.h" #include <network/zeroconf/zeroconfservicebrowser.h>
#include <platform/platformzeroconfcontroller.h>
#include "network/zeroconf/zeroconfservicebrowser.h"
#include "platform/platformzeroconfcontroller.h"
#include <coap/coap.h> #include <coap/coap.h>
@ -63,10 +63,12 @@ static QHash<QString, QString> updateStatusMap = {
IntegrationPluginShelly::IntegrationPluginShelly() IntegrationPluginShelly::IntegrationPluginShelly()
{ {
} }
IntegrationPluginShelly::~IntegrationPluginShelly() IntegrationPluginShelly::~IntegrationPluginShelly()
{ {
} }
void IntegrationPluginShelly::init() void IntegrationPluginShelly::init()
@ -81,61 +83,64 @@ void IntegrationPluginShelly::init()
void IntegrationPluginShelly::discoverThings(ThingDiscoveryInfo *info) void IntegrationPluginShelly::discoverThings(ThingDiscoveryInfo *info)
{ {
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
qCDebug(dcShelly()) << "Have entry" << entry; if (entry.protocol() != QAbstractSocket::IPv4Protocol)
QRegExp namePattern;
if (info->thingClassId() == shelly1ThingClassId) {
namePattern = QRegExp("^shelly1-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlus1ThingClassId) {
namePattern = QRegExp("^ShellyPlus1-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shelly1pmThingClassId) {
namePattern = QRegExp("^shelly1pm-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlus1pmThingClassId) {
namePattern = QRegExp("^ShellyPlus1PM-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shellyPro1PmThingClassId) {
namePattern = QRegExp("^ShellyPro1PM-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shelly1lThingClassId) {
namePattern = QRegExp("^shelly1l-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlugThingClassId) {
namePattern = QRegExp("^shellyplug(-s)?-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlusPlugThingClassId) {
namePattern = QRegExp("^(ShellyPlusPlugS|ShellyPlug(US|IT|UK))-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shellyRgbw2ThingClassId) {
namePattern = QRegExp("^shellyrgbw2-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyDimmerThingClassId) {
namePattern = QRegExp("^(shellydimmer(2)?|ShellyVintage)-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shelly2ThingClassId) {
namePattern = QRegExp("^shellyswitch-[0-9A-Z]+$");
} else if (info->thingClassId() == shelly25ThingClassId) {
namePattern = QRegExp("^(shellyswitch25|ShellyPlus2PM)-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shellyButton1ThingClassId) {
namePattern = QRegExp("^shellybutton1-[0-9-A-Z]+$");
} else if (info->thingClassId() == shellyEmThingClassId) {
namePattern = QRegExp("^shellyem-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyEm3ThingClassId) {
namePattern = QRegExp("^shellyem3-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPro3EMThingClassId) {
namePattern = QRegExp("^ShellyPro3EM-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shellyHTThingClassId) {
namePattern = QRegExp("shellyht-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyI3ThingClassId) {
namePattern = QRegExp("shellyix3-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyMotionThingClassId) {
namePattern = QRegExp("shellymotionsensor-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyTrvThingClassId) {
namePattern = QRegExp("shellytrv-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyFloodThingClassId) {
namePattern = QRegExp("^shellyflood-[0-9A-Z]+$");
} else if (info->thingClassId() == shellySmokeThingClassId) {
namePattern = QRegExp("^shellysmoke-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlusSmokeThingClassId) {
namePattern = QRegExp("^shellyplussmoke-[0-9A-Z]+$", Qt::CaseInsensitive);
} else if (info->thingClassId() == shellyGasThingClassId) {
namePattern = QRegExp("^shellygas-[0-9A-Z]+$");
}
if (!entry.name().contains(namePattern)) {
continue; continue;
qCDebug(dcShelly()) << "Have entry" << entry;
QRegularExpression namePattern;
if (info->thingClassId() == shelly1ThingClassId) {
namePattern = QRegularExpression("^shelly1(mini)?(g3)?-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyPlus1ThingClassId) {
namePattern = QRegularExpression("^ShellyPlus1-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shelly1pmThingClassId) {
namePattern = QRegularExpression("^shelly1pm(g3)?-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyPlus1pmThingClassId) {
namePattern = QRegularExpression("^ShellyPlus1PM-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyPro1PmThingClassId) {
namePattern = QRegularExpression("^ShellyPro1PM-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shelly1lThingClassId) {
namePattern = QRegularExpression("^shelly1l-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlugThingClassId) {
namePattern = QRegularExpression("^shellyplug(-s)?(sg3)?-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyPlusPlugThingClassId) {
namePattern = QRegularExpression("^(ShellyPlusPlugS|ShellyPlug(US|IT|UK))-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyRgbw2ThingClassId) {
namePattern = QRegularExpression("^shellyrgbw2-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyDimmerThingClassId) {
namePattern = QRegularExpression("^(shellydimmer(2)?(g3)?|ShellyVintage)-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shelly2ThingClassId) {
namePattern = QRegularExpression("^shellyswitch-[0-9A-Z]+$");
} else if (info->thingClassId() == shelly25ThingClassId) {
namePattern = QRegularExpression("^(shellyswitch25|ShellyPlus2PM)-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyButton1ThingClassId) {
namePattern = QRegularExpression("^shellybutton1-[0-9-A-Z]+$");
} else if (info->thingClassId() == shellyEmThingClassId) {
namePattern = QRegularExpression("^shellyem(g3)?-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyEm3ThingClassId) {
namePattern = QRegularExpression("^shellyem3-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPro3EMThingClassId) {
namePattern = QRegularExpression("^ShellyPro3EM-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyHTThingClassId) {
namePattern = QRegularExpression("shellyht(g3)?-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyI3ThingClassId) {
namePattern = QRegularExpression("shellyix3-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyMotionThingClassId) {
namePattern = QRegularExpression("shellymotionsensor-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyTrvThingClassId) {
namePattern = QRegularExpression("shellytrv-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyFloodThingClassId) {
namePattern = QRegularExpression("^shellyflood-[0-9A-Z]+$");
} else if (info->thingClassId() == shellySmokeThingClassId) {
namePattern = QRegularExpression("^shellysmoke-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyPlusSmokeThingClassId) {
namePattern = QRegularExpression("^shellyplussmoke-[0-9A-Z]+$", QRegularExpression::CaseInsensitiveOption);
} else if (info->thingClassId() == shellyGasThingClassId) {
namePattern = QRegularExpression("^shellygas-[0-9A-Z]+$");
} }
if (!entry.name().contains(namePattern))
continue;
ThingDescriptor descriptor(info->thingClassId(), entry.name(), entry.hostAddress().toString()); ThingDescriptor descriptor(info->thingClassId(), entry.name(), entry.hostAddress().toString());
ParamList params; ParamList params;
ThingClass thingClass = supportedThings().findById(info->thingClassId()); ThingClass thingClass = supportedThings().findById(info->thingClassId());
@ -176,7 +181,7 @@ void IntegrationPluginShelly::confirmPairing(ThingPairingInfo *info, const QStri
QString shellyId = info->params().paramValue(thingClass.paramTypes().findByName("id").id()).toString(); QString shellyId = info->params().paramValue(thingClass.paramTypes().findByName("id").id()).toString();
ZeroConfServiceEntry zeroConfEntry; ZeroConfServiceEntry zeroConfEntry;
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
if (entry.name() == shellyId) { if (entry.name() == shellyId && entry.protocol() == QAbstractSocket::IPv4Protocol) {
zeroConfEntry = entry; zeroConfEntry = entry;
} }
} }
@ -195,7 +200,8 @@ void IntegrationPluginShelly::confirmPairing(ThingPairingInfo *info, const QStri
// GetDeviceInfo wouldn't require authentication if enabled, so if the setup is changed to fetch some info from GetDeviceInfo, // GetDeviceInfo wouldn't require authentication if enabled, so if the setup is changed to fetch some info from GetDeviceInfo,
// make sure to not just replace the GetStatus call, or authentication verification won't work any more. // make sure to not just replace the GetStatus call, or authentication verification won't work any more.
ShellyRpcReply *reply = client->sendRequest("Shelly.GetStatus"); ShellyRpcReply *reply = client->sendRequest("Shelly.GetStatus");
connect(reply, &ShellyRpcReply::finished, info, [info, client, this, password](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ connect(reply, &ShellyRpcReply::finished, info, [info, this, password](ShellyRpcReply::Status status, const QVariantMap &response){
Q_UNUSED(response)
if (status != ShellyRpcReply::StatusSuccess) { if (status != ShellyRpcReply::StatusSuccess) {
qCWarning(dcShelly) << "Error during shelly paring"; qCWarning(dcShelly) << "Error during shelly paring";
info->finish(Thing::ThingErrorHardwareFailure); info->finish(Thing::ThingErrorHardwareFailure);
@ -218,9 +224,11 @@ void IntegrationPluginShelly::setupThing(ThingSetupInfo *info)
if (!thing->thingClass().paramTypes().findByName("id").id().isNull()) { if (!thing->thingClass().paramTypes().findByName("id").id().isNull()) {
QString shellyId = info->thing()->paramValue("id").toString(); QString shellyId = info->thing()->paramValue("id").toString();
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
setupGen2(info); qCDebug(dcShelly()) << "Setting up" << shellyId << "Gen2+";
setupGen2Plus(info);
} else { } else {
qCDebug(dcShelly()) << "Setting up" << shellyId << "Gen1";
setupGen1(info); setupGen1(info);
} }
@ -238,8 +246,8 @@ void IntegrationPluginShelly::postSetupThing(Thing *thing)
} }
if (thing->parentId().isNull()) { if (thing->parentId().isNull()) {
if (isGen2(thing->paramValue("id").toString())) { if (isGen2Plus(thing->paramValue("id").toString())) {
fetchStatusGen2(thing); fetchStatusGen2Plus(thing);
} else { } else {
fetchStatusGen1(thing); fetchStatusGen1(thing);
} }
@ -247,8 +255,8 @@ void IntegrationPluginShelly::postSetupThing(Thing *thing)
// Check if a Addon is connected // Check if a Addon is connected
if (thing->thingClassId() == shellyPlus1ThingClassId if (thing->thingClassId() == shellyPlus1ThingClassId
|| thing->thingClassId() == shellyPlus1pmThingClassId || thing->thingClassId() == shellyPlus1pmThingClassId
|| thing->thingClassId() == shellyPlus25ThingClassId) { || thing->thingClassId() == shellyPlus25ThingClassId) {
// Narf... seems they forgot to register the SensorAddon namespace on the RPC interface // Narf... seems they forgot to register the SensorAddon namespace on the RPC interface
ShellyJsonRpcClient *client = m_rpcClients.value(thing); ShellyJsonRpcClient *client = m_rpcClients.value(thing);
@ -324,7 +332,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
ActionType actionType = thing->thingClass().actionTypes().findById(action.actionTypeId()); ActionType actionType = thing->thingClass().actionTypes().findById(action.actionTypeId());
if (actionType.name() == "reboot") { if (actionType.name() == "reboot") {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Shelly.Reboot"); ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Shelly.Reboot");
connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){
info->finish(status == ShellyRpcReply::StatusSuccess ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); info->finish(status == ShellyRpcReply::StatusSuccess ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
@ -344,7 +352,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
} }
if (actionType.name() == "performUpdate") { if (actionType.name() == "performUpdate") {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Shelly.Update"); ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Shelly.Update");
connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){
info->finish(status == ShellyRpcReply::StatusSuccess ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); info->finish(status == ShellyRpcReply::StatusSuccess ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
@ -535,7 +543,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
} }
if (action.actionTypeId() == shellyRollerOpenActionTypeId) { if (action.actionTypeId() == shellyRollerOpenActionTypeId) {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
QVariantMap params; QVariantMap params;
int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1; int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1;
params.insert("id", channelNbr); params.insert("id", channelNbr);
@ -558,7 +566,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
} }
if (action.actionTypeId() == shellyRollerCloseActionTypeId) { if (action.actionTypeId() == shellyRollerCloseActionTypeId) {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
QVariantMap params; QVariantMap params;
int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1; int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1;
params.insert("id", channelNbr); params.insert("id", channelNbr);
@ -581,7 +589,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
} }
if (action.actionTypeId() == shellyRollerStopActionTypeId) { if (action.actionTypeId() == shellyRollerStopActionTypeId) {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
QVariantMap params; QVariantMap params;
int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1; int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1;
params.insert("id", channelNbr); params.insert("id", channelNbr);
@ -604,7 +612,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
} }
if (action.actionTypeId() == shellyRollerCalibrateActionTypeId) { if (action.actionTypeId() == shellyRollerCalibrateActionTypeId) {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
QVariantMap params; QVariantMap params;
int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1; int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1;
params.insert("id", channelNbr); params.insert("id", channelNbr);
@ -624,7 +632,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
} }
if (action.actionTypeId() == shellyRollerPercentageActionTypeId) { if (action.actionTypeId() == shellyRollerPercentageActionTypeId) {
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
QVariantMap params; QVariantMap params;
int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1; int channelNbr = info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1;
int positionTarget = info->action().paramValue(shellyRollerPercentageActionPercentageParamTypeId).toInt(); int positionTarget = info->action().paramValue(shellyRollerPercentageActionPercentageParamTypeId).toInt();
@ -738,7 +746,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
ParamTypeId powerParamTypeId = actionType.id(); ParamTypeId powerParamTypeId = actionType.id();
bool on = action.param(powerParamTypeId).value().toBool(); bool on = action.param(powerParamTypeId).value().toBool();
if (isGen2(shellyId)) { if (isGen2Plus(shellyId)) {
QVariantMap params; QVariantMap params;
params.insert("id", relay - 1); params.insert("id", relay - 1);
params.insert("on", on); params.insert("on", on);
@ -1139,11 +1147,11 @@ void IntegrationPluginShelly::onMulticastMessageReceived(const QHostAddress &sou
if (thing->thingClassId() == shellyEm3ThingClassId) { if (thing->thingClassId() == shellyEm3ThingClassId) {
thing->setStateValue(shellyEm3CurrentPowerStateTypeId, thing->setStateValue(shellyEm3CurrentPowerStateTypeId,
thing->stateValue(shellyEm3CurrentPowerPhaseAStateTypeId).toDouble() + thing->stateValue(shellyEm3CurrentPowerPhaseAStateTypeId).toDouble() +
thing->stateValue(shellyEm3CurrentPowerPhaseBStateTypeId).toDouble() + thing->stateValue(shellyEm3CurrentPowerPhaseBStateTypeId).toDouble() +
thing->stateValue(shellyEm3CurrentPowerPhaseCStateTypeId).toDouble()); thing->stateValue(shellyEm3CurrentPowerPhaseCStateTypeId).toDouble());
double totalConsumption = thing->stateValue(shellyEm3EnergyConsumedPhaseAStateTypeId).toDouble() + double totalConsumption = thing->stateValue(shellyEm3EnergyConsumedPhaseAStateTypeId).toDouble() +
thing->stateValue(shellyEm3EnergyConsumedPhaseBStateTypeId).toDouble() + thing->stateValue(shellyEm3EnergyConsumedPhaseBStateTypeId).toDouble() +
thing->stateValue(shellyEm3EnergyConsumedPhaseCStateTypeId).toDouble(); thing->stateValue(shellyEm3EnergyConsumedPhaseCStateTypeId).toDouble();
if (totalConsumption >= 0) { if (totalConsumption >= 0) {
thing->setStateValue(shellyEm3TotalEnergyConsumedStateTypeId, totalConsumption); thing->setStateValue(shellyEm3TotalEnergyConsumedStateTypeId, totalConsumption);
} else { } else {
@ -1151,8 +1159,8 @@ void IntegrationPluginShelly::onMulticastMessageReceived(const QHostAddress &sou
qCWarning(dcShelly()) << "Detected negative value on shelly total consumption counter. Ignoring value." << qUtf8Printable(jsonDoc.toJson()); qCWarning(dcShelly()) << "Detected negative value on shelly total consumption counter. Ignoring value." << qUtf8Printable(jsonDoc.toJson());
} }
double totalProduction = thing->stateValue(shellyEm3EnergyProducedPhaseAStateTypeId).toDouble() + double totalProduction = thing->stateValue(shellyEm3EnergyProducedPhaseAStateTypeId).toDouble() +
thing->stateValue(shellyEm3EnergyProducedPhaseBStateTypeId).toDouble() + thing->stateValue(shellyEm3EnergyProducedPhaseBStateTypeId).toDouble() +
thing->stateValue(shellyEm3EnergyProducedPhaseCStateTypeId).toDouble(); thing->stateValue(shellyEm3EnergyProducedPhaseCStateTypeId).toDouble();
if (totalProduction >= 0) { if (totalProduction >= 0) {
thing->setStateValue(shellyEm3TotalEnergyProducedStateTypeId, totalProduction); thing->setStateValue(shellyEm3TotalEnergyProducedStateTypeId, totalProduction);
} else { } else {
@ -1190,7 +1198,7 @@ void IntegrationPluginShelly::onMulticastMessageReceived(const QHostAddress &sou
// Fetching info about signal strength, battery level for sleepy devices as they may be still awake when sending us something. // Fetching info about signal strength, battery level for sleepy devices as they may be still awake when sending us something.
if (thing->thingClassId() == shellyFloodThingClassId || if (thing->thingClassId() == shellyFloodThingClassId ||
thing->thingClassId() == shellyTrvThingClassId) { thing->thingClassId() == shellyTrvThingClassId) {
fetchStatusGen1(thing); fetchStatusGen1(thing);
} }
} }
@ -1202,12 +1210,12 @@ void IntegrationPluginShelly::updateStatus()
continue; continue;
} }
if (isGen2(thing->paramValue("id").toString())) { if (isGen2Plus(thing->paramValue("id").toString())) {
fetchStatusGen2(thing); fetchStatusGen2Plus(thing);
} else { } else {
//Skipping sleepy devices, as they won't reply to cyclic requests. //Skipping sleepy devices, as they won't reply to cyclic requests.
if (thing->thingClassId() == shellyFloodThingClassId if (thing->thingClassId() == shellyFloodThingClassId
|| thing->thingClassId() == shellyTrvThingClassId) { || thing->thingClassId() == shellyTrvThingClassId) {
continue; continue;
} }
@ -1260,13 +1268,11 @@ void IntegrationPluginShelly::fetchStatusGen1(Thing *thing)
child->setStateValue("signalStrength", signalStrength); child->setStateValue("signalStrength", signalStrength);
} }
QVariantMap updateMap = map.value("update").toMap(); QVariantMap updateMap = map.value("update").toMap();
thing->setStateValue("currentVersion", updateMap.value("old_version").toString()); thing->setStateValue("currentVersion", updateMap.value("old_version").toString());
thing->setStateValue("availableVersion", updateMap.value("new_version").toString()); thing->setStateValue("availableVersion", updateMap.value("new_version").toString());
thing->setStateValue("updateStatus", updateStatusMap.value(updateMap.value("status").toString())); thing->setStateValue("updateStatus", updateStatusMap.value(updateMap.value("status").toString()));
// Sometimes, some shellies just stop to send CoIoT messages until they are rebooted... // Sometimes, some shellies just stop to send CoIoT messages until they are rebooted...
// If communication to the shelly per se works fine, but we didn't receive anything in more than a minute, // If communication to the shelly per se works fine, but we didn't receive anything in more than a minute,
// let's reconfigure coap and reboot the shelly // let's reconfigure coap and reboot the shelly
@ -1315,7 +1321,7 @@ void IntegrationPluginShelly::fetchStatusGen1(Thing *thing)
}); });
} }
void IntegrationPluginShelly::fetchStatusGen2(Thing *thing) void IntegrationPluginShelly::fetchStatusGen2Plus(Thing *thing)
{ {
ShellyJsonRpcClient *client = m_rpcClients.value(thing); ShellyJsonRpcClient *client = m_rpcClients.value(thing);
ShellyRpcReply *statusReply = client->sendRequest("Shelly.GetStatus"); ShellyRpcReply *statusReply = client->sendRequest("Shelly.GetStatus");
@ -1478,7 +1484,7 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
if (myThings().filterByParentId(info->thing()->id()).isEmpty()) { if (myThings().filterByParentId(info->thing()->id()).isEmpty()) {
// Always create the switch thing if we don't have one yet for shellies with input (1, 1pm etc) // Always create the switch thing if we don't have one yet for shellies with input (1, 1pm etc)
if (info->thing()->thingClassId() == shelly1ThingClassId if (info->thing()->thingClassId() == shelly1ThingClassId
|| info->thing()->thingClassId() == shelly1pmThingClassId) { || info->thing()->thingClassId() == shelly1pmThingClassId) {
ThingDescriptor switchChild(shellySwitchThingClassId, info->thing()->name() + " switch", QString(), info->thing()->id()); ThingDescriptor switchChild(shellySwitchThingClassId, info->thing()->name() + " switch", QString(), info->thing()->id());
switchChild.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 1)); switchChild.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 1));
autoChilds.append(switchChild); autoChilds.append(switchChild);
@ -1486,9 +1492,9 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
// Create 2 switches for some that have 2 // Create 2 switches for some that have 2
if (info->thing()->thingClassId() == shelly2ThingClassId if (info->thing()->thingClassId() == shelly2ThingClassId
|| info->thing()->thingClassId() == shelly25ThingClassId || info->thing()->thingClassId() == shelly25ThingClassId
|| (info->thing()->thingClassId() == shellyDimmerThingClassId && info->thing()->paramValue(shellyDimmerThingIdParamTypeId).toString().startsWith("shellydimmer")) // Don't create chids for shelly vintage || (info->thing()->thingClassId() == shellyDimmerThingClassId && info->thing()->paramValue(shellyDimmerThingIdParamTypeId).toString().startsWith("shellydimmer")) // Don't create chids for shelly vintage
|| info->thing()->thingClassId() == shelly1lThingClassId) { || info->thing()->thingClassId() == shelly1lThingClassId) {
ThingDescriptor switchChild(shellySwitchThingClassId, info->thing()->name() + " switch 1", QString(), info->thing()->id()); ThingDescriptor switchChild(shellySwitchThingClassId, info->thing()->name() + " switch 1", QString(), info->thing()->id());
switchChild.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 1)); switchChild.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 1));
autoChilds.append(switchChild); autoChilds.append(switchChild);
@ -1570,10 +1576,10 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
// Handle thing settings of gateway devices // Handle thing settings of gateway devices
if (info->thing()->thingClassId() == shellyPlugThingClassId || if (info->thing()->thingClassId() == shellyPlugThingClassId ||
info->thing()->thingClassId() == shellyButton1ThingClassId || info->thing()->thingClassId() == shellyButton1ThingClassId ||
info->thing()->thingClassId() == shellyI3ThingClassId || info->thing()->thingClassId() == shellyI3ThingClassId ||
info->thing()->thingClassId() == shellyTrvThingClassId || info->thing()->thingClassId() == shellyTrvThingClassId ||
info->thing()->thingClassId() == shellyGasThingClassId) { info->thing()->thingClassId() == shellyGasThingClassId) {
connect(info->thing(), &Thing::settingChanged, this, [this, thing, shellyId](const ParamTypeId &settingTypeId, const QVariant &value) { connect(info->thing(), &Thing::settingChanged, this, [this, thing, shellyId](const ParamTypeId &settingTypeId, const QVariant &value) {
pluginStorage()->beginGroup(thing->id().toString()); pluginStorage()->beginGroup(thing->id().toString());
@ -1637,7 +1643,7 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
} }
} }
void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) void IntegrationPluginShelly::setupGen2Plus(ThingSetupInfo *info)
{ {
Thing *thing = info->thing(); Thing *thing = info->thing();
QHostAddress address = getIP(thing); QHostAddress address = getIP(thing);
@ -1772,7 +1778,22 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info)
} }
}); });
return; return;
}
if (info->thing()->thingClassId() == shelly1ThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
}
if (info->thing()->thingClassId() == shelly1pmThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
}
if (info->thing()->thingClassId() == shellyDimmerThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
} }
if (info->thing()->thingClassId() == shellyPro3EMThingClassId) { if (info->thing()->thingClassId() == shellyPro3EMThingClassId) {
@ -1780,6 +1801,21 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info)
return; return;
} }
if (info->thing()->thingClassId() == shellyEmThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
}
if (info->thing()->thingClassId() == shellyHTThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
}
if (info->thing()->thingClassId() == shellyPlugThingClassId) {
info->finish(Thing::ThingErrorNoError);
return;
}
if (info->thing()->thingClassId() == shellyPlusSmokeThingClassId) { if (info->thing()->thingClassId() == shellyPlusSmokeThingClassId) {
info->finish(Thing::ThingErrorNoError); info->finish(Thing::ThingErrorNoError);
return; return;
@ -1799,7 +1835,7 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info)
}); });
} else { } else {
if (thing->setupStatus() == Thing::ThingSetupStatusComplete) { if (thing->setupStatus() == Thing::ThingSetupStatusComplete) {
fetchStatusGen2(thing); fetchStatusGen2Plus(thing);
} }
} }
}); });
@ -1924,7 +1960,6 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info)
QVariantMap map = notification.value("smoke:0").toMap(); QVariantMap map = notification.value("smoke:0").toMap();
thing->setStateValue(shellyPlusSmokeFireDetectedStateTypeId, map.value("alarm").toBool()); thing->setStateValue(shellyPlusSmokeFireDetectedStateTypeId, map.value("alarm").toBool());
} }
} }
}); });
@ -1964,10 +1999,8 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info)
} }
}); });
} }
}); });
} }
} }
void IntegrationPluginShelly::setupShellyChild(ThingSetupInfo *info) void IntegrationPluginShelly::setupShellyChild(ThingSetupInfo *info)
@ -2068,7 +2101,7 @@ QHostAddress IntegrationPluginShelly::getIP(Thing *thing) const
ZeroConfServiceEntry zeroConfEntry; ZeroConfServiceEntry zeroConfEntry;
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
if (entry.name() == shellyId) { if (entry.name() == shellyId && entry.protocol() == QAbstractSocket::IPv4Protocol) {
zeroConfEntry = entry; zeroConfEntry = entry;
} }
} }
@ -2089,12 +2122,13 @@ QHostAddress IntegrationPluginShelly::getIP(Thing *thing) const
return address; return address;
} }
bool IntegrationPluginShelly::isGen2(const QString &shellyId) const bool IntegrationPluginShelly::isGen2Plus(const QString &shellyId) const
{ {
return shellyId.contains("Plus", Qt::CaseInsensitive) return shellyId.contains("Plus", Qt::CaseInsensitive)
|| shellyId.contains("Pro", Qt::CaseInsensitive) || shellyId.contains("Pro", Qt::CaseInsensitive)
|| QRegExp("^(ShellyPlusPlugS|ShellyPlug(US|IT|UK))-[0-9A-Z]+$", Qt::CaseInsensitive).exactMatch(shellyId) // Plus plug variants need to be matched quite precisely to not also match the v1 Plug || shellyId.contains("G3", Qt::CaseInsensitive) // Gen3 devices have API 2
; || QRegExp("^(ShellyPlusPlugS|ShellyPlug(US|IT|UK))-[0-9A-Z]+$", Qt::CaseInsensitive).exactMatch(shellyId) // Plus plug variants need to be matched quite precisely to not also match the v1 Plug
;
} }
void IntegrationPluginShelly::handleInputEvent(Thing *thing, const QString &buttonName, const QString &inputEventString, int inputEventCount) void IntegrationPluginShelly::handleInputEvent(Thing *thing, const QString &buttonName, const QString &inputEventString, int inputEventCount)

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright 2013 - 2020, nymea GmbH * Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io * Contact: contact@nymea.io
* *
* This file is part of nymea. * This file is part of nymea.
@ -31,15 +31,15 @@
#ifndef INTEGRATIONPLUGINSHELLY_H #ifndef INTEGRATIONPLUGINSHELLY_H
#define INTEGRATIONPLUGINSHELLY_H #define INTEGRATIONPLUGINSHELLY_H
#include "integrations/integrationplugin.h" #include <integrations/integrationplugin.h>
#include "extern-plugininfo.h"
#include <coap/coap.h> #include <coap/coap.h>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrlQuery> #include <QUrlQuery>
#include "extern-plugininfo.h"
class ZeroConfServiceBrowser; class ZeroConfServiceBrowser;
class PluginTimer; class PluginTimer;
@ -57,7 +57,6 @@ public:
explicit IntegrationPluginShelly(); explicit IntegrationPluginShelly();
~IntegrationPluginShelly() override; ~IntegrationPluginShelly() override;
void init() override; void init() override;
void discoverThings(ThingDiscoveryInfo *info) override; void discoverThings(ThingDiscoveryInfo *info) override;
void startPairing(ThingPairingInfo *info) override; void startPairing(ThingPairingInfo *info) override;
@ -73,15 +72,15 @@ private slots:
void updateStatus(); void updateStatus();
void fetchStatusGen1(Thing *thing); void fetchStatusGen1(Thing *thing);
void fetchStatusGen2(Thing *thing); void fetchStatusGen2Plus(Thing *thing);
private: private:
void setupGen1(ThingSetupInfo *info); void setupGen1(ThingSetupInfo *info);
void setupGen2(ThingSetupInfo *info); void setupGen2Plus(ThingSetupInfo *info);
void setupShellyChild(ThingSetupInfo *info); void setupShellyChild(ThingSetupInfo *info);
QHostAddress getIP(Thing *thing) const; QHostAddress getIP(Thing *thing) const;
bool isGen2(const QString &shellyId) const; bool isGen2Plus(const QString &shellyId) const;
void handleInputEvent(Thing *thing, const QString &buttonName, const QString &inputEventString, int inputEventCount); void handleInputEvent(Thing *thing, const QString &buttonName, const QString &inputEventString, int inputEventCount);