diff --git a/lib/libesp32/berry/tools/coc/coc b/lib/libesp32/berry/tools/coc/coc index ffc833433..6a1e6fad1 100755 --- a/lib/libesp32/berry/tools/coc/coc +++ b/lib/libesp32/berry/tools/coc/coc @@ -16,7 +16,7 @@ class builder: def __init__(self, input_folders, output_folder, macro_files): self.output = output_folder self.input = input_folders - self.config = macro_files + self.config = list(macro_files) self.macro = None self.strmap = {} self.strmap_weak = {} @@ -24,8 +24,9 @@ class builder: self.bytesmap = {} self.macro = macro_table() + self.config.append("../../../tasmota/tasmota_defines_for_berry.h") for path in self.config: - self.macro.scan_file(path) + self.macro.scan_file(path) for d in self.input: self.scandir(d) diff --git a/lib/libesp32/berry_matter/src/be_matter_module.c b/lib/libesp32/berry_matter/src/be_matter_module.c index e4dee163f..65917083e 100644 --- a/lib/libesp32/berry_matter/src/be_matter_module.c +++ b/lib/libesp32/berry_matter/src/be_matter_module.c @@ -273,6 +273,11 @@ extern const bclass be_class_Matter_TLV; // need to declare it upfront because #include "solidify/solidified_Matter_Plugin_9_Zigbee_Humidity.h" #include "solidify/solidified_Matter_Plugin_9_Zigbee_Occupancy.h" #include "solidify/solidified_Matter_Plugin_z_All.h" +#if USE_MI_EXT_GUI +#include "solidify/solidified_Matter_BTP.h" +#include "solidify/solidified_Matter_zz_Device_BLE.h" +// #warning ________________ +#endif //USE_MI_EXT_GUI #include "solidify/solidified_Matter_zz_Device.h" #include "be_fixed_matter.h" @@ -436,6 +441,12 @@ module matter (scope: global, strings: weak) { // Base38 for QR Code Base38, class(be_class_Matter_Base38) + // optional BTP for BLE + BTP, class(be_class_Matter_BTP), USE_MI_EXT_GUI + + // optional Matter BLE Device core class + Device_BLE, class(be_class_Matter_Device_BLE), USE_MI_EXT_GUI + // Matter Device core class Device, class(be_class_Matter_Device) diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_BTP.be b/lib/libesp32/berry_matter/src/embedded/Matter_BTP.be new file mode 100644 index 000000000..c9dcdec8a --- /dev/null +++ b/lib/libesp32/berry_matter/src/embedded/Matter_BTP.be @@ -0,0 +1,212 @@ +# +# Matter_BTP.be - implements the Matter Bluetooth Transport Protocol (BTP) +# +# Copyright (C) 2026 Stephan Hadinger, Christian Baars & 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 . +# + +import matter + +#@ solidify:Matter_BTP,weak +#if USE_MI_EXT_GUI + +################################################################################# +# Matter_BTP +# +# Pure protocol implementation of the Matter Bluetooth Transport Protocol (BTP). +# Transport-agnostic: only requires the owning `device` to expose `ble_send(payload)`. +################################################################################# +class Matter_BTP + + static MTU = 244 + static VERSION = 4 + static WINDOW_SIZE = 6 + # control flag masks + static F_HANDSHAKE = 0x40 + static F_MGMT = 0x20 + static F_ACK = 0x08 + static F_END = 0x04 + static F_CONT = 0x02 + static F_BEGIN = 0x01 + + var device + var flags # control flags byte + var management_opcode + var ack_number + var seq_tx + var seq_rx + var message_length + var segment_payload + var combined_payload + var send_queue + var can_send + + def init(device) + self.flags = 0 + self.send_queue = [] + self.device = device + self.seq_tx = 0 + self.can_send = true + end + + # Serialize the frame to bytes + def serialize() + var frame = bytes() + frame.add(self.flags) + if (self.flags & self.F_MGMT) && self.management_opcode != nil + frame.add(self.management_opcode) + end + if (self.flags & self.F_HANDSHAKE) + if self.segment_payload != nil frame += self.segment_payload end + return frame + end + if (self.flags & self.F_ACK) && self.ack_number != nil + frame.add(self.ack_number & 0xff) + end + frame.add(self.seq_tx & 0xff) + if (self.flags & self.F_BEGIN) && self.message_length != nil + frame.add(self.message_length, 2) + end + if self.segment_payload != nil + frame += self.segment_payload + end + return frame + end + + # Parse frame from bytes + def parse(data) + if size(data) < 1 return false end + + var offset = 0 + self.flags = data[offset] + offset += 1 + + # management message (handshake) + if (self.flags & self.F_MGMT) + self.management_opcode = data[offset] + self.segment_payload = data[2..] + return true + end + + if (self.flags & self.F_ACK) + self.ack_number = data[offset] + offset += 1 + self.can_send = true + end + + self.seq_rx = data[offset] + if self.seq_rx > 1 && self.can_send == false + self.seq_rx = nil + end + offset += 1 + + if size(data) < 4 + # pure ACK + self.message_length = 0 + self.combined_payload = nil + self.continue_queue() + return false + end + + if (self.flags & self.F_BEGIN) + self.message_length = data.get(offset, 2) + offset += 2 + self.combined_payload = bytes(1024) + end + + if offset < size(data) + self.combined_payload .. data[offset..] + end + + if !(self.flags & self.F_END) + return false + end + return true + end + + def continue_queue() + self.can_send = true + var p = self.get_packet() + if !p return end + self.can_send = false + tasmota.defer(/-> self.device.ble_send(p)) + self.segment_payload = nil + end + + def create_send_queue(payload) + var bytes_left = size(payload) + var pl_sz = self.MTU - 5 + var offset = 0 + var segment_type = 0 + if bytes_left > pl_sz segment_type = 1 end + while bytes_left > 0 + if bytes_left - pl_sz <= 0 && offset != 0 segment_type = 3 end + self.send_queue.push([payload[offset .. (offset + pl_sz - 1)], segment_type, bytes_left]) + segment_type = 2 + bytes_left -= pl_sz + offset += pl_sz + end + end + + def get_packet() + self.seq_tx += 1 + if size(self.send_queue) > 0 + var el = self.send_queue.pop(0) + self.segment_payload = el[0] + if el[1] == 0 # single segment + self.flags = self.F_BEGIN | self.F_END + self.message_length = size(el[0]) + elif el[1] == 1 # first segment + self.flags = self.F_BEGIN + self.message_length = el[2] + elif el[1] == 3 # last segment + self.flags = self.F_CONT | self.F_END + else # middle segment + self.flags = self.F_CONT + end + else + self.flags = 0 + self.segment_payload = nil + end + # add ack + if self.seq_rx != nil + self.ack_number = self.seq_rx + self.flags |= self.F_ACK + self.seq_rx = nil + end + return self.serialize() + end + + # Create handshake response frame + def create_handshake_response() + self.flags = 0x65 + self.management_opcode = 0x6C + var payload = bytes() + payload.add(self.VERSION & 0x0F) + payload.add(self.MTU, 2) + payload.add(self.WINDOW_SIZE) + self.segment_payload = payload + end + + def delete() + self.combined_payload = nil + self.message_length = 0 + end +end + +matter.BTP = Matter_BTP +#else +class Matter_BTP end # will be discarded +#endif // USE_MI_EXT_GUI \ No newline at end of file diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be index 4f5f059d8..2ac001610 100644 --- a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be +++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_0.be @@ -109,7 +109,7 @@ class Matter_Plugin # 0x0003: 0x00, # Identify: no optional features 0x0006: 0x01, # On/Off: Lighting feature (bit 0) 0x0008: 0x03, # Level Control: On/Off (bit 0) + Lighting (bit 1) - 0x0031: 0x04, # Network Commissioning: Ethernet (for WiFi/Ethernet devices) + 0x0031: 0x05, # Eth + WiFi - the latter is needed for Bluetooth commissioning 0x0046: 0x00, # ICD Management: 0x00 = no optional features (base SIT mode, no CIP/UAT/LITS) 0x0062: 0x01, # Scenes Management: SceneNames (bit 0) 0x0102: 1 + 4, # Window Covering: Lift (bit 0) + PA_LF (bit 2) diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Root.be b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Root.be index 0530bd85e..1966993d9 100644 --- a/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Root.be +++ b/lib/libesp32/berry_matter/src/embedded/Matter_Plugin_1_Root.be @@ -1268,6 +1268,32 @@ class Matter_Plugin_Root : Matter_Plugin return true # OK end +#if USE_MI_EXT_GUI + elif cluster == 0x0031 # ========== Network Commissioning Cluster 11.9. ========== + + if command == 0x0002 # ---------- AddOrUpdateWiFiNetwork ---------- + var ssid = (val.findsubval(0)).asstring() + var pass = (val.findsubval(1)).asstring() + tasmota.cmd(f"wifitest1 {ssid}+{pass}") + tasmota.delay(1000) + + var ncresp = TLV.Matter_TLV_struct() + ncresp.add_TLV(0, 0x04 #-TLV.U1-#, 0x00 #-matter.SUCCESS-#) # NetworkCommissioningStatusEnum + ncresp.add_TLV(1, 0x0D #-TLV.UTF2-#, tasmota.wifi().tostring()) # DebugText + ncresp.add_TLV(2, 0x04 #-TLV.U1-#, 1) # NetworkIndex + ctx.command = 0x05 # NetworkConfigResponse + return ncresp + + elif command == 0x0006 # ---------- ConnectNetwork ---------- + # Defer response - poll_deferred_connect_network() in Matter_BLE_Device handles it + import global + log("MTR: ConnectNetwork deferred, waiting for WiFi", 2) + global.matter_device.deferred_connect_network = ctx.msg + ctx.status = nil + return nil + end +#endif + else return super(self).invoke_request(session, val, ctx) end diff --git a/lib/libesp32/berry_matter/src/embedded/Matter_zz_Device_BLE.be b/lib/libesp32/berry_matter/src/embedded/Matter_zz_Device_BLE.be new file mode 100644 index 000000000..342505ddf --- /dev/null +++ b/lib/libesp32/berry_matter/src/embedded/Matter_zz_Device_BLE.be @@ -0,0 +1,443 @@ +# +# Matter_zz_Device_BLE.be - generic Matter-over-BLE commissionee +# +# Copyright (C) 2026 Stephan Hadinger, Christian Baars & 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 . +# + + +import matter + +#@ solidify:Matter_Device_BLE,weak +#if USE_MI_EXT_GUI +################################################################################# +# Matter_Device_BLE +# +# Generic Matter-over-BLE commissionee. Subclasses `matter.Device` and owns the +# BLE bring-up (GATT service FFF6, characteristics C1/C2, advertising), BTP +# wiring, msg multiplexing between BTP ("BLE" remote_ip marker) and UDP, the +# GATT callback dispatcher, deferred ConnectNetwork polling and commissioning +# finalization. +# +# Subclasses can override the following hooks for application-specific behavior: +# - on_ble_init() - called once at the end of init() +# - on_ble_ready() - called when the BLE stack signals ready (op 229) +# - on_ble_disconnected() - called on BLE disconnect (op 228) +# - on_commissioning_success() - called from check_final() on success +# - on_commissioning_failure() - called from check_final() on failure +# - heart_beat() - periodic feedback while commissioning is open +################################################################################# +class Matter_Device_BLE : Matter_Device + var current_func, next_func, btp, send_over_udp, check_if_commissioned + var ble_ready + var deferred_connect_network # holds msg frame for deferred ConnectNetwork response + var cbuf # shared BLE I/O buffer (bytes(-255)) + # commissioning progress / error tracking + var saw_ble_peer # set when GATT op 227 fires (commissioner connected) + var saw_pase_start # set when PASE has begun (admin_fabric assigned) + var last_wifi_result # last wifitest1 string from a deferred ConnectNetwork + + # failure reason constants - kept as plain strings for easy logging + static var REASON_SUCCESS = "success" + static var REASON_BLE_DROP = "ble_drop" + static var REASON_PASE_FAILED = "pase_failed" + static var REASON_WIFI_FAILED = "wifi_failed" + static var REASON_WIFI_TIMEOUT = "wifi_timeout" + static var REASON_WINDOW_TIMEOUT = "window_timeout" + static var REASON_ALREADY_COMMISSIONED = "already_commissioned" + static var REASON_UNKNOWN = "unknown" + + def init() + import cb + import global + import BLE + if global.matter_device + tasmota.remove_driver(global.matter_device) + global.matter_device = self + tasmota.gc() + end + # minimal setup to inspect persisted state before bringing BLE up + self.plugins = [] + self.plugins_persist = false + self.plugins_config_remotes = {} + self.next_ep = self.EP + self.ipv4only = false + self.commissioning = matter.Commissioning(self) + self.load_param() + self.sessions = matter.Session_Store(self) + self.sessions.load_fabrics() + + # if device is already commissioned, do NOT bring BLE up. + # Let the application decide what to do (delete fabrics, restart, go operational, ...). + var fabric_count = self.sessions.count_active_fabrics() + if fabric_count > 0 + var ctx = {'reason': self.REASON_ALREADY_COMMISSIONED, 'detail': '', 'fabric_count': fabric_count} + log(f"MTR: BLE commissioning skipped - device already commissioned (fabrics={fabric_count:i})", 2) + self.on_commissioning_failure(ctx) + return + end + + self.cbuf = bytes(-255) + matter.profiler = matter.Profiler() + var cbp = cb.gen_cb(/e,o,u,h->self.cb(e,o,u,h)) + BLE.conn_cb(cbp, self.cbuf) + self.current_func = /->self.init_C1() + BLE.set_svc("FFF6") + self.btp = matter.BTP(self) + self.tick = 0 + self.message_handler = matter.MessageHandler(self) + self.events = matter.EventHandler(self) + self.autoconf_device() + self.commissioning.init_basic_commissioning() + tasmota.add_fast_loop(/-> BLE.loop()) + tasmota.add_driver(self) + self.on_ble_init() + self.ble_ready = true + log(f"BLE: start MATTER commissionee, discriminator:{self.root_discriminator:i}", 1) + end + + # --------------------------------------------------------------------------- + # Subclass hooks - empty defaults + # --------------------------------------------------------------------------- + # ctx for success/failure hooks is a map: { 'reason': , 'detail': , 'fabric_count': } + def on_ble_init() end + def on_ble_ready() end + def on_ble_disconnected() end + def on_commissioning_success(ctx) end + def on_commissioning_failure(ctx) end + def heart_beat() end + + # --------------------------------------------------------------------------- + # Driver lifecycle + # --------------------------------------------------------------------------- + def start() + tasmota.when_network_up(def () + self._start_udp(self.UDP_PORT) + self.commissioning.start_mdns_announce_hostnames() + self.send_over_udp = true + end) + end + + def stop() + tasmota.remove_driver(self) + end + + # Override: prevent flush_socket during WiFi stabilization after BLE commissioning + def network_down() + end + + def every_second() + self.sessions.every_second() + self.message_handler.every_second() + self.events.every_second() + self.commissioning.every_second() + # latch PASE start as soon as commissioner kicks off SPAKE2+ + if !self.saw_pase_start && self.commissioning && self.commissioning.commissioning_admin_fabric != nil + self.saw_pase_start = true + end + if self.check_if_commissioned == true + self.check_final() + end + self.poll_deferred_connect_network() + end + + # Poll wifitest1 for deferred ConnectNetwork response + def poll_deferred_connect_network() + if self.deferred_connect_network == nil return end + var r = tasmota.cmd("wifitest1")["WiFiTest"] + if r == "Testing" return end + + var msg = self.deferred_connect_network + self.deferred_connect_network = nil + self.last_wifi_result = r + + import global + var TLV = matter.TLV + var im = self.message_handler.im + + var ctx = matter.Path() + ctx.endpoint = 0 + ctx.cluster = 0x0031 + ctx.command = 0x07 # ConnectNetworkResponse + + var cnresp = TLV.Matter_TLV_struct() + if r == "Successful" + cnresp.add_TLV(0, 0x04 #-TLV.U1-#, 0x00 #-SUCCESS-#) + cnresp.add_TLV(1, 0x0D #-TLV.UTF2-#, format("ip6=%s", tasmota.wifi()['ip6local'])) + log("MTR: ConnectNetwork deferred -> Successful", 2) + global.matter_device.start() + try + self.commissioning._mdns_announce_hostname(false) + except .. as e, m + log(f"MTR: mDNS hostname FAILED: {e} {m}", 2) + end + for fabric : self.sessions.fabrics + if fabric.get_device_id() && fabric.get_fabric_id() + try + self.commissioning.mdns_announce_op_discovery(fabric) + except .. as e, m + log(f"MTR: mDNS op_discovery FAILED: {e} {m}", 2) + end + end + end + else + cnresp.add_TLV(0, 0x04 #-TLV.U1-#, 0x01 #-FAILURE-#) + cnresp.add_TLV(1, 0x0D #-TLV.UTF2-#, format("wifi connect failed: %s", r)) + log(f"MTR: ConnectNetwork deferred -> Failed: {r}", 2) + end + cnresp.add_TLV(2, 0x02 #-TLV.I4-#, 0) # NetworkIndex + + var raw = bytes(32) + im.invokeresponse2raw(raw, ctx, cnresp) + + var ret = matter.InvokeResponseMessage() + ret.suppress_response = false + ret.invoke_responses = [raw] + + im.send_queue.push(matter.IM_InvokeResponse(msg, ret)) + im.send_enqueued(self.message_handler) + end + + def every_50ms() + self.current_func() + self.tick += 1 + self.message_handler.every_50ms() + end + + def wait() + end + + def then(func) + self.next_func = func + self.current_func = /->self.wait() + end + + # --------------------------------------------------------------------------- + # Message multiplexing: BLE/BTP vs UDP + # --------------------------------------------------------------------------- + def received_ack(msg) + self.heart_beat() + if msg.remote_ip != "BLE" && self.udp_server + self.udp_server.received_ack(msg) + end + end + + def msg_send(msg) + if msg.remote_ip == "BLE" + self.btp.create_send_queue(msg.raw) + self.heart_beat() + tasmota.delay(30) + if self.btp.can_send == false || self.ble_ready == false + return + end + var p = self.btp.get_packet() + self.btp.can_send = false + self.ble_send(p) + else + self.udp_server.send_UDP(msg) + end + end + + def parse() + var msg = self.btp + var is_synced = msg.parse(self.cbuf[1..self.cbuf[0]]) + if !(msg.flags & matter.BTP.F_HANDSHAKE) && msg.combined_payload + if (msg.flags & matter.BTP.F_END) + if size(msg.combined_payload) == 0 return end + if is_synced + log(f"BLE: <<< {self.cbuf[0]:i} bytes") + self.message_handler.msg_received(msg.combined_payload, "BLE", 0) + end + msg.delete() + end + end + end + + def handshake_ack() + self.btp.create_handshake_response() + self.ble_send(self.btp.serialize()) + end + + def check_final() + if self.commissioning && self.commissioning.is_commissioning_open() + return + end + var fabric_count = self.sessions.count_active_fabrics() + if fabric_count > 0 + log("MTR: Successfully finished BLE commissioning.", 2) + self.on_commissioning_success({'reason': self.REASON_SUCCESS, 'detail': '', 'fabric_count': fabric_count}) + else + log("MTR: commissioning failure", 2) + self.on_commissioning_failure({'reason': self.REASON_UNKNOWN, 'detail': '', 'fabric_count': 0}) + self.rearm() + end + self.check_if_commissioned = false + end + + # Handle a BLE disconnect event - decide whether it is a normal completion + # (PASE/CASE done, fabrics stored) or a premature drop that needs recovery. + # Called from cb() op 228. Single source of truth for disconnect logic. + def handle_disconnect() + # always notify subclass first + self.on_ble_disconnected() + + var fabric_count = self.sessions.count_active_fabrics() + var pase_open = self.commissioning && self.commissioning.is_commissioning_open() + + if fabric_count > 0 && !pase_open + # clean disconnect after commissioning succeeded - defer to check_final + # via the every_second() tick so success is reported in one place + log("BLE: disconnect after successful commissioning", 2) + self.check_if_commissioned = true + return + end + + # PREMATURE disconnect - PASE window may still be open so check_final() + # would early-return; classify and recover here directly. + log(f"BLE: PREMATURE disconnect (fabrics={fabric_count:i}, pase_open={pase_open}, saw_pase={self.saw_pase_start}, saw_peer={self.saw_ble_peer})", 2) + + var ctx = {'reason': self.REASON_UNKNOWN, 'detail': '', 'fabric_count': fabric_count} + if self.last_wifi_result != nil && self.last_wifi_result != "Successful" + ctx['reason'] = self.REASON_WIFI_FAILED + ctx['detail'] = self.last_wifi_result + elif self.deferred_connect_network != nil + ctx['reason'] = self.REASON_WIFI_TIMEOUT + elif self.saw_pase_start + ctx['reason'] = self.REASON_PASE_FAILED + elif self.saw_ble_peer + ctx['reason'] = self.REASON_BLE_DROP + else + ctx['reason'] = self.REASON_WINDOW_TIMEOUT + end + + log(f"MTR: commissioning failure reason={ctx['reason']} detail={ctx['detail']}", 2) + self.on_commissioning_failure(ctx) + self.check_if_commissioned = false + self.rearm() + end + + # Return to a clean state where a new commissioner can connect. + # Idempotent. Safe to call from a subclass hook. + def rearm() + log("MTR: rearming BLE commissioning", 2) + self.deferred_connect_network = nil + self.last_wifi_result = nil + self.saw_ble_peer = false + self.saw_pase_start = false + self.btp = matter.BTP(self) + try + self.commissioning.stop_basic_commissioning() + except .. as e, m + log(f"MTR: rearm stop_basic_commissioning err: {e} {m}", 3) + end + self.commissioning.init_basic_commissioning() + self.ble_ready = true + self.current_func = /->self.init_C1() + end + + def ble_send(payload) + import BLE + self.cbuf[0] = size(payload) + self.cbuf.setbytes(1, payload) + BLE.set_chr("18EE2EF5-263D-4559-959F-4F9C429F9D12") + BLE.run(211, true) + self.ble_ready = false + log(f"BLE: >>> {self.cbuf[0]:i} bytes") + self.then(/->self.wait()) + tasmota.defer(/->self.heart_beat()) + return true + end + + # --------------------------------------------------------------------------- + # GATT event dispatcher + # --------------------------------------------------------------------------- + def cb(error, op, uuid, handle) + if op == 201 + log(f"BLE: Handles created: {self.cbuf[1..self.cbuf[0]].tohex()}") + elif op == 222 + self.parse() + elif op == 224 || op == 225 + var kind = op == 224 ? "notification" : "indication" + log(f"BLE: Subscribed to {kind}") + self.next_func = /->self.handshake_ack() + elif op == 227 + log(f"BLE: peer MAC: {self.cbuf[1..self.cbuf[0]].tohex()}") + self.saw_ble_peer = true + elif op == 228 + log("BLE: Disconnected") + self.handle_disconnect() + elif op == 229 + self.ble_ready = true + log("BLE: stack ready") + self.on_ble_ready() + return + end + if error == 0 && op != 229 + self.current_func = self.next_func + end + end + + # --------------------------------------------------------------------------- + # BLE GATT service setup + # --------------------------------------------------------------------------- + def init_C1() + import BLE + BLE.set_chr("18EE2EF5-263D-4559-959F-4F9C429F9D11") + self.cbuf.setbytes(0, bytes("0100")) + BLE.run(211, true, 8) + self.then(/->self.init_C2()) + end + + def init_C2() + import BLE + BLE.set_chr("18EE2EF5-263D-4559-959F-4F9C429F9D12") + self.cbuf.setbytes(0, bytes("0100")) + BLE.run(211, true, 32) + self.then(/->self.add_ScanResp()) + end + + def add_ADV() + import BLE + var descriptor = bytes("05025000A000") # connectable mode = 2, itvl_min = 50 ms, itvl_max = 100 ms + self.cbuf.setbytes(0, descriptor) + BLE.run(232) + var payload = bytes("0201060B16F6FF00") + payload.add(self.root_discriminator, 2) + payload.add(self.VENDOR_ID, 2) + payload.add(self.PRODUCT_ID, 2) + payload.add(0x00) + self.cbuf[0] = size(payload) + self.cbuf.setbytes(1, payload) + BLE.run(201) + self.then(/->self.wait()) + log("BLE: advertising Matter accessory") + end + + def add_ScanResp() + import BLE + var local_name = "Tasmota Matter" + var payload = bytes("0201060008") + bytes().fromstring(local_name) + payload[3] = size(local_name) + 1 + self.cbuf[0] = size(payload) + self.cbuf.setbytes(1, payload) + BLE.run(202) + self.then(/->self.add_ADV()) + end +end + +matter.Device_BLE = Matter_Device_BLE +#else +class Matter_Device_BLE end # will be discarded +#endif //USE_MI_EXT_GUI \ No newline at end of file