diff --git a/shelly/README.md b/shelly/README.md index cd89004a..7148ec3d 100644 --- a/shelly/README.md +++ b/shelly/README.md @@ -1,20 +1,29 @@ -# Tasmota +# Shelly -This plugin allows to make use of Sonoff-Tasmota devices via the nymea internal MQTT broker. There is no external MQTT broker needed. +The Shelly plugin adds support for Shelly devices (https://shelly.cloud). -Note that Sonoff devices must be flashed with the Tasmota sofware and connected to the WiFi network in order to work with this plugin. +Currently the Shelly1 and Shelly1PM are supported. -See the [Sonoff-Tasmota wiki](https://github.com/arendst/Sonoff-Tasmota/wiki) for a list of all supported devices and instructions on how to -install Tasmota on those. +## Requirements +Shelly devices communicate with via MQTT. This means, in order to add Shelly devices to nymea, the nymea instance is required +to have the MQTT broker enabled in the nymea settings and the Shelly device needs to be connected to the same WiFi as nymea is +in. New Shelly devices will open a WiFi named with their name as SSID. For instance, a Shelly 1 would appear as "shelly1-XXXXXX". +Connect to this WiFi and open the webpage that will pop up. From there, it can be configured it to connect to the same +network where the nymea system is located. No other options need to be set as they can be configured using nymea later on. -After flashing Tasmota to a Sonoff device and connecting it to WiFi, it can be added to nymea. The only required -thing is the IP address to the device. This plugin will create a new isoloated MQTT channel on the nymea internal -MQTT broker and provision login details to the Tasmota device via HTTP. Once that is successful, the Tasmota device -will connect to the MQTT broker and appear as connected in nymea. + +## Setting up devices +Once the Shelly is connected to the WiFi, a device discovery in nymea can be performed and will list the Shelly device. +During setup, the connected device can be configured. If the Shelly is connected to e.g. a light bulb, choose "Light" here. +Optionally, a username and password can be set. If the Shelly device is already configured to require authentication, +the username and password here must match the ones set on the Shelly. NOTE: If the Shelly is not configured to require a +login yet, but credentials are entered during setup, the Shelly device will be configured to require authentication from +now on. ## Plugin properties -When adding a Tasmota device it will add a new Gateway type device representing the Tasmota device itself. In addition -to that a power switch device will appear which can be used to control the switches in the Tasmota device. Upon -device setup, the user can optionally select the type of the connected hardware, (e.g. a light) which causes this -plugin to create a light device in the system which also controls the switches inside the Tasmota device and nicely -integrates with the nymea:ux for the given device type. +When adding a Shelly device it will add a new Gateway type device representing the Shelly device itself. It will allow +basic monitoring (such as the connected state) and interaction (e.g. reboot the Shelly device). In addition to that, a +power switch device will appear which will reflect presses on the Shelly's SW input. This power switch device also +offers the possiblity to configure the used switch (e.g. toggle, momentary, edge or detached from the Shelly's output). +If a connected device has been selected during setup, an additional device, e.g. the light will appear in the system and +can be used to control the power output of the Shelly, e.g. turning on or off the connected light. diff --git a/shelly/devicepluginshelly.cpp b/shelly/devicepluginshelly.cpp index 354ad850..f69a16ab 100644 --- a/shelly/devicepluginshelly.cpp +++ b/shelly/devicepluginshelly.cpp @@ -38,6 +38,18 @@ DevicePluginShelly::DevicePluginShelly() { + m_connectedStateTypesMap[shellySwitchDeviceClassId] = shellySwitchConnectedStateTypeId; + m_connectedStateTypesMap[shellyGenericDeviceClassId] = shellyGenericConnectedStateTypeId; + m_connectedStateTypesMap[shellyLightDeviceClassId] = shellyLightConnectedStateTypeId; + + m_powerActionTypesMap[shellyGenericPowerActionTypeId] = shellyGenericDeviceClassId; + m_powerActionTypesMap[shellyLightPowerActionTypeId] = shellyLightDeviceClassId; + + m_powerActionParamTypesMap[shellyGenericPowerActionTypeId] = shellyGenericPowerActionPowerParamTypeId; + m_powerActionParamTypesMap[shellyLightPowerActionTypeId] = shellyLightPowerActionPowerParamTypeId; + + m_powerStateTypeMap[shellyGenericDeviceClassId] = shellyGenericPowerStateTypeId; + m_powerStateTypeMap[shellyLightDeviceClassId] = shellyLightPowerStateTypeId; } DevicePluginShelly::~DevicePluginShelly() @@ -53,14 +65,17 @@ void DevicePluginShelly::discoverDevices(DeviceDiscoveryInfo *info) { foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { // qCDebug(dcShelly()) << "Have entry" << entry; - QRegExp namePattern("^shelly[1-2]-[0-9A-Z]+$"); + QRegExp namePattern; + if (info->deviceClassId() == shelly1DeviceClassId) { + namePattern = QRegExp("^shelly(1|1pm|plug|plug-s)-[0-9A-Z]+$"); + } if (!entry.name().contains(namePattern)) { continue; } - DeviceDescriptor descriptor(shellyOneDeviceClassId, entry.name(), entry.hostAddress().toString()); + DeviceDescriptor descriptor(shelly1DeviceClassId, entry.name(), entry.hostAddress().toString()); ParamList params; - params << Param(shellyOneDeviceIdParamTypeId, entry.name()); + params << Param(shelly1DeviceIdParamTypeId, entry.name()); descriptor.setParams(params); Device *existingDevice = myDevices().findByParams(params); @@ -80,87 +95,12 @@ void DevicePluginShelly::setupDevice(DeviceSetupInfo *info) { Device *device = info->device(); - if (device->deviceClassId() == shellyOneDeviceClassId) { - QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString(); - ZeroConfServiceEntry zeroConfEntry; - foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { - if (entry.name() == shellyId) { - zeroConfEntry = entry; - } - } - QHostAddress address; - pluginStorage()->beginGroup(device->id().toString()); - if (zeroConfEntry.isValid()) { - address = zeroConfEntry.hostAddress().toString(); - pluginStorage()->setValue("cachedAddress", address.toString()); - } else { - qCWarning(dcShelly()) << "Could not find Shelly device on zeroconf. Trying cached address."; - address = pluginStorage()->value("cachedAddress").toString(); - } - pluginStorage()->endGroup(); - - if (address.isNull()) { - qCWarning(dcShelly()) << "Unable to determine Shelly's network address. Failed to set up device."; - info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Unable to find the device in the network.")); - return; - } - - MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(shellyId, QHostAddress(address), {"shellies"}); - if (!channel) { - qCWarning(dcShelly()) << "Failed to create MQTT channel."; - return info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings.")); - } - - QUrl url; - url.setScheme("http"); - url.setHost(address.toString()); - url.setPort(80); - url.setPath("/settings"); - - QUrlQuery query; - query.addQueryItem("mqtt_server", channel->serverAddress().toString() + ":" + QString::number(channel->serverPort())); - query.addQueryItem("mqtt_user", channel->username()); - query.addQueryItem("mqtt_pass", channel->password()); - query.addQueryItem("mqtt_enable", "true"); - - url.setQuery(query); - - QNetworkRequest request(url); - qCDebug(dcShelly()) << "Connecting to" << url.toString(); - QNetworkReply *reply = hardwareManager()->networkManager()->get(request); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - connect(info, &DeviceSetupInfo::aborted, channel, [this, channel](){ - hardwareManager()->mqttProvider()->releaseChannel(channel); - }); - connect(reply, &QNetworkReply::finished, info, [this, info, reply, channel](){ - if (reply->error() != QNetworkReply::NoError) { - qCWarning(dcShelly()) << "Error fetching device settings" << reply->error() << reply->errorString(); - info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error connecting to Shelly device.")); - hardwareManager()->mqttProvider()->releaseChannel(channel); - return; - } - QByteArray data = reply->readAll(); - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcShelly()) << "Error parsing settings reply" << error.errorString() << "\n" << data; - info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Unexpected data received from Shelly device.")); - hardwareManager()->mqttProvider()->releaseChannel(channel); - return; - } - qCDebug(dcShelly()) << "Settings data" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); - - m_mqttChannels.insert(info->device(), channel); - connect(channel, &MqttChannel::clientConnected, this, &DevicePluginShelly::onClientConnected); - connect(channel, &MqttChannel::clientDisconnected, this, &DevicePluginShelly::onClientDisconnected); - connect(channel, &MqttChannel::publishReceived, this, &DevicePluginShelly::onPublishReceived); - - info->finish(Device::DeviceErrorNoError); - }); + if (device->deviceClassId() == shelly1DeviceClassId) { + setupShellyGateway(info); return; } - qCWarning(dcShelly) << "Unhandled DeviceClass in setupDevice" << device->deviceClassId(); + setupShellyChild(info); } void DevicePluginShelly::deviceRemoved(Device *device) @@ -176,10 +116,27 @@ void DevicePluginShelly::executeAction(DeviceActionInfo *info) Device *device = info->device(); Action action = info->action(); - if (action.actionTypeId() == shellyOnePowerActionTypeId) { - MqttChannel *channel = m_mqttChannels.value(device); - QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString(); - bool on = action.param(shellyOnePowerActionPowerParamTypeId).value().toBool(); + if (action.actionTypeId() == shelly1RebootActionTypeId) { + QUrl url; + url.setScheme("http"); + url.setHost(getIP(info->device())); + url.setPath("/reboot"); + url.setUserName(device->paramValue(shelly1DeviceUsernameParamTypeId).toString()); + url.setPassword(device->paramValue(shelly1DevicePasswordParamTypeId).toString()); + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, info, [info, reply](){ + info->finish(reply->error() == QNetworkReply::NoError ? Device::DeviceErrorNoError : Device::DeviceErrorHardwareFailure); + }); + return; + } + + if (m_powerActionTypesMap.contains(action.actionTypeId())) { + Device *parentDevice = myDevices().findById(device->parentId()); + MqttChannel *channel = m_mqttChannels.value(parentDevice); + QString shellyId = parentDevice->paramValue(shelly1DeviceIdParamTypeId).toString(); + ParamTypeId powerParamTypeId = m_powerActionParamTypesMap.value(action.actionTypeId()); + bool on = action.param(powerParamTypeId).value().toBool(); channel->publish("shellies/" + shellyId + "/relay/0/command", on ? "on" : "off"); info->finish(Device::DeviceErrorNoError); return; @@ -195,7 +152,11 @@ void DevicePluginShelly::onClientConnected(MqttChannel *channel) qCWarning(dcShelly()) << "Received a client connect for a device we don't know!"; return; } - device->setStateValue(shellyOneConnectedStateTypeId, true); + device->setStateValue(shelly1ConnectedStateTypeId, true); + + foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) { + child->setStateValue(m_connectedStateTypesMap[child->deviceClassId()], true); + } } void DevicePluginShelly::onClientDisconnected(MqttChannel *channel) @@ -205,7 +166,11 @@ void DevicePluginShelly::onClientDisconnected(MqttChannel *channel) qCWarning(dcShelly()) << "Received a client disconnect for a device we don't know!"; return; } - device->setStateValue(shellyOneConnectedStateTypeId, false); + device->setStateValue(shelly1ConnectedStateTypeId, false); + + foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) { + child->setStateValue(m_connectedStateTypesMap[child->deviceClassId()], false); + } } void DevicePluginShelly::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload) @@ -216,16 +181,215 @@ void DevicePluginShelly::onPublishReceived(MqttChannel *channel, const QString & return; } - QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString(); + QString shellyId = device->paramValue(shelly1DeviceIdParamTypeId).toString(); if (topic == "shellies/" + shellyId + "/input/0") { // "1" or "0" // Emit event button pressed + bool on = payload == "1"; + foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) { + if (child->deviceClassId() == shellySwitchDeviceClassId) { + if (child->stateValue(shellySwitchPowerStateTypeId).toBool() != on) { + child->setStateValue(shellySwitchPowerStateTypeId, on); + emit emitEvent(Event(shellySwitchPressedEventTypeId, child->id())); + } + } + } } if (topic == "shellies/" + shellyId + "/relay/0") { bool on = payload == "on"; - device->setStateValue(shellyOnePowerStateTypeId, on); + + foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) { + if (m_powerStateTypeMap.contains(child->deviceClassId())) { + child->setStateValue(m_powerStateTypeMap.value(child->deviceClassId()), on); + } + } } qCDebug(dcShelly()) << "Publish received from" << device->name() << topic << payload; - +} + +void DevicePluginShelly::setupShellyGateway(DeviceSetupInfo *info) +{ + QString shellyId = info->device()->paramValue(shelly1DeviceIdParamTypeId).toString(); + ZeroConfServiceEntry zeroConfEntry; + foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) { + if (entry.name() == shellyId) { + zeroConfEntry = entry; + } + } + QHostAddress address; + pluginStorage()->beginGroup(info->device()->id().toString()); + if (zeroConfEntry.isValid()) { + address = zeroConfEntry.hostAddress().toString(); + pluginStorage()->setValue("cachedAddress", address.toString()); + } else { + qCWarning(dcShelly()) << "Could not find Shelly device on zeroconf. Trying cached address."; + address = pluginStorage()->value("cachedAddress").toString(); + } + pluginStorage()->endGroup(); + + if (address.isNull()) { + qCWarning(dcShelly()) << "Unable to determine Shelly's network address. Failed to set up device."; + info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Unable to find the device in the network.")); + return; + } + + MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(shellyId, QHostAddress(address), {"shellies"}); + if (!channel) { + qCWarning(dcShelly()) << "Failed to create MQTT channel."; + return info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings.")); + } + + QUrl url; + url.setScheme("http"); + url.setHost(address.toString()); + url.setPort(80); + url.setPath("/settings"); + url.setUserName(info->device()->paramValue(shelly1DeviceUsernameParamTypeId).toString()); + url.setPassword(info->device()->paramValue(shelly1DevicePasswordParamTypeId).toString()); + + QUrlQuery query; + query.addQueryItem("mqtt_server", channel->serverAddress().toString() + ":" + QString::number(channel->serverPort())); + query.addQueryItem("mqtt_user", channel->username()); + query.addQueryItem("mqtt_pass", channel->password()); + query.addQueryItem("mqtt_enable", "true"); + + url.setQuery(query); + QNetworkRequest request(url); + + qCDebug(dcShelly()) << "Connecting to" << url.toString(); + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(info, &DeviceSetupInfo::aborted, channel, [this, channel](){ + hardwareManager()->mqttProvider()->releaseChannel(channel); + }); + connect(reply, &QNetworkReply::finished, info, [this, info, reply, channel, address](){ + if (reply->error() != QNetworkReply::NoError) { + hardwareManager()->mqttProvider()->releaseChannel(channel); + qCWarning(dcShelly()) << "Error fetching device settings" << reply->error() << reply->errorString(); + if (reply->error() == QNetworkReply::AuthenticationRequiredError) { + info->finish(Device::DeviceErrorAuthenticationFailure, QT_TR_NOOP("Username and password not set correctly.")); + } else { + info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error connecting to Shelly device.")); + } + return; + } + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcShelly()) << "Error parsing settings reply" << error.errorString() << "\n" << data; + info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Unexpected data received from Shelly device.")); + hardwareManager()->mqttProvider()->releaseChannel(channel); + return; + } + qCDebug(dcShelly()) << "Settings data" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + + m_mqttChannels.insert(info->device(), channel); + connect(channel, &MqttChannel::clientConnected, this, &DevicePluginShelly::onClientConnected); + connect(channel, &MqttChannel::clientDisconnected, this, &DevicePluginShelly::onClientDisconnected); + connect(channel, &MqttChannel::publishReceived, this, &DevicePluginShelly::onPublishReceived); + + DeviceDescriptors autoChilds; + + // Always create the switch device if we don't have one yet + if (myDevices().filterByParentDeviceId(info->device()->id()).filterByDeviceClassId(shellySwitchDeviceClassId).isEmpty()) { + DeviceDescriptor switchChild(shellySwitchDeviceClassId, "Shelly switch", QString(), info->device()->id()); + autoChilds.append(switchChild); + } + + // Add connected devices as configured in params + if (info->device()->paramValue(shelly1DeviceConnectedDeviceParamTypeId).toString() == "Generic") { + if (myDevices().filterByParentDeviceId(info->device()->id()).filterByDeviceClassId(shellyGenericDeviceClassId).isEmpty()) { + DeviceDescriptor genericChild(shellyGenericDeviceClassId, "Shelly connected device", QString(), info->device()->id()); + autoChilds.append(genericChild); + } + } + if (info->device()->paramValue(shelly1DeviceConnectedDeviceParamTypeId).toString() == "Light") { + if (myDevices().filterByParentDeviceId(info->device()->id()).filterByDeviceClassId(shellyLightDeviceClassId).isEmpty()) { + DeviceDescriptor genericChild(shellyLightDeviceClassId, "Shelly connected light", QString(), info->device()->id()); + autoChilds.append(genericChild); + } + } + + info->finish(Device::DeviceErrorNoError); + + emit autoDevicesAppeared(autoChilds); + + // Make sure authentication is enalbed if the user wants it + QString username = info->device()->paramValue(shelly1DeviceUsernameParamTypeId).toString(); + QString password = info->device()->paramValue(shelly1DevicePasswordParamTypeId).toString(); + if (!username.isEmpty()) { + QUrl url; + url.setScheme("http"); + url.setHost(address.toString()); + url.setPort(80); + url.setPath("/settings/login"); + url.setUserName(username); + url.setPassword(password); + + QUrlQuery query; + query.addQueryItem("username", username); + query.addQueryItem("password", password); + query.addQueryItem("enabled", "true"); + + url.setQuery(query); + + QNetworkRequest request(url); + qCDebug(dcShelly()) << "Enabling auth" << username << password; + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + } + }); +} + +void DevicePluginShelly::setupShellyChild(DeviceSetupInfo *info) +{ + Device *device = info->device(); + + // Connect to settings changes to store them to the device + connect(info->device(), &Device::settingChanged, this, [this, device](const ParamTypeId ¶mTypeId, const QVariant &value){ + Device *parentDevice = myDevices().findById(device->parentId()); + pluginStorage()->beginGroup(parentDevice->id().toString()); + QString address = pluginStorage()->value("cachedAddress").toString(); + pluginStorage()->endGroup(); + + QUrl url; + url.setScheme("http"); + url.setHost(address); + url.setPort(80); + url.setPath("/settings/relay/0"); + url.setUserName(parentDevice->paramValue(shelly1DeviceUsernameParamTypeId).toString()); + url.setPassword(parentDevice->paramValue(shelly1DevicePasswordParamTypeId).toString()); + + QUrlQuery query; + if (paramTypeId == shellySwitchSettingsButtonTypeParamTypeId) { + query.addQueryItem("btn_type", value.toString()); + } + if (paramTypeId == shellySwitchSettingsInvertButtonParamTypeId) { + query.addQueryItem("btn_reverse", value.toBool() ? "1" : "0"); + } + if (paramTypeId == shellyGenericSettingsDefaultStateParamTypeId || paramTypeId == shellyLightSettingsDefaultStateParamTypeId) { + query.addQueryItem("default_state", value.toString()); + } + + url.setQuery(query); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + }); + + info->finish(Device::DeviceErrorNoError); +} + +QString DevicePluginShelly::getIP(Device *device) const +{ + Device *d = device; + if (!device->parentId().isNull()) { + d = myDevices().findById(device->parentId()); + } + pluginStorage()->beginGroup(d->id().toString()); + QString ip = pluginStorage()->value("cachedAddress").toString(); + pluginStorage()->endGroup(); + return ip; } diff --git a/shelly/devicepluginshelly.h b/shelly/devicepluginshelly.h index 34f35a68..085e0891 100644 --- a/shelly/devicepluginshelly.h +++ b/shelly/devicepluginshelly.h @@ -50,10 +50,21 @@ private slots: void onClientDisconnected(MqttChannel* channel); void onPublishReceived(MqttChannel* channel, const QString &topic, const QByteArray &payload); +private: + void setupShellyGateway(DeviceSetupInfo *info); + void setupShellyChild(DeviceSetupInfo *info); + + QString getIP(Device *device) const; + private: ZeroConfServiceBrowser *m_zeroconfBrowser = nullptr; QHash m_mqttChannels; + + QHash m_connectedStateTypesMap; + QHash m_powerStateTypeMap; + QHash m_powerActionTypesMap; + QHash m_powerActionParamTypesMap; }; #endif // DEVICEPLUGINSHELLY_H diff --git a/shelly/devicepluginshelly.json b/shelly/devicepluginshelly.json index 0b45d850..2d78a83c 100644 --- a/shelly/devicepluginshelly.json +++ b/shelly/devicepluginshelly.json @@ -10,15 +10,36 @@ "deviceClasses": [ { "id": "f810b66a-7177-4397-9771-4229abaabbb6", - "name": "shellyOne", - "displayName": "Shelly One", + "name": "shelly1", + "displayName": "Shelly1 / Shelly1PM", "createMethods": ["discovery"], - "interfaces": [ "powerswitch", "connectable" ], + "interfaces": [ "gateway" ], "paramTypes": [ { "id": "1d301dc0-5e48-473f-a611-8e407289e545", "name":"id", - "displayName": "ID", + "displayName": "Shelly ID", + "type": "QString", + "readOnly": true + }, + { + "id": "d0e0499e-faa0-432a-a760-c295b0aefed0", + "name": "connectedDevice", + "displayName": "Connected device", + "type": "QString", + "allowedValues": ["None", "Generic", "Light"], + "defaultValue": "Generic" + }, + { + "id": "fa1aa0f6-93b2-410d-a2c5-7b2f45eae679", + "name": "username", + "displayName": "Username (optional)", + "type": "QString" + }, + { + "id": "d29b8399-bfa6-4146-921d-a1d43ca4e184", + "name": "password", + "displayName": "Password (optional)", "type": "QString" } ], @@ -31,9 +52,95 @@ "type": "bool", "defaultValue": false, "cached": false + } + ], + "actionTypes": [ + { + "id": "b4067d54-36c5-4d30-bbc3-c8c712d6fd32", + "name": "reboot", + "displayName": "Reboot" + } + ] + }, + { + "id": "6de35a17-0f54-4397-894d-4321b64c53d1", + "name": "shellySwitch", + "displayName": "Shelly switch", + "createMethods": ["auto"], + "interfaces": [ "powerswitch", "connectable"], + "settingsTypes": [ + { + "id": "ce9f1650-5e12-40f4-97de-27af86afa40b", + "name": "buttonType", + "displayName": "Button type", + "allowedValues": ["momentary", "toggle", "edge", "detached"], + "type": "QString", + "defaultValue": "toggle" }, { - "id": "0f6df838-7fc4-4fc0-9247-b9b8fa4ec924", + "id": "f31eb52b-9aaf-409d-8bba-badda7c1a249", + "name": "invertButton", + "displayName": "Invert button", + "type": "bool", + "defaultValue": false + } + ], + "stateTypes": [ + { + "id": "0c233312-7b8f-4ca3-880d-523cab9b3ccb", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "20f74d88-0683-4d3a-9513-6b29b5112b7b", + "name": "power", + "displayName": "On/Off", + "displayNameEvent": "On/Off toggled", + "type": "bool", + "defaultValue": false + } + ], + "eventTypes": [ + { + "id": "41498655-1943-4b46-ac36-adea7bafab87", + "name": "pressed", + "displayName": "Pressed" + } + ] + }, + { + "id": "512c3c7d-d6a6-4d2a-bccd-83147e5f9a25", + "name": "shellyGeneric", + "displayName": "Shelly connected device", + "createMethods": ["auto"], + "interfaces": ["power", "connectable"], + "settingsTypes": [ + { + "id": "7d35aea3-1444-48c8-9732-a41bfc3b9d75", + "name": "defaultState", + "displayName": "Default state", + "allowedValues": ["on", "off", "last", "switch"], + "defaultValue": "off", + "type": "QString" + } + + ], + "stateTypes": [ + { + "id": "4a141674-faa6-4953-8272-5b4a4da84d31", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "72d7dbba-757c-4b03-a092-1d3f374fa961", "name": "power", "displayName": "Power", "displayNameEvent": "Power changed", @@ -42,12 +149,44 @@ "defaultValue": false, "writable": true } - ], - "eventTypes": [ + + ] + }, + { + "id": "62a2d6b8-d70d-45fc-ba8c-1c680282a399", + "name": "shellyLight", + "displayName": "Shelly connected light", + "createMethods": ["auto"], + "interfaces": ["light", "connectable"], + "settingsTypes": [ { - "id": "172e6aa3-13d3-4c71-8a4d-112605460863", - "name": "pressed", - "displayName": "Pressed" + "id": "4fe9ae31-3657-41bf-bd40-a219d58465d3", + "name": "defaultState", + "displayName": "Default state", + "allowedValues": ["on", "off", "last", "switch"], + "defaultValue": "off", + "type": "QString" + } + ], + "stateTypes": [ + { + "id": "61b7d8ac-d229-4268-8143-6edb2eca978d", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "2ee5bfab-271e-4b95-9464-122a5208f1a5", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true } ] }