diff --git a/drivers/SmartThings/zigbee-contact/src/aqara/init.lua b/drivers/SmartThings/zigbee-contact/src/aqara/init.lua index 46da673401..a952e0bfef 100644 --- a/drivers/SmartThings/zigbee-contact/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-contact/src/aqara/init.lua @@ -63,11 +63,17 @@ local function do_configure(self, device) PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local function added_handler(driver, device) device:emit_event(capabilities.batteryLevel.type("CR1632")) device:emit_event(capabilities.batteryLevel.quantity(1)) device:emit_event(capabilities.batteryLevel.battery("normal")) - device:emit_event(capabilities.contactSensor.contact.closed()) + emit_event_if_latest_state_missing(device, "main", capabilities.contactSensor, capabilities.contactSensor.contact.NAME, capabilities.contactSensor.contact.open()) end local function contact_status_handler(self, device, value, zb_rx) diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua index 0fd017d6b0..119e3fbdab 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_aqara_contact_sensor.lua @@ -81,13 +81,21 @@ test.register_coroutine_test( test.register_coroutine_test( "added lifecycle handler", function() + -- The initial contactSensor event should be send during the device's first time onboarding test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.type("CR1632"))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.quantity(1))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.battery("normal"))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", - capabilities.contactSensor.contact.closed())) + capabilities.contactSensor.contact.open())) + test.wait_for_events() + -- Avoid sending the initial contactSensor event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.type("CR1632"))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.batteryLevel.quantity(1))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.batteryLevel.battery("normal"))) end ) diff --git a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua index c0a597a99d..b529dd3fd1 100644 --- a/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua +++ b/drivers/SmartThings/zigbee-lock/src/samsungsds/init.lua @@ -57,9 +57,15 @@ local refresh = function(driver, device, cmd) -- do nothing in refresh capability handler end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local device_added = function(self, device) lock_utils.populate_state_from_data(device) - device:emit_event(capabilities.lock.lock.unlocked()) + emit_event_if_latest_state_missing(device, "main", capabilities.lock, capabilities.lock.lock.NAME, capabilities.lock.lock.unlocked()) device:emit_event(capabilities.battery.battery(100)) end diff --git a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua index 1667b0ecb8..bac1554790 100644 --- a/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua +++ b/drivers/SmartThings/zigbee-lock/src/test/test_zigbee_samsungsds.lua @@ -1377,11 +1377,17 @@ test.register_coroutine_test( test.register_coroutine_test( "Device added function handler", function() + -- The initial lock event should be send during the device's first time onboarding test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added"}) test.socket.capability:__set_channel_ordering("relaxed") test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.lock.lock.unlocked())) test.wait_for_events() + -- Avoid sending the initial lock event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added"}) + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) + test.wait_for_events() end ) diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua b/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua index dd0566a3a0..b7bece2efe 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/arrival-sensor-v1/init.lua @@ -100,8 +100,14 @@ local function beep_handler(self, device, command) end end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local function added_handler(self, device) - device:emit_event(PresenceSensor.presence("present")) + emit_event_if_latest_state_missing(device, "main", PresenceSensor, PresenceSensor.presence.NAME, PresenceSensor.presence("present")) end local function init_handler(self, device, event, args) diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/init.lua b/drivers/SmartThings/zigbee-presence-sensor/src/init.lua index 1ab62e821d..d52722ed16 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/init.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/init.lua @@ -139,8 +139,14 @@ local function beep_handler(self, device, command) device:send(IdentifyCluster.server.commands.Identify(device, BEEP_IDENTIFY_TIME)) end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local function added_handler(self, device) - device:emit_event(PresenceSensor.presence("present")) + emit_event_if_latest_state_missing(device, "main", PresenceSensor, PresenceSensor.presence.NAME, PresenceSensor.presence("present")) device:set_field(IS_PRESENCE_BASED_ON_BATTERY_REPORTS, false, {persist = true}) device:send(PowerConfiguration.attributes.BatteryVoltage:read(device)) end diff --git a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua index 0ddffcedab..4c2533036d 100644 --- a/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua +++ b/drivers/SmartThings/zigbee-presence-sensor/src/test/test_st_arrival_sensor_v1.lua @@ -68,6 +68,7 @@ end zigbee_test_utils.prepare_zigbee_env_info() local add_device = function() + -- The initial presenceSensor event should be send during the device's first time onboarding test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "added"}) test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present") @@ -75,6 +76,12 @@ local add_device = function() test.wait_for_events() end +local add_device_after_switch_over = function() + -- Avoid sending the initial presenceSensor event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "added"}) + test.wait_for_events() +end + local function test_init() test.mock_device.add_test_device(mock_simple_device)end @@ -126,6 +133,7 @@ test.register_coroutine_test( "Added lifecycle should be handlded", function () add_device() + add_device_after_switch_over() end ) 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 30fec61f6d..55a0d7cf51 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 @@ -109,6 +109,7 @@ test.register_message_test( ) local add_device = function() + -- The initial presenceSensor event should be send during the device's first time onboarding test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "added"}) test.socket.capability:__expect_send(mock_simple_device:generate_test_message("main", capabilities.presenceSensor.presence("present") @@ -120,6 +121,16 @@ local add_device = function() test.wait_for_events() end +local add_device_after_switch_over = function() + -- Avoid sending the initial presenceSensor event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + test.socket.device_lifecycle:__queue_receive({ mock_simple_device.id, "added"}) + test.socket.zigbee:__expect_send({ + mock_simple_device.id, + PowerConfiguration.attributes.BatteryVoltage:read(mock_simple_device) + }) + test.wait_for_events() +end + test.register_coroutine_test( "Battery Voltage test cases when polling from hub", function() @@ -185,6 +196,7 @@ test.register_coroutine_test( "Added lifecycle should be handlded", function () add_device() + add_device_after_switch_over() end ) diff --git a/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua b/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua index 9523614bcc..662d337b82 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/aqara/init.lua @@ -110,17 +110,23 @@ local function device_init(driver, device) do_refresh(driver, device) end +local function emit_component_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component.id, capability.ID, attribute_name) == nil then + device:emit_component_event(component, value) + end +end + local function device_added(driver, device) supported_thermostat_modes_handler(driver, device, nil) device:emit_event(capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"})) device:emit_event(capabilities.temperatureMeasurement.temperature({value = 27.0, unit = "C"})) device:emit_event(capabilities.thermostatMode.thermostatMode.manual()) - device:emit_event(capabilities.valve.valve.open()) - device:emit_component_event(device.profile.components.ChildLock, capabilities.lock.lock.unlocked()) device:emit_event(capabilities.hardwareFault.hardwareFault.clear()) device:emit_event(valveCalibration.calibrationState.calibrationPending()) device:emit_event(invisibleCapabilities.invisibleCapabilities({""})) device:emit_event(capabilities.battery.battery(100)) + emit_component_event_if_latest_state_missing(device, device.profile.components.main, capabilities.valve, capabilities.valve.valve.NAME, capabilities.valve.valve.open()) + emit_component_event_if_latest_state_missing(device, device.profile.components.ChildLock, capabilities.lock, capabilities.lock.lock.NAME, capabilities.lock.lock.unlocked()) end local function thermostat_alarm_status_handler(driver, device, value, zb_rx) diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua index 355c48b240..4a1dc2057f 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_aqara_thermostat.lua @@ -73,46 +73,8 @@ end test.set_test_init_function(test_init) --- test.register_coroutine_test( --- "Handle added lifecycle", --- function() --- test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", --- capabilities.thermostatMode.supportedThermostatModes({ --- capabilities.thermostatMode.thermostatMode.manual.NAME, --- capabilities.thermostatMode.thermostatMode.antifreezing.NAME --- }, { visibility = { displayed = false } })) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"})) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 27.0, unit = "C"})) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.manual()) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", capabilities.valve.valve.open()) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("ChildLock", capabilities.lock.lock.unlocked()) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", valveCalibration.calibrationState.calibrationPending()) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", invisibleCapabilities.invisibleCapabilities({""})) --- ) --- test.socket.capability:__expect_send( --- mock_device:generate_test_message("main", capabilities.battery.battery(100)) --- ) --- end --- ) + + test.register_coroutine_test( @@ -312,4 +274,76 @@ test.register_coroutine_test( end ) --]] +test.register_coroutine_test( + "Handle added lifecycle", + function() + -- The initial valve and lock event should be send during the device's first time onboarding + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({ + capabilities.thermostatMode.thermostatMode.manual.NAME, + capabilities.thermostatMode.thermostatMode.antifreezing.NAME + }, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 27.0, unit = "C"})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.manual()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", valveCalibration.calibrationState.calibrationPending()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", invisibleCapabilities.invisibleCapabilities({""})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.battery.battery(100)) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.valve.valve.open()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("ChildLock", capabilities.lock.lock.unlocked()) + ) + -- Avoid sending the initial open and lock event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({ + capabilities.thermostatMode.thermostatMode.manual.NAME, + capabilities.thermostatMode.thermostatMode.antifreezing.NAME + }, { visibility = { displayed = false } })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatHeatingSetpoint.heatingSetpoint({value = 21.0, unit = "C"})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.temperatureMeasurement.temperature({value = 27.0, unit = "C"})) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.thermostatMode.thermostatMode.manual()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.hardwareFault.hardwareFault.clear()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", valveCalibration.calibrationState.calibrationPending()) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", invisibleCapabilities.invisibleCapabilities({""})) + ) + 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/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua index 63f4d8d8a0..698fffcceb 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-1/init.lua @@ -66,13 +66,18 @@ local function do_configure(driver, device) device:send(Association:Remove({grouping_identifier = 1, node_ids = driver.environment_info.hub_zwave_id})) end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local function device_added(driver, device) do_refresh(driver, device) - device:emit_event(capabilities.tamperAlert.tamper.clear()) - device:emit_event(capabilities.contactSensor.contact.open()) + emit_event_if_latest_state_missing(device, "main", capabilities.contactSensor, capabilities.contactSensor.contact.NAME, capabilities.contactSensor.contact.open()) + emit_event_if_latest_state_missing(device, "main", capabilities.tamperAlert, capabilities.tamperAlert.tamper.NAME, capabilities.tamperAlert.tamper.clear()) end - local fibaro_door_window_sensor_1 = { NAME = "fibaro door window sensor 1", lifecycle_handlers = { diff --git a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua index 6290f15a41..16c5ec2017 100644 --- a/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua +++ b/drivers/SmartThings/zwave-sensor/src/fibaro-door-window-sensor/fibaro-door-window-sensor-2/init.lua @@ -33,10 +33,16 @@ local function can_handle_fibaro_door_window_sensor_2(opts, driver, device, cmd, return false end +local function emit_event_if_latest_state_missing(device, component, capability, attribute_name, value) + if device:get_latest_state(component, capability.ID, attribute_name) == nil then + device:emit_event(value) + end +end + local function device_added(self, device) - device:emit_event(capabilities.tamperAlert.tamper.clear()) - device:emit_event(capabilities.contactSensor.contact.open()) - device:emit_event(capabilities.temperatureAlarm.temperatureAlarm.cleared()) + emit_event_if_latest_state_missing(device, "main", capabilities.tamperAlert, capabilities.tamperAlert.tamper.NAME, capabilities.tamperAlert.tamper.clear()) + emit_event_if_latest_state_missing(device, "main", capabilities.contactSensor, capabilities.contactSensor.contact.NAME, capabilities.contactSensor.contact.open()) + emit_event_if_latest_state_missing(device, "main", capabilities.temperatureAlarm, capabilities.temperatureAlarm.temperatureAlarm.NAME, capabilities.temperatureAlarm.temperatureAlarm.cleared()) end local function alarm_report_handler(self, device, cmd) diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua index 4cd8d0e905..7975e8f9b1 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_1.lua @@ -58,6 +58,7 @@ test.set_test_init_function(test_init) test.register_message_test( "Device should be polled with refresh right after inclusion", { + -- The initial tamperAlert and contactSensor event should be send during the device's first time onboarding { channel = "device_lifecycle", direction = "receive", @@ -96,6 +97,36 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_fibaro_door_window_sensor1:generate_test_message("main", capabilities.contactSensor.contact.open()) + }, + -- Avoid sending the initial tamperAlert and contactSensor event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + { + channel = "device_lifecycle", + direction = "receive", + message = { mock_fibaro_door_window_sensor1.id, "added" } + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_fibaro_door_window_sensor1, + Battery:Get({}) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_fibaro_door_window_sensor1, + SensorBinary:Get({}) + ) + }, + { + channel = "zwave", + direction = "send", + message = zw_test_utils.zwave_test_build_send_command( + mock_fibaro_door_window_sensor1, + SensorAlarm:Get({}) + ) } }, { diff --git a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua index fccd88ad97..bc78bd02ca 100644 --- a/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua +++ b/drivers/SmartThings/zwave-sensor/src/test/test_fibaro_door_window_sensor_2.lua @@ -339,6 +339,7 @@ test.register_coroutine_test( test.register_message_test( "device_added should be handled", { + -- The initial tamperAlert, contactSensor & temperatureAlarm event should be send during the device's first time onboarding { channel = "device_lifecycle", direction = "receive", @@ -358,6 +359,12 @@ test.register_message_test( channel = "capability", direction = "send", message = mock_fibaro_door_window_sensor:generate_test_message("main", capabilities.temperatureAlarm.temperatureAlarm.cleared()) + }, + -- Avoid sending the initial tamperAlert, contactSensor & temperatureAlarm event after driver switch-over, as the switch-over event itself re-triggers the added lifecycle. + { + channel = "device_lifecycle", + direction = "receive", + message = {mock_fibaro_door_window_sensor.id, "added"} } } )