diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua index 883a3dc5a5..09f539b53b 100755 --- a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua +++ b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua @@ -227,4 +227,198 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "AQI moderate (51-100) emits moderate airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 75 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "moderate"}))) + end +) + +test.register_coroutine_test( + "AQI slightlyUnhealthy (101-150) emits slightlyUnhealthy airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 125 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "slightlyUnhealthy"}))) + end +) + +test.register_coroutine_test( + "AQI unhealthy (151-200) emits unhealthy airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 175 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "AQI veryUnhealthy (201-300) emits veryUnhealthy airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 250 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "veryUnhealthy"}))) + end +) + +test.register_coroutine_test( + "AQI hazardous (>=301) emits hazardous airQualityHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 350 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC5, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualityHealthConcern.airQualityHealthConcern({value = "hazardous"}))) + end +) + +test.register_coroutine_test( + "carbonDioxide moderate (1501-2500) emits moderate carbonDioxideHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 2000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC3, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideMeasurement.carbonDioxide({value = 2000, unit = "ppm"}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "moderate"}))) + end +) + +test.register_coroutine_test( + "carbonDioxide unhealthy (>2500) emits unhealthy carbonDioxideHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 3000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC3, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideMeasurement.carbonDioxide({value = 3000, unit = "ppm"}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "pm2.5 moderate emits moderate fineDustHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 90 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustSensor.fineDustLevel({value = 90}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "moderate"}))) + end +) + +test.register_coroutine_test( + "pm2.5 unhealthy (>=115) emits unhealthy fineDustHealthConcern", + function() + local attr_report_data = { + { 0x0000, data_types.Uint16.ID, 120 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustSensor.fineDustLevel({value = 120}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.fineDustHealthConcern.fineDustHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "pm1.0 unhealthy (>100) emits unhealthy veryFineDustHealthConcern", + function() + local attr_report_data = { + { 0x0001, data_types.Uint16.ID, 150 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.veryFineDustSensor.veryFineDustLevel({value = 150}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "pm10 unhealthy (>150) emits unhealthy dustHealthConcern", + function() + local attr_report_data = { + { 0x0002, data_types.Uint16.ID, 200 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC1, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.dustSensor.dustLevel({value = 200}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.dustHealthConcern.dustHealthConcern({value = "unhealthy"}))) + end +) + +test.register_coroutine_test( + "tvoc good (<600) emits good tvocHealthConcern", + function() + local attr_report_data = { + { 0x0001, data_types.SinglePrecisionFloat.ID, SinglePrecisionFloat(0, 8, 0.953125) } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, 0xFCC2, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocMeasurement.tvocLevel({value = 500.0, unit = "ug/m3"}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocHealthConcern.tvocHealthConcern({value = "good"}))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua index 7e5ade0cf5..a3227b04bc 100755 --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -398,6 +398,21 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Device reported yoga 3 and driver emit custom_capabilities.yoga.state.both()", + function() + local attr_report_data = { + { 0x0008, data_types.Uint8.ID, 3 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.yoga.state.both())) + end +) + test.register_coroutine_test( "Device reported yoga 2 and driver emit custom_capabilities.yoga.state.right()", function() @@ -910,4 +925,26 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "capability left_control backControl soft emits idle event after delay", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.soft())) + test.wait_for_events() + + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback("idle", { visibility = { displayed = false }}))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-button/src/aqara/init.lua b/drivers/SmartThings/zigbee-button/src/aqara/init.lua index 7467fcfe42..b9545e4721 100644 --- a/drivers/SmartThings/zigbee-button/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-button/src/aqara/init.lua @@ -85,6 +85,7 @@ local function battery_level_handler(driver, device, value, zb_rx) batteryLevel = "warning" end + -- Note that all aqara buttons use batteryLevel and not battery capability. if device:supports_capability_by_id(capabilities.battery.ID) then device:emit_event(capabilities.battery.battery(calc_battery_percentage(voltage))) elseif device:supports_capability_by_id(capabilities.batteryLevel.ID) then diff --git a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua index 67314ef651..b2d5a986d6 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_aqara_button.lua @@ -21,6 +21,21 @@ local PRIVATE_ATTRIBUTE_ID_ALIVE = 0x00F7 local MODE_CHANGE = "stse.allowOperationModeChange" local COMP_LIST = { "button1", "button2", "all" } + +local mock_device_h1_single = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-single-button-mode.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.remote.b18ac1", + server_clusters = { 0x0001, 0x0012 } + } + } + } +) + local mock_device_e1 = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("one-button-batteryLevel.yml"), @@ -51,6 +66,7 @@ local mock_device_h1_double_rocker = test.mock_device.build_test_zigbee_device( zigbee_test_utils.prepare_zigbee_env_info() local function test_init() + test.mock_device.add_test_device(mock_device_h1_single) test.mock_device.add_test_device(mock_device_e1) test.mock_device.add_test_device(mock_device_h1_double_rocker) end @@ -286,4 +302,23 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Handle added lifecycle - H1 single rocker (sets mode=1)", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device_h1_single.id, "added" }) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.button.button.pushed({ state_change = false }))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.batteryLevel.battery.normal())) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.batteryLevel.type("CR2450"))) + test.socket.capability:__expect_send(mock_device_h1_single:generate_test_message("main", + capabilities.batteryLevel.quantity(1))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua index bd7e7512dd..b73f7ee3fe 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_frient_contact_sensor.lua @@ -165,36 +165,6 @@ test.register_message_test( } ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.wait_for_events() - --- test.mock_time.advance_time(50000) -- battery is 21600 for max reporting interval --- test.socket.zigbee:__set_channel_ordering("relaxed") - --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- PowerConfiguration.attributes.BatteryVoltage:read(mock_device) --- } --- ) - --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IASZone.attributes.ZoneStatus:read(mock_device) --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) - test.register_message_test( "Refresh should read all necessary attributes", { @@ -260,4 +230,36 @@ test.register_message_test( } ) +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: contact/closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua index 035c49d27f..d5482e7d6e 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartsense_multi.lua @@ -5,6 +5,9 @@ local test = require "integration_test" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" + +local IASZone = clusters.IASZone local SMARTSENSE_PROFILE_ID = 0xFC01 local MFG_CODE = 0x110A @@ -426,4 +429,68 @@ test.register_coroutine_test( end ) +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + +test.register_message_test( + "ZoneStatus attr report should generate contact event when garageSensor not set: open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatus attr report should generate contact event when garageSensor not set: closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index 5ef40d5aaf..c86078f224 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua @@ -219,6 +219,38 @@ test.register_coroutine_test( end ) +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: open", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.open()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should generate contact event when garageSensor not set: closed", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.contactSensor.contact.closed()) + } + } +) + test.register_coroutine_test( "Refresh necessary attributes", function() diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua index 90d29656d8..751fc1bee9 100644 --- a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua +++ b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_accessory_dimmer.lua @@ -217,4 +217,81 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "On command when current level is 0 should reset level then toggle switch", + function() + mock_device:set_field("current_level", 0) + test.socket.zigbee:__queue_receive({ mock_device.id, OnOff.server.commands.On.build_test_rx(mock_device) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(10))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + end +) + +test.register_coroutine_test( + "Step command(MoveStepMode.DOWN) to level 0 should emit switch off and level 0", + function() + mock_device:set_field("current_level", 10) + local step_command = Level.server.commands.Step.build_test_rx(mock_device, Level.types.MoveStepMode.DOWN, 0x00, + 0x0000, 0x00, 0x00) + local frm_ctrl = FrameCtrl(0x01) + step_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(0))) + end +) + +test.register_coroutine_test( + "Step command(MoveStepMode.UP) when device is off should emit switch on", + function() + mock_device:set_field("current_status", "off") + local step_command = Level.server.commands.Step.build_test_rx(mock_device, Level.types.MoveStepMode.UP, 0x00, + 0x0000, 0x00, 0x00) + local frm_ctrl = FrameCtrl(0x01) + step_command.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({ mock_device.id, step_command }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) + end +) + +test.register_coroutine_test( + "Capability command setLevel should be handled", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 50 } } }) + test.wait_for_events() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(50))) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Capability command setLevel 0 should restore previous level", + function() + mock_device:set_field("current_level", 30) + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = "switchLevel", component = "main", command = "setLevel", args = { 0 } } }) + test.wait_for_events() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(30))) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "device added lifecycle should initialize device state", + function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switchLevel.level(100))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.supportedButtonValues({"pushed", "held"}, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.button.button.pushed({ state_change = true }))) + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua index b16fe8cd11..243f559466 100644 Binary files a/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua and b/drivers/SmartThings/zigbee-dimmer-remote/src/test/test_zigbee_battery_accessory_dimmer.lua differ diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua index 0fa987e35a..1a61602c1e 100644 --- a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua @@ -432,4 +432,46 @@ test.register_message_test( } ) +test.register_message_test( + "Fan switch on command from main component", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "main", command = "on", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 1) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + "Fan switch off command from main component", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "main", command = "off", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x00) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua index 497395ac55..5ced35a0d3 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_aqara_sensor.lua @@ -18,11 +18,17 @@ local t_utils = require "integration_test.utils" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local clusters = require "st.zigbee.zcl.clusters" local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" local PowerConfiguration = clusters.PowerConfiguration local TemperatureMeasurement = clusters.TemperatureMeasurement local RelativeHumidity = clusters.RelativeHumidity +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F + local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("humidity-temp-battery-aqara.yml"), @@ -256,4 +262,23 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Added handler should send manufacturer attribute and initialize device state", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ value = 0, unit = "C" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.relativeHumidityMeasurement.humidity(0))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.batteryLevel.battery("normal"))) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua index e985dafce5..063afbdf9f 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_air_quality_sensor.lua @@ -123,6 +123,7 @@ test.register_message_test( test.register_coroutine_test( "Configure should configure all necessary attributes", function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zigbee:__set_channel_ordering("relaxed") @@ -191,6 +192,26 @@ test.register_coroutine_test( mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + --refresh happens after configure + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, Frient_VOCMeasurement.ID, Frient_VOCMeasurement.attributes.MeasuredValue.ID, Frient_VOCMeasurement.ManufacturerSpecificCode):to_endpoint(0x26) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(0x26) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(0x26) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + }) end ) @@ -291,4 +312,18 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Added handler should initialize VOC and air quality state", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.airQualitySensor.airQuality({ value = 0 }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocHealthConcern.tvocHealthConcern({ value = "good" }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.tvocMeasurement.tvocLevel({ value = 0, unit = "ppb" }))) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua index 5b9ef77634..7d925ae94d 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_frient_sensor.lua @@ -105,6 +105,7 @@ test.register_message_test( test.register_coroutine_test( "Configure should configure all necessary attributes", function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zigbee:__set_channel_ordering("relaxed") test.socket.zigbee:__expect_send({ @@ -141,6 +142,22 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 0x001E, 0x0E10, 100) }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + test.wait_for_events() + + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.wait_for_events() end ) @@ -190,6 +207,7 @@ test.register_message_test( test.register_coroutine_test( "info_changed to check for necessary preferences settings: Temperature Sensitivity", function() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") local updates = { preferences = { temperatureSensitivity = 0.9, @@ -217,6 +235,21 @@ test.register_coroutine_test( ) }) test.wait_for_events() + + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + HumidityMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) + }) + test.wait_for_events() end ) diff --git a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua index 557d4e2345..81fee9249f 100644 --- a/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua +++ b/drivers/SmartThings/zigbee-illuminance-sensor/src/test/test_illuminance_sensor_aqara.lua @@ -9,6 +9,10 @@ local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" +local write_attribute_response = require "st.zigbee.zcl.global_commands.write_attribute_response" +local zcl_messages = require "st.zigbee.zcl" test.add_package_capability("detectionFrequency.yaml") local IlluminanceMeasurement = clusters.IlluminanceMeasurement @@ -19,8 +23,10 @@ local detectionFrequency = capabilities["stse.detectionFrequency"] local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F +local FREQUENCY_ATTRIBUTE_ID = 0x0000 local FREQUENCY_DEFAULT_VALUE = 5 +local FREQUENCY_PREF = "frequencyPref" local mock_device = test.mock_device.build_test_zigbee_device( { @@ -131,4 +137,50 @@ test.register_message_test( } ) +local function build_write_attr_res(cluster, status) + local addr_header = messages.AddressHeader( + mock_device:get_short_address(), + mock_device.fingerprinted_endpoint_id, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local write_attribute_body = write_attribute_response.WriteAttributeResponse(status, {}) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(write_attribute_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = write_attribute_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + +test.register_coroutine_test( + "Handle setDetectionFrequency capability command", + function() + local frequency = 10 + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "stse.detectionFrequency", component = "main", command = "setDetectionFrequency", args = { frequency } } }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, FREQUENCY_ATTRIBUTE_ID, + MFG_CODE, data_types.Uint16, frequency) }) + end +) + +test.register_coroutine_test( + "Handle write attr res on PRIVATE_CLUSTER_ID emits detectionFrequency", + function() + mock_device:set_field(FREQUENCY_PREF, FREQUENCY_DEFAULT_VALUE) + test.socket.zigbee:__queue_receive({ mock_device.id, + build_write_attr_res(PRIVATE_CLUSTER_ID, 0x00) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + detectionFrequency.detectionFrequency(FREQUENCY_DEFAULT_VALUE, { visibility = { displayed = false } }))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua index 3ed037cd54..2fd55fd3f0 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_lock.lua @@ -771,4 +771,88 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Alarm code 0 should generate lock unknown event", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Alarm.client.commands.Alarm.build_test_rx(mock_device, 0x00, DoorLock.ID) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + } + } +) + +test.register_message_test( + "Alarm code 1 should generate lock unknown event", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, Alarm.client.commands.Alarm.build_test_rx(mock_device, 0x01, DoorLock.ID) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lock.lock.unknown()) + } + } +) + +test.register_message_test( + "Pin response for unoccupied slot with no existing code should generate unset event", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x05, + DoorLockUserStatus.OCCUPIED_DISABLED, + DoorLockUserType.UNRESTRICTED, + "" + ) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.lockCodes.codeChanged("5 unset", + { data = { codeName = "Code 5" }, state_change = true })) + } + } +) + +test.register_coroutine_test( + "Pin response for already-set slot should use changed change type", + function() + init_code_slot(1, "Code 1", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 0x01, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 changed", { data = { codeName = "Code 1" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "Code 1"}), { visibility = { displayed = false } }))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua index 75ad49a1f5..931a4b143c 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_yale.lua @@ -319,4 +319,98 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Setting a user code for a slot that is empty should indicate failure and unset", + function() + test.timer.__create_and_queue_test_time_advance_timer(4, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, { capability = capabilities.lockCodes.ID, command = "setCode", args = { 1, "1234", "test" } } }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + DoorLock.server.commands.SetPINCode(mock_device, + 1, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.wait_for_events() + test.mock_time.advance_time(4) + test.socket.zigbee:__expect_send({ mock_device.id, DoorLock.server.commands.GetPINCode(mock_device, 1) }) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, + DoorLockUserStatus.OCCUPIED_DISABLED, + DoorLockUserType.UNRESTRICTED, + "" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 failed", { state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 is not set", { state_change = true }))) + end +) + +test.register_coroutine_test( + "Pin response for already-set slot without pending operation should use changed change type", + function() + init_code_slot(1, "initialName", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, + DoorLockUserStatus.OCCUPIED_ENABLED, + DoorLockUserType.UNRESTRICTED, + "1234" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 changed", { data = { codeName = "initialName" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) + end +) + +test.register_coroutine_test( + "Pin response for already-set slot that is now empty should delete the code", + function() + init_code_slot(1, "initialName", mock_device) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({["1"] = "initialName"}), { visibility = { displayed = false }}))) + test.wait_for_events() + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + DoorLock.client.commands.GetPINCodeResponse.build_test_rx( + mock_device, + 1, + DoorLockUserStatus.OCCUPIED_DISABLED, + DoorLockUserType.UNRESTRICTED, + "" + ) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.codeChanged("1 deleted", { data = { codeName = "initialName" }, state_change = true }))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.lockCodes.lockCodes(json.encode({}), { visibility = { displayed = false }}))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua index 2580af0ceb..c8bb2c20f0 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_aqara_motion_illuminance.lua @@ -10,6 +10,10 @@ local data_types = require "st.zigbee.data_types" local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" +local messages = require "st.zigbee.messages" +local zb_const = require "st.zigbee.constants" +local write_attribute_response = require "st.zigbee.zcl.global_commands.write_attribute_response" +local zcl_messages = require "st.zigbee.zcl" test.add_package_capability("sensitivityAdjustment.yaml") test.add_package_capability("detectionFrequency.yaml") @@ -46,6 +50,29 @@ local function test_init() test.set_test_init_function(test_init) +local function build_write_attr_res(cluster, status) + local addr_header = messages.AddressHeader( + mock_device:get_short_address(), + mock_device.fingerprinted_endpoint_id, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local write_attribute_body = write_attribute_response.WriteAttributeResponse(status, {}) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(write_attribute_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = write_attribute_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + test.register_coroutine_test( "Handle added lifecycle", function() @@ -123,4 +150,59 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Motion detected twice cancels existing timer and creates a new one", + function() + local detect_duration = PREF_FREQUENCY_VALUE_DEFAULT + -- Pre-register two timers: first will be cancelled, second will fire + test.timer.__create_and_queue_test_time_advance_timer(detect_duration, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(detect_duration, "oneshot") + local attr_report_data = { + { MOTION_ILLUMINANCE_ATTRIBUTE_ID, data_types.Int32.ID, 0x0001006E } -- 65646 + } + -- First motion event + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(110)) + ) + test.wait_for_events() + -- Second motion event before first timer fires - cancels first timer + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance(110)) + ) + test.wait_for_events() + -- Only the second timer fires + test.mock_time.advance_time(detect_duration) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.motionSensor.motion.inactive())) + end +) + +test.register_coroutine_test( + "WriteAttributeResponse with PREF_FREQUENCY_KEY updates detection frequency", + function() + mock_device:set_field(PREF_CHANGED_KEY, PREF_FREQUENCY_KEY) + mock_device:set_field(PREF_CHANGED_VALUE, PREF_FREQUENCY_VALUE_DEFAULT) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_write_attr_res(PRIVATE_CLUSTER_ID, 0x00) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + detectionFrequency.detectionFrequency(PREF_FREQUENCY_VALUE_DEFAULT, {visibility = {displayed = false}}))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua index 89d014dc90..3e3acf69e2 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor.lua @@ -72,39 +72,6 @@ test.register_message_test( } ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.wait_for_events() --- test.mock_time.advance_time(50000) --- test.socket.zigbee:__set_channel_ordering("relaxed") --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IASZone.attributes.ZoneStatus:read(mock_device) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) - test.register_coroutine_test( "Refresh should read all necessary attributes", function() @@ -130,6 +97,7 @@ test.register_coroutine_test( ) test.wait_for_events() + test.timer.__create_and_queue_test_time_advance_timer(5, "oneshot") test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) test.socket.zigbee:__expect_send({ @@ -227,9 +195,52 @@ test.register_coroutine_test( }) mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + -- Advance time to trigger the do_refresh call scheduled in do_configure + test.wait_for_events() + test.mock_time.advance_time(5) + test.socket.zigbee:__expect_send({ + mock_device.id, + OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) + }) end ) +test.register_message_test( + "Occupancy attribute handler emits motion active", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OccupancySensing.attributes.Occupancy:build_test_attr_report(mock_device, 0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) + } + } +) + +test.register_message_test( + "Occupancy attribute handler emits motion inactive", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, OccupancySensing.attributes.Occupancy:build_test_attr_report(mock_device, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) + } + } +) + test.register_coroutine_test( "infochanged to check for necessary preferences settings: Motion Turn-Off Delay, Motion Turn-On Delay, Movement Threshold in Turn-On Delay", function() diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua index 9556a6e58e..1f72d365e6 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_motion_sensor_pro.lua @@ -176,51 +176,6 @@ test.register_message_test( } ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.wait_for_events() --- test.mock_time.advance_time(50000) --- test.socket.zigbee:__set_channel_ordering("relaxed") --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IASZone.attributes.ZoneStatus:read(mock_device):to_endpoint(TAMPER_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- IlluminanceMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(ILLUMINANCE_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- OccupancySensing.attributes.Occupancy:read(mock_device):to_endpoint(OCCUPANCY_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- PowerConfiguration.attributes.BatteryVoltage:read(mock_device):to_endpoint(POWER_CONFIGURATION_ENDPOINT) --- } --- ) --- test.socket.zigbee:__expect_send( --- { --- mock_device.id, --- TemperatureMeasurement.attributes.MeasuredValue:read(mock_device):to_endpoint(TEMPERATURE_MEASUREMENT_ENDPOINT) --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) - test.register_coroutine_test( "Refresh should read all necessary attributes", function() @@ -432,4 +387,38 @@ test.register_coroutine_test( end ) +test.register_message_test( + "IASZone attribute status handler: tamper detected", + { + { + channel = "zigbee", + direction = "receive", + -- ZoneStatus | Bit2: Tamper set to 1 + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0004) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.detected()) + } + } +) + +test.register_message_test( + "IASZone attribute status handler: tamper clear", + { + { + channel = "zigbee", + direction = "receive", + -- ZoneStatus | Bit2: Tamper set to 0 + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.tamperAlert.tamper.clear()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua index 7f47416e93..3afdbbcaa2 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_gator_motion.lua @@ -143,4 +143,20 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "ZoneStatusChangeNotification with alarm1 triggers motion active and inactive", + function() + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.socket.zigbee:__queue_receive( + { + mock_device.id, + IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) + } + ) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + test.mock_time.advance_time(120) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua index 12e0038c6c..36312c8fdb 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_ikea_motion.lua @@ -223,4 +223,29 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Second OnWithTimedOff cancels existing timer and resets motion", + function() + local frm_ctrl = FrameCtrl(0x01) + -- Pre-register two timers: first will be cancelled, second will fire + test.timer.__create_and_queue_test_time_advance_timer(0x0708/10, "oneshot") + test.timer.__create_and_queue_test_time_advance_timer(0x0708/10, "oneshot") + -- First motion event + local first_cmd = OnOff.server.commands.OnWithTimedOff.build_test_rx(mock_device, 0x00, 0x0708, 0x0000) + first_cmd.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({mock_device.id, first_cmd}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + test.wait_for_events() + -- Second motion event before first timer fires - cancels first timer + local second_cmd = OnOff.server.commands.OnWithTimedOff.build_test_rx(mock_device, 0x00, 0x0708, 0x0000) + second_cmd.body.zcl_header.frame_ctrl = frm_ctrl + test.socket.zigbee:__queue_receive({mock_device.id, second_cmd}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.active())) + test.wait_for_events() + -- Only the second timer fires + test.mock_time.advance_time(180) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive())) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua index ddeaf77ce4..7a1655fc41 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_thirdreality_sensor.lua @@ -140,4 +140,23 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Handle added lifecycle - reads ApplicationVersion", + { + { + channel = "device_lifecycle", + direction = "receive", + message = {mock_device1.id, "added"} + }, + { + channel = "zigbee", + direction = "send", + message = { + mock_device1.id, + Basic.attributes.ApplicationVersion:read(mock_device1) + } + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua index 5308e11ee2..a148df5b1e 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_1p.lua @@ -7,6 +7,7 @@ local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" +local constants = require "st.zigbee.constants" -- Mock out globals @@ -222,4 +223,99 @@ test.register_coroutine_test( end ) +test.register_message_test( + "resetEnergyMeter command should send OnOff On to reset device", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "energyMeter", component = "main", command = "resetEnergyMeter", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, clusters.OnOff.server.commands.On(mock_device) } + } + } +) + +test.register_coroutine_test( + "refresh capability command should read device attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + cluster_base.read_attribute(mock_device, data_types.ClusterId(SimpleMetering.ID), data_types.AttributeId(0x0001)) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSVoltage:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.RMSCurrent:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) + }) + end +) + +test.register_coroutine_test( + "energy handler resets offset when reading is below stored offset", + function() + -- Set an offset larger than the incoming value (100 raw / 100 = 1.0 kWh, offset = 5.0) + mock_device:set_field(constants.ENERGY_METER_OFFSET, 5.0, {persist = true}) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 100) + }) + -- Offset resets to 0; raw_value_kilowatts = 1.0 - 0 = 1.0; no powerConsumptionReport (delta_tick < 15 min) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.0, unit = "kWh"})) + ) + end +) + +test.register_coroutine_test( + "energy handler resets save tick when timer has slipped beyond 30 minutes", + function() + -- Advance time > 30 min so that curr_save_tick + 15*60 < os.time() is true + test.timer.__create_and_queue_test_time_advance_timer(40*60, "oneshot") + test.mock_time.advance_time(40*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 100) + }) + -- raw_value = 100, divisor = 100, kWh = 1.0, watts = 1000.0; first report: deltaEnergy = 0.0 + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 1000.0, deltaEnergy = 0.0})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.0, unit = "kWh"})) + ) + end +) + test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua index a4b8b8c037..2949961fb1 100644 --- a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_consumption_report_sihas.lua @@ -26,6 +26,7 @@ local zb_const = require "st.zigbee.constants" local zcl_messages = require "st.zigbee.zcl" local data_types = require "st.zigbee.data_types" local Status = require "st.zigbee.generated.types.ZclStatus" +local constants = require "st.zigbee.constants" local mock_device = test.mock_device.build_test_zigbee_device( @@ -228,6 +229,41 @@ test.register_coroutine_test( end } ) +test.register_coroutine_test( + "energy handler resets shinasystems offset when reading is below stored offset", + function() + -- divisor=1000; raw_value=100 -> 0.1 kWh; offset=0.5 -> 0.1 < 0.5 triggers reset + mock_device:set_field(constants.ENERGY_METER_OFFSET, 0.5, {persist = true}) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 100) + }) + -- offset resets to 0; raw_value_kilowatts = 0.1; no powerConsumptionReport (delta_tick < 15 min) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 0.1, unit = "kWh"})) + ) + end +) + +test.register_coroutine_test( + "shinasystems energy handler resets save tick when timer has slipped beyond 30 minutes", + function() + -- Advance time > 30 min so that curr_save_tick + 15*60 < os.time() is true + test.timer.__create_and_queue_test_time_advance_timer(40*60, "oneshot") + test.mock_time.advance_time(40*60) + test.socket.zigbee:__queue_receive({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 1500) + }) + -- raw_value=1500, divisor=1000, kWh=1.5, watts=1500.0; first report: deltaEnergy=0.0 + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerConsumptionReport.powerConsumption({energy = 1500.0, deltaEnergy = 0.0})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({value = 1.5, unit = "kWh"})) + ) + end +) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua new file mode 100644 index 0000000000..81caddae56 --- /dev/null +++ b/drivers/SmartThings/zigbee-power-meter/src/test/test_zigbee_power_meter_frient.lua @@ -0,0 +1,102 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local ElectricalMeasurement = clusters.ElectricalMeasurement +local SimpleMetering = clusters.SimpleMetering +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" +local constants = require "st.zigbee.constants" + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("power-meter.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Develco Products A/S", + model = "EMIZB-132", + server_clusters = {SimpleMetering.ID, ElectricalMeasurement.ID} + } + } +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + mock_device:set_field("_configuration_version", 1, {persist = true}) + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "frient device_init sets divisor fields", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" }) + test.wait_for_events() + assert(mock_device:get_field(constants.SIMPLE_METERING_DIVISOR_KEY) == 1000, + "SIMPLE_METERING_DIVISOR_KEY should be 1000") + assert(mock_device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) == 10000, + "ELECTRICAL_MEASUREMENT_DIVISOR_KEY should be 10000") + end +) + +test.register_coroutine_test( + "frient lifecycle configure event should configure device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMetering.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 5, 3600, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurement.ID) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 5, 3600, 5) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + ElectricalMeasurement.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) + }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua index 6957148647..ca98e8a1a7 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_zigbee_presence_sensor.lua @@ -10,6 +10,7 @@ local PowerConfiguration = clusters.PowerConfiguration local capabilities = require "st.capabilities" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local t_utils = require "integration_test.utils" +local presence_utils = require "presence_utils" -- Needed for building ConfigureReportingResponse msg local messages = require "st.zigbee.messages" @@ -294,4 +295,96 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "battery_config_response_handler cancels pre-existing recurring poll timer", + function() + -- Place a live timer in the field so the nil-check branch is taken. + local pre_timer = test.timer.__create_test_time_advance_timer(60, "interval") + mock_simple_device:set_field(presence_utils.RECURRING_POLL_TIMER, pre_timer) + test.socket.zigbee:__queue_receive({ + mock_simple_device.id, + build_config_response_msg(PowerConfiguration.ID, 0x00) + }) + -- poke() emits "present" for every inbound zigbee message + test.socket.capability:__expect_send( + mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present")) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "info_changed with changed check_interval cancels existing recurring poll timer", + function() + local pre_timer = test.timer.__create_test_time_advance_timer(60, "interval") + mock_simple_device:set_field(presence_utils.RECURRING_POLL_TIMER, pre_timer) + test.socket.device_lifecycle():__queue_receive( + mock_simple_device:generate_info_changed({ preferences = { check_interval = 100 } }) + ) + test.wait_for_events() + end +) + +-- Build two additional mock devices (module-level) for checkInterval type variants. +-- The profile default sets checkInterval = 120 (number); we override after building. +local mock_device_str_interval = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("smartthings-arrival-sensor.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SmartThings", + model = "tagv4", + server_clusters = {0x0000, 0x0001, 0x0003} + } + } + } +) +mock_device_str_interval.preferences.checkInterval = "120" -- string → triggers elseif branch + +local mock_device_nil_interval = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("smartthings-arrival-sensor.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SmartThings", + model = "tagv4", + server_clusters = {0x0000, 0x0001, 0x0003} + } + } + } +) +mock_device_nil_interval.preferences.checkInterval = nil -- nil → triggers default-return branch + +test.register_coroutine_test( + "init with string checkInterval uses parsed value for presence timeout", + function() + test.mock_device.add_test_device(mock_device_str_interval) + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.socket.device_lifecycle:__queue_receive({ mock_device_str_interval.id, "init" }) + test.wait_for_events() + test.mock_time.advance_time(121) + test.socket.capability:__expect_send( + mock_device_str_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) + ) + end, + { test_init = function() end } +) + +test.register_coroutine_test( + "init with nil checkInterval uses default presence timeout", + function() + test.mock_device.add_test_device(mock_device_nil_interval) + test.timer.__create_and_queue_test_time_advance_timer(120, "oneshot") + test.socket.device_lifecycle:__queue_receive({ mock_device_nil_interval.id, "init" }) + test.wait_for_events() + test.mock_time.advance_time(121) + test.socket.capability:__expect_send( + mock_device_nil_interval:generate_test_message("main", capabilities.presenceSensor.presence("not present")) + ) + end, + { test_init = function() end } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua index 4aa03d2bca..bdd79dd59f 100644 --- a/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua +++ b/drivers/SmartThings/zigbee-range-extender/src/test/test_frient_zigbee_range_extender.lua @@ -174,4 +174,36 @@ test.register_message_test( } ) +test.register_message_test( + "ZoneStatusChangeNotification - mains should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + } +) + +test.register_message_test( + "Device added lifecycle should emit mains powerSource", + { + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_device.id, "added" } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerSource.powerSource.mains()) + } + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua index 8140da64bd..c88d272807 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren.lua @@ -1001,4 +1001,120 @@ test.register_message_test( } ) +local function build_sw_version_attr_report(device, fw_bytes) + local zcl_messages_mod = require "st.zigbee.zcl" + local messages_mod = require "st.zigbee.messages" + local zb_const_mod = require "st.zigbee.constants" + local report_attr = require "st.zigbee.zcl.global_commands.report_attribute" + local zcl_cmds_mod = require "st.zigbee.zcl.global_commands" + + local attr_record = report_attr.ReportAttributeAttributeRecord( + DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR, + data_types.CharString.ID, + fw_bytes + ) + local report_body = report_attr.ReportAttribute({ attr_record }) + local zclh = zcl_messages_mod.ZclHeader({ + cmd = data_types.ZCLCommandId(zcl_cmds_mod.REPORT_ATTRIBUTE_ID) + }) + local addrh = messages_mod.AddressHeader( + device:get_short_address(), + device:get_endpoint(Basic.ID), + zb_const_mod.HUB.ADDR, + zb_const_mod.HUB.ENDPOINT, + zb_const_mod.HA_PROFILE_ID, + Basic.ID + ) + local message_body = zcl_messages_mod.ZclMessageBody({ zcl_header = zclh, zcl_body = report_body }) + return messages_mod.ZigbeeMessageRx({ address_header = addrh, body = message_body }) +end + +test.register_coroutine_test( + "SW version attr handler with new firmware should configure battery percentage", + function() + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + mock_device:set_field("_frient_battery_config_applied", nil, {persist = true}) + + -- Binary "\x01\x09\x03" -> hex "010903" (new firmware >= SIREN_FIXED_ENDIAN_SW_VERSION) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_sw_version_attr_report(mock_device, "\x01\x09\x03") + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SW version attr handler with old firmware should configure battery voltage", + function() + mock_device:set_field(PRIMARY_SW_VERSION, nil, {persist = true}) + mock_device:set_field("_frient_battery_config_applied", nil, {persist = true}) + + -- Binary "\x01\x09\x01" -> hex "010901" (old firmware < SIREN_FIXED_ENDIAN_SW_VERSION) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_sw_version_attr_report(mock_device, "\x01\x09\x01") + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "doConfigure with existing sw_version should call configure_battery_handling", + function() + mock_device:set_field(PRIMARY_SW_VERSION, "010903", {persist = true}) + mock_device:set_field("_frient_battery_config_applied", nil, {persist = true}) + + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASWD.attributes.MaxDuration:write(mock_device, ALARM_DEFAULT_MAX_DURATION) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + PowerConfiguration.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + PowerConfiguration.attributes.BatteryPercentageRemaining:configure_reporting( + mock_device, 30, 21600, 1) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request( + mock_device, + zigbee_test_utils.mock_hub_eui, + IASZone.ID, + 0x2B + ):to_endpoint(0x2B) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.ZoneStatus:configure_reporting(mock_device, 0, 21600, 1) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.attributes.IASCIEAddress:write(mock_device, zigbee_test_utils.mock_hub_eui) + }) + + test.socket.zigbee:__expect_send({ + mock_device.id, + IASZone.server.commands.ZoneEnrollResponse(mock_device, IasEnrollResponseCode.SUCCESS, 0x00) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua index 810e691849..aed318f49a 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_frient_siren_tamper.lua @@ -64,6 +64,24 @@ local mock_device = test.mock_device.build_test_zigbee_device( } ) +local mock_device_112 = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("frient-siren-battery-source-tamper.yml"), + zigbee_endpoints = { + [0x01] = { + id = 0x01, + manufacturer = "frient A/S", + model = "SIRZB-112", + server_clusters = { Scenes.ID, OnOff.ID} + }, + [0x2B] = { + id = 0x2B, + server_clusters = { Basic.ID, Identify.ID, PowerConfiguration.ID, Groups.ID, IASZone.ID, IASWD.ID } + } + } + } +) + zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device) @@ -1026,4 +1044,40 @@ test.register_message_test( } ) +local function get_siren_OFF_commands_112(duration) + local expectedSirenOFFConfiguration = SirenConfiguration(0x00) + expectedSirenOFFConfiguration:set_warning_mode(WarningMode.STOP) + expectedSirenOFFConfiguration:set_siren_level(IaswdLevel.LOW_LEVEL) + + test.socket.zigbee:__expect_send({ + mock_device_112.id, + IASWD.server.commands.StartWarning( + mock_device_112, + expectedSirenOFFConfiguration, + data_types.Uint16(duration and duration or ALARM_DURATION_TEST_VALUE), + data_types.Uint8(0x00), + data_types.Enum8(0x00) + ) + }) +end + +test.register_coroutine_test( + "SIRZB-112 alarm off command should be handled via frient sub-driver", + function() + mock_device_112:set_field(PRIMARY_SW_VERSION, "010903", {persist = true}) + mock_device_112:set_field(ALARM_DURATION, ALARM_DURATION_TEST_VALUE, {persist = true}) + + test.socket.capability:__queue_receive({ + mock_device_112.id, + { capability = "alarm", component = "main", command = "off", args = {} } + }) + + get_siren_OFF_commands_112() + test.wait_for_events() + end, + { test_init = function() + test.mock_device.add_test_device(mock_device_112) + end } +) + test.run_registered_tests() \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua index 3dc707a297..52db9f96f4 100644 --- a/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua +++ b/drivers/SmartThings/zigbee-siren/src/test/test_zigbee_siren.lua @@ -340,6 +340,63 @@ test.register_coroutine_test( end ) +local function build_default_response_zigbee_msg() + local zcl_messages = require "st.zigbee.zcl" + local messages = require "st.zigbee.messages" + local zb_const = require "st.zigbee.constants" + local buf_lib = require "st.buf" + local buf_from_str = function(str) return buf_lib.Reader(str) end + local frame = "\x00\x00" + local default_response = zcl_cmds.DefaultResponse.deserialize(buf_from_str(frame)) + local zclh = zcl_messages.ZclHeader({ cmd = data_types.ZCLCommandId(zcl_cmds.DefaultResponse.ID) }) + local addrh = messages.AddressHeader( + mock_device:get_short_address(), + mock_device:get_endpoint(data_types.ClusterId(IASWD.ID)), + zb_const.HUB.ADDR, zb_const.HUB.ENDPOINT, zb_const.HA_PROFILE_ID, IASWD.ID) + local message_body = zcl_messages.ZclMessageBody({ zcl_header = zclh, zcl_body = default_response }) + return messages.ZigbeeMessageRx({ address_header = addrh, body = message_body }) +end + +test.register_coroutine_test( + "Default response with OFF alarm command should emit off events", + function() + mock_device:set_field("alarmCommand", 0, {persist = true}) + test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + end +) + +test.register_coroutine_test( + "Default response with STROBE alarm command should emit strobe event and timer off", + function() + test.timer.__create_and_queue_test_time_advance_timer(180, "oneshot") + mock_device:set_field("alarmCommand", 2, {persist = true}) + test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.strobe())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(180) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Default response with BOTH alarm command should emit both event", + function() + test.timer.__create_and_queue_test_time_advance_timer(180, "oneshot") + mock_device:set_field("alarmCommand", 3, {persist = true}) + test.socket.zigbee:__queue_receive({ mock_device.id, build_default_response_zigbee_msg() }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.both())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(180) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.alarm.alarm.off())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.wait_for_events() + end +) + test.register_coroutine_test( "Setting a max duration should be handled", function() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua index 4c1dcb1552..1203ffa00a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua @@ -244,6 +244,66 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "selfCheck report should be handled, idle", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x00 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + selfCheck.selfCheckState.idle())) + end +) + +test.register_coroutine_test( + "sensitivityAdjustment report should be handled, High", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x02 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.High())) + end +) + +test.register_coroutine_test( + "Capability on command should be handled : setSensitivityAdjustment High", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x02 } + } + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"High"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x02) + }) + test.wait_for_events() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.High()) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"High"}} + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.High()) + ) + end +) test.register_coroutine_test( diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua index 1af67b36cd..b0b6a4875a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua @@ -7,13 +7,10 @@ local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" - - local PowerConfiguration = clusters.PowerConfiguration local selfCheck = capabilities["stse.selfCheck"] local selfCheckId = "stse.selfCheck" - test.add_package_capability("selfCheck.yaml") local PRIVATE_CLUSTER_ID = 0xFCC0 @@ -23,8 +20,6 @@ local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 local PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID = 0x013A - - local mock_device = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("smoke-battery-aqara.yml"), @@ -39,15 +34,11 @@ local mock_device = test.mock_device.build_test_zigbee_device( } ) - - zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device) end - - test.set_test_init_function(test_init) test.register_coroutine_test( @@ -89,7 +80,6 @@ test.register_coroutine_test( end ) - test.register_coroutine_test( "smokeDetector report should be handled", function() @@ -105,8 +95,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "audioMute report should be handled", function() @@ -122,8 +110,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "Capability on command should be handled : device mute", function() @@ -135,8 +121,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "selfCheck report should be handled", function() @@ -152,8 +136,6 @@ test.register_coroutine_test( end ) - - test.register_coroutine_test( "Capability on command should be handled : device selfCheck", function() @@ -166,7 +148,50 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "smokeDetector report should be handled, smoke clear", + function() + local attr_report_data = { + { PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.smokeDetector.smoke.clear())) + end +) + +test.register_coroutine_test( + "audioMute report should be handled, unmuted", + function() + local attr_report_data = { + { PRIVATE_MUTE_ATTRIBUTE_ID, data_types.Uint8.ID, 0x00 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.audioMute.mute.unmuted())) + end +) +test.register_coroutine_test( + "selfCheck report should be handled, idle", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x00 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + selfCheck.selfCheckState.idle())) + end +) test.register_message_test( "Battery voltage report should be handled", @@ -184,5 +209,4 @@ test.register_message_test( } ) - test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua index 364aae57a0..21b1660c1a 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_heat_detector.lua @@ -569,4 +569,36 @@ test.register_coroutine_test( end ) +test.register_message_test( + "IASZone attribute report should be handled: detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.heat()) + } + } +) + +test.register_coroutine_test( + "IASZone attribute report should be handled: cleared", + function() + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) + }) + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua index fa012a321b..fdb05cf22d 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_frient_smoke_detector.lua @@ -28,6 +28,12 @@ local DEVELCO_BASIC_PRIMARY_SW_VERSION_ATTR = 0x8000 local DEVELCO_MANUFACTURER_CODE = 0x1015 local cluster_base = require "st.zigbee.cluster_base" local defaultWarningDuration = 240 +local messages = require "st.zigbee.messages" +local default_response = require "st.zigbee.zcl.global_commands.default_response" +local zb_const = require "st.zigbee.constants" +local Status = require "st.zigbee.generated.types.ZclStatus" +local zcl_messages = require "st.zigbee.zcl" +local ALARM_COMMAND = "alarmCommand" local mock_device = test.mock_device.build_test_zigbee_device( @@ -609,4 +615,88 @@ test.register_coroutine_test( end ) +local function build_default_response_msg(device, cluster, command, status) + local addr_header = messages.AddressHeader( + device:get_short_address(), + device.fingerprinted_endpoint_id, + zb_const.HUB.ADDR, + zb_const.HUB.ENDPOINT, + zb_const.HA_PROFILE_ID, + cluster + ) + local default_response_body = default_response.DefaultResponse(command, status) + local zcl_header = zcl_messages.ZclHeader({ + cmd = data_types.ZCLCommandId(default_response_body.ID) + }) + local message_body = zcl_messages.ZclMessageBody({ + zcl_header = zcl_header, + zcl_body = default_response_body + }) + return messages.ZigbeeMessageRx({ + address_header = addr_header, + body = message_body + }) +end + +test.register_message_test( + "IASZone attribute report should be handled: detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0001) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", smokeDetector.smoke.detected()) + } + } +) + +test.register_coroutine_test( + "IASZone attribute report should be handled: clear", + function() + test.timer.__create_and_queue_test_time_advance_timer(6, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) + }) + test.mock_time.advance_time(6) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", smokeDetector.smoke.clear()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "default_response_handler: siren active, emit siren then off after delay", + function() + mock_device:set_field(ALARM_COMMAND, 1) + test.timer.__create_and_queue_test_time_advance_timer(ALARM_DEFAULT_MAX_DURATION, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(mock_device, IASWD.ID, IASWD.server.commands.StartWarning.ID, Status.SUCCESS) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.siren())) + test.mock_time.advance_time(ALARM_DEFAULT_MAX_DURATION) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.off())) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "default_response_handler: alarm off, emit off", + function() + mock_device:set_field(ALARM_COMMAND, 0) + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(mock_device, IASWD.ID, IASWD.server.commands.StartWarning.ID, Status.SUCCESS) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", alarm.alarm.off())) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua index c521ba3e44..516a18c326 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_light.lua @@ -180,4 +180,38 @@ test.register_coroutine_test( end ) +local mock_device_cwacn1 = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-light.yml"), + preferences = { ["stse.lightFadeInTimeInSec"] = 0, ["stse.lightFadeOutTimeInSec"] = 0 }, + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.light.cwacn1", + server_clusters = { 0x0006, 0x0008, 0x0300 } + } + } + } +) + +test.register_coroutine_test( + "Handle added lifecycle for lumi.light.cwacn1 model (colorTemperatureRange max = 6500)", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device_cwacn1.id, "added" }) + + test.socket.zigbee:__expect_send({ + mock_device_cwacn1.id, cluster_base.write_manufacturer_specific_attribute(mock_device_cwacn1, PRIVATE_CLUSTER_ID, + PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + test.socket.capability:__expect_send(mock_device_cwacn1:generate_test_message("main", capabilities.colorTemperature.colorTemperatureRange({ minimum = 2700, maximum = 6500 }))) + end, + { + test_init = function() + test.mock_device.add_test_device(mock_device_cwacn1) + end + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua index d37dafe45d..bd54b1b20f 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_ge_link_bulb.lua @@ -138,4 +138,38 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Handle infoChanged when dimOnOff changes from 1 to 0 should write transition time 0", + function() + -- First: change dimOnOff from default 0 to 1 (triggers write with dimRate=20) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 1 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 20) }) + test.wait_for_events() + -- Now: change dimOnOff from 1 to 0 (driver old=1, new=0 -> writes 0) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 0 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 0) }) + end +) + +test.register_coroutine_test( + "Handle infoChanged when dimRate changes while dimOnOff is 1 should write new dimRate", + function() + -- First: change dimOnOff from default 0 to 1 (triggers write with dimRate=20) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 1 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 20) }) + test.wait_for_events() + -- Now: change dimRate while dimOnOff stays 1 (driver old={dimOnOff=1,dimRate=20}, new={dimOnOff=1,dimRate=50}) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { dimOnOff = 1, dimRate = 50 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnOffTransitionTime:write(mock_device, 50) }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua index 52ba3ef2a6..d6dcc08ea4 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_jasco_switch.lua @@ -5,11 +5,14 @@ local test = require "integration_test" local clusters = require "st.zigbee.zcl.clusters" local OnOffCluster = clusters.OnOff local SimpleMeteringCluster = clusters.SimpleMetering +local ElectricalMeasurementCluster = clusters.ElectricalMeasurement local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" local mock_device = test.mock_device.build_test_zigbee_device({ profile = t_utils.get_profile_definition("switch-power-energy.yml"), + fingerprinted_endpoint_id = 0x01, zigbee_endpoints = { [1] = { id = 1, @@ -21,6 +24,8 @@ local mock_device = test.mock_device.build_test_zigbee_device({ } }) +zigbee_test_utils.prepare_zigbee_env_info() + local function test_init() mock_device:set_field("_configuration_version", 1, {persist = true}) test.mock_device.add_test_device(mock_device) @@ -113,4 +118,30 @@ test.register_message_test( } ) +test.register_coroutine_test( + "doConfigure lifecycle should call refresh and configure on device", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + -- device:refresh() reads + test.socket.zigbee:__expect_send({ mock_device.id, OnOffCluster.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, SimpleMeteringCluster.attributes.InstantaneousDemand:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, SimpleMeteringCluster.attributes.CurrentSummationDelivered:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ActivePower:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ACPowerDivisor:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ACPowerMultiplier:read(mock_device) }) + -- device:configure() bind requests and configure_reporting + test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOffCluster.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300) }) + test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, SimpleMeteringCluster.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, SimpleMeteringCluster.attributes.InstantaneousDemand:configure_reporting(mock_device, 5, 3600, 5) }) + test.socket.zigbee:__expect_send({ mock_device.id, SimpleMeteringCluster.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 5, 3600, 1) }) + test.socket.zigbee:__expect_send({ mock_device.id, zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ElectricalMeasurementCluster.ID) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ActivePower:configure_reporting(mock_device, 5, 3600, 5) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ACPowerDivisor:configure_reporting(mock_device, 1, 43200, 1) }) + test.socket.zigbee:__expect_send({ mock_device.id, ElectricalMeasurementCluster.attributes.ACPowerMultiplier:configure_reporting(mock_device, 1, 43200, 1) }) + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua b/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua index 0457c0eb62..3b004c8f91 100755 --- a/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua @@ -458,4 +458,21 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "component main Capability on command emits on then off after delay", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.switch.switch.on()) + ) + test.wait_for_events() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.switch.switch.off()) + ) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua index 0f00d47fdb..b4ded05175 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_multi_switch_no_master.lua @@ -476,4 +476,45 @@ test.register_coroutine_test( end ) +local mock_non_mns_device = test.mock_device.build_test_zigbee_device( + { + label = "Generic Switch 1", + profile = profile, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "GenericMfr", + model = "GenericModel-DualSwitch", + server_clusters = { 0x0006 } + }, + [2] = { + id = 2, + manufacturer = "GenericMfr", + model = "GenericModel-DualSwitch", + server_clusters = { 0x0006 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + +test.register_coroutine_test( + "device added lifecycle creates child device for secondary OnOff endpoint", + function() + test.socket.device_lifecycle:__queue_receive({ mock_non_mns_device.id, "added" }) + mock_non_mns_device:expect_device_create({ + type = "EDGE_CHILD", + label = "Generic Switch 1 2", + profile = "basic-switch", + parent_device_id = mock_non_mns_device.id, + parent_assigned_child_key = "02" + }) + end, + { + test_init = function() + test.mock_device.add_test_device(mock_non_mns_device) + end + } +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua index ce1a9e2882..1a8d42dba5 100644 --- a/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_wallhero_switch.lua @@ -118,6 +118,23 @@ local mock_seventh_child = test.mock_device.build_test_child_device( } ) +-- Single button device matching WALL HERO fingerprint (used to test button capability events) +local mock_button_device = test.mock_device.build_test_zigbee_device( + { + label = "WALL HERO Switch 1", + profile = scene_switch_profile_def, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "WALL HERO", + model = "ACL-401S1I", + server_clusters = { 0x0003, 0x0004, 0x0005, 0x0006 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + zigbee_test_utils.prepare_zigbee_env_info() local function test_init() @@ -129,7 +146,9 @@ local function test_init() test.mock_device.add_test_device(mock_fourth_child) test.mock_device.add_test_device(mock_fifth_child) test.mock_device.add_test_device(mock_sixth_child) - test.mock_device.add_test_device(mock_seventh_child)end + test.mock_device.add_test_device(mock_seventh_child) + test.mock_device.add_test_device(mock_button_device) +end test.set_test_init_function(test_init) @@ -710,4 +729,15 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "device_added lifecycle event should emit button capability events for button device", + function() + test.socket.device_lifecycle:__queue_receive({ mock_button_device.id, "added" }) + test.socket.capability:__expect_send(mock_button_device:generate_test_message("main", + capabilities.button.numberOfButtons({ value = 1 }, { visibility = { displayed = false } }))) + test.socket.capability:__expect_send(mock_button_device:generate_test_message("main", + capabilities.button.supportedButtonValues({ "pushed" }, { visibility = { displayed = false } }))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua index 0717fbb9fa..74cc7a2115 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_sinope_thermostat.lua @@ -174,4 +174,31 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Refresh should send read requests for all necessary attributes", + function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.LocalTemperature:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.PIHeatingDemand:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.SystemMode:read(mock_device) + }) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua index afb36720a3..4ff17803fa 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_stelpro_ki_zigbee_thermostat.lua @@ -517,5 +517,51 @@ test.register_coroutine_test( }) end ) +test.register_coroutine_test( + "LocalTemperature handler should request PIHeatingDemand when setpoint > temperature", + function() + local RAW_SETPOINT_FIELD = "raw_setpoint" + mock_device:set_field(RAW_SETPOINT_FIELD, 3000, { persist = true }) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device, 2000) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + Thermostat.attributes.PIHeatingDemand:read(mock_device) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({ value = 20.0, unit = "C" })) + ) + end +) + +test.register_coroutine_test( + "Setting an unsupported thermostat mode should re-emit the current mode", + function() + -- Establish a known current mode state + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, ThermostatSystemMode.OFF) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", ThermostatMode.thermostatMode.off()) + ) + test.wait_for_events() + + -- "cool" is not in SUPPORTED_MODES for stelpro-ki; the driver re-emits the current mode + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatMode", component = "main", command = "setThermostatMode", args = { "cool" } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", ThermostatMode.thermostatMode.off()) + ) + end +) test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua index 638f3f7ff4..0886a7cd7f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_zenwithin_thermostat.lua @@ -450,4 +450,44 @@ test.register_message_test( } ) +test.register_coroutine_test( + "Setting cooling setpoint while in heat mode should re-emit the current cooling setpoint", + function() + -- Put device in heat mode + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, 0x04) -- HEAT + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.heat()) + ) + test.wait_for_events() + + -- Set a known cooling setpoint state + test.socket.zigbee:__queue_receive({ + mock_device.id, + Thermostat.attributes.OccupiedCoolingSetpoint:build_test_attr_report(mock_device, 2500) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) + ) + test.wait_for_events() + + -- Try to set cooling setpoint while in heat mode; driver defers and re-emits current + test.timer.__create_and_queue_test_time_advance_timer(10, "oneshot") + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "thermostatCoolingSetpoint", component = "main", command = "setCoolingSetpoint", args = { 27 } } + }) + test.wait_for_events() + + test.mock_time.advance_time(10) + -- After the delay, update_device_setpoint re-emits the unchanged cooling setpoint (25.0 C) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatCoolingSetpoint.coolingSetpoint({ value = 25.0, unit = "C" })) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua index e925491c87..083d522bb3 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_ezex_valve.lua @@ -82,6 +82,22 @@ test.register_message_test( } ) +test.register_message_test( + "Battery percentage report - not low should return 50%", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.attributes.ZoneStatus:build_test_attr_report(mock_device, 0x0000) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(50)) + } + } +) + test.register_message_test( "PowerSource(unknown) reporting should be handled", { diff --git a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua index 8710cef0c9..d30c60ddc6 100644 --- a/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua +++ b/drivers/SmartThings/zigbee-valve/src/test/test_sinope_valve.lua @@ -131,4 +131,12 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Battery voltage above max should clamp to 100 percent", + function() + test.socket.zigbee:__queue_receive({ mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 65) }) + test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.battery.battery(100)) ) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua index 86e4aa774a..082c9fb97c 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_leaksmart_water.lua @@ -79,27 +79,15 @@ test.register_coroutine_test( end ) --- test.register_coroutine_test( --- "Health check should check all relevant attributes", --- function() --- test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) --- test.socket.capability:__expect_send( --- { --- mock_device.id, --- { --- capability_id = "waterSensor", component_id = "main", --- attribute_id = "water", state={value="dry"} --- } --- } --- ) --- end, --- { --- test_init = function() --- test.mock_device.add_test_device(mock_device) --- test.timer.__create_and_queue_test_time_advance_timer(30, "interval", "health_check") --- end --- } --- ) +test.register_coroutine_test( + "Added lifecycle should emit water dry event", + function() + test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.waterSensor.water.dry()) + ) + end +) test.register_coroutine_test( "Configure should configure all necessary attributes", diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua index b52884f6a3..63be06f0a8 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_thirdreality_water_leak_sensor.lua @@ -36,6 +36,14 @@ end test.set_test_init_function(test_init) +test.register_coroutine_test( + "Added lifecycle should read ApplicationVersion", + function() + test.socket.device_lifecycle:__queue_receive({mock_device.id, "added"}) + test.socket.zigbee:__expect_send({mock_device.id, Basic.attributes.ApplicationVersion:read(mock_device)}) + end +) + test.register_coroutine_test( "Refresh necessary attributes", function() diff --git a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua index 35ab4d915c..1ad780782c 100644 --- a/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua +++ b/drivers/SmartThings/zigbee-watering-kit/src/test/test_thirdreality_watering_kit.lua @@ -218,4 +218,50 @@ test.register_coroutine_test( end ) +test.register_message_test( + "ZoneStatusChangeNotification should be handled: detected", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0001, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.detected()) + } + } +) + +test.register_message_test( + "ZoneStatusChangeNotification should be handled: clear", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, IASZone.client.commands.ZoneStatusChangeNotification.build_test_rx(mock_device, 0x0000, 0x00) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) + } + } +) + +test.register_coroutine_test( + "fanspeed reported should be clamped to 0 when value >= 1000", + function() + local attr_report_data = { + { WATERING_TIME_ATTR, data_types.Uint16.ID, 1000 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, THIRDREALITY_WATERING_CLUSTER, attr_report_data, 0x1407) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0))) + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua index a73bb6c5a3..8c87907886 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_ikea.lua @@ -215,4 +215,114 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Cancel existing set-status timer when a new partial level report arrives", + function() + -- First attr: level 90 sets T1 + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr arrives before T1 fires: should cancel T1 and create T2 + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 15) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 85 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + -- T2 fires; T1 was cancelled so only partially_open from T2 + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Timer callback emits closed when shade reaches level 0", + function() + -- First attr starts partial movement and arms a 1-second status timer + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr reports fully closed (level=0); goes through elseif branch, T1 still pending + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(0)) + ) + -- T1 fires; get_latest_state returns 0 so the callback emits closed() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Timer callback emits open when shade reaches level 100", + function() + -- First attr starts partial movement and arms a 1-second status timer + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr reports fully open (level=100); goes through elseif branch, T1 still pending + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + -- T1 fires; get_latest_state returns 100 so the callback emits open() + test.mock_time.advance_time(1) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua index c13164df09..725fda3f62 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_shade_battery_yoolax.lua @@ -322,4 +322,109 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Default response emits opening when current level is higher than target", + function() + -- Establish a partially-closed shade state (zigbee value 90 → shadeLevel 10) + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 90) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(10)) + ) + test.wait_for_events() + -- Send open command: MOST_RECENT_SETLEVEL = 0 (level = 100 - 100 = 0) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShade", component = "main", command = "open", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) + }) + test.wait_for_events() + -- Default response: current_level_zigbee=90, most_recent=0 → 90 > 0 → opening() + test.socket.zigbee:__queue_receive({ + mock_device.id, + build_default_response_msg(WindowCovering.ID, WindowCovering.server.commands.GoToLiftPercentage.ID, Status.SUCCESS) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Attr handler emits partially_open when report matches most-recent set level", + function() + -- Send presetPosition; preset level = 50 so MOST_RECENT_SETLEVEL = 50 (100-50=50) + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShadePreset", component = "main", command = "presetPosition", args = {} } + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + WindowCovering.server.commands.GoToLiftPercentage(mock_device, 50) + }) + test.wait_for_events() + -- Attr report value=50 matches MOST_RECENT_SETLEVEL; shade stops at partial level + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 50) + }) + -- current_level was nil → partially_open from the nil-check branch + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + -- most_recent matches and value is partial → partially_open again (from the match branch) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(50)) + ) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "Spontaneous level report towards open emits opening event", + function() + -- First attr establishes a partial shade level (value=10 → shadeLevel=90) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(90)) + ) + -- Second attr moves toward open (value=5 < current zigbee 10 → opening) + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 5) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(95)) + ) + test.wait_for_events() + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua index 6c8ea27d01..fa764a5db0 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_somfy.lua @@ -517,4 +517,96 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Attribute handler reports closed when level is 0", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 0 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + } + } +) + +test.register_message_test( + "Attribute handler reports open when level is 100", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 100 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + } + } +) + +test.register_coroutine_test( + "Cancel existing poll timer when a new partial level report arrives", + function() + -- First attr: level 90 creates T1 via overwrite_existing_timer_if_needed + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + -- Second attr before T1 fires: overwrite_existing_timer_if_needed cancels T1 and stores T2 + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 15) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 85 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + -- T2 fires; T1 was cancelled so only one partially_open + test.mock_time.advance_time(2) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.partially_open()) + ) + test.wait_for_events() + end +) + test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua index ae87438c99..37841524c4 100755 --- a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_vimar.lua @@ -315,4 +315,122 @@ test.register_coroutine_test( end ) +test.register_message_test( + "Attribute handler reports closed when shade reaches level 0", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 100) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 0 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closed()) + } + } +) + +test.register_message_test( + "Attribute handler reports open when shade reaches level 100", + { + { + channel = "zigbee", + direction = "receive", + message = { + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 0) + } + }, + { + channel = "capability", + direction = "send", + message = { + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 100 } } + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.windowShade.windowShade.open()) + } + } +) + +test.register_coroutine_test( + "SetLevel command emits closing when requested level is below current level", + function() + -- Attr report sets current shade level to 90 (inverted value=10) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ + mock_device.id, + clusters.WindowCovering.attributes.CurrentPositionLiftPercentage:build_test_attr_report(mock_device, 10) + }) + test.socket.capability:__expect_send({ + mock_device.id, + { capability_id = "windowShadeLevel", component_id = "main", attribute_id = "shadeLevel", state = { value = 90 } } + }) + test.wait_for_events() + -- Now both vimar flags are false; requesting level 30 (< 90) triggers closing branch + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 30 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.closing()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(30)) + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 70) + }) + test.wait_for_events() + end +) + +test.register_coroutine_test( + "SetLevel command is ignored (early return) when shades are already moving", + function() + -- Open command: current=0 < 100 → opening, sets VIMAR_SHADES_OPENING=true + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShade", component = "main", command = "open", args = {} } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShade.windowShade.opening()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + clusters.WindowCovering.server.commands.GoToLiftPercentage(mock_device, 0) + }) + test.wait_for_events() + -- While opening, a different setShadeLevel is requested: ignored with current level re-emitted + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = "windowShadeLevel", component = "main", command = "setShadeLevel", args = { 50 } } + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.windowShadeLevel.shadeLevel(100)) + ) + test.wait_for_events() + end +) + test.run_registered_tests()