9191#define SMOKE_SENSOR_DP_BATTERY 15
9292#define SMOKE_SENSOR_DP_TEST 101
9393
94+ #define THERMOSTAT_DP_HEATING_SETPOINT 2
95+ #define THERMOSTAT_DP_LOCAL_TEMP 3
96+ #define THERMOSTAT_DP_MODE 4
97+ #define THERMOSTAT_DP_CHILD_LOCK 7
98+ #define THERMOSTAT_DP_WINDOW_OPEN_SITERWELL 18
99+ #define THERMOSTAT_DP_TEMP_CALIBRATION 44
100+ #define THERMOSTAT_DP_VALVE_OPEN 20
101+ #define THERMOSTAT_DP_BATTERY 21
102+ #define THERMOSTAT_DP_WINDOW_DETECTION 104
103+ #define THERMOSTAT_DP_BATTERY_LOW 110
104+ #define THERMOSTAT_DP_WINDOW_OPEN 115
105+
106+
94107IntegrationPluginZigbeeTuya::IntegrationPluginZigbeeTuya (): ZigbeeIntegrationPlugin(ZigbeeHardwareResource::HandlerTypeVendor, dcZigbeeTuya())
95108{
96109}
@@ -125,7 +138,16 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne
125138 return true ;
126139 }
127140
128- if (node->nodeDescriptor ().manufacturerCode == 0x1002 && node->modelName () == " TS0601" ) {
141+ if (match (node, " TS0601" , {
142+ " _TZE200_auin8mzr" ,
143+ " _TZE200_lyetpprm" ,
144+ " _TZE200_jva8ink8" ,
145+ " _TZE200_holel4dk" ,
146+ " _TZE200_xpq2rzhq" ,
147+ " _TZE200_wukb7rhc" ,
148+ " _TZE204_xsm7l9xa" ,
149+ " _TZE204_ztc6ggyl" ,
150+ " _TZE200_ztc6ggyl" })) {
129151 createThing (presenceSensorThingClassId, node);
130152 return true ;
131153 }
@@ -170,6 +192,19 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne
170192 return true ;
171193 }
172194
195+ if (match (node, " TS0601" , {
196+ " _TZE200_hhrtiq0x" ,
197+ " _TZE200_zivfvd7h" ,
198+ " _TZE200_kfvq6avy" ,
199+ " _TZE200_ps5v5jor" ,
200+ " _TZE200_jeaxp72v" ,
201+ " _TZE200_owwdxjbx" ,
202+ " _TZE200_2cs6g9i7" ,
203+ " _TZE200_04yfvweb" })) {
204+ createThing (thermostatThingClassId, node);
205+ return true ;
206+ }
207+
173208 return false ;
174209}
175210
@@ -667,6 +702,111 @@ void IntegrationPluginZigbeeTuya::createConnections(Thing *thing)
667702
668703 });
669704 }
705+
706+ if (thing->thingClassId () == thermostatThingClassId) {
707+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (1 );
708+ if (!endpoint) {
709+ qCWarning (dcZigbeeTuya ()) << " Unable to find endpoint 1 on node" << node;
710+ return ;
711+ }
712+ ZigbeeCluster *cluster = endpoint->getInputCluster (static_cast <ZigbeeClusterLibrary::ClusterId>(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA ));
713+ if (!cluster) {
714+ qCWarning (dcZigbeeTuya ()) << " Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node;
715+ return ;
716+ }
717+
718+ if (node->reachable ()) {
719+ cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
720+ }
721+ connect (node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){
722+ if (reachable) {
723+ cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
724+ }
725+ });
726+
727+ connect (cluster, &ZigbeeCluster::dataIndication, thing, [this , thing](const ZigbeeClusterLibrary::Frame &frame){
728+
729+ if (frame.header .command == COMMAND_ID_DATA_REPORT || frame.header .command == COMMAND_ID_DATA_RESPONSE ) {
730+ DpValue dpValue = DpValue::fromData (frame.payload );
731+
732+ switch (dpValue.dp ()) {
733+ case THERMOSTAT_DP_HEATING_SETPOINT :
734+ qCDebug (dcZigbeeTuya ()) << " Heating setpoint changed:" << dpValue;
735+ thing->setStateValue (thermostatTargetTemperatureStateTypeId, dpValue.value ().toUInt () / 10.0 );
736+ break ;
737+ case THERMOSTAT_DP_LOCAL_TEMP :
738+ qCDebug (dcZigbeeTuya ()) << " Local temp changed:" << dpValue;
739+ thing->setStateValue (thermostatTemperatureStateTypeId, dpValue.value ().toUInt () / 10 );
740+ break ;
741+ case THERMOSTAT_DP_MODE :
742+ qCDebug (dcZigbeeTuya ()) << " System mode changed:" << dpValue;
743+ thing->setStateValue (thermostatModeStateTypeId, dpValue.value ().toUInt ());
744+ break ;
745+ case THERMOSTAT_DP_CHILD_LOCK :
746+ qCDebug (dcZigbeeTuya ()) << " Child lock changed:" << dpValue;
747+ thing->setStateValue (thermostatChildLockStateTypeId, dpValue.value ().toUInt () == 1 );
748+ break ;
749+ case THERMOSTAT_DP_WINDOW_OPEN :
750+ case THERMOSTAT_DP_WINDOW_OPEN_SITERWELL :
751+ qCDebug (dcZigbeeTuya ()) << " Window open changed:" << dpValue;
752+ thing->setStateValue (thermostatWindowOpenStateTypeId, dpValue.value ().toUInt () == 0 );
753+ break ;
754+ case THERMOSTAT_DP_WINDOW_DETECTION :
755+ qCDebug (dcZigbeeTuya ()) << " Window detection enabled changed:" << dpValue;
756+ thing->setSettingValue (thermostatSettingsWindowDetectionParamTypeId, dpValue.value ().toUInt ());
757+ break ;
758+ case THERMOSTAT_DP_TEMP_CALIBRATION :
759+ qCDebug (dcZigbeeTuya ()) << " Temp calibration changed:" << dpValue;
760+ thing->setSettingValue (thermostatSettingsTemperatureCalibrationParamTypeId, dpValue.value ().toUInt () / 10 );
761+ break ;
762+ case THERMOSTAT_DP_VALVE_OPEN :
763+ qCDebug (dcZigbeeTuya ()) << " Valve open changed:" << dpValue;
764+ thing->setStateValue (thermostatHeatingOnStateTypeId, dpValue.value ().toUInt () == 1 );
765+ break ;
766+ case THERMOSTAT_DP_BATTERY :
767+ qCDebug (dcZigbeeTuya ()) << " Battery changed:" << dpValue;
768+ thing->setStateValue (thermostatBatteryLevelStateTypeId, dpValue.value ().toUInt ());
769+ break ;
770+ case THERMOSTAT_DP_BATTERY_LOW :
771+ qCDebug (dcZigbeeTuya ()) << " Battery low changed:" << dpValue;
772+ thing->setStateValue (thermostatBatteryCriticalStateTypeId, dpValue.value ().toUInt () == 1 );
773+ break ;
774+ default :
775+ qCWarning (dcZigbeeTuya ()) << " Unhandled data point" << dpValue;
776+ }
777+
778+ if (frame.header .command == COMMAND_ID_DATA_RESPONSE ) {
779+ qCDebug (dcZigbeeTuya ()) << " Command response:" << dpValue;;
780+ foreach (ThingActionInfo *info, m_actionQueue.keys ()) {
781+ if (info->thing () == thing && m_actionQueue.value (info).dp () == dpValue.dp ()) {
782+ qCDebug (dcZigbeeTuya ()) << " Finishing action" ;
783+ info->finish (Thing::ThingErrorNoError);
784+ return ;
785+ }
786+ }
787+ qCWarning (dcZigbeeTuya) << " No pending action for command response found!" ;
788+ }
789+ } else {
790+ qCWarning (dcZigbeeTuya ()) << " Unhandled thermostat command:" << frame.header .command ;
791+ }
792+
793+
794+ });
795+
796+ connect (thing, &Thing::settingChanged, cluster, [cluster, thing, this ](const ParamTypeId &settingTypeId, const QVariant &value) {
797+ DpValue dp;
798+
799+ if (settingTypeId == thermostatSettingsWindowDetectionParamTypeId) {
800+ dp = DpValue (THERMOSTAT_DP_WINDOW_DETECTION , DpValue::TypeUInt32, value.toUInt (), m_seq++);
801+ }
802+ if (settingTypeId == thermostatSettingsTemperatureCalibrationParamTypeId) {
803+ dp = DpValue (THERMOSTAT_DP_WINDOW_DETECTION , DpValue::TypeUInt32, value.toDouble () * 10 , m_seq++);
804+ }
805+ qCDebug (dcZigbeeTuya ()) << " setting" << thing->thingClass ().settingsTypes ().findById (settingTypeId).name () << dp << dp.toData ().toHex ();
806+ writeDpDelayed (cluster, dp);
807+ });
808+
809+ }
670810}
671811
672812void IntegrationPluginZigbeeTuya::executeAction (ThingActionInfo *info)
@@ -702,6 +842,38 @@ void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info)
702842 }
703843 }
704844
845+ if (thing->thingClassId () == thermostatThingClassId) {
846+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (0x01 );
847+ ZigbeeCluster *cluster = endpoint->getInputCluster (static_cast <ZigbeeClusterLibrary::ClusterId>(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA ));
848+ if (!cluster) {
849+ qCWarning (dcZigbeeTuya ()) << " Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node;
850+ info->finish (Thing::ThingErrorHardwareFailure);
851+ return ;
852+ }
853+
854+ if (info->action ().actionTypeId () == thermostatChildLockActionTypeId) {
855+ bool locked = info->action ().param (thermostatChildLockActionChildLockParamTypeId).value ().toBool ();
856+ DpValue dp = DpValue (THERMOSTAT_DP_CHILD_LOCK , DpValue::TypeBool,locked ? 1 : 0 , m_seq++);
857+ qCDebug (dcZigbeeTuya ()) << " setting child lock:" << dp << dp.toData ().toHex ();
858+ writeDpDelayed (cluster, dp, info);
859+ return ;
860+ }
861+ if (info->action ().actionTypeId () == thermostatTargetTemperatureActionTypeId) {
862+ quint16 heatingSetpoint = info->action ().param (thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value ().toDouble () * 10 ;
863+ DpValue dp = DpValue (THERMOSTAT_DP_HEATING_SETPOINT , DpValue::TypeUInt32, heatingSetpoint, m_seq++);
864+ qCDebug (dcZigbeeTuya ()) << " setting heating setpoint:" << dp << dp.toData ().toHex ();
865+ writeDpDelayed (cluster, dp, info);
866+ return ;
867+ }
868+ if (info->action ().actionTypeId () == thermostatModeActionTypeId) {
869+ quint8 mode = info->action ().param (thermostatModeActionModeParamTypeId).value ().toUInt ();
870+ DpValue dp = DpValue (THERMOSTAT_DP_MODE , DpValue::TypeUInt32, mode, m_seq++);
871+ qCDebug (dcZigbeeTuya ()) << " setting mode:" << dp << dp.toData ().toHex ();
872+ writeDpDelayed (cluster, dp, info);
873+ return ;
874+ }
875+ }
876+
705877 info->finish (Thing::ThingErrorUnsupportedFeature);
706878}
707879
@@ -733,7 +905,7 @@ bool IntegrationPluginZigbeeTuya::match(ZigbeeNode *node, const QString &modelNa
733905 return node->modelName () == modelName && manufacturerNames.contains (node->manufacturerName ());
734906}
735907
736- void IntegrationPluginZigbeeTuya::writeDpDelayed (ZigbeeCluster *cluster, const DpValue &dp)
908+ void IntegrationPluginZigbeeTuya::writeDpDelayed (ZigbeeCluster *cluster, const DpValue &dp, ThingActionInfo *info )
737909{
738910 DelayedDpWrite op;
739911 op.cluster = cluster;
@@ -743,5 +915,12 @@ void IntegrationPluginZigbeeTuya::writeDpDelayed(ZigbeeCluster *cluster, const D
743915 // Trigger the delayed write asap by trying to read to trigger a lastSeen change
744916 cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
745917
918+ if (info) {
919+ m_actionQueue.insert (info, dp);
920+ connect (info, &ThingActionInfo::finished, this , [=](){
921+ m_actionQueue.remove (info);
922+ });
923+ }
924+
746925}
747926
0 commit comments