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,30 @@ 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+
173219 return false ;
174220}
175221
@@ -667,6 +713,120 @@ void IntegrationPluginZigbeeTuya::createConnections(Thing *thing)
667713
668714 });
669715 }
716+
717+ if (thing->thingClassId () == closableSensorThingClassId) {
718+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (1 );
719+ if (!endpoint) {
720+ qCWarning (dcZigbeeTuya ()) << " Unable to find endpoint 1 on node" << node;
721+ return ;
722+ }
723+ connectToIasZoneInputCluster (thing, endpoint, " closed" , true );
724+ }
725+
726+ if (thing->thingClassId () == thermostatThingClassId) {
727+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (1 );
728+ if (!endpoint) {
729+ qCWarning (dcZigbeeTuya ()) << " Unable to find endpoint 1 on node" << node;
730+ return ;
731+ }
732+ ZigbeeCluster *cluster = endpoint->getInputCluster (static_cast <ZigbeeClusterLibrary::ClusterId>(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA ));
733+ if (!cluster) {
734+ qCWarning (dcZigbeeTuya ()) << " Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node;
735+ return ;
736+ }
737+
738+ if (node->reachable ()) {
739+ cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
740+ }
741+ connect (node, &ZigbeeNode::reachableChanged, thing, [=](bool reachable){
742+ if (reachable) {
743+ cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
744+ }
745+ });
746+
747+ connect (cluster, &ZigbeeCluster::dataIndication, thing, [this , thing](const ZigbeeClusterLibrary::Frame &frame){
748+
749+ if (frame.header .command == COMMAND_ID_DATA_REPORT || frame.header .command == COMMAND_ID_DATA_RESPONSE ) {
750+ DpValue dpValue = DpValue::fromData (frame.payload );
751+
752+ switch (dpValue.dp ()) {
753+ case THERMOSTAT_DP_HEATING_SETPOINT :
754+ qCDebug (dcZigbeeTuya ()) << " Heating setpoint changed:" << dpValue;
755+ thing->setStateValue (thermostatTargetTemperatureStateTypeId, dpValue.value ().toUInt () / 10.0 );
756+ break ;
757+ case THERMOSTAT_DP_LOCAL_TEMP :
758+ qCDebug (dcZigbeeTuya ()) << " Local temp changed:" << dpValue;
759+ thing->setStateValue (thermostatTemperatureStateTypeId, dpValue.value ().toUInt () / 10 );
760+ break ;
761+ case THERMOSTAT_DP_MODE :
762+ qCDebug (dcZigbeeTuya ()) << " System mode changed:" << dpValue;
763+ thing->setStateValue (thermostatModeStateTypeId, dpValue.value ().toUInt ());
764+ break ;
765+ case THERMOSTAT_DP_CHILD_LOCK :
766+ qCDebug (dcZigbeeTuya ()) << " Child lock changed:" << dpValue;
767+ thing->setStateValue (thermostatChildLockStateTypeId, dpValue.value ().toUInt () == 1 );
768+ break ;
769+ case THERMOSTAT_DP_WINDOW_OPEN :
770+ case THERMOSTAT_DP_WINDOW_OPEN_SITERWELL :
771+ qCDebug (dcZigbeeTuya ()) << " Window open changed:" << dpValue;
772+ thing->setStateValue (thermostatWindowOpenStateTypeId, dpValue.value ().toUInt () == 0 );
773+ break ;
774+ case THERMOSTAT_DP_WINDOW_DETECTION :
775+ qCDebug (dcZigbeeTuya ()) << " Window detection enabled changed:" << dpValue;
776+ thing->setSettingValue (thermostatSettingsWindowDetectionParamTypeId, dpValue.value ().toUInt ());
777+ break ;
778+ case THERMOSTAT_DP_TEMP_CALIBRATION :
779+ qCDebug (dcZigbeeTuya ()) << " Temp calibration changed:" << dpValue;
780+ thing->setSettingValue (thermostatSettingsTemperatureCalibrationParamTypeId, dpValue.value ().toUInt () / 10 );
781+ break ;
782+ case THERMOSTAT_DP_VALVE_OPEN :
783+ qCDebug (dcZigbeeTuya ()) << " Valve open changed:" << dpValue;
784+ thing->setStateValue (thermostatHeatingOnStateTypeId, dpValue.value ().toUInt () == 1 );
785+ break ;
786+ case THERMOSTAT_DP_BATTERY :
787+ qCDebug (dcZigbeeTuya ()) << " Battery changed:" << dpValue;
788+ thing->setStateValue (thermostatBatteryLevelStateTypeId, dpValue.value ().toUInt ());
789+ break ;
790+ case THERMOSTAT_DP_BATTERY_LOW :
791+ qCDebug (dcZigbeeTuya ()) << " Battery low changed:" << dpValue;
792+ thing->setStateValue (thermostatBatteryCriticalStateTypeId, dpValue.value ().toUInt () == 1 );
793+ break ;
794+ default :
795+ qCWarning (dcZigbeeTuya ()) << " Unhandled data point" << dpValue;
796+ }
797+
798+ if (frame.header .command == COMMAND_ID_DATA_RESPONSE ) {
799+ qCDebug (dcZigbeeTuya ()) << " Command response:" << dpValue;;
800+ foreach (ThingActionInfo *info, m_actionQueue.keys ()) {
801+ if (info->thing () == thing && m_actionQueue.value (info).dp () == dpValue.dp ()) {
802+ qCDebug (dcZigbeeTuya ()) << " Finishing action" ;
803+ info->finish (Thing::ThingErrorNoError);
804+ return ;
805+ }
806+ }
807+ qCWarning (dcZigbeeTuya) << " No pending action for command response found!" ;
808+ }
809+ } else {
810+ qCWarning (dcZigbeeTuya ()) << " Unhandled thermostat command:" << frame.header .command ;
811+ }
812+
813+
814+ });
815+
816+ connect (thing, &Thing::settingChanged, cluster, [cluster, thing, this ](const ParamTypeId &settingTypeId, const QVariant &value) {
817+ DpValue dp;
818+
819+ if (settingTypeId == thermostatSettingsWindowDetectionParamTypeId) {
820+ dp = DpValue (THERMOSTAT_DP_WINDOW_DETECTION , DpValue::TypeUInt32, value.toUInt (), m_seq++);
821+ }
822+ if (settingTypeId == thermostatSettingsTemperatureCalibrationParamTypeId) {
823+ dp = DpValue (THERMOSTAT_DP_WINDOW_DETECTION , DpValue::TypeUInt32, value.toDouble () * 10 , m_seq++);
824+ }
825+ qCDebug (dcZigbeeTuya ()) << " setting" << thing->thingClass ().settingsTypes ().findById (settingTypeId).name () << dp << dp.toData ().toHex ();
826+ writeDpDelayed (cluster, dp);
827+ });
828+
829+ }
670830}
671831
672832void IntegrationPluginZigbeeTuya::executeAction (ThingActionInfo *info)
@@ -702,6 +862,38 @@ void IntegrationPluginZigbeeTuya::executeAction(ThingActionInfo *info)
702862 }
703863 }
704864
865+ if (thing->thingClassId () == thermostatThingClassId) {
866+ ZigbeeNodeEndpoint *endpoint = node->getEndpoint (0x01 );
867+ ZigbeeCluster *cluster = endpoint->getInputCluster (static_cast <ZigbeeClusterLibrary::ClusterId>(CLUSTER_ID_MANUFACTURER_SPECIFIC_TUYA ));
868+ if (!cluster) {
869+ qCWarning (dcZigbeeTuya ()) << " Unable to find Tuya manufacturer specific cluuster on endpoint 1 on node" << node;
870+ info->finish (Thing::ThingErrorHardwareFailure);
871+ return ;
872+ }
873+
874+ if (info->action ().actionTypeId () == thermostatChildLockActionTypeId) {
875+ bool locked = info->action ().param (thermostatChildLockActionChildLockParamTypeId).value ().toBool ();
876+ DpValue dp = DpValue (THERMOSTAT_DP_CHILD_LOCK , DpValue::TypeBool,locked ? 1 : 0 , m_seq++);
877+ qCDebug (dcZigbeeTuya ()) << " setting child lock:" << dp << dp.toData ().toHex ();
878+ writeDpDelayed (cluster, dp, info);
879+ return ;
880+ }
881+ if (info->action ().actionTypeId () == thermostatTargetTemperatureActionTypeId) {
882+ quint16 heatingSetpoint = info->action ().param (thermostatTargetTemperatureActionTargetTemperatureParamTypeId).value ().toDouble () * 10 ;
883+ DpValue dp = DpValue (THERMOSTAT_DP_HEATING_SETPOINT , DpValue::TypeUInt32, heatingSetpoint, m_seq++);
884+ qCDebug (dcZigbeeTuya ()) << " setting heating setpoint:" << dp << dp.toData ().toHex ();
885+ writeDpDelayed (cluster, dp, info);
886+ return ;
887+ }
888+ if (info->action ().actionTypeId () == thermostatModeActionTypeId) {
889+ quint8 mode = info->action ().param (thermostatModeActionModeParamTypeId).value ().toUInt ();
890+ DpValue dp = DpValue (THERMOSTAT_DP_MODE , DpValue::TypeUInt32, mode, m_seq++);
891+ qCDebug (dcZigbeeTuya ()) << " setting mode:" << dp << dp.toData ().toHex ();
892+ writeDpDelayed (cluster, dp, info);
893+ return ;
894+ }
895+ }
896+
705897 info->finish (Thing::ThingErrorUnsupportedFeature);
706898}
707899
@@ -733,7 +925,7 @@ bool IntegrationPluginZigbeeTuya::match(ZigbeeNode *node, const QString &modelNa
733925 return node->modelName () == modelName && manufacturerNames.contains (node->manufacturerName ());
734926}
735927
736- void IntegrationPluginZigbeeTuya::writeDpDelayed (ZigbeeCluster *cluster, const DpValue &dp)
928+ void IntegrationPluginZigbeeTuya::writeDpDelayed (ZigbeeCluster *cluster, const DpValue &dp, ThingActionInfo *info )
737929{
738930 DelayedDpWrite op;
739931 op.cluster = cluster;
@@ -743,5 +935,12 @@ void IntegrationPluginZigbeeTuya::writeDpDelayed(ZigbeeCluster *cluster, const D
743935 // Trigger the delayed write asap by trying to read to trigger a lastSeen change
744936 cluster->executeClusterCommand (COMMAND_ID_DATA_QUERY , QByteArray (), ZigbeeClusterLibrary::DirectionClientToServer, true );
745937
938+ if (info) {
939+ m_actionQueue.insert (info, dp);
940+ connect (info, &ThingActionInfo::finished, this , [=](){
941+ m_actionQueue.remove (info);
942+ });
943+ }
944+
746945}
747946
0 commit comments