iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
Software Development

以MicroPython在ESP32上實作Insulin Delivery Service系列 第 6

Day 06 - 封裝 BLE 相關功能 (1)

  • 分享至 

  • xImage
  •  

目前 IDS 伺服器在藍牙的使用上分為三個部分:

  • 直接呼叫 MicroPython bluetooth 提供的功能。比如初始化 bluetooth.BLE、註冊服務、送出廣播、設定 characteristic 等。
  • 自訂輔助函數。如組織廣播資訊、將 bluetooth.UUID 轉換為適合人識別的字串等。
  • 實作 IDS,如 IDD Features。

目前這些全都寫在 main.py 裡,當程式規模還小時,不會有太大問題。但當往後需求愈來愈多,若全累積在一個檔案裡實在不好查閱,所以本喵將它們拆分開來管理:

  • ble/stack.py:負責直接與 MicroPython 的 bluetooth 模組高度相關的溝通。
  • ble/server.py:實作 IDS 伺服器。
  • ble/utils.py:輔助工具程式。

1. ble/stack.py

1-1. init

因 bluetooth.BLE 必須先啟用後才能呼叫它的其它方法,所以將此前置作業放在此函數內:

def init():
    ble = bluetooth.BLE()
    ble.irq(_ble_isr)
    ble.active(True)

這裡讓 _ble_isr() 負責處理 BLE 中斷事件,它會先行處理完中斷事件後,才將事件分發給上層各自的 ISR。

1-2. register_irq_handler

因 bluetooth.BLE.irq() 只能指定一個函數來處理 BLE 中斷,而在 init() 裡已經指定 _ble_isr() 為處理 BLE 中斷的 ISR,這樣若有人再以 bluetooth.BLE.irq() 指定新的 ISR,會導致 stack.py 模組無法接收到 BLE 中斷。為了解決這問題,本喵提供 register_irq_handler(),讓其它想監聽 BLE 中斷的模組可以註冊自己的 BLE ISR。當 _ble_isr() 處理完相關事件後,就會讓註冊的 ISR 依序執行:

_irq_handlers = []


def register_irq_handler(handler):
    _irq_handlers.append(handler)


def _ble_isr(event, data):
    ret = None

    # 處理訊息後,分發 BLE IRQ 給所有訂閱的函式
    for h in _irq_handlers:
        r = h(event, data)

        if r is not None:
            ret = r

    return ret

1-3. set_pairing_mode

將設定配對相關的安全層級放在一個函數內,並且強制使用命名參數:

def set_pairing_mode(*, bond: bool, mitm: bool, le_secure: bool):
    ble = bluetooth.BLE()
    ble.config(bond=bond)
    ble.config(mitm=mitm)
    ble.config(le_secure=le_secure)

這樣呼叫時就不可以如此使用:

set_pairing_mode(True, False, True)

而必須以參數名指定:

set_pairing_mode(bond=True, mitm=False, le_secure=True)

雖然略顯蠻橫,但能讓查閱程式的人更明確了解呼叫者的意圖。

1-4. Characteristic

此類別是為方便註冊服務時,將其格式轉為 bluetooth.BLE.gatts_register_services() 所要求的格式。因為 IDS 伺服器沒有使用除 CCCD(Client Characteristic Configuration Descriptor)以外的 Descriptor,所以沒有建立 Descriptor 類別。

class Characteristic:
    def __init__(
        self,
        uuid: int | str,
        *,
        read=False,
        read_enc=False,
        read_authen=False,
        read_author=False,
        write=False,
        write_enc=False,
        write_authen=False,
        write_author=False,
        write_no_rsp=False,
        notify=False,
        indicate=False,
    ):
        self.uuid = bluetooth.UUID(uuid)
        self.char_flags = _build_props(
            read=read,
            read_enc=read_enc,
            read_authen=read_authen,
            read_author=read_author,
            write=write,
            write_enc=write_enc,
            write_authen=write_authen,
            write_author=write_author,
            write_no_rsp=write_no_rsp,
            notify=notify,
            indicate=indicate,
        )
        self.value_handle = 0

    def _to_tuple(self) -> tuple[bluetooth.UUID, int]:
        return (self.uuid, self.char_flags)

1-5. Service

出於與 Characteristic 類別相同的理由而設。

class Service:
    def __init__(self, uuid: int | str):
        self.uuid = bluetooth.UUID(uuid)
        self.chars: list[Characteristic] = []

    def add_char(self, char: Characteristic):
        self.chars.append(char)

    def _to_tuple(
        self,
    ) -> tuple[bluetooth.UUID, tuple[tuple[bluetooth.UUID, int], ...]]:
        return (self.uuid, tuple(c._to_tuple() for c in self.chars))

1-6. Server

由 Server 類別負責註冊 BLE 服務,但實際建立 services 和相關 characteristics 的則是由子類別負責,因為只有子類別才知道自己需要什麼服務。當註冊完成,會將得到的 characteristic value handles 設定到相應的 Characteristic 物件。

class Server:
    def _build_services(self) -> tuple[Service, ...]:
        """由子類別負責建立所需的 Service 物件"""
        return tuple()

    def register_services(self):
        # 儲存所有的 Service 物件
        self.srvs = self._build_services()

        handles = _register_services(tuple(s._to_tuple() for s in self.srvs))

        for i, s in enumerate(self.srvs):
            char_handles = handles[i]

            for j, char in enumerate(s.chars):
                h = char_handles[j]
                char.value_handle = h

1-7. 潛藏的問題

最初的想法是希望儘量將 MicroPython 的 bluetooth 模組都封裝到 stack.py,讓其它模組能不直接相依於 MicroPython 的 bluetooth 模組,方便管理、觀看程式碼為由才拆分,因為本喵的腦容量很小,只能看少少小小的檔案。

但這樣的設計產生了不小的問題──浪費記憶體,因為有些物件只在註冊服務時用到,之後完全不會使用,這樣無形中就浪費了記憶體。如 Characteristic.uuid、Characteristic.char_flags 和 Service.uuid,甚至 Service.chars 此 list 在註冊後也未必是必要的存在。當然,也可以在註冊完服務後將其刪除,雖然這樣的做法讓本喵疑惑 Characteristic 等類別有必要存在嗎?沒有更好的做法嗎?所以若之後遇到記憶體不足的情況,需要重新考慮方法解決,甚至可能要放棄一些封裝原則來重新設計。

1-8. 其餘未解說部分

stack.py 裡還有一些前面未說明的部分,但因大部分都只是簡單的封裝,請直接參閱原始碼


未完待續
Σ( ° △ °)


上一篇
Day 05 - 製作外部設定檔
下一篇
Day 07 - 封裝 BLE 相關功能 (2) 之翻車現場
系列文
以MicroPython在ESP32上實作Insulin Delivery Service31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言