More work on Shelly. Pretty complete already for the Shelly1

master
Michael Zanetti 2019-09-26 00:51:10 +02:00
parent 0d4d6cd4f6
commit b744b3b245
4 changed files with 437 additions and 114 deletions

View File

@ -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.

View File

@ -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 &paramTypeId, 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;
}

View File

@ -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<Device*, MqttChannel*> m_mqttChannels;
QHash<DeviceClassId, StateTypeId> m_connectedStateTypesMap;
QHash<DeviceClassId, StateTypeId> m_powerStateTypeMap;
QHash<ActionTypeId, DeviceClassId> m_powerActionTypesMap;
QHash<ActionTypeId, ParamTypeId> m_powerActionParamTypesMap;
};
#endif // DEVICEPLUGINSHELLY_H

View File

@ -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
}
]
}