diff --git a/shelly/integrationpluginshelly.cpp b/shelly/integrationpluginshelly.cpp index c92df2a2..2a660b5a 100644 --- a/shelly/integrationpluginshelly.cpp +++ b/shelly/integrationpluginshelly.cpp @@ -61,142 +61,6 @@ static QHash updateStatusMap = { {"unknown", "idle"} }; -static QHash colorTemperatureStateTypeMap = { - {shellyRgbw2ThingClassId, shellyRgbw2ColorTemperatureStateTypeId}, -}; - -// Actions and their params -static QHash rebootActionTypeMap = { - {shelly1RebootActionTypeId, shelly1ThingClassId}, - {shelly1pmRebootActionTypeId, shelly1pmThingClassId}, - {shelly1lRebootActionTypeId, shelly1lThingClassId}, - {shellyPlugRebootActionTypeId, shellyPlugThingClassId}, - {shellyPlusPlugRebootActionTypeId, shellyPlusPlugThingClassId}, - {shellyRgbw2RebootActionTypeId, shellyRgbw2ThingClassId}, - {shellyDimmerRebootActionTypeId, shellyDimmerThingClassId}, - {shelly2RebootActionTypeId, shelly2ThingClassId}, - {shelly25RebootActionTypeId, shelly25ThingClassId}, - {shellyI3RebootActionTypeId, shellyI3ThingClassId}, - {shellyTrvRebootActionTypeId, shellyTrvThingClassId}, -}; - -static QHash powerActionTypesMap = { - {shelly1PowerActionTypeId, shelly1ThingClassId}, - {shelly1pmPowerActionTypeId, shelly1pmThingClassId}, - {shelly1lPowerActionTypeId, shelly1lThingClassId}, - {shellyPlugPowerActionTypeId, shellyPlugThingClassId}, - {shellyPlusPlugPowerActionTypeId, shellyPlusPlugThingClassId}, - {shellyEmPowerActionTypeId, shellyEmThingClassId}, - {shellyEm3PowerActionTypeId, shellyEm3ThingClassId}, - {shelly2Channel1ActionTypeId, shelly2ThingClassId}, - {shelly2Channel2ActionTypeId, shelly2ThingClassId}, - {shelly25Channel1ActionTypeId, shelly25ThingClassId}, - {shelly25Channel2ActionTypeId, shelly25ThingClassId} -}; - -static QHash powerActionParamTypesMap = { - {shelly1PowerActionTypeId, shelly1PowerActionPowerParamTypeId}, - {shelly1pmPowerActionTypeId, shelly1pmPowerActionPowerParamTypeId}, - {shelly1lPowerActionTypeId, shelly1lPowerActionPowerParamTypeId}, - {shellyPlugPowerActionTypeId, shellyPlugPowerActionPowerParamTypeId}, - {shellyPlusPlugPowerActionTypeId, shellyPlusPlugPowerActionPowerParamTypeId}, - {shellyEmPowerActionTypeId, shellyEmPowerActionPowerParamTypeId}, - {shellyEm3PowerActionTypeId, shellyEm3PowerActionPowerParamTypeId}, - {shelly2Channel1ActionTypeId, shelly2Channel1ActionChannel1ParamTypeId}, - {shelly2Channel2ActionTypeId, shelly2Channel2ActionChannel2ParamTypeId}, - {shelly25Channel1ActionTypeId, shelly25Channel1ActionChannel1ParamTypeId}, - {shelly25Channel2ActionTypeId, shelly25Channel2ActionChannel2ParamTypeId} -}; - -static QHash colorPowerActionTypesMap = { - {shellyRgbw2PowerActionTypeId, shellyRgbw2ThingClassId}, -}; - -static QHash colorPowerActionParamTypesMap = { - {shellyRgbw2PowerActionPowerParamTypeId, shellyRgbw2PowerActionTypeId}, -}; - -static QHash colorActionTypesMap = { - {shellyRgbw2ColorActionTypeId, shellyRgbw2ThingClassId}, -}; - -static QHash colorActionParamTypesMap = { - {shellyRgbw2ColorActionTypeId, shellyRgbw2ColorActionTypeId}, -}; - -static QHash colorBrightnessActionTypesMap = { - {shellyRgbw2BrightnessActionTypeId, shellyRgbw2ThingClassId}, -}; - -static QHash colorBrightnessActionParamTypesMap = { - {shellyRgbw2BrightnessActionBrightnessParamTypeId, shellyRgbw2BrightnessActionTypeId}, -}; - -static QHash colorTemperatureActionTypesMap = { - {shellyRgbw2ColorTemperatureActionTypeId, shellyRgbw2ThingClassId}, -}; - -static QHash colorTemperatureActionParamTypesMap = { - {shellyRgbw2ColorTemperatureActionTypeId, shellyRgbw2ColorTemperatureActionColorTemperatureParamTypeId}, -}; - -static QHash dimmablePowerActionTypesMap = { - {shellyDimmerPowerActionTypeId, shellyDimmerThingClassId}, -}; - -static QHash dimmablePowerActionParamTypesMap = { - {shellyDimmerPowerActionTypeId, shellyDimmerPowerActionPowerParamTypeId}, -}; - -static QHash dimmableBrightnessActionTypesMap = { - {shellyDimmerBrightnessActionTypeId, shellyDimmerThingClassId}, -}; - -static QHash dimmableBrightnessActionParamTypesMap = { - {shellyDimmerBrightnessActionTypeId, shellyDimmerBrightnessActionBrightnessParamTypeId}, -}; - -static QHash updateActionTypesMap = { - {shelly1PerformUpdateActionTypeId, shelly1ThingClassId}, - {shelly1pmPerformUpdateActionTypeId, shelly1pmThingClassId}, - {shelly1lPerformUpdateActionTypeId, shelly1lThingClassId}, - {shelly2PerformUpdateActionTypeId, shelly2ThingClassId}, - {shelly25PerformUpdateActionTypeId, shelly25ThingClassId}, - {shellyPlugPerformUpdateActionTypeId, shellyPlugThingClassId}, - {shellyPlusPlugPerformUpdateActionTypeId, shellyPlusPlugThingClassId}, - {shellyRgbw2PerformUpdateActionTypeId, shellyRgbw2ThingClassId}, - {shellyDimmerPerformUpdateActionTypeId, shellyDimmerThingClassId}, - {shellyButton1PerformUpdateActionTypeId, shellyButton1ThingClassId}, - {shellyEmPerformUpdateActionTypeId, shellyEmThingClassId}, - {shellyEm3PerformUpdateActionTypeId, shellyEm3ThingClassId}, - {shellyHTPerformUpdateActionTypeId, shellyHTThingClassId}, - {shellyI3PerformUpdateActionTypeId, shellyI3ThingClassId}, - {shellyMotionPerformUpdateActionTypeId, shellyMotionThingClassId}, - {shellyTrvPerformUpdateActionTypeId, shellyTrvThingClassId}, - {shellyFloodPerformUpdateActionTypeId, shellyFloodThingClassId}, - {shellySmokePerformUpdateActionTypeId, shellySmokeThingClassId}, - {shellyGasPerformUpdateActionTypeId, shellyGasThingClassId} -}; - -// Settings -static QHash longpushMinDurationSettingIds = { - {shellyI3ThingClassId, shellyI3SettingsLongpushMinDurationParamTypeId} -}; -static QHash longpushMaxDurationSettingIds = { - {shellyButton1ThingClassId, shellyButton1SettingsLongpushMaxDurationParamTypeId}, - {shellyI3ThingClassId, shellyI3SettingsLongpushMaxDurationParamTypeId} -}; -static QHash multipushTimeBetweenPushesSettingIds = { - {shellyButton1ThingClassId, shellyButton1SettingsMultipushTimeBetweenPushesParamTypeId}, - {shellyI3ThingClassId, shellyI3SettingsMultipushTimeBetweenPushesParamTypeId} -}; -static QHash defaultStateSettingIds = { - {shellyPlusPlugThingClassId, shellyPlusPlugSettingsDefaultStateParamTypeId} -}; -static QHash ledModeSettingIds = { - {shellyPlusPlugThingClassId, shellyPlusPlugSettingsLedModeParamTypeId} -}; - IntegrationPluginShelly::IntegrationPluginShelly() { } @@ -220,9 +84,13 @@ void IntegrationPluginShelly::discoverThings(ThingDiscoveryInfo *info) qCDebug(dcShelly()) << "Have entry" << entry; QRegExp namePattern; if (info->thingClassId() == shelly1ThingClassId) { - namePattern = QRegExp("^(shelly1|ShellyPlus1)-[0-9A-Z]+$"); + namePattern = QRegExp("^shelly1-[0-9A-Z]+$"); + } else if (info->thingClassId() == shellyPlus1ThingClassId) { + namePattern = QRegExp("^ShellyPlus1-[0-9A-Z]+$"); } else if (info->thingClassId() == shelly1pmThingClassId) { - namePattern = QRegExp("^(shelly1pm|ShellyPlus1PM)-[0-9A-Z]+$"); + namePattern = QRegExp("^shelly1pm-[0-9A-Z]+$"); + } else if (info->thingClassId() == shellyPlus1pmThingClassId) { + namePattern = QRegExp("^ShellyPlus1PM-[0-9A-Z]+$"); } else if (info->thingClassId() == shelly1lThingClassId) { namePattern = QRegExp("^shelly1l-[0-9A-Z]+$"); } else if (info->thingClassId() == shellyPlugThingClassId) { @@ -290,6 +158,55 @@ void IntegrationPluginShelly::discoverThings(ThingDiscoveryInfo *info) info->finish(Thing::ThingErrorNoError); } +void IntegrationPluginShelly::startPairing(ThingPairingInfo *info) +{ + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the password for your Shelly device. By default this is empty.")); +} + +void IntegrationPluginShelly::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password) +{ + Q_UNUSED(username) + + qCDebug(dcShelly) << "Confirm pairing called"; + ThingClass thingClass = supportedThings().findById(info->thingClassId()); + QString shellyId = info->params().paramValue(thingClass.paramTypes().findByName("id").id()).toString(); + ZeroConfServiceEntry zeroConfEntry; + foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { + if (entry.name() == shellyId) { + zeroConfEntry = entry; + } + } + QHostAddress address = zeroConfEntry.hostAddress(); + + if (address.isNull()) { + qCWarning(dcShelly()) << "Unable to determine Shelly's network address. Failed to set up device."; + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to find the thing in the network.")); + return; + } + + ShellyJsonRpcClient *client = new ShellyJsonRpcClient(info); + client->open(address, "admin", password, shellyId); + connect(client, &ShellyJsonRpcClient::stateChanged, info, [info, client, this, password](QAbstractSocket::SocketState state) { + qCDebug(dcShelly()) << "Websocket state changed:" << state; + // 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. + ShellyRpcReply *reply = client->sendRequest("Shelly.GetStatus"); + connect(reply, &ShellyRpcReply::finished, info, [info, client, this, password](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ + if (status != ShellyRpcReply::StatusSuccess) { + qCWarning(dcShelly) << "Error during shelly paring"; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + qCDebug(dcShelly) << "Pairing successful!"; + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("password", password); + pluginStorage()->endGroup(); + + info->finish(Thing::ThingErrorNoError); + }); + }); +} + void IntegrationPluginShelly::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); @@ -338,6 +255,13 @@ void IntegrationPluginShelly::thingRemoved(Thing *thing) if (m_rpcClients.contains(thing)) { m_rpcClients.remove(thing); // Deleted by parenting } + + if (thing->parentId().isNull()) { // Only parents (gen1 and gen2) store stuff in the storage + pluginStorage()->beginGroup(thing->id().toString()); + pluginStorage()->remove(""); + pluginStorage()->endGroup(); + } + qCDebug(dcShelly()) << "Device removed" << thing->name(); } @@ -356,7 +280,8 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) url.setPassword(thing->paramValue("password").toString()); } - if (rebootActionTypeMap.contains(action.actionTypeId())) { + ActionType actionType = thing->thingClass().actionTypes().findById(action.actionTypeId()); + if (actionType.name() == "reboot") { if (isGen2(shellyId)) { ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Shelly.Reboot"); connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ @@ -376,59 +301,28 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } - if (updateActionTypesMap.contains(action.actionTypeId())) { - url.setPath("/ota"); - QUrlQuery query; - query.addQueryItem("update", "true"); - url.setQuery(query); - QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, info, [info, reply](){ - info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); - }); - return; - } - - if (powerActionTypesMap.contains(action.actionTypeId())) { - int relay = 1; - QHash actionChannelMap = { - {shelly25Channel1ActionTypeId, 1}, - {shelly25Channel2ActionTypeId, 2} - }; - if (!thing->thingClass().paramTypes().findByName("channel").id().isNull()) { - relay = thing->paramValue("channel").toInt(); - } else if (actionChannelMap.contains(action.actionTypeId())) { - relay = actionChannelMap.value(action.actionTypeId()); - } - - ParamTypeId powerParamTypeId = powerActionParamTypesMap.value(action.actionTypeId()); - bool on = action.param(powerParamTypeId).value().toBool(); - + if (actionType.name() == "performUpdate") { if (isGen2(shellyId)) { - QVariantMap params; - params.insert("id", relay - 1); - params.insert("on", on); - ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Switch.Set", params); // Switch.Set not supported by Shelly Plus 2PM in shutter mode; will return error "No handler for Switch.Set" + ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Shelly.Update"); connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ info->finish(status == ShellyRpcReply::StatusSuccess ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); }); } else { - url.setPath(QString("/relay/%1").arg(relay - 1)); + url.setPath("/ota"); QUrlQuery query; - query.addQueryItem("turn", on ? "on" : "off"); + query.addQueryItem("update", "true"); url.setQuery(query); QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, info, [info, reply, on](){ - info->thing()->setStateValue("power", on); + connect(reply, &QNetworkReply::finished, info, [info, reply](){ info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); }); } return; } - if (colorPowerActionTypesMap.contains(action.actionTypeId())) { - ParamTypeId colorPowerParamTypeId = colorPowerActionParamTypesMap.value(action.actionTypeId()); + if (action.actionTypeId() == shellyRgbw2PowerActionTypeId) { + ParamTypeId colorPowerParamTypeId = shellyRgbw2PowerActionPowerParamTypeId; bool on = action.param(colorPowerParamTypeId).value().toBool(); url.setPath("/color/0"); QUrlQuery query; @@ -443,8 +337,9 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } - if (colorActionTypesMap.contains(action.actionTypeId())) { - ParamTypeId colorParamTypeId = colorActionParamTypesMap.value(action.actionTypeId()); + + if (action.actionTypeId() == shellyRgbw2ColorActionTypeId) { + ParamTypeId colorParamTypeId = shellyRgbw2ColorActionColorParamTypeId; QColor color = action.param(colorParamTypeId).value().value(); url.setPath("/color/0"); QUrlQuery query; @@ -476,9 +371,8 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } - if (colorTemperatureStateTypeMap.contains(action.actionTypeId())) { - ParamTypeId colorTemperatureParamTypeId = colorTemperatureActionParamTypesMap.value(action.actionTypeId()); - int ct = action.param(colorTemperatureParamTypeId).value().toInt(); + if (action.actionTypeId() == shellyRgbw2ColorTemperatureActionTypeId) { + int ct = action.param(shellyRgbw2ColorTemperatureActionColorTemperatureParamTypeId).value().toInt(); url.setPath("/color/0"); QUrlQuery query; query.addQueryItem("red", QString::number(qMin(255, ct * 255 / 50))); @@ -495,8 +389,8 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } - if (colorBrightnessActionTypesMap.contains(action.actionTypeId())) { - ParamTypeId brightnessParamTypeId = colorBrightnessActionParamTypesMap.value(action.actionTypeId()); + if (action.actionTypeId() == shellyRgbw2BrightnessActionTypeId) { + ParamTypeId brightnessParamTypeId = shellyRgbw2BrightnessActionBrightnessParamTypeId; int brightness = action.param(brightnessParamTypeId).value().toInt(); url.setPath("/color/0"); QUrlQuery query; @@ -511,8 +405,8 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } - if (dimmablePowerActionTypesMap.contains(action.actionTypeId())) { - ParamTypeId powerParamTypeId = dimmablePowerActionParamTypesMap.value(action.actionTypeId()); + if (action.actionTypeId() == shellyDimmerPowerActionTypeId) { + ParamTypeId powerParamTypeId = shellyDimmerPowerActionPowerParamTypeId; bool on = action.param(powerParamTypeId).value().toBool(); url.setPath("/light/0"); QUrlQuery query; @@ -527,8 +421,8 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } - if (dimmableBrightnessActionTypesMap.contains(action.actionTypeId())) { - ParamTypeId brightnessParamTypeId = dimmableBrightnessActionParamTypesMap.value(action.actionTypeId()); + if (action.actionTypeId() == shellyDimmerBrightnessActionTypeId) { + ParamTypeId brightnessParamTypeId = shellyDimmerBrightnessActionBrightnessParamTypeId; int brightness = action.param(brightnessParamTypeId).value().toInt(); url.setPath("/light/0"); QUrlQuery query; @@ -779,6 +673,44 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info) return; } + if (actionType.name() == "power") { + int relay = 1; + QHash actionChannelMap = { + {shelly25Channel1ActionTypeId, 1}, + {shelly25Channel2ActionTypeId, 2} + }; + if (!thing->thingClass().paramTypes().findByName("channel").id().isNull()) { + relay = thing->paramValue("channel").toInt(); + } else if (actionChannelMap.contains(action.actionTypeId())) { + relay = actionChannelMap.value(action.actionTypeId()); + } + + ParamTypeId powerParamTypeId = actionType.id(); + bool on = action.param(powerParamTypeId).value().toBool(); + + if (isGen2(shellyId)) { + QVariantMap params; + params.insert("id", relay - 1); + params.insert("on", on); + ShellyRpcReply *reply = m_rpcClients.value(thing)->sendRequest("Switch.Set", params); // Switch.Set not supported by Shelly Plus 2PM in shutter mode; will return error "No handler for Switch.Set" + connect(reply, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ + info->finish(status == ShellyRpcReply::StatusSuccess ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); + }); + } else { + url.setPath(QString("/relay/%1").arg(relay - 1)); + QUrlQuery query; + query.addQueryItem("turn", on ? "on" : "off"); + url.setQuery(query); + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, info, [info, reply, on](){ + info->thing()->setStateValue("power", on); + info->finish(reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure); + }); + } + return; + } + qCWarning(dcShelly()) << "Unhandled execute action" << info->action().actionTypeId() << "call for device" << thing; } @@ -1202,6 +1134,10 @@ void IntegrationPluginShelly::onMulticastMessageReceived(const QHostAddress &sou void IntegrationPluginShelly::updateStatus() { foreach (Thing *thing, myThings().filterByParentId(ThingId())) { + if (!thing->setupComplete()) { + continue; + } + if (isGen2(thing->paramValue("id").toString())) { fetchStatusGen2(thing); } else { @@ -1276,6 +1212,7 @@ void IntegrationPluginShelly::fetchStatusGen2(Thing *thing) qCWarning(dcShelly()) << "Error updating status from shelly:" << status; return; } + qCDebug(dcShelly()) << thing->name() << "Status reply:" << response; int signalStrength = qMin(100, qMax(0, (response.value("wifi").toMap().value("rssi").toInt() + 100) * 2)); thing->setStateValue("connected", true); thing->setStateValue("signalStrength", signalStrength); @@ -1291,8 +1228,25 @@ void IntegrationPluginShelly::fetchStatusGen2(Thing *thing) qCWarning(dcShelly()) << "Error updating device info from shelly:" << status; return; } + qCDebug(dcShelly()) << thing->name() << "GetDeviceInfo reply:" << response; thing->setStateValue("currentVersion", response.value("ver").toString()); }); + ShellyRpcReply *updateReply = client->sendRequest("Shelly.CheckForUpdate"); + connect(updateReply, &ShellyRpcReply::finished, thing, [thing](ShellyRpcReply::Status status, const QVariantMap &response){ + if (status != ShellyRpcReply::StatusSuccess) { + qCWarning(dcShelly()) << "Error chcking for updates from shelly:" << status; + return; + } + qCDebug(dcShelly()) << thing->name() << "CheckForUpdate reply:" << response; + if (response.contains("stable")) { + thing->setStateValue("availableVersion", response.value("stable").toMap().value("version").toString()); + thing->setStateValue("updateStatus", "available"); + } else { + thing->setStateValue("availableVersion", ""); + thing->setStateValue("updateStatus", "idle"); + } + }); + } void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info) @@ -1575,7 +1529,9 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) return; } - QString password = info->thing()->paramValue("password").toString(); + pluginStorage()->beginGroup(thing->id().toString()); + QString password = pluginStorage()->value("password").toString(); + pluginStorage()->endGroup(); ShellyJsonRpcClient *client = new ShellyJsonRpcClient(info->thing()); client->open(address, "admin", password, shellyId); @@ -1593,7 +1549,7 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) qCDebug(dcShelly) << "Init response:" << response; m_rpcClients.insert(info->thing(), client); - if (info->thing()->thingClassId() == shelly1pmThingClassId) { + if (info->thing()->thingClassId() == shellyPlus1pmThingClassId || info->thing()->thingClassId() == shellyPlus1ThingClassId) { info->finish(Thing::ThingErrorNoError); @@ -1605,8 +1561,8 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) return; } - if (info->thing()->thingClassId() == shelly25ThingClassId) { - // Make sure the shelly 2.5 is in the mode we expect it to be (roller/cover or relay/switch) + if (info->thing()->thingClassId() == shellyPlus25ThingClassId) { + // Make sure the shelly plus 2PM is in the mode we expect it to be (roller/cover or relay/switch) bool rollerMode = info->thing()->paramValue("rollerMode").toBool(); QVariantMap params; if(rollerMode) { @@ -1615,39 +1571,39 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) params.insert("name", "switch"); } ShellyRpcReply *reply2 = client->sendRequest("Shelly.SetProfile", params); - connect(reply2, &ShellyRpcReply::finished, info, [info](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ + connect(reply2, &ShellyRpcReply::finished, info, [this, info, rollerMode](ShellyRpcReply::Status status, const QVariantMap &/*response*/){ if (status != ShellyRpcReply::StatusSuccess) { qCWarning(dcShelly) << "Error during shelly setup"; info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to configure shelly device.")); return; } info->finish(Thing::ThingErrorNoError); - }); - if (myThings().filterByParentId(info->thing()->id()).count() == 0) { - ThingDescriptors children; - ThingDescriptor switchChild(shellySwitchThingClassId, info->thing()->name() + " switch 1", QString(), info->thing()->id()); - switchChild.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 1)); - children.append(switchChild); - ThingDescriptor switch2Child(shellySwitchThingClassId, info->thing()->name() + " switch 2", QString(), info->thing()->id()); - switch2Child.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 2)); - children.append(switch2Child); + if (myThings().filterByParentId(info->thing()->id()).count() == 0) { + ThingDescriptors children; + ThingDescriptor switchChild(shellySwitchThingClassId, info->thing()->name() + " switch 1", QString(), info->thing()->id()); + switchChild.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 1)); + children.append(switchChild); + ThingDescriptor switch2Child(shellySwitchThingClassId, info->thing()->name() + " switch 2", QString(), info->thing()->id()); + switch2Child.setParams(ParamList() << Param(shellySwitchThingChannelParamTypeId, 2)); + children.append(switch2Child); - if (rollerMode == true) { - ThingDescriptor rollerShutterChild(shellyRollerThingClassId, info->thing()->name() + " connected shutter", QString(), info->thing()->id()); - rollerShutterChild.setParams(ParamList() << Param(shellyRollerThingChannelParamTypeId, 1)); - children.append(rollerShutterChild); - // Create 2 measurement channels for Shelly Plus 2PM (unless in roller mode) - } else { - ThingDescriptor channelChild(shellyPowerMeterChannelThingClassId, info->thing()->name() + " channel 1", QString(), info->thing()->id()); - channelChild.setParams(ParamList() << Param(shellyPowerMeterChannelThingChannelParamTypeId, 1)); - children.append(channelChild); - ThingDescriptor channel2Child(shellyPowerMeterChannelThingClassId, info->thing()->name() + " channel 2", QString(), info->thing()->id()); - channel2Child.setParams(ParamList() << Param(shellyPowerMeterChannelThingChannelParamTypeId, 2)); - children.append(channel2Child); + if (rollerMode == true) { + ThingDescriptor rollerShutterChild(shellyRollerThingClassId, info->thing()->name() + " connected shutter", QString(), info->thing()->id()); + rollerShutterChild.setParams(ParamList() << Param(shellyRollerThingChannelParamTypeId, 1)); + children.append(rollerShutterChild); + // Create 2 measurement channels for Shelly Plus 2PM (unless in roller mode) + } else { + ThingDescriptor channelChild(shellyPowerMeterChannelThingClassId, info->thing()->name() + " channel 1", QString(), info->thing()->id()); + channelChild.setParams(ParamList() << Param(shellyPowerMeterChannelThingChannelParamTypeId, 1)); + children.append(channelChild); + ThingDescriptor channel2Child(shellyPowerMeterChannelThingClassId, info->thing()->name() + " channel 2", QString(), info->thing()->id()); + channel2Child.setParams(ParamList() << Param(shellyPowerMeterChannelThingChannelParamTypeId, 2)); + children.append(channel2Child); + } + emit autoThingsAppeared(children); } - emit autoThingsAppeared(children); - } + }); return; } @@ -1656,7 +1612,7 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) // Set default state & led mode of the Plus Plug (S) QString defaultState = "off"; QString ledMode = "switch"; - defaultState = info->thing()->setting(defaultStateSettingIds.value(info->thing()->thingClassId())).toString(); + defaultState = info->thing()->setting("defaultState").toString(); QVariantMap config; config.insert("initial_state", defaultState); QVariantMap params; @@ -1673,7 +1629,7 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) info->finish(Thing::ThingErrorNoError); }); - ledMode = info->thing()->setting(ledModeSettingIds.value(info->thing()->thingClassId())).toString(); + ledMode = info->thing()->setting("ledMode").toString(); QVariantMap leds; leds.insert("mode", ledMode); QVariantMap config2; @@ -1700,16 +1656,20 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) }); }); - connect(client, &ShellyJsonRpcClient::stateChanged, thing, [thing, client, this](QAbstractSocket::SocketState state) { + connect(client, &ShellyJsonRpcClient::stateChanged, thing, [thing, client, password, shellyId, this](QAbstractSocket::SocketState state) { thing->setStateValue("connected", state == QAbstractSocket::ConnectedState); foreach (Thing *child, myThings().filterByParentId(thing->id())) { child->setStateValue("connected", state == QAbstractSocket::ConnectedState); } if (state == QAbstractSocket::UnconnectedState) { - QTimer::singleShot(1000, thing, [this, client, thing](){ - client->open(getIP(thing), "admin", thing->paramValue("password").toString(), thing->paramValue("id").toString()); + QTimer::singleShot(1000, thing, [this, client, thing, password, shellyId](){ + client->open(getIP(thing), "admin", password, shellyId); }); + } else { + if (thing->setupStatus() == Thing::ThingSetupStatusComplete) { + fetchStatusGen2(thing); + } } }); connect(client, &ShellyJsonRpcClient::notificationReceived, thing, [thing, this](const QVariantMap ¬ification){ @@ -1814,7 +1774,8 @@ void IntegrationPluginShelly::setupGen2(ThingSetupInfo *info) thing->setStateValue(shellyPro3EMEnergyConsumedPhaseBStateTypeId, emdata0.value("b_total_act_energy").toDouble() / 1000); thing->setStateValue(shellyPro3EMEnergyProducedPhaseBStateTypeId, emdata0.value("b_total_act_ret_energy").toDouble() / 1000); thing->setStateValue(shellyPro3EMEnergyConsumedPhaseCStateTypeId, emdata0.value("c_total_act_energy").toDouble() / 1000); - thing->setStateValue(shellyPro3EMEnergyProducedPhaseCStateTypeId, emdata0.value("c_total_act_ret_energy").toDouble() / 1000); thing->setStateValue(shellyPro3EMTotalEnergyConsumedStateTypeId, emdata0.value("total_act").toDouble() / 1000); + thing->setStateValue(shellyPro3EMEnergyProducedPhaseCStateTypeId, emdata0.value("c_total_act_ret_energy").toDouble() / 1000); + thing->setStateValue(shellyPro3EMTotalEnergyConsumedStateTypeId, emdata0.value("total_act").toDouble() / 1000); thing->setStateValue(shellyPro3EMTotalEnergyProducedStateTypeId, emdata0.value("total_act_ret").toDouble() / 1000); } }); @@ -1956,6 +1917,7 @@ QHostAddress IntegrationPluginShelly::getIP(Thing *thing) const } QString shellyId = d->paramValue("id").toString(); + ZeroConfServiceEntry zeroConfEntry; foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { if (entry.name() == shellyId) { @@ -1982,9 +1944,9 @@ QHostAddress IntegrationPluginShelly::getIP(Thing *thing) const bool IntegrationPluginShelly::isGen2(const QString &shellyId) const { return shellyId.contains("Plus") - || shellyId.contains("Pro") - || shellyId.startsWith("ShellyPlug") // Plus plug variants don't have Plus in the name, but are camelcased as opposed to 1st gen plugs - ; + || shellyId.contains("Pro") + || shellyId.startsWith("ShellyPlug") // Plus plug variants don't have Plus in the name, but are camelcased as opposed to 1st gen plugs + ; } void IntegrationPluginShelly::handleInputEvent(Thing *thing, const QString &buttonName, const QString &inputEventString, int inputEventCount) diff --git a/shelly/integrationpluginshelly.h b/shelly/integrationpluginshelly.h index 2da38343..468cae8d 100644 --- a/shelly/integrationpluginshelly.h +++ b/shelly/integrationpluginshelly.h @@ -55,8 +55,11 @@ public: explicit IntegrationPluginShelly(); ~IntegrationPluginShelly() override; + void init() override; void discoverThings(ThingDiscoveryInfo *info) override; + void startPairing(ThingPairingInfo *info) override; + void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password) override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; void thingRemoved(Thing *thing) override; diff --git a/shelly/integrationpluginshelly.json b/shelly/integrationpluginshelly.json index d3fe79f2..c86e032e 100644 --- a/shelly/integrationpluginshelly.json +++ b/shelly/integrationpluginshelly.json @@ -11,7 +11,7 @@ { "id": "f810b66a-7177-4397-9771-4229abaabbb6", "name": "shelly1", - "displayName": "Shelly 1/Plus 1", + "displayName": "Shelly 1", "createMethods": ["discovery"], "interfaces": [ "gateway", "wirelessconnectable", "update" ], "paramTypes": [ @@ -115,10 +115,98 @@ } ] }, + { + "id": "83766db4-a553-4df7-aeff-35fb18c01f3a", + "name": "shellyPlus1", + "displayName": "Shelly Plus 1", + "createMethods": ["discovery"], + "setupMethod": "enterpin", + "interfaces": [ "gateway", "wirelessconnectable", "update" ], + "paramTypes": [ + { + "id": "41cf8c80-b641-46fd-a329-2b81ecf9d445", + "name":"id", + "displayName": "Shelly ID", + "type": "QString", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "231365ce-45d6-4087-8ed6-1de3b0ef0485", + "name": "power", + "displayName": "Powered", + "displayNameEvent": "Turned on/off", + "displayNameAction": "Turn on/off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalOutput" + }, + { + "id": "30c5bbb2-7b02-445b-af16-8a082321383b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "f2b1f70c-dab0-474d-81bd-816ff3c63f47", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "type": "uint", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0, + "cached": false + }, + { + "id": "2c312a9e-ae76-4042-8030-89c8c86320e9", + "name": "updateStatus", + "displayName": "Update status", + "displayNameEvent": "Update status changed", + "type": "QString", + "possibleValues": ["idle", "available", "updating"], + "defaultValue": "idle" + }, + { + "id": "3fed15bd-500b-4cce-9b21-a931f411b587", + "name": "currentVersion", + "displayName": "Firmware version", + "displayNameEvent": "Firmware version changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "d236c735-012c-44c7-919e-809cae89de55", + "name": "availableVersion", + "displayName": "Available firmware version", + "displayNameEvent": "Available firmware version changed", + "type": "QString", + "defaultValue": "" + } + ], + "actionTypes": [ + { + "id": "212aaf17-faf1-4495-8475-0718b9b39628", + "name": "reboot", + "displayName": "Reboot" + }, + { + "id": "fa3a8bd6-182f-443f-ae41-d2c4aee6339d", + "name": "performUpdate", + "displayName": "Update firmware" + } + ] + }, { "id": "30e74e9f-57f4-4bbc-b0df-f2c4f28b2f06", "name": "shelly1pm", - "displayName": "Shelly 1PM/Plus 1PM", + "displayName": "Shelly 1PM", "createMethods": ["discovery"], "interfaces": [ "gateway", "smartmeterconsumer", "wirelessconnectable", "update" ], "paramTypes": [ @@ -241,6 +329,105 @@ } ] }, + { + "id": "4c497fe1-c399-463a-9a73-0c5f4ebac100", + "name": "shellyPlus1pm", + "displayName": "Shelly Plus 1PM", + "createMethods": ["discovery"], + "setupMethod": "enterpin", + "interfaces": [ "gateway", "smartmeterconsumer", "wirelessconnectable", "update" ], + "paramTypes": [ + { + "id": "0d97dbf6-0545-4953-ad71-4888df37df70", + "name":"id", + "displayName": "Shelly ID", + "type": "QString", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "bc425ba1-7776-4004-ba67-b256db7c511d", + "name": "power", + "displayName": "Powered", + "displayNameAction": "Turn on/off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalOutput" + }, + { + "id": "1b0ecbb2-5ed6-439e-81fb-5788ed8679e2", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "bffa3472-a09f-4e85-9d31-0b966e415cd2", + "name": "signalStrength", + "displayName": "Signal strength", + "type": "uint", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0, + "cached": false + }, + { + "id": "c9515efd-f16a-4a29-97e1-516d1d5a6a93", + "name": "updateStatus", + "displayName": "Update status", + "type": "QString", + "possibleValues": ["idle", "available", "updating"], + "defaultValue": "idle" + }, + { + "id": "e3859bc0-f96d-4fa1-958b-0e087a39a0e6", + "name": "currentVersion", + "displayName": "Firmware version", + "type": "QString", + "defaultValue": "" + }, + { + "id": "0dda20f7-e13e-494d-8351-c4401ded6c1e", + "name": "availableVersion", + "displayName": "Available firmware version", + "type": "QString", + "defaultValue": "" + }, + { + "id": "76e87d1f-bc65-4f63-b692-106861827633", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "40e63755-7220-4c7c-9c6a-fe5d7e230a35", + "name": "currentPower", + "displayName": "Current power consumption", + "type": "double", + "unit": "Watt", + "defaultValue": 0, + "cached": false + } + ], + "actionTypes": [ + { + "id": "b18211d8-528b-4bff-93c2-ce6a8b55759e", + "name": "reboot", + "displayName": "Reboot" + }, + { + "id": "0cabc075-9abc-49be-a50d-e8a8d7637d03", + "name": "performUpdate", + "displayName": "Update firmware" + } + ] + }, { "id": "20754114-1591-48b5-af2f-8c9966adb7c4", "name": "shelly1l", @@ -495,7 +682,7 @@ { "id": "465efb0d-da68-4177-a040-940c7f451e29", "name": "shelly25", - "displayName": "Shelly 2.5/Shelly Plus 2PM", + "displayName": "Shelly 2.5", "createMethods": ["discovery"], "interfaces": [ "gateway", "wirelessconnectable", "update" ], "paramTypes": [ @@ -617,10 +804,116 @@ } ] }, + { + "id": "86b037f5-c7f8-4778-b6d1-2b11c4ba1027", + "name": "shellyPlus25", + "displayName": "Shelly Plus 2PM", + "createMethods": ["discovery"], + "interfaces": [ "gateway", "wirelessconnectable", "update" ], + "setupMethod": "enterpin", + "paramTypes": [ + { + "id": "0cd68080-5da5-44c6-a1c2-12af086a16de", + "name":"id", + "displayName": "Shelly ID", + "type": "QString", + "readOnly": true + }, + { + "id": "0b45bc3d-f598-484b-8de4-03bc67b90664", + "name": "rollerMode", + "displayName": "Roller shutter mode", + "type": "bool", + "defaultValue": false + } + ], + "stateTypes": [ + { + "id": "8f4fe4d4-7036-4a1a-9960-091a4a7f6f02", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "01158eee-3605-45c2-8b6d-2c0a69321c5b", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "type": "uint", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0, + "cached": false + }, + { + "id": "395a188b-c2e2-40d1-a5f2-9ed4a9ff558f", + "name": "updateStatus", + "displayName": "Update status", + "displayNameEvent": "Update status changed", + "type": "QString", + "possibleValues": ["idle", "available", "updating"], + "defaultValue": "idle" + }, + { + "id": "c1f5d20a-3b06-4405-afca-20dfd41e3a34", + "name": "currentVersion", + "displayName": "Firmware version", + "displayNameEvent": "Firmware version changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "32134deb-8e37-4d65-ae10-aad0cb44ff7b", + "name": "availableVersion", + "displayName": "Available firmware version", + "displayNameEvent": "Available firmware version changed", + "type": "QString", + "defaultValue": "" + }, + { + "id": "bf969a1f-aa8a-40aa-8e50-af37b8b0ffe6", + "name": "channel1", + "displayName": "Power channel 1", + "displayNameEvent": "Channel 1 turned on or off", + "displayNameAction": "Turn channel 1 on or off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalOutput" + }, + { + "id": "43b41b49-def1-469b-b993-788647dc8839", + "name": "channel2", + "displayName": "Power channel 2", + "displayNameEvent": "Channel 2 turned on or off", + "displayNameAction": "Turn channel 2 on or off", + "type": "bool", + "defaultValue": false, + "writable": true, + "ioType": "digitalOutput" + } + ], + "actionTypes": [ + { + "id": "7714490b-117d-4d25-a695-9e151997f128", + "name": "reboot", + "displayName": "Reboot" + }, + { + "id": "9d5e04b8-cac4-49ce-9e32-efc3a06beb4b", + "name": "performUpdate", + "displayName": "Update firmware" + } + ] + }, { "id": "2c470ea4-6ef2-4aa2-b2f3-b6d8750ac577", "name": "shellyPlusPlug", - "displayName": "Shelly Plus Plug S", + "displayName": "Shelly Plus Plug S/US/UK/IT", "createMethods": ["discovery"], "interfaces": [ "powersocket", "smartmeterconsumer", "wirelessconnectable", "update" ], "paramTypes": [ @@ -3446,6 +3739,7 @@ "displayNameEvent": "Position changed", "displayNameAction": "Set position", "type": "int", + "unit": "Percentage", "defaultValue": 0, "minValue": 0, "maxValue": 100,