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,31 @@ bool IntegrationPluginZigbeeTuya::handleNode(ZigbeeNode *node, const QUuid &/*ne
170192 return true ;
171193 }
172194
195+ if (match (node, " TS0203" , {" _TZ3000_7d8yme6f" })) {
196+ // Implements IAS Zone spec, but doesn't reply to ZoneType attribute, thus not handled properly by generic plugin
197+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (0x01 );
198+ configurePowerConfigurationInputClusterAttributeReporting (endpoint);
199+ bindCluster (endpoint, ZigbeeClusterLibrary::ClusterIdIasZone);
200+ configureIasZoneInputClusterAttributeReporting (endpoint);
201+ enrollIasZone (endpoint, 0x42 );
202+ createThing (closableSensorThingClassId, node);
203+ return true ;
204+ }
205+
206+ if (match (node, " TS0601" , {
207+ " _TZE200_hhrtiq0x" ,
208+ " _TZE200_zivfvd7h" ,
209+ " _TZE200_kfvq6avy" ,
210+ " _TZE200_ps5v5jor" ,
211+ " _TZE200_jeaxp72v" ,
212+ " _TZE200_owwdxjbx" ,
213+ " _TZE200_2cs6g9i7" ,
214+ " _TZE200_04yfvweb" })) {
215+ createThing (thermostatThingClassId, node);
216+ return true ;
217+ }
218+
219+ >>>>>>> 95b0cca (Tuya: Add thermostat support)
173220 return false ;
174221}
175222
@@ -667,6 +714,120 @@ void IntegrationPluginZigbeeTuya::createConnections(Thing *thing)
667714
668715 });
669716 }
717+
718+ if (thing->thingClassId () == closableSensorThingClassId) {
719+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (1 );
720+ if (!endpoint) {
721+ qCWarning (dcZigbeeTuya ()) << " Unable to find endpoint 1 on node" << node;
722+ return ;
723+ }
724+ connectToIasZoneInputCluster (thing, endpoint, " closed" , true );
725+ }
726+
727+ if (thing->thingClassId () == thermostatThingClassId) {
728+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (1 );
729+ if (!endpoint) {
730+ qCWarning (dcZigbeeTuya ()) << " Unable to find endpoint 1 on node" << node;
731+ return ;
732+ }
733+ ZigbeeCluster *cluster = endpoint->getInputCluster (static_cast <ZigbeeClusterLibrary::ClusterId>(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA ));
734+ if (!cluster) {
735+ qCWarning (dcZigbeeTuya ()) << " Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node;
736+ return ;
737+ }
738+
739+ if (node->reachable ()) {
740+ cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
741+ }
742+ connect (node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){
743+ if (reachable) {
744+ cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
745+ }
746+ });
747+
748+ connect (cluster, &ZigbeeCluster::dataIndication, thing, [this , thing](const ZigbeeClusterLibrary::Frame &frame){
749+
750+ if (frame.header .command == COMMAND_ID_DATA_REPORT || frame.header .command == COMMAND_ID_DATA_RESPONSE ) {
751+ DpValue dpValue = DpValue::fromData (frame.payload );
752+
753+ switch (dpValue.dp ()) {
754+ case THERMOSTAT_DP_HEATING_SETPOINT :
755+ qCDebug (dcZigbeeTuya ()) << " Heating setpoint changed:" << dpValue;
756+ thing->setStateValue (thermostatTargetTemperatureStateTypeId, dpValue.value ().toUInt () / 10.0 );
757+ break ;
758+ case THERMOSTAT_DP_LOCAL_TEMP :
759+ qCDebug (dcZigbeeTuya ()) << " Local temp changed:" << dpValue;
760+ thing->setStateValue (thermostatTemperatureStateTypeId, dpValue.value ().toUInt () / 10 );
761+ break ;
762+ case THERMOSTAT_DP_MODE :
763+ qCDebug (dcZigbeeTuya ()) << " System mode changed:" << dpValue;
764+ thing->setStateValue (thermostatModeStateTypeId, dpValue.value ().toUInt ());
765+ break ;
766+ case THERMOSTAT_DP_CHILD_LOCK :
767+ qCDebug (dcZigbeeTuya ()) << " Child lock changed:" << dpValue;
768+ thing->setStateValue (thermostatChildLockStateTypeId, dpValue.value ().toUInt () == 1 );
769+ break ;
770+ case THERMOSTAT_DP_WINDOW_OPEN :
771+ case THERMOSTAT_DP_WINDOW_OPEN_SITERWELL :
772+ qCDebug (dcZigbeeTuya ()) << " Window open changed:" << dpValue;
773+ thing->setStateValue (thermostatWindowOpenStateTypeId, dpValue.value ().toUInt () == 0 );
774+ break ;
775+ case THERMOSTAT_DP_WINDOW_DETECTION :
776+ qCDebug (dcZigbeeTuya ()) << " Window detection enabled changed:" << dpValue;
777+ thing->setSettingValue (thermostatSettingsWindowDetectionParamTypeId, dpValue.value ().toUInt ());
778+ break ;
779+ case THERMOSTAT_DP_TEMP_CALIBRATION :
780+ qCDebug (dcZigbeeTuya ()) << " Temp calibration changed:" << dpValue;
781+ thing->setSettingValue (thermostatSettingsTemperatureCalibrationParamTypeId, dpValue.value ().toUInt () / 10 );
782+ break ;
783+ case THERMOSTAT_DP_VALVE_OPEN :
784+ qCDebug (dcZigbeeTuya ()) << " Valve open changed:" << dpValue;
785+ thing->setStateValue (thermostatHeatingOnStateTypeId, dpValue.value ().toUInt () == 1 );
786+ break ;
787+ case THERMOSTAT_DP_BATTERY :
788+ qCDebug (dcZigbeeTuya ()) << " Battery changed:" << dpValue;
789+ thing->setStateValue (thermostatBatteryLevelStateTypeId, dpValue.value ().toUInt ());
790+ break ;
791+ case THERMOSTAT_DP_BATTERY_LOW :
792+ qCDebug (dcZigbeeTuya ()) << " Battery low changed:" << dpValue;
793+ thing->setStateValue (thermostatBatteryCriticalStateTypeId, dpValue.value ().toUInt () == 1 );
794+ break ;
795+ default :
796+ qCWarning (dcZigbeeTuya ()) << " Unhandled data point" << dpValue;
797+ }
798+
799+ if (frame.header .command == COMMAND_ID_DATA_RESPONSE ) {
800+ qCDebug (dcZigbeeTuya ()) << " Command response:" << dpValue;;
801+ foreach (ThingActionInfo *info, m_actionQueue.keys ()) {
802+ if (info->thing () == thing && m_actionQueue.value (info).dp () == dpValue.dp ()) {
803+ qCDebug (dcZigbeeTuya ()) << " Finishing action" ;
804+ info->finish (Thing::ThingErrorNoError);
805+ return ;
806+ }
807+ }
808+ qCWarning (dcZigbeeTuya) << " No pending action for command response found!" ;
809+ }
810+ } else {
811+ qCWarning (dcZigbeeTuya ()) << " Unhandled thermostat command:" << frame.header .command ;
812+ }
813+
814+
815+ });
816+
817+ connect (thing, &Thing::settingChanged, cluster, [cluster, thing, this ](const ParamTypeId &settingTypeId, const QVariant &value) {
818+ DpValue dp;
819+
820+ if (settingTypeId == thermostatSettingsWindowDetectionParamTypeId) {
821+ dp = DpValue (THERMOSTAT_DP_WINDOW_DETECTION , DpValue::TypeUInt32, value.toUInt (), m_seq++);
822+ }
823+ if (settingTypeId == thermostatSettingsTemperatureCalibrationParamTypeId) {
824+ dp = DpValue (THERMOSTAT_DP_WINDOW_DETECTION , DpValue::TypeUInt32, value.toDouble () * 10 , m_seq++);
825+ }
826+ qCDebug (dcZigbeeTuya ()) << " setting" << thing->thingClass ().settingsTypes ().findById (settingTypeId).name () << dp << dp.toData ().toHex ();
827+ writeDpDelayed (cluster, dp);
828+ });
829+
830+ }
670831}
671832
672833void IntegrationPluginZigbeeTuya::executeAction (ThingActionInfo *info)
@@ -702,6 +863,38 @@ void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info)
702863 }
703864 }
704865
866+ if (thing->thingClassId () == thermostatThingClassId) {
867+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (0x01 );
868+ ZigbeeCluster *cluster = endpoint->getInputCluster (static_cast <ZigbeeClusterLibrary::ClusterId>(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA ));
869+ if (!cluster) {
870+ qCWarning (dcZigbeeTuya ()) << " Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node;
871+ info->finish (Thing::ThingErrorHardwareFailure);
872+ return ;
873+ }
874+
875+ if (info->action ().actionTypeId () == thermostatChildLockActionTypeId) {
876+ bool locked = info->action ().param (thermostatChildLockActionChildLockParamTypeId).value ().toBool ();
877+ DpValue dp = DpValue (THERMOSTAT_DP_CHILD_LOCK , DpValue::TypeBool,locked ? 1 : 0 , m_seq++);
878+ qCDebug (dcZigbeeTuya ()) << " setting child lock:" << dp << dp.toData ().toHex ();
879+ writeDpDelayed (cluster, dp, info);
880+ return ;
881+ }
882+ if (info->action ().actionTypeId () == thermostatTargetTemperatureActionTypeId) {
883+ quint16 heatingSetpoint = info->action ().param (thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value ().toDouble () * 10 ;
884+ DpValue dp = DpValue (THERMOSTAT_DP_HEATING_SETPOINT , DpValue::TypeUInt32, heatingSetpoint, m_seq++);
885+ qCDebug (dcZigbeeTuya ()) << " setting heating setpoint:" << dp << dp.toData ().toHex ();
886+ writeDpDelayed (cluster, dp, info);
887+ return ;
888+ }
889+ if (info->action ().actionTypeId () == thermostatModeActionTypeId) {
890+ quint8 mode = info->action ().param (thermostatModeActionModeParamTypeId).value ().toUInt ();
891+ DpValue dp = DpValue (THERMOSTAT_DP_MODE , DpValue::TypeUInt32, mode, m_seq++);
892+ qCDebug (dcZigbeeTuya ()) << " setting mode:" << dp << dp.toData ().toHex ();
893+ writeDpDelayed (cluster, dp, info);
894+ return ;
895+ }
896+ }
897+
705898 info->finish (Thing::ThingErrorUnsupportedFeature);
706899}
707900
@@ -733,7 +926,7 @@ bool IntegrationPluginZigbeeTuya::match(ZigbeeNode *node, const QString &modelNa
733926 return node->modelName () == modelName && manufacturerNames.contains (node->manufacturerName ());
734927}
735928
736- void IntegrationPluginZigbeeTuya::writeDpDelayed (ZigbeeCluster *cluster, const DpValue &dp)
929+ void IntegrationPluginZigbeeTuya::writeDpDelayed (ZigbeeCluster *cluster, const DpValue &dp, ThingActionInfo *info )
737930{
738931 DelayedDpWrite op;
739932 op.cluster = cluster;
@@ -743,5 +936,12 @@ void IntegrationPluginZigbeeTuya::writeDpDelayed(ZigbeeCluster *cluster, const D
743936 // Trigger the delayed write asap by trying to read to trigger a lastSeen change
744937 cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
745938
939+ if (info) {
940+ m_actionQueue.insert (info, dp);
941+ connect (info, &ThingActionInfo::finished, this , [=](){
942+ m_actionQueue.remove (info);
943+ });
944+ }
945+
746946}
747947
0 commit comments