diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua
index 4f58e960e9..09f384ee4e 100755
--- a/drivers/SmartThings/matter-lock/src/init.lua
+++ b/drivers/SmartThings/matter-lock/src/init.lua
@@ -83,7 +83,13 @@ local function lock_state_handler(driver, device, ib, response)
}
if ib.data.value ~= nil then
- device:emit_event(LOCK_STATE[ib.data.value])
+ local event = LOCK_STATE[ib.data.value]
+ if event ~= nil then
+ device:emit_event(event)
+ else
+ device.log.warn(string.format("Received unknown lock state value (%s), emitting unknown", ib.data.value))
+ device:emit_event(attr.unknown())
+ end
else
device:emit_event(LOCK_STATE[LockState.NOT_FULLY_LOCKED])
end
diff --git a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua
index 2477e2745b..9693289951 100644
--- a/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua
+++ b/drivers/SmartThings/matter-lock/src/test/test_matter_lock.lua
@@ -191,6 +191,29 @@ test.register_message_test(
}
)
+test.register_message_test(
+ "Handle unknown LockState value from Matter device.", {
+ {
+ channel = "matter",
+ direction = "receive",
+ message = {
+ mock_device.id,
+ clusters.DoorLock.attributes.LockState:build_test_report_data(
+ mock_device, 10, 0xFF
+ ),
+ },
+ },
+ {
+ channel = "capability",
+ direction = "send",
+ message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()),
+ },
+ },
+ {
+ min_api_version = 17
+ }
+)
+
test.register_message_test(
"Handle received BatPercentRemaining from device.", {
{
diff --git a/drivers/SmartThings/samsung-audio/src/command.lua b/drivers/SmartThings/samsung-audio/src/command.lua
index deca7c4416..d03fe07850 100644
--- a/drivers/SmartThings/samsung-audio/src/command.lua
+++ b/drivers/SmartThings/samsung-audio/src/command.lua
@@ -34,6 +34,19 @@ local function is_empty(t)
return not t or (type(t) == "table" and #t == 0)
end
+local function get_uic_response(ret, command_name)
+ local root = ret and ret.handler_res and ret.handler_res.root
+ local uic = root and root.UIC
+ local response = uic and uic.response
+
+ if not uic then
+ log.warn(string.format("Missing UIC data in %s response", tostring(command_name)))
+ return nil, nil
+ end
+
+ return uic, response
+end
+
local function tr(s,mappings)
return string.gsub(s,
"(.)",
@@ -94,8 +107,9 @@ function Command.volume(ip)
if ip then
local url = format_url(ip, "/UIC?cmd=GetVolume")
local ret = handle_http_request(ip, url)
- if ret then
- response_map = { volume = ret.handler_res.root.UIC.response.volume, }
+ local _, response = get_uic_response(ret, "GetVolume")
+ if response and response.volume ~= nil then
+ response_map = { volume = response.volume, }
end
end
return response_map
@@ -114,8 +128,9 @@ function Command.set_volume(ip, level)
local encoded_str_vol = "/UIC?cmd=%3Cpwron%3Eon%3C/pwron%3E%3Cname%3ESetVolume%3C/name%3E%3Cp%20type=%22dec%22%20name=%22volume%22%20val=%22" .. level .. "%22%3E%3C/p%3E"
local url = format_url(ip, encoded_str_vol)
local ret = handle_http_request(ip, url)
- if ret then
- response_map = { volume = ret.handler_res.root.UIC.response.volume, }
+ local _, response = get_uic_response(ret, "SetVolume")
+ if response and response.volume ~= nil then
+ response_map = { volume = response.volume, }
end
end
return response_map
@@ -326,8 +341,9 @@ function Command.getMute(ip)
if ip then
local url = format_url(ip, "/UIC?cmd=GetMute")
local ret = handle_http_request(ip, url)
- if ret then
- response_map = { muted = ret.handler_res.root.UIC.response.mute,}
+ local _, response = get_uic_response(ret, "GetMute")
+ if response and response.mute ~= nil then
+ response_map = { muted = response.mute,}
end
end
return response_map
@@ -342,8 +358,9 @@ function Command.getPlayStatus(ip)
if ip then
local url = format_url(ip, "/UIC?cmd=GetPlayStatus")
local ret = handle_http_request(ip, url)
- if ret then
- response_map = { playstatus = ret.handler_res.root.UIC.response.playstatus,}
+ local _, response = get_uic_response(ret, "GetPlayStatus")
+ if response and response.playstatus ~= nil then
+ response_map = { playstatus = response.playstatus,}
end
end
return response_map
diff --git a/drivers/SmartThings/samsung-audio/src/handlers.lua b/drivers/SmartThings/samsung-audio/src/handlers.lua
index 6233a86326..65a0027bf9 100644
--- a/drivers/SmartThings/samsung-audio/src/handlers.lua
+++ b/drivers/SmartThings/samsung-audio/src/handlers.lua
@@ -137,7 +137,7 @@ end
function CapabilityHandlers.handle_audio_notification(driver, device, cmd)
local ip = device:get_field("ip")
local mute_status = command.getMute(ip)
- if mute_status.muted ~= "off" then
+ if mute_status and mute_status.muted ~= nil and mute_status.muted ~= "off" then
--unmute before playig notification
command.unmute(ip)
end
diff --git a/drivers/SmartThings/samsung-audio/src/init.lua b/drivers/SmartThings/samsung-audio/src/init.lua
index de1958ff25..2e51b8e0b4 100644
--- a/drivers/SmartThings/samsung-audio/src/init.lua
+++ b/drivers/SmartThings/samsung-audio/src/init.lua
@@ -94,14 +94,23 @@ local function emit_refresh_data_to_server(driver, device, cmd)
-- get volume
local vol = command.volume(device:get_field("ip"))
- device:emit_event(capabilities.audioVolume.volume(tonumber(vol.volume)))
+ local current_volume = vol and tonumber(vol.volume)
+ if current_volume ~= nil then
+ device:emit_event(capabilities.audioVolume.volume(current_volume))
+ else
+ log.warn("Unable to read speaker volume from refresh response")
+ end
-- get mute status
local muteStatus = command.getMute(device:get_field("ip"))
- if muteStatus.muted ~= "off" then
- device:emit_event(capabilities.audioMute.mute.muted())
+ if muteStatus and muteStatus.muted ~= nil then
+ if muteStatus.muted ~= "off" then
+ device:emit_event(capabilities.audioMute.mute.muted())
+ else
+ device:emit_event(capabilities.audioMute.mute.unmuted())
+ end
else
- device:emit_event(capabilities.audioMute.mute.unmuted())
+ log.warn("Unable to read speaker mute state from refresh response")
end
end
diff --git a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua
index b464b30acc..814e0f70ce 100644
--- a/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua
+++ b/drivers/SmartThings/zigbee-switch/src/zll-polling/init.lua
@@ -5,36 +5,63 @@ local device_lib = require "st.device"
local clusters = require "st.zigbee.zcl.clusters"
local configurationMap = require "configurations"
-local function set_up_zll_polling(driver, device)
- local INFREQUENT_POLL_COUNTER = "_infrequent_poll_counter"
- local function poll()
- local infrequent_counter = device:get_field(INFREQUENT_POLL_COUNTER) or 1
- if infrequent_counter == 12 then
- -- do a full refresh once an hour
- device:refresh()
- infrequent_counter = 0
- else
- -- Read On/Off every poll
- for _, ep in pairs(device.zigbee_endpoints) do
- if device:supports_server_cluster(clusters.OnOff.ID, ep.id) then
- device:send(clusters.OnOff.attributes.OnOff:read(device):to_endpoint(ep.id))
- end
+local INFREQUENT_POLL_COUNTER = "_infrequent_poll_counter"
+local ZLL_POLL_TIMER = "_zll_poll_timer"
+
+local function do_zll_poll(device)
+ if device == nil or type(device.get_field) ~= "function" then
+ return
+ end
+
+ local infrequent_counter = device:get_field(INFREQUENT_POLL_COUNTER) or 1
+ if infrequent_counter == 12 then
+ -- do a full refresh once an hour
+ device:refresh()
+ infrequent_counter = 0
+ else
+ -- Read On/Off every poll
+ for _, ep in pairs(device.zigbee_endpoints) do
+ if device:supports_server_cluster(clusters.OnOff.ID, ep.id) then
+ device:send(clusters.OnOff.attributes.OnOff:read(device):to_endpoint(ep.id))
end
- infrequent_counter = infrequent_counter + 1
end
- device:set_field(INFREQUENT_POLL_COUNTER, infrequent_counter)
+ infrequent_counter = infrequent_counter + 1
end
+ device:set_field(INFREQUENT_POLL_COUNTER, infrequent_counter)
+end
+local function set_up_zll_polling(driver, device)
-- only set this up for non-child devices
- if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then
- device.thread:call_on_schedule(5 * 60, poll, "zll_polling")
+ if device.network_type ~= device_lib.NETWORK_TYPE_ZIGBEE then
+ return
+ end
+
+ -- should never happen, but defensive check
+ local existing_timer = device:get_field(ZLL_POLL_TIMER)
+ if existing_timer ~= nil then
+ device.thread:cancel_timer(existing_timer)
+ end
+
+ local timer = device.thread:call_on_schedule(5 * 60, function()
+ do_zll_poll(device)
+ end, "zll_polling")
+
+ device:set_field(ZLL_POLL_TIMER, timer)
+end
+
+local function remove_zll_polling(driver, device)
+ local existing_timer = device:get_field(ZLL_POLL_TIMER)
+ if existing_timer ~= nil then
+ device.thread:cancel_timer(existing_timer)
+ device:set_field(ZLL_POLL_TIMER, nil)
end
end
local ZLL_polling = {
NAME = "ZLL Polling",
lifecycle_handlers = {
- init = configurationMap.reconfig_wrapper(set_up_zll_polling)
+ init = configurationMap.reconfig_wrapper(set_up_zll_polling),
+ removed = remove_zll_polling
},
can_handle = require("zll-polling.can_handle"),
}
diff --git a/drivers/SmartThings/zigbee-valve/src/sinope/init.lua b/drivers/SmartThings/zigbee-valve/src/sinope/init.lua
index ab3786511c..0c6994ad00 100644
--- a/drivers/SmartThings/zigbee-valve/src/sinope/init.lua
+++ b/drivers/SmartThings/zigbee-valve/src/sinope/init.lua
@@ -12,7 +12,9 @@ local PowerConfiguration = clusters.PowerConfiguration
local function device_init(driver, device)
battery_defaults.use_battery_voltage_handling(device)
-- according to the DTH, this attribute cannot be configured for reporting
- device.thread:call_on_schedule(900, function() device:send(PowerConfiguration.attributes.BatteryVoltage:read()) end)
+ device.thread:call_on_schedule(900, function()
+ device:send(PowerConfiguration.attributes.BatteryVoltage:read(device))
+ end)
end
local function battery_voltage_handler(driver, device, command)