From f0b72b1208f4caeda99411b9524eda1bfedaea45 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 25 Nov 2025 17:10:49 -0600 Subject: [PATCH 1/4] Break AQS files apart, add supported air quality sensor value handling --- .../device_configuration.lua | 6 +- .../{ => air_quality_sensor_utils}/fields.lua | 6 +- .../legacy_device_configuration.lua | 41 ++------- .../air_quality_sensor_utils/utils.lua | 88 +++++++++++++++++++ .../sub_drivers/air_quality_sensor/init.lua | 75 ++++++---------- .../test/test_matter_air_quality_sensor.lua | 1 + ...test_matter_air_quality_sensor_modular.lua | 40 ++++++--- 7 files changed, 156 insertions(+), 101 deletions(-) rename drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/{ => air_quality_sensor_utils}/device_configuration.lua (97%) rename drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/{ => air_quality_sensor_utils}/fields.lua (100%) rename drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/{ => air_quality_sensor_utils}/legacy_device_configuration.lua (61%) create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua similarity index 97% rename from drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/device_configuration.lua rename to drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua index f1ea0506d0..d646cb3c0b 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua @@ -1,11 +1,11 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local version = require "version" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" -local fields = require "sub_drivers.air_quality_sensor.fields" -local version = require "version" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" local DeviceConfiguration = {} @@ -16,7 +16,7 @@ function DeviceConfiguration.supported_level_measurements(device) local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] -- capability describes either a HealthConcern or Measurement/Sensor if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) or {} if #attr_eps > 0 then table.insert(level_caps, cap_id) end diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/fields.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua similarity index 100% rename from drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/fields.lua rename to drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua index 4cb435d8dd..1e4658c99e 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/fields.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/fields.lua @@ -1,10 +1,10 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local capabilities = require "st.capabilities" -local clusters = require "st.matter.clusters" -local utils = require "st.utils" local version = require "version" +local utils = require "st.utils" +local clusters = require "st.matter.clusters" +local capabilities = require "st.capabilities" -- Include driver-side definitions when lua libs api version is < 10 if version.api < 10 then diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/legacy_device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua similarity index 61% rename from drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/legacy_device_configuration.lua rename to drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua index aa76cb8d0e..7b6ef483da 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/legacy_device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua @@ -3,40 +3,13 @@ local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" -local fields = require "sub_drivers.air_quality_sensor.fields" local sensor_utils = require "sensor_utils.utils" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" +local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" local LegacyDeviceConfiguration = {} -local function set_supported_health_concern_values(device, setter_function, cluster, cluster_ep) - -- read_datatype_value works since all the healthConcern capabilities' datatypes are equivalent to the one in airQualityHealthConcern - local read_datatype_value = capabilities.airQualityHealthConcern.airQualityHealthConcern - local supported_values = {read_datatype_value.unknown.NAME, read_datatype_value.good.NAME, read_datatype_value.unhealthy.NAME} - if cluster == clusters.AirQuality then - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.FAIR }) > 0 then - table.insert(supported_values, 3, read_datatype_value.moderate.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MODERATE }) > 0 then - table.insert(supported_values, 4, read_datatype_value.slightlyUnhealthy.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.VERY_POOR }) > 0 then - table.insert(supported_values, read_datatype_value.veryUnhealthy.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.EXTREMELY_POOR }) > 0 then - table.insert(supported_values, read_datatype_value.hazardous.NAME) - end - else -- ConcentrationMeasurement clusters - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MEDIUM_LEVEL }) > 0 then - table.insert(supported_values, 3, read_datatype_value.moderate.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.CRITICAL_LEVEL }) > 0 then - table.insert(supported_values, read_datatype_value.hazardous.NAME) - end - end - device:emit_event_for_endpoint(cluster_ep, setter_function(supported_values, { visibility = { displayed = false }})) -end - function LegacyDeviceConfiguration.create_level_measurement_profile(device) local meas_name, level_name = "", "" for _, cap in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do @@ -44,10 +17,10 @@ function LegacyDeviceConfiguration.create_level_measurement_profile(device) local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] -- capability describes either a HealthConcern or Measurement/Sensor if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) or {} if #attr_eps > 0 then level_name = level_name .. fields.CONCENTRATION_MEASUREMENT_MAP[cap][1] - set_supported_health_concern_values(device, fields.CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) + aqs_utils.set_supported_health_concern_values_helper(device, fields.CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) end elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) @@ -65,8 +38,8 @@ function LegacyDeviceConfiguration.match_profile(device) local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) local profile_name = "aqs" - local aq_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) - set_supported_health_concern_values(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aq_eps[1]) + local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} + aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) if #temp_eps > 0 then profile_name = profile_name .. "-temp" diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua new file mode 100644 index 0000000000..c979b4784a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua @@ -0,0 +1,88 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local capabilities = require "st.capabilities" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + + +local AirQualitySensorUtils = {} + +function AirQualitySensorUtils.is_matter_air_quality_sensor(opts, driver, device) + for _, ep in ipairs(device.endpoints) do + for _, dt in ipairs(ep.device_types) do + if dt.device_type_id == fields.AIR_QUALITY_SENSOR_DEVICE_TYPE_ID then + return true + end + end + end + + return false + end + +function AirQualitySensorUtils.supports_capability_by_id_modular(device, capability, component) + if not device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then + device.log.warn_with({hub_logs = true}, "Device has overriden supports_capability_by_id, but does not have supported capabilities set.") + return false + end + for _, component_capabilities in ipairs(device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES)) do + local comp_id = component_capabilities[1] + local capability_ids = component_capabilities[2] + if (component == nil) or (component == comp_id) then + for _, cap in ipairs(capability_ids) do + if cap == capability then + return true + end + end + end + end + return false +end + +function AirQualitySensorUtils.set_supported_health_concern_values_helper(device, setter_function, cluster, cluster_ep) + -- read_datatype_value works since all the healthConcern capabilities' datatypes are equivalent to the one in airQualityHealthConcern + local read_datatype_value = capabilities.airQualityHealthConcern.airQualityHealthConcern + local supported_values = {read_datatype_value.unknown.NAME, read_datatype_value.good.NAME, read_datatype_value.unhealthy.NAME} + if cluster == clusters.AirQuality then + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.FAIR }) > 0 then + table.insert(supported_values, 3, read_datatype_value.moderate.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MODERATE }) > 0 then + table.insert(supported_values, 4, read_datatype_value.slightlyUnhealthy.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.VERY_POOR }) > 0 then + table.insert(supported_values, read_datatype_value.veryUnhealthy.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.EXTREMELY_POOR }) > 0 then + table.insert(supported_values, read_datatype_value.hazardous.NAME) + end + else -- ConcentrationMeasurement clusters + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MEDIUM_LEVEL }) > 0 then + table.insert(supported_values, 3, read_datatype_value.moderate.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.CRITICAL_LEVEL }) > 0 then + table.insert(supported_values, read_datatype_value.hazardous.NAME) + end + end + device:emit_event_for_endpoint(cluster_ep, setter_function(supported_values, { visibility = { displayed = false }})) +end + +function AirQualitySensorUtils.set_supported_health_concern_values(device) + local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} + AirQualitySensorUtils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) + + for _, cap in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do + local cap_id = cap.ID + local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] + -- capability describes either a HealthConcern or Measurement/Sensor + if (cap_id:match("HealthConcern$")) then + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) or {} + if #attr_eps > 0 then + AirQualitySensorUtils.set_supported_health_concern_values_helper(device, fields.CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) + end + end + end +end + +return AirQualitySensorUtils diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua index fea5e99e48..0d640208a9 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua @@ -1,13 +1,14 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 +local log = require "log" +local version = require "version" +local st_utils = require "st.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local utils = require "st.utils" -local version = require "version" -local log = require "log" - -local fields = require "sub_drivers.air_quality_sensor.fields" +local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" +local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" -- Include driver-side definitions when lua libs api version is < 10 if version.api < 10 then @@ -24,43 +25,6 @@ if version.api < 10 then clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement = require "embedded_clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement" end - --- SUBDRIVER UTILS -- - -local air_quality_sensor_utils = {} - -function air_quality_sensor_utils.is_matter_air_quality_sensor(opts, driver, device) - for _, ep in ipairs(device.endpoints) do - for _, dt in ipairs(ep.device_types) do - if dt.device_type_id == fields.AIR_QUALITY_SENSOR_DEVICE_TYPE_ID then - return true - end - end - end - - return false - end - -function air_quality_sensor_utils.supports_capability_by_id_modular(device, capability, component) - if not device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then - device.log.warn_with({hub_logs = true}, "Device has overriden supports_capability_by_id, but does not have supported capabilities set.") - return false - end - for _, component_capabilities in ipairs(device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES)) do - local comp_id = component_capabilities[1] - local capability_ids = component_capabilities[2] - if (component == nil) or (component == comp_id) then - for _, cap in ipairs(capability_ids) do - if cap == capability then - return true - end - end - end - end - return false -end - - -- AIR QUALITY SENSOR LIFECYCLE HANDLERS -- local AirQualitySensorLifecycleHandlers = {} @@ -70,11 +34,16 @@ function AirQualitySensorLifecycleHandlers.do_configure(driver, device) for _, cluster in ipairs(fields.units_required) do device:send(cluster.attributes.MeasurementUnit:read(device)) end + -- If a device only supports the airQualityHealthConcern capability, no profile update will end up occurring, + -- so this logic will never end up being run in the ensuing infoChanged event. This catches that edge case + local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} + aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) + if version.api >= 14 and version.rpc >= 8 then - local modular_device_cfg = require "sub_drivers.air_quality_sensor.device_configuration" + local modular_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.device_configuration" modular_device_cfg.match_profile(device) else - local legacy_device_cfg = require "sub_drivers.air_quality_sensor.legacy_device_configuration" + local legacy_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.legacy_device_configuration" legacy_device_cfg.match_profile(device) end end @@ -84,11 +53,16 @@ function AirQualitySensorLifecycleHandlers.driver_switched(driver, device) for _, cluster in ipairs(fields.units_required) do device:send(cluster.attributes.MeasurementUnit:read(device)) end + -- If a device only supports the airQualityHealthConcern capability, no profile update will end up occurring, + -- so this logic will never end up being run in the ensuing infoChanged event. This catches that edge case + local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} + aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) + if version.api >= 14 and version.rpc >= 8 then - local modular_device_cfg = require "sub_drivers.air_quality_sensor.device_configuration" + local modular_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.device_configuration" modular_device_cfg.match_profile(device) else - local legacy_device_cfg = require "sub_drivers.air_quality_sensor.legacy_device_configuration" + local legacy_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.legacy_device_configuration" legacy_device_cfg.match_profile(device) end end @@ -97,7 +71,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) and (version.api < 15 or version.rpc < 9) then -- assume that device is using a modular profile on 0.57 FW, override supports_capability_by_id -- library function to utilize optional capabilities - device:extend_device("supports_capability_by_id", air_quality_sensor_utils.supports_capability_by_id_modular) + device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) end device:subscribe() end @@ -106,9 +80,10 @@ function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, a if device.profile.id ~= args.old_st_store.profile.id then if device:get_field(fields.SUPPORTED_COMPONENT_CAPABILITIES) then --re-up subscription with new capabilities using the modular supports_capability override - device:extend_device("supports_capability_by_id", air_quality_sensor_utils.supports_capability_by_id_modular) + device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) end device:subscribe() + aqs_utils.set_supported_health_concern_values(device) end end @@ -183,7 +158,7 @@ function sub_driver_handlers.air_quality_handler(driver, device, ib, response) end function sub_driver_handlers.pressure_measured_value_handler(driver, device, ib, response) - local pressure = utils.round(ib.data.value / 10.0) + local pressure = st_utils.round(ib.data.value / 10.0) device:emit_event_for_endpoint(ib.endpoint_id, capabilities.atmosphericPressureMeasurement.atmosphericPressure(pressure)) end @@ -258,7 +233,7 @@ local matter_air_quality_sensor_handler = { } } }, - can_handle = air_quality_sensor_utils.is_matter_air_quality_sensor + can_handle = aqs_utils.is_matter_air_quality_sensor } return matter_air_quality_sensor_handler diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua index fbf5babd5c..fd3a5c139b 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua @@ -476,6 +476,7 @@ local function test_aqs_device_type_do_configure(generic_mock_device, expected_p test.socket.matter:__expect_send({generic_mock_device.id, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) if expected_supported_values_setters ~= nil then expected_supported_values_setters() end diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua index 0f186ee655..93e6de54f0 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua @@ -4,8 +4,6 @@ local test = require "integration_test" local capabilities = require "st.capabilities" local t_utils = require "integration_test.utils" -local utils = require "st.utils" -local dkjson = require "dkjson" local clusters = require "st.matter.clusters" @@ -30,7 +28,7 @@ local mock_device = test.mock_device.build_test_matter_device({ { endpoint_id = 1, clusters = { - {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER", feature_map = 3}, {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.CarbonMonoxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 3}, @@ -70,7 +68,7 @@ local mock_device_common = test.mock_device.build_test_matter_device({ { endpoint_id = 1, clusters = { - {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.AirQuality.ID, cluster_type = "SERVER", feature_map = 3}, {cluster_id = clusters.TemperatureMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.RelativeHumidityMeasurement.ID, cluster_type = "SERVER"}, {cluster_id = clusters.CarbonDioxideConcentrationMeasurement.ID, cluster_type = "SERVER", feature_map = 1}, @@ -219,7 +217,7 @@ end test.set_test_init_function(test_init) -- run the profile configuration tests -local function test_aqs_device_type_update_modular_profile(generic_mock_device, expected_metadata, subscribe_request) +local function test_aqs_device_type_update_modular_profile(generic_mock_device, expected_metadata, subscribe_request, expected_supported_values_setters) test.socket.device_lifecycle:__queue_receive({generic_mock_device.id, "doConfigure"}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) @@ -231,13 +229,17 @@ local function test_aqs_device_type_update_modular_profile(generic_mock_device, test.socket.matter:__expect_send({generic_mock_device.id, clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) + test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) generic_mock_device:expect_metadata_update(expected_metadata) generic_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - local device_info_copy = utils.deep_copy(generic_mock_device.raw_st_data) - device_info_copy.profile.id = "aqs-modular" - local device_info_json = dkjson.encode(device_info_copy) - test.socket.device_lifecycle:__queue_receive({ generic_mock_device.id, "infoChanged", device_info_json }) + local updated_device_profile = t_utils.get_profile_definition("aqs-modular.yml", + {enabled_optional_capabilities = expected_metadata.optional_component_capabilities} + ) + test.socket.device_lifecycle:__queue_receive(generic_mock_device:generate_info_changed({ profile = updated_device_profile })) test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) + if expected_supported_values_setters ~= nil then + expected_supported_values_setters() + end end local expected_metadata_all = { @@ -276,7 +278,20 @@ local expected_metadata_all = { test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - all clusters", function() - test_aqs_device_type_update_modular_profile(mock_device, expected_metadata_all, subscribe_request_all) + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + end + test_aqs_device_type_update_modular_profile(mock_device, expected_metadata_all, subscribe_request_all, expected_supported_values_setters) end ) @@ -299,7 +314,10 @@ local expected_metadata_common = { test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - common clusters", function() - test_aqs_device_type_update_modular_profile(mock_device_common, expected_metadata_common, subscribe_request_common) + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device_common:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) + end + test_aqs_device_type_update_modular_profile(mock_device_common, expected_metadata_common, subscribe_request_common, expected_supported_values_setters) end, { test_init = test_init_common } ) From b6fad717778c650d4059661c18c57b59871f11c2 Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 26 Nov 2025 10:07:49 -0600 Subject: [PATCH 2/4] remove minor changes --- .../air_quality_sensor_utils/device_configuration.lua | 2 +- .../legacy_device_configuration.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua index d646cb3c0b..a30eaed0f8 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/device_configuration.lua @@ -16,7 +16,7 @@ function DeviceConfiguration.supported_level_measurements(device) local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] -- capability describes either a HealthConcern or Measurement/Sensor if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) or {} + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) if #attr_eps > 0 then table.insert(level_caps, cap_id) end diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua index 7b6ef483da..5722b5f831 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua @@ -17,7 +17,7 @@ function LegacyDeviceConfiguration.create_level_measurement_profile(device) local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] -- capability describes either a HealthConcern or Measurement/Sensor if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) or {} + local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) if #attr_eps > 0 then level_name = level_name .. fields.CONCENTRATION_MEASUREMENT_MAP[cap][1] aqs_utils.set_supported_health_concern_values_helper(device, fields.CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) @@ -38,8 +38,8 @@ function LegacyDeviceConfiguration.match_profile(device) local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) local profile_name = "aqs" - local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} - aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) + local aq_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) + aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aq_eps[1]) if #temp_eps > 0 then profile_name = profile_name .. "-temp" From 994d6f81926776fbc9c1c79dd422a76ca3e2ffdc Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 26 Nov 2025 10:14:03 -0600 Subject: [PATCH 3/4] move attribute handlers to unique file --- .../attribute_handlers.lua | 90 +++++++++++ .../sub_drivers/air_quality_sensor/init.lua | 142 ++++-------------- 2 files changed, 123 insertions(+), 109 deletions(-) create mode 100644 drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua new file mode 100644 index 0000000000..0d908a175c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_handlers/attribute_handlers.lua @@ -0,0 +1,90 @@ +-- Copyright © 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local st_utils = require "st.utils" +local capabilities = require "st.capabilities" +local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" + +local AirQualityServerAttributeHandlers = {} + + +-- [[ GENERIC CONCENTRATION MEASUREMENT CLUSTER ATTRIBUTES ]] + +function AirQualityServerAttributeHandlers.measurement_unit_factory(capability_name) + return function(driver, device, ib, response) + device:set_field(capability_name.."_unit", ib.data.value, {persist = true}) + end +end + +function AirQualityServerAttributeHandlers.level_value_factory(attribute) + return function(driver, device, ib, response) + device:emit_event_for_endpoint(ib.endpoint_id, attribute(fields.level_strings[ib.data.value])) + end +end + +local function unit_conversion(device, value, from_unit, to_unit) + local conversion_function = fields.conversion_tables[from_unit][to_unit] + if conversion_function == nil then + device.log.info_with( {hub_logs = true} , string.format("Unsupported unit conversion from %s to %s", fields.unit_strings[from_unit], fields.unit_strings[to_unit])) + return 1 + end + + if value == nil then + device.log.info_with( {hub_logs = true} , "unit conversion value is nil") + return 1 + end + return conversion_function(value) +end + +function AirQualityServerAttributeHandlers.measured_value_factory(capability_name, attribute, target_unit) + return function(driver, device, ib, response) + local reporting_unit = device:get_field(capability_name.."_unit") + + if reporting_unit == nil then + reporting_unit = fields.unit_default[capability_name] + device:set_field(capability_name.."_unit", reporting_unit, {persist = true}) + end + + if reporting_unit then + local value = unit_conversion(device, ib.data.value, reporting_unit, target_unit) + device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = value, unit = fields.unit_strings[target_unit]})) + + -- handle case where device profile supports both fineDustLevel and dustLevel + if capability_name == capabilities.fineDustSensor.NAME and device:supports_capability(capabilities.dustSensor) then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = value, unit = fields.unit_strings[target_unit]})) + end + end + end +end + + +-- [[ AIR QUALITY CLUSTER ATTRIBUTES ]] -- + +function AirQualityServerAttributeHandlers.air_quality_handler(driver, device, ib, response) + local state = ib.data.value + if state == 0 then -- Unknown + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unknown()) + elseif state == 1 then -- Good + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.good()) + elseif state == 2 then -- Fair + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.moderate()) + elseif state == 3 then -- Moderate + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.slightlyUnhealthy()) + elseif state == 4 then -- Poor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unhealthy()) + elseif state == 5 then -- VeryPoor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.veryUnhealthy()) + elseif state == 6 then -- ExtremelyPoor + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) + end +end + + +-- [[ PRESSURE MEASUREMENT CLUSTER ATTRIBUTES ]] -- + +function AirQualityServerAttributeHandlers.pressure_measured_value_handler(driver, device, ib, response) + local pressure = st_utils.round(ib.data.value / 10.0) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.atmosphericPressureMeasurement.atmosphericPressure(pressure)) +end + +return AirQualityServerAttributeHandlers diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua index 0d640208a9..895c496d21 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua @@ -1,14 +1,13 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local log = require "log" local version = require "version" -local st_utils = require "st.utils" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" +local attribute_handlers = require "sub_drivers.air_quality_sensor.air_quality_sensor_handlers.attribute_handlers" -- Include driver-side definitions when lua libs api version is < 10 if version.api < 10 then @@ -87,81 +86,6 @@ function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, a end end --- ATTRIBUTE HANDLERS -- - -local sub_driver_handlers = {} - -function sub_driver_handlers.measurement_unit_factory(capability_name) - return function(driver, device, ib, response) - device:set_field(capability_name.."_unit", ib.data.value, {persist = true}) - end -end - -local function unit_conversion(value, from_unit, to_unit) - local conversion_function = fields.conversion_tables[from_unit][to_unit] - if conversion_function == nil then - log.info_with( {hub_logs = true} , string.format("Unsupported unit conversion from %s to %s", fields.unit_strings[from_unit], fields.unit_strings[to_unit])) - return 1 - end - - if value == nil then - log.info_with( {hub_logs = true} , "unit conversion value is nil") - return 1 - end - return conversion_function(value) -end - -function sub_driver_handlers.measured_value_factory(capability_name, attribute, target_unit) - return function(driver, device, ib, response) - local reporting_unit = device:get_field(capability_name.."_unit") - - if reporting_unit == nil then - reporting_unit = fields.unit_default[capability_name] - device:set_field(capability_name.."_unit", reporting_unit, {persist = true}) - end - - if reporting_unit then - local value = unit_conversion(ib.data.value, reporting_unit, target_unit) - device:emit_event_for_endpoint(ib.endpoint_id, attribute({value = value, unit = fields.unit_strings[target_unit]})) - - -- handle case where device profile supports both fineDustLevel and dustLevel - if capability_name == capabilities.fineDustSensor.NAME and device:supports_capability(capabilities.dustSensor) then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.dustSensor.fineDustLevel({value = value, unit = fields.unit_strings[target_unit]})) - end - end - end -end - -function sub_driver_handlers.level_value_factory(attribute) - return function(driver, device, ib, response) - device:emit_event_for_endpoint(ib.endpoint_id, attribute(fields.level_strings[ib.data.value])) - end -end - -function sub_driver_handlers.air_quality_handler(driver, device, ib, response) - local state = ib.data.value - if state == 0 then -- Unknown - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unknown()) - elseif state == 1 then -- Good - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.good()) - elseif state == 2 then -- Fair - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.moderate()) - elseif state == 3 then -- Moderate - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.slightlyUnhealthy()) - elseif state == 4 then -- Poor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.unhealthy()) - elseif state == 5 then -- VeryPoor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.veryUnhealthy()) - elseif state == 6 then -- ExtremelyPoor - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.airQualityHealthConcern.airQualityHealthConcern.hazardous()) - end -end - -function sub_driver_handlers.pressure_measured_value_handler(driver, device, ib, response) - local pressure = st_utils.round(ib.data.value / 10.0) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.atmosphericPressureMeasurement.atmosphericPressure(pressure)) -end - -- SUBDRIVER TEMPLATE -- @@ -176,60 +100,60 @@ local matter_air_quality_sensor_handler = { matter_handlers = { attr = { [clusters.AirQuality.ID] = { - [clusters.AirQuality.attributes.AirQuality.ID] = sub_driver_handlers.air_quality_handler, + [clusters.AirQuality.attributes.AirQuality.ID] = attribute_handlers.air_quality_handler, }, [clusters.CarbonDioxideConcentrationMeasurement.ID] = { - [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.carbonDioxideMeasurement.NAME, capabilities.carbonDioxideMeasurement.carbonDioxide, fields.units.PPM), - [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.carbonDioxideMeasurement.NAME), - [clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.carbonDioxideMeasurement.NAME, capabilities.carbonDioxideMeasurement.carbonDioxide, fields.units.PPM), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.carbonDioxideMeasurement.NAME), + [clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.carbonDioxideHealthConcern.carbonDioxideHealthConcern), }, [clusters.CarbonMonoxideConcentrationMeasurement.ID] = { - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.carbonMonoxideMeasurement.NAME, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel, fields.units.PPM), - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.carbonMonoxideMeasurement.NAME), - [clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.carbonMonoxideHealthConcern.carbonMonoxideHealthConcern), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.carbonMonoxideMeasurement.NAME, capabilities.carbonMonoxideMeasurement.carbonMonoxideLevel, fields.units.PPM), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.carbonMonoxideMeasurement.NAME), + [clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.carbonMonoxideHealthConcern.carbonMonoxideHealthConcern), }, [clusters.FormaldehydeConcentrationMeasurement.ID] = { - [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.formaldehydeMeasurement.NAME, capabilities.formaldehydeMeasurement.formaldehydeLevel, fields.units.PPM), - [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.formaldehydeMeasurement.NAME), - [clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.formaldehydeHealthConcern.formaldehydeHealthConcern), + [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.formaldehydeMeasurement.NAME, capabilities.formaldehydeMeasurement.formaldehydeLevel, fields.units.PPM), + [clusters.FormaldehydeConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.formaldehydeMeasurement.NAME), + [clusters.FormaldehydeConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.formaldehydeHealthConcern.formaldehydeHealthConcern), }, [clusters.NitrogenDioxideConcentrationMeasurement.ID] = { - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.nitrogenDioxideMeasurement.NAME, capabilities.nitrogenDioxideMeasurement.nitrogenDioxide, fields.units.PPM), - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.nitrogenDioxideMeasurement.NAME), - [clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.nitrogenDioxideHealthConcern.nitrogenDioxideHealthConcern) + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.nitrogenDioxideMeasurement.NAME, capabilities.nitrogenDioxideMeasurement.nitrogenDioxide, fields.units.PPM), + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.nitrogenDioxideMeasurement.NAME), + [clusters.NitrogenDioxideConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.nitrogenDioxideHealthConcern.nitrogenDioxideHealthConcern) }, [clusters.OzoneConcentrationMeasurement.ID] = { - [clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.ozoneMeasurement.NAME, capabilities.ozoneMeasurement.ozone, fields.units.PPM), - [clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.ozoneMeasurement.NAME), - [clusters.OzoneConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.ozoneHealthConcern.ozoneHealthConcern) + [clusters.OzoneConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.ozoneMeasurement.NAME, capabilities.ozoneMeasurement.ozone, fields.units.PPM), + [clusters.OzoneConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.ozoneMeasurement.NAME), + [clusters.OzoneConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.ozoneHealthConcern.ozoneHealthConcern) }, [clusters.Pm1ConcentrationMeasurement.ID] = { - [clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.veryFineDustSensor.NAME, capabilities.veryFineDustSensor.veryFineDustLevel, fields.units.UGM3), - [clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.veryFineDustSensor.NAME), - [clusters.Pm1ConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern), + [clusters.Pm1ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.veryFineDustSensor.NAME, capabilities.veryFineDustSensor.veryFineDustLevel, fields.units.UGM3), + [clusters.Pm1ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.veryFineDustSensor.NAME), + [clusters.Pm1ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.veryFineDustHealthConcern.veryFineDustHealthConcern), }, [clusters.Pm10ConcentrationMeasurement.ID] = { - [clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.dustSensor.NAME, capabilities.dustSensor.dustLevel, fields.units.UGM3), - [clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.dustSensor.NAME), - [clusters.Pm10ConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.dustHealthConcern.dustHealthConcern), + [clusters.Pm10ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.dustSensor.NAME, capabilities.dustSensor.dustLevel, fields.units.UGM3), + [clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.dustSensor.NAME), + [clusters.Pm10ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.dustHealthConcern.dustHealthConcern), }, [clusters.Pm25ConcentrationMeasurement.ID] = { - [clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.fineDustSensor.NAME, capabilities.fineDustSensor.fineDustLevel, fields.units.UGM3), - [clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.fineDustSensor.NAME), - [clusters.Pm25ConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.fineDustHealthConcern.fineDustHealthConcern), + [clusters.Pm25ConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.fineDustSensor.NAME, capabilities.fineDustSensor.fineDustLevel, fields.units.UGM3), + [clusters.Pm25ConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.fineDustSensor.NAME), + [clusters.Pm25ConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.fineDustHealthConcern.fineDustHealthConcern), }, [clusters.PressureMeasurement.ID] = { - [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.pressure_measured_value_handler + [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.pressure_measured_value_handler }, [clusters.RadonConcentrationMeasurement.ID] = { - [clusters.RadonConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.radonMeasurement.NAME, capabilities.radonMeasurement.radonLevel, fields.units.PCIL), - [clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.radonMeasurement.NAME), - [clusters.RadonConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.radonHealthConcern.radonHealthConcern) + [clusters.RadonConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.radonMeasurement.NAME, capabilities.radonMeasurement.radonLevel, fields.units.PCIL), + [clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.radonMeasurement.NAME), + [clusters.RadonConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.radonHealthConcern.radonHealthConcern) }, [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.ID] = { - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = sub_driver_handlers.measured_value_factory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, fields.units.PPB), - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = sub_driver_handlers.measurement_unit_factory(capabilities.tvocMeasurement.NAME), - [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = sub_driver_handlers.level_value_factory(capabilities.tvocHealthConcern.tvocHealthConcern) + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasuredValue.ID] = attribute_handlers.measured_value_factory(capabilities.tvocMeasurement.NAME, capabilities.tvocMeasurement.tvocLevel, fields.units.PPB), + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit.ID] = attribute_handlers.measurement_unit_factory(capabilities.tvocMeasurement.NAME), + [clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue.ID] = attribute_handlers.level_value_factory(capabilities.tvocHealthConcern.tvocHealthConcern) } } }, From 171fd57814a2319e0326930b999cb799055b8e7a Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Wed, 26 Nov 2025 15:01:54 -0600 Subject: [PATCH 4/4] move setter out of configure, into init --- .../legacy_device_configuration.lua | 5 - .../air_quality_sensor_utils/utils.lua | 76 ++++--- .../sub_drivers/air_quality_sensor/init.lua | 14 +- .../test/test_matter_air_quality_sensor.lua | 101 ++++----- ...test_matter_air_quality_sensor_modular.lua | 207 ++++++++++-------- 5 files changed, 199 insertions(+), 204 deletions(-) diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua index 5722b5f831..bc45021341 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/legacy_device_configuration.lua @@ -1,12 +1,10 @@ -- Copyright © 2025 SmartThings, Inc. -- Licensed under the Apache License, Version 2.0 -local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" local sensor_utils = require "sensor_utils.utils" local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" -local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" local LegacyDeviceConfiguration = {} @@ -20,7 +18,6 @@ function LegacyDeviceConfiguration.create_level_measurement_profile(device) local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) if #attr_eps > 0 then level_name = level_name .. fields.CONCENTRATION_MEASUREMENT_MAP[cap][1] - aqs_utils.set_supported_health_concern_values_helper(device, fields.CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) end elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT }) @@ -38,8 +35,6 @@ function LegacyDeviceConfiguration.match_profile(device) local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID) local profile_name = "aqs" - local aq_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) - aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aq_eps[1]) if #temp_eps > 0 then profile_name = profile_name .. "-temp" diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua index c979b4784a..4d166fc882 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/air_quality_sensor_utils/utils.lua @@ -40,47 +40,51 @@ function AirQualitySensorUtils.supports_capability_by_id_modular(device, capabil return false end -function AirQualitySensorUtils.set_supported_health_concern_values_helper(device, setter_function, cluster, cluster_ep) - -- read_datatype_value works since all the healthConcern capabilities' datatypes are equivalent to the one in airQualityHealthConcern - local read_datatype_value = capabilities.airQualityHealthConcern.airQualityHealthConcern - local supported_values = {read_datatype_value.unknown.NAME, read_datatype_value.good.NAME, read_datatype_value.unhealthy.NAME} - if cluster == clusters.AirQuality then - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.FAIR }) > 0 then - table.insert(supported_values, 3, read_datatype_value.moderate.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MODERATE }) > 0 then - table.insert(supported_values, 4, read_datatype_value.slightlyUnhealthy.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.VERY_POOR }) > 0 then - table.insert(supported_values, read_datatype_value.veryUnhealthy.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.EXTREMELY_POOR }) > 0 then - table.insert(supported_values, read_datatype_value.hazardous.NAME) - end - else -- ConcentrationMeasurement clusters - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MEDIUM_LEVEL }) > 0 then - table.insert(supported_values, 3, read_datatype_value.moderate.NAME) - end - if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.CRITICAL_LEVEL }) > 0 then - table.insert(supported_values, read_datatype_value.hazardous.NAME) - end +local function get_supported_health_concern_values_for_air_quality(device) + local health_concern_datatype = capabilities.airQualityHealthConcern.airQualityHealthConcern + local supported_values = {health_concern_datatype.unknown.NAME, health_concern_datatype.good.NAME, health_concern_datatype.unhealthy.NAME} + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.FAIR }) > 0 then + table.insert(supported_values, 3, health_concern_datatype.moderate.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.MODERATE }) > 0 then + table.insert(supported_values, 4, health_concern_datatype.slightlyUnhealthy.NAME) end - device:emit_event_for_endpoint(cluster_ep, setter_function(supported_values, { visibility = { displayed = false }})) + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.VERY_POOR }) > 0 then + table.insert(supported_values, health_concern_datatype.veryUnhealthy.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID, { feature_bitmap = clusters.AirQuality.types.Feature.EXTREMELY_POOR }) > 0 then + table.insert(supported_values, health_concern_datatype.hazardous.NAME) + end + return supported_values +end + +local function get_supported_health_concern_values_for_concentration_cluster(device, cluster) + -- note: health_concern_datatype is generic since all the healthConcern capabilities' datatypes are equivalent to those in airQualityHealthConcern + local health_concern_datatype = capabilities.airQualityHealthConcern.airQualityHealthConcern + local supported_values = {health_concern_datatype.unknown.NAME, health_concern_datatype.good.NAME, health_concern_datatype.unhealthy.NAME} + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.MEDIUM_LEVEL }) > 0 then + table.insert(supported_values, 3, health_concern_datatype.moderate.NAME) + end + if #embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.CRITICAL_LEVEL }) > 0 then + table.insert(supported_values, health_concern_datatype.hazardous.NAME) + end + return supported_values end function AirQualitySensorUtils.set_supported_health_concern_values(device) - local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} - AirQualitySensorUtils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) + -- handle AQ Health Concern, since this is a mandatory capability + local supported_aqs_values = get_supported_health_concern_values_for_air_quality(device) + local aqs_ep_ids = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} + device:emit_event_for_endpoint(aqs_ep_ids[1], capabilities.airQualityHealthConcern.supportedAirQualityValues(supported_aqs_values, { visibility = { displayed = false }})) - for _, cap in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do - local cap_id = cap.ID - local cluster = fields.CONCENTRATION_MEASUREMENT_MAP[cap][2] - -- capability describes either a HealthConcern or Measurement/Sensor - if (cap_id:match("HealthConcern$")) then - local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION }) or {} - if #attr_eps > 0 then - AirQualitySensorUtils.set_supported_health_concern_values_helper(device, fields.CONCENTRATION_MEASUREMENT_MAP[cap][3], cluster, attr_eps[1]) - end + for _, capability in ipairs(fields.CONCENTRATION_MEASUREMENT_PROFILE_ORDERING) do + -- all of these capabilities are optional, and capabilities stored in this field are for either a HealthConcern or a Measurement/Sensor + if device:supports_capability_by_id(capability.ID) and capability.ID:match("HealthConcern$") then + local cluster_info = fields.CONCENTRATION_MEASUREMENT_MAP[capability][2] + local supported_values_setter = fields.CONCENTRATION_MEASUREMENT_MAP[capability][3] + local supported_values = get_supported_health_concern_values_for_concentration_cluster(device, cluster_info) + local cluster_ep_ids = embedded_cluster_utils.get_endpoints(device, cluster_info.ID, { feature_bitmap = cluster_info.types.Feature.LEVEL_INDICATION }) or {} -- cluster associated with the supported capability + device:emit_event_for_endpoint(cluster_ep_ids[1], supported_values_setter(supported_values, { visibility = { displayed = false }})) end end end diff --git a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua index 895c496d21..869f2d337b 100644 --- a/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua +++ b/drivers/SmartThings/matter-sensor/src/sub_drivers/air_quality_sensor/init.lua @@ -4,7 +4,6 @@ local version = require "version" local capabilities = require "st.capabilities" local clusters = require "st.matter.clusters" -local embedded_cluster_utils = require "sensor_utils.embedded_cluster_utils" local aqs_utils = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.utils" local fields = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.fields" local attribute_handlers = require "sub_drivers.air_quality_sensor.air_quality_sensor_handlers.attribute_handlers" @@ -33,11 +32,6 @@ function AirQualitySensorLifecycleHandlers.do_configure(driver, device) for _, cluster in ipairs(fields.units_required) do device:send(cluster.attributes.MeasurementUnit:read(device)) end - -- If a device only supports the airQualityHealthConcern capability, no profile update will end up occurring, - -- so this logic will never end up being run in the ensuing infoChanged event. This catches that edge case - local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} - aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) - if version.api >= 14 and version.rpc >= 8 then local modular_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.device_configuration" modular_device_cfg.match_profile(device) @@ -52,11 +46,6 @@ function AirQualitySensorLifecycleHandlers.driver_switched(driver, device) for _, cluster in ipairs(fields.units_required) do device:send(cluster.attributes.MeasurementUnit:read(device)) end - -- If a device only supports the airQualityHealthConcern capability, no profile update will end up occurring, - -- so this logic will never end up being run in the ensuing infoChanged event. This catches that edge case - local aqs_eps = embedded_cluster_utils.get_endpoints(device, clusters.AirQuality.ID) or {} - aqs_utils.set_supported_health_concern_values_helper(device, capabilities.airQualityHealthConcern.supportedAirQualityValues, clusters.AirQuality, aqs_eps[1]) - if version.api >= 14 and version.rpc >= 8 then local modular_device_cfg = require "sub_drivers.air_quality_sensor.air_quality_sensor_utils.device_configuration" modular_device_cfg.match_profile(device) @@ -72,6 +61,7 @@ function AirQualitySensorLifecycleHandlers.device_init(driver, device) -- library function to utilize optional capabilities device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) end + aqs_utils.set_supported_health_concern_values(device) device:subscribe() end @@ -81,8 +71,8 @@ function AirQualitySensorLifecycleHandlers.info_changed(driver, device, event, a --re-up subscription with new capabilities using the modular supports_capability override device:extend_device("supports_capability_by_id", aqs_utils.supports_capability_by_id_modular) end - device:subscribe() aqs_utils.set_supported_health_concern_values(device) + device:subscribe() end end diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua index fd3a5c139b..c3baf0ea1c 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor.lua @@ -140,7 +140,7 @@ local mock_device_level = test.mock_device.build_test_matter_device({ }) local mock_device_co = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -169,7 +169,7 @@ local mock_device_co = test.mock_device.build_test_matter_device({ }) local mock_device_co2 = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -229,7 +229,12 @@ local mock_device_tvoc = test.mock_device.build_test_matter_device({ }) -- create test_init functions -local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes) +local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes, expected_supported_values_setters) + test.mock_device.add_test_device(generic_mock_device) + test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) + if expected_supported_values_setters ~= nil then + expected_supported_values_setters() + end local subscribe_request = nil for _, attributes in pairs(generic_subscribed_attributes) do for _, attribute in ipairs(attributes) do @@ -241,7 +246,6 @@ local function initialize_mock_device(generic_mock_device, generic_subscribed_at end end test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) - test.mock_device.add_test_device(generic_mock_device) end -- TODO add tests for configuration using modular profiles @@ -329,7 +333,19 @@ local function test_init() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, }, } - initialize_mock_device(mock_device, subscribed_attributes) + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + end + initialize_mock_device(mock_device, subscribed_attributes, expected_supported_values_setters) end test.set_test_init_function(test_init) @@ -406,7 +422,19 @@ local function test_init_level() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, } } - initialize_mock_device(mock_device_level, subscribed_attributes) + local expected_supported_values_setters = function() + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + end + initialize_mock_device(mock_device_level, subscribed_attributes, expected_supported_values_setters) end local function test_init_tvoc() @@ -435,26 +463,12 @@ local function test_init_co_co2() [capabilities.airQualityHealthConcern.ID] = { clusters.AirQuality.attributes.AirQuality }, - [capabilities.carbonMonoxideMeasurement.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonMonoxideHealthConcern.ID] = { - clusters.CarbonMonoxideConcentrationMeasurement.attributes.LevelValue, - }, } local attr_co2 = { [capabilities.airQualityHealthConcern.ID] = { clusters.AirQuality.attributes.AirQuality - }, - [capabilities.carbonDioxideMeasurement.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasuredValue, - clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit, - }, - [capabilities.carbonDioxideHealthConcern.ID] = { - clusters.CarbonDioxideConcentrationMeasurement.attributes.LevelValue, - }, + } } initialize_mock_device(mock_device_co, attr_co) @@ -463,7 +477,7 @@ end -- run the profile configuration tests -local function test_aqs_device_type_do_configure(generic_mock_device, expected_profile, expected_supported_values_setters) +local function test_aqs_device_type_do_configure(generic_mock_device, expected_profile) test.socket.device_lifecycle:__queue_receive({generic_mock_device.id, "doConfigure"}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonMonoxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.CarbonDioxideConcentrationMeasurement.attributes.MeasurementUnit:read()}) @@ -475,11 +489,6 @@ local function test_aqs_device_type_do_configure(generic_mock_device, expected_p test.socket.matter:__expect_send({generic_mock_device.id, clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) - test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) - if expected_supported_values_setters ~= nil then - expected_supported_values_setters() - end generic_mock_device:expect_metadata_update({ profile = expected_profile }) generic_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) end @@ -487,19 +496,7 @@ end test.register_coroutine_test( "Configure should read units from device and profile change as needed", function() - local expected_supported_values_setters = function() - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas", expected_supported_values_setters) + test_aqs_device_type_do_configure(mock_device, "aqs-temp-humidity-all-level-all-meas") end ) @@ -514,19 +511,7 @@ test.register_coroutine_test( test.register_coroutine_test( "Configure should read units from device and profile change as needed", function() - local expected_supported_values_setters = function() - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device_level:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - test_aqs_device_type_do_configure(mock_device_level, "aqs-temp-humidity-all-level", expected_supported_values_setters) + test_aqs_device_type_do_configure(mock_device_level, "aqs-temp-humidity-all-level") end, { test_init = test_init_level } ) @@ -534,14 +519,8 @@ test.register_coroutine_test( test.register_coroutine_test( "Configure should not catch co2, only co in the first check", function() - local expected_supported_co_values = function() - test.socket.capability:__expect_send(mock_device_co:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - local expected_supported_co2_values = function() - test.socket.capability:__expect_send(mock_device_co2:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - end - test_aqs_device_type_do_configure(mock_device_co, "aqs-temp-humidity-all-meas", expected_supported_co_values) - test_aqs_device_type_do_configure(mock_device_co2, "aqs-temp-humidity-co2-pm25-tvoc-meas", expected_supported_co2_values) + test_aqs_device_type_do_configure(mock_device_co, "aqs-temp-humidity-all-meas") + test_aqs_device_type_do_configure(mock_device_co2, "aqs-temp-humidity-co2-pm25-tvoc-meas") end, { test_init = test_init_co_co2 } ) diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua index 93e6de54f0..bdf55023e0 100644 --- a/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_air_quality_sensor_modular.lua @@ -7,10 +7,10 @@ local t_utils = require "integration_test.utils" local clusters = require "st.matter.clusters" -test.set_rpc_version(8) +test.disable_startup_messages() -local mock_device = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-all-level-all-meas.yml"), +local mock_device_all = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -50,7 +50,7 @@ local mock_device = test.mock_device.build_test_matter_device({ }) local mock_device_common = test.mock_device.build_test_matter_device({ - profile = t_utils.get_profile_definition("aqs-temp-humidity-co2-pm25-tvoc-meas.yml"), + profile = t_utils.get_profile_definition("aqs.yml"), manufacturer_info = { vendor_id = 0x0000, product_id = 0x0000, @@ -82,25 +82,34 @@ local mock_device_common = test.mock_device.build_test_matter_device({ } }) --- create test_init functions -local function initialize_mock_device(generic_mock_device, generic_subscribed_attributes) - local subscribe_request = nil - for _, attributes in pairs(generic_subscribed_attributes) do - for _, attribute in ipairs(attributes) do - if subscribe_request == nil then - subscribe_request = attribute:subscribe(generic_mock_device) - else - subscribe_request:merge(attribute:subscribe(generic_mock_device)) - end - end - end - test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) - test.mock_device.add_test_device(generic_mock_device) - return subscribe_request +local function test_init_all() + test.mock_device.add_test_device(mock_device_all) + test.socket.device_lifecycle:__queue_receive({ mock_device_all.id, "init" }) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", + capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, + {visibility={displayed=false}})) + ) + -- on device create, a generic AQS device will be profiled as aqs, thus only subscribing to one attribute + local subscribe_request = clusters.AirQuality.attributes.AirQuality:subscribe(mock_device_all) + test.socket.matter:__expect_send({mock_device_all.id, subscribe_request}) +end + +local function test_init_common() + test.mock_device.add_test_device(mock_device_common) + test.socket.device_lifecycle:__queue_receive({ mock_device_common.id, "added" }) + test.socket.device_lifecycle:__queue_receive({ mock_device_common.id, "init" }) + test.socket.capability:__expect_send(mock_device_common:generate_test_message("main", + capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, + {visibility={displayed=false}})) + ) + -- on device create, a generic AQS device will be profiled as aqs, thus only subscribing to one attribute + local subscribe_request = clusters.AirQuality.attributes.AirQuality:subscribe(mock_device_common) + test.socket.matter:__expect_send({mock_device_common.id, subscribe_request}) end -local subscribe_request_all -local function test_init() +test.set_test_init_function(test_init_all) + +local function get_subscribe_request_all() local subscribed_attributes = { [capabilities.relativeHumidityMeasurement.ID] = { clusters.RelativeHumidityMeasurement.attributes.MeasuredValue @@ -182,11 +191,20 @@ local function test_init() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.LevelValue, }, } - subscribe_request_all = initialize_mock_device(mock_device, subscribed_attributes) + local subscribe_request = nil + for _, attributes in pairs(subscribed_attributes) do + for _, attribute in pairs(attributes) do + if subscribe_request == nil then + subscribe_request = attribute:subscribe(mock_device_all) + else + subscribe_request:merge(attribute:subscribe(mock_device_all)) + end + end + end + return subscribe_request end -local subscribe_request_common -local function test_init_common() +local function get_subscribe_request_common() local subscribed_attributes = { [capabilities.relativeHumidityMeasurement.ID] = { clusters.RelativeHumidityMeasurement.attributes.MeasuredValue @@ -212,9 +230,18 @@ local function test_init_common() clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit, }, } - subscribe_request_common = initialize_mock_device(mock_device_common, subscribed_attributes) + local subscribe_request = nil + for _, attributes in pairs(subscribed_attributes) do + for _, attribute in pairs(attributes) do + if subscribe_request == nil then + subscribe_request = attribute:subscribe(mock_device_common) + else + subscribe_request:merge(attribute:subscribe(mock_device_common)) + end + end + end + return subscribe_request end -test.set_test_init_function(test_init) -- run the profile configuration tests local function test_aqs_device_type_update_modular_profile(generic_mock_device, expected_metadata, subscribe_request, expected_supported_values_setters) @@ -229,94 +256,94 @@ local function test_aqs_device_type_update_modular_profile(generic_mock_device, test.socket.matter:__expect_send({generic_mock_device.id, clusters.Pm10ConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.RadonConcentrationMeasurement.attributes.MeasurementUnit:read()}) test.socket.matter:__expect_send({generic_mock_device.id, clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement.attributes.MeasurementUnit:read()}) - test.socket.capability:__expect_send(generic_mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) generic_mock_device:expect_metadata_update(expected_metadata) generic_mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) local updated_device_profile = t_utils.get_profile_definition("aqs-modular.yml", {enabled_optional_capabilities = expected_metadata.optional_component_capabilities} ) test.socket.device_lifecycle:__queue_receive(generic_mock_device:generate_info_changed({ profile = updated_device_profile })) - test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) if expected_supported_values_setters ~= nil then expected_supported_values_setters() end + test.socket.matter:__expect_send({generic_mock_device.id, subscribe_request}) end -local expected_metadata_all = { - optional_component_capabilities={ - { - "main", - { - "temperatureMeasurement", - "relativeHumidityMeasurement", - "carbonMonoxideMeasurement", - "carbonDioxideMeasurement", - "nitrogenDioxideMeasurement", - "ozoneMeasurement", - "formaldehydeMeasurement", - "veryFineDustSensor", - "fineDustSensor", - "dustSensor", - "radonMeasurement", - "tvocMeasurement", - "carbonMonoxideHealthConcern", - "carbonDioxideHealthConcern", - "nitrogenDioxideHealthConcern", - "ozoneHealthConcern", - "formaldehydeHealthConcern", - "veryFineDustHealthConcern", - "fineDustHealthConcern", - "dustHealthConcern", - "radonHealthConcern", - "tvocHealthConcern", - }, - }, - }, - profile="aqs-modular-temp-humidity", -} - test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - all clusters", function() + local expected_metadata_all = { + optional_component_capabilities={ + { + "main", + { + "temperatureMeasurement", + "relativeHumidityMeasurement", + "carbonMonoxideMeasurement", + "carbonDioxideMeasurement", + "nitrogenDioxideMeasurement", + "ozoneMeasurement", + "formaldehydeMeasurement", + "veryFineDustSensor", + "fineDustSensor", + "dustSensor", + "radonMeasurement", + "tvocMeasurement", + "carbonMonoxideHealthConcern", + "carbonDioxideHealthConcern", + "nitrogenDioxideHealthConcern", + "ozoneHealthConcern", + "formaldehydeHealthConcern", + "veryFineDustHealthConcern", + "fineDustHealthConcern", + "dustHealthConcern", + "radonHealthConcern", + "tvocHealthConcern", + }, + }, + }, + profile="aqs-modular-temp-humidity", + } local expected_supported_values_setters = function() - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.carbonMonoxideHealthConcern.supportedCarbonMonoxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.carbonDioxideHealthConcern.supportedCarbonDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.nitrogenDioxideHealthConcern.supportedNitrogenDioxideValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.ozoneHealthConcern.supportedOzoneValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.formaldehydeHealthConcern.supportedFormaldehydeValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.veryFineDustHealthConcern.supportedVeryFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.fineDustHealthConcern.supportedFineDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.dustHealthConcern.supportedDustValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.radonHealthConcern.supportedRadonValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) + test.socket.capability:__expect_send(mock_device_all:generate_test_message("main", capabilities.tvocHealthConcern.supportedTvocValues({"unknown", "good", "unhealthy"}, {visibility={displayed=false}}))) end - test_aqs_device_type_update_modular_profile(mock_device, expected_metadata_all, subscribe_request_all, expected_supported_values_setters) - end + local subscribe_request_all = get_subscribe_request_all() + test_aqs_device_type_update_modular_profile(mock_device_all, expected_metadata_all, subscribe_request_all, expected_supported_values_setters) + end, + { test_init = test_init_all } ) -local expected_metadata_common = { - optional_component_capabilities={ - { - "main", - { - "temperatureMeasurement", - "relativeHumidityMeasurement", - "carbonDioxideMeasurement", - "fineDustSensor", - "tvocMeasurement", - }, - }, - }, - profile="aqs-modular-temp-humidity", -} - test.register_coroutine_test( "Device with modular profile should enable correct optional capabilities - common clusters", function() + local expected_metadata_common = { + optional_component_capabilities={ + { + "main", + { + "temperatureMeasurement", + "relativeHumidityMeasurement", + "carbonDioxideMeasurement", + "fineDustSensor", + "tvocMeasurement", + }, + }, + }, + profile="aqs-modular-temp-humidity", + } local expected_supported_values_setters = function() test.socket.capability:__expect_send(mock_device_common:generate_test_message("main", capabilities.airQualityHealthConcern.supportedAirQualityValues({"unknown", "good", "moderate", "slightlyUnhealthy", "unhealthy"}, {visibility={displayed=false}}))) end + local subscribe_request_common = get_subscribe_request_common() test_aqs_device_type_update_modular_profile(mock_device_common, expected_metadata_common, subscribe_request_common, expected_supported_values_setters) end, { test_init = test_init_common }