2026-09-04
lite-line-simple-beacon 是我拿來模擬 LINE Simple Beacon
的一個小型 Python 專案。它的工作其實很單純:接收 Hardware ID(HWID)和
Device Message,組成 Bluetooth Low
Energy(BLE)廣播封包,再交給作業系統的藍牙介面送出去。
當初寫這個專案,是想在 Linux 以及 macOS 都能夠發送 LINE Simple Beacon 的訊號,但開始實作之後才發現 macOS 上面的限制還不少,所以最後 Linux 的部分是完成了,但 macOS 的部分只能算是半成品,因為作業系統本身的限制,沒辦法發送 LINE Simple Beacon 的訊號,但可以透過 USB 外接 ESP32 來實現。以下整理我重新檢查專案後做的重構,也說明 macOS 原生藍牙為什麼無法直接完成這件事,以及最後如何把實際廣播工作交給外接 ESP32。
lite-line-simple-beacon 的功能很簡單:建立符合 LINE
Simple Beacon 規格的資料,然後依照執行平台選擇適合的 transmitter。
專案目錄結構如下:
lite-line-simple-beacon/
├── broadcaster.py
├── core/
│ └── beacon_core.py
├── platforms/
│ ├── linux.py
│ ├── macos.py
│ └── esp32.py
├── requirements.txt
├── requirements-linux.txt
├── requirements-macos.txt
└── tests/
各部分的責任:
broadcaster.py:CLI
入口,接收參數、驗證資料、判斷平台並控制 transmitter 的生命週期。core/beacon_core.py:處理 HWID、Device Message
驗證,以及 LINE Simple Beacon frame 和完整 advertising data
的建立。platforms/linux.py:透過 BlueZ 的 command-line tools
控制 Linux BLE adapter。platforms/macos.py:清楚回報 Mac 內建 CoreBluetooth
無法送出 LINE 規格要求的 Service Data。platforms/esp32.py:在 macOS 透過 USB serial 控制執行
ESP-AT firmware 的 ESP32。requirements*.txt:區分共用、Linux 與 macOS 所需的
dependency。tests/:驗證封包、輸入、平台路由、Linux HCI command 與
ESP-AT command sequence。基本的執行方式如下:
python3 broadcaster.py \
--hwid 018741a0bd \
--message 012345程式收到參數後,會依序處理:
讀取 HWID 與 Device Message
↓
驗證長度與十六進位格式
↓
依平台與 --adapter 選擇 transmitter
↓
初始化 Bluetooth 或外接裝置
↓
建立完整 advertising data
↓
開始廣播
↓
持續執行,直到使用者按下 Ctrl+C
↓
停止廣播並清理裝置狀態
LINE Simple Beacon 使用 BLE legacy advertising data,單一 payload 的上限是 31 bytes。程式建立的內容可以拆成三個 Advertising Data(AD)structure:
Flags + Complete List of 16-bit Service UUIDs + Service Data
實際欄位如下:
02 01 06,表示 LE General Discoverable
Mode,而且不支援 BR/EDR。0xFE6F,在封包中依 little-endian 排列為
6F FE。0x16。0x02。0x7F。0x00。用結構表示會比較清楚:
02 01 06 # Flags
03 03 6F FE # 16-bit Service UUID 0xFE6F
17 16 6F FE # Service Data header + UUID
02 # LINE frame type
32 AF 51 9E 88 # HWID,5 bytes
7F # Measured Tx Power
00 32 00 69 00 FA FF FF
00 01 99 FF 88 # Device Message,13 bytes
上面是 31 bytes 的完整範例。Device Message 比較短時,程式會在 payload
尾端補 0x00,讓交給 Linux HCI 或 ESP32 的資料維持固定 31
bytes。
BeaconCore 對輸入有幾個明確限制:
0-9、a-f 或
A-F。我在輸入驗證的部分,會選擇直接擋下來超長資料,因為既然是廣播訊息,我還是比較希望訊息內容一開始就被控制好。
CLI 會先讀取作業系統以及是否有 ESP32 裝置,再決定由哪一個模組負責真正的廣播工作:
Linux -> LinuxTransmitter
macOS + no adapter -> MacOSTransmitter(unsupported)
macOS + ESP32 + port -> ESP32Transmitter
Linux transmitter 固定使用 hci0,執行前會確認
sudo、hciconfig 與 hcitool
都能使用。初始化時先啟用 adapter,再清除可能存在的 advertising
狀態:
sudo hciconfig hci0 up
sudo hciconfig hci0 noleadv
開始廣播時,BeaconCore 會先建立完整 31-byte
payload,接著交給 Bluetooth HCI 的 LE Set Advertising Data command:
sudo hcitool -i hci0 cmd 0x08 0x0008 1f <31 bytes>
sudo hciconfig hci0 leadv
0x08 是 Bluetooth LE controller command
group,0x0008 是 Set Advertising Data,1f
代表後面有 31 bytes。程式是用參數陣列呼叫這些命令。
使用者按下 Ctrl+C 後,transmitter 會停止 advertising 並
reset hci0。因此 Linux 執行這個工具時,需要 BlueZ
提供相關指令,也需要足以控制 Bluetooth adapter 的 sudo
權限。
在 macOS 沒有指定 --adapter esp32 時,CLI 會選擇
MacOSTransmitter。這個 transmitter 不會存取 serial
port,也不會嘗試呼叫 CoreBluetooth 廣播,而是直接回報:Mac 內建
CoreBluetooth 無法送出 LINE Simple Beacon 所需的自訂 Service Data。
這個 unsupported 路徑是刻意保留的平台行為,雖然我也在考慮是不是直接拿掉,這樣好像會比較乾淨。
另外在 macOS 上,如果要用 python 發送藍牙訊號,大概是這樣的呼叫關係:
Python
↓
PyObjC
↓
Apple CoreBluetooth
Python 負責程式邏輯,PyObjC 是呼叫 Objective-C framework 的 bridge,真正決定 Mac 能廣播哪些資料的仍然是 Apple 公開的 CoreBluetooth API。
根據 LINE Simple Beacon 官方規格,BLE advertising data 必須同時包含:
0xFE6F。0x16 的 Service Data。但 Apple
Core Bluetooth Programming Guide 對
CBPeripheralManager.startAdvertising()
的說明很明確:peripheral manager 的 advertisement data dictionary
只支援兩個 key:
CBAdvertisementDataLocalNameKey
CBAdvertisementDataServiceUUIDsKey
這裡沒有可以讓應用程式任意放入內容的
CBAdvertisementDataServiceDataKey。也就是說,Mac
可以宣告「我提供 0xFE6F 這個 Service
UUID」,卻不能透過這個公開 API 把 HWID、Measured Tx Power 與 Device
Message 組成的 LINE frame 一起放進 Service Data 廣播。
兩者的差別可以簡化成:
CoreBluetooth 可以送:
Service UUID = 0xFE6F
LINE Simple Beacon 需要:
Service UUID = 0xFE6F
+ Service Data = UUID + frame type + HWID + Tx Power + Device Message
所以 PyObjC 至此也無能為力,畢竟 PyObjC 只能存取 CoreBluetooth 已提供的介面,不能替系統 framework 增加 raw advertising payload 或也沒有自訂 Service Data 的能力。
這也是我在 MacOSTransmitter 直接回報 unsupported
的原因:只送出 Service UUID 並不是完整的 LINE Simple
Beacon。程式如果只確認 startAdvertising()
沒有報錯,很容易把「CoreBluetooth 接受了它支援的欄位」誤認成「LINE
需要的封包已經透過無線送出」。
既然 Mac 內建藍牙的公開 API 無法設定完整 payload,我採用的做法是把 BLE 廣播交給外接 ESP32。Mac 仍然負責參數驗證和封包建立,只是最後不走 CoreBluetooth:
Python on macOS
↓
USB serial,115200 baud
↓
ESP32 running ESP-AT
↓
BLE advertising data
這條路徑能避開 CoreBluetooth,是因為真正操作 BLE controller 的裝置已經變成 ESP32。Python 會把完整 31-byte payload 轉成十六進位字串,再透過 serial 交給 ESP-AT。
這個 backend 使用的是 ESP-AT 指令,不是任意 ESP32 firmware 都能接受。接上的開發板必須執行包含 Bluetooth LE AT commands 的 ESP-AT firmware;如果板子跑的是 Arduino sketch、MicroPython,或沒有編入 BLE AT commands 的 firmware,就不能直接使用這組流程。
初始化與廣播會使用:
AT
AT+BLEINIT?
AT+BLEINIT=2
AT+BLEADVDATA="<advertising data>"
AT+BLEADVSTART
AT+BLEADVSTOP
程式先用 AT 確認裝置有回應,再以
AT+BLEINIT? 取得 BLE role。未初始化時使用
AT+BLEINIT=2 切換成 server role;若裝置處於不相容的
role,則先清除再設定。
接著 AT+BLEADVDATA 寫入完整十六進位 payload,收到
OK 後才執行 AT+BLEADVSTART。使用者按下
Ctrl+C 時,程式會送出 AT+BLEADVSTOP
再結束。
Espressif
的 Bluetooth LE AT command 文件 說明 AT+BLEADVDATA 接受
HEX string,最大長度為 31 bytes,剛好可以承載這個專案建立的 BLE legacy
advertising data。
requirements-macos.txt 使用
pyserial==3.5。這個 dependency 只負責控制 USB serial
port,和 Mac 內建藍牙的 CoreBluetooth 能力無關。
python3 -m venv venv
source venv/bin/activate
python3 -m pip install -r requirements.txt -r requirements-macos.txt接上 ESP32 後,可以先列出 pyserial 偵測到的裝置:
python3 -m serial.tools.list_portsmacOS 上常見的名稱是 /dev/cu.usbserial-xxxx 或
/dev/cu.usbmodemxxxx,但不能直接照抄範例,必須以自己的 Mac
實際列出的 port 為準。
完整指令如下:
python3 broadcaster.py \
--adapter esp32 \
--port /dev/cu.usbserial-xxxx \
--hwid 018741a0bd \
--message 012345--adapter esp32 會讓 macOS 選擇
ESP32Transmitter;--port 是必要參數,而且 CLI
會在開啟硬體前檢查它是否存在。Serial port 無法開啟、AT command 在 3
秒內沒有完成,或 ESP32 回覆 ERROR
時,程式都會回報失敗,不會顯示已經開始廣播。
專案的測試可以從根目錄執行:
python3 -m unittest discover -s tests -v目前共有 19 個測試,涵蓋:
AT+BLEADVDATA、開始與停止指令。ERROR
是否正確回報。ESP32 測試使用 mock serial device,所以它能證明 Python 送出的設定、payload 與 command sequence 符合程式設計,也能確認錯誤時不會宣稱廣播成功。
不過完整的驗證還是建議使用實體 ESP32,再由第二台 BLE scanning device
或 packet sniffer 擷取封包,確認 0xFE6F Service
Data、HWID、Measured Tx Power 與 Device Message 都和程式產生的 bytes
一致。
lite-line-simple-beacon 的邏輯就是先可靠地建立符合 LINE
規格的 31-byte advertising data,再找到能完整控制這些 bytes 的 BLE
controller。
hci0