Перейти к содержанию
Материалы раздела

BLE

Модуль принимает и передаёт широковещательные пакеты Bluetooth Low Energy (advertising). Сканер распознаёт Eddystone UID, URL и TLM, iBeacon и BTHome v2. Пакеты неизвестного формата передаются в Lua без изменений, поэтому данные конкретного устройства можно разобрать в программе.

Сканер не устанавливает BLE-соединение и не выполняет сопряжение. При активном сканировании устройство запрашивает дополнительные данные и принимает scanResponse. Такой режим создаёт большую нагрузку на радиоканал и расходует больше энергии, чем пассивное сканирование.

bt.attach(mode)

Включает Bluetooth. Для сканирования достаточно режима bt.mode.BLE.

bt.attach(bt.mode.BLE)

Модуль bt доступен только в версиях прошивки с поддержкой Bluetooth.

bt.scan.start(callback [, options])

Запускает непрерывное сканирование. Старый вызов bt.scan.start(callback) сохранён; без options используется активное сканирование с интервалом и окном 50 мс.

bt.scan.start(function(frame)
    print(frame.address, frame.rssi, frame.raw)
end, {
    active = true,
    intervalMs = 50,
    windowMs = 50,
    duplicateMode = "logical",
    duplicateWindowMs = 1500,

    filters = {
        {address = "54:48:E6:8F:80:A5"},
        {adType = 0x16, prefix = "D2FC"},
        {adType = 0xFF, prefix = "3412"},
    },

    bthomeKeys = {
        ["54:48:E6:8F:80:A5"] = "231d39c1d7cc1ab1aee224cd096db932",
    },
})

Параметры:

  • activetrue для активного сканирования, false для пассивного;
  • intervalMs — период сканирования от 2,5 до 10 240 мс;
  • windowMs — длительность окна в том же диапазоне, не больше intervalMs;
  • duplicateMode"logical" или "all";
  • duplicateWindowMs — время подавления одинаковых пакетов неизвестного формата, целое число от 1 до 600 000 мс;
  • filters — массив правил приёма;
  • bthomeKeys — таблица ключей BTHome по MAC-адресам.

Значения времени округляются с шагом 0,625 мс. Стандартная прошивка принимает до 16 фильтров и до 16 ключей BTHome за один запуск. Если таблица длиннее, start возвращает ошибку.

В bthomeKeys ключом таблицы служит MAC-адрес, а значением — 128-битный ключ из 32 шестнадцатеричных символов. Один адрес нельзя указывать дважды.

Повторный start до завершения stop возвращает ошибку.

Фильтры

Пакет достаточно сопоставить с одним правилом из filters. Внутри правила должны совпасть все заданные поля. MAC-адрес можно записать с двоеточиями или без них, регистр не важен. adType — тип AD-элемента от 0 до 255. prefix — шестнадцатеричный префикс данных элемента, то есть байтов после adType; prefix требует adType.

Например, правило {adType = 0x16, prefix = "D2FC"} принимает Service Data BTHome с UUID 0xFCD2, записанным байтами D2 FC. Правило {adType = 0xFF, prefix = "3412"} принимает данные производителя, которые начинаются с байтов 34 12.

Без filters сканер передаёт все широковещательные пакеты. Повреждённая последовательность AD-элементов остаётся доступной в raw или scanResponse, но не разбирается как BTHome, Eddystone или iBeacon.

Подавление повторов

В режиме "logical" сканер объединяет повторные передачи одного события:

  • для BTHome-пакетов с packetId сканер учитывает адрес и последовательность номеров; повторные и устаревшие номера подавляются в течение 4 секунд, переход 255 -> 0 обрабатывается корректно;
  • повтор одного зашифрованного BTHome-пакета с тем же счётчиком в течение 4 секунд не вызывает второй callback;
  • явно отфильтрованный пакет неизвестного формата проверяется по адресу, типу и содержимому основного пакета и scanResponse в течение duplicateWindowMs.

Два последовательных действия с новыми BTHome packetId передаются отдельно. Неизвестные пакеты, для которых не задан фильтр, передаются без подавления повторов. Режим "all" передаёт каждый допустимый пакет; защита зашифрованного BTHome от повторного воспроизведения и подмены открытым пакетом остаётся включённой.

Формат callback

Каждый callback получает таблицу с общими полями:

{
    type = bt.frameType.Unknown,
    address = "54:48:E6:8F:80:A5",
    addressType = 0,
    advType = 3,
    rssi = -61,
    raw = "020106...",
    len = 18,
    scanResponse = "",
    scanResponseLen = 0,
    ad = {
        {source = "adv", type = 0x01, data = "06"},
        {source = "adv", type = 0x16, data = "D2FC..."},
    },
}

raw содержит основной широковещательный пакет, scanResponse — отдельный ответ при активном сканировании. source равен "adv" или "scanResponse". MAC-адрес всегда возвращается прописными буквами с двоеточиями.

Номера bt.frameType:

  • Unknown = 0;
  • EddystoneUID = 1;
  • EddystoneURL = 2;
  • EddystoneTLM = 3;
  • IBeacon = 4;
  • BTHome = 5.

Поля существующих Eddystone/iBeacon-кадров сохранены: namespace, instance, url, version, battery, temperature, advCount, timeSinceBoot, uuid, major, minor, txPower и distance в зависимости от типа.

BTHome v2

Для Service Data UUID 0xFCD2 callback дополнительно содержит bthome:

{
    accepted = true,
    status = "ok",
    version = 2,
    encrypted = false,
    authenticated = false,
    triggerBased = true,
    packetId = 9,
    partial = false,
    objects = {
        {
            id = 0x3A,
            name = "button",
            index = 1,
            event = "press",
            value = 1,
            valueText = "1",
            raw = "01",
        },
    },
}

Поддерживаются все актуальные объекты официального BTHome v2: числовые и бинарные значения, text, raw, тип устройства, версия прошивки, а также события button (0x3A), command (0x3B) и dimmer (0x3C). Полная таблица идентификаторов, размеров, единиц и коэффициентов приведена в спецификации BTHome.

Повторяющиеся объекты сохраняют исходный порядок. index нумерует объекты с одинаковым ID начиная с единицы. У числового объекта:

  • value — удобное Lua-число;
  • valueText — точная десятичная запись без потери 24- и 32-битного значения;
  • raw — точные байты объекта без ID в том порядке, в котором они пришли;
  • unit — единица измерения, если она определена спецификацией.

У text значение возвращается строкой UTF-8, у raw — шестнадцатеричной строкой. У command есть event и шестнадцатеричное поле args; у dimmerevent и steps. Известный усечённый объект отклоняет весь пакет. Неизвестный ID останавливает разбор, сохраняет предыдущие объекты и выставляет partial = true.

Возможные status:

  • ok — пакет принят;
  • missing_key — зашифрованный пакет получен без ключа;
  • auth_failed — AES-CCM MIC не подтверждён;
  • replay — счётчик уменьшился, повторился с другими данными или тот же пакет повторно пришёл позже разрешённых 4 секунд;
  • downgrade — для адреса с ключом получен незашифрованный пакет;
  • malformed — нарушены длина, зарезервированные биты или структура объекта;
  • unsupported_version — Service Data использует не BTHome v2.

Только при accepted = true таблица содержит разобранные objects. Для зашифрованного BTHome используется AES-CCM с 128-битным ключом по официальной схеме BTHome. Ключи нужно передавать при каждом запуске сканирования: модуль не сохраняет их после stop или ошибки запуска. История счётчиков также действует только до stop. После сброса счётчика передатчика нужно остановить и заново запустить сканирование.

Shelly BLU Button/Remote и другие BTHome-пульты передают кнопки и колесо в широковещательных пакетах; соединение с ними не требуется. Настройка самого пульта через BLE-соединение в этот API не входит.

bt.scan.stats()

Возвращает накопительные счётчики текущего запуска сканирования:

{
    received = 0,
    delivered = 0,
    filtered = 0,
    duplicates = 0,
    malformed = 0,
    authFailed = 0,
    replayBlocked = 0,
    downgradeBlocked = 0,
    queueDropped = 0,
}

Счётчики сбрасываются при новом start. queueDropped показывает, сколько пакетов сканер не успел обработать из-за перегрузки.

bt.scan.stop()

Останавливает сканирование. После успешного возврата callback больше не вызывается, а ключи и история текущего запуска очищаются. stop можно вызвать из callback: текущий вызов завершится, но следующий уже не начнётся.

Передача маяков

bt.service.beacon.eddystone_uid(mac, tx_power, namespace, instance)

Создаёт Eddystone UID beacon.

bt.service.beacon.eddystone_url(mac, tx_power, url)

Создаёт Eddystone URL beacon.

bt.service.beacon.eddystone_tlm(mac, tx_power, mv, deg)

Создаёт Eddystone TLM beacon. mv — напряжение в милливольтах, deg — температура.

bt.service.beacon.eddystone_tlm_update(beacon, mv, deg)

Обновляет напряжение и температуру TLM beacon.

bt.service.beacon.ibeacon(mac, tx_power, uuid, major, minor)

Создаёт iBeacon.

Методы Beacon:start() и Beacon:stop() запускают и останавливают передачу.

local mac = cpu.getmac(2, 1)
local tx_power = -59

local b1 = bt.service.beacon.eddystone_uid(
    mac, tx_power, "3F3B51B60B88EF9949E5", "CF484185AE0B"
)
local b2 = bt.service.beacon.eddystone_url(mac, tx_power, "https://open-dev.ru")
local b3 = bt.service.beacon.eddystone_tlm(mac, tx_power, 3220, 26)
local b4 = bt.service.beacon.ibeacon(
    mac, tx_power, "00112233445566778899AABBCCDDEEFF", 100, 1
)

b1:start()
b2:start()
b3:start()
b4:start()