Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matter virtual lights #19511

Merged
merged 1 commit into from
Sep 13, 2023
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
3 changes: 2 additions & 1 deletion lib/libesp32/berry_matter/src/be_matter_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Plugin_9_Virt_Light1.h"
#include "solidify/solidified_Matter_Plugin_4_Light2.h"
#include "solidify/solidified_Matter_Plugin_9_Virt_Light2.h"
#include "solidify/solidified_Matter_Plugin_3_Light3.h"
#include "solidify/solidified_Matter_Plugin_4_Light3.h"
#include "solidify/solidified_Matter_Plugin_9_Virt_Light3.h"
#include "solidify/solidified_Matter_Plugin_2_Shutter.h"
#include "solidify/solidified_Matter_Plugin_3_ShutterTilt.h"
Expand Down Expand Up @@ -296,6 +296,7 @@ module matter (scope: global, strings: weak) {
jitter, closure(matter_jitter_closure)
inspect, closure(matter_inspect_closure)
consolidate_clusters, closure(matter_consolidate_clusters_closure)
UC_LIST, closure(matter_UC_LIST_closure)
Profiler, class(be_class_Matter_Profiler)

// Status codes
Expand Down
11 changes: 11 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_0_Inspect.be
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,14 @@ def consolidate_clusters(cl, m)
return ret
end
matter.consolidate_clusters = consolidate_clusters

#############################################################
# consolidate_update_commands_list
#
# Build a consolidated list and remove duplicates
#@ solidify:matter.UC_LIST,weak
def UC_LIST(cl, *l)
var uc_parent = super(cl).UPDATE_COMMANDS
return uc_parent + l
end
matter.UC_LIST = UC_LIST
123 changes: 94 additions & 29 deletions lib/libesp32/berry_matter/src/embedded/Matter_Device.be
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,7 @@ class Matter_Device
def register_commands()
tasmota.add_cmd("MtrJoin", /cmd_found, idx, payload, payload_json -> self.MtrJoin(cmd_found, idx, payload, payload_json))
tasmota.add_cmd("MtrUpdate", /cmd_found, idx, payload, payload_json -> self.MtrUpdate(cmd_found, idx, payload, payload_json))
tasmota.add_cmd("MtrInfo", /cmd_found, idx, payload, payload_json -> self.MtrInfo(cmd_found, idx, payload, payload_json))
end

#####################################################################
Expand All @@ -1553,47 +1554,111 @@ class Matter_Device
# `MtrUpdate`
#
# MtrUpdate {"ep":1, "Power":1}
# MtrUpdate {"Name":"ep1", "Power":1}
# MtrUpdate {"Name":"My_virtual_light", "Power":1}
# MtrUpdate {"name":"ep1", "power":1}
# MtrUpdate {"Name":"Light0", "Power":0}
# MtrUpdate {"Name":"Light0", "Power":1}
# MtrUpdate {"Name":"Light1", "Power":0}
# MtrUpdate {"Name":"Light1", "Power":1,"Bri":55}
# MtrUpdate {"Name":"Light2", "Power":0}
# MtrUpdate {"Name":"Light2", "Power":1, "CT":400, "Bri":20}
# MtrUpdate {"Name":"Light3", "Power":0}
# MtrUpdate {"Name":"Light3", "Power":1, "Bri":20, "Hue":85, "Sat":200}
#
def MtrUpdate(cmd_found, idx, payload, payload_json)
if payload_json == nil return tasmota.resp_cmnd("Invalid JSON") end
if payload_json == nil return tasmota.resp_cmnd_str("Invalid JSON") end

var key_ep = tasmota.find_key_i(payload_json, 'Ep')
var key_name = tasmota.find_key_i(payload_json, 'Name')
if key_ep || key_name
var pl = nil # plugin instance

if key_ep
var ep = int(payload_json[key_ep])
if ep <= 0 return tasmota.resp_cmnd_str("Invalid 'Ep' attribute") end
pl = self.find_plugin_by_endpoint(ep)
payload_json.remove(key_ep)
end

if key_name
if pl == nil
pl = self.find_plugin_by_friendly_name(payload_json[key_name])
end
payload_json.remove(key_name)
end

var key_i
if (key_i := tasmota.find_key_i(payload_json, 'Device')) != nil
var pl = self.find_plugin_by_name_or_ep(payload[key_i])
if (pl == nil) return tasmota.resp_cmnd("Invalid Device") end
if (!pl.virtual) return tasmota.resp_cmnd("Device is not virtual") end
# find endpoint (plugin) by name
# can be:
# - integer: endpoint number
# - "ep<n>": endpoint number
# - "<name>": friendly name for endpoint
if (pl == nil) return tasmota.resp_cmnd_str("Invalid Device") end
if (!pl.virtual) return tasmota.resp_cmnd_str("Device is not virtual") end
# filter parameter accedpted by plugin, and rename with canonical
# Ex: {"power":1,"HUE":2} becomes {"Power":1,"Hue":2}
var uc = pl.consolidate_update_commands()
# check that all commands are in the list of supported commands
var cmd_cleaned = {}
for k: payload_json.keys()
var cleaned_command_idx = tasmota.find_list_i(uc, k)
if (cleaned_command_idx == nil)
tasmota.resp_cmnd_str(f"Invalid command '{payload_json[k]}'")
return
end
cmd_cleaned[uc[cleaned_command_idx]] = payload_json[k]
end
# call plug-in
pl.update_virtual(cmd_cleaned)
var state_json = pl.state_json()
if state_json
var cmnd_status = f'{{"{cmd_found}":{state_json}}}'
return tasmota.resp_cmnd(cmnd_status)
else
return tasmota.resp_cmnd_done()
end
end

tasmota.resp_cmnd_done()
tasmota.resp_cmnd_str("Missing 'Device' attribute")
end

#####################################################################
# find_plugin_by_name_or_ep
# `MtrInfo`
#
# `name`can be:
# - integer: endpoint number
# - "ep<n>": endpoint number
# - "<name>": friendly name for endpoint
def find_plugin_by_name_or_ep(name)
if type(name) == 'int'
if (name > 0) return self.find_plugin_by_endpoint(name) end
elif type(name) == 'string'
if name[0..1] == "ep"
var ep_num = int(name[2..])
if ep_num > 0 return self.find_plugin_by_endpoint(ep_num) end
else
return self.find_plugin_by_friendly_name(name)
# MtrInfo 9
def MtrInfo(cmd_found, idx, payload, payload_json)
if payload == ""
# dump all devices
end

if payload == ""
# dump all
for pl: self.plugins
self.MtrInfo_one(pl.endpoint)
end

elif type(payload_json) == 'int'
# try ep number
self.MtrInfo_one(payload_json)

else
# try by name
var pl = self.find_plugin_by_friendly_name(payload)
if pl != nil
self.MtrInfo_one(pl.endpoint)
end
end
return nil # invalid type

tasmota.resp_cmnd_done()
end

# output for a single endpoint
def MtrInfo_one(ep)
var pl = self.find_plugin_by_endpoint(ep)
if pl == nil return end # abort

var state_json = pl.state_json()
if state_json
var mtr_info = f'{{"' 'MtrInfo"' ':{state_json}}}'
# publish
# tasmota.publish_rule(mtr_info)
tasmota.publish_result(mtr_info, "")
end
end

end
matter.Device = Matter_Device

Expand Down
47 changes: 47 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ class Matter_Plugin
0x001D: [0,1,2,3,0xFFFC,0xFFFD], # Descriptor Cluster 9.5 p.453
0x0039: [0x11], # Bridged Device Basic Information 9.13 p.485
}
# Accepted Update commands for virtual devices
static var UPDATE_COMMANDS = []
var device # reference to the `device` global object
var endpoint # current endpoint
var clusters # map from cluster to list of attributes, typically constructed from CLUSTERS hierachy
var tick # tick value when it was last updated
var node_label # name of the endpoint, used only in bridge mode, "" if none
var virtual # (bool) is the device pure virtual (i.e. not related to a device implementation by Tasmota)

#############################################################
# MVC Model
Expand All @@ -63,6 +66,7 @@ class Matter_Plugin
self.clusters = self.consolidate_clusters()
self.parse_configuration(config)
self.node_label = config.find("name", "")
self.virtual = false
end

# proxy for the same method in IM
Expand Down Expand Up @@ -149,6 +153,14 @@ class Matter_Plugin
# return ret
end

#############################################################
# consolidate_update_commands
#
# Return consolidated "update commands" for this class
def consolidate_update_commands()
return self.UPDATE_COMMANDS
end

#############################################################
# Publish to MQTT a command received from controller
#
Expand Down Expand Up @@ -370,6 +382,41 @@ class Matter_Plugin
return conf
end

#############################################################
# append_state_json
#
# Output the current state in JSON
# Takes the JSON string prefix
# New values need to be appended with `,"key":value` (including prefix comma)
def append_state_json()
return ""
end

# This is to be called by matter_device to get the full state JSON
# including "Ep":<ep>,"Name"="<friendly_name"
def state_json()
import json
var ep_name = self.node_label ? f',"Name":{json.dump(self.node_label)}' : ""
var state = self.append_state_json()
if state
var ret = f'{{"Ep":{self.endpoint:i}{ep_name}{state}}}'
return ret
else
return nil
end
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
# The map is pre-cleaned and contains only keys declared in
# `self.UPDATE_COMMANDS` with the adequate case
# (no need to handle case-insensitive)
def update_virtual(payload_json)
# pass
end

end

matter.Plugin = Matter_Plugin
17 changes: 1 addition & 16 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Device.be
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,7 @@ class Matter_Plugin_Device : Matter_Plugin
# var clusters # map from cluster to list of attributes, typically constructed from CLUSTERS hierachy
# var tick # tick value when it was last updated
# var node_label # name of the endpoint, used only in bridge mode, "" if none
var virtual # (bool) is the device pure virtual (i.e. not related to a device implementation by Tasmota)

#############################################################
# Constructor
def init(device, endpoint, config)
self.virtual = config.find("virtual", false)
super(self).init(device, endpoint, config)
end
# var virtual # (bool) is the device pure virtual (i.e. not related to a device implementation by Tasmota)

#############################################################
# read an attribute
Expand Down Expand Up @@ -179,13 +172,5 @@ class Matter_Plugin_Device : Matter_Plugin
end
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload_json)
# pass
end

end
matter.Plugin_Device = Matter_Plugin_Device
12 changes: 11 additions & 1 deletion lib/libesp32/berry_matter/src/embedded/Matter_Plugin_2_Light0.be
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Matter_Plugin_Light0 : Matter_Plugin_Device
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
0x0006: [0,0xFFFC,0xFFFD], # On/Off 1.5 p.48
})
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "Power")
static var TYPES = { 0x0100: 2 } # OnOff Light, but not actually used because Relay is managed by OnOff

# Inherited
Expand Down Expand Up @@ -145,12 +146,21 @@ class Matter_Plugin_Light0 : Matter_Plugin_Device
return m.find(key_i)
end

#############################################################
# append_state_json
#
# Output the current state in JSON
# New values need to be appended with `,"key":value` (including prefix comma)
def append_state_json()
return f',"Power":{int(self.shadow_onoff)}'
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload_json)
var val_onoff = self.find_val_i(payload_json, 'Power')
var val_onoff = payload_json.find("Power")
if val_onoff != nil
self.set_onoff(bool(val_onoff))
end
Expand Down
23 changes: 23 additions & 0 deletions lib/libesp32/berry_matter/src/embedded/Matter_Plugin_2_OnOff.be
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Matter_Plugin_OnOff : Matter_Plugin_Device
# 0x0005: inherited # Scenes 1.4 p.30 - no writable
0x0006: [0,0xFFFC,0xFFFD], # On/Off 1.5 p.48
})
static var UPDATE_COMMANDS = matter.UC_LIST(_class, "Power")
static var TYPES = { 0x010A: 2 } # On/Off Plug-in Unit

# Inherited
Expand Down Expand Up @@ -149,5 +150,27 @@ class Matter_Plugin_OnOff : Matter_Plugin_Device

end

#############################################################
# append_state_json
#
# Output the current state in JSON
# Takes the JSON string prefix
# New values need to be appended with `,"key":value` (including prefix comma)
def append_state_json()
return f',"Power":{int(self.shadow_onoff)}'
end

#############################################################
# update_virtual
#
# Update internal state for virtual devices
def update_virtual(payload_json)
var val_onoff = payload_json.find("Power")
if val_onoff != nil
self.set_onoff(bool(val_onoff))
end
super(self).update_virtual(payload_json)
end

end
matter.Plugin_OnOff = Matter_Plugin_OnOff
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,14 @@ class Matter_Plugin_Shutter : Matter_Plugin_Device
end
end

#############################################################
# append_state_json
#
# Output the current state in JSON
# New values need to be appended with `,"key":value` (including prefix comma)
def append_state_json(payload_str)
return f',"ShutterPos":{self.shadow_shutter_pos},"ShutterTarget":{self.shadow_shutter_target}'
end

end
matter.Plugin_Shutter = Matter_Plugin_Shutter
Loading