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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function RvcOperationalState:get_server_command_by_id(command_id)
[0x0001] = "Stop",
[0x0002] = "Start",
[0x0003] = "Resume",
[0x0080] = "GoHome",
}
if server_id_map[command_id] ~= nil then
return self.server.commands[server_id_map[command_id]]
Expand Down Expand Up @@ -74,6 +75,7 @@ RvcOperationalState.command_direction_map = {
["Stop"] = "server",
["Start"] = "server",
["Resume"] = "server",
["GoHome"] = "server",
["OperationalCommandResponse"] = "client",
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
local cluster_base = require "st.matter.cluster_base"
local data_types = require "st.matter.data_types"
local TLVParser = require "st.matter.TLV.TLVParser"

local AcceptedCommandList = {
ID = 0xFFF9,
NAME = "AcceptedCommandList",
base_type = require "st.matter.data_types.Array",
element_type = require "st.matter.data_types.Uint32",
}

function AcceptedCommandList:augment_type(data_type_obj)
for i, v in ipairs(data_type_obj.elements) do
data_type_obj.elements[i] = data_types.validate_or_build_type(v, AcceptedCommandList.element_type)
end
end

function AcceptedCommandList:new_value(...)
local o = self.base_type(table.unpack({...}))

return o
end

function AcceptedCommandList:read(device, endpoint_id)
return cluster_base.read(
device,
endpoint_id,
self._cluster.ID,
self.ID,
nil --event_id
)
end

function AcceptedCommandList:subscribe(device, endpoint_id)
return cluster_base.subscribe(
device,
endpoint_id,
self._cluster.ID,
self.ID,
nil --event_id
)
end

function AcceptedCommandList:set_parent_cluster(cluster)
self._cluster = cluster
return self
end

function AcceptedCommandList:build_test_report_data(
device,
endpoint_id,
value,
status
)
local data = data_types.validate_or_build_type(value, self.base_type)

return cluster_base.build_test_report_data(
device,
endpoint_id,
self._cluster.ID,
self.ID,
data,
status
)
end

function AcceptedCommandList:deserialize(tlv_buf)
local data = TLVParser.decode_tlv(tlv_buf)

return data
end

setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type})
return AcceptedCommandList

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
local data_types = require "st.matter.data_types"
local TLVParser = require "st.matter.TLV.TLVParser"

local GoHome = {}

GoHome.NAME = "GoHome"
GoHome.ID = 0x0080
GoHome.field_defs = {
}

function GoHome:init(device, endpoint_id)
local out = {}
local args = {}
if #args > #self.field_defs then
error(self.NAME .. " received too many arguments")
end
for i,v in ipairs(self.field_defs) do
if v.is_optional and args[i] == nil then
out[v.name] = nil
elseif v.is_nullable and args[i] == nil then
out[v.name] = data_types.validate_or_build_type(args[i], data_types.Null, v.name)
out[v.name].field_id = v.field_id
elseif not v.is_optional and args[i] == nil then
out[v.name] = data_types.validate_or_build_type(v.default, v.data_type, v.name)
out[v.name].field_id = v.field_id
else
out[v.name] = data_types.validate_or_build_type(args[i], v.data_type, v.name)
out[v.name].field_id = v.field_id
end
end
setmetatable(out, {
__index = GoHome,
__tostring = GoHome.pretty_print
})
return self._cluster:build_cluster_command(
device,
out,
endpoint_id,
self._cluster.ID,
self.ID
)
end

function GoHome:set_parent_cluster(cluster)
self._cluster = cluster
return self
end

function GoHome:augment_type(base_type_obj)
local elems = {}
for _, v in ipairs(base_type_obj.elements) do
for _, field_def in ipairs(self.field_defs) do
if field_def.field_id == v.field_id and
field_def.is_nullable and
(v.value == nil and v.elements == nil) then
elems[field_def.name] = data_types.validate_or_build_type(v, data_types.Null, field_def.field_name)
elseif field_def.field_id == v.field_id and not
(field_def.is_optional and v.value == nil) then
elems[field_def.name] = data_types.validate_or_build_type(v, field_def.data_type, field_def.field_name)
if field_def.element_type ~= nil then
for i, e in ipairs(elems[field_def.name].elements) do
elems[field_def.name].elements[i] = data_types.validate_or_build_type(e, field_def.element_type)
end
end
end
end
end
base_type_obj.elements = elems
end

function GoHome:deserialize(tlv_buf)
local data = TLVParser.decode_tlv(tlv_buf)
self:augment_type(data)
return data
end

setmetatable(GoHome, {__call = GoHome.init})

return GoHome
68 changes: 44 additions & 24 deletions drivers/SmartThings/matter-rvc/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,21 @@ end
local RUN_MODE_SUPPORTED_MODES = "__run_mode_supported_modes"
local CURRENT_RUN_MODE = "__current_run_mode"
local CLEAN_MODE_SUPPORTED_MODES = "__clean_mode_supported_modes"
local OPERATING_STATE_SUPPORTED_COMMANDS = "__operating_state_supported_commands"
local SERVICE_AREA_PROFILED = "__SERVICE_AREA_PROFILED"

local clus_op_enum = clusters.OperationalState.types.OperationalStateEnum
local clus_rvc_op_enum = clusters.RvcOperationalState.types.OperationalStateEnum
local cap_op_enum = capabilities.robotCleanerOperatingState.operatingState
local cap_op_cmds = capabilities.robotCleanerOperatingState.commands
local OPERATING_STATE_MAP = {
[clus_op_enum.STOPPED] = cap_op_enum.stopped,
[clus_op_enum.RUNNING] = cap_op_enum.running,
[clus_op_enum.PAUSED] = cap_op_enum.paused,
[clus_rvc_op_enum.SEEKING_CHARGER] = cap_op_enum.seekingCharger,
[clus_rvc_op_enum.CHARGING] = cap_op_enum.charging,
[clus_rvc_op_enum.DOCKED] = cap_op_enum.docked
}

local subscribed_attributes = {
[capabilities.mode.ID] = {
clusters.RvcRunMode.attributes.SupportedModes,
Expand All @@ -46,6 +58,7 @@ local subscribed_attributes = {
clusters.RvcCleanMode.attributes.CurrentMode
},
[capabilities.robotCleanerOperatingState.ID] = {
clusters.RvcOperationalState.attributes.OperationalStateList,
clusters.RvcOperationalState.attributes.OperationalState,
clusters.RvcOperationalState.attributes.OperationalError
},
Expand Down Expand Up @@ -103,6 +116,12 @@ local function do_configure(driver, device)
device:send(clusters.RvcOperationalState.attributes.AcceptedCommandList:read())
end

local function driver_switched(driver, device)
match_profile(driver, device)
device:set_field(SERVICE_AREA_PROFILED, true, { persist = true })
device:send(clusters.RvcOperationalState.attributes.AcceptedCommandList:read())
end

local function info_changed(driver, device, event, args)
if device.profile.id ~= args.old_st_store.profile.id then
device:subscribe()
Expand All @@ -111,7 +130,11 @@ end

-- Helper functions --
local function supports_rvc_operational_state(device, command_name)
local supported_op_commands = device:get_field(OPERATING_STATE_SUPPORTED_COMMANDS) or {}
local supported_op_commands = device:get_latest_state(
"main",
capabilities.robotCleanerOperatingState.ID,
capabilities.robotCleanerOperatingState.supportedCommands.NAME
) or {}
for _, cmd in ipairs(supported_op_commands) do
if cmd == command_name then
return true
Expand All @@ -127,8 +150,6 @@ local function can_send_state_command(device, command_name, current_state, curre
end

local set_mode = capabilities.mode.commands.setMode.NAME
local cap_op_cmds = capabilities.robotCleanerOperatingState.commands
local cap_op_enum = capabilities.robotCleanerOperatingState.operatingState
if command_name ~= set_mode and supports_rvc_operational_state(device, command_name) == false then
return false
end
Expand Down Expand Up @@ -196,9 +217,7 @@ local function update_supported_arguments(device, ep, current_run_mode, current_
end

-- Set Supported Operating State Commands
local cap_op_cmds = capabilities.robotCleanerOperatingState.commands
local supported_op_commands = {}

if can_send_state_command(device, cap_op_cmds.goHome.NAME, current_state, nil) == true then
table.insert(supported_op_commands, cap_op_cmds.goHome.NAME)
end
Expand Down Expand Up @@ -312,17 +331,6 @@ end

local function rvc_operational_state_attr_handler(driver, device, ib, response)
device.log.info(string.format("rvc_operational_state_attr_handler operationalState: %s", ib.data.value))
local clus_op_enum = clusters.OperationalState.types.OperationalStateEnum
local clus_rvc_op_enum = clusters.RvcOperationalState.types.OperationalStateEnum
local cap_op_enum = capabilities.robotCleanerOperatingState.operatingState
local OPERATING_STATE_MAP = {
[clus_op_enum.STOPPED] = cap_op_enum.stopped,
[clus_op_enum.RUNNING] = cap_op_enum.running,
[clus_op_enum.PAUSED] = cap_op_enum.paused,
[clus_rvc_op_enum.SEEKING_CHARGER] = cap_op_enum.seekingCharger,
[clus_rvc_op_enum.CHARGING] = cap_op_enum.charging,
[clus_rvc_op_enum.DOCKED] = cap_op_enum.docked
}
if ib.data.value ~= clus_op_enum.ERROR then
device:emit_event_for_endpoint(ib.endpoint_id, OPERATING_STATE_MAP[ib.data.value]())
end
Expand Down Expand Up @@ -369,9 +377,21 @@ local function rvc_operational_error_attr_handler(driver, device, ib, response)
end
end

local function rvc_operational_state_list_attr_handler(driver, device, ib, response)
local supportedOperatingState = {}
for _, state in ipairs(ib.data.elements) do
clusters.RvcOperationalState.types.OperationalStateStruct:augment_type(state)
if OPERATING_STATE_MAP[state.elements.operational_state_id.value] ~= nil then
table.insert(supportedOperatingState, OPERATING_STATE_MAP[state.elements.operational_state_id.value].NAME)
end
end
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.robotCleanerOperatingState.supportedOperatingStates(
supportedOperatingState, {visibility = {displayed = false}}
))
end

local function handle_rvc_operational_state_accepted_command_list(driver, device, ib, response)
device.log.info("handle_rvc_operational_state_accepted_command_list")
local cap_op_cmds = capabilities.robotCleanerOperatingState.commands
local OP_COMMAND_MAP = {
[clusters.RvcOperationalState.commands.Pause.ID] = cap_op_cmds.pause,
[clusters.RvcOperationalState.commands.Resume.ID] = cap_op_cmds.start,
Expand All @@ -381,7 +401,9 @@ local function handle_rvc_operational_state_accepted_command_list(driver, device
for _, attr in ipairs(ib.data.elements) do
table.insert(supportedOperatingStateCommands, OP_COMMAND_MAP[attr.value].NAME)
end
device:set_field(OPERATING_STATE_SUPPORTED_COMMANDS, supportedOperatingStateCommands, { persist = true })
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.robotCleanerOperatingState.supportedCommands(
supportedOperatingStateCommands, {visibility = {displayed = false}}
))

-- Get current run mode, current tag, current operating state
local current_run_mode = device:get_field(CURRENT_RUN_MODE)
Expand All @@ -398,15 +420,13 @@ local function handle_rvc_operational_state_accepted_command_list(driver, device
capabilities.robotCleanerOperatingState.ID,
capabilities.robotCleanerOperatingState.operatingState.NAME
)
local cap_op_enum = capabilities.robotCleanerOperatingState.operatingState
if current_state ~= cap_op_enum.stopped.NAME and current_state ~= cap_op_enum.running.NAME and
current_state ~= cap_op_enum.paused.NAME and current_state ~= cap_op_enum.seekingCharger.NAME and
current_state ~= cap_op_enum.charging.NAME and current_state ~= cap_op_enum.docked.NAME then
current_state = "Error"
end

-- Set Supported Operating State Commands
local cap_op_cmds = capabilities.robotCleanerOperatingState.commands
local supported_op_commands = {}
if can_send_state_command(device, cap_op_cmds.goHome.NAME, current_state, current_tag) == true then
table.insert(supported_op_commands, cap_op_cmds.goHome.NAME)
Expand Down Expand Up @@ -482,7 +502,7 @@ local function rvc_service_area_selected_areas_handler(driver, device, ib, respo
capabilities.serviceArea.ID,
capabilities.serviceArea.supportedAreas.NAME
)
for i, area in ipairs(supported_areas) do
for i, area in ipairs(supported_areas or {}) do
table.insert(selected_areas, area.areaId)
end
end
Expand Down Expand Up @@ -528,14 +548,12 @@ local function handle_robot_cleaner_operating_state_start(driver, device, cmd)
capabilities.robotCleanerOperatingState.ID,
capabilities.robotCleanerOperatingState.operatingState.NAME
)
local cap_op_enum = capabilities.robotCleanerOperatingState.operatingState
if current_state ~= cap_op_enum.stopped.NAME and current_state ~= cap_op_enum.running.NAME and
current_state ~= cap_op_enum.paused.NAME and current_state ~= cap_op_enum.seekingCharger.NAME and
current_state ~= cap_op_enum.charging.NAME and current_state ~= cap_op_enum.docked.NAME then
current_state = "Error"
end

local cap_op_cmds = capabilities.robotCleanerOperatingState.commands
if can_send_state_command(device, cap_op_cmds.start.NAME, current_state, current_tag) == true then
device:send(clusters.RvcOperationalState.commands.Resume(device, endpoint_id))
elseif can_send_state_command(device, capabilities.mode.commands.setMode.NAME, current_state, current_tag) == true then
Expand Down Expand Up @@ -594,6 +612,7 @@ local matter_rvc_driver = {
init = device_init,
doConfigure = do_configure,
infoChanged = info_changed,
driverSwitched = driver_switched
},
matter_handlers = {
attr = {
Expand All @@ -606,6 +625,7 @@ local matter_rvc_driver = {
[clusters.RvcCleanMode.attributes.CurrentMode.ID] = clean_mode_current_mode_handler,
},
[clusters.RvcOperationalState.ID] = {
[clusters.RvcOperationalState.attributes.OperationalStateList.ID] = rvc_operational_state_list_attr_handler,
[clusters.RvcOperationalState.attributes.OperationalState.ID] = rvc_operational_state_attr_handler,
[clusters.RvcOperationalState.attributes.OperationalError.ID] = rvc_operational_error_attr_handler,
[clusters.RvcOperationalState.attributes.AcceptedCommandList.ID] = handle_rvc_operational_state_accepted_command_list,
Expand Down
26 changes: 20 additions & 6 deletions drivers/SmartThings/matter-rvc/src/test/test_matter_rvc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,15 @@ local function test_init()
test.mock_device.add_test_device(mock_device)
local subscribed_attributes = {
[capabilities.mode.ID] = {
clusters.RvcRunMode.attributes.SupportedModes,
clusters.RvcRunMode.attributes.CurrentMode,
clusters.RvcCleanMode.attributes.SupportedModes,
clusters.RvcCleanMode.attributes.CurrentMode,
clusters.RvcRunMode.attributes.SupportedModes,
clusters.RvcRunMode.attributes.CurrentMode,
clusters.RvcCleanMode.attributes.SupportedModes,
clusters.RvcCleanMode.attributes.CurrentMode,
},
[capabilities.robotCleanerOperatingState.ID] = {
clusters.RvcOperationalState.attributes.OperationalState,
clusters.RvcOperationalState.attributes.OperationalError
clusters.RvcOperationalState.attributes.OperationalStateList,
clusters.RvcOperationalState.attributes.OperationalState,
clusters.RvcOperationalState.attributes.OperationalError
},
[capabilities.serviceArea.ID] = {
clusters.ServiceArea.attributes.SupportedAreas,
Expand Down Expand Up @@ -192,6 +193,19 @@ local function operating_state_init()
SUPPORTED_OPERATIONAL_STATE_COMMAND
)
})
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
capabilities.robotCleanerOperatingState.supportedCommands(
{
capabilities.robotCleanerOperatingState.commands.pause.NAME,
capabilities.robotCleanerOperatingState.commands.start.NAME,
capabilities.robotCleanerOperatingState.commands.goHome.NAME
},
{visibility = {displayed = false}}
)
)
)
test.socket.capability:__expect_send(
mock_device:generate_test_message(
"main",
Expand Down
Loading