Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ lua_libs-api_*
tools/test_output/*
tools/coverage_output/*
.DS_Store
.venv/
54 changes: 33 additions & 21 deletions drivers/SmartThings/matter-energy/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ if version.api < 12 then
end

local COMPONENT_TO_ENDPOINT_MAP = "__component_to_endpoint_map"
local SUPPORTED_EVSE_MODES_MAP = "__supported_evse_modes_map"
local SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP = "__supported_device_energy_management_modes_map"
local SUPPORTED_EVSE_MODES = "__supported_evse_modes"
local SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES = "__supported_device_energy_management_modes"

local CUMULATIVE_REPORTS_NOT_SUPPORTED = "__cumulative_reports_not_supported"
local LAST_IMPORTED_REPORT_TIMESTAMP = "__last_imported_report_timestamp"
Expand All @@ -47,6 +47,11 @@ local TOTAL_CUMULATIVE_ENERGY_IMPORTED = "__total_cumulative_energy_imported"
local TOTAL_CUMULATIVE_ENERGY_EXPORTED = "__total_cumulative_energy_exported"
local TOTAL_ACTIVE_POWER = "__total_active_power"

local updated_fields = {
{ current_field_name = "__supported_evse_modes_map", updated_field_name = nil },
{ current_field_name = "__supported_device_energy_management_modes_map", updated_field_name = nil }
}

local MAX_CHARGING_CURRENT_CONSTRAINT = 80000 -- In v1.3 release of stack, this check for 80 A is performed.

local EVSE_DEVICE_TYPE_ID = 0x050C
Expand All @@ -55,7 +60,6 @@ local BATTERY_STORAGE_DEVICE_TYPE_ID = 0x0018
local ELECTRICAL_SENSOR_DEVICE_TYPE_ID = 0x0510
local DEVICE_ENERGY_MANAGEMENT_DEVICE_TYPE_ID = 0x050D


local function get_endpoints_for_dt(device, device_type)
local endpoints = {}
for _, ep in ipairs(device.endpoints) do
Expand Down Expand Up @@ -100,6 +104,25 @@ local function component_to_endpoint(device, component)
end
end

local function get_field_for_endpoint(device, field, endpoint)
return device:get_field(string.format("%s_%d", field, endpoint))
end

local function set_field_for_endpoint(device, field, endpoint, value, additional_params)
device:set_field(string.format("%s_%d", field, endpoint), value, additional_params)
end

local function check_field_name_updates(device)
for _, field in ipairs(updated_fields) do
if device:get_field(field.current_field_name) then
if field.updated_field_name ~= nil then
device:set_field(field.updated_field_name, device:get_field(field.current_field_name), {persist = true})
end
device:set_field(field.current_field_name, nil)
end
end
end

local function time_zone_offset()
return os.difftime(os.time(), os.time(os.date("!*t", os.time())))
end
Expand Down Expand Up @@ -193,6 +216,7 @@ local BATTERY_CHARGING_STATE_MAP = {

-- Lifecycle Handlers --
local function device_init(driver, device)
check_field_name_updates(device)
device:subscribe()
device:set_endpoint_to_component_fn(endpoint_to_component)
device:set_component_to_endpoint_fn(component_to_endpoint)
Expand Down Expand Up @@ -386,27 +410,22 @@ local function power_mode_handler(driver, device, ib, response)
end

local function energy_evse_supported_modes_attr_handler(driver, device, ib, response)
local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {}
local supportedEvseModes = {}
for _, mode in ipairs(ib.data.elements) do
if version.api < 11 then
clusters.EnergyEvseMode.types.ModeOptionStruct:augment_type(mode)
end
table.insert(supportedEvseModes, mode.elements.label.value)
end
supportedEvseModesMap[ib.endpoint_id] = supportedEvseModes
device:set_field(SUPPORTED_EVSE_MODES_MAP, supportedEvseModesMap, { persist = true })
set_field_for_endpoint(device, SUPPORTED_EVSE_MODES, ib.endpoint_id, supportedEvseModes, { persist = true })
local event = capabilities.mode.supportedModes(supportedEvseModes, { visibility = { displayed = false } })
device:emit_event_for_endpoint(ib.endpoint_id, event)
event = capabilities.mode.supportedArguments(supportedEvseModes, { visibility = { displayed = false } })
device:emit_event_for_endpoint(ib.endpoint_id, event)
end

local function energy_evse_mode_attr_handler(driver, device, ib, response)
device.log.info(string.format("energy_evse_modes_attr_handler currentMode: %s", ib.data.value))

local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP) or {}
local supportedEvseModes = supportedEvseModesMap[ib.endpoint_id] or {}
local supportedEvseModes = get_field_for_endpoint(device, SUPPORTED_EVSE_MODES, ib.endpoint_id) or {}
local currentMode = ib.data.value
for i, mode in ipairs(supportedEvseModes) do
if i - 1 == currentMode then
Expand All @@ -417,27 +436,22 @@ local function energy_evse_mode_attr_handler(driver, device, ib, response)
end

local function device_energy_mgmt_supported_modes_attr_handler(driver, device, ib, response)
local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {}
local supportedDeviceEnergyMgmtModes = {}
for _, mode in ipairs(ib.data.elements) do
if version.api < 12 then
clusters.DeviceEnergyManagementMode.types.ModeOptionStruct:augment_type(mode)
end
table.insert(supportedDeviceEnergyMgmtModes, mode.elements.label.value)
end
supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] = supportedDeviceEnergyMgmtModes
device:set_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP, supportedDeviceEnergyMgmtModesMap, { persist = true })
set_field_for_endpoint(device, SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES, ib.endpoint_id, supportedDeviceEnergyMgmtModes, { persist = true })
local event = capabilities.mode.supportedModes(supportedDeviceEnergyMgmtModes, { visibility = { displayed = false } })
device:emit_event_for_endpoint(ib.endpoint_id, event)
event = capabilities.mode.supportedArguments(supportedDeviceEnergyMgmtModes, { visibility = { displayed = false } })
device:emit_event_for_endpoint(ib.endpoint_id, event)
end

local function device_energy_mgmt_mode_attr_handler(driver, device, ib, response)
device.log.info(string.format("device_energy_mgmt_mode_attr_handler currentMode: %s", ib.data.value))

local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP) or {}
local supportedDeviceEnergyMgmtModes = supportedDeviceEnergyMgmtModesMap[ib.endpoint_id] or {}
local supportedDeviceEnergyMgmtModes = get_field_for_endpoint(device, SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES, ib.endpoint_id) or {}
local currentMode = ib.data.value
for i, mode in ipairs(supportedDeviceEnergyMgmtModes) do
if i - 1 == currentMode then
Expand Down Expand Up @@ -603,8 +617,7 @@ local function handle_set_mode_command(driver, device, cmd)
local set_mode_handlers = {
["main"] = function( ... )
local ep = component_to_endpoint(device, cmd.component)
local supportedEvseModesMap = device:get_field(SUPPORTED_EVSE_MODES_MAP)
local supportedEvseModes = supportedEvseModesMap[ep] or {}
local supportedEvseModes = get_field_for_endpoint(device, SUPPORTED_EVSE_MODES, ep) or {}
for i, mode in ipairs(supportedEvseModes) do
if cmd.args.mode == mode then
device:send(clusters.EnergyEvseMode.commands.ChangeToMode(device, ep, i - 1))
Expand All @@ -615,8 +628,7 @@ local function handle_set_mode_command(driver, device, cmd)
end,
["deviceEnergyManagement"] = function( ... )
local ep = component_to_endpoint(device, cmd.component)
local supportedDeviceEnergyMgmtModesMap = device:get_field(SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES_MAP)
local supportedDeviceEnergyMgmtModes = supportedDeviceEnergyMgmtModesMap[ep] or {}
local supportedDeviceEnergyMgmtModes = get_field_for_endpoint(device, SUPPORTED_DEVICE_ENERGY_MANAGEMENT_MODES, ep) or {}
for i, mode in ipairs(supportedDeviceEnergyMgmtModes) do
if cmd.args.mode == mode then
device:send(clusters.DeviceEnergyManagementMode.commands.ChangeToMode(device, ep, i - 1))
Expand Down
7 changes: 0 additions & 7 deletions drivers/SmartThings/matter-switch/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,6 @@ matterManufacturer:
productId: 0x0016
deviceProfileName: light-color-level-2000K-7000K

# Govee
- id: "4999/24584"
deviceLabel: Govee Smart Bulb
vendorId: 0x1387
productId: 0x6008
deviceProfileName: light-color-level-2700K-6500K

# Hue
- id: "4107/2049"
deviceLabel: Hue W 1600 A21 E26 1P NAM
Expand Down
6 changes: 6 additions & 0 deletions drivers/SmartThings/matter-thermostat/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ matterManufacturer:
vendorId: 0x1206
productId: 0x0001
deviceProfileName: thermostat-nostate-nobattery
#Meross
- id: "4933/57345"
deviceLabel: Smart Wi-Fi Thermostat
vendorId: 0x1345
productId: 0xE001
deviceProfileName: thermostat-fan-nostate-nobattery
#Siterwell
- id: "4736/769"
deviceLabel: Siterwell Radiator Thermostat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ components:
categories:
- name: Blind
preferences:
- preferenceId: presetPosition
explicit: true
- preferenceId: reverse
explicit: true
29 changes: 25 additions & 4 deletions drivers/SmartThings/matter-window-covering/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ local battery_support = {
BATTERY_PERCENTAGE = "BATTERY_PERCENTAGE"
}
local REVERSE_POLARITY = "__reverse_polarity"
local PRESET_LEVEL_KEY = "__preset_level_key"
local PRESET_LEVEL = 50

local function find_default_endpoint(device, cluster)
local res = device.MATTER_DEFAULT_ENDPOINT
Expand Down Expand Up @@ -69,6 +71,17 @@ end

local function device_init(driver, device)
device:set_component_to_endpoint_fn(component_to_endpoint)
if device:supports_capability_by_id(capabilities.windowShadePreset.ID) and
device:get_latest_state("main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME) == nil then
-- These should only ever be nil once (and at the same time) for already-installed devices
-- It can be removed after migration is complete
device:emit_event(capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed = false}}))
local preset_position = device:get_field(PRESET_LEVEL_KEY) or
(device.preferences ~= nil and device.preferences.presetPosition) or
PRESET_LEVEL
device:emit_event(capabilities.windowShadePreset.position(preset_position, {visibility = {displayed = false}}))
device:set_field(PRESET_LEVEL_KEY, preset_position, {persist = true})
end
device:subscribe()
end

Expand Down Expand Up @@ -122,22 +135,29 @@ local function handle_preset(driver, device, cmd)
local lift_eps = device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.LIFT})
local tilt_eps = device:get_endpoints(clusters.WindowCovering.ID, {feature_bitmap = clusters.WindowCovering.types.Feature.TILT})
if #lift_eps > 0 then
local lift_value = 100 - device.preferences.presetPosition
local hundredths_lift_percent = lift_value * 100
local lift_value = device:get_latest_state(
"main", capabilities.windowShadePreset.ID, capabilities.windowShadePreset.position.NAME
) or PRESET_LEVEL
local hundredths_lift_percent = (100 - lift_value) * 100
local req = clusters.WindowCovering.server.commands.GoToLiftPercentage(
device, endpoint_id, hundredths_lift_percent
)
device:send(req)
end
if #tilt_eps > 0 then
-- Use default preset tilt percentage to 50 until a canonical preference is created for preset tilt position
local req = clusters.WindowCovering.server.commands.GoToTiltPercentage(
device, endpoint_id, 50 * 100
device, endpoint_id, PRESET_LEVEL * 100
)
device:send(req)
end
end

local function handle_set_preset(driver, device, cmd)
local endpoint_id = device:component_to_endpoint(cmd.component)
device:set_field(PRESET_LEVEL_KEY, cmd.args.position)
device:emit_event_for_endpoint(endpoint_id, capabilities.windowShadePreset.position(cmd.args.position))
end

-- close covering
local function handle_close(driver, device, cmd)
local endpoint_id = device:component_to_endpoint(cmd.component)
Expand Down Expand Up @@ -345,6 +365,7 @@ local matter_driver_template = {
capability_handlers = {
[capabilities.windowShadePreset.ID] = {
[capabilities.windowShadePreset.commands.presetPosition.NAME] = handle_preset,
[capabilities.windowShadePreset.commands.setPresetPosition.NAME] = handle_set_preset,
},
[capabilities.windowShade.ID] = {
[capabilities.windowShade.commands.close.NAME] = handle_close,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ local mock_device = test.mock_device.build_test_matter_device(
{
profile = t_utils.get_profile_definition("window-covering-tilt-battery.yml"),
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
preferences = { presetPosition = 30 },
endpoints = {
{
endpoint_id = 2,
Expand Down Expand Up @@ -58,7 +57,6 @@ local mock_device_mains_powered = test.mock_device.build_test_matter_device(
{
profile = t_utils.get_profile_definition("window-covering.yml"),
manufacturer_info = {vendor_id = 0x0000, product_id = 0x0000},
preferences = { presetPosition = 30 },
endpoints = {
{
endpoint_id = 2,
Expand Down Expand Up @@ -100,6 +98,19 @@ local CLUSTER_SUBSCRIBE_LIST_NO_BATTERY = {
WindowCovering.server.attributes.OperationalStatus,
}

local function set_preset(device)
test.socket.capability:__expect_send(
device:generate_test_message(
"main", capabilities.windowShadePreset.supportedCommands({"presetPosition", "setPresetPosition"}, {visibility = {displayed = false}})
)
)
test.socket.capability:__expect_send(
device:generate_test_message(
"main", capabilities.windowShadePreset.position(50, {visibility = {displayed = false}})
)
)
end

local function test_init()
test.mock_device.add_test_device(mock_device)
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
Expand All @@ -111,6 +122,7 @@ local function test_init()
)

test.socket.device_lifecycle:__queue_receive({ mock_device.id, "init" })
set_preset(mock_device)
local subscribe_request = CLUSTER_SUBSCRIBE_LIST[1]:subscribe(mock_device)
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST) do
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device)) end
Expand All @@ -134,6 +146,7 @@ local function test_init_mains_powered()
)

test.socket.device_lifecycle:__queue_receive({ mock_device_mains_powered.id, "init" })
set_preset(mock_device_mains_powered)
local subscribe_request = CLUSTER_SUBSCRIBE_LIST_NO_BATTERY[1]:subscribe(mock_device_mains_powered)
for i, clus in ipairs(CLUSTER_SUBSCRIBE_LIST_NO_BATTERY) do
if i > 1 then subscribe_request:merge(clus:subscribe(mock_device_mains_powered)) end
Expand Down Expand Up @@ -756,20 +769,31 @@ test.register_coroutine_test("OperationalStatus report contains current position
)
end)

test.register_coroutine_test("Handle windowcoveringPreset", function()
test.socket.capability:__queue_receive(
{
test.register_coroutine_test(
"Handle preset commands",
function()
local PRESET_LEVEL = 30
test.socket.capability:__queue_receive({
mock_device.id,
{capability = "windowShadePreset", component = "main", command = "setPresetPosition", args = { PRESET_LEVEL }},
})
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main", capabilities.windowShadePreset.position(PRESET_LEVEL)
)
)
test.socket.capability:__queue_receive({
mock_device.id,
{capability = "windowShadePreset", component = "main", command = "presetPosition", args = {}},
}
)
test.socket.matter:__expect_send(
{mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, 7000)}
)
test.socket.matter:__expect_send(
{mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 5000)}
)
end)
})
test.socket.matter:__expect_send(
{mock_device.id, WindowCovering.server.commands.GoToLiftPercentage(mock_device, 10, (100 - PRESET_LEVEL) * 100)}
)
test.socket.matter:__expect_send(
{mock_device.id, WindowCovering.server.commands.GoToTiltPercentage(mock_device, 10, 5000)}
)
end
)

test.register_coroutine_test(
"Test profile change to window-covering-battery when battery percent remaining attribute (attribute ID 12) is available",
Expand Down
2 changes: 1 addition & 1 deletion drivers/SmartThings/sonos/src/api/rest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ local SonosRestApi = {}
--- Query a Sonos Group IP address for individual player info
---@param url table a URL table created by `net_url`
---@param headers table<string,string>?
---@return SonosDiscoveryInfo|SonosErrorResponse|nil
---@return SonosDiscoveryInfoObject|SonosErrorResponse|nil
---@return string|nil error
function SonosRestApi.get_player_info(url, headers)
url.path = "/api/v1/players/local/info"
Expand Down
Loading
Loading