Skip to content

Commit 91e95e5

Browse files
committed
Matter Sensor: Add modular profile supports for AQS
1 parent 0d97bf5 commit 91e95e5

File tree

2 files changed

+159
-10
lines changed

2 files changed

+159
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: aqs-modular
2+
components:
3+
- id: main
4+
capabilities:
5+
- id: airQualityHealthConcern
6+
- id: temperatureMeasurement
7+
optional: true
8+
- id: relativeHumidityMeasurement
9+
optional: true
10+
- id: carbonMonoxideMeasurement
11+
optional: true
12+
- id: carbonMonoxideHealthConcern
13+
optional: true
14+
- id: carbonDioxideMeasurement
15+
optional: true
16+
- id: carbonDioxideHealthConcern
17+
optional: true
18+
- id: nitrogenDioxideMeasurement
19+
optional: true
20+
- id: ozoneMeasurement
21+
optional: true
22+
- id: formaldehydeMeasurement
23+
optional: true
24+
- id: formaldehydeHealthConcern
25+
optional: true
26+
- id: veryFineDustSensor
27+
optional: true
28+
- id: veryFineDustHealthConcern
29+
optional: true
30+
- id: fineDustHealthConcern
31+
optional: true
32+
- id: dustSensor
33+
optional: true
34+
- id: dustHealthConcern
35+
optional: true
36+
- id: radonMeasurement
37+
optional: true
38+
- id: tvocMeasurement
39+
optional: true
40+
- id: firmwareUpdate
41+
- id: refresh
42+
- id: nitrogenDioxideHealthConcern
43+
optional: true
44+
- id: ozoneHealthConcern
45+
optional: true
46+
- id: radonHealthConcern
47+
optional: true
48+
- id: tvocHealthConcern
49+
optional: true
50+
- id: fineDustSensor
51+
optional: true
52+
categories:
53+
- name: AirQualityDetector
54+
preferences:
55+
- preferenceId: tempOffset
56+
explicit: true
57+
- preferenceId: humidityOffset
58+
explicit: true

drivers/SmartThings/matter-sensor/src/air-quality-sensor/init.lua

+101-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ local embedded_cluster_utils = require "embedded-cluster-utils"
2020
local log = require "log"
2121
local AIR_QUALITY_SENSOR_DEVICE_TYPE_ID = 0x002C
2222

23+
local SUPPORTED_COMPONENT_CAPABILITIES = "__supported_component_capabilities"
24+
25+
2326
-- Include driver-side definitions when lua libs api version is < 10
2427
local version = require "version"
2528
if version.api < 10 then
@@ -141,10 +144,6 @@ local units_required = {
141144
clusters.TotalVolatileOrganicCompoundsConcentrationMeasurement
142145
}
143146

144-
local function device_init(driver, device)
145-
device:subscribe()
146-
end
147-
148147
local tbl_contains = function(t, val)
149148
for _, v in pairs(t) do
150149
if v == val then
@@ -210,15 +209,33 @@ local function create_level_measurement_profile(device)
210209
return meas_name, level_name
211210
end
212211

213-
local function do_configure(driver, device)
212+
local function supported_level_measurements(device)
213+
local measurement_caps, level_caps = {}, {}
214+
for _, details in ipairs(AIR_QUALITY_MAP) do
215+
local cap_id = details[1]
216+
local cluster = details[3]
217+
-- capability describes either a HealthConcern or Measurement/Sensor
218+
if (cap_id:match("HealthConcern$")) then
219+
local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.LEVEL_INDICATION })
220+
if #attr_eps > 0 then
221+
device.log.info(string.format("Adding %s cap to table", cap_id))
222+
table.insert(level_caps, cap_id)
223+
end
224+
elseif (cap_id:match("Measurement$") or cap_id:match("Sensor$")) then
225+
local attr_eps = embedded_cluster_utils.get_endpoints(device, cluster.ID, { feature_bitmap = cluster.types.Feature.NUMERIC_MEASUREMENT })
226+
if #attr_eps > 0 then
227+
device.log.info(string.format("Adding %s cap to table", cap_id))
228+
table.insert(measurement_caps, cap_id)
229+
end
230+
end
231+
end
232+
return measurement_caps, level_caps
233+
end
234+
235+
local function match_profile_switch(driver, device)
214236
local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID)
215237
local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID)
216238

217-
-- we have to read the unit before reports of values will do anything
218-
for _, cluster in ipairs(units_required) do
219-
device:send(cluster.attributes.MeasurementUnit:read(device))
220-
end
221-
222239
local profile_name = "aqs"
223240

224241
if #temp_eps > 0 then
@@ -271,6 +288,80 @@ local function do_configure(driver, device)
271288
device:try_update_metadata({profile = profile_name})
272289
end
273290

291+
local function supports_capability_by_id_modular(device, capability, component)
292+
for _, component_capabilities in ipairs(device:get_field(SUPPORTED_COMPONENT_CAPABILITIES)) do
293+
local comp_id = component_capabilities[1]
294+
local capability_ids = component_capabilities[2]
295+
if (component == nil) or (component == comp_id) then
296+
for _, cap in ipairs(capability_ids) do
297+
if cap == capability then
298+
return true
299+
end
300+
end
301+
end
302+
end
303+
return false
304+
end
305+
306+
local function match_modular_profile(driver, device)
307+
local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureMeasurement.ID)
308+
local humidity_eps = embedded_cluster_utils.get_endpoints(device, clusters.RelativeHumidityMeasurement.ID)
309+
310+
local optional_supported_component_capabilities = {}
311+
local main_component_capabilities = {}
312+
313+
if #temp_eps > 0 then
314+
table.insert(main_component_capabilities, capabilities.temperatureMeasurement.ID)
315+
end
316+
if #humidity_eps > 0 then
317+
table.insert(main_component_capabilities, capabilities.relativeHumidityMeasurement.ID)
318+
end
319+
320+
local measurement_caps, level_caps = supported_level_measurements(device)
321+
322+
for _, cap_id in ipairs(measurement_caps) do
323+
table.insert(main_component_capabilities, cap_id)
324+
end
325+
326+
for _, cap_id in ipairs(level_caps) do
327+
table.insert(main_component_capabilities, cap_id)
328+
end
329+
330+
table.insert(optional_supported_component_capabilities, {"main", main_component_capabilities})
331+
332+
device:set_field(SUPPORTED_COMPONENT_CAPABILITIES, optional_supported_component_capabilities)
333+
334+
device:try_update_metadata({profile = "aqs-modular", optional_component_capabilities = optional_supported_component_capabilities})
335+
336+
-- add mandatory capabilities for subscription
337+
local total_supported_capabilities = optional_supported_component_capabilities
338+
table.insert(total_supported_capabilities[1][2], capabilities.airQualityHealthConcern.ID)
339+
340+
device:set_field(SUPPORTED_COMPONENT_CAPABILITIES, total_supported_capabilities, { persist = true })
341+
342+
--re-up subscription with new capabiltiies using the moudlar supports_capability override
343+
device:extend_device("supports_capability_by_id", supports_capability_by_id_modular)
344+
device:subscribe()
345+
end
346+
347+
local function do_configure(driver, device)
348+
-- must use profile switching on older hubs
349+
if version.api < 14 and version.rpc < 7 then
350+
match_profile_switch(driver, device)
351+
else
352+
match_modular_profile(driver, device)
353+
end
354+
end
355+
356+
local function device_init(driver, device)
357+
if device:get_field(SUPPORTED_COMPONENT_CAPABILITIES) then
358+
-- assume that device is using a modular profile, override supports_capability_by_id
359+
-- library function to utilize optional capabilities
360+
device:extend_device("supports_capability_by_id", supports_capability_by_id_modular)
361+
end
362+
device:subscribe()
363+
end
364+
274365
local function store_unit_factory(capability_name)
275366
return function(driver, device, ib, response)
276367
device:set_field(capability_name.."_unit", ib.data.value, {persist = true})

0 commit comments

Comments
 (0)