diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be b/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be
index 4171ad08c..39a54a989 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning.be
@@ -1,5 +1,5 @@
#
-# Matter_Commissioning.be - suppport for Matter Commissioning process
+# Matter_Commissioning.be - suppport for Matter Commissioning process PASE and CASE
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
@@ -109,6 +109,7 @@ class Matter_Commisioning_Context
def parse_PBKDFParamRequest(msg)
import crypto
+ import string
# sanity checks
if msg.opcode != 0x20 || msg.local_session_id != 0 || msg.protocol_id != 0
tasmota.log("MTR: invalid PBKDFParamRequest message", 2)
@@ -132,7 +133,7 @@ class Matter_Commisioning_Context
# record the initiator_session_id
self.future_initiator_session_id = pbkdfparamreq.initiator_session_id
self.future_local_session_id = self.device.sessions.gen_local_session_id()
- tasmota.log("MTR: Loc_session=" + str(self.future_local_session_id))
+ tasmota.log(string.format("MTR: +Session (%6i) from '[%s]:%i'", self.future_local_session_id, msg.remote_ip, msg.remote_port), 2)
# prepare response
var pbkdfparamresp = matter.PBKDFParamResponse()
@@ -144,7 +145,7 @@ class Matter_Commisioning_Context
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()
+ var pbkdfparamresp_raw = pbkdfparamresp.tlv2raw()
tasmota.log("MTR: pbkdfparamresp_raw: " + pbkdfparamresp_raw.tohex(), 4)
self.PBKDFParamResponse = pbkdfparamresp_raw
@@ -167,23 +168,23 @@ class Matter_Commisioning_Context
var pake1 = matter.Pake1().parse(msg.raw, msg.app_payload_idx)
self.pA = pake1.pA
- tasmota.log("MTR: received pA=" + self.pA.tohex(), 4)
+ # tasmota.log("MTR: received pA=" + self.pA.tohex(), 4)
- tasmota.log("MTR: spake: " + matter.inspect(self.spake), 4)
+ # tasmota.log("MTR: spake: " + matter.inspect(self.spake), 4)
# instanciate SPAKE
# 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
- tasmota.log("MTR: y=" + self.y.tohex(), 4)
- tasmota.log("MTR: pb=" + self.pB.tohex(), 4)
+ # tasmota.log("MTR: y=" + self.y.tohex(), 4)
+ # tasmota.log("MTR: pb=" + self.pB.tohex(), 4)
# compute ZV
self.spake.compute_ZV_verifier(self.pA)
- tasmota.log("MTR: Z=" + self.spake.Z.tohex(), 4)
- tasmota.log("MTR: V=" + self.spake.V.tohex(), 4)
+ # tasmota.log("MTR: Z=" + self.spake.Z.tohex(), 4)
+ # tasmota.log("MTR: V=" + self.spake.V.tohex(), 4)
var context = crypto.SHA256()
context.update(bytes().fromstring(self.Matter_Context_Prefix))
@@ -191,7 +192,7 @@ class Matter_Commisioning_Context
context.update(self.PBKDFParamResponse)
var context_hash = context.out()
- tasmota.log("MTR: Context=" + context_hash.tohex(), 4)
+ # tasmota.log("MTR: Context=" + context_hash.tohex(), 4)
# add pA
self.spake.pA = self.pA
@@ -199,33 +200,33 @@ class Matter_Commisioning_Context
self.spake.set_context(context_hash)
self.spake.compute_TT_hash(true) # `true` to indicate it's Matter variant to SPAKE2+
- tasmota.log("MTR: ------------------------------", 4)
- tasmota.log("MTR: Context = " + self.spake.Context.tohex(), 4)
- tasmota.log("MTR: M = " + self.spake.M.tohex(), 4)
- tasmota.log("MTR: N = " + self.spake.N.tohex(), 4)
- tasmota.log("MTR: pA = " + self.spake.pA.tohex(), 4)
- tasmota.log("MTR: pB = " + self.spake.pB.tohex(), 4)
- tasmota.log("MTR: Z = " + self.spake.Z.tohex(), 4)
- tasmota.log("MTR: V = " + self.spake.V.tohex(), 4)
- tasmota.log("MTR: w0 = " + self.spake.w0.tohex(), 4)
- tasmota.log("MTR: ------------------------------", 4)
+ # tasmota.log("MTR: ------------------------------", 4)
+ # tasmota.log("MTR: Context = " + self.spake.Context.tohex(), 4)
+ # tasmota.log("MTR: M = " + self.spake.M.tohex(), 4)
+ # tasmota.log("MTR: N = " + self.spake.N.tohex(), 4)
+ # tasmota.log("MTR: pA = " + self.spake.pA.tohex(), 4)
+ # tasmota.log("MTR: pB = " + self.spake.pB.tohex(), 4)
+ # tasmota.log("MTR: Z = " + self.spake.Z.tohex(), 4)
+ # tasmota.log("MTR: V = " + self.spake.V.tohex(), 4)
+ # tasmota.log("MTR: w0 = " + self.spake.w0.tohex(), 4)
+ # tasmota.log("MTR: ------------------------------", 4)
- tasmota.log("MTR: Kmain =" + self.spake.Kmain.tohex(), 4)
+ # tasmota.log("MTR: Kmain =" + self.spake.Kmain.tohex(), 4)
- tasmota.log("MTR: KcA =" + self.spake.KcA.tohex(), 4)
- tasmota.log("MTR: KcB =" + self.spake.KcB.tohex(), 4)
- tasmota.log("MTR: K_shared=" + self.spake.K_shared.tohex(), 4)
- tasmota.log("MTR: Ke =" + self.spake.Ke.tohex(), 4)
+ # tasmota.log("MTR: KcA =" + self.spake.KcA.tohex(), 4)
+ # tasmota.log("MTR: KcB =" + self.spake.KcB.tohex(), 4)
+ # tasmota.log("MTR: K_shared=" + self.spake.K_shared.tohex(), 4)
+ # tasmota.log("MTR: Ke =" + self.spake.Ke.tohex(), 4)
self.cB = self.spake.cB
self.Ke = self.spake.Ke
- tasmota.log("MTR: cB=" + self.cB.tohex(), 4)
+ # tasmota.log("MTR: cB=" + self.cB.tohex(), 4)
var pake2 = matter.Pake2()
pake2.pB = self.pB
pake2.cB = self.cB
- tasmota.log("MTR: pake2: " + matter.inspect(pake2), 4)
- var pake2_raw = pake2.encode()
- tasmota.log("MTR: pake2_raw: " + pake2_raw.tohex(), 4)
+ # tasmota.log("MTR: pake2: " + matter.inspect(pake2), 4)
+ var pake2_raw = pake2.tlv2raw()
+ # tasmota.log("MTR: pake2_raw: " + pake2_raw.tohex(), 4)
# now package the response message
@@ -247,7 +248,7 @@ class Matter_Commisioning_Context
var pake3 = matter.Pake3().parse(msg.raw, msg.app_payload_idx)
self.cA = pake3.cA
- tasmota.log("MTR: received cA=" + self.cA.tohex(), 4)
+ # tasmota.log("MTR: received cA=" + self.cA.tohex(), 4)
# check the value against computed
if self.cA != self.spake.cA
@@ -264,12 +265,12 @@ class Matter_Commisioning_Context
self.R2IKey = session_keys[16..31]
self.AttestationChallenge = session_keys[32..47]
- tasmota.log("MTR: ******************************", 4)
- tasmota.log("MTR: session_keys=" + session_keys.tohex(), 4)
- tasmota.log("MTR: I2RKey =" + self.I2RKey.tohex(), 4)
- tasmota.log("MTR: R2IKey =" + self.R2IKey.tohex(), 4)
- tasmota.log("MTR: AC =" + self.AttestationChallenge.tohex(), 4)
- tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: session_keys=" + session_keys.tohex(), 4)
+ # tasmota.log("MTR: I2RKey =" + self.I2RKey.tohex(), 4)
+ # tasmota.log("MTR: R2IKey =" + self.R2IKey.tohex(), 4)
+ # tasmota.log("MTR: AC =" + self.AttestationChallenge.tohex(), 4)
+ # tasmota.log("MTR: ******************************", 4)
# StatusReport(GeneralCode: SUCCESS, ProtocolId: SECURE_CHANNEL, ProtocolCode: SESSION_ESTABLISHMENT_SUCCESS)
var raw = self.send_status_report(msg, 0x00, 0x0000, 0x0000, false)
@@ -302,6 +303,7 @@ class Matter_Commisioning_Context
def parse_Sigma1(msg)
import crypto
+ import string
# sanity checks
if msg.opcode != 0x30 || msg.local_session_id != 0 || msg.protocol_id != 0
tasmota.log("MTR: invalid Sigma1 message", 2)
@@ -340,7 +342,7 @@ class Matter_Commisioning_Context
session.__future_initiator_session_id = sigma1.initiator_session_id # update initiator_session_id
session.__future_local_session_id = self.device.sessions.gen_local_session_id()
self.future_local_session_id = session.__future_local_session_id
- tasmota.log("MTR: Loc_session=" + str(self.future_local_session_id))
+ tasmota.log(string.format("MTR: +Session (%6i) from '[%s]:%i'", self.future_local_session_id, msg.remote_ip, msg.remote_port), 2)
# Check that it's a resumption
if is_resumption
@@ -356,12 +358,12 @@ class Matter_Commisioning_Context
var Resume1MICPayload = ec.decrypt(encrypted)
var decrypted_tag = ec.tag()
- tasmota.log("****************************************", 4)
- tasmota.log("MTR: * s1rk = " + s1rk.tohex(), 4)
- tasmota.log("MTR: * tag = " + tag.tohex(), 4)
- tasmota.log("MTR: * Resume1MICPayload = " + Resume1MICPayload.tohex(), 4)
- tasmota.log("MTR: * decrypted_tag = " + decrypted_tag.tohex(), 4)
- tasmota.log("****************************************", 4)
+ # tasmota.log("****************************************", 4)
+ # tasmota.log("MTR: * s1rk = " + s1rk.tohex(), 4)
+ # tasmota.log("MTR: * tag = " + tag.tohex(), 4)
+ # tasmota.log("MTR: * Resume1MICPayload = " + Resume1MICPayload.tohex(), 4)
+ # tasmota.log("MTR: * decrypted_tag = " + decrypted_tag.tohex(), 4)
+ # tasmota.log("****************************************", 4)
if tag == decrypted_tag
# Generate and Send Sigma2_Resume
session.resumption_id = crypto.random(16) # generate a new resumption id
@@ -390,15 +392,15 @@ class Matter_Commisioning_Context
var ac = session_keys[32..47]
var created = tasmota.rtc()['utc']
- tasmota.log("MTR: ******************************", 4)
- tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
- tasmota.log("MTR: R2IKey =" + r2i.tohex(), 4)
- tasmota.log("MTR: AC =" + ac.tohex(), 4)
- tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
+ # tasmota.log("MTR: R2IKey =" + r2i.tohex(), 4)
+ # tasmota.log("MTR: AC =" + ac.tohex(), 4)
+ # tasmota.log("MTR: ******************************", 4)
- var sigma2resume_raw = sigma2resume.encode()
+ var sigma2resume_raw = sigma2resume.tlv2raw()
session.__Msg1 = nil
- tasmota.log("MTR: sigma2resume_raw: " + sigma2resume_raw.tohex(), 4)
+ # tasmota.log("MTR: sigma2resume_raw: " + sigma2resume_raw.tohex(), 4)
# now package the response message
var resp = msg.build_response(0x33 #-sigma-2-resume-#, true)
@@ -437,7 +439,7 @@ class Matter_Commisioning_Context
sigma2_tbsdata.add_TLV(3, matter.TLV.B2, self.ResponderEph_pub)
sigma2_tbsdata.add_TLV(4, matter.TLV.B2, sigma1.initiatorEphPubKey)
- var TBSData2Signature = crypto.EC_P256().ecdsa_sign_sha256(session.get_pk(), sigma2_tbsdata.encode())
+ var TBSData2Signature = crypto.EC_P256().ecdsa_sign_sha256(session.get_pk(), sigma2_tbsdata.tlv2raw())
var sigma2_tbedata = matter.TLV.Matter_TLV_struct()
sigma2_tbedata.add_TLV(1, matter.TLV.B2, session.get_noc())
@@ -446,9 +448,9 @@ class Matter_Commisioning_Context
sigma2_tbedata.add_TLV(4, matter.TLV.B2, session.resumption_id)
# compute TranscriptHash = Crypto_Hash(message = Msg1)
- tasmota.log("****************************************", 4)
+ # tasmota.log("****************************************", 4)
session.__Msg1 = sigma1.Msg1
- tasmota.log("MTR: * MSG1 = " + session.__Msg1.tohex(), 4)
+ # tasmota.log("MTR: * MSG1 = " + session.__Msg1.tohex(), 4)
var TranscriptHash = crypto.SHA256().update(session.__Msg1).out()
# tasmota.log("MTR: TranscriptHash =" + TranscriptHash.tohex(), 4)
@@ -457,27 +459,27 @@ class Matter_Commisioning_Context
var s2k_salt = session.get_ipk_group_key() + responderRandom + self.ResponderEph_pub + TranscriptHash
var s2k = crypto.HKDF_SHA256().derive(session.shared_secret, s2k_salt, s2k_info, 16)
- tasmota.log("MTR: * SharedSecret = " + session.shared_secret.tohex(), 4)
- tasmota.log("MTR: * s2k_salt = " + s2k_salt.tohex(), 4)
- tasmota.log("MTR: * s2k = " + s2k.tohex(), 4)
+ # tasmota.log("MTR: * SharedSecret = " + session.shared_secret.tohex(), 4)
+ # tasmota.log("MTR: * s2k_salt = " + s2k_salt.tohex(), 4)
+ # tasmota.log("MTR: * s2k = " + s2k.tohex(), 4)
- var sigma2_tbedata_raw = sigma2_tbedata.encode()
+ var sigma2_tbedata_raw = sigma2_tbedata.tlv2raw()
# // `AES_CCM.init(secret_key:bytes(16 or 32), iv:bytes(7..13), aad:bytes(), data_len:int, tag_len:int) -> instance`
var aes = crypto.AES_CCM(s2k, bytes().fromstring(self.TBEData2_Nonce), bytes(), size(sigma2_tbedata_raw), 16)
var TBEData2Encrypted = aes.encrypt(sigma2_tbedata_raw) + aes.tag()
- tasmota.log("MTR: * TBEData2Enc = " + TBEData2Encrypted.tohex(), 4)
- tasmota.log("****************************************", 4)
+ # tasmota.log("MTR: * TBEData2Enc = " + TBEData2Encrypted.tohex(), 4)
+ # tasmota.log("****************************************", 4)
var sigma2 = matter.Sigma2()
sigma2.responderRandom = responderRandom
sigma2.responderSessionId = self.future_local_session_id
sigma2.responderEphPubKey = self.ResponderEph_pub
sigma2.encrypted2 = TBEData2Encrypted
- tasmota.log("MTR: sigma2: " + matter.inspect(sigma2), 4)
- var sigma2_raw = sigma2.encode()
+ # tasmota.log("MTR: sigma2: " + matter.inspect(sigma2), 4)
+ var sigma2_raw = sigma2.tlv2raw()
session.__Msg2 = sigma2_raw
- tasmota.log("MTR: sigma2_raw: " + sigma2_raw.tohex(), 4)
+ # tasmota.log("MTR: sigma2_raw: " + sigma2_raw.tohex(), 4)
# now package the response message
var resp = msg.build_response(0x31 #-sigma-2-#, true) # no reliable flag
@@ -501,22 +503,22 @@ class Matter_Commisioning_Context
var session = msg.session
var sigma3 = matter.Sigma3().parse(msg.raw, msg.app_payload_idx)
- tasmota.log("****************************************", 4)
+ # tasmota.log("****************************************", 4)
# compute TranscriptHash = Crypto_Hash(message = Msg1 || Msg2)
var TranscriptHash = crypto.SHA256().update(session.__Msg1).update(session.__Msg2).out()
- tasmota.log("MTR: * session = " + str(session), 4)
- tasmota.log("MTR: session.ipk_epoch_key " + str(session.get_ipk_epoch_key()), 4)
- tasmota.log("MTR: session.fabric_compressed " + str(session.get_fabric_compressed()), 4)
- tasmota.log("MTR: * ipk_group_key = " + session.get_ipk_group_key().tohex(), 4)
- tasmota.log("MTR: * TranscriptHash= " + TranscriptHash.tohex(), 4)
+ # tasmota.log("MTR: * session = " + str(session), 4)
+ # tasmota.log("MTR: session.ipk_epoch_key " + str(session.get_ipk_epoch_key()), 4)
+ # tasmota.log("MTR: session.fabric_compressed " + str(session.get_fabric_compressed()), 4)
+ # tasmota.log("MTR: * ipk_group_key = " + session.get_ipk_group_key().tohex(), 4)
+ # tasmota.log("MTR: * TranscriptHash= " + TranscriptHash.tohex(), 4)
var s3k_info = bytes().fromstring(self.S3K_Info)
var s3k = crypto.HKDF_SHA256().derive(session.shared_secret, session.get_ipk_group_key() + TranscriptHash, s3k_info, 16)
- tasmota.log("****************************************", 4)
- tasmota.log("MTR: * s3k_salt = " + (session.get_ipk_group_key() + TranscriptHash).tohex(), 4)
- tasmota.log("MTR: * s3k = " + s3k.tohex(), 4)
- tasmota.log("****************************************", 4)
+ # tasmota.log("****************************************", 4)
+ # tasmota.log("MTR: * s3k_salt = " + (session.get_ipk_group_key() + TranscriptHash).tohex(), 4)
+ # tasmota.log("MTR: * s3k = " + s3k.tohex(), 4)
+ # tasmota.log("****************************************", 4)
# decrypt
var encrypted = sigma3.TBEData3Encrypted[0..-17]
@@ -524,10 +526,10 @@ class Matter_Commisioning_Context
var ec = crypto.AES_CCM(s3k, bytes().fromstring(self.TBEData3_Nonce), bytes(), size(encrypted), 16)
var TBEData3 = ec.decrypt(encrypted)
var TBETag3 = ec.tag()
- tasmota.log("MTR: * TBEData3 = " + TBEData3.tohex(), 4)
- tasmota.log("MTR: * TBETag3 = " + TBETag3.tohex(), 4)
- tasmota.log("MTR: * tag_sent = " + tag.tohex(), 4)
- tasmota.log("****************************************", 4)
+ # tasmota.log("MTR: * TBEData3 = " + TBEData3.tohex(), 4)
+ # tasmota.log("MTR: * TBETag3 = " + TBETag3.tohex(), 4)
+ # tasmota.log("MTR: * tag_sent = " + tag.tohex(), 4)
+ # tasmota.log("****************************************", 4)
if TBETag3 != tag
tasmota.log("MTR: Tag don't match", 2)
@@ -543,24 +545,24 @@ class Matter_Commisioning_Context
# Success = Crypto_VerifyChain(certificates = [TBEData3.initiatorNOC, TBEData3.initiatorICAC, TrustedRCAC]), when TBEData3.initiatorICAC is present
# TODO
var initiatorNOCTLV = matter.TLV.parse(initiatorNOC)
- tasmota.log("MTR: initiatorNOCTLV = " + str(initiatorNOCTLV), 3)
+ # tasmota.log("MTR: initiatorNOCTLV = " + str(initiatorNOCTLV), 3)
var initiatorNOCPubKey = initiatorNOCTLV.findsubval(9)
var initiatorNOCListDN = initiatorNOCTLV.findsub(6)
var initiatorFabricId = initiatorNOCListDN.findsubval(17)
if type(initiatorFabricId) == 'int' initiatorFabricId = int64(initiatorFabricId) end
session.peer_node_id = initiatorFabricId.tobytes()
- tasmota.log("MTR: initiatorFabricId="+str(session.peer_node_id), 3)
+ # tasmota.log("MTR: initiatorFabricId="+str(session.peer_node_id), 3)
var sigma3_tbs = matter.TLV.Matter_TLV_struct()
sigma3_tbs.add_TLV(1, matter.TLV.B1, initiatorNOC)
sigma3_tbs.add_TLV(2, matter.TLV.B1, initiatorICAC)
sigma3_tbs.add_TLV(3, matter.TLV.B1, self.initiatorEph_pub)
sigma3_tbs.add_TLV(4, matter.TLV.B1, self.ResponderEph_pub)
- var sigma3_tbs_raw = sigma3_tbs.encode()
+ var sigma3_tbs_raw = sigma3_tbs.tlv2raw()
- tasmota.log("MTR: * initiatorNOCPubKey = " + initiatorNOCPubKey.tohex(), 4)
- tasmota.log("MTR: * ec_signature = " + ec_signature.tohex(), 4)
- tasmota.log("****************************************", 4)
+ # tasmota.log("MTR: * initiatorNOCPubKey = " + initiatorNOCPubKey.tohex(), 4)
+ # tasmota.log("MTR: * ec_signature = " + ec_signature.tohex(), 4)
+ # tasmota.log("****************************************", 4)
# `crypto.EC_P256().ecdsa_verify_sha256(public_key:bytes(65), message:bytes(), hash:bytes()) -> bool`
var sigma3_tbs_valid = crypto.EC_P256().ecdsa_verify_sha256(initiatorNOCPubKey, sigma3_tbs_raw, ec_signature)
@@ -580,9 +582,9 @@ class Matter_Commisioning_Context
session.__Msg1 = nil
session.__Msg2 = nil
- tasmota.log("MTR: ******************************", 4)
- tasmota.log("MTR: shared_secret =" + session.shared_secret.tohex(), 4)
- tasmota.log("MTR: ipk + hash =" + (session.get_ipk_group_key() + TranscriptHash).tohex(), 4)
+ # tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: shared_secret =" + session.shared_secret.tohex(), 4)
+ # tasmota.log("MTR: ipk + hash =" + (session.get_ipk_group_key() + TranscriptHash).tohex(), 4)
# compute session key
var session_keys = crypto.HKDF_SHA256().derive(session.shared_secret #- input key -#,
session.get_ipk_group_key() + TranscriptHash #- salt -#,
@@ -593,11 +595,11 @@ class Matter_Commisioning_Context
var ac = session_keys[32..47]
var created = tasmota.rtc()['utc']
- tasmota.log("MTR: ******************************", 4)
- tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
- tasmota.log("MTR: R2IKey =" + r2i.tohex(), 4)
- tasmota.log("MTR: AC =" + ac.tohex(), 4)
- tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: ******************************", 4)
+ # tasmota.log("MTR: I2RKey =" + i2r.tohex(), 4)
+ # tasmota.log("MTR: R2IKey =" + r2i.tohex(), 4)
+ # tasmota.log("MTR: AC =" + ac.tohex(), 4)
+ # tasmota.log("MTR: ******************************", 4)
# StatusReport(GeneralCode: SUCCESS, ProtocolId: SECURE_CHANNEL, ProtocolCode: SESSION_ESTABLISHMENT_SUCCESS)
var raw = self.send_status_report(msg, 0x00, 0x0000, 0x0000, true)
@@ -607,6 +609,7 @@ class Matter_Commisioning_Context
# CASE Session completed, persist it
session._breadcrumb = 0 # clear breadcrumb
+ session.counter_snd_next() # force a first counter. It's important it's used before set_persist(true) to not have a double save
session.set_persist(true) # keep session on flash
session.set_no_expiration() # never expire
session.persist_to_fabric()
@@ -617,7 +620,7 @@ class Matter_Commisioning_Context
def parse_StatusReport(msg)
var session = msg.session
- tasmota.log("MTR: StatusReport = "+msg.raw[msg.app_payload_idx..].tohex())
+ tasmota.log("MTR: StatusReport = "+msg.raw[msg.app_payload_idx..].tohex(), 2)
return true
end
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning_Data.be b/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning_Data.be
index 05147d605..41b4bc66c 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning_Data.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Commissioning_Data.be
@@ -70,7 +70,7 @@ class Matter_PBKDFParamResponse
var SLEEPY_IDLE_INTERVAL
var SLEEPY_ACTIVE_INTERVAL
- def encode(b)
+ def tlv2raw(b)
var s = matter.TLV.Matter_TLV_struct()
# initiatorRandom
s.add_TLV(1, matter.TLV.B1, self.initiatorRandom)
@@ -84,7 +84,7 @@ class Matter_PBKDFParamResponse
s2.add_TLV(1, matter.TLV.U4, self.SLEEPY_IDLE_INTERVAL)
s2.add_TLV(2, matter.TLV.U4, self.SLEEPY_ACTIVE_INTERVAL)
end
- return s.encode(b)
+ return s.tlv2raw(b)
end
end
matter.PBKDFParamResponse = Matter_PBKDFParamResponse
@@ -113,12 +113,12 @@ class Matter_Pake2
var pB # 65 bytes
var cB # 32 bytes
- def encode(b)
+ def tlv2raw(b)
var s = matter.TLV.Matter_TLV_struct()
#
s.add_TLV(1, matter.TLV.B1, self.pB)
s.add_TLV(2, matter.TLV.B1, self.cB)
- return s.encode(b)
+ return s.tlv2raw(b)
end
end
matter.Pake2 = Matter_Pake2
@@ -186,7 +186,7 @@ class Matter_Sigma2
var SLEEPY_IDLE_INTERVAL
var SLEEPY_ACTIVE_INTERVAL
- def encode(b)
+ def tlv2raw(b)
var s = matter.TLV.Matter_TLV_struct()
# initiatorRandom
s.add_TLV(1, matter.TLV.B1, self.responderRandom)
@@ -198,7 +198,7 @@ class Matter_Sigma2
s2.add_TLV(1, matter.TLV.U4, self.SLEEPY_IDLE_INTERVAL)
s2.add_TLV(2, matter.TLV.U4, self.SLEEPY_ACTIVE_INTERVAL)
end
- return s.encode(b)
+ return s.tlv2raw(b)
end
end
matter.Sigma2 = Matter_Sigma2
@@ -213,7 +213,7 @@ class Matter_Sigma2Resume
var SLEEPY_IDLE_INTERVAL
var SLEEPY_ACTIVE_INTERVAL
- def encode(b)
+ def tlv2raw(b)
var s = matter.TLV.Matter_TLV_struct()
# initiatorRandom
s.add_TLV(1, matter.TLV.B1, self.resumptionID)
@@ -224,7 +224,7 @@ class Matter_Sigma2Resume
s2.add_TLV(1, matter.TLV.U4, self.SLEEPY_IDLE_INTERVAL)
s2.add_TLV(2, matter.TLV.U4, self.SLEEPY_ACTIVE_INTERVAL)
end
- return s.encode(b)
+ return s.tlv2raw(b)
end
end
matter.Sigma2Resume = Matter_Sigma2Resume
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Device.be b/lib/libesp32/berry_matter/src/embedded/Matter_Device.be
index d17cf5e5e..d7622bc86 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Device.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Device.be
@@ -35,15 +35,14 @@ class Matter_Device
# 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_discriminator # commissioning_discriminator
var commissioning_salt # current salt
- var commissioning_w0 # current w0
- # var commissioning_w1 # current w1
- var commissioning_L # current L
+ var commissioning_w0 # current w0 (SPAKE2+)
+ var commissioning_L # current L (SPAKE2+)
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
+ var commissioning_instance_wifi # random instance name for commissioning (mDNS)
+ var commissioning_instance_eth # random instance name for commissioning (mDNS)
var hostname_wifi # MAC-derived hostname for commissioning
var hostname_eth # MAC-derived hostname for commissioning
var vendorid
@@ -52,15 +51,14 @@ class Matter_Device
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 root_discriminator
- var root_passcode
+ var root_discriminator # as `int`
+ var root_passcode # as `int`
var ipv4only # advertize only IPv4 addresses (no IPv6)
# context for PBKDF
- var root_iterations
+ var root_iterations # PBKDF number of iterations
# PBKDF information used only during PASE (freed afterwards)
var root_salt
var root_w0
- # var root_w1
var root_L
#############################################################
@@ -93,68 +91,71 @@ class Matter_Device
# self.plugins.push(matter.Plugin_Temp_Sensor(self, 10, "ESP32#Temperature"))
# for now read sensors every 5 seconds
- tasmota.add_cron("*/5 * * * * *", def () self.trigger_read_sensors() end, "matter_sensors_5s")
+ tasmota.add_cron("*/5 * * * * *", def () self._trigger_read_sensors() end, "matter_sensors_5s")
self.start_mdns_announce_hostnames()
if tasmota.wifi()['up']
- self.start_udp(self.UDP_PORT)
+ self._start_udp(self.UDP_PORT)
else
tasmota.add_rule("Wifi#Connected", def ()
- self.start_udp(self.UDP_PORT)
+ self._start_udp(self.UDP_PORT)
tasmota.remove_rule("Wifi#Connected", "matter_device_udp")
end, "matter_device_udp")
end
if tasmota.eth()['up']
- self.start_udp(self.UDP_PORT)
+ self._start_udp(self.UDP_PORT)
else
tasmota.add_rule("Eth#Connected", def ()
- self.start_udp(self.UDP_PORT)
+ self._start_udp(self.UDP_PORT)
tasmota.remove_rule("Eth#Connected", "matter_device_udp")
end, "matter_device_udp")
end
- self.init_basic_commissioning()
+ self._init_basic_commissioning()
tasmota.add_driver(self)
end
#############################################################
- # Start Basic Commissioning Window
- def init_basic_commissioning()
+ # Start Basic Commissioning Window if needed at startup
+ def _init_basic_commissioning()
# if no fabric is configured, automatically open commissioning at restart
if self.sessions.count_active_fabrics() == 0
self.start_root_basic_commissioning()
end
end
+ #############################################################
+ # Start Basic Commissioning with root parameters
+ #
+ # Open window for `timeout_s` (default 10 minutes)
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._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.message_handler.im.subs_shop.remove_by_fabric(fabric)
self.mdns_remove_op_discovery(fabric)
self.sessions.remove_fabric(fabric)
self.sessions.save_fabrics()
end
#############################################################
- # Start Basic Commissioning Window
- def start_basic_commissioning(timeout_s, iterations, discriminator, salt, w0, #-w1,-# L, admin_fabric)
+ # Start Basic Commissioning Window with custom parameters
+ def start_basic_commissioning(timeout_s, iterations, discriminator, salt, w0, 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
@@ -172,10 +173,14 @@ class Matter_Device
end
end
+ #############################################################
+ # Is root commissioning currently open. Mostly for UI to know if QRCode needs to be shown.
def is_root_commissioning_open()
return self.commissioning_open != nil && self.commissioning_admin_fabric == nil
end
+ #############################################################
+ # Stop PASE commissioning, mostly called when CASE is about to start
def stop_basic_commissioning()
self.commissioning_open = nil
@@ -193,13 +198,11 @@ class Matter_Device
def is_commissioning_open()
return self.commissioning_open != nil
end
- def finish_commissioning()
- end
-
+
#############################################################
- # Compute the PBKDF parameters for SPAKE2+
+ # (internal) Compute the PBKDF parameters for SPAKE2+ from root parameters
#
- def compute_pbkdf(passcode_int, iterations, salt)
+ def _compute_pbkdf(passcode_int, iterations, salt)
import crypto
import string
var passcode = bytes().add(passcode_int, 4)
@@ -227,7 +230,7 @@ class Matter_Device
end
#############################################################
- # compute QR Code content - can be done only for root PASE
+ # 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
@@ -243,7 +246,8 @@ class Matter_Device
#############################################################
- # compute the 11 digits manual pairing code (wihout vendorid nor productid) p.223
+ # 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
@@ -274,7 +278,8 @@ class Matter_Device
#############################################################
# trigger a read_sensors and dispatch to plugins
- def trigger_read_sensors()
+ # Internally used by cron
+ def _trigger_read_sensors()
import json
var rs_json = tasmota.read_sensors()
if rs_json == nil return end
@@ -302,26 +307,34 @@ class Matter_Device
#############################################################
def stop()
+ tasmota.remove_driver(self)
if self.udp_server self.udp_server.stop() end
end
#############################################################
- # callback when message is received
+ # Callback when message is received.
+ # Send to `message_handler`
def msg_received(raw, addr, port)
return self.message_handler.msg_received(raw, addr, port)
end
+ #############################################################
+ # Global entry point for sending a message.
+ # Delegates to `udp_server`
def msg_send(raw, addr, port, id)
return self.udp_server.send_response(raw, addr, port, id)
end
- def packet_ack(id)
- return self.udp_server.packet_ack(id)
+ #############################################################
+ # Signals that a ack was received.
+ # Delegates to `udp_server` to remove from resending list.
+ def received_ack(id)
+ return self.udp_server.received_ack(id)
end
#############################################################
- # Start UDP Server
- def start_udp(port)
+ # (internal) Start UDP Server
+ def _start_udp(port)
if self.udp_server return end # already started
if port == nil port = 5540 end
tasmota.log("MTR: starting UDP server on port: " + str(port), 2)
@@ -330,22 +343,28 @@ class Matter_Device
end
#############################################################
- # start_operational_discovery
+ # Start Operational Discovery for this session
#
- # Pass control to `device`
+ # Deferred until next tick.
def start_operational_discovery_deferred(session)
# defer to next click
tasmota.set_timer(0, /-> self.start_operational_discovery(session))
end
#############################################################
+ # Start Commissioning Complete for this session
+ #
+ # Deferred until next tick.
def start_commissioning_complete_deferred(session)
# defer to next click
tasmota.set_timer(0, /-> self.start_commissioning_complete(session))
end
#############################################################
- # Start Operational Discovery
+ # Start Operational Discovery for this session
+ #
+ # Stop Basic Commissioning and clean PASE specific values (to save memory).
+ # Announce fabric entry in mDNS.
def start_operational_discovery(session)
import crypto
import mdns
@@ -366,6 +385,7 @@ class Matter_Device
#############################################################
# Commissioning Complete
#
+ # Stop basic commissioning.
def start_commissioning_complete(session)
tasmota.log("MTR: *** Commissioning complete ***", 2)
self.stop_basic_commissioning() # by default close commissioning when it's complete
@@ -403,22 +423,30 @@ class Matter_Device
end
#############################################################
- # signal that an attribute has been changed
+ # Signal that an attribute has been changed and propagate
+ # to any active subscription.
#
+ # Delegates to `message_handler`
def attribute_updated(endpoint, cluster, attribute, fabric_specific)
if fabric_specific == nil fabric_specific = false end
var ctx = matter.Path()
ctx.endpoint = endpoint
ctx.cluster = cluster
ctx.attribute = attribute
- self.message_handler.im.subs.attribute_updated_ctx(ctx, fabric_specific)
+ self.message_handler.im.subs_shop.attribute_updated_ctx(ctx, fabric_specific)
end
#############################################################
- # expand attribute list based
+ # Proceed to attribute expansion (used for Attribute Read/Write/Subscribe)
#
- # called only when expansion is needed,
- # so we don't need to report any error since they are ignored
+ # Called only when expansion is needed, so we don't need to report any error since they are ignored
+ #
+ # calls `cb(pi, ctx, direct)` for each attribute expanded.
+ # `pi`: plugin instance targeted by the attribute (via endpoint). Note: nothing is sent if the attribute is not declared in supported attributes in plugin.
+ # `ctx`: context object with `endpoint`, `cluster`, `attribute` (no `command`)
+ # `direct`: `true` if the attribute is directly targeted, `false` if listed as part of a wildcard
+ # returns: `true` if processed succesfully, `false` if error occured. If `direct`, the error is returned to caller, but if expanded the error is silently ignored and the attribute skipped.
+ # In case of `direct` but the endpoint/cluster/attribute is not suppported, it calls `cb(nil, ctx, true)` so you have a chance to encode the exact error (UNSUPPORTED_ENDPOINT/UNSUPPORTED_CLUSTER/UNSUPPORTED_ATTRIBUTE/UNREPORTABLE_ATTRIBUTE)
def process_attribute_expansion(ctx, cb)
#################################################################################
# Returns the keys of a map as a sorted list
@@ -524,9 +552,7 @@ class Matter_Device
end
#############################################################
- # get active endpoints
- #
- # return the list of endpoints from all plugins (distinct)
+ # Return the list of endpoints from all plugins (distinct), exclud endpoint zero if `exclude_zero` is `true`
def get_active_endpoints(exclude_zero)
var ret = []
for p:self.plugins
@@ -560,6 +586,7 @@ class Matter_Device
end
#############################################################
+ # Load Matter Device parameters
def load_param()
import string
import crypto
@@ -686,8 +713,6 @@ class Matter_Device
#############################################################
# 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
@@ -757,8 +782,6 @@ class Matter_Device
#############################################################
# 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
@@ -823,7 +846,7 @@ class Matter_Device
end
#############################################################
- # Remove all mDNS announces
+ # Remove all mDNS announces for all fabrics
def mdns_remove_op_discovery_all_fabrics()
for fabric: self.sessions.active_fabrics()
if fabric.get_device_id() && fabric.get_fabric_id()
@@ -833,7 +856,7 @@ class Matter_Device
end
#############################################################
- # Start UDP mDNS announcements for commissioning
+ # Remove mDNS announce for fabric
def mdns_remove_op_discovery(fabric)
import mdns
import string
@@ -857,8 +880,9 @@ class Matter_Device
end
#############################################################
- # Try to clean MDNS entries before restart
+ # Try to clean MDNS entries before restart.
#
+ # Called by Tasmota loop as a Tasmota driver.
def save_before_restart()
self.stop_basic_commissioning()
self.mdns_remove_op_discovery_all_fabrics()
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Expirable.be b/lib/libesp32/berry_matter/src/embedded/Matter_Expirable.be
index 9320f1949..391476982 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Expirable.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Expirable.be
@@ -73,6 +73,13 @@ class Matter_Expirable
def hydrate_post()
end
+ #############################################################
+ # before_remove
+ #
+ # called right before the element is removed
+ def before_remove()
+ end
+
#############################################################
# set absolute time for expiration
def set_no_expiration()
@@ -131,6 +138,13 @@ class Matter_Expirable_list : list
return super(self).setitem(i, o)
end
+ #############################################################
+ # remove - override
+ #
+ def remove(i)
+ if i >= 0 && i < size(self) self[i].before_remove() end
+ return super(self).remove(i)
+ end
#############################################################
# remove_expired
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_IM.be b/lib/libesp32/berry_matter/src/embedded/Matter_IM.be
index ba1ec6064..07c51648b 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_IM.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_IM.be
@@ -26,14 +26,14 @@ import matter
#################################################################################
class Matter_IM
var device
- var subs # subscriptions shop
+ var subs_shop # subscriptions shop
var send_queue # list of IM_Message queued for sending as part of exchange-id
def init(device)
self.device = device
self.send_queue = []
- self.subs = matter.IM_Subscription_Shop(self)
+ self.subs_shop = matter.IM_Subscription_Shop(self)
end
def process_incoming(msg)
@@ -42,27 +42,31 @@ class Matter_IM
var val = matter.TLV.parse(msg.raw, msg.app_payload_idx)
- tasmota.log("MTR: IM TLV: " + str(val), 3)
+ # tasmota.log("MTR: IM TLV: " + str(val), 3)
var InteractionModelRevision = val.findsubval(0xFF)
- tasmota.log("MTR: InteractionModelRevision=" + (InteractionModelRevision != nil ? str(InteractionModelRevision) : "nil"), 4)
+ # tasmota.log("MTR: InteractionModelRevision=" + (InteractionModelRevision != nil ? str(InteractionModelRevision) : "nil"), 4)
var opcode = msg.opcode
if opcode == 0x01 # Status Response
return self.process_status_response(msg, val)
elif opcode == 0x02 # Read Request
+ self.send_ack_now(msg)
return self.process_read_request(msg, val)
elif opcode == 0x03 # Subscribe Request
+ self.send_ack_now(msg)
return self.subscribe_request(msg, val)
elif opcode == 0x04 # Subscribe Response
return self.subscribe_response(msg, val)
elif opcode == 0x05 # Report Data
return self.report_data(msg, val)
elif opcode == 0x06 # Write Request
+ self.send_ack_now(msg)
return self.process_write_request(msg, val)
elif opcode == 0x07 # Write Response
return self.process_write_response(msg, val)
elif opcode == 0x08 # Invoke Request
+ self.send_ack_now(msg)
return self.process_invoke_request(msg, val)
elif opcode == 0x09 # Invoke Response
return self.process_invoke_response(msg, val)
@@ -87,22 +91,39 @@ class Matter_IM
return false
end
+ #############################################################
+ # send Ack response now and don't enqueue it
+ #
+ # returns `true` if packet could be sent
+ def send_ack_now(msg)
+ if msg.x_flag_r # send Ack only if requester asks for it
+ var resp = msg.build_standalone_ack(false #-not reliable-#)
+ resp.encode_frame()
+ resp.encrypt()
+ import string
+ tasmota.log(string.format("MTR:
+ #
def send_enqueued(responder)
var idx = 0
while idx < size(self.send_queue)
var message = self.send_queue[idx]
- if message.ready
- var finish = message.send(responder) # send message
- if finish
- self.send_queue.remove(idx)
- idx -= 1
- else
- message.ready = false # needs more to proceed
- end
+ var finish = message.send_im(responder) # send message
+ if finish
+ self.send_queue.remove(idx)
+ idx -= 1
end
idx += 1
@@ -171,7 +192,7 @@ class Matter_IM
if message
return message.status_ok_received(msg) # re-arm the sending of next packets for the same exchange
else
- tasmota.log("MTR: >Status_OK", 2) # don't show 'SUCCESS' to not overflow logs with non-information
+ tasmota.log(string.format("MTR: >OK (%6i) exch=%i not found", msg.session.local_session_id, msg.exchange_id), 3) # don't show 'SUCCESS' to not overflow logs with non-information
end
else
# error
@@ -188,7 +209,8 @@ class Matter_IM
# Inner code shared between read_attributes and subscribe_request
#
# query: `ReadRequestMessage` or `SubscribeRequestMessage`
- def _inner_process_read_request(session, query)
+ def _inner_process_read_request(session, query, no_log)
+ import string
### Inner function to be iterated upon
# ret is the ReportDataMessage list to send back
@@ -216,7 +238,9 @@ class Matter_IM
a1.attribute_data.data = res
ret.attribute_reports.push(a1)
- tasmota.log(string.format("MTR: Read_Attr %s%s - %s", str(ctx), attr_name, str(res)), 2)
+ if !no_log
+ tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - %s", session.local_session_id, str(ctx), attr_name, str(res)), 2)
+ end
return true # stop expansion since we have a value
elif ctx.status != nil
if direct
@@ -230,11 +254,11 @@ class Matter_IM
a1.attribute_status.status.status = ctx.status
ret.attribute_reports.push(a1)
- tasmota.log(string.format("MTR: Read_Attr %s%s - STATUS: 0x%02X %s", str(ctx), attr_name, ctx.status, ctx.status == matter.UNSUPPORTED_ATTRIBUTE ? "UNSUPPORTED_ATTRIBUTE" : ""), 2)
+ tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - STATUS: 0x%02X %s", session.local_session_id, str(ctx), attr_name, ctx.status, ctx.status == matter.UNSUPPORTED_ATTRIBUTE ? "UNSUPPORTED_ATTRIBUTE" : ""), 2)
return true
end
else
- tasmota.log(string.format("MTR: Read_Attr %s%s - IGNORED", str(ctx), attr_name), 2)
+ tasmota.log(string.format("MTR: >Read_Attr (%6i) %s%s - IGNORED", session.local_session_id, str(ctx), attr_name), 2)
# ignore if content is nil and status is undefined
return false
end
@@ -261,9 +285,9 @@ class Matter_IM
# we need expansion, log first
if ctx.cluster != nil && ctx.attribute != nil
var attr_name = matter.get_attribute_name(ctx.cluster, ctx.attribute)
- tasmota.log("MTR: Read_Attr " + str(ctx) + (attr_name ? " (" + attr_name + ")" : ""), 2)
+ tasmota.log(string.format("MTR: >Read_Attr (%6i) %s", session.local_session_id, str(ctx) + (attr_name ? " (" + attr_name + ")" : "")), 2)
else
- tasmota.log("MTR: Read_Attr " + str(ctx), 2)
+ tasmota.log(string.format("MTR: >Read_Attr (%6i) %s", session.local_session_id, str(ctx)), 2)
end
end
@@ -304,12 +328,12 @@ class Matter_IM
var query = matter.SubscribeRequestMessage().from_TLV(val)
if !query.keep_subscriptions
- self.subs.remove_by_session(msg.session) # if `keep_subscriptions`, kill all subscriptions from current session
+ self.subs_shop.remove_by_session(msg.session) # if `keep_subscriptions`, kill all subscriptions from current session
end
tasmota.log("MTR: received SubscribeRequestMessage=" + str(query), 3)
- var sub = self.subs.new_subscription(msg.session, query)
+ var sub = self.subs_shop.new_subscription(msg.session, query)
# expand a string with all attributes requested
var attr_req = []
@@ -320,10 +344,10 @@ class Matter_IM
ctx.attribute = q.attribute
attr_req.push(str(ctx))
end
- tasmota.log(string.format("MTR: >Subscribe %s (min=%i, max=%i) sub_id=%i",
- attr_req.concat(" "), sub.min_interval, sub.max_interval, sub.subscription_id), 2)
+ tasmota.log(string.format("MTR: >Subscribe (%6i) %s (min=%i, max=%i, keep=%i) sub=%i",
+ msg.session.local_session_id, attr_req.concat(" "), sub.min_interval, sub.max_interval, query.keep_subscriptions ? 1 : 0, sub.subscription_id), 2)
- var ret = self._inner_process_read_request(msg.session, query)
+ var ret = self._inner_process_read_request(msg.session, query, true #-no_log-#)
# ret is of type `Matter_ReportDataMessage`
ret.subscription_id = sub.subscription_id # enrich with subscription id TODO
self.send_subscribe_response(msg, ret, sub)
@@ -356,7 +380,7 @@ class Matter_IM
ctx.status = matter.UNSUPPORTED_COMMAND #default error if returned `nil`
var cmd_name = matter.get_command_name(ctx.cluster, ctx.command)
- tasmota.log(string.format("MTR: >Received %s %s from [%s]:%i", str(ctx), cmd_name ? cmd_name : "", msg.remote_ip, msg.remote_port), 2)
+ tasmota.log(string.format("MTR: >Command (%6i) %s %s from [%s]:%i", msg.session.local_session_id, str(ctx), cmd_name ? cmd_name : "", msg.remote_ip, msg.remote_port), 2)
var res = self.device.invoke_request(msg.session, q.command_fields, ctx)
var a1 = matter.InvokeResponseIB()
if res == true || ctx.status == matter.SUCCESS # special case, just respond ok
@@ -368,7 +392,7 @@ class Matter_IM
a1.status.status = matter.StatusIB()
a1.status.status.status = matter.SUCCESS
ret.invoke_responses.push(a1)
- tasmota.log("MTR: Received TimedRequest=%i from [%s]:%i", query.timeout, msg.remote_ip, msg.remote_port), 2)
+ tasmota.log(string.format("MTR: >Command (%6i) TimedRequest=%i from [%s]:%i", msg.session.local_session_id, query.timeout, msg.remote_ip, msg.remote_port), 2)
# Send success status report
self.send_status(msg, matter.SUCCESS)
@@ -589,16 +613,20 @@ class Matter_IM
end
if size(fake_read.attributes_requests) > 0
- tasmota.log("MTR: 0)
if was_chunked
- tasmota.log(string.format("MTR: Read_Attr next_chunk exch=%i", self.get_exchangeid()), 3)
+ tasmota.log(string.format("MTR: .Read_Attr next_chunk exch=%i", self.get_exchangeid()), 3)
end
if data.more_chunked_messages
if !was_chunked
- tasmota.log(string.format("MTR: Read_Attr first_chunk exch=%i", self.get_exchangeid()), 3)
+ tasmota.log(string.format("MTR: .Read_Attr first_chunk exch=%i", self.get_exchangeid()), 3)
end
# tasmota.log("MTR: sending TLV" + str(data), 4)
end
@@ -227,17 +234,20 @@ class Matter_IM_ReportData : Matter_IM_Message
# print(">>>>> send elements before encode")
var raw_tlv = self.data.to_TLV()
# print(">>>>> send elements before encode 2")
- var encoded_tlv = raw_tlv.encode(bytes(self.MAX_MESSAGE)) # takes time
+ var encoded_tlv = raw_tlv.tlv2raw(bytes(self.MAX_MESSAGE)) # takes time
# print(">>>>> send elements before encode 3")
resp.encode_frame(encoded_tlv) # payload in cleartext, pre-allocate max buffer
# print(">>>>> send elements after encode")
resp.encrypt()
# print(">>>>> send elements after encrypt")
+ tasmota.log(string.format("MTR: 0
data.attribute_reports = next_elemnts
- # tasmota.log("MTR: to_be_sent_later TLV" + str(data), 3)
+ tasmota.log(string.format("MTR: to_be_sent_later size=%i exch=%i", size(data.attribute_reports), resp.exchange_id), 3)
+ self.ready = false # wait for Status Report before continuing sending
return false # keep alive
else
return true # finished, remove
@@ -260,7 +270,7 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
def init(message_handler, session, data, sub)
self.resp = matter.Frame.initiate_response(message_handler, session, 0x05 #-Report Data-#, true)
self.data = data
- self.ready = true # send immediately
+ self.ready = true # by default send immediately
self.expiration = tasmota.millis() + self.MSG_TIMEOUT
#
self.sub = sub
@@ -273,11 +283,14 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
# ack received, confirm the heartbeat
def ack_received(msg)
+ import string
+ tasmota.log(string.format("MTR: IM_ReportDataSubscribed ack_received sub=%i", self.sub.subscription_id), 3)
super(self).ack_received(msg)
if !self.report_data_phase
# if ack is received while all data is sent, means that it finished without error
- self.ready = true
- self.sub.re_arm() # signal that we can proceed to next sub report
+ if self.sub.is_keep_alive # only if keep-alive, for normal reports, re_arm is called at last StatusReport
+ self.sub.re_arm() # signal that we can proceed to next sub report
+ end
return true # proceed to calling send() which removes the message
else
return false # do nothing
@@ -286,6 +299,8 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
# we received an ACK error, remove subscription
def status_error_received(msg)
+ import string
+ tasmota.log(string.format("MTR: IM_ReportDataSubscribed status_error_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
self.sub.remove_self()
end
@@ -293,10 +308,11 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
# return true if we manage the ack ourselves, false if it needs to be done upper
def status_ok_received(msg)
import string
- # tasmota.log(string.format("MTR: >Sub_OK sub_id="+str(self.sub.subscription_id)), 2)
+ tasmota.log(string.format("MTR: IM_ReportDataSubscribed status_ok_received sub=%i exch=%i", self.sub.subscription_id, self.resp.exchange_id), 3)
if self.report_data_phase
return super(self).status_ok_received(msg)
else
+ self.sub.re_arm() # always re_arm at last StatusReport. The only case where it does not happen is during keep-alive, hence we need to lookg for Ack (see above)
super(self).status_ok_received(nil)
return false # let the caller to the ack
end
@@ -304,27 +320,32 @@ class Matter_IM_ReportDataSubscribed : Matter_IM_ReportData
# returns true if transaction is complete (remove object from queue)
# default responder for data
- def send(responder)
+ def send_im(responder)
+ import string
+ tasmota.log(string.format("MTR: IM_ReportDataSubscribed send sub=%i exch=%i ready=%i", self.sub.subscription_id, self.resp.exchange_id, self.ready ? 1 : 0), 3)
+ if !self.ready return false end
if size(self.data.attribute_reports) > 0
if self.report_data_phase
- var ret = super(self).send(responder)
+ var ret = super(self).send_im(responder)
if !ret return false end # ReportData needs to continue
# ReportData is finished
self.report_data_phase = false
return false
else
# send a simple ACK
- var resp = self.resp.build_standalone_ack()
+ var resp = self.resp.build_standalone_ack(false)
resp.encode_frame()
resp.encrypt()
- responder.send_response(resp.raw, resp.remote_ip, resp.remote_port, resp.message_counter)
+ tasmota.log(string.format("MTR: Sub_OK sub_id="+str(self.sub.subscription_id)), 2)
+ tasmota.log(string.format("MTR: >Sub_OK (%6i) sub=%i", msg.session.local_session_id, self.sub.subscription_id), 2)
return super(self).status_ok_received(msg)
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 29f06941b..2837b72f9 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_IM_Subscription.be
@@ -29,7 +29,7 @@ import matter
#################################################################################
class Matter_IM_Subscription
static var MAX_INTERVAL_MARGIN = 5 # we always keep 5s margin
- var subs # pointer to sub shop
+ var subs_shop # pointer to sub shop
# parameters of the subscription
var subscription_id # id of the subcription as known by requester
var session # the session it belongs to
@@ -41,12 +41,13 @@ class Matter_IM_Subscription
var not_before # rate-limiting
var expiration # expiration epoch, we need to respond before
var wait_status # if `true` wait for Status Response before sending anything new
+ var is_keep_alive # was the last message sent an empty keep-alive
# updates
var updates
# req: SubscribeRequestMessage
- def init(subs, id, session, req)
- self.subs = subs
+ def init(subs_shop, id, session, req)
+ self.subs_shop = subs_shop
self.subscription_id = id
self.session = session
# check values for min_interval
@@ -78,14 +79,15 @@ class Matter_IM_Subscription
# update next time interval
self.updates = []
self.clear_before_arm()
+ self.is_keep_alive = false
# tasmota.log("MTR: new subsctiption " + matter.inspect(self), 3)
end
- # remove self from subs list
+ # remove self from subs_shop list
def remove_self()
- tasmota.log("MTR: Remove_Sub sub_id=" + str(self.subscription_id))
- self.subs.remove_sub(self)
+ tasmota.log("MTR: -Sub_Del ( ) sub=" + str(self.subscription_id), 2)
+ self.subs_shop.remove_sub(self)
end
# clear log after it was sent, and re-arm next expiration
@@ -101,7 +103,9 @@ class Matter_IM_Subscription
var now = tasmota.millis()
self.expiration = now + (self.max_interval - self.MAX_INTERVAL_MARGIN) * 1000
self.not_before = now + self.min_interval * 1000 - 1
- tasmota.log(string.format("MTR: >Sub_Done sub_id="+str(self.subscription_id)), 2)
+ if !self.is_keep_alive
+ tasmota.log(string.format("MTR: .Sub_Done ( ) sub=%i", self.subscription_id), 2)
+ end
end
# signal that an attribute was updated, to add to the list of reportable
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Message.be b/lib/libesp32/berry_matter/src/embedded/Matter_Message.be
index 739b65173..ba5029b53 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_Message.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_Message.be
@@ -228,7 +228,7 @@ class Matter_Frame
#############################################################
# Generate a Standalone Acknowledgment
# Uses `PROTOCOL_ID_SECURE_CHANNEL` no ecnryption required
- def build_standalone_ack()
+ def build_standalone_ack(reliable)
import string
# send back response
var resp = classof(self)(self.message_handler)
@@ -253,9 +253,7 @@ class Matter_Frame
resp.protocol_id = 0 # PROTOCOL_ID_SECURE_CHANNEL
resp.x_flag_a = 1 # ACK of previous message
resp.ack_message_counter = self.message_counter
- resp.x_flag_r = 1
-
- tasmota.log(string.format("MTR: Received %s from [%s]:%i", op_name, addr, port), 2)
+ tasmota.log(string.format("MTR: >Received (%6i) %s rid=%i exch=%i from [%s]:%i", session.local_session_id, op_name, frame.message_counter, frame.exchange_id, addr, port), 2)
+ else
+ tasmota.log(string.format("MTR: >rcv Ack (%6i) rid=%i exch=%i ack=%s %sfrom [%s]:%i", session.local_session_id, frame.message_counter, frame.x_flag_r ? "{reliable} " : "", frame.exchange_id, str(frame.ack_message_counter), addr, port), 3)
end
self.commissioning.process_incoming(frame)
return true
@@ -87,7 +89,7 @@ class Matter_MessageHandler
var session = self.device.sessions.get_session_by_local_session_id(frame.local_session_id)
if session == nil
tasmota.log("MTR: unknown local_session_id="+str(frame.local_session_id), 2)
- tasmota.log("MTR: frame="+matter.inspect(frame), 3)
+ # tasmota.log("MTR: frame="+matter.inspect(frame), 3)
return false
end
if addr session._ip = addr end
@@ -97,7 +99,7 @@ class Matter_MessageHandler
# check if it's a duplicate
if !session.counter_rcv_validate(frame.message_counter, true)
- tasmota.log("MTR: rejected duplicate encrypted message = " + str(frame.message_counter) + " counter=" + str(session.counter_rcv), 3)
+ tasmota.log("MTR: . Rejected duplicate encrypted message = " + str(frame.message_counter) + " counter=" + str(session.counter_rcv), 3)
return false
end
@@ -111,9 +113,11 @@ class Matter_MessageHandler
# continue decoding
tasmota.log(string.format("MTR: idx=%i clear=%s", frame.payload_idx, frame.raw.tohex()), 4)
frame.decode_payload()
- tasmota.log("MTR: decrypted message: protocol_id:"+str(frame.protocol_id)+" opcode="+str(frame.opcode)+" exchange_id="+str(frame.exchange_id & 0xFFFF), 3)
+ tasmota.log("MTR: > Decrypted message: protocol_id:"+str(frame.protocol_id)+" opcode="+str(frame.opcode)+" exchange_id="+str(frame.exchange_id & 0xFFFF), 3)
- self.device.packet_ack(frame.ack_message_counter) # acknowledge packet
+ tasmota.log(string.format("MTR: >rcv (%6i) [%02X/%02X] rid=%i exch=%i ack=%s %sfrom [%s]:%i", session.local_session_id, frame.protocol_id, frame.opcode, frame.message_counter, frame.exchange_id, str(frame.ack_message_counter), frame.x_flag_r ? "{reliable} " : "", addr, port), 3)
+
+ self.device.received_ack(frame.ack_message_counter) # remove acknowledge packet from sending list
# dispatch according to protocol_id
var protocol_id = frame.protocol_id
@@ -135,9 +139,10 @@ class Matter_MessageHandler
self.im.send_enqueued(self)
elif frame.x_flag_r # nothing to respond, check if we need a standalone ack
- var resp = frame.build_standalone_ack()
+ var resp = frame.build_standalone_ack(true)
resp.encode_frame()
resp.encrypt()
+ tasmota.log(string.format("MTR: >> NEXT counter_snd=", self.counter_snd, "_impl=", self._counter_snd_impl.val())
+ tasmota.log(string.format("MTR: . Counter_snd=%i", next), 3)
+ # print(">>> NEXT counter_snd=", self.counter_snd, "_impl=", self._counter_snd_impl.val(), 4)
if matter.Counter.is_greater(next, self.counter_snd)
+ self.counter_snd = next + self._COUNTER_SND_INCR
if self.does_persist()
# the persisted counter is behind the actual counter
- self.counter_snd = next + self._COUNTER_SND_INCR
self.save()
- else
- self.counter_snd = next # if no persistance, just keep track
end
end
return next
@@ -869,7 +890,7 @@ class Matter_Session_Store
var f = open(self._FABRICS, "w")
f.write(fabs)
f.close()
- tasmota.log(string.format("MTR: Saved %i fabric(s) and %i session(s)", fabs_size, sessions_saved), 2)
+ tasmota.log(string.format("MTR: =Saved %i fabric(s) and %i session(s)", fabs_size, sessions_saved), 2)
except .. as e, m
tasmota.log("MTR: Session_Store::save Exception:" + str(e) + "|" + str(m), 2)
end
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be b/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be
index c2b25390e..3a21f553b 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_TLV.be
@@ -46,6 +46,20 @@ class Matter_TLV
]
# type values (enum like)
+ #
+ # Type|Description
+ # :----|:---
+ # I1 I2 I4|Signed integer of at most (1/2/4) bytes (as 32 bits signed Berry type)
+ # U1 U2 U4|Unsiged integer of at motst (1/2/4) bytes (as 32 bits signed Berry type, be careful when comparing. Use `matter.Counter.is_greater(a,b)`)
+ # I8 U8|Signed/insigned 8 bytes. You can pass `bytes(8)`, `int64()` or `int`. Type is collapsed to a lower type if possible when encoding.
+ # BOOL|boolean, takes `true` and `false`. Abstracts the internal `BTRUE` and `BFALSE` that you don't need to use
+ # FLOAT|32 bites float
+ # UTF1 UTF2|String as UTF, size is encoded as 1 or 2 bytes automatically
+ # B1 B2|raw `bytes()`, size is encoded as 1 or 2 bytes automatically
+ # NULL|takes only `nil` value
+ # STRUCT
ARRAY
LIST
EOC|(internal) Use through abstractions
+ # DOUBLE
UTF4 UTF8
B4 B8|Unsuppored in Tasmota
+
static var I1 = 0x00
static var I2 = 0x01
static var I4 = 0x02
@@ -206,7 +220,7 @@ class Matter_TLV
# encode TLV
#
# appends to the bytes() object
- def encode(b)
+ def tlv2raw(b)
var TLV = self.TLV
if b == nil b = bytes() end # start new buffer if none passed
@@ -605,7 +619,7 @@ class Matter_TLV
#############################################################
# encode to bytes
- def encode(b)
+ def tlv2raw(b)
if b == nil b = bytes() end
# encode tag and type
self._encode_tag(b)
@@ -618,7 +632,7 @@ class Matter_TLV
# output each one after the other
for v : val_list
- v.encode(b)
+ v.tlv2raw(b)
end
# add 'end of container'
@@ -885,7 +899,7 @@ import matter
def test_TLV(b, s)
var m = matter.TLV.parse(b)
assert(m.tostring() == s)
- assert(m.encode() == b)
+ assert(m.tlv2raw() == b)
assert(m.encode_len() == size(b))
end
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_UDPServer.be b/lib/libesp32/berry_matter/src/embedded/Matter_UDPServer.be
index 5716d4715..cdfe9efc4 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_UDPServer.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_UDPServer.be
@@ -46,9 +46,13 @@ class Matter_UDPPacket_sent
self.port = port
self.msg_id = id
self.retries = 0
- self.next_try = tasmota.millis() + self.backoff_time(self.retries)
+ self.next_try = tasmota.millis() + matter.UDPServer._backoff_time(self.retries)
end
+ #############################################################
+ # Send packet now.
+ #
+ # Returns `true` if packet was successfully sent.
def send(udp_socket)
import string
var ok = udp_socket.send(self.addr ? self.addr : udp_socket.remote_ip, self.port ? self.port : udp_socket.remote_port, self.raw)
@@ -57,27 +61,9 @@ class Matter_UDPPacket_sent
else
tasmota.log(string.format("MTR: error sending packet to '[%s]:%i'", self.addr, self.port), 3)
end
+ return ok
end
- #############################################################
- # Compute exponential backoff as per 4.11.2.1 p.137
- def backoff_time(n)
- def power_int(v, n)
- var r = 1
- while n > 0
- r *= v
- n -= 1
- end
- return r
- end
-
- import math
- var i = 300 # SLEEPY_ACTIVE_INTERVAL
- var rand = real(math.rand() & 0xFF) / 255 # 0..1 with reasonable granularity
- var n_power = n > 0 ? n - 1 : 0
- var mrpBackoffTime = i * power_int(1.6, n_power) * (1.0 + rand * 0.25 )
- return int(mrpBackoffTime)
- end
end
matter.UDPPacket_sent = Matter_UDPPacket_sent
@@ -88,30 +74,33 @@ matter.UDPPacket_sent = Matter_UDPPacket_sent
class Matter_UDPServer
static var RETRIES = 4 # 5 transmissions max (4 retries) `MRP_MAX_TRANSMISSIONS` 4.11.8 p.146
static var MAX_PACKETS_READ = 4 # read at most 4 packets per tick
- var address, port # local address and port
+ var addr, port # local addr and port
var listening # true if active
var udp_socket
var dispatch_cb # callback to call when a message is received
var packets_sent # list map of packets sent to be acknowledged
#############################################################
- def init(address, port)
- self.address = address ? address : ""
+ # Init UDP Server listening to `addr` and `port` (opt).
+ #
+ # By default, the server listens to `""` (all addresses) and port `5540`
+ def init(addr, port)
+ self.addr = addr ? addr : ""
self.port = port ? port : 5540
self.listening = false
self.packets_sent = []
end
#############################################################
- # start the server
- # raises an exception if something is wrong
- # registers as device handle
+ # Starts the server.
+ # Registers as device handler to Tasmota
#
- # `cb`: callback to call when a message is received
+ # `cb(packet, from_addr, from_port)`: callback to call when a message is received.
+ # Raises an exception if something is wrong.
def start(cb)
if !self.listening
self.udp_socket = udp()
- var ok = self.udp_socket.begin(self.address, self.port)
+ var ok = self.udp_socket.begin(self.addr, self.port)
if !ok raise "network_error", "could not open UDP server" end
self.listening = true
self.dispatch_cb = cb
@@ -120,8 +109,7 @@ class Matter_UDPServer
end
#############################################################
- # stop the server
- # remove driver
+ # Stops the server and remove driver
def stop()
if self.listening
self.udp_socket.stop()
@@ -131,6 +119,11 @@ class Matter_UDPServer
end
#############################################################
+ # At every tick:
+ # Check if a packet has arrived, and dispatch to `cb`.
+ # Read at most `MAX_PACKETS_READ (4) packets at each tick to
+ # avoid any starvation.
+ # Then resend queued outgoing packets.
def every_50ms()
import string
var packet_read = 0
@@ -152,25 +145,33 @@ class Matter_UDPServer
packet = nil
end
end
- self.resend_packets() # resend any packet
+ self._resend_packets() # resend any packet
end
#############################################################
- def resend_packets()
+ # Resend packets if they have not been acknowledged by receiver
+ # either with direct Ack packet or ack embedded in another packet.
+ # Packets with `id`=`nil` are not resent.
+ #
+ # Packets are re-sent at most `RETRIES` (4) times, i.e. sent maximum 5 times.
+ # Exponential backoff is added after each resending.
+ #
+ # If all retries expired, remove packet and log.
+ def _resend_packets()
var idx = 0
while idx < size(self.packets_sent)
var packet = self.packets_sent[idx]
if tasmota.time_reached(packet.next_try)
if packet.retries <= self.RETRIES
- tasmota.log("MTR: resending packet id=" + str(packet.msg_id), 3)
+ tasmota.log("MTR: . Resending packet id=" + str(packet.msg_id), 3)
packet.send(self.udp_socket) # resend
- packet.next_try = tasmota.millis() + packet.backoff_time(packet.retries)
+ packet.next_try = tasmota.millis() + self._backoff_time(packet.retries)
packet.retries += 1
idx += 1
else
import string
self.packets_sent.remove(idx)
- tasmota.log(string.format("MTR: target unreachable '[%s]:%i' msg_id=%i", packet.addr, packet.port, packet.msg_id), 2)
+ tasmota.log(string.format("MTR: . Unacked packet '[%s]:%i' msg_id=%i", packet.addr, packet.port, packet.msg_id), 2)
end
else
idx += 1
@@ -179,14 +180,15 @@ class Matter_UDPServer
end
#############################################################
- # just received acknowledgment, remove packet from sender
- def packet_ack(id)
+ # Just received acknowledgment, remove packet from sender
+ def received_ack(id)
if id == nil return end
+ tasmota.log("MTR: receveived ACK id="+str(id), 3)
var idx = 0
while idx < size(self.packets_sent)
if self.packets_sent[idx].msg_id == id
self.packets_sent.remove(idx)
- tasmota.log("MTR: removed packet from sending list id=" + str(id), 4)
+ tasmota.log("MTR: . Removed packet from sending list id=" + str(id), 3)
else
idx += 1
end
@@ -194,10 +196,12 @@ class Matter_UDPServer
end
#############################################################
+ # Send a packet, enqueue it if `id` is not `nil`
def send_response(raw, addr, port, id)
var packet = matter.UDPPacket_sent(raw, addr, port, id)
packet.send(self.udp_socket) # send
if id
+ # tasmota.log("MTR: <<< enqueue id="+str(id))
self.packets_sent.push(packet)
end
end
@@ -206,13 +210,25 @@ class Matter_UDPServer
# placeholder, nothing to run for now
def every_second()
end
+
+ #############################################################
+ # Compute exponential backoff as per 4.11.2.1 p.137
+ static def _backoff_time(n)
+ def power_int(v, n)
+ var r = 1
+ while n > 0
+ r *= v
+ n -= 1
+ end
+ return r
+ end
+
+ import math
+ var i = 300 # SLEEPY_ACTIVE_INTERVAL
+ var rand = real(math.rand() & 0xFF) / 255 # 0..1 with reasonable granularity
+ var n_power = n > 0 ? n - 1 : 0
+ var mrpBackoffTime = i * power_int(1.6, n_power) * (1.0 + rand * 0.25 )
+ return int(mrpBackoffTime)
+ end
end
matter.UDPServer = Matter_UDPServer
-
-#-
-
-import matter
-var udps = matter.UDPServer()
-udps.listen()
-
--#
diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_UI.be b/lib/libesp32/berry_matter/src/embedded/Matter_UI.be
index 0ddae333b..300a669c4 100644
--- a/lib/libesp32/berry_matter/src/embedded/Matter_UI.be
+++ b/lib/libesp32/berry_matter/src/embedded/Matter_UI.be
@@ -185,7 +185,9 @@ class Matter_UI
if !first webserver.content_send("
") end
first = false
- webserver.content_send(string.format("