Matter refactor commissioning (#21870)

This commit is contained in:
s-hadinger 2024-07-30 13:35:06 +02:00 committed by GitHub
parent 0c52b40f49
commit eb71fcfd2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 8423 additions and 8711 deletions

View File

@ -222,7 +222,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_Session.h"
#include "solidify/solidified_Matter_Session_Store.h"
#include "solidify/solidified_Matter_Commissioning_Data.h"
#include "solidify/solidified_Matter_Commissioning.h"
#include "solidify/solidified_Matter_Commissioning_Context.h"
#include "solidify/solidified_Matter_Message.h"
#include "solidify/solidified_Matter_MessageHandler.h"
#include "solidify/solidified_Matter_IM_Message.h"
@ -231,6 +231,7 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because
#include "solidify/solidified_Matter_EventHandler.h"
#include "solidify/solidified_Matter_Control_Message.h"
#include "solidify/solidified_Matter_Plugin_0.h"
#include "solidify/solidified_Matter_z_Commissioning.h"
#include "solidify/solidified_Matter_Base38.h"
#include "solidify/solidified_Matter_UI.h"
#include "solidify/solidified_Matter_Profiler.h"
@ -470,6 +471,9 @@ module matter (scope: global, strings: weak) {
Control_Message, class(be_class_Matter_Control_Message)
UI, class(be_class_Matter_UI)
// Commissioning
Commissioning, class(be_class_Matter_Commissioning)
// QR Code
QRCode, class(be_class_Matter_QRCode)

View File

@ -1,5 +1,5 @@
#
# Matter_Commissioning.be - suppport for Matter Commissioning process PASE and CASE
# Matter_Commissioning_Context.be - suppport for Matter Commissioning process PASE and CASE
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
@ -53,7 +53,7 @@ class Matter_Commisioning_Context
def process_incoming(msg)
#
if !self.device.is_commissioning_open() && msg.opcode >= 0x20 && msg.opcode <= 0x24
if !self.device.commissioning.is_commissioning_open() && msg.opcode >= 0x20 && msg.opcode <= 0x24
log("MTR: commissioning not open", 2)
return false
end
@ -139,8 +139,8 @@ class Matter_Commisioning_Context
# generate 32 bytes random
pbkdfparamresp.responderRandom = crypto.random(32)
pbkdfparamresp.responderSessionId = session.__future_local_session_id
pbkdfparamresp.pbkdf_parameters_salt = self.device.commissioning_salt
pbkdfparamresp.pbkdf_parameters_iterations = self.device.commissioning_iterations
pbkdfparamresp.pbkdf_parameters_salt = self.device.commissioning.commissioning_salt
pbkdfparamresp.pbkdf_parameters_iterations = self.device.commissioning.commissioning_iterations
# log("MTR: pbkdfparamresp: " + str(matter.inspect(pbkdfparamresp)), 4)
var pbkdfparamresp_raw = pbkdfparamresp.tlv2raw()
# log("MTR: pbkdfparamresp_raw: " + pbkdfparamresp_raw.tohex(), 4)
@ -172,7 +172,7 @@ class Matter_Commisioning_Context
# instanciate SPAKE
# for testing purpose, we don't send `w1` to make sure
var spake = crypto.SPAKE2P_Matter(self.device.commissioning_w0, nil, self.device.commissioning_L)
var spake = crypto.SPAKE2P_Matter(self.device.commissioning.commissioning_w0, nil, self.device.commissioning.commissioning_L)
# generate `y` nonce (not persisted)
var y = crypto.random(32) # 32 bytes random known only by verifier

View File

@ -196,17 +196,17 @@ class Matter_Plugin_Root : Matter_Plugin
# ====================================================================================================
elif cluster == 0x003C # ========== Administrator Commissioning Cluster 11.18 p.725 ==========
if attribute == 0x0000 # ---------- WindowStatus / u8 ----------
var commissioning_open = self.device.is_commissioning_open()
var basic_commissioning = self.device.is_root_commissioning_open()
var commissioning_open = self.device.commissioning.is_commissioning_open()
var basic_commissioning = self.device.commissioning.is_root_commissioning_open()
var val = commissioning_open ? (basic_commissioning ? 2 #-BasicWindowOpen-# : 1 #-EnhancedWindowOpen-#) : 0 #-WindowNotOpen-#
return tlv_solo.set(TLV.U1, val)
elif attribute == 0x0001 # ---------- AdminFabricIndex / u16 ----------
var admin_fabric = self.device.commissioning_admin_fabric
var admin_fabric = self.device.commissioning.commissioning_admin_fabric
if admin_fabric != nil
return tlv_solo.set_or_nil(TLV.U2, admin_fabric.get_fabric_index())
end
elif attribute == 0x0002 # ---------- AdminVendorId / u16 ----------
var admin_fabric = self.device.commissioning_admin_fabric
var admin_fabric = self.device.commissioning.commissioning_admin_fabric
if admin_fabric != nil
return tlv_solo.set_or_nil(TLV.U2, admin_fabric.get_admin_vendor())
end
@ -387,7 +387,7 @@ class Matter_Plugin_Root : Matter_Plugin
ccr.add_TLV(1, TLV.UTF1, "") # DebugText = ""
ctx.command = 0x05 # CommissioningCompleteResponse
self.device.start_commissioning_complete_deferred(session)
self.device.commissioning.start_commissioning_complete_deferred(session)
return ccr
else
raise "context_error", "CommissioningComplete: no fabric attached"
@ -526,7 +526,7 @@ class Matter_Plugin_Root : Matter_Plugin
var hk = crypto.HKDF_SHA256()
var fabric_rev = fabric_id.copy().reverse()
var k_fabric = hk.derive(root_ca_pub, fabric_rev, info, 8)
var parent_fabric = session._fabric ? session._fabric : self.device.commissioning_admin_fabric # get parent fabric whether CASE or PASE
var parent_fabric = session._fabric ? session._fabric : self.device.commissioning.commissioning_admin_fabric # get parent fabric whether CASE or PASE
new_fabric.set_fabric_device(fabric_id, deviceid, k_fabric, parent_fabric)
# log("MTR: AddNoc k_fabric=" + str(k_fabric), 3)
@ -534,7 +534,7 @@ class Matter_Plugin_Root : Matter_Plugin
new_fabric.fabric_candidate()
# move to next step
self.device.start_operational_discovery_deferred(new_fabric)
self.device.commissioning.start_operational_discovery_deferred(new_fabric)
# we keep the PASE session for 1 minute
if session.is_PASE()
@ -627,7 +627,7 @@ class Matter_Plugin_Root : Matter_Plugin
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())
self.device.commissioning.start_basic_commissioning(timeout, iterations, discriminator, salt, w0, #-w1,-# L, session.get_fabric())
# TODO announce in MDNS
return true # OK
elif command == 0x0001 # ---------- OpenBasicCommissioningWindow ----------
@ -637,7 +637,7 @@ class Matter_Plugin_Root : Matter_Plugin
return true
elif command == 0x0002 # ---------- RevokeCommissioning ----------
# TODO add checks that the commissioning window was opened by the same fabric
self.device.stop_basic_commissioning()
self.device.commissioning.stop_basic_commissioning()
return true
end

View File

@ -108,7 +108,7 @@ class Matter_UI
if self.matter_enabled()
# checkbox for Matter commissioning
var commissioning_open_checked = self.device.commissioning_open != nil ? "checked" : ""
var commissioning_open_checked = self.device.commissioning.commissioning_open != nil ? "checked" : ""
webserver.content_send(f"<p><input id='comm' type='checkbox' name='comm' {commissioning_open_checked}>")
webserver.content_send("<label for='comm'><b>Commissioning open</b></label></p>")
var disable_bridge_mode_checked = self.device.disable_bridge_mode ? " checked" : ""
@ -173,17 +173,17 @@ class Matter_UI
def show_commissioning_info()
import webserver
var seconds_left = (self.device.commissioning_open - tasmota.millis()) / 1000
var seconds_left = (self.device.commissioning.commissioning_open - tasmota.millis()) / 1000
if seconds_left < 0 seconds_left = 0 end
var min_left = (seconds_left + 30) / 60
webserver.content_send(f"<fieldset><legend><b>&nbsp;Commissioning open for {min_left:i} min&nbsp;</b></legend><p></p>")
var pairing_code = self.device.compute_manual_pairing_code()
var pairing_code = self.device.commissioning.compute_manual_pairing_code()
webserver.content_send(f"<p>Manual pairing code:<br><b>{pairing_code[0..3]}-{pairing_code[4..6]}-{pairing_code[7..]}</b></p><hr>")
webserver.content_send("<div><center>")
var qr_text = self.device.compute_qrcode_content()
var qr_text = self.device.commissioning.compute_qrcode_content()
self.show_qrcode(qr_text)
webserver.content_send(f"<p> {qr_text}</p>")
webserver.content_send("</div><p></p></fieldset><p></p>")
@ -794,11 +794,11 @@ class Matter_UI
end
#- and force restart -#
webserver.redirect("/?rst=")
elif matter_commissioning_requested != (self.device.commissioning_open != nil)
elif matter_commissioning_requested != (self.device.commissioning.commissioning_open != nil)
if matter_commissioning_requested
self.device.start_root_basic_commissioning()
else
self.device.stop_basic_commissioning()
self.device.commissioning.stop_basic_commissioning()
end
#- and force restart -#
@ -1085,7 +1085,7 @@ class Matter_UI
self.show_bridge_status()
if self.device.is_root_commissioning_open()
if self.device.commissioning.is_root_commissioning_open()
self.show_commissioning_info()
end
@ -1095,7 +1095,7 @@ class Matter_UI
def web_get_arg()
import webserver
if webserver.has_arg("mtc0") # Close Commissioning
self.device.stop_basic_commissioning()
self.device.commissioning.stop_basic_commissioning()
elif webserver.has_arg("mtc1") # Open Commissioning
self.device.start_root_basic_commissioning()
end

View File

@ -0,0 +1,483 @@
#
# Matter_z_Commissioning.be - implements the commissioning and MDNS logic
#
# Copyright (C) 2023 Stephan Hadinger & Theo Arends
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import matter
#@ solidify:Matter_Commissioning,weak
class Matter_Commissioning
var device # reference to the main Device instance
static var PBKDF_ITERATIONS = 1000 # I don't see any reason to choose a different number
static var PASE_TIMEOUT = 10*60 # default open commissioning window (10 minutes)
# 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 # commissioning_discriminator
var commissioning_salt # current salt
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 (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
# 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
#############################################################
def init(device)
self.device = device
self._init_basic_commissioning()
end
#############################################################
# Start Basic Commissioning Window if needed at startup
def _init_basic_commissioning()
# if no fabric is configured, automatically open commissioning at restart
if self.device.sessions.count_active_fabrics() == 0
self.start_root_basic_commissioning()
end
end
#############################################################
# dispatch every second click to sub-objects that need it
def every_second()
if self.commissioning_open != nil && tasmota.time_reached(self.commissioning_open) # timeout reached, close provisioning
self.commissioning_open = nil
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
# show Manual pairing code in logs
var pairing_code = self.compute_manual_pairing_code()
log(format("MTR: Manual pairing code: %s", pairing_code), 2)
# output MQTT
var qr_code = self.compute_qrcode_content()
tasmota.publish_result(format('{"Matter":{"Commissioning":1,"PairingCode":"%s","QRCode":"%s"}}', pairing_code, qr_code), 'Matter')
# compute PBKDF
import crypto
var root_salt = crypto.random(16)
# Compute the PBKDF parameters for SPAKE2+ from root parameters
var passcode = bytes().add(self.device.root_passcode, 4)
var tv = crypto.PBKDF2_HMAC_SHA256().derive(passcode, root_salt, self.PBKDF_ITERATIONS, 80)
var w0s = tv[0..39]
var w1s = tv[40..79]
var 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)
var root_L = crypto.EC_P256().public_key(w1)
self.start_basic_commissioning(timeout_s, self.PBKDF_ITERATIONS, self.device.root_discriminator, root_salt, root_w0, #-self.root_w1,-# root_L, nil)
end
#############################################################
# 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_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
#############################################################
# 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()
var n = nil
if self.is_root_commissioning_open()
tasmota.publish_result('{"Matter":{"Commissioning":0}}', 'Matter')
end
self.commissioning_open = n
self.mdns_remove_PASE()
# clear any PBKDF information to free memory
self.commissioning_iterations = n
self.commissioning_discriminator = n
self.commissioning_salt = n
self.commissioning_w0 = n
# self.commissioning_w1 = nil
self.commissioning_L = n
self.commissioning_admin_fabric = n
end
def is_commissioning_open()
return self.commissioning_open != nil
end
#############################################################
# 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
raw.setbits(3, 16, self.device.VENDOR_ID)
raw.setbits(19, 16, self.device.PRODUCT_ID)
# custom flow = 0 (offset=35, len=2)
raw.setbits(37, 8, 0x04) # already on IP network
raw.setbits(45, 12, self.device.root_discriminator & 0xFFF)
raw.setbits(57, 27, self.device.root_passcode & 0x7FFFFFF)
# padding (offset=84 len=4)
return "MT:" + matter.Base38.encode(raw)
end
#############################################################
# Compute the 11 digits manual pairing code (wihout vendorid nor productid) p.223
# <BR>
# 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()
var digit_1 = (self.device.root_discriminator & 0x0FFF) >> 10
var digit_2_6 = ((self.device.root_discriminator & 0x0300) << 6) | (self.device.root_passcode & 0x3FFF)
var digit_7_10 = (self.device.root_passcode >> 14)
var ret = format("%1i%05i%04i", digit_1, digit_2_6, digit_7_10)
ret += matter.Verhoeff.checksum(ret)
return ret
end
#############################################################
# Start Operational Discovery for this session
#
# Deferred until next tick.
def start_operational_discovery_deferred(fabric)
# defer to next click
tasmota.set_timer(0, /-> self.start_operational_discovery(fabric))
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 for this session
#
# Stop Basic Commissioning and clean PASE specific values (to save memory).
# Announce fabric entry in mDNS.
def start_operational_discovery(fabric)
import crypto
import mdns
self.stop_basic_commissioning() # close all PASE commissioning information
self.mdns_announce_op_discovery(fabric)
end
#############################################################
# Commissioning Complete
#
# Stop basic commissioning.
def start_commissioning_complete(session)
var fabric = session.get_fabric()
var fabric_id = fabric.get_fabric_id().copy().reverse().tohex()
var vendor_name = fabric.get_admin_vendor_name()
log(f"MTR: --- Commissioning complete for Fabric '{fabric_id}' (Vendor {vendor_name}) ---", 2)
self.stop_basic_commissioning() # by default close commissioning when it's complete
end
#############################################################
# mDNS Configuration
#############################################################
# Start mDNS and announce hostnames for Wifi and ETH from MAC
#
# When the announce is active, `hostname_wifi` and `hostname_eth`
# are defined
def start_mdns_announce_hostnames()
if tasmota.wifi()['up']
self._mdns_announce_hostname(false)
else
tasmota.add_rule("Wifi#Connected", def ()
self._mdns_announce_hostname(false)
tasmota.remove_rule("Wifi#Connected", "matter_mdns_host")
end, "matter_mdns_host")
end
if tasmota.eth()['up']
self._mdns_announce_hostname(true)
else
tasmota.add_rule("Eth#Connected", def ()
self._mdns_announce_hostname(true)
tasmota.remove_rule("Eth#Connected", "matter_mdns_host")
end, "matter_mdns_host")
end
end
#############################################################
# 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 _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.device.ipv4only || !eth.contains('ip6local')
# log(format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_eth, eth.find('ip6local',''), eth.find('ip','')), 4)
mdns.add_hostname(self.hostname_eth, eth.find('ip6local',''), eth.find('ip',''), eth.find('ip6',''))
else
log(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.device.ipv4only || !wifi.contains('ip6local')
# log(format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip','')), 4)
mdns.add_hostname(self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip',''), wifi.find('ip6',''))
else
log(format("MTR: calling mdns.add_hostname(%s, %s)", self.hostname_wifi, wifi.find('ip','')), 3)
mdns.add_hostname(self.hostname_wifi, wifi.find('ip',''))
end
end
log(format("MTR: start mDNS on %s host '%s.local'", is_eth ? "eth" : "wifi", is_eth ? self.hostname_eth : self.hostname_wifi), 3)
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
self.mdns_announce_op_discovery_all_fabrics()
end
#############################################################
# Announce MDNS for PASE commissioning
def mdns_announce_PASE()
import mdns
import crypto
var services = {
"VP": f"{self.device.VENDOR_ID}+{self.device.PRODUCT_ID}",
"D": self.commissioning_discriminator,
"CM":1, # requires passcode
"T":0, # no support for TCP
"SII":5000, "SAI":300
}
self.commissioning_instance_wifi = crypto.random(8).tohex() # 16 characters random hostname
self.commissioning_instance_eth = crypto.random(8).tohex() # 16 characters random hostname
try
if self.hostname_eth
# Add Matter `_matterc._udp` service
# log(format("MTR: calling mdns.add_service(%s, %s, %i, %s, %s, %s)", "_matterc", "_udp", 5540, str(services), self.commissioning_instance_eth, self.hostname_eth), 4)
mdns.add_service("_matterc", "_udp", 5540, services, self.commissioning_instance_eth, self.hostname_eth)
self.mdns_pase_eth = true
log(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.commissioning_discriminator & 0xFFF)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_S" + str((self.commissioning_discriminator & 0xF00) >> 8)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_V" + str(self.device.VENDOR_ID)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_CM1"
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
end
if self.hostname_wifi
# log(format("MTR: calling mdns.add_service(%s, %s, %i, %s, %s, %s)", "_matterc", "_udp", 5540, str(services), self.commissioning_instance_wifi, self.hostname_wifi), 4)
mdns.add_service("_matterc", "_udp", 5540, services, self.commissioning_instance_wifi, self.hostname_wifi)
self.mdns_pase_wifi = true
log(format("MTR: starting mDNS on %s '%s' ptr to `%s.local`", "wifi", self.commissioning_instance_wifi, self.hostname_wifi), 3)
# `mdns.add_subtype(service:string, proto:string, instance:string, hostname:string, subtype:string) -> nil`
var subtype = "_L" + str(self.commissioning_discriminator & 0xFFF)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_S" + str((self.commissioning_discriminator & 0xF00) >> 8)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_V" + str(self.device.VENDOR_ID)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_CM1"
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# MDNS remove any PASE announce
def mdns_remove_PASE()
import mdns
try
if self.mdns_pase_eth
log(format("MTR: calling mdns.remove_service(%s, %s, %s, %s)", "_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth), 3)
log(format("MTR: remove mDNS on %s '%s'", "eth", self.commissioning_instance_eth), 3)
self.mdns_pase_eth = false
mdns.remove_service("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth)
end
if self.mdns_pase_wifi
log(format("MTR: calling mdns.remove_service(%s, %s, %s, %s)", "_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi), 3)
log(format("MTR: remove mDNS on %s '%s'", "wifi", self.commissioning_instance_wifi), 3)
self.mdns_pase_wifi = false
mdns.remove_service("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# Start UDP mDNS announcements for commissioning for all persisted sessions
def mdns_announce_op_discovery_all_fabrics()
for fabric: self.device.sessions.active_fabrics()
if fabric.get_device_id() && fabric.get_fabric_id()
self.mdns_announce_op_discovery(fabric)
end
end
end
#############################################################
# Start UDP mDNS announcements for commissioning
def mdns_announce_op_discovery(fabric)
import mdns
try
var device_id = fabric.get_device_id().copy().reverse()
var k_fabric = fabric.get_fabric_compressed()
var op_node = k_fabric.tohex() + "-" + device_id.tohex()
log("MTR: Operational Discovery node = " + op_node, 3)
# mdns
if (tasmota.eth().find("up"))
log(format("MTR: adding mDNS on %s '%s' ptr to `%s.local`", "eth", op_node, self.hostname_eth), 3)
mdns.add_service("_matter","_tcp", 5540, nil, op_node, self.hostname_eth)
var subtype = "_I" + k_fabric.tohex()
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matter", "_tcp", op_node, self.hostname_eth, subtype)
end
if (tasmota.wifi().find("up"))
log(format("MTR: adding mDNS on %s '%s' ptr to `%s.local`", "wifi", op_node, self.hostname_wifi), 3)
mdns.add_service("_matter","_tcp", 5540, nil, op_node, self.hostname_wifi)
var subtype = "_I" + k_fabric.tohex()
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matter", "_tcp", op_node, self.hostname_wifi, subtype)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# Remove all mDNS announces for all fabrics
def mdns_remove_op_discovery_all_fabrics()
for fabric: self.device.sessions.active_fabrics()
if fabric.get_device_id() && fabric.get_fabric_id()
self.mdns_remove_op_discovery(fabric)
end
end
end
#############################################################
# Remove mDNS announce for fabric
def mdns_remove_op_discovery(fabric)
import mdns
try
var device_id = fabric.get_device_id().copy().reverse()
var k_fabric = fabric.get_fabric_compressed()
var op_node = k_fabric.tohex() + "-" + device_id.tohex()
# mdns
if (tasmota.eth().find("up"))
log(format("MTR: remove mDNS on %s '%s'", "eth", op_node), 3)
mdns.remove_service("_matter", "_tcp", op_node, self.hostname_eth)
end
if (tasmota.wifi().find("up"))
log(format("MTR: remove mDNS on %s '%s'", "wifi", op_node), 3)
mdns.remove_service("_matter", "_tcp", op_node, self.hostname_wifi)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#####################################################################
# Generate random passcode
#####################################################################
static var PASSCODE_INVALID = [ 0, 11111111, 22222222, 33333333, 44444444, 55555555, 66666666, 77777777, 88888888, 99999999, 12345678, 87654321]
def generate_random_passcode()
import crypto
var passcode
while true
passcode = crypto.random(4).get(0, 4) & 0x7FFFFFF
if passcode > 0x5F5E0FE continue end # larger than allowed
for inv: self.PASSCODE_INVALID
if passcode == inv passcode = nil end
end
if passcode != nil return passcode end
end
end
end
matter.Commissioning = Matter_Commissioning

View File

@ -23,11 +23,9 @@ import matter
class Matter_Device
static var UDP_PORT = 5540 # this is the default port for group multicast, we also use it for unicast
static var PBKDF_ITERATIONS = 1000 # I don't see any reason to choose a different number
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 started # is the Matter Device started (configured, mDNS and UDPServer started)
var plugins # list of plugins instances
var plugins_persist # true if plugins configuration needs to be saved
@ -37,27 +35,12 @@ class Matter_Device
var udp_server # `matter.UDPServer()` object
var profiler
var message_handler # `matter.MessageHandler()` object
var commissioning # `matter.Commissioning()` object
var sessions # `matter.Session_Store()` objet
var ui
var tick # increment at each tick, avoids to repeat too frequently some actions
# Events
var events # Event handler
# 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 # commissioning_discriminator
var commissioning_salt # current salt
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 (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
# 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
# for brige mode, list of HTTP_remote objects (only one instance per remote object)
var http_remotes # map of 'domain:port' to `Matter_HTTP_remote` instance or `nil` if no bridges
# saved in parameters
@ -114,7 +97,7 @@ class Matter_Device
end, "matter_start")
end
self._init_basic_commissioning()
self.commissioning = matter.Commissioning(self)
tasmota.add_driver(self)
@ -131,60 +114,18 @@ class Matter_Device
self._start_udp(self.UDP_PORT)
self.start_mdns_announce_hostnames()
self.commissioning.start_mdns_announce_hostnames()
self.started = true
end
#############################################################
# 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
# show Manual pairing code in logs
var pairing_code = self.compute_manual_pairing_code()
log(format("MTR: Manual pairing code: %s", pairing_code), 2)
# output MQTT
var qr_code = self.compute_qrcode_content()
tasmota.publish_result(format('{"Matter":{"Commissioning":1,"PairingCode":"%s","QRCode":"%s"}}', pairing_code, qr_code), 'Matter')
# compute PBKDF
import crypto
var root_salt = crypto.random(16)
# Compute the PBKDF parameters for SPAKE2+ from root parameters
var passcode = bytes().add(self.root_passcode, 4)
var tv = crypto.PBKDF2_HMAC_SHA256().derive(passcode, root_salt, self.PBKDF_ITERATIONS, 80)
var w0s = tv[0..39]
var w1s = tv[40..79]
var 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)
var root_L = crypto.EC_P256().public_key(w1)
self.start_basic_commissioning(timeout_s, self.PBKDF_ITERATIONS, self.root_discriminator, root_salt, root_w0, #-self.root_w1,-# root_L, nil)
end
#####################################################################
# Remove a fabric and clean all corresponding values and mDNS entries
def remove_fabric(fabric)
if fabric != nil
log("MTR: removing fabric " + fabric.get_fabric_id().copy().reverse().tohex(), 2)
self.message_handler.im.subs_shop.remove_by_fabric(fabric)
self.mdns_remove_op_discovery(fabric)
self.commissioning.mdns_remove_op_discovery(fabric)
self.sessions.remove_fabric(fabric)
end
# var sub_fabrics = self.sessions.find_children_fabrics(fabric_parent.get_fabric_index())
@ -201,91 +142,6 @@ class Matter_Device
self.sessions.save_fabrics()
end
#############################################################
# 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_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
#############################################################
# 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()
var n = nil
if self.is_root_commissioning_open()
tasmota.publish_result('{"Matter":{"Commissioning":0}}', 'Matter')
end
self.commissioning_open = n
self.mdns_remove_PASE()
# clear any PBKDF information to free memory
self.commissioning_iterations = n
self.commissioning_discriminator = n
self.commissioning_salt = n
self.commissioning_w0 = n
# self.commissioning_w1 = nil
self.commissioning_L = n
self.commissioning_admin_fabric = n
end
def is_commissioning_open()
return self.commissioning_open != nil
end
#############################################################
# 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
raw.setbits(3, 16, self.VENDOR_ID)
raw.setbits(19, 16, self.PRODUCT_ID)
# custom flow = 0 (offset=35, len=2)
raw.setbits(37, 8, 0x04) # already on IP network
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
#############################################################
# Compute the 11 digits manual pairing code (wihout vendorid nor productid) p.223
# <BR>
# 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()
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 = format("%1i%05i%04i", digit_1, digit_2_6, digit_7_10)
ret += matter.Verhoeff.checksum(ret)
return ret
end
#####################################################################
# Driver handling of buttons
#####################################################################
@ -330,9 +186,7 @@ class Matter_Device
self.sessions.every_second()
self.message_handler.every_second()
self.events.every_second() # periodically remove bytes() representation of events
if self.commissioning_open != nil && tasmota.time_reached(self.commissioning_open) # timeout reached, close provisioning
self.commissioning_open = nil
end
self.commissioning.every_second()
end
#############################################################
@ -440,50 +294,6 @@ class Matter_Device
self.udp_server.start(/ raw, addr, port -> self.msg_received(raw, addr, port))
end
#############################################################
# Start Operational Discovery for this session
#
# Deferred until next tick.
def start_operational_discovery_deferred(fabric)
# defer to next click
tasmota.set_timer(0, /-> self.start_operational_discovery(fabric))
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 for this session
#
# Stop Basic Commissioning and clean PASE specific values (to save memory).
# Announce fabric entry in mDNS.
def start_operational_discovery(fabric)
import crypto
import mdns
self.stop_basic_commissioning() # close all PASE commissioning information
self.mdns_announce_op_discovery(fabric)
end
#############################################################
# Commissioning Complete
#
# Stop basic commissioning.
def start_commissioning_complete(session)
var fabric = session.get_fabric()
var fabric_id = fabric.get_fabric_id().copy().reverse().tohex()
var vendor_name = fabric.get_admin_vendor_name()
log(f"MTR: --- Commissioning complete for Fabric '{fabric_id}' (Vendor {vendor_name}) ---", 2)
self.stop_basic_commissioning() # by default close commissioning when it's complete
end
#################################################################################
# Simple insertion sort - sorts the list in place, and returns the list
@ -734,7 +544,7 @@ class Matter_Device
dirty = true
end
if self.root_passcode == nil
self.root_passcode = self.generate_random_passcode()
self.root_passcode = self.commissioning.generate_random_passcode()
dirty = true
end
if dirty self.save_param() end
@ -814,239 +624,6 @@ class Matter_Device
ctx.status = matter.UNSUPPORTED_ENDPOINT
end
#############################################################
# mDNS Configuration
#############################################################
# Start mDNS and announce hostnames for Wifi and ETH from MAC
#
# When the announce is active, `hostname_wifi` and `hostname_eth`
# are defined
def start_mdns_announce_hostnames()
if tasmota.wifi()['up']
self._mdns_announce_hostname(false)
else
tasmota.add_rule("Wifi#Connected", def ()
self._mdns_announce_hostname(false)
tasmota.remove_rule("Wifi#Connected", "matter_mdns_host")
end, "matter_mdns_host")
end
if tasmota.eth()['up']
self._mdns_announce_hostname(true)
else
tasmota.add_rule("Eth#Connected", def ()
self._mdns_announce_hostname(true)
tasmota.remove_rule("Eth#Connected", "matter_mdns_host")
end, "matter_mdns_host")
end
end
#############################################################
# 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 _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 || !eth.contains('ip6local')
# log(format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_eth, eth.find('ip6local',''), eth.find('ip','')), 4)
mdns.add_hostname(self.hostname_eth, eth.find('ip6local',''), eth.find('ip',''), eth.find('ip6',''))
else
log(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 || !wifi.contains('ip6local')
# log(format("MTR: calling mdns.add_hostname(%s, %s, %s)", self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip','')), 4)
mdns.add_hostname(self.hostname_wifi, wifi.find('ip6local',''), wifi.find('ip',''), wifi.find('ip6',''))
else
log(format("MTR: calling mdns.add_hostname(%s, %s)", self.hostname_wifi, wifi.find('ip','')), 3)
mdns.add_hostname(self.hostname_wifi, wifi.find('ip',''))
end
end
log(format("MTR: start mDNS on %s host '%s.local'", is_eth ? "eth" : "wifi", is_eth ? self.hostname_eth : self.hostname_wifi), 3)
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
self.mdns_announce_op_discovery_all_fabrics()
end
#############################################################
# Announce MDNS for PASE commissioning
def mdns_announce_PASE()
import mdns
import crypto
var services = {
"VP": f"{self.VENDOR_ID}+{self.PRODUCT_ID}",
"D": self.commissioning_discriminator,
"CM":1, # requires passcode
"T":0, # no support for TCP
"SII":5000, "SAI":300
}
self.commissioning_instance_wifi = crypto.random(8).tohex() # 16 characters random hostname
self.commissioning_instance_eth = crypto.random(8).tohex() # 16 characters random hostname
try
if self.hostname_eth
# Add Matter `_matterc._udp` service
# log(format("MTR: calling mdns.add_service(%s, %s, %i, %s, %s, %s)", "_matterc", "_udp", 5540, str(services), self.commissioning_instance_eth, self.hostname_eth), 4)
mdns.add_service("_matterc", "_udp", 5540, services, self.commissioning_instance_eth, self.hostname_eth)
self.mdns_pase_eth = true
log(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.commissioning_discriminator & 0xFFF)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_S" + str((self.commissioning_discriminator & 0xF00) >> 8)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_V" + str(self.VENDOR_ID)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
subtype = "_CM1"
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth, subtype)
end
if self.hostname_wifi
# log(format("MTR: calling mdns.add_service(%s, %s, %i, %s, %s, %s)", "_matterc", "_udp", 5540, str(services), self.commissioning_instance_wifi, self.hostname_wifi), 4)
mdns.add_service("_matterc", "_udp", 5540, services, self.commissioning_instance_wifi, self.hostname_wifi)
self.mdns_pase_wifi = true
log(format("MTR: starting mDNS on %s '%s' ptr to `%s.local`", "wifi", self.commissioning_instance_wifi, self.hostname_wifi), 3)
# `mdns.add_subtype(service:string, proto:string, instance:string, hostname:string, subtype:string) -> nil`
var subtype = "_L" + str(self.commissioning_discriminator & 0xFFF)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_S" + str((self.commissioning_discriminator & 0xF00) >> 8)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_V" + str(self.VENDOR_ID)
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
subtype = "_CM1"
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi, subtype)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# MDNS remove any PASE announce
def mdns_remove_PASE()
import mdns
try
if self.mdns_pase_eth
log(format("MTR: calling mdns.remove_service(%s, %s, %s, %s)", "_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth), 3)
log(format("MTR: remove mDNS on %s '%s'", "eth", self.commissioning_instance_eth), 3)
self.mdns_pase_eth = false
mdns.remove_service("_matterc", "_udp", self.commissioning_instance_eth, self.hostname_eth)
end
if self.mdns_pase_wifi
log(format("MTR: calling mdns.remove_service(%s, %s, %s, %s)", "_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi), 3)
log(format("MTR: remove mDNS on %s '%s'", "wifi", self.commissioning_instance_wifi), 3)
self.mdns_pase_wifi = false
mdns.remove_service("_matterc", "_udp", self.commissioning_instance_wifi, self.hostname_wifi)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# Start UDP mDNS announcements for commissioning for all persisted sessions
def mdns_announce_op_discovery_all_fabrics()
for fabric: self.sessions.active_fabrics()
if fabric.get_device_id() && fabric.get_fabric_id()
self.mdns_announce_op_discovery(fabric)
end
end
end
#############################################################
# Start UDP mDNS announcements for commissioning
def mdns_announce_op_discovery(fabric)
import mdns
try
var device_id = fabric.get_device_id().copy().reverse()
var k_fabric = fabric.get_fabric_compressed()
var op_node = k_fabric.tohex() + "-" + device_id.tohex()
log("MTR: Operational Discovery node = " + op_node, 3)
# mdns
if (tasmota.eth().find("up"))
log(format("MTR: adding mDNS on %s '%s' ptr to `%s.local`", "eth", op_node, self.hostname_eth), 3)
mdns.add_service("_matter","_tcp", 5540, nil, op_node, self.hostname_eth)
var subtype = "_I" + k_fabric.tohex()
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matter", "_tcp", op_node, self.hostname_eth, subtype)
end
if (tasmota.wifi().find("up"))
log(format("MTR: adding mDNS on %s '%s' ptr to `%s.local`", "wifi", op_node, self.hostname_wifi), 3)
mdns.add_service("_matter","_tcp", 5540, nil, op_node, self.hostname_wifi)
var subtype = "_I" + k_fabric.tohex()
log("MTR: adding subtype: "+subtype, 3)
mdns.add_subtype("_matter", "_tcp", op_node, self.hostname_wifi, subtype)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# 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()
self.mdns_remove_op_discovery(fabric)
end
end
end
#############################################################
# Remove mDNS announce for fabric
def mdns_remove_op_discovery(fabric)
import mdns
try
var device_id = fabric.get_device_id().copy().reverse()
var k_fabric = fabric.get_fabric_compressed()
var op_node = k_fabric.tohex() + "-" + device_id.tohex()
# mdns
if (tasmota.eth().find("up"))
log(format("MTR: remove mDNS on %s '%s'", "eth", op_node), 3)
mdns.remove_service("_matter", "_tcp", op_node, self.hostname_eth)
end
if (tasmota.wifi().find("up"))
log(format("MTR: remove mDNS on %s '%s'", "wifi", op_node), 3)
mdns.remove_service("_matter", "_tcp", op_node, self.hostname_wifi)
end
except .. as e, m
log("MTR: Exception" + str(e) + "|" + str(m), 2)
end
end
#############################################################
# Try to clean MDNS entries before restart.
#
@ -1408,23 +985,6 @@ class Matter_Device
end
end
#####################################################################
# Generate random passcode
#####################################################################
static var PASSCODE_INVALID = [ 0, 11111111, 22222222, 33333333, 44444444, 55555555, 66666666, 77777777, 88888888, 99999999, 12345678, 87654321]
def generate_random_passcode()
import crypto
var passcode
while true
passcode = crypto.random(4).get(0, 4) & 0x7FFFFFF
if passcode > 0x5F5E0FE continue end # larger than allowed
for inv: self.PASSCODE_INVALID
if passcode == inv passcode = nil end
end
if passcode != nil return passcode end
end
end
#####################################################################
# Manager HTTP remotes
#####################################################################
@ -1523,7 +1083,7 @@ class Matter_Device
def MtrJoin(cmd_found, idx, payload, payload_json)
var payload_int = int(payload)
if payload_int
self.start_root_basic_commissioning()
self.commissioning.start_root_basic_commissioning()
else
self.stop_basic_commissioning()
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff