diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e2240264..0f1d9338c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Support for PMSA003I Air Quality Sensor by Jean-Pierre Deschamps (#18214)
- Support for DingTian virtual switch/button/relay (#18223)
- Berry add `mdns.remove_service()`
+- Matter support simple Relay on Apple Homekit
### Breaking Changed
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be b/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be
index 96cf863d2..9d5fed265 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be
@@ -110,8 +110,8 @@ class Matter_Commisioning_Context
# generate 32 bytes random
pbkdfparamresp.responderRandom = crypto.random(32)
pbkdfparamresp.responderSessionId = self.future_local_session_id
- pbkdfparamresp.pbkdf_parameters_salt = self.device.salt
- pbkdfparamresp.pbkdf_parameters_iterations = self.device.iterations
+ pbkdfparamresp.pbkdf_parameters_salt = self.device.commissioning_salt
+ pbkdfparamresp.pbkdf_parameters_iterations = self.device.commissioning_iterations
tasmota.log("MTR: pbkdfparamresp: " + str(matter.inspect(pbkdfparamresp)), 4)
var pbkdfparamresp_raw = pbkdfparamresp.encode()
tasmota.log("MTR: pbkdfparamresp_raw: " + pbkdfparamresp_raw.tohex(), 4)
@@ -138,7 +138,8 @@ class Matter_Commisioning_Context
tasmota.log("MTR: spake: " + matter.inspect(self.spake), 4)
# instanciate SPAKE
- self.spake = crypto.SPAKE2P_Matter(self.device.w0, self.device.w1, self.device.L)
+ # for testing purpose, we don't send `w1` to make sure
+ self.spake = crypto.SPAKE2P_Matter(self.device.commissioning_w0, nil, self.device.commissioning_L)
# compute pB
self.spake.compute_pB(self.y)
self.pB = self.spake.pB
@@ -288,7 +289,7 @@ class Matter_Commisioning_Context
session._fabric = fabric
end
if session == nil || session._fabric == nil raise "valuer_error", "StatusReport(GeneralCode: FAILURE, ProtocolId: SECURE_CHANNEL, ProtocolCode: NO_SHARED_TRUST_ROOTS)" end
- session.source_node_id = msg.source_node_id
+ session._source_node_id = msg.source_node_id
session.set_mode_CASE()
if msg.session != session
@@ -559,8 +560,9 @@ class Matter_Commisioning_Context
session.close()
session.set_keys(i2r, r2i, ac, created)
-
+
# CASE Session completed, persist it
+ session._breadcrumb = 0 # clear breadcrumb
session.set_persist(true) # keep session on flash
session.set_no_expiration() # never expire
session.persist_to_fabric()
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Device.be b/lib/libesp32/berry_matter/src/embedded/Matter_Device.be
index b541c07d4..5d9c8bb8b 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Device.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Device.be
@@ -26,6 +26,7 @@ class Matter_Device
static var VENDOR_ID = 0xFFF1
static var PRODUCT_ID = 0x8000
static var FILENAME = "_matter_device.json"
+ static var PASE_TIMEOUT = 10*60 # default open commissioning window (10 minutes)
var plugins # list of plugins
var udp_server # `matter.UDPServer()` object
var message_handler # `matter.MessageHandler()` object
@@ -33,6 +34,13 @@ class Matter_Device
var ui
# Commissioning open
var commissioning_open # timestamp for timeout of commissioning (millis()) or `nil` if closed
+ var commissioning_iterations # current PBKDF number of iterations
+ var commissioning_discriminator # current discriminator
+ var commissioning_salt # current salt
+ var commissioning_w0 # current w0
+ # var commissioning_w1 # current w1
+ var commissioning_L # current L
+ var commissioning_admin_fabric # the fabric that opened the currint commissioning window, or `nil` for default
# information about the device
var commissioning_instance_wifi # random instance name for commissioning
var commissioning_instance_eth # random instance name for commissioning
@@ -40,15 +48,20 @@ class Matter_Device
var hostname_eth # MAC-derived hostname for commissioning
var vendorid
var productid
+ # MDNS active announces
+ var mdns_pase_eth # do we have an active PASE mdns announce for eth
+ var mdns_pase_wifi # do we have an active PASE mdns announce for wifi
# saved in parameters
- var discriminator
- var passcode
+ var root_discriminator
+ var root_passcode
var ipv4only # advertize only IPv4 addresses (no IPv6)
# context for PBKDF
- var iterations
+ var root_iterations
# PBKDF information used only during PASE (freed afterwards)
- var salt
- var w0, w1, L
+ var root_salt
+ var root_w0
+ # var root_w1
+ var root_L
#############################################################
def init()
@@ -62,9 +75,11 @@ class Matter_Device
self.plugins = []
self.vendorid = self.VENDOR_ID
self.productid = self.PRODUCT_ID
- self.iterations = self.PBKDF_ITERATIONS
+ self.root_iterations = self.PBKDF_ITERATIONS
+ self.root_salt = crypto.random(16) # bytes("5350414B453250204B65792053616C74")
self.ipv4only = false
self.load_param()
+
self.commissioning_instance_wifi = crypto.random(8).tohex() # 16 characters random hostname
self.commissioning_instance_eth = crypto.random(8).tohex() # 16 characters random hostname
@@ -74,8 +89,8 @@ class Matter_Device
self.ui = matter.UI(self)
# add the default plugin
- self.plugins.push(matter.Plugin_Root(self))
- self.plugins.push(matter.Plugin_OnOff(self))
+ self.plugins.push(matter.Plugin_Root(self), 0)
+ self.plugins.push(matter.Plugin_OnOff(self), 1, 0 #-tasmota relay 1-#)
self.start_mdns_announce_hostnames()
@@ -106,23 +121,71 @@ class Matter_Device
#############################################################
# Start Basic Commissioning Window
def init_basic_commissioning()
- # compute PBKDF
- self.compute_pbkdf(self.passcode)
-
# if no fabric is configured, automatically open commissioning at restart
if self.sessions.count_active_fabrics() == 0
- self.start_basic_commissioning()
+ self.start_root_basic_commissioning()
end
end
+ def start_root_basic_commissioning(timeout_s)
+ if timeout_s == nil timeout_s = self.PASE_TIMEOUT end
+ # compute PBKDF
+ self.compute_pbkdf(self.root_passcode, self.root_iterations, self.root_salt)
+ self.start_basic_commissioning(timeout_s, self.root_iterations, self.root_discriminator, self.root_salt, self.root_w0, #-self.root_w1,-# self.root_L, nil)
+ end
+
+ #####################################################################
+ # Remove a fabric and clean all corresponding values and MDNS entries
+ def remove_fabric(fabric)
+ self.message_handler.im.subs.remove_by_fabric(fabric)
+ self.sessions.remove_fabric(fabric)
+ self.sessions.save_fabrics()
+ # TODO remove MDNS entries
+ end
+
#############################################################
# Start Basic Commissioning Window
- def start_basic_commissioning()
- self.commissioning_open = tasmota.millis() + 10 * 60 * 1000
+ def start_basic_commissioning(timeout_s, iterations, discriminator, salt, w0, #-w1,-# L, admin_fabric)
+ self.commissioning_open = tasmota.millis() + timeout_s * 1000
+ self.commissioning_iterations = iterations
+ self.commissioning_discriminator = discriminator
+ self.commissioning_salt = salt
+ self.commissioning_w0 = w0
+ # self.commissioning_w1 = w1
+ self.commissioning_L = L
+ self.commissioning_admin_fabric = admin_fabric
+
+ if tasmota.wifi()['up'] || tasmota.eth()['up']
+ self.mdns_announce_PASE()
+ else
+ tasmota.add_rule("Wifi#Connected", def ()
+ self.mdns_announce_PASE()
+ tasmota.remove_rule("Wifi#Connected", "mdns_announce_PASE")
+ end, "mdns_announce_PASE")
+ tasmota.add_rule("Eth#Connected", def ()
+ self.mdns_announce_PASE()
+ tasmota.remove_rule("Eth#Connected", "mdns_announce_PASE")
+ end, "mdns_announce_PASE")
+ end
+ end
+
+ def is_root_commissioning_open()
+ return self.commissioning_open != nil && self.commissioning_admin_fabric == nil
end
def stop_basic_commissioning()
self.commissioning_open = nil
+
+ self.mdns_remove_PASE()
+
+ # clear any PBKDF information to free memory
+ self.commissioning_iterations = nil
+ self.commissioning_discriminator = nil
+ self.commissioning_salt = nil
+ self.commissioning_w0 = nil
+ # self.commissioning_w1 = nil
+ self.commissioning_L = nil
+ self.commissioning_admin_fabric = nil
end
def is_commissioning_open()
return self.commissioning_open != nil
@@ -133,27 +196,26 @@ class Matter_Device
#############################################################
# Compute the PBKDF parameters for SPAKE2+
#
- # iterations is set to 1000 which is large enough
- def compute_pbkdf(passcode_int)
+ def compute_pbkdf(passcode_int, iterations, salt)
import crypto
import string
- self.salt = crypto.random(16) # bytes("5350414B453250204B65792053616C74")
var passcode = bytes().add(passcode_int, 4)
- var tv = crypto.PBKDF2_HMAC_SHA256().derive(passcode, self.salt, self.iterations, 80)
+ var tv = crypto.PBKDF2_HMAC_SHA256().derive(passcode, salt, iterations, 80)
var w0s = tv[0..39]
var w1s = tv[40..79]
- self.w0 = crypto.EC_P256().mod(w0s)
- self.w1 = crypto.EC_P256().mod(w1s)
- self.L = crypto.EC_P256().public_key(self.w1)
+ self.root_w0 = crypto.EC_P256().mod(w0s)
+ var w1 = crypto.EC_P256().mod(w1s) # w1 is temporarily computed then discarded
+ # self.root_w1 = crypto.EC_P256().mod(w1s)
+ self.root_L = crypto.EC_P256().public_key(w1)
tasmota.log("MTR: ******************************", 4)
- tasmota.log("MTR: salt = " + self.salt.tohex(), 4)
+ tasmota.log("MTR: salt = " + self.root_salt.tohex(), 4)
tasmota.log("MTR: passcode_hex = " + passcode.tohex(), 4)
- tasmota.log("MTR: w0 = " + self.w0.tohex(), 4)
- tasmota.log("MTR: w1 = " + self.w1.tohex(), 4)
- tasmota.log("MTR: L = " + self.L.tohex(), 4)
+ tasmota.log("MTR: w0 = " + self.root_w0.tohex(), 4)
+ # tasmota.log("MTR: w1 = " + self.root_w1.tohex(), 4)
+ tasmota.log("MTR: L = " + self.root_L.tohex(), 4)
tasmota.log("MTR: ******************************", 4)
# show Manual pairing code in logs
@@ -162,7 +224,7 @@ class Matter_Device
end
#############################################################
- # compute QR Code content
+ # compute QR Code content - can be done only for root PASE
def compute_qrcode_content()
var raw = bytes().resize(11) # we don't use TLV Data so it's only 88 bits or 11 bytes
# version is `000` dont touch
@@ -170,8 +232,8 @@ class Matter_Device
raw.setbits(19, 16, self.productid)
# custom flow = 0 (offset=35, len=2)
raw.setbits(37, 8, 0x04) # already on IP network
- raw.setbits(45, 12, self.discriminator & 0xFFF)
- raw.setbits(57, 27, self.passcode & 0x7FFFFFF)
+ raw.setbits(45, 12, self.root_discriminator & 0xFFF)
+ raw.setbits(57, 27, self.root_passcode & 0x7FFFFFF)
# padding (offset=84 len=4)
return "MT:" + matter.Base38.encode(raw)
end
@@ -179,11 +241,12 @@ class Matter_Device
#############################################################
# compute the 11 digits manual pairing code (wihout vendorid nor productid) p.223
+ # can be done only for root PASE (we need the passcode, but we don't get it with OpenCommissioningWindow command)
def compute_manual_pairing_code()
import string
- var digit_1 = (self.discriminator & 0x0FFF) >> 10
- var digit_2_6 = ((self.discriminator & 0x0300) << 6) | (self.passcode & 0x3FFF)
- var digit_7_10 = (self.passcode >> 14)
+ var digit_1 = (self.root_discriminator & 0x0FFF) >> 10
+ var digit_2_6 = ((self.root_discriminator & 0x0300) << 6) | (self.root_passcode & 0x3FFF)
+ var digit_7_10 = (self.root_passcode >> 14)
var ret = string.format("%1i%05i%04i", digit_1, digit_2_6, digit_7_10)
ret += matter.Verhoeff.checksum(ret)
@@ -198,6 +261,12 @@ class Matter_Device
if self.commissioning_open != nil && tasmota.time_reached(self.commissioning_open) # timeout reached, close provisioning
self.commissioning_open = nil
end
+ # call all plugins
+ var idx = 0
+ while idx < size(self.plugins)
+ self.plugins[idx].every_second()
+ idx += 1
+ end
end
#############################################################
@@ -257,11 +326,11 @@ class Matter_Device
import mdns
import string
+ self.stop_basic_commissioning() # close all PASE commissioning information
# clear any PBKDF information to free memory
- self.salt = nil
- self.w0 = nil
- self.w1 = nil
- self.L = nil
+ self.root_w0 = nil
+ # self.root_w1 = nil
+ self.root_L = nil
# we keep the PASE session for 1 minute
session.set_expire_in_seconds(60)
@@ -456,7 +525,7 @@ class Matter_Device
#
def save_param()
import json
- var j = json.dump({'distinguish':self.discriminator, 'passcode':self.passcode, 'ipv4only':self.ipv4only})
+ var j = json.dump({'distinguish':self.root_discriminator, 'passcode':self.root_passcode, 'ipv4only':self.ipv4only})
try
import string
var f = open(self.FILENAME, "w")
@@ -482,8 +551,8 @@ class Matter_Device
import json
var j = json.load(s)
- self.discriminator = j.find("distinguish", self.discriminator)
- self.passcode = j.find("passcode", self.passcode)
+ self.root_discriminator = j.find("distinguish", self.root_discriminator)
+ self.root_passcode = j.find("passcode", self.root_passcode)
self.ipv4only = bool(j.find("ipv4only", false))
except .. as e, m
if e != "io_error"
@@ -492,12 +561,12 @@ class Matter_Device
end
var dirty = false
- if self.discriminator == nil
- self.discriminator = crypto.random(2).get(0,2) & 0xFFF
+ if self.root_discriminator == nil
+ self.root_discriminator = crypto.random(2).get(0,2) & 0xFFF
dirty = true
end
- if self.passcode == nil
- self.passcode = self.PASSCODE_DEFAULT
+ if self.root_passcode == nil
+ self.root_passcode = self.PASSCODE_DEFAULT
dirty = true
end
if dirty self.save_param() end
@@ -533,112 +602,157 @@ class Matter_Device
# are defined
def start_mdns_announce_hostnames()
if tasmota.wifi()['up']
- self._start_mdns_announce(false)
+ self._mdns_announce_hostname(false)
else
tasmota.add_rule("Wifi#Connected", def ()
- self._start_mdns_announce(false)
+ self._mdns_announce_hostname(false)
tasmota.remove_rule("Wifi#Connected", "matter_mdns_host")
end, "matter_mdns_host")
end
if tasmota.eth()['up']
- self._start_mdns_announce(true)
+ self._mdns_announce_hostname(true)
else
tasmota.add_rule("Eth#Connected", def ()
- self._start_mdns_announce(true)
+ self._mdns_announce_hostname(true)
tasmota.remove_rule("Eth#Connected", "matter_mdns_host")
end, "matter_mdns_host")
end
end
#############################################################
- # Start UDP mDNS announcements for commissioning
+ # Start UDP mDNS announcements hostname
+ # This announcement is independant from commissioning windows
#
# eth is `true` if ethernet turned up, `false` is wifi turned up
- def _start_mdns_announce(is_eth)
+ def _mdns_announce_hostname(is_eth)
import mdns
import string
mdns.start()
+ try
+ if is_eth
+ # Add Hostname (based on MAC) with IPv4/IPv6 addresses
+ var eth = tasmota.eth()
+ self.hostname_eth = string.replace(eth.find("mac"), ':', '')
+ if !self.ipv4only
+ tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_eth, eth.find('ip6local',''), eth.find('ip','')), 3)
+ mdns.add_hostname(self.hostname_eth, eth.find('ip6local',''), eth.find('ip',''), eth.find('ip6',''))
+ else
+ tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s)", self.hostname_eth, eth.find('ip','')), 3)
+ mdns.add_hostname(self.hostname_eth, eth.find('ip',''))
+ end
+ else
+ var wifi = tasmota.wifi()
+ self.hostname_wifi = string.replace(wifi.find("mac"), ':', '')
+ if !self.ipv4only
+ tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip','')), 3)
+ mdns.add_hostname(self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip',''), wifi.find('ip6',''))
+ else
+ tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s)", self.hostname_eth, wifi.find('ip','')), 3)
+ mdns.add_hostname(self.hostname_wifi, wifi.find('ip',''))
+ end
+ end
+ tasmota.log(string.format("MTR: start mDNS on %s host '%s.local'", is_eth ? "eth" : "wifi", is_eth ? self.hostname_eth : self.hostname_wifi), 2)
+ except .. as e, m
+ tasmota.log("MTR: Exception" + str(e) + "|" + str(m), 2)
+ end
+
+ self.mdns_announce_op_discovery_all_fabrics()
+ end
+
+ #############################################################
+ # Announce MDNS for PASE commissioning
+ #
+ # eth is `true` if ethernet turned up, `false` is wifi turned up
+ def mdns_announce_PASE()
+ import mdns
+ import string
+
var services = {
- # "VP":str(self.vendorid) + "+" + str(self.productid),
- "D": self.discriminator,
+ "VP":str(self.vendorid) + "+" + str(self.productid),
+ "D": self.commissioning_discriminator,
"CM":1, # requires passcode
"T":0, # no support for TCP
"SII":5000, "SAI":300
}
- # mdns
try
- if is_eth
- var eth = tasmota.eth()
- self.hostname_eth = string.replace(eth.find("mac"), ':', '')
- if !self.ipv4only
- # mdns.add_hostname(self.hostname_eth, eth.find('ip6local',''), eth.find('ip',''), eth.find('ip6',''))
- tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_eth, eth.find('ip6local',''), eth.find('ip','')), 3)
- mdns.add_hostname(self.hostname_eth, eth.find('ip6local',''), eth.find('ip',''))
- else
- tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s)", self.hostname_eth, eth.find('ip','')), 3)
- mdns.add_hostname(self.hostname_eth, eth.find('ip',''))
- end
+ if self.hostname_eth
+ # Add Matter `_matterc._udp` service
tasmota.log(string.format("MTR: calling mdns.add_service(%s, %s, %i, %s, %s, %s)", "_matterc", "_udp", 5540, str(services), self.commissioning_instance_eth, self.hostname_eth), 3)
mdns.add_service("_matterc", "_udp", 5540, services, self.commissioning_instance_eth, self.hostname_eth)
+ self.mdns_pase_eth = true
- tasmota.log(string.format("MTR: starting mDNS on %s '%s' ptr to `%s.local`", is_eth ? "eth" : "wifi",
- is_eth ? self.commissioning_instance_eth : self.commissioning_instance_wifi,
- is_eth ? self.hostname_eth : self.hostname_wifi), 2)
+ tasmota.log(string.format("MTR: announce mDNS on %s '%s' ptr to `%s.local`", "eth", self.commissioning_instance_eth, self.hostname_eth), 2)
# `mdns.add_subtype(service:string, proto:string, instance:string, hostname:string, subtype:string) -> nil`
- var subtype = "_L" + str(self.discriminator & 0xFFF)
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ var subtype = "_L" + str(self.commissioning_discriminator & 0xFFF)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
- subtype = "_S" + str((self.discriminator & 0xF00) >> 8)
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ subtype = "_S" + str((self.commissioning_discriminator & 0xF00) >> 8)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_V" + str(self.vendorid)
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_CM1"
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
- else
- var wifi = tasmota.wifi()
- self.hostname_wifi = string.replace(wifi.find("mac"), ':', '')
- if !self.ipv4only
- mdns.add_hostname(self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip',''), wifi.find('ip6',''))
- tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip','')), 3)
- mdns.add_hostname(self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip',''))
- else
- tasmota.log(string.format("MTR: calling mdns.add_hostname(%s, %s)", self.hostname_eth, wifi.find('ip','')), 3)
- mdns.add_hostname(self.hostname_wifi, wifi.find('ip',''))
- end
+ end
+ if self.hostname_wifi
+
tasmota.log(string.format("MTR: calling mdns.add_service(%s, %s, %i, %s, %s, %s)", "_matterc", "_udp", 5540, str(services), self.commissioning_instance_wifi, self.hostname_wifi), 3)
mdns.add_service("_matterc", "_udp", 5540, services, self.commissioning_instance_wifi, self.hostname_wifi)
+ self.mdns_pase_wifi = true
- tasmota.log(string.format("MTR: starting mDNS on %s '%s' ptr to `%s.local`", is_eth ? "eth" : "wifi",
- is_eth ? self.commissioning_instance_eth : self.commissioning_instance_wifi,
- is_eth ? self.hostname_eth : self.hostname_wifi), 2)
+ tasmota.log(string.format("MTR: starting mDNS on %s '%s' ptr to `%s.local`", "wifi", self.commissioning_instance_wifi, self.hostname_wifi), 2)
# `mdns.add_subtype(service:string, proto:string, instance:string, hostname:string, subtype:string) -> nil`
- var subtype = "_L" + str(self.discriminator & 0xFFF)
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ var subtype = "_L" + str(self.commissioning_discriminator & 0xFFF)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
- subtype = "_S" + str((self.discriminator & 0xF00) >> 8)
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ subtype = "_S" + str((self.commissioning_discriminator & 0xF00) >> 8)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
+ mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
+ subtype = "_V" + str(self.vendorid)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
- # subtype = "_V" + str(self.vendorid)
- # tasmota.log("MTR: adding subtype: "+subtype, 3)
- # mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_CM1"
- tasmota.log("MTR: adding subtype: "+subtype, 3)
+ tasmota.log("MTR: adding subtype: "+subtype, 2)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
end
except .. as e, m
tasmota.log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
- self.mdns_announce_op_discovery_all_fabrics()
+ end
+
+ #############################################################
+ # MDNS remove any PASE announce
+ #
+ # eth is `true` if ethernet turned up, `false` is wifi turned up
+ def mdns_remove_PASE()
+ import mdns
+ import string
+
+ try
+ if self.mdns_pase_eth
+ tasmota.log(string.format("MTR: calling mdns.remove_service(%s, %s, %s, %s)", "_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth), 3)
+ tasmota.log(string.format("MTR: remove mdns on %s '%s'", "eth", self.commissioning_instance_eth), 2)
+ self.mdns_pase_eth = false
+ mdns.remove_service("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth)
+ end
+ if self.mdns_pase_wifi
+ tasmota.log(string.format("MTR: calling mdns.remove_service(%s, %s, %s, %s)", "_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi), 3)
+ tasmota.log(string.format("MTR: remove mdns on %s '%s'", "wifi", self.commissioning_instance_wifi), 2)
+ self.mdns_pase_wifi = false
+ mdns.remove_service("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi)
+ end
+ except .. as e, m
+ tasmota.log("MTR: Exception" + str(e) + "|" + str(m), 2)
+ end
end
#############################################################
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_IM.be b/lib/libesp32/berry_matter/src/embedded/Matter_IM.be
index 75972787e..29746c6ca 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_IM.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_IM.be
@@ -273,8 +273,8 @@ class Matter_IM
)
end
- tasmota.log("MTR: ReportDataMessage=" + str(ret), 3)
- tasmota.log("MTR: ReportDataMessageTLV=" + str(ret.to_TLV()), 3)
+ # tasmota.log("MTR: ReportDataMessage=" + str(ret), 3)
+ # tasmota.log("MTR: ReportDataMessageTLV=" + str(ret.to_TLV()), 3)
return ret
end
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be b/lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
index 5809d380d..8b9022eb9 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
@@ -193,6 +193,12 @@ class Matter_IM_Subscription_Shop
end
end
+ def remove_by_fabric(fabric)
+ for session: fabric._sessions
+ self.remove_by_session(session)
+ end
+ end
+
#############################################################
# dispatch every 250ms click to sub-objects that need it
def every_250ms()
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Message.be b/lib/libesp32/berry_matter/src/embedded/Matter_Message.be
index 5728f192e..c0d26621b 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Message.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Message.be
@@ -287,7 +287,7 @@ class Matter_Frame
resp.message_counter = self.session.counter_snd.next()
resp.local_session_id = self.session.initiator_session_id
else
- resp.message_counter = self.session.__counter_insecure_snd.next()
+ resp.message_counter = self.session._counter_insecure_snd.next()
resp.local_session_id = 0
end
@@ -330,7 +330,7 @@ class Matter_Frame
resp.message_counter = session.counter_snd.next()
resp.local_session_id = session.initiator_session_id
else
- resp.message_counter = session.__counter_insecure_snd.next()
+ resp.message_counter = session._counter_insecure_snd.next()
resp.local_session_id = 0
end
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_MessageHandler.be b/lib/libesp32/berry_matter/src/embedded/Matter_MessageHandler.be
index 2f21cba6a..b7fde4077 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_MessageHandler.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_MessageHandler.be
@@ -28,15 +28,12 @@ class Matter_MessageHandler
# handlers
var commissioning
var im # handler for Interaction Model
- # counters
- var counter_rcv # Global Unencrypted Message Counter incoming
#############################################################
def init(device)
self.device = device
self.commissioning = matter.Commisioning_Context(self)
self.im = matter.IM(device)
- self.counter_rcv = matter.Counter()
end
#############################################################
@@ -61,15 +58,15 @@ class Matter_MessageHandler
#############################################################
### unencrypted session, handled by commissioning
var session = self.device.sessions.find_session_source_id_unsecure(frame.source_node_id, 90) # 90 seconds max
- tasmota.log("MTR: find session by source_node_id = " + str(frame.source_node_id) + "session_id = " + str(session.local_session_id), 3)
+ tasmota.log("MTR: find session by source_node_id = " + str(frame.source_node_id) + " session_id = " + str(session.local_session_id), 3)
if addr session._ip = addr end
if port session._port = port end
session._message_handler = self
frame.session = session
# check if it's a duplicate
- if !self.counter_rcv.validate(frame.message_counter, false)
- tasmota.log(string.format("MTR: rejected duplicate unencrypted message = %i ref = %i", frame.message_counter, self.counter_rcv.val()), 3)
+ if !session._counter_insecure_rcv.validate(frame.message_counter, false)
+ tasmota.log(string.format("MTR: rejected duplicate unencrypted message = %i ref = %i", frame.message_counter, session._counter_insecure_rcv.val()), 3)
return false
end
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin.be
index 4d2a856ba..ad01637bc 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin.be
@@ -26,13 +26,21 @@ class Matter_Plugin
static var EMPTY_LIST = []
static var EMPTY_MAP = {}
var device # reference to the `device` global object
- var endpoints # list of supported endpoints
+ var endpoints # list of supported endpoints TODO refactor
+ var endpoint # current endpoint
var clusters # map from cluster to list of attributes
+ #############################################################
+ # MVC Model
+ #
+ # Model linking the plugin to the Tasmota behavior
+ #############################################################
+
#############################################################
# Constructor
- def init(device)
+ def init(device, endpoint)
self.device = device
+ self.endpoint = endpoint
self.endpoints = self.EMPTY_LIST
self.clusters = self.EMPTY_LIST
end
@@ -68,6 +76,11 @@ class Matter_Plugin
return self.clusters.contains(cluster) && self.endpoints.find(endpoint) != nil
end
+ #############################################################
+ # MVC Model
+ #
+ # View reading attributes
+ #############################################################
#############################################################
# read attribute
def read_attribute(session, ctx)
@@ -95,12 +108,22 @@ class Matter_Plugin
return nil
end
+ #############################################################
+ # MVC Model
+ #
+ # Controller write attributes
+ #############################################################
#############################################################
# write attribute
def write_attribute(session, ctx, write_data)
return nil
end
+ #############################################################
+ # MVC Model
+ #
+ # Controller invoke request
+ #############################################################
#############################################################
# invoke command
def invoke_request(session, val, ctx)
@@ -113,5 +136,11 @@ class Matter_Plugin
def timed_request(session, val, ctx)
return nil
end
+
+ #############################################################
+ # every_second
+ def every_second()
+ end
end
+
matter.Plugin = Matter_Plugin
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_OnOff.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_OnOff.be
index 1bac296c3..ae7ed0b73 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_OnOff.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_OnOff.be
@@ -32,19 +32,46 @@ class Matter_Plugin_OnOff : Matter_Plugin
0x0004: [0,0xFFFC,0xFFFD], # Groups 1.3 p.21
0x0005: [0,1,2,3,4,5,0xFFFC,0xFFFD], # Scenes 1.4 p.30 - no writable
0x0006: [0,0xFFFC,0xFFFD], # On/Off 1.5 p.48
- 0x0008: [0,15,17,0xFFFC,0xFFFD] # Level Control 1.6 p.57
+ # 0x0008: [0,15,17,0xFFFC,0xFFFD] # Level Control 1.6 p.57
}
static var TYPES = { 0x010A: 2 } # On/Off Light
- var onoff # fake status for now # TODO
+ var tasmota_relay_index # Relay number in Tasmota (zero based)
+ var shadow_onoff # fake status for now # TODO
#############################################################
# Constructor
- def init(device)
- super(self).init(device)
+ def init(device, endpoint, tasmota_relay_index)
+ super(self).init(device, endpoint)
self.endpoints = self.ENDPOINTS
+ self.endpoint = self.ENDPOINTS[0] # TODO refactor endpoint management
self.clusters = self.CLUSTERS
- self.onoff = false # fake status for now # TODO
+ self.get_onoff() # read actual value
+ if tasmota_relay_index == nil tasmota_relay_index = 0 end
+ self.tasmota_relay_index = tasmota_relay_index
+ end
+
+ #############################################################
+ # Model
+ #
+ def set_onoff(v)
+ tasmota.set_power(self.tasmota_relay_index, bool(v))
+ self.get_onoff()
+ end
+ #############################################################
+ # get_onoff
+ #
+ # Update shadow and signal any change
+ def get_onoff()
+ var state = tasmota.get_power(self.tasmota_relay_index)
+ if state != nil
+ if self.shadow_onoff != nil && self.shadow_onoff != bool(state)
+ self.onoff_changed() # signal any change
+ end
+ self.shadow_onoff = state
+ end
+ if self.shadow_onoff == nil self.shadow_onoff = false end # avoid any `nil` value when initializing
+ return self.shadow_onoff
end
#############################################################
@@ -118,7 +145,7 @@ class Matter_Plugin_OnOff : Matter_Plugin
# ====================================================================================================
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
if attribute == 0x0000 # ---------- OnOff / bool ----------
- return TLV.create_TLV(TLV.BOOL, self.onoff)
+ return TLV.create_TLV(TLV.BOOL, self.get_onoff())
elif attribute == 0xFFFC # ---------- FeatureMap / map32 ----------
return TLV.create_TLV(TLV.U4, 0) # 0 = no Level Control for Lighting
elif attribute == 0xFFFD # ---------- ClusterRevision / u2 ----------
@@ -182,16 +209,13 @@ class Matter_Plugin_OnOff : Matter_Plugin
# ====================================================================================================
elif cluster == 0x0006 # ========== On/Off 1.5 p.48 ==========
if command == 0x0000 # ---------- Off ----------
- if self.onoff self.onoff_changed(ctx) end
- self.onoff = false
+ self.set_onoff(false)
return true
elif command == 0x0001 # ---------- On ----------
- if !self.onoff self.onoff_changed(ctx) end
- self.onoff = true
+ self.set_onoff(true)
return true
elif command == 0x0002 # ---------- Toggle ----------
- self.onoff_changed(ctx)
- self.onoff = !self.onoff
+ self.set_onoff(!self.get_onoff())
return true
end
# ====================================================================================================
@@ -218,10 +242,15 @@ class Matter_Plugin_OnOff : Matter_Plugin
#############################################################
# Signal that onoff attribute changed
- def onoff_changed(ctx)
- self.attribute_updated(ctx.endpoint, 0x0006, 0x0000)
+ def onoff_changed()
+ self.attribute_updated(self.endpoint, 0x0006, 0x0000)
end
+ #############################################################
+ # every_second
+ def every_second()
+ self.get_onoff() # force reading value and sending subscriptions
+ end
end
matter.Plugin_OnOff = Matter_Plugin_OnOff
\ No newline at end of file
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Root.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Root.be
index 36b98b260..9068e91cc 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Root.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_Root.be
@@ -29,7 +29,7 @@ class Matter_Plugin_Root : Matter_Plugin
static var CLUSTERS = {
0x001D: [0,1,2,3], # Descriptor Cluster 9.5 p.453
0x001F: [0,2,3,4], # Access Control Cluster, p.461
- 0x0028: [0,1,2,3,4,5,6,7,8,9,0x12,0x13],# Basic Information Cluster cluster 11.1 p.565
+ 0x0028: [0,1,2,3,4,5,6,7,8,9,0x0F,0x12,0x13],# Basic Information Cluster cluster 11.1 p.565
# 0x002A: [0,1,2,3], # OTA Software Update Requestor Cluster Definition 11.19.7 p.762
0x002B: [0,1], # Localization Configuration Cluster 11.3 p.580
0x002C: [0,1,2], # Time Format Localization Cluster 11.4 p.581
@@ -39,7 +39,7 @@ class Matter_Plugin_Root : Matter_Plugin
0x0033: [0,1,2,8], # General Diagnostics Cluster 11.11 p.642
0x0034: [], # Software Diagnostics Cluster 11.12 p.654
0x0038: [0,1,7], # Time Synchronization 11.16 p.689
- 0x003C: [], # Administrator Commissioning Cluster 11.18 p.725
+ 0x003C: [0,1,2], # Administrator Commissioning Cluster 11.18 p.725
0x003E: [0,1,2,3,4,5], # Node Operational Credentials Cluster 11.17 p.704
0x003F: [] # Group Key Management Cluster 11.2 p.572
}
@@ -47,8 +47,8 @@ class Matter_Plugin_Root : Matter_Plugin
#############################################################
# Constructor
- def init(device)
- super(self).init(device)
+ def init(device, endpoint)
+ super(self).init(device, endpoint)
self.endpoints = self.ENDPOINTS
self.clusters = self.CLUSTERS
end
@@ -157,6 +157,7 @@ class Matter_Plugin_Root : Matter_Plugin
var nocs = nocl.add_struct(nil)
nocs.add_TLV(1, TLV.B2, loc_fabric.get_noc()) # NOC
nocs.add_TLV(2, TLV.B2, loc_fabric.get_icac()) # ICAC
+ nocs.add_TLV(0xFE, TLV.U2, loc_fabric.get_fabric_index()) # Label
end
return nocl
elif attribute == 0x0001 # ---------- Fabrics / list[FabricDescriptorStruct] ----------
@@ -166,9 +167,10 @@ class Matter_Plugin_Root : Matter_Plugin
var fab = fabrics.add_struct(nil) # encoding see p.303
fab.add_TLV(1, TLV.B2, root_ca_tlv.findsubval(9)) # RootPublicKey
fab.add_TLV(2, TLV.U2, loc_fabric.get_admin_vendor()) # VendorID
- fab.add_TLV(3, TLV.U8, loc_fabric.get_fabric_compressed()) # FabricID
+ fab.add_TLV(3, TLV.U8, loc_fabric.get_fabric_id()) # FabricID
fab.add_TLV(4, TLV.U8, loc_fabric.get_device_id()) # NodeID
fab.add_TLV(5, TLV.UTF1, loc_fabric.get_fabric_label()) # Label
+ fab.add_TLV(0xFE, TLV.U2, loc_fabric.get_fabric_index()) # idx
end
return fabrics
elif attribute == 0x0002 # ---------- SupportedFabrics / u1 ----------
@@ -179,29 +181,36 @@ class Matter_Plugin_Root : Matter_Plugin
elif attribute == 0x0004 # ---------- TrustedRootCertificates / list[octstr] ----------
# TODO
elif attribute == 0x0005 # ---------- Current FabricIndex / u1 ----------
- var fabric_active = session._fabric
- var fabric_index = 0
- var found = false
- for fab: self.device.sessions.active_fabrics()
- if fab == fabric_active
- found = true
- break
- else
- fabric_index += 1
- end
- end
- if !found fabric_index = -1 end
- return TLV.create_TLV(TLV.U1, fabric_index + 1) # number of active sessions
+ return TLV.create_TLV(TLV.U1, session._fabric.get_fabric_index()) # number of active sessions
end
# ====================================================================================================
elif cluster == 0x003C # ========== Administrator Commissioning Cluster 11.18 p.725 ==========
- # TODO
-
+ if attribute == 0x0000 # ---------- WindowStatus / u8 ----------
+ var commissioning_open = self.device.is_commissioning_open()
+ var basic_commissioning = self.device.is_root_commissioning_open()
+ var val = commissioning_open ? (basic_commissioning ? 2 #-BasicWindowOpen-# : 1 #-EnhancedWindowOpen-#) : 0 #-WindowNotOpen-#
+ return TLV.create_TLV(TLV.U1, val)
+ elif attribute == 0x0001 # ---------- AdminFabricIndex / u16 ----------
+ var admin_fabric = self.device.commissioning_admin_fabric
+ if admin_fabric != nil
+ return TLV.create_TLV(TLV.U2, admin_fabric.get_fabric_index())
+ else
+ return TLV.create_TLV(TLV.NULL, nil)
+ end
+ elif attribute == 0x0002 # ---------- AdminVendorId / u16 ----------
+ var admin_fabric = self.device.commissioning_admin_fabric
+ if admin_fabric != nil
+ return TLV.create_TLV(TLV.U2, admin_fabric.get_admin_vendor())
+ else
+ return TLV.create_TLV(TLV.NULL, nil)
+ end
+ end
+
# ====================================================================================================
elif cluster == 0x0028 # ========== Basic Information Cluster cluster 11.1 p.565 ==========
- if attribute == 0x0000 # ---------- DataModelRevision / u16 ----------
+ if attribute == 0x0000 # ---------- DataModelRevision / CommissioningWindowStatus ----------
return TLV.create_TLV(TLV.U2, 0)
elif attribute == 0x0001 # ---------- VendorName / string ----------
return TLV.create_TLV(TLV.UTF1, "Tasmota")
@@ -223,6 +232,8 @@ class Matter_Plugin_Root : Matter_Plugin
return TLV.create_TLV(TLV.U2, 1)
elif attribute == 0x000A # ---------- SoftwareVersionString / string ----------
return TLV.create_TLV(TLV.UTF1, tasmota.cmd("Status 2", true)['StatusFWR']['Version'])
+ elif attribute == 0x000F # ---------- SerialNumber / string ----------
+ return TLV.create_TLV(TLV.UTF1, tasmota.wifi().find("mac", ""))
elif attribute == 0x0012 # ---------- UniqueID / string 32 max ----------
return TLV.create_TLV(TLV.UTF1, tasmota.wifi().find("mac", ""))
elif attribute == 0x0013 # ---------- CapabilityMinima / CapabilityMinimaStruct ----------
@@ -323,6 +334,7 @@ class Matter_Plugin_Root : Matter_Plugin
# or an `int` to indicate a status
def invoke_request(session, val, ctx)
import crypto
+ import string
var TLV = matter.TLV
var cluster = ctx.cluster
var command = ctx.command
@@ -520,20 +532,61 @@ class Matter_Plugin_Root : Matter_Plugin
elif command == 0x000A # ---------- RemoveFabric ----------
var index = val.findsubval(0) # FabricIndex
- var sessions_act = self.device.sessions.sessions_active()
- if index >= 1 && index <= size(sessions_act)
- var session_deleted = sessions_act[index - 1]
- tasmota.log("MTR: removing fabric " + session.get_fabric_id().copy().reverse().tohex())
- self.device.sessions.remove_session()
- self.device.sessions.save_fabrics()
- else
- # TODO return error 11 InvalidFabricIndex
+
+ for fab: self.device.sessions.active_fabrics()
+ if fab.get_fabric_index() == index
+ tasmota.log("MTR: removing fabric " + fab.get_fabric_id().copy().reverse().tohex(), 2)
+ self.device.remove_fabric(fab)
+ break
+ end
end
+ tasmota.log("MTR: RemoveFabric fabric("+str(index)+") not found", 2)
ctx.status = matter.SUCCESS # OK
return nil # trigger a standalone ack
end
+ # ====================================================================================================
+ elif cluster == 0x003C # ========== Administrator Commissioning Cluster 11.18 p.725 ==========
+
+ if command == 0x0000 # ---------- OpenCommissioningWindow ----------
+ var timeout = val.findsubval(0) # CommissioningTimeout u2
+ var passcode_verifier = val.findsubval(1) # PAKEPasscodeVerifier octstr
+ var discriminator = val.findsubval(2) # Discriminator u2
+ var iterations = val.findsubval(3) # Iterations u4
+ var salt = val.findsubval(4) # Salt octstr
+
+ tasmota.log(string.format("MTR: OpenCommissioningWindow(timeout=%i, passcode=%s, discriminator=%i, iterations=%i, salt=%s)",
+ timeout, passcode_verifier.tohex(), discriminator, iterations, salt.tohex()), 2)
+
+ # check values
+ if timeout == nil || passcode_verifier == nil || discriminator == nil || iterations == nil || salt == nil
+ ctx.status = matter.INVALID_DATA_TYPE
+ return nil # trigger a standalone ack
+ end
+ if size(passcode_verifier) != 32+65 || size(salt) < 16 || size(salt) > 32
+ tasmota.log("MTR: wrong size for PAKE parameters")
+ ctx.status = matter.CONSTRAINT_ERROR
+ return nil # trigger a standalone ack
+ end
+
+ var w0 = passcode_verifier[0..31]
+ var L = passcode_verifier[32..]
+
+ self.device.start_basic_commissioning(timeout, iterations, discriminator, salt, w0, #-w1,-# L, session.get_fabric())
+ # TODO announce in MDNS
+ return true # OK
+ elif command == 0x0001 # ---------- OpenBasicCommissioningWindow ----------
+ var commissioning_timeout = val.findsubval(0) # CommissioningTimeout
+ tasmota.log("MTR: OpenBasicCommissioningWindow commissioning_timeout="+str(commissioning_timeout), 2)
+ self.device.start_root_basic_commissioning(commissioning_timeout)
+ return true
+ elif command == 0x0002 # ---------- RevokeCommissioning ----------
+ # TODO add checks that the commissioning window was opened by the same fabric
+ self.device.stop_basic_commissioning()
+ return true
+ end
+
# ====================================================================================================
elif cluster == 0x002A # ========== OTA Software Update Requestor Cluster Definition 11.19.7 p.762 ==========
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Session.be b/lib/libesp32/berry_matter/src/embedded/Matter_Session.be
index 75ee9bbde..fcf367d79 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Session.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Session.be
@@ -45,6 +45,8 @@ class Matter_Fabric : Matter_Expirable
var _store # reference back to session store
# timestamp
var created
+ # fabric-index
+ var fabric_index # index number for fabrics, starts with `1`
# list of active sessions
var _sessions # only active CASE sessions that need to be persisted
# our own private key
@@ -82,6 +84,9 @@ class Matter_Fabric : Matter_Expirable
def get_admin_subject() return self.admin_subject end
def get_admin_vendor() return self.admin_vendor end
def get_ca() return self.root_ca_certificate end
+ def get_fabric_index() return self.fabric_index end
+
+ def set_fabric_index(v) self.fabric_index = v end
#############################################################
# Operational Group Key Derivation, 4.15.2, p.182
@@ -136,7 +141,7 @@ class Matter_Fabric : Matter_Expirable
end
#############################################################
- # Fabric::to_json()
+ # Fabric::tojson()
#
# convert a single entry as json
# returns a JSON string
@@ -146,6 +151,7 @@ class Matter_Fabric : Matter_Expirable
import string
import introspect
+ self.persist_pre()
var keys = []
for k : introspect.members(self)
var v = introspect.get(self, k)
@@ -171,6 +177,7 @@ class Matter_Fabric : Matter_Expirable
r.push('"_sessions":' + s_list)
end
+ self.persist_post()
return "{" + r.concat(",") + "}"
end
@@ -203,7 +210,7 @@ class Matter_Fabric : Matter_Expirable
introspect.set(self, k, v)
end
end
-
+ self.hydrate_post()
return self
end
@@ -231,7 +238,7 @@ class Matter_Session : Matter_Expirable
var initiator_session_id # id used to respond to the initiator
var created # timestamp (UTC) when the session was created
var last_used # timestamp (UTC) when the session was last used
- var source_node_id # source node if bytes(8) (opt, used only when session is not established)
+ var _source_node_id # source node if bytes(8) (opt, used only when session is not established)
# session_ids when the session will be active
var __future_initiator_session_id
var __future_local_session_id
@@ -244,8 +251,8 @@ class Matter_Session : Matter_Expirable
var _port # port of the last received packet
var _message_handler # pointer to the message handler for this session
# non-session counters
- var __counter_insecure_rcv # counter for incoming messages
- var __counter_insecure_snd # counter for outgoing messages
+ var _counter_insecure_rcv # counter for incoming messages
+ var _counter_insecure_snd # counter for outgoing messages
# encryption keys and challenges
var i2rkey # key initiator to receiver (incoming)
var r2ikey # key receiver to initiator (outgoing)
@@ -275,8 +282,8 @@ class Matter_Session : Matter_Expirable
self.initiator_session_id = initiator_session_id
self.counter_rcv = matter.Counter()
self.counter_snd = matter.Counter()
- self.__counter_insecure_rcv = matter.Counter()
- self.__counter_insecure_snd = matter.Counter()
+ self._counter_insecure_rcv = matter.Counter()
+ self._counter_insecure_snd = matter.Counter()
self._breadcrumb = 0
self._exchange_id = crypto.random(2).get(0,2) # generate a random 16 bits number, then increment with rollover
@@ -300,6 +307,9 @@ class Matter_Session : Matter_Expirable
def fabric_completed()
self._fabric.set_no_expiration()
self._fabric.set_persist(true)
+ if (self._fabric.get_fabric_index() == nil)
+ self._fabric.set_fabric_index(self._store.next_fabric_idx())
+ end
self._store.add_fabric(self._fabric)
end
@@ -324,7 +334,6 @@ class Matter_Session : Matter_Expirable
# close the PASE session, it will be re-opened with a CASE session
self.local_session_id = self.__future_local_session_id
self.initiator_session_id = self.__future_initiator_session_id
- self.source_node_id = nil
self.counter_rcv.reset()
self.counter_snd.reset()
self.i2rkey = nil
@@ -448,6 +457,7 @@ class Matter_Session : Matter_Expirable
import string
import introspect
+ self.persist_pre()
var keys = []
for k : introspect.members(self)
var v = introspect.get(self, k)
@@ -468,6 +478,7 @@ class Matter_Session : Matter_Expirable
r.push(string.format("%s:%s", json.dump(str(k)), json.dump(v)))
end
+ self.persist_post()
return "{" + r.concat(",") + "}"
end
@@ -501,7 +512,7 @@ class Matter_Session : Matter_Expirable
end
end
end
-
+ self.hydrate_post()
return self
end
@@ -585,6 +596,12 @@ class Matter_Session_Store
end
end
+ #############################################################
+ # remove fabric
+ def remove_fabric(fabric)
+ self.fabrics.remove(self.fabrics.find(fabric)) # fail safe
+ end
+
#############################################################
# Remove redudant fabric
#
@@ -618,6 +635,22 @@ class Matter_Session_Store
return self.fabrics.count_persistables()
end
+ #############################################################
+ # Next fabric-idx
+ #
+ # starts at `1`, computes the next available fabric-idx
+ def next_fabric_idx()
+ self.remove_expired() # clean before
+ var next_idx = 1
+ for fab: self.active_fabrics()
+ var fab_idx = fab.fabric_index
+ if type(fab_idx) == 'int' && fab_idx >= next_idx
+ next_idx = fab_idx + 1
+ end
+ end
+ return next_idx
+ end
+
#############################################################
# add session
def create_session(local_session_id, initiator_session_id)
@@ -661,7 +694,7 @@ class Matter_Session_Store
var sessions = self.sessions
while i < sz
var session = sessions[i]
- if session.source_node_id == nodeid
+ if session._source_node_id == nodeid
session.update()
return session
end
@@ -720,7 +753,7 @@ class Matter_Session_Store
var session = self.get_session_by_source_node_id(source_node_id)
if session == nil
session = matter.Session(self, 0, 0)
- session.source_node_id = source_node_id
+ session._source_node_id = source_node_id
self.sessions.push(session)
end
session.set_expire_in_seconds(expire)
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be b/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be
index 4296e83e0..5b97fef9d 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be
@@ -107,7 +107,7 @@ class Matter_TLV
#############################################################
# create simple TLV
static def create_TLV(t, value)
- if value != nil
+ if value != nil || t == 0x14 #-t == matter.TLV.NULL-# # put the actual number for performance
var v = _class() # parent is nil
v.typ = t
v.val = value
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_UI.be b/lib/libesp32/berry_matter/src/embedded/Matter_UI.be
index e421cb7df..0ddae333b 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_UI.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_UI.be
@@ -158,9 +158,9 @@ class Matter_UI
webserver.content_send("
")
@@ -185,7 +185,7 @@ class Matter_UI
if !first webserver.content_send("
") end
first = false
- webserver.content_send(string.format("