Skip to content

Commit 31ea66a

Browse files
authored
Merge pull request #2102 from pInksenberg/add_support_for_tuya_devices
add support for tuya devices
2 parents 55856ba + 5b9c034 commit 31ea66a

22 files changed

+2322
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: 'Tuya Zigbee Device'
2+
packageKey: 'tuya-zigbee-device'
3+
permissions:
4+
zigbee: {}
5+
description: "Driver for uncertificed Tuya Zigbee devices"
6+
isWWST: false
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
zigbeeManufacturer:
2+
- id: "_TZ3000_ja5osu5g/TS004F"
3+
deviceLabel: Tuya Button
4+
manufacturer: _TZ3000_ja5osu5g
5+
model: TS004F
6+
deviceProfileName: one-button-battery
7+
- id: "_TZ3000_8rppvwda/TS0041"
8+
deviceLabel: Tuya Button
9+
manufacturer: _TZ3000_8rppvwda
10+
model: TS0041
11+
deviceProfileName: one-button-battery
12+
- id: "_TZ3000_pkfazisv/TS0215A"
13+
deviceLabel: Tuya Button
14+
manufacturer: _TZ3000_pkfazisv
15+
model: TS0215A
16+
deviceProfileName: one-button-battery
17+
- id: "_TZE284_nladmfvf/TS0601"
18+
deviceLabel: Tuya Curtain
19+
manufacturer: _TZE284_nladmfvf
20+
model: TS0601
21+
deviceProfileName: window-treatment-reverse
22+
- id: "_TZE200_mgxy2d9f/TS0601"
23+
deviceLabel: Tuya Motion Sensor
24+
manufacturer: _TZE200_mgxy2d9f
25+
model: TS0601
26+
deviceProfileName: motion-sensor
27+
- id: "_TZE200_ioxkjvuz/TS0601"
28+
deviceLabel: Tuya Gas Sensor
29+
manufacturer: _TZE200_ioxkjvuz
30+
model: TS0601
31+
deviceProfileName: smoke-detector
32+
- id: "_TZE204_mby4kbtq/TS0601"
33+
deviceLabel: Tuya Gas Sensor
34+
manufacturer: _TZE204_mby4kbtq
35+
model: TS0601
36+
deviceProfileName: smoke-detector
37+
- id: "_TZE200_uebojraa/TS0601"
38+
deviceLabel: Tuya Smoke Detector
39+
manufacturer: _TZE200_uebojraa
40+
model: TS0601
41+
deviceProfileName: smoke-battery
42+
- id: "_TZE204_h2rctifa/TS0601"
43+
deviceLabel: Tuya Switch 1
44+
manufacturer: _TZE204_h2rctifa
45+
model: TS0601
46+
deviceProfileName: basic-switch
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: basic-switch
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: switch
6+
version: 1
7+
- id: firmwareUpdate
8+
version: 1
9+
- id: refresh
10+
version: 1
11+
categories:
12+
- name: Switch
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: motion-sensor
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: motionSensor
6+
version: 1
7+
- id: battery
8+
version: 1
9+
- id: firmwareUpdate
10+
version: 1
11+
- id: refresh
12+
version: 1
13+
categories:
14+
- name: MotionSensor
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: one-button-battery
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: button
6+
version: 1
7+
- id: battery
8+
version: 1
9+
- id: firmwareUpdate
10+
version: 1
11+
- id: refresh
12+
version: 1
13+
categories:
14+
- name: Button
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: smoke-battery
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: smokeDetector
6+
version: 1
7+
- id: batteryLevel
8+
version: 1
9+
- id: firmwareUpdate
10+
version: 1
11+
- id: refresh
12+
version: 1
13+
categories:
14+
- name: SmokeDetector
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: smoke-detector
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: smokeDetector
6+
version: 1
7+
- id: firmwareUpdate
8+
version: 1
9+
- id: refresh
10+
version: 1
11+
categories:
12+
- name: SmokeDetector
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: window-treatment-reverse
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: windowShade
6+
version: 1
7+
- id: windowShadeLevel
8+
version: 1
9+
- id: windowShadePreset
10+
version: 1
11+
- id: firmwareUpdate
12+
version: 1
13+
- id: refresh
14+
version: 1
15+
categories:
16+
- name: Blind
17+
18+
preferences:
19+
- preferenceId: reverse
20+
explicit: true
21+
- preferenceId: presetPosition
22+
explicit: true
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
local capabilities = require "st.capabilities"
16+
local clusters = require "st.zigbee.zcl.clusters"
17+
local OnOff = clusters.OnOff
18+
local device_management = require "st.zigbee.device_management"
19+
local PRESENT_ATTRIBUTE_ID = 0x00fd
20+
21+
local FINGERPRINTS = {
22+
{ mfr = "_TZ3000_ja5osu5g", model = "TS004F"},
23+
{ mfr = "_TZ3000_8rppvwda", model = "TS0041"},
24+
{ mfr = "_TZ3000_pkfazisv", model = "TS0215A"}
25+
}
26+
27+
local function is_tuya_button(opts, driver, device)
28+
for _, fingerprint in ipairs(FINGERPRINTS) do
29+
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
30+
return true
31+
end
32+
end
33+
return false
34+
end
35+
36+
local function added_handler(self, device)
37+
device:emit_event(capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))
38+
device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = { displayed = false }}))
39+
device:emit_event(capabilities.button.button.pushed({state_change = false}))
40+
end
41+
42+
local function button_handler(event)
43+
return function(driver, device, value, zb_rx)
44+
device:emit_event(event)
45+
end
46+
end
47+
48+
local tuya_private_cluster_button_handler = function(driver, device, zb_rx)
49+
local event
50+
local additional_fields = {
51+
state_change = true
52+
}
53+
local value = string.byte(zb_rx.body.zcl_body.body_bytes, 1)
54+
55+
if value == 0x00 then
56+
event = capabilities.button.button.pushed(additional_fields)
57+
elseif value == 0x01 then
58+
event = capabilities.button.button.double(additional_fields)
59+
elseif value == 0x02 then
60+
event = capabilities.button.button.held(additional_fields)
61+
end
62+
if event ~= nil then
63+
device:emit_event(event)
64+
end
65+
end
66+
67+
local function do_configure(driver, device)
68+
device:configure()
69+
device:send(device_management.build_bind_request(device, OnOff.ID, driver.environment_info.hub_zigbee_eui))
70+
end
71+
72+
local tuya_button_driver = {
73+
NAME = "tuya button",
74+
lifecycle_handlers = {
75+
added = added_handler,
76+
doConfigure = do_configure
77+
},
78+
can_handle = is_tuya_button,
79+
zigbee_handlers = {
80+
cluster = {
81+
[OnOff.ID] = {
82+
[OnOff.server.commands.On.ID] = button_handler(capabilities.button.button.double({ state_change = true })),
83+
[OnOff.server.commands.Off.ID] = button_handler(capabilities.button.button.held({ state_change = true })),
84+
[OnOff.server.commands.Toggle.ID] = button_handler(capabilities.button.button.pushed({ state_change = true })),
85+
[PRESENT_ATTRIBUTE_ID] = tuya_private_cluster_button_handler
86+
}
87+
}
88+
},
89+
sub_drivers = {
90+
require("button.meian-button")
91+
}
92+
}
93+
94+
return tuya_button_driver
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
-- Copyright 2025 SmartThings
2+
--
3+
-- Licensed under the Apache License, Version 2.0 (the "License");
4+
-- you may not use this file except in compliance with the License.
5+
-- You may obtain a copy of the License at
6+
--
7+
-- http://www.apache.org/licenses/LICENSE-2.0
8+
--
9+
-- Unless required by applicable law or agreed to in writing, software
10+
-- distributed under the License is distributed on an "AS IS" BASIS,
11+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
-- See the License for the specific language governing permissions and
13+
-- limitations under the License.
14+
15+
local clusters = require "st.zigbee.zcl.clusters"
16+
local capabilities = require "st.capabilities"
17+
local read_attribute = require "st.zigbee.zcl.global_commands.read_attribute"
18+
local constants = require "st.zigbee.constants"
19+
local zcl_messages = require "st.zigbee.zcl"
20+
local data_types = require "st.zigbee.data_types"
21+
local messages = require "st.zigbee.messages"
22+
local defaults = require "st.zigbee.defaults"
23+
local PowerConfiguration = clusters.PowerConfiguration
24+
25+
local IASACE = clusters.IASACE
26+
27+
local FINGERPRINTS = {
28+
{ mfr = "_TZ3000_pkfazisv", model = "TS0215A"}
29+
}
30+
31+
local function is_meian_sos_button(opts, driver, device)
32+
for _, fingerprint in ipairs(FINGERPRINTS) do
33+
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
34+
return true
35+
end
36+
end
37+
return false
38+
end
39+
40+
local function read_attribute_function(device, cluster_id, attr_id)
41+
local read_body = read_attribute.ReadAttribute( attr_id )
42+
local zclh = zcl_messages.ZclHeader({
43+
cmd = data_types.ZCLCommandId(read_attribute.ReadAttribute.ID)
44+
})
45+
local addrh = messages.AddressHeader(
46+
constants.HUB.ADDR,
47+
constants.HUB.ENDPOINT,
48+
device:get_short_address(),
49+
device:get_endpoint(cluster_id),
50+
constants.HA_PROFILE_ID,
51+
cluster_id
52+
)
53+
local message_body = zcl_messages.ZclMessageBody({
54+
zcl_header = zclh,
55+
zcl_body = read_body
56+
})
57+
return messages.ZigbeeMessageTx({
58+
address_header = addrh,
59+
body = message_body
60+
})
61+
end
62+
63+
local function added_handler(driver, device, event, args)
64+
device:emit_event(capabilities.button.supportedButtonValues({"pushed"}, {visibility = { displayed = false }}))
65+
device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = { displayed = false }}))
66+
device:emit_event(capabilities.button.button.pushed({state_change = false}))
67+
68+
local magic_spell = {0x0004, 0x0000, 0x0001, 0x0005, 0x0007, 0xfffe}
69+
device:send(read_attribute_function(device, clusters.Basic.ID, magic_spell))
70+
device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:read(device))
71+
end
72+
73+
local ias_ace_emergency_handler = function(driver, device)
74+
device:emit_event(capabilities.button.button.pushed({ state_change = true }))
75+
end
76+
77+
local meian_sos_button_handler = {
78+
NAME = "Meian Sos Button",
79+
supported_capabilities = {
80+
capabilities.battery,
81+
capabilities.button,
82+
capabilities.refresh
83+
},
84+
lifecycle_handlers = {
85+
added = added_handler
86+
},
87+
zigbee_handlers = {
88+
cluster = {
89+
[IASACE.ID] = {
90+
[IASACE.server.commands.Emergency.ID] = ias_ace_emergency_handler
91+
}
92+
}
93+
},
94+
can_handle = is_meian_sos_button
95+
}
96+
defaults.register_for_default_handlers(meian_sos_button_handler, meian_sos_button_handler.supported_capabilities)
97+
return meian_sos_button_handler

0 commit comments

Comments
 (0)