Skip to content

Commit

Permalink
Add Aqara Light Switch H2 (#1822)
Browse files Browse the repository at this point in the history
This is a new Matter Device with four physical switches.
 - Two of each of the four switchs have On/Off Light(0x0100) and Generic Switch(0x000F) device types.
 - The other two switches have a Generic Switch(0x000F) device type.
 - The root node has Electrical Sensor(0x0510) utility device type.
  • Loading branch information
DongHoon-Ryu authored Jan 22, 2025
1 parent 78ed039 commit a8aa597
Show file tree
Hide file tree
Showing 3 changed files with 391 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: light-power-energy-powerConsumption
components:
- id: main
capabilities:
- id: switch
version: 1
- id: powerMeter
version: 1
- id: energyMeter
version: 1
- id: powerConsumptionReport
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
version: 1
categories:
- name: Light
43 changes: 40 additions & 3 deletions drivers/SmartThings/matter-switch/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map"
-- containing both button endpoints and switch endpoints will use this field
-- rather than COMPONENT_TO_ENDPOINT_MAP.
local COMPONENT_TO_ENDPOINT_MAP_BUTTON = "__component_to_endpoint_map_button"
local ENERGY_MANAGEMENT_ENDPOINT = "__energy_management_endpoint"
local IS_PARENT_CHILD_DEVICE = "__is_parent_child_device"
local COLOR_TEMP_BOUND_RECEIVED_KELVIN = "__colorTemp_bound_received_kelvin"
local COLOR_TEMP_BOUND_RECEIVED_MIRED = "__colorTemp_bound_received_mired"
Expand All @@ -66,6 +67,7 @@ local ON_OFF_SWITCH_ID = 0x0103
local ON_OFF_DIMMER_SWITCH_ID = 0x0104
local ON_OFF_COLOR_DIMMER_SWITCH_ID = 0x0105
local GENERIC_SWITCH_ID = 0x000F
local ELECTRICAL_SENSOR_ID = 0x0510
local device_type_profile_map = {
[ON_OFF_LIGHT_DEVICE_TYPE_ID] = "light-binary",
[DIMMABLE_LIGHT_DEVICE_TYPE_ID] = "light-level",
Expand Down Expand Up @@ -148,12 +150,19 @@ local device_type_attribute_map = {
clusters.Switch.events.LongPress,
clusters.Switch.events.ShortRelease,
clusters.Switch.events.MultiPressComplete
},
[ELECTRICAL_SENSOR_ID] = {
clusters.ElectricalPowerMeasurement.attributes.ActivePower,
clusters.ElectricalEnergyMeasurement.attributes.CumulativeEnergyImported,
clusters.ElectricalEnergyMeasurement.attributes.PeriodicEnergyImported
}
}

local child_device_profile_overrides = {
{ vendor_id = 0x1321, product_id = 0x000C, child_profile = "switch-binary" },
{ vendor_id = 0x1321, product_id = 0x000D, child_profile = "switch-binary" },
{ vendor_id = 0x115F, product_id = 0x1008, child_profile = "light-power-energy-powerConsumption" }, -- 2 switch
{ vendor_id = 0x115F, product_id = 0x1009, child_profile = "light-power-energy-powerConsumption" }, -- 4 switch
}

local detect_matter_thing
Expand Down Expand Up @@ -257,6 +266,7 @@ local HELD_THRESHOLD = 1
local STATIC_BUTTON_PROFILE_SUPPORTED = {1, 2, 3, 4, 5, 6, 7, 8}

local DEFERRED_CONFIGURE = "__DEFERRED_CONFIGURE"
local BUTTON_DEVICE_PROFILED = "__button_device_profiled"

-- Some switches will send a MultiPressComplete event as part of a long press sequence. Normally the driver will create a
-- button capability event on receipt of MultiPressComplete, but in this case that would result in an extra event because
Expand All @@ -271,6 +281,7 @@ local SUPPORTS_MULTI_PRESS = "__multi_button" -- for MSM devices (MomentarySwitc
local INITIAL_PRESS_ONLY = "__initial_press_only" -- for devices that support MS (MomentarySwitch), but not MSR (MomentarySwitchRelease)

local HUE_MANUFACTURER_ID = 0x100B
local AQARA_MANUFACTURER_ID = 0x115F

--helper function to create list of multi press values
local function create_multi_press_values_list(size, supportsHeld)
Expand Down Expand Up @@ -414,6 +425,13 @@ local function assign_child_profile(device, child_ep)
for _, fingerprint in ipairs(child_device_profile_overrides) do
if device.manufacturer_info.vendor_id == fingerprint.vendor_id and
device.manufacturer_info.product_id == fingerprint.product_id then
if device.manufacturer_info.vendor_id == AQARA_MANUFACTURER_ID then
if child_ep ~= 1 then
-- To add Electrical Sensor only to the first EDGE_CHILD(light-power-energy-powerConsumption)
-- The profile of the second EDGE_CHILD is determined in the "for" loop below (e.g., light-binary)
break
end
end
return fingerprint.child_profile
end
end
Expand All @@ -437,6 +455,9 @@ local function assign_child_profile(device, child_ep)
end

local function do_configure(driver, device)
if device:get_field(BUTTON_DEVICE_PROFILED) then
return
end
local energy_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalEnergyMeasurement.ID)
local power_eps = embedded_cluster_utils.get_endpoints(device, clusters.ElectricalPowerMeasurement.ID)
local valve_eps = embedded_cluster_utils.get_endpoints(device, clusters.ValveConfigurationAndControl.ID)
Expand Down Expand Up @@ -539,6 +560,10 @@ local function initialize_switch(driver, device)
end

for _, ep in ipairs(switch_eps) do
if _ == 1 then
-- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
device:set_field(ENERGY_MANAGEMENT_ENDPOINT, ep)
end
if device:supports_server_cluster(clusters.OnOff.ID, ep) then
num_switch_server_eps = num_switch_server_eps + 1
if ep ~= main_endpoint then -- don't create a child device that maps to the main endpoint
Expand Down Expand Up @@ -580,6 +605,7 @@ local function initialize_switch(driver, device)
end
device:try_update_metadata({profile = profile_name})
device:set_field(DEFERRED_CONFIGURE, true)
device:set_field(BUTTON_DEVICE_PROFILED, true)
elseif #button_eps > 0 then
local battery_support = false
if device.manufacturer_info.vendor_id ~= HUE_MANUFACTURER_ID and
Expand All @@ -600,6 +626,7 @@ local function initialize_switch(driver, device)
if profile_name then
device:try_update_metadata({profile = profile_name})
device:set_field(DEFERRED_CONFIGURE, true)
device:set_field(BUTTON_DEVICE_PROFILED, true)
else
configure_buttons(device)
end
Expand Down Expand Up @@ -681,7 +708,7 @@ local function device_init(driver, device)
end
local main_endpoint = find_default_endpoint(device)
for _, ep in ipairs(device.endpoints) do
if ep.endpoint_id ~= main_endpoint and ep.endpoint_id ~= 0 then
if ep.endpoint_id ~= main_endpoint then
local id = 0
for _, dt in ipairs(ep.device_types) do
id = math.max(id, dt.device_type_id)
Expand Down Expand Up @@ -1009,7 +1036,12 @@ local function cumul_energy_imported_handler(driver, device, ib, response)
if ib.data.elements.energy then
local watt_hour_value = ib.data.elements.energy.value / CONVERSION_CONST_MILLIWATT_TO_WATT
device:set_field(TOTAL_IMPORTED_ENERGY, watt_hour_value)
device:emit_event(capabilities.energyMeter.energy({ value = watt_hour_value, unit = "Wh" }))
if ib.endpoint_id ~= 0 then
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.energyMeter.energy({ value = watt_hour_value, unit = "Wh" }))
else
-- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
device:emit_event_for_endpoint(device:get_field(ENERGY_MANAGEMENT_ENDPOINT), capabilities.energyMeter.energy({ value = watt_hour_value, unit = "Wh" }))
end
end
end

Expand Down Expand Up @@ -1072,7 +1104,12 @@ end
local function active_power_handler(driver, device, ib, response)
if ib.data.value then
local watt_value = ib.data.value / CONVERSION_CONST_MILLIWATT_TO_WATT
device:emit_event(capabilities.powerMeter.power({ value = watt_value, unit = "W"}))
if ib.endpoint_id ~= 0 then
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.powerMeter.power({ value = watt_value, unit = "W"}))
else
-- when energy management is defined in the root endpoint(0), replace it with the first switch endpoint and process it.
device:emit_event_for_endpoint(device:get_field(ENERGY_MANAGEMENT_ENDPOINT), capabilities.powerMeter.power({ value = watt_value, unit = "W"}))
end
end
end

Expand Down
Loading

0 comments on commit a8aa597

Please sign in to comment.