From 77a12381bb2f428253f17aae5c434263ddd0279c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 25 Nov 2023 22:46:07 +0100 Subject: [PATCH 1/2] Tuya: Add support for tuya closable sensors --- .../integrationpluginzigbeegeneric.cpp | 14 ---- zigbeetuya/README.md | 10 ++- zigbeetuya/descriptors/_TZ3000_7d8yme6f.txt | 21 +++++ zigbeetuya/integrationpluginzigbeetuya.cpp | 19 +++++ zigbeetuya/integrationpluginzigbeetuya.json | 80 +++++++++++++++++++ zigbeetuya/meta.json | 3 + 6 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 zigbeetuya/descriptors/_TZ3000_7d8yme6f.txt diff --git a/zigbeegeneric/integrationpluginzigbeegeneric.cpp b/zigbeegeneric/integrationpluginzigbeegeneric.cpp index 8fae132..f508254 100644 --- a/zigbeegeneric/integrationpluginzigbeegeneric.cpp +++ b/zigbeegeneric/integrationpluginzigbeegeneric.cpp @@ -345,20 +345,6 @@ void IntegrationPluginZigbeeGeneric::createConnections(Thing *thing) if (thing->thingClassId() == doorSensorThingClassId) { connectToIasZoneInputCluster(thing, endpoint, "closed", true); - ZigbeeClusterIasZone *iasZoneCluster = endpoint->inputCluster(ZigbeeClusterLibrary::ClusterIdIasZone); - if (!iasZoneCluster) { - qCWarning(dcZigbeeGeneric()) << "Could not find IAS zone cluster on" << thing << endpoint; - } else { - if (iasZoneCluster->hasAttribute(ZigbeeClusterIasZone::AttributeZoneStatus)) { - qCDebug(dcZigbeeGeneric()) << thing << iasZoneCluster->zoneStatus(); - ZigbeeClusterIasZone::ZoneStatusFlags zoneStatus = iasZoneCluster->zoneStatus(); - thing->setStateValue(doorSensorClosedStateTypeId, !zoneStatus.testFlag(ZigbeeClusterIasZone::ZoneStatusAlarm1) && !zoneStatus.testFlag(ZigbeeClusterIasZone::ZoneStatusAlarm2)); - } - connect(iasZoneCluster, &ZigbeeClusterIasZone::zoneStatusChanged, thing, [=](ZigbeeClusterIasZone::ZoneStatusFlags zoneStatus, quint8 extendedStatus, quint8 zoneId, quint16 delays) { - qCDebug(dcZigbeeGeneric()) << "Zone status changed to:" << zoneStatus << extendedStatus << zoneId << delays; - thing->setStateValue(doorSensorClosedStateTypeId, !zoneStatus.testFlag(ZigbeeClusterIasZone::ZoneStatusAlarm1) && !zoneStatus.testFlag(ZigbeeClusterIasZone::ZoneStatusAlarm2)); - }); - } } if (thing->thingClassId() == motionSensorThingClassId) { diff --git a/zigbeetuya/README.md b/zigbeetuya/README.md index 1990255..db7e41b 100644 --- a/zigbeetuya/README.md +++ b/zigbeetuya/README.md @@ -4,4 +4,12 @@ This plugin adds support for ZigBee devices by Tuya. ## Supported Things -* Smart plugs with energy metering (TS011 plugs) +* Smart plugs with energy metering +* PIR motion sensors +* Mm Wave presence sensors +* Vibration sensors +* H&T sensors +* H&T Display sensors +* AirHouseKeeper +* Smoke sensors +* Door/Window sensors diff --git a/zigbeetuya/descriptors/_TZ3000_7d8yme6f.txt b/zigbeetuya/descriptors/_TZ3000_7d8yme6f.txt new file mode 100644 index 0000000..6a22646 --- /dev/null +++ b/zigbeetuya/descriptors/_TZ3000_7d8yme6f.txt @@ -0,0 +1,21 @@ +--> ZigbeeNode(0x1577, A4:C1:38:EB:34:5E:C9:DB, _TZ3000_7d8yme6f (0x1141), TS0203, End device, RxOn:false) + ZigbeeNodeEndpoint(0x01, Zigbee::ZigbeeProfileHomeAutomation, Zigbee::HomeAutomationDeviceIasZone) + Manufacturer: "_TZ3000_7d8yme6f" + Model "TS0203" + Input clusters ( 4 ) + - ZigbeeCluster(0x0000, Basic, Server) + - ZigbeeClusterAttribute(0x0004, ZigbeeDataType(Character string, _TZ3000_7d8yme6f)) + - ZigbeeClusterAttribute(0x0005, ZigbeeDataType(Character string, TS0203)) + - ZigbeeCluster(0x0001, PowerConfiguration, Server) + - ZigbeeCluster(0x0003, Identify, Server) + - ZigbeeCluster(0x0500, IasZone, Server) + - ZigbeeClusterAttribute(0x0000, ZigbeeDataType(16-bit bitmap, 0x04 0x00)) + Output clusters ( 8 ) + - ZigbeeCluster(0x0008, LevelControl, Client) + - ZigbeeCluster(0x000a, Time, Client) + - ZigbeeCluster(0x0019, OtaUpgrade, Client) + - ZigbeeCluster(0x0004, Groups, Client) + - ZigbeeCluster(0x0005, Scenes, Client) + - ZigbeeCluster(0x0006, OnOff, Client) + - ZigbeeCluster(0x1000, TouchlinkCommissioning, Client) + - ZigbeeCluster(0x0003, Identify, Client) diff --git a/zigbeetuya/integrationpluginzigbeetuya.cpp b/zigbeetuya/integrationpluginzigbeetuya.cpp index 4cb3871..6093b69 100644 --- a/zigbeetuya/integrationpluginzigbeetuya.cpp +++ b/zigbeetuya/integrationpluginzigbeetuya.cpp @@ -170,6 +170,16 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne return true; } + if (match(node, "TS0203", {"_TZ3000_7d8yme6f"})) { + // Implements IAS Zone spec, but doesn't reply to ZoneType attribute, thus not handled properly by generic plugin + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + configurePowerConfigurationInputClusterAttributeReporting(endpoint); + bindCluster(endpoint, ZigbeeClusterLibrary::ClusterIdIasZone); + configureIasZoneInputClusterAttributeReporting(endpoint); + enrollIasZone(endpoint, 0x42); + createThing(closableSensorThingClassId, node); + return true; + } return false; } @@ -667,6 +677,15 @@ void IntegrationPluginZigbeeTuya::createConnections(Thing *thing) }); } + + if (thing->thingClassId() == closableSensorThingClassId) { + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(1); + if (!endpoint) { + qCWarning(dcZigbeeTuya()) << "Unable to find endpoint 1 on node" << node; + return; + } + connectToIasZoneInputCluster(thing, endpoint, "closed", true); + } } void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info) diff --git a/zigbeetuya/integrationpluginzigbeetuya.json b/zigbeetuya/integrationpluginzigbeetuya.json index bb53d3d..f40ce51 100644 --- a/zigbeetuya/integrationpluginzigbeetuya.json +++ b/zigbeetuya/integrationpluginzigbeetuya.json @@ -703,6 +703,86 @@ "defaultValue": false } ] + }, + { + "id": "82a859f0-41ed-4a53-93b0-260a1435ac75", + "name": "closableSensor", + "displayName": "Door/window sensor", + "createMethods": ["auto"], + "interfaces": ["closablesensor", "battery", "wirelessconnectable"], + "paramTypes": [ + { + "id": "06a60993-2ce7-484d-9ffd-13020be8fa78", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "7850cdee-7d65-48f9-a08a-4cabe9c20594", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes": [ + { + "id": "6d4523c2-66f1-4fbf-b0d9-bd11a1920b50", + "name": "closed", + "displayName": "Closed", + "displayNameEvent": "Opened or closed", + "type": "bool", + "defaultValue": false + }, + { + "id": "948bc2da-23fb-44fd-a74c-c917bf657bf0", + "name": "tampered", + "displayName": "Tampered", + "displayNameEvent": "Tampered", + "type": "bool", + "defaultValue": false + }, + { + "id": "34a8f7f1-19f8-47be-a1b2-4d7ea52fee1e", + "name": "batteryLevel", + "displayName": "Battery level", + "displayNameEvent": "Battery level changed", + "type": "int", + "minValue": 0, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": 50 + }, + { + "id": "455e1ebc-5534-4053-8db3-c0b6b79f8c41", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "10d8aa94-943e-40f6-a10e-0ac77bcdecd3", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected or disconnected", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "4ef57e47-2b83-4d41-8cfb-21bf2f5ee62d", + "name": "signalStrength", + "displayName": "Signal strength", + "displayNameEvent": "Signal strength changed", + "type": "uint", + "minValue": 0, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": 0 + } + ] } ] } diff --git a/zigbeetuya/meta.json b/zigbeetuya/meta.json index de719b7..2e7b876 100644 --- a/zigbeetuya/meta.json +++ b/zigbeetuya/meta.json @@ -9,5 +9,8 @@ ], "categories": [ "socket", + "sensor", + "door", + "energy" ] } From 30df1a708f0dcb75f15bdd4857bf177d84e2fdfd Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 26 Sep 2023 00:10:39 +0200 Subject: [PATCH 2/2] Tuya: Add thermostat support --- zigbeetuya/integrationpluginzigbeetuya.cpp | 184 +++++++++++++++++++- zigbeetuya/integrationpluginzigbeetuya.h | 3 +- zigbeetuya/integrationpluginzigbeetuya.json | 142 +++++++++++++++ 3 files changed, 326 insertions(+), 3 deletions(-) diff --git a/zigbeetuya/integrationpluginzigbeetuya.cpp b/zigbeetuya/integrationpluginzigbeetuya.cpp index 6093b69..7f2acea 100644 --- a/zigbeetuya/integrationpluginzigbeetuya.cpp +++ b/zigbeetuya/integrationpluginzigbeetuya.cpp @@ -91,6 +91,19 @@ #define SMOKE_SENSOR_DP_BATTERY 15 #define SMOKE_SENSOR_DP_TEST 101 +#define THERMOSTAT_DP_HEATING_SETPOINT 2 +#define THERMOSTAT_DP_LOCAL_TEMP 3 +#define THERMOSTAT_DP_MODE 4 +#define THERMOSTAT_DP_CHILD_LOCK 7 +#define THERMOSTAT_DP_WINDOW_OPEN_SITERWELL 18 +#define THERMOSTAT_DP_TEMP_CALIBRATION 44 +#define THERMOSTAT_DP_VALVE_OPEN 20 +#define THERMOSTAT_DP_BATTERY 21 +#define THERMOSTAT_DP_WINDOW_DETECTION 104 +#define THERMOSTAT_DP_BATTERY_LOW 110 +#define THERMOSTAT_DP_WINDOW_OPEN 115 + + IntegrationPluginZigbeeTuya::IntegrationPluginZigbeeTuya(): ZigbeeIntegrationPlugin(ZigbeeHardwareResource::HandlerTypeVendor, dcZigbeeTuya()) { } @@ -125,7 +138,16 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne return true; } - if (node->nodeDescriptor().manufacturerCode == 0x1002 && node->modelName() == "TS0601") { + if (match(node, "TS0601", { + "_TZE200_auin8mzr", + "_TZE200_lyetpprm", + "_TZE200_jva8ink8", + "_TZE200_holel4dk", + "_TZE200_xpq2rzhq", + "_TZE200_wukb7rhc", + "_TZE204_xsm7l9xa", + "_TZE204_ztc6ggyl", + "_TZE200_ztc6ggyl"})) { createThing(presenceSensorThingClassId, node); return true; } @@ -180,6 +202,20 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne createThing(closableSensorThingClassId, node); return true; } + + if (match(node, "TS0601", { + "_TZE200_hhrtiq0x", + "_TZE200_zivfvd7h", + "_TZE200_kfvq6avy", + "_TZE200_ps5v5jor", + "_TZE200_jeaxp72v", + "_TZE200_owwdxjbx", + "_TZE200_2cs6g9i7", + "_TZE200_04yfvweb"})) { + createThing(thermostatThingClassId, node); + return true; + } + return false; } @@ -686,6 +722,111 @@ void IntegrationPluginZigbeeTuya::createConnections(Thing *thing) } connectToIasZoneInputCluster(thing, endpoint, "closed", true); } + + if (thing->thingClassId() == thermostatThingClassId) { + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(1); + if (!endpoint) { + qCWarning(dcZigbeeTuya()) << "Unable to find endpoint 1 on node" << node; + return; + } + ZigbeeCluster *cluster = endpoint->getInputCluster(static_cast(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA)); + if (!cluster) { + qCWarning(dcZigbeeTuya()) << "Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node; + return; + } + + if (node->reachable()) { + cluster->executeClusterCommand(COMMAND_ID_DATA_QUERY, QByteArray(), ZigbeeClusterLibrary::DirectionClientToServer, true); + } + connect(node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){ + if (reachable) { + cluster->executeClusterCommand(COMMAND_ID_DATA_QUERY, QByteArray(), ZigbeeClusterLibrary::DirectionClientToServer, true); + } + }); + + connect(cluster, &ZigbeeCluster::dataIndication, thing, [this, thing](const ZigbeeClusterLibrary::Frame &frame){ + + if (frame.header.command == COMMAND_ID_DATA_REPORT || frame.header.command == COMMAND_ID_DATA_RESPONSE) { + DpValue dpValue = DpValue::fromData(frame.payload); + + switch (dpValue.dp()) { + case THERMOSTAT_DP_HEATING_SETPOINT: + qCDebug(dcZigbeeTuya()) << "Heating setpoint changed:" << dpValue; + thing->setStateValue(thermostatTargetTemperatureStateTypeId, dpValue.value().toUInt() / 10.0); + break; + case THERMOSTAT_DP_LOCAL_TEMP: + qCDebug(dcZigbeeTuya()) << "Local temp changed:" << dpValue; + thing->setStateValue(thermostatTemperatureStateTypeId, dpValue.value().toUInt() / 10); + break; + case THERMOSTAT_DP_MODE: + qCDebug(dcZigbeeTuya()) << "System mode changed:" << dpValue; + thing->setStateValue(thermostatModeStateTypeId, dpValue.value().toUInt()); + break; + case THERMOSTAT_DP_CHILD_LOCK: + qCDebug(dcZigbeeTuya()) << "Child lock changed:" << dpValue; + thing->setStateValue(thermostatChildLockStateTypeId, dpValue.value().toUInt() == 1); + break; + case THERMOSTAT_DP_WINDOW_OPEN: + case THERMOSTAT_DP_WINDOW_OPEN_SITERWELL: + qCDebug(dcZigbeeTuya()) << "Window open changed:" << dpValue; + thing->setStateValue(thermostatWindowOpenStateTypeId, dpValue.value().toUInt() == 0); + break; + case THERMOSTAT_DP_WINDOW_DETECTION: + qCDebug(dcZigbeeTuya()) << "Window detection enabled changed:" << dpValue; + thing->setSettingValue(thermostatSettingsWindowDetectionParamTypeId, dpValue.value().toUInt()); + break; + case THERMOSTAT_DP_TEMP_CALIBRATION: + qCDebug(dcZigbeeTuya()) << "Temp calibration changed:" << dpValue; + thing->setSettingValue(thermostatSettingsTemperatureCalibrationParamTypeId, dpValue.value().toUInt() / 10); + break; + case THERMOSTAT_DP_VALVE_OPEN: + qCDebug(dcZigbeeTuya()) << "Valve open changed:" << dpValue; + thing->setStateValue(thermostatHeatingOnStateTypeId, dpValue.value().toUInt() == 1); + break; + case THERMOSTAT_DP_BATTERY: + qCDebug(dcZigbeeTuya()) << "Battery changed:" << dpValue; + thing->setStateValue(thermostatBatteryLevelStateTypeId, dpValue.value().toUInt()); + break; + case THERMOSTAT_DP_BATTERY_LOW: + qCDebug(dcZigbeeTuya()) << "Battery low changed:" << dpValue; + thing->setStateValue(thermostatBatteryCriticalStateTypeId, dpValue.value().toUInt() == 1); + break; + default: + qCWarning(dcZigbeeTuya()) << "Unhandled data point" << dpValue; + } + + if (frame.header.command == COMMAND_ID_DATA_RESPONSE) { + qCDebug(dcZigbeeTuya()) << "Command response:" << dpValue;; + foreach (ThingActionInfo *info, m_actionQueue.keys()) { + if (info->thing() == thing && m_actionQueue.value(info).dp() == dpValue.dp()) { + qCDebug(dcZigbeeTuya()) << "Finishing action"; + info->finish(Thing::ThingErrorNoError); + return; + } + } + qCWarning(dcZigbeeTuya) << "No pending action for command response found!"; + } + } else { + qCWarning(dcZigbeeTuya()) << "Unhandled thermostat command:" << frame.header.command; + } + + + }); + + connect(thing, &Thing::settingChanged, cluster, [cluster, thing, this](const ParamTypeId &settingTypeId, const QVariant &value) { + DpValue dp; + + if (settingTypeId == thermostatSettingsWindowDetectionParamTypeId) { + dp = DpValue(THERMOSTAT_DP_WINDOW_DETECTION, DpValue::TypeUInt32, value.toUInt(), m_seq++); + } + if (settingTypeId == thermostatSettingsTemperatureCalibrationParamTypeId) { + dp = DpValue(THERMOSTAT_DP_WINDOW_DETECTION, DpValue::TypeUInt32, value.toDouble() * 10, m_seq++); + } + qCDebug(dcZigbeeTuya()) << "setting" << thing->thingClass().settingsTypes().findById(settingTypeId).name() << dp << dp.toData().toHex(); + writeDpDelayed(cluster, dp); + }); + + } } void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info) @@ -721,6 +862,38 @@ void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info) } } + if (thing->thingClassId() == thermostatThingClassId) { + ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01); + ZigbeeCluster *cluster = endpoint->getInputCluster(static_cast(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA)); + if (!cluster) { + qCWarning(dcZigbeeTuya()) << "Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node; + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + + if (info->action().actionTypeId() == thermostatChildLockActionTypeId) { + bool locked = info->action().param(thermostatChildLockActionChildLockParamTypeId).value().toBool(); + DpValue dp = DpValue(THERMOSTAT_DP_CHILD_LOCK, DpValue::TypeBool,locked ? 1 : 0, m_seq++); + qCDebug(dcZigbeeTuya()) << "setting child lock:" << dp << dp.toData().toHex(); + writeDpDelayed(cluster, dp, info); + return; + } + if (info->action().actionTypeId() == thermostatTargetTemperatureActionTypeId) { + quint16 heatingSetpoint = info->action().param(thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value().toDouble() * 10; + DpValue dp = DpValue(THERMOSTAT_DP_HEATING_SETPOINT, DpValue::TypeUInt32, heatingSetpoint, m_seq++); + qCDebug(dcZigbeeTuya()) << "setting heating setpoint:" << dp << dp.toData().toHex(); + writeDpDelayed(cluster, dp, info); + return; + } + if (info->action().actionTypeId() == thermostatModeActionTypeId) { + quint8 mode = info->action().param(thermostatModeActionModeParamTypeId).value().toUInt(); + DpValue dp = DpValue(THERMOSTAT_DP_MODE, DpValue::TypeUInt32, mode, m_seq++); + qCDebug(dcZigbeeTuya()) << "setting mode:" << dp << dp.toData().toHex(); + writeDpDelayed(cluster, dp, info); + return; + } + } + info->finish(Thing::ThingErrorUnsupportedFeature); } @@ -752,7 +925,7 @@ bool IntegrationPluginZigbeeTuya::match(ZigbeeNode *node, const QString &modelNa return node->modelName() == modelName && manufacturerNames.contains(node->manufacturerName()); } -void IntegrationPluginZigbeeTuya::writeDpDelayed(ZigbeeCluster *cluster, const DpValue &dp) +void IntegrationPluginZigbeeTuya::writeDpDelayed(ZigbeeCluster *cluster, const DpValue &dp, ThingActionInfo *info) { DelayedDpWrite op; op.cluster = cluster; @@ -762,5 +935,12 @@ void IntegrationPluginZigbeeTuya::writeDpDelayed(ZigbeeCluster *cluster, const D // Trigger the delayed write asap by trying to read to trigger a lastSeen change cluster->executeClusterCommand(COMMAND_ID_DATA_QUERY, QByteArray(), ZigbeeClusterLibrary::DirectionClientToServer, true); + if (info) { + m_actionQueue.insert(info, dp); + connect(info, &ThingActionInfo::finished, this, [=](){ + m_actionQueue.remove(info); + }); + } + } diff --git a/zigbeetuya/integrationpluginzigbeetuya.h b/zigbeetuya/integrationpluginzigbeetuya.h index 5e3c3a5..4071220 100644 --- a/zigbeetuya/integrationpluginzigbeetuya.h +++ b/zigbeetuya/integrationpluginzigbeetuya.h @@ -66,7 +66,7 @@ private slots: private: bool match(ZigbeeNode *node, const QString &modelName, const QStringList &manufacturerNames); - void writeDpDelayed(ZigbeeCluster *cluster, const DpValue &dp); + void writeDpDelayed(ZigbeeCluster *cluster, const DpValue &dp, ThingActionInfo *info = nullptr); private: struct DelayedDpWrite { @@ -77,6 +77,7 @@ private slots: PluginTimer *m_energyPollTimer = nullptr; quint16 m_seq = 0; QList m_delayedDpWrites; + QHash m_actionQueue; }; #endif // INTEGRATIONPLUGINZIGBEETUYA_H diff --git a/zigbeetuya/integrationpluginzigbeetuya.json b/zigbeetuya/integrationpluginzigbeetuya.json index f40ce51..05a8f80 100644 --- a/zigbeetuya/integrationpluginzigbeetuya.json +++ b/zigbeetuya/integrationpluginzigbeetuya.json @@ -783,6 +783,148 @@ "defaultValue": 0 } ] + }, + { + "id": "51cf21ce-28a0-4b48-851b-d27a18d00b9c", + "name": "thermostat", + "displayName": "Thermostat", + "createMethods": [ "auto" ], + "interfaces": ["thermostat", "temperaturesensor", "battery", "childlock", "wirelessconnectable"], + "paramTypes": [ + { + "id": "2c562d32-0351-4f39-9d33-178817a9d413", + "name": "ieeeAddress", + "displayName": "IEEE adress", + "type": "QString", + "defaultValue": "00:00:00:00:00:00:00:00" + }, + { + "id": "35aa5409-43b9-4f66-bb22-225f226427d5", + "name": "networkUuid", + "displayName": "Zigbee network UUID", + "type": "QString", + "defaultValue": "" + } + ], + "settingsTypes": [ + { + "id": "df9414af-9db3-4ee0-bf31-ff832b437324", + "name": "windowDetection", + "displayName": "Window open detection", + "type": "bool", + "defaultValue": true + }, + { + "id": "c94e536e-7bb4-4e66-906f-a370783011d8", + "name": "temperatureCalibration", + "displayName": "Temperature calibration offset", + "type": "double", + "defaultValue": 0 + } + ], + "stateTypes": [ + { + "id": "08e33c27-54cb-4270-a82d-a2b24bea8c45", + "name": "targetTemperature", + "displayName": "Target temperature", + "displayNameAction": "Set target temperature", + "type": "double", + "unit": "DegreeCelsius", + "minValue": 7, + "maxValue": 30, + "defaultValue": 0, + "writable": true + }, + { + "id": "6db38434-4f6c-4326-a4c1-deb3868cc402", + "name": "temperature", + "displayName": "Current temperature", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "fb58531f-c650-486c-bef7-b1a63073c681", + "name": "heatingOn", + "displayName": "Heating on", + "type": "bool", + "defaultValue": false + }, + { + "id": "31e58198-a7c0-4866-9aac-6cac3a07fdb9", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "95e83e40-5f85-41cf-9f22-060bec40392d", + "name": "signalStrength", + "displayName": "Signal strength", + "defaultValue": 0, + "maxValue": 100, + "minValue": 0, + "type": "uint", + "unit": "Percentage" + }, + { + "id": "97c0b335-b29f-4297-9039-44ac612dabfb", + "name": "batteryLevel", + "displayName": "Battery level", + "type": "int", + "unit": "Percentage", + "defaultValue": 50, + "minValue": 0, + "maxValue": 100 + }, + { + "id": "21564d78-d28c-47f2-8e67-5a6330df4bef", + "name": "batteryCritical", + "displayName": "Battery critical", + "type": "bool", + "defaultValue": false + }, + { + "id": "81d70a7c-2151-4354-b81e-cf406cf72979", + "name": "windowOpen", + "displayName": "Window open", + "type": "bool", + "defaultValue": false + }, + { + "id": "2db4b96b-8892-4864-af6a-3d40c296fb6f", + "name": "childLock", + "displayName": "Child protection lock", + "displayNameAction": "Set child protection lock", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "288c6bcf-afa4-4851-87b5-d23c008ec771", + "name": "mode", + "displayName": "Mode", + "displayNameAction": "Set mode", + "type": "uint", + "possibleValues": [ + { + "value": 0, + "displayName": "Off" + }, + { + "value": 1, + "displayName": "Auto" + }, + { + "value": 2, + "displayName": "Manual" + } + ], + "defaultValue": 1, + "writable": true + } + ] } ] }