Skip to content

Commit

Permalink
Merge PR #75: WIP: Tuya: Add thermostat support
Browse files Browse the repository at this point in the history
  • Loading branch information
jenkins committed Sep 28, 2023
2 parents 4eeafb9 + 3ad9a8e commit df8e5e3
Show file tree
Hide file tree
Showing 2 changed files with 266 additions and 1 deletion.
148 changes: 147 additions & 1 deletion zigbeetuya/integrationpluginzigbeetuya.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@
#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_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())
{
}
Expand Down Expand Up @@ -125,7 +137,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;
}
Expand Down Expand Up @@ -170,6 +191,11 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne
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;
}

Expand Down Expand Up @@ -667,6 +693,99 @@ void IntegrationPluginZigbeeTuya::createConnections(Thing *thing)

});
}

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<ZigbeeClusterLibrary::ClusterId>(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, [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_CHILD_LOCK:
qCDebug(dcZigbeeTuya()) << "Child lock changed:" << dpValue;
thing->setStateValue(thermostatChildLockStateTypeId, dpValue.value().toUInt() == 0);
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;
}

} else {
qCWarning(dcZigbeeTuya()) << "Unhandled thermostat command:" << frame.header.command;
}

if (frame.header.command == COMMAND_ID_DATA_RESPONSE) {
qCDebug(dcZigbeeTuya()) << "Command response:" << frame.payload.toHex();
}

});

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)
Expand Down Expand Up @@ -702,6 +821,33 @@ void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info)
}
}

if (thing->thingClassId() == thermostatThingClassId) {
ZigbeeNodeEndpoint *endpoint = node->getEndpoint(0x01);
ZigbeeCluster *cluster = endpoint->getInputCluster(static_cast<ZigbeeClusterLibrary::ClusterId>(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::TypeUInt32,locked ? 1 : 0, m_seq++);
qCDebug(dcZigbeeTuya()) << "setting child lock:" << dp << dp.toData().toHex();
writeDpDelayed(cluster, dp);
info->finish(Thing::ThingErrorNoError); // FIXME: Should check the command response data point to determine if the call succeeded
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->finish(Thing::ThingErrorNoError); // FIXME: Should check the command response data point to determine if the call succeeded
return;
}
}

info->finish(Thing::ThingErrorUnsupportedFeature);
}

Expand Down
119 changes: 119 additions & 0 deletions zigbeetuya/integrationpluginzigbeetuya.json
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,125 @@
"defaultValue": false
}
]
},
{
"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
}
]
}
]
}
Expand Down

0 comments on commit df8e5e3

Please sign in to comment.