Merge PR #343: Shelly: Add support for the Shelly Button 1

master
Jenkins nymea 2020-12-21 18:01:38 +01:00
commit 2cc56c3d01
5 changed files with 738 additions and 345 deletions

View File

@ -9,6 +9,7 @@ The currently supported devices are:
* Shelly Plug / PlugS
* Shelly RGBW2
* Shelly Dimmer / Dimmer 2
* Shelly Button 1
## Requirements
Shelly devices communicate with via MQTT. This means, in order to add Shelly devices to nymea, the nymea instance is required
@ -26,6 +27,11 @@ authentication, the username and password here must match the ones set on the Sh
to require a login yet, but credentials are entered during setup, the Shelly device will be configured to require authentication
from now on and this login will be required also for the web interface of the Shelly device.
### Note for the Shelly Button 1
It is recommended to keep the Shelly Button 1 plugged into a power source during setup and configuration. Without a power source
it will shut down right after a button press which might interrupt the setup. However, once set up, the Shelly Button 1 will work
perfectly fine in the low power mode.
## Plugin properties
When adding a Shelly device that is meant to be installed in walls and has connectors to switches, a new Gateway type device
representing the Shelly device itself will be added. The gateway device allow basic monitoring (such as the connected state)

View File

@ -64,6 +64,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_idParamTypeMap[shellyRgbw2ThingClassId] = shellyRgbw2ThingIdParamTypeId;
m_idParamTypeMap[shellyDimmerThingClassId] = shellyDimmerThingIdParamTypeId;
m_idParamTypeMap[shelly25ThingClassId] = shelly25ThingIdParamTypeId;
m_idParamTypeMap[shellyButton1ThingClassId] = shellyButton1ThingIdParamTypeId;
m_usernameParamTypeMap[shelly1ThingClassId] = shelly1ThingUsernameParamTypeId;
m_usernameParamTypeMap[shelly1pmThingClassId] = shelly1pmThingUsernameParamTypeId;
@ -71,6 +72,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_usernameParamTypeMap[shellyRgbw2ThingClassId] = shellyRgbw2ThingUsernameParamTypeId;
m_usernameParamTypeMap[shellyDimmerThingClassId] = shellyDimmerThingUsernameParamTypeId;
m_usernameParamTypeMap[shelly25ThingClassId] = shelly25ThingUsernameParamTypeId;
m_usernameParamTypeMap[shellyButton1ThingClassId] = shellyButton1ThingUsernameParamTypeId;
m_passwordParamTypeMap[shelly1ThingClassId] = shelly1ThingPasswordParamTypeId;
m_passwordParamTypeMap[shelly1pmThingClassId] = shelly1pmThingPasswordParamTypeId;
@ -78,6 +80,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_passwordParamTypeMap[shellyRgbw2ThingClassId] = shellyRgbw2ThingPasswordParamTypeId;
m_passwordParamTypeMap[shellyDimmerThingClassId] = shellyDimmerThingPasswordParamTypeId;
m_passwordParamTypeMap[shelly25ThingClassId] = shelly25ThingPasswordParamTypeId;
m_passwordParamTypeMap[shellyButton1ThingClassId] = shellyButton1ThingPasswordParamTypeId;
m_connectedDeviceParamTypeMap[shelly1ThingClassId] = shelly1ThingConnectedDeviceParamTypeId;
m_connectedDeviceParamTypeMap[shelly1pmThingClassId] = shelly1pmThingConnectedDeviceParamTypeId;
@ -100,6 +103,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_connectedStateTypesMap[shellyPlugThingClassId] = shellyPlugConnectedStateTypeId;
m_connectedStateTypesMap[shellyRgbw2ThingClassId] = shellyRgbw2ConnectedStateTypeId;
m_connectedStateTypesMap[shellyDimmerThingClassId] = shellyDimmerConnectedStateTypeId;
m_connectedStateTypesMap[shellyButton1ThingClassId] = shellyButton1ConnectedStateTypeId;
m_connectedStateTypesMap[shellySwitchThingClassId] = shellySwitchConnectedStateTypeId;
m_connectedStateTypesMap[shellyGenericThingClassId] = shellyGenericConnectedStateTypeId;
m_connectedStateTypesMap[shellyLightThingClassId] = shellyLightConnectedStateTypeId;
@ -115,6 +119,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_signalStrengthStateTypesMap[shellyPlugThingClassId] = shellyPlugSignalStrengthStateTypeId;
m_signalStrengthStateTypesMap[shellyRgbw2ThingClassId] = shellyRgbw2SignalStrengthStateTypeId;
m_signalStrengthStateTypesMap[shellyDimmerThingClassId] = shellyDimmerSignalStrengthStateTypeId;
m_signalStrengthStateTypesMap[shellyButton1ThingClassId] = shellyButton1SignalStrengthStateTypeId;
m_signalStrengthStateTypesMap[shellySwitchThingClassId] = shellySwitchSignalStrengthStateTypeId;
m_signalStrengthStateTypesMap[shellyGenericThingClassId] = shellyGenericSignalStrengthStateTypeId;
m_signalStrengthStateTypesMap[shellyLightThingClassId] = shellyLightSignalStrengthStateTypeId;
@ -160,6 +165,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_updateStatusStateTypesMap[shellyPlugThingClassId] = shellyPlugUpdateStatusStateTypeId;
m_updateStatusStateTypesMap[shellyRgbw2ThingClassId] = shellyRgbw2UpdateStatusStateTypeId;
m_updateStatusStateTypesMap[shellyDimmerThingClassId] = shellyDimmerUpdateStatusStateTypeId;
m_updateStatusStateTypesMap[shellyButton1ThingClassId] = shellyButton1UpdateStatusStateTypeId;
m_currentVersionStateTypesMap[shelly1ThingClassId] = shelly1CurrentVersionStateTypeId;
m_currentVersionStateTypesMap[shelly1pmThingClassId] = shelly1pmCurrentVersionStateTypeId;
@ -167,6 +173,7 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_currentVersionStateTypesMap[shellyPlugThingClassId] = shellyPlugCurrentVersionStateTypeId;
m_currentVersionStateTypesMap[shellyRgbw2ThingClassId] = shellyRgbw2CurrentVersionStateTypeId;
m_currentVersionStateTypesMap[shellyDimmerThingClassId] = shellyDimmerCurrentVersionStateTypeId;
m_currentVersionStateTypesMap[shellyButton1ThingClassId] = shellyButton1CurrentVersionStateTypeId;
m_availableVersionStateTypesMap[shelly1ThingClassId] = shelly1AvailableVersionStateTypeId;
m_availableVersionStateTypesMap[shelly1pmThingClassId] = shelly1pmAvailableVersionStateTypeId;
@ -174,6 +181,11 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_availableVersionStateTypesMap[shellyPlugThingClassId] = shellyPlugAvailableVersionStateTypeId;
m_availableVersionStateTypesMap[shellyRgbw2ThingClassId] = shellyRgbw2AvailableVersionStateTypeId;
m_availableVersionStateTypesMap[shellyDimmerThingClassId] = shellyDimmerAvailableVersionStateTypeId;
m_availableVersionStateTypesMap[shellyButton1ThingClassId] = shellyButton1AvailableVersionStateTypeId;
m_batteryLevelStateTypeMap[shellyButton1ThingClassId] = shellyButton1BatteryLevelStateTypeId;
m_batteryCriticalStateTypeMap[shellyButton1ThingClassId] = shellyButton1BatteryCriticalStateTypeId;
// Actions and their params
m_rebootActionTypeMap[shelly1RebootActionTypeId] = shelly1ThingClassId;
@ -220,6 +232,14 @@ IntegrationPluginShelly::IntegrationPluginShelly()
m_rollerOpenActionTypeMap[shellyRollerOpenActionTypeId] = shellyRollerThingClassId;
m_rollerCloseActionTypeMap[shellyRollerCloseActionTypeId] = shellyRollerThingClassId;
m_rollerStopActionTypeMap[shellyRollerStopActionTypeId] = shellyRollerThingClassId;
m_updateActionTypesMap[shelly1PerformUpdateActionTypeId] = shelly1ThingClassId;
m_updateActionTypesMap[shelly1pmPerformUpdateActionTypeId] = shelly1pmThingClassId;
m_updateActionTypesMap[shelly25PerformUpdateActionTypeId] = shelly25ThingClassId;
m_updateActionTypesMap[shellyPlugPerformUpdateActionTypeId] = shellyPlugThingClassId;
m_updateActionTypesMap[shellyRgbw2PerformUpdateActionTypeId] = shellyRgbw2ThingClassId;
m_updateActionTypesMap[shellyDimmerPerformUpdateActionTypeId] = shellyDimmerThingClassId;
m_updateActionTypesMap[shellyButton1PerformUpdateActionTypeId] = shellyButton1ThingClassId;
}
IntegrationPluginShelly::~IntegrationPluginShelly()
@ -248,6 +268,8 @@ void IntegrationPluginShelly::discoverThings(ThingDiscoveryInfo *info)
namePattern = QRegExp("^shellydimmer(2)?-[0-9A-Z]+$");
} else if (info->thingClassId() == shelly25ThingClassId) {
namePattern = QRegExp("^shellyswitch25-[0-9A-Z]+$");
} else if (info->thingClassId() == shellyButton1ThingClassId) {
namePattern = QRegExp("^shellybutton1-[0-9-A-Z]+$");
}
if (!entry.name().contains(namePattern)) {
continue;
@ -289,7 +311,8 @@ void IntegrationPluginShelly::setupThing(ThingSetupInfo *info)
|| thing->thingClassId() == shellyPlugThingClassId
|| thing->thingClassId() == shellyRgbw2ThingClassId
|| thing->thingClassId() == shellyDimmerThingClassId
|| thing->thingClassId() == shelly25ThingClassId) {
|| thing->thingClassId() == shelly25ThingClassId
|| thing->thingClassId() == shellyButton1ThingClassId) {
setupShellyGateway(info);
return;
}
@ -335,6 +358,7 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
QString shellyId = thing->paramValue(m_idParamTypeMap.value(thing->thingClassId())).toString();
channel->publish(QString("shellies/%1/command").arg(shellyId), "update_fw");
info->finish(Thing::ThingErrorNoError);
return;
}
if (m_powerActionTypesMap.contains(action.actionTypeId())) {
@ -519,7 +543,7 @@ void IntegrationPluginShelly::onPublishReceived(MqttChannel *channel, const QStr
return;
}
qCDebug(dcShelly()) << "Publish received from" << thing->name() << topic;
qCDebug(dcShelly()) << "Publish received from" << thing->name() << topic << payload;
QString shellyId = thing->paramValue(m_idParamTypeMap.value(thing->thingClassId())).toString();
if (topic == "shellies/" + shellyId + "/info") {
@ -707,6 +731,33 @@ void IntegrationPluginShelly::onPublishReceived(MqttChannel *channel, const QStr
child->setStateValue(shellyRollerPercentageStateTypeId, 100 - pos);
}
}
if (topic == "shellies/" + shellyId + "/sensor/battery") {
if (m_batteryLevelStateTypeMap.contains(thing->thingClassId())) {
int batteryLevel = payload.toInt();
thing->setStateValue(m_batteryLevelStateTypeMap.value(thing->thingClassId()), batteryLevel);
thing->setStateValue(m_batteryCriticalStateTypeMap.value(thing->thingClassId()), batteryLevel < 10);
}
}
if (topic == "shellies/" + shellyId + "/input_event/0") {
if (thing->thingClassId() == shellyButton1ThingClassId) {
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(payload, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcShelly()) << "Failed to parse JSON from shelly:" << error.errorString() << qUtf8Printable(payload);
return;
}
QString event = jsonDoc.toVariant().toMap().value("event").toString();
if (event.isEmpty()) {
return;
}
EventTypeId eventTypeId = event == "L" ? shellyButton1LongPressedEventTypeId : shellyButton1PressedEventTypeId;
ParamTypeId paramTypeId = eventTypeId == shellyButton1PressedEventTypeId ? shellyButton1PressedEventButtonNameParamTypeId : shellyButton1LongPressedEventButtonNameParamTypeId;
QString param = QString::number(event.length());
thing->emitEvent(eventTypeId, ParamList() << Param(paramTypeId, param));
}
}
}
void IntegrationPluginShelly::updateStatus()
@ -834,6 +885,16 @@ void IntegrationPluginShelly::setupShellyGateway(ThingSetupInfo *info)
return;
}
qCDebug(dcShelly()) << "Settings data" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
QVariantMap settingsMap = jsonDoc.toVariant().toMap();
if (info->thing()->thingClassId() == shellyPlugThingClassId) {
info->thing()->setSettingValue(shellyPlugSettingsDefaultStateParamTypeId, settingsMap.value("relays").toList().first().toMap().value("default_state").toString());
} else if (info->thing()->thingClassId() == shellyButton1ThingClassId) {
info->thing()->setSettingValue(shellyButton1SettingsRemainAwakeParamTypeId, settingsMap.value("remain_awake").toInt());
info->thing()->setSettingValue(shellyButton1SettingsStatusLedEnabledParamTypeId, !settingsMap.value("led_status_disable").toBool());
info->thing()->setSettingValue(shellyButton1SettingsLongpushMaxDurationParamTypeId, settingsMap.value("longpush_duration_ms").toMap().value("max").toUInt());
info->thing()->setSettingValue(shellyButton1SettingsMultipressIntervalParamTypeId, settingsMap.value("multipush_time_between_pushes_ms").toMap().value("max").toUInt());
}
ThingDescriptors autoChilds;
@ -959,8 +1020,10 @@ void IntegrationPluginShelly::setupShellyGateway(ThingSetupInfo *info)
});
// Handle thing settings of gateway devices
if (info->thing()->thingClassId() == shellyPlugThingClassId) {
connect(info->thing(), &Thing::settingChanged, this, [this, thing, shellyId](const ParamTypeId &paramTypeId, const QVariant &value) {
if (info->thing()->thingClassId() == shellyPlugThingClassId ||
info->thing()->thingClassId() == shellyButton1ThingClassId) {
connect(info->thing(), &Thing::settingChanged, this, [this, thing, shellyId](const ParamTypeId &settingTypeId, const QVariant &value) {
pluginStorage()->beginGroup(thing->id().toString());
QString address = pluginStorage()->value("cachedAddress").toString();
pluginStorage()->endGroup();
@ -969,13 +1032,22 @@ void IntegrationPluginShelly::setupShellyGateway(ThingSetupInfo *info)
url.setScheme("http");
url.setHost(address);
url.setPort(80);
url.setPath("/settings/relay/0");
url.setUserName(thing->paramValue(m_usernameParamTypeMap.value(thing->thingClassId())).toString());
url.setPassword(thing->paramValue(m_passwordParamTypeMap.value(thing->thingClassId())).toString());
QUrlQuery query;
if (paramTypeId == shellyPlugSettingsDefaultStateParamTypeId) {
if (settingTypeId == shellyPlugSettingsDefaultStateParamTypeId) {
url.setPath("/settings/relay/0");
query.addQueryItem("default_state", value.toString());
} else if (settingTypeId == shellyButton1SettingsRemainAwakeParamTypeId) {
url.setPath("/settings");
query.addQueryItem("remain_awake", value.toString());
} else if (settingTypeId == shellyButton1SettingsStatusLedEnabledParamTypeId) {
url.setPath("/settings");
query.addQueryItem("led_status_disable", value.toBool() ? "false" : "true");
} else if (settingTypeId == shellyButton1SettingsLongpushMaxDurationParamTypeId) {
url.setPath("/settings");
query.addQueryItem("longpush_duration_ms_max", value.toString());
}
url.setQuery(query);

View File

@ -93,6 +93,8 @@ private:
QHash<ThingClassId, StateTypeId> m_updateStatusStateTypesMap;
QHash<ThingClassId, StateTypeId> m_currentVersionStateTypesMap;
QHash<ThingClassId, StateTypeId> m_availableVersionStateTypesMap;
QHash<ThingClassId, StateTypeId> m_batteryLevelStateTypeMap;
QHash<ThingClassId, StateTypeId> m_batteryCriticalStateTypeMap;
QHash<ActionTypeId, ThingClassId> m_rebootActionTypeMap;
// Relay based power actions

View File

@ -686,6 +686,175 @@
}
]
},
{
"id": "3eba6b29-f634-4ade-80a3-2159803373cc",
"name": "shellyButton1",
"displayName": "Shelly button 1",
"createMethods": ["discovery"],
"interfaces": ["longpressmultibutton", "wirelessconnectable", "batterylevel", "update"],
"paramTypes": [
{
"id": "ef42a9f5-b6f4-4bb9-ad17-a9419be4a44e",
"name":"id",
"displayName": "Shelly ID",
"type": "QString",
"readOnly": true
},
{
"id": "cc4a3365-b302-4782-9eec-ee6b66df8ed6",
"name": "username",
"displayName": "Username (optional)",
"type": "QString"
},
{
"id": "bf9a47c4-0773-461e-af5b-c1bd90167646",
"name": "password",
"displayName": "Password (optional)",
"type": "QString"
}
],
"settingsTypes": [
{
"id": "45d4628d-7d8c-43b6-ac86-6232caa5816f",
"name": "remainAwake",
"displayName": "Remain awake",
"type": "uint",
"unit": "Seconds",
"minValue": 0,
"maxValue": 5,
"defaultValue": 0
},
{
"id": "420298a7-bcf8-4970-951e-f6ee5efa1013",
"name": "statusLedEnabled",
"displayName": "Status LED enabled",
"type": "bool",
"defaultValue": true
},
{
"id": "b98423a8-c758-4dae-b979-e22446d06b22",
"name": "longpushMaxDuration",
"displayName": "Longpress duration",
"type": "uint",
"unit": "MilliSeconds",
"minValue": 800,
"maxValue": 2000,
"defaultValue": 800
},
{
"id": "b1f5a911-76ec-42e5-ac64-17f85d82b875",
"name": "multipressInterval",
"displayName": "Max time between multiple presses",
"type": "uint",
"minValue": 200,
"maxValue": 2000,
"defaultValue": 500
}
],
"stateTypes": [
{
"id": "d23e25a1-f723-4de1-806a-83fb073f01f4",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected/disconnected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "fff3aa51-ec42-40c7-b603-cbd2d58d781e",
"name": "signalStrength",
"displayName": "Signal strength",
"displayNameEvent": "Signal strength changed",
"type": "uint",
"minValue": 0,
"maxValue": 100,
"unit": "Percentage",
"defaultValue": 100
},
{
"id": "338355e5-9506-48b1-be86-757d69b34755",
"name": "batteryLevel",
"displayName": "Battery level",
"displayNameEvent": "Battery level changed",
"type": "int",
"minValue": 0,
"maxValue": 100,
"unit": "Percentage",
"defaultValue": 0
},
{
"id": "18edddee-1b30-48e4-b233-1e3b68bd6ff1",
"name": "batteryCritical",
"displayName": "Battery level critical",
"displayNameEvent": "Battery critical changed",
"type": "bool",
"defaultValue": false
},
{
"id": "aa3cbd93-192a-4035-8f46-c5ff68fe331b",
"name": "updateStatus",
"displayName": "Update status",
"displayNameEvent": "Update status changed",
"type": "QString",
"possibleValues": ["idle", "available", "updating"],
"defaultValue": "idle"
},
{
"id": "b17a7df2-952b-4cdd-8d28-a8e8582b49d4",
"name": "currentVersion",
"displayName": "Current firmware version",
"displayNameEvent": "Current firmware version changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "46f33cf8-82bf-4798-9100-69f54aabd9e0",
"name": "availableVersion",
"displayName": "Available firmware version",
"displayNameEvent": "Available firmware version changed",
"type": "QString",
"defaultValue": ""
}
],
"eventTypes": [
{
"id": "25955cb9-dc0e-48dc-91b1-ba27e30a3a3f",
"name": "pressed",
"displayName": "Pressed",
"paramTypes": [
{
"id": "a384450d-12b5-4269-9469-2e986423bde0",
"name": "buttonName",
"displayName": "Count",
"type": "QString",
"allowedValues": ["1", "2", "3"]
}
]
},
{
"id": "47cab6b6-eed3-4628-b3ad-2ceda26d6f84",
"name": "longPressed",
"displayName": "Longpressed",
"paramTypes": [
{
"id": "f8b5f587-d266-4fd3-9f01-941d0dcedc1f",
"name": "buttonName",
"displayName": "Count",
"type": "QString",
"allowedValues": ["1"]
}
]
}
],
"actionTypes": [
{
"id": "87b24064-5db7-4590-a9d8-f6d8fd02ed6e",
"name": "performUpdate",
"displayName": "Start firmware update"
}
]
},
{
"id": "6de35a17-0f54-4397-894d-4321b64c53d1",
"name": "shellySwitch",