Shelly: Add support for the Shelly TRV
This commit is contained in:
parent
860fbac0e8
commit
fd5307772c
@ -19,6 +19,7 @@ The currently supported devices are:
|
||||
* Shelly i3
|
||||
* Shelly Motion
|
||||
* Shelly Vintage
|
||||
* Shelly TRV
|
||||
|
||||
## Requirements
|
||||
The Shelly device needs to be connected to the same WiFi as nymea is in. New Shelly devices will open a WiFi named with
|
||||
|
||||
@ -75,6 +75,7 @@ static QHash<ThingClassId, ParamTypeId> idParamTypeMap = {
|
||||
{shellyHTThingClassId, shellyHTThingIdParamTypeId},
|
||||
{shellyI3ThingClassId, shellyI3ThingIdParamTypeId},
|
||||
{shellyMotionThingClassId, shellyMotionThingIdParamTypeId},
|
||||
{shellyTrvThingClassId, shellyTrvThingIdParamTypeId},
|
||||
};
|
||||
|
||||
static QHash<ThingClassId, ParamTypeId> usernameParamTypeMap = {
|
||||
@ -91,7 +92,8 @@ static QHash<ThingClassId, ParamTypeId> usernameParamTypeMap = {
|
||||
{shellyEm3ThingClassId, shellyEm3ThingUsernameParamTypeId},
|
||||
{shellyHTThingClassId, shellyHTThingUsernameParamTypeId},
|
||||
{shellyI3ThingClassId, shellyI3ThingUsernameParamTypeId},
|
||||
{shellyMotionThingClassId, shellyMotionThingUsernameParamTypeId}
|
||||
{shellyMotionThingClassId, shellyMotionThingUsernameParamTypeId},
|
||||
{shellyTrvThingClassId, shellyTrvThingUsernameParamTypeId},
|
||||
};
|
||||
|
||||
static QHash<ThingClassId, ParamTypeId> passwordParamTypeMap = {
|
||||
@ -108,7 +110,8 @@ static QHash<ThingClassId, ParamTypeId> passwordParamTypeMap = {
|
||||
{shellyEm3ThingClassId, shellyEm3ThingPasswordParamTypeId},
|
||||
{shellyHTThingClassId, shellyHTThingPasswordParamTypeId},
|
||||
{shellyI3ThingClassId, shellyI3ThingPasswordParamTypeId},
|
||||
{shellyMotionThingClassId, shellyMotionThingPasswordParamTypeId}
|
||||
{shellyMotionThingClassId, shellyMotionThingPasswordParamTypeId},
|
||||
{shellyTrvThingClassId, shellyTrvThingPasswordParamTypeId}
|
||||
};
|
||||
|
||||
static QHash<ThingClassId, ParamTypeId> rollerModeParamTypeMap = {
|
||||
@ -138,6 +141,7 @@ static QHash<ActionTypeId, ThingClassId> rebootActionTypeMap = {
|
||||
{shelly2RebootActionTypeId, shelly2ThingClassId},
|
||||
{shelly25RebootActionTypeId, shelly25ThingClassId},
|
||||
{shellyI3RebootActionTypeId, shellyI3ThingClassId},
|
||||
{shellyTrvRebootActionTypeId, shellyTrvThingClassId},
|
||||
};
|
||||
|
||||
static QHash<ActionTypeId, ThingClassId> powerActionTypesMap = {
|
||||
@ -228,7 +232,8 @@ static QHash<ActionTypeId, ThingClassId> updateActionTypesMap = {
|
||||
{shellyEm3PerformUpdateActionTypeId, shellyEm3ThingClassId},
|
||||
{shellyHTPerformUpdateActionTypeId, shellyHTThingClassId},
|
||||
{shellyI3PerformUpdateActionTypeId, shellyI3ThingClassId},
|
||||
{shellyMotionPerformUpdateActionTypeId, shellyMotionThingClassId}
|
||||
{shellyMotionPerformUpdateActionTypeId, shellyMotionThingClassId},
|
||||
{shellyTrvPerformUpdateActionTypeId, shellyTrvThingClassId}
|
||||
};
|
||||
|
||||
// Settings
|
||||
@ -294,6 +299,8 @@ void IntegrationPluginShelly::discoverThings(ThingDiscoveryInfo *info)
|
||||
namePattern = QRegExp("shellyix3-[0-9A-Z]+$");
|
||||
} else if (info->thingClassId() == shellyMotionThingClassId) {
|
||||
namePattern = QRegExp("shellymotionsensor-[0-9A-Z]+$");
|
||||
} else if (info->thingClassId() == shellyTrvThingClassId) {
|
||||
namePattern = QRegExp("shellytrv-[0-9A-Z]+$");
|
||||
}
|
||||
if (!entry.name().contains(namePattern)) {
|
||||
continue;
|
||||
@ -384,8 +391,10 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(getIP(info->thing()).toString());
|
||||
url.setUserName(thing->paramValue(usernameParamTypeMap.value(thing->thingClassId())).toString());
|
||||
url.setPassword(thing->paramValue(passwordParamTypeMap.value(thing->thingClassId())).toString());
|
||||
if (!thing->paramValue(usernameParamTypeMap.value(thing->thingClassId())).toString().isEmpty()) {
|
||||
url.setUserName(thing->paramValue(usernameParamTypeMap.value(thing->thingClassId())).toString());
|
||||
url.setPassword(thing->paramValue(passwordParamTypeMap.value(thing->thingClassId())).toString());
|
||||
}
|
||||
|
||||
if (rebootActionTypeMap.contains(action.actionTypeId())) {
|
||||
if (shellyId.contains("Plus")) {
|
||||
@ -574,6 +583,61 @@ void IntegrationPluginShelly::executeAction(ThingActionInfo *info)
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.actionTypeId() == shellyTrvTargetTemperatureActionTypeId) {
|
||||
double targetValue = action.paramValue(shellyTrvTargetTemperatureActionTargetTemperatureParamTypeId).toDouble();
|
||||
url.setPath(QString("/thermostats/0"));
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("target_t", QString::number(targetValue));
|
||||
query.addQueryItem("target_t_enabled", "true");
|
||||
url.setQuery(query);
|
||||
qCDebug(dcShelly()) << "Requesting:" << url;
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, info, [info, reply, targetValue](){
|
||||
// The Shelly TRV seems to reply with OK, but then takes ages to actually set the value
|
||||
// If we send another value within that time frame, it will again reply with OK but just ognore it...
|
||||
// As a workaround we'll make nymea wait for a second until allowing to send the next action.
|
||||
info->thing()->setStateValue(shellyTrvTargetTemperatureStateTypeId, targetValue);
|
||||
Thing::ThingError status = reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure;
|
||||
QTimer::singleShot(1000, info, [info, status](){
|
||||
info->finish(status);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action.actionTypeId() == shellyTrvValvePositionActionTypeId) {
|
||||
int targetValue = action.paramValue(shellyTrvValvePositionActionValvePositionParamTypeId).toInt();
|
||||
url.setPath(QString("/thermostats/0"));
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("pos", QString::number(targetValue));
|
||||
url.setQuery(query);
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, info, [info, reply, targetValue](){
|
||||
// The Shelly TRV seems to reply with OK, but then takes ages to actually set the value
|
||||
// If we send another value within that time frame, it will again reply with OK but just ognore it...
|
||||
// As a workaround we'll make nymea wait for a second until allowing to send the next action.
|
||||
info->thing()->setStateValue(shellyTrvValvePositionStateTypeId, targetValue);
|
||||
Thing::ThingError status = reply->error() == QNetworkReply::NoError ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure;
|
||||
QTimer::singleShot(1000, info, [info, status](){
|
||||
info->finish(status);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action.actionTypeId() == shellyTrvBoostActionTypeId) {
|
||||
url.setPath(QString("/thermostats/0"));
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("boost_minutes", thing->setting(shellyTrvSettingsBoostDurationParamTypeId).toString());
|
||||
url.setQuery(query);
|
||||
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 ? Thing::ThingErrorNoError : Thing::ThingErrorHardwareFailure);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.actionTypeId() == shellyRollerOpenActionTypeId) {
|
||||
url.setPath(QString("/roller/%1").arg(info->thing()->paramValue(shellyRollerThingChannelParamTypeId).toInt() - 1));
|
||||
QUrlQuery query;
|
||||
@ -790,8 +854,12 @@ void IntegrationPluginShelly::onMulticastMessageReceived(const QHostAddress &sou
|
||||
case 3101:
|
||||
thing->setStateValue("temperature", value.toDouble());
|
||||
break;
|
||||
case 3103:
|
||||
thing->setStateValue("humidity", value.toDouble());
|
||||
case 3103: // This is target tempererature for the TRV, but humidity for other sensors
|
||||
if (thing->thingClassId() == shellyTrvThingClassId) {
|
||||
thing->setStateValue("targetTemperature", value.toDouble());
|
||||
} else {
|
||||
thing->setStateValue("humidity", value.toDouble());
|
||||
}
|
||||
break;
|
||||
case 3106:
|
||||
thing->setStateValue("lightIntensity", value.toInt());
|
||||
@ -804,6 +872,13 @@ void IntegrationPluginShelly::onMulticastMessageReceived(const QHostAddress &sou
|
||||
}
|
||||
thing->setStateValue("batteryCritical", thing->stateValue("batteryLevel").toUInt() < 10);
|
||||
break;
|
||||
case 3121:
|
||||
thing->setStateValue("valvePosition", value.toUInt());
|
||||
thing->setStateValue("heatingOn", value.toUInt() > 0);
|
||||
break;
|
||||
case 3122:
|
||||
thing->setStateValue("boost", value.toUInt() > 0);
|
||||
break;
|
||||
case 4101: // power meter for channel 1
|
||||
if (thing->hasState("currentPower")) {
|
||||
thing->setStateValue("currentPower", value);
|
||||
@ -1132,8 +1207,10 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
|
||||
url.setHost(address.toString());
|
||||
url.setPort(80);
|
||||
url.setPath("/settings");
|
||||
url.setUserName(info->thing()->paramValue(usernameParamTypeMap.value(info->thing()->thingClassId())).toString());
|
||||
url.setPassword(info->thing()->paramValue(passwordParamTypeMap.value(info->thing()->thingClassId())).toString());
|
||||
if (!thing->paramValue(usernameParamTypeMap.value(thing->thingClassId())).toString().isEmpty()) {
|
||||
url.setUserName(info->thing()->paramValue(usernameParamTypeMap.value(info->thing()->thingClassId())).toString());
|
||||
url.setPassword(info->thing()->paramValue(passwordParamTypeMap.value(info->thing()->thingClassId())).toString());
|
||||
}
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("coiot_enable", "true");
|
||||
@ -1181,6 +1258,10 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
|
||||
info->thing()->setSettingValue(shellyI3SettingsLongpushMinDurationParamTypeId, settingsMap.value("longpush_duration_ms").toMap().value("min").toUInt());
|
||||
info->thing()->setSettingValue(shellyI3SettingsLongpushMaxDurationParamTypeId, settingsMap.value("longpush_duration_ms").toMap().value("max").toUInt());
|
||||
info->thing()->setSettingValue(shellyI3SettingsMultipushTimeBetweenPushesParamTypeId, settingsMap.value("multipush_time_between_pushes_ms").toMap().value("max").toUInt());
|
||||
} else if (info->thing()->thingClassId() == shellyTrvThingClassId) {
|
||||
info->thing()->setSettingValue(shellyTrvSettingsChildLockParamTypeId, settingsMap.value("child_lock").toBool());
|
||||
info->thing()->setSettingValue(shellyTrvSettingsDisplayFlippedParamTypeId, settingsMap.value("display").toMap().value("flipped").toBool());
|
||||
info->thing()->setSettingValue(shellyTrvSettingsDisplayBrightnessParamTypeId, settingsMap.value("display").toMap().value("brightness").toUInt());
|
||||
}
|
||||
|
||||
ThingDescriptors autoChilds;
|
||||
@ -1235,6 +1316,7 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
info->thing()->setStateValue("connected", true);
|
||||
|
||||
emit autoThingsAppeared(autoChilds);
|
||||
|
||||
@ -1281,7 +1363,8 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
|
||||
// Handle thing settings of gateway devices
|
||||
if (info->thing()->thingClassId() == shellyPlugThingClassId ||
|
||||
info->thing()->thingClassId() == shellyButton1ThingClassId ||
|
||||
info->thing()->thingClassId() == shellyI3ThingClassId) {
|
||||
info->thing()->thingClassId() == shellyI3ThingClassId ||
|
||||
info->thing()->thingClassId() == shellyTrvThingClassId) {
|
||||
connect(info->thing(), &Thing::settingChanged, this, [this, thing, shellyId](const ParamTypeId &settingTypeId, const QVariant &value) {
|
||||
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
@ -1316,6 +1399,15 @@ void IntegrationPluginShelly::setupGen1(ThingSetupInfo *info)
|
||||
|| settingTypeId == shellyI3SettingsMultipushTimeBetweenPushesParamTypeId) {
|
||||
url.setPath("/settings");
|
||||
query.addQueryItem("multipush_time_between_pushes_ms_max", value.toString());
|
||||
} else if (settingTypeId == shellyTrvSettingsChildLockParamTypeId) {
|
||||
url.setPath("/settings");
|
||||
query.addQueryItem("child_lock", value.toString());
|
||||
} else if (settingTypeId == shellyTrvSettingsDisplayBrightnessParamTypeId) {
|
||||
url.setPath("/settings");
|
||||
query.addQueryItem("display_brightness", value.toString());
|
||||
} else if (settingTypeId == shellyTrvSettingsDisplayFlippedParamTypeId) {
|
||||
url.setPath("/settings");
|
||||
query.addQueryItem("display_flipped", value.toString());
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
|
||||
@ -1458,6 +1458,209 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "52932a47-38cd-4dce-b338-88122ce4ab8a",
|
||||
"name": "shellyTrv",
|
||||
"displayName": "Shelly TRV",
|
||||
"createMethods": ["discovery"],
|
||||
"interfaces": ["thermostat", "temperaturesensor", "wirelessconnectable", "battery", "update"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "20e21853-cfc1-4f78-9ba3-12682f81e021",
|
||||
"name":"id",
|
||||
"displayName": "Shelly ID",
|
||||
"type": "QString",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"id": "ee942141-3102-4b6a-87dc-0fc6481924e5",
|
||||
"name": "username",
|
||||
"displayName": "Username (optional)",
|
||||
"type": "QString"
|
||||
},
|
||||
{
|
||||
"id": "bf4fead2-7a1a-44e7-bd32-ea2064944098",
|
||||
"name": "password",
|
||||
"displayName": "Password (optional)",
|
||||
"type": "QString"
|
||||
}
|
||||
],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "3a1dbc59-5b61-4650-a0fa-e127d337169e",
|
||||
"name": "boostDuration",
|
||||
"displayName": "Boost duration (minutes)",
|
||||
"type": "uint",
|
||||
"minValue": 0,
|
||||
"maxValue": 120,
|
||||
"defaultValue": 10
|
||||
},
|
||||
{
|
||||
"id": "38a98b85-9c6e-4dc8-8d73-5248532d2ed8",
|
||||
"name": "childLock",
|
||||
"displayName": "Child lock",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "83cfbdb7-a807-4a81-9eb0-5e0d62efdbaf",
|
||||
"name": "displayFlipped",
|
||||
"displayName": "Display flipped",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "e0f7aae7-d576-4897-9626-2cc7e452b30a",
|
||||
"name": "displayBrightness",
|
||||
"displayName": "Display brightness",
|
||||
"type": "uint",
|
||||
"minValue": 1,
|
||||
"maxValue": 7,
|
||||
"defaultValue": 7
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "d9a26a08-5735-403a-ab02-7638bd0a471f",
|
||||
"name": "temperature",
|
||||
"displayName": "Temperature",
|
||||
"displayNameEvent": "Temperature changed",
|
||||
"type": "double",
|
||||
"unit": "DegreeCelsius",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "9800babf-a6cc-4eda-b42e-8f5481b61aea",
|
||||
"name": "targetTemperature",
|
||||
"displayName": "Target temperature",
|
||||
"displayNameEvent": "Target temperature changed",
|
||||
"displayNameAction": "Set target temperature",
|
||||
"type": "double",
|
||||
"unit": "DegreeCelsius",
|
||||
"defaultValue": 0,
|
||||
"minValue": 4,
|
||||
"maxValue": 31,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "e442ca7a-ee17-482b-aae4-579915029abf",
|
||||
"name": "valvePosition",
|
||||
"displayName": "Valve position",
|
||||
"displayNameEvent": "Valve position changed",
|
||||
"displayNameAction": "Set valve position",
|
||||
"type": "uint",
|
||||
"unit": "Percentage",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "1935b7fa-72a5-4aee-877e-d656cd79d688",
|
||||
"name": "heatingOn",
|
||||
"displayName": "Heating",
|
||||
"displayNameEvent": "Heating changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "ef74da4d-70f4-49cd-9697-a8e2bf25dee1",
|
||||
"name": "boost",
|
||||
"displayName": "Boost",
|
||||
"displayNameEvent": "Boost enabled/disabled",
|
||||
"displayNameAction": "Enable/disable boost",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "a5944856-6b0f-4b45-9d9f-fe0f3c2de8aa",
|
||||
"name": "windowOpen",
|
||||
"displayName": "Window open",
|
||||
"displayNameEvent": "Window opened/closed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "92fa20b0-2b66-4d68-8819-6eeb43f1c0fb",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected or disconnected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "42e080f3-c00c-49d2-b046-7bd26331b8f0",
|
||||
"name": "signalStrength",
|
||||
"displayName": "Signal strength",
|
||||
"displayNameEvent": "Signal strength changed",
|
||||
"type": "uint",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "f45dff98-41ac-43bb-a005-24294973611b",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"displayNameEvent": "Battery level changed",
|
||||
"type": "int",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"unit": "Percentage",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "48c3a619-331e-44eb-b080-877fdcfd03f6",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery level critical",
|
||||
"displayNameEvent": "Battery critical changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "113625e0-e171-48e6-a9ca-6a13b75b9234",
|
||||
"name": "updateStatus",
|
||||
"displayName": "Update status",
|
||||
"displayNameEvent": "Update status changed",
|
||||
"type": "QString",
|
||||
"possibleValues": ["idle", "available", "updating"],
|
||||
"defaultValue": "idle"
|
||||
},
|
||||
{
|
||||
"id": "ad5cdb22-42ce-4592-ad72-61a854981f69",
|
||||
"name": "currentVersion",
|
||||
"displayName": "Current firmware version",
|
||||
"displayNameEvent": "Current firmware version changed",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "f3e00825-cd05-45e6-bc99-457bf74255c5",
|
||||
"name": "availableVersion",
|
||||
"displayName": "Available firmware version",
|
||||
"displayNameEvent": "Available firmware version changed",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "27e4c7f5-1828-443c-a18d-6d79382e001d",
|
||||
"name": "performUpdate",
|
||||
"displayName": "Start firmware update"
|
||||
},
|
||||
{
|
||||
"id": "4cef8e3a-853b-4313-8f70-d22122e7bb04",
|
||||
"name": "reboot",
|
||||
"displayName": "Reboot device"
|
||||
}
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"id": "6de35a17-0f54-4397-894d-4321b64c53d1",
|
||||
"name": "shellySwitch",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user