Skip to content

Commit

Permalink
Merge pull request #1491 from SmartThingsCommunity/handle-min-and-max…
Browse files Browse the repository at this point in the history
…-zigbee-temps

CHAD-12675: Handle Zigbee Min, Max, and Measured Value temperatures
  • Loading branch information
wkhenon authored Jul 15, 2024
2 parents 6583fe8 + 4d8e9e8 commit 4decd29
Show file tree
Hide file tree
Showing 38 changed files with 1,057 additions and 10 deletions.
47 changes: 47 additions & 0 deletions drivers/SmartThings/zigbee-button/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ local ZigbeeDriver = require "st.zigbee"
local defaults = require "st.zigbee.defaults"
local constants = require "st.zigbee.constants"
local IASZone = (require "st.zigbee.zcl.clusters").IASZone
local TemperatureMeasurement = (require "st.zigbee.zcl.clusters").TemperatureMeasurement

local temperature_measurement_defaults = {
MIN_TEMP = "MIN_TEMP",
MAX_TEMP = "MAX_TEMP"
}

local generate_event_from_zone_status = function(driver, device, zone_status, zb_rx)
local event
Expand Down Expand Up @@ -65,10 +71,47 @@ local ias_zone_status_change_handler = function(driver, device, zb_rx)
generate_event_from_zone_status(driver, device, zb_rx.body.zcl_body.zone_status, zb_rx)
end

--- Default handler for Temperature min and max measured value on the Temperature measurement cluster
---
--- This starts initially by performing the same conversion in the temperature_measurement_attr_handler function.
--- It then sets the field of whichever measured value is defined by the @param and checks if the fields
--- correctly compare
---
--- @param minOrMax string the string that determines which attribute to set
--- @param driver Driver The current driver running containing necessary context for execution
--- @param device ZigbeeDevice The device this message was received from containing identifying information
--- @param value Int16 the value of the measured temperature
--- @param zb_rx containing the full message this report came in

local temperature_measurement_min_max_attr_handler = function(minOrMax)
return function(driver, device, value, zb_rx)
local raw_temp = value.value
local celc_temp = raw_temp / 100.0
local temp_scale = "C"

device:set_field(string.format("%s", minOrMax), celc_temp)

local min = device:get_field(temperature_measurement_defaults.MIN_TEMP)
local max = device:get_field(temperature_measurement_defaults.MAX_TEMP)

if min ~= nil and max ~= nil then
if min < max then
device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = temp_scale }))
device:set_field(temperature_measurement_defaults.MIN_TEMP, nil)
device:set_field(temperature_measurement_defaults.MAX_TEMP, nil)
else
device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max))
end
end
end
end

local function added_handler(self, device)
device:emit_event(capabilities.button.supportedButtonValues({"pushed","held","double"}, {visibility = { displayed = false }}))
device:emit_event(capabilities.button.numberOfButtons({value = 1}, {visibility = { displayed = false }}))
device:emit_event(capabilities.button.button.pushed({state_change = false}))
device:send(TemperatureMeasurement.attributes.MaxMeasuredValue:read(device))
device:send(TemperatureMeasurement.attributes.MinMeasuredValue:read(device))
end

local zigbee_button_driver_template = {
Expand All @@ -81,6 +124,10 @@ local zigbee_button_driver_template = {
attr = {
[IASZone.ID] = {
[IASZone.attributes.ZoneStatus.ID] = ias_zone_status_attr_handler
},
[TemperatureMeasurement.ID] = {
[TemperatureMeasurement.attributes.MinMeasuredValue.ID] = temperature_measurement_min_max_attr_handler(temperature_measurement_defaults.MIN_TEMP),
[TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = temperature_measurement_min_max_attr_handler(temperature_measurement_defaults.MAX_TEMP),
}
},
cluster = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ local zigbee_test_utils = require "integration_test.zigbee_test_utils"

local OnOff = clusters.OnOff
local PowerConfiguration = clusters.PowerConfiguration
local TemperatureMeasurement = clusters.TemperatureMeasurement

local button = capabilities.button

Expand Down Expand Up @@ -64,6 +65,14 @@ test.register_coroutine_test(
test.socket.capability:__expect_send(
mock_device:generate_test_message("main", button.button.pushed({ state_change = false }))
)
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MaxMeasuredValue:read(mock_device)
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MinMeasuredValue:read(mock_device)
})
end
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ test.register_coroutine_test(
attribute_id = "button", state = { value = "pushed" }
}
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MaxMeasuredValue:read(mock_device)
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MinMeasuredValue:read(mock_device)
})
-- test.socket.zigbee:__expect_send({
-- mock_device.id,
-- PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ local test = require "integration_test"
local clusters = require "st.zigbee.zcl.clusters"
local IASZone = clusters.IASZone
local PowerConfiguration = clusters.PowerConfiguration
local TemperatureMeasurement = clusters.TemperatureMeasurement
local capabilities = require "st.capabilities"
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
local IasEnrollResponseCode = require "st.zigbee.generated.zcl_clusters.IASZone.types.EnrollResponseCode"
Expand All @@ -26,7 +27,7 @@ local ZoneStatusAttribute = IASZone.attributes.ZoneStatus
local button_attr = capabilities.button.button

local mock_device = test.mock_device.build_test_zigbee_device(
{ profile = t_utils.get_profile_definition("button-profile.yml") }
{ profile = t_utils.get_profile_definition("one-button-temp-battery.yml") }
)
zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
Expand Down Expand Up @@ -159,6 +160,46 @@ test.register_message_test(
}
)

test.register_message_test(
"Temperature report should be handled (C)",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:build_test_attr_report(mock_device, 2500)
}
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 25.0, unit = "C"}))
}
}
)

test.register_message_test(
"Minimum & Maximum Temperature report should be handled (C)",
{
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, TemperatureMeasurement.attributes.MinMeasuredValue:build_test_attr_report(mock_device, 2000)
}
},
{
channel = "zigbee",
direction = "receive",
message = { mock_device.id, TemperatureMeasurement.attributes.MaxMeasuredValue:build_test_attr_report(mock_device, 3000)
}
},
{
channel = "capability",
direction = "send",
message = mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = 20.00, maximum = 30.00 }, unit = "C" }))
}
}
)

test.register_coroutine_test(
"Health check should check all relevant attributes",
function()
Expand All @@ -182,6 +223,14 @@ test.register_coroutine_test(
attribute_id = "button", state = { value = "pushed" }
}
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MaxMeasuredValue:read(mock_device)
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MinMeasuredValue:read(mock_device)
})
test.wait_for_events()

test.mock_time.advance_time(50000) -- Battery has a max reporting interval of 21600
Expand All @@ -192,6 +241,10 @@ test.register_coroutine_test(
PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)
}
)
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MeasuredValue:read(mock_device)
})
end,
{
test_init = function()
Expand All @@ -218,6 +271,12 @@ test.register_coroutine_test(
IASZone.attributes.ZoneStatus:read(mock_device)
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
TemperatureMeasurement.attributes.MeasuredValue:read(mock_device)
}
)
end
)

Expand All @@ -237,6 +296,14 @@ test.register_coroutine_test(
1)
}
)
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device,
30,
600,
100)
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
Expand All @@ -245,6 +312,14 @@ test.register_coroutine_test(
PowerConfiguration.ID)
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
zigbee_test_utils.build_bind_request(mock_device,
zigbee_test_utils.mock_hub_eui,
TemperatureMeasurement.ID)
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
Expand All @@ -263,6 +338,12 @@ test.register_coroutine_test(
PowerConfiguration.attributes.BatteryPercentageRemaining:read(mock_device)
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
TemperatureMeasurement.attributes.MeasuredValue:read(mock_device)
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
Expand Down
57 changes: 56 additions & 1 deletion drivers/SmartThings/zigbee-contact/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,50 @@ local capabilities = require "st.capabilities"
local ZigbeeDriver = require "st.zigbee"
local constants = require "st.zigbee.constants"
local defaults = require "st.zigbee.defaults"
local TemperatureMeasurement = (require "st.zigbee.zcl.clusters").TemperatureMeasurement
local configurationMap = require "configurations"
local SMARTSENSE_MULTI_SENSOR_CUSTOM_PROFILE = 0xFC01

local temperature_measurement_defaults = {
MIN_TEMP = "MIN_TEMP",
MAX_TEMP = "MAX_TEMP"
}

--- Default handler for Temperature min and max measured value on the Temperature measurement cluster
---
--- This starts initially by performing the same conversion in the temperature_measurement_attr_handler function.
--- It then sets the field of whichever measured value is defined by the @param and checks if the fields
--- correctly compare
---
--- @param minOrMax string the string that determines which attribute to set
--- @param driver Driver The current driver running containing necessary context for execution
--- @param device ZigbeeDevice The device this message was received from containing identifying information
--- @param value Int16 the value of the measured temperature
--- @param zb_rx containing the full message this report came in

local temperature_measurement_min_max_attr_handler = function(minOrMax)
return function(driver, device, value, zb_rx)
local raw_temp = value.value
local celc_temp = raw_temp / 100.0
local temp_scale = "C"

device:set_field(string.format("%s", minOrMax), celc_temp)

local min = device:get_field(temperature_measurement_defaults.MIN_TEMP)
local max = device:get_field(temperature_measurement_defaults.MAX_TEMP)

if min ~= nil and max ~= nil then
if min < max then
device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, capabilities.temperatureMeasurement.temperatureRange({ value = { minimum = min, maximum = max }, unit = temp_scale }))
device:set_field(temperature_measurement_defaults.MIN_TEMP, nil)
device:set_field(temperature_measurement_defaults.MAX_TEMP, nil)
else
device.log.warn_with({hub_logs = true}, string.format("Device reported a min temperature %d that is not lower than the reported max temperature %d", min, max))
end
end
end
end

local function device_init(driver, device)
local configuration = configurationMap.get_device_configuration(device)
if configuration ~= nil then
Expand All @@ -29,6 +70,11 @@ local function device_init(driver, device)
end
end

local function added_handler(driver, device)
device:send(TemperatureMeasurement.attributes.MaxMeasuredValue:read(device))
device:send(TemperatureMeasurement.attributes.MinMeasuredValue:read(device))
end

local zigbee_contact_driver_template = {
supported_capabilities = {
capabilities.contactSensor,
Expand All @@ -37,11 +83,20 @@ local zigbee_contact_driver_template = {
capabilities.threeAxis,
capabilities.accelerationSensor
},
zigbee_handlers = {
attr = {
[TemperatureMeasurement.ID] = {
[TemperatureMeasurement.attributes.MinMeasuredValue.ID] = temperature_measurement_min_max_attr_handler(temperature_measurement_defaults.MIN_TEMP),
[TemperatureMeasurement.attributes.MaxMeasuredValue.ID] = temperature_measurement_min_max_attr_handler(temperature_measurement_defaults.MAX_TEMP),
}
}
},
additional_zcl_profiles = {
[SMARTSENSE_MULTI_SENSOR_CUSTOM_PROFILE] = true
},
lifecycle_handlers = {
init = device_init
init = device_init,
added = added_handler,
},
sub_drivers = {
require("aqara"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ local data_types = require "st.zigbee.data_types"

local IASZone = clusters.IASZone
local PowerConfiguration = clusters.PowerConfiguration
local TemperatureMeasurement = clusters.TemperatureMeasurement
local IASCIEAddress = IASZone.attributes.IASCIEAddress
local EnrollResponseCode = IASZone.types.EnrollResponseCode

Expand Down Expand Up @@ -50,6 +51,14 @@ test.register_coroutine_test(
"Configure should configure all necessary attributes",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MaxMeasuredValue:read(mock_device)
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MinMeasuredValue:read(mock_device)
})
test.wait_for_events()

test.socket.zigbee:__set_channel_ordering("relaxed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ local zigbee_test_utils = require "integration_test.zigbee_test_utils"

local IASZone = clusters.IASZone
local PowerConfiguration = clusters.PowerConfiguration
local TemperatureMeasurement = clusters.TemperatureMeasurement

local IASCIEAddress = IASZone.attributes.IASCIEAddress
local EnrollResponseCode = IASZone.types.EnrollResponseCode
Expand Down Expand Up @@ -59,6 +60,14 @@ test.register_coroutine_test(
"Configure should configure all necessary attributes",
function()
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MaxMeasuredValue:read(mock_device)
})
test.socket.zigbee:__expect_send({
mock_device.id,
TemperatureMeasurement.attributes.MinMeasuredValue:read(mock_device)
})
test.wait_for_events()

test.socket.zigbee:__set_channel_ordering("relaxed")
Expand Down
Loading

0 comments on commit 4decd29

Please sign in to comment.