Embedded HandbookПриложения
Все разделы › X. Проектирование, сети и IoT
STM32F103, STM32F407 · уровень продвинутый · 11 мин чтения
Открыть в интерактивном справочнике

94. LoRaWAN: сеть, ключи, классы

Радио LoRa (раздел 93) доставляет байты от передатчика к приёмнику. LoRaWAN — сеть поверх этого радио. Она решает четыре задачи: - кто принимает кадры; - как сервер узнаёт прибор; - почему посторонний не прочитает и не подделает данные; - когда прибор может получить ответ.

Проверено опытом: кадр собран на переносимом C и отправлен настоящему сетевому серверу ChirpStack в контейнерах.

Как это называется в документации

Оригинал Перевод
gateway, packet forwarder шлюз, пересыльщик пакетов шлюза
network server, application server, join server сетевой сервер, сервер приложений, сервер подключения
DevEUI, JoinEUI, DevAddr заводской номер прибора, номер сервера подключения, сетевой адрес
OTAA, ABP подключение по воздуху, активация с вшитыми ключами
AppKey, NwkSKey, AppSKey корневой ключ, сетевой и прикладной ключи сессии
MIC подпись кадра
FCnt, FPort счётчик кадров, порт
MHDR, FHDR, FCtrl, FOpts заголовки кадра, управляющий байт, MAC-команды в заголовке
class A, B, C классы приборов по тому, когда они слушают
RX1, RX2 первое и второе окно приёма
data rate (DR), ADR скорость, её автоматический подбор
confirmed / unconfirmed uplink кадр вверх с подтверждением и без

94.1 Как устроена сеть

LoRaWAN — это «звезда звёзд»:

Звено Что делает
прибор передаёт в эфир и ни к чему не подключён; его кадр может услышать несколько шлюзов
шлюз принимает всё подряд на всех своих каналах и всех SF одновременно (раздел 93.2) и пересылает на сервер по IP, не разбирая и не расшифровывая, — в том числе чужие кадры
сетевой сервер убирает дубликаты от нескольких шлюзов, проверяет подпись и счётчик, подбирает прибору скорость, выбирает шлюз для ответа
сервер приложений расшифровывает данные и отдаёт их приложению — через MQTT или HTTP (разделы 87 и 88)

Шлюз ничего не знает о приборах, поэтому шлюзы можно ставить где угодно и добавлять по мере надобности. Вся логика сети — на сервере.


94.2 Кадр

Поле Байт Что внутри
MHDR 1 тип кадра: 0x40 — данные вверх без подтверждения
DevAddr 4 сетевой адрес прибора
FCtrl 1 флаги: ADR, подтверждение, длина FOpts
FCnt 2 16 младших бит счётчика кадров
FOpts 0–15 MAC-команды сети, если есть
FPort 1 0 — MAC-команды в данных, 1–223 — данные приложения
FRMPayload сколько нужно зашифрованные данные
MIC 4 подпись

Без FOpts служебных байт 13 (раздел 89). Кадр опыта с 7 байтами данных t=21.50 — 20 байт:

40 | EF CD AB 01 | 00 | 01 00 | 01 | 36 B6 EF 94 14 F5 C6 | 7A D3 2F 6B
MHDR  DevAddr     FCtrl  FCnt  FPort   зашифрованные данные      MIC

Адрес 01ABCDEF и счётчик записаны младшим байтом вперёд, а не старшим, как данные Modbus и числа в заголовках TCP/IP. Путаница в порядке байт — первая причина, по которой сервер молча не видит прибор.


94.3 Ключи и активация

В сессии LoRaWAN 1.0 два ключа, и разделены они не случайно:

Прибор получает ключи одним из двух способов:

Для серийных приборов берут OTAA, ABP оставляют для стенда и опытов. В LoRaWAN 1.1 сетевой ключ разделён на три, а в сессии 1.0, как на стенде, все три равны NwkSKey. Ключи в прошивке открытым массивом читаются из образа (разделы 43 и 77.9).


94.4 Шифрование и подпись

Данные шифруются в режиме счётчика. Для каждых 16 байт данных AES шифрует блок A_i с адресом, счётчиком кадра и номером блока, и данные складываются с результатом по xor. Подпись — первые 4 байта AES-CMAC от блока B0 и всего кадра:

/* Блоки A_i и B0: метка 0x01 или 0x49, четыре нуля, направление (0 — вверх),
 * адрес и счётчик — младшим байтом вперёд, ноль, номер блока или длина */
static void lw_block(uint8_t b[16], uint8_t tag, uint32_t dev_addr, uint32_t fcnt, uint8_t last)
{
    memset(b, 0, 16);
    b[0] = tag;
    for (int i = 0; i < 4; i++) {
        b[6 + i] = (uint8_t)(dev_addr >> (8 * i));
        b[10 + i] = (uint8_t)(fcnt >> (8 * i));
    }
    b[15] = last;
}

/* Неподтверждаемый кадр вверх: MHDR 0x40, DevAddr, FCtrl, FCnt (2 младших
 * байта), FPort, зашифрованные данные (до 64 байт) и MIC. Вернёт длину или 0. */
size_t lorawan_uplink(uint8_t *out, uint32_t dev_addr, uint32_t fcnt, uint8_t fport,
                      const uint8_t *data, size_t n, const uint8_t nwk_skey[16], const uint8_t app_skey[16])
{
    uint8_t a[16], s[16], b[16 + 9 + 64], mac[16];
    size_t k = 0;
    if (n > 64)
        return 0;
    out[k++] = 0x40;                            /* неподтверждаемые данные вверх */
    for (int i = 0; i < 4; i++)
        out[k++] = (uint8_t)(dev_addr >> (8 * i));
    out[k++] = 0x00;                            /* FCtrl: без ADR и без FOpts */
    out[k++] = (uint8_t)fcnt;
    out[k++] = (uint8_t)(fcnt >> 8);
    out[k++] = fport;
    for (size_t i = 0; i < n; i++) {            /* шифр — xor с AES(AppSKey, A_i) */
        if (i % 16 == 0) {
            lw_block(a, 0x01, dev_addr, fcnt, (uint8_t)(i / 16 + 1));
            aes128_encrypt(app_skey, a, s);
        }
        out[k++] = data[i] ^ s[i % 16];
    }
    lw_block(b, 0x49, dev_addr, fcnt, (uint8_t)k);  /* подпись — CMAC(NwkSKey, B0 | кадр) */
    memcpy(b + 16, out, k);
    aes128_cmac(nwk_skey, b, 16 + k, mac);
    memcpy(out + k, mac, 4);
    return k + 4;
}

aes128_encrypt и aes128_cmac в проверке — компактные реализации только для опыта. Они сверены с опубликованными проверочными значениями и с библиотекой на Python. В приборе берут библиотеку или аппаратный блок (раздел 43). На Cortex-M3 AES занимает 184 байта стека, сборка кадра — 208.

⚠️ Счётчик — часть шифра. Счётчик кадра входит в блок A_i, поэтому одни и те же данные с разными счётчиками шифруются по-разному. Повторите счётчик с теми же ключами — например, после перезагрузки прибора с ABP — и два кадра окажутся зашифрованы одной и той же гаммой. Сложив их по xor, посторонний получит xor двух открытых текстов без всякого ключа. Счётчик с одними ключами не должен повторяться никогда.


94.5 Опыт: настоящий сетевой сервер

Стенд. В контейнерах работают: - сетевой сервер и сервер приложений ChirpStack с базой PostgreSQL и Redis; - брокер Mosquitto; - мост шлюза.

Прибор заведён через программный интерфейс сервера (gRPC): ABP, LoRaWAN 1.0.3, EU868.

«Шлюз» — программа на C. Она заворачивает кадр так, как его пересылает пересыльщик пакетов настоящего шлюза: UDP-пакет с JSON, где указаны частота, SF, мощность, SNR и сам кадр в base64. Мост подтвердил все четыре пересылки:

Что отправлено Что сделал сервер
счётчик 1, t=21.50 принял, расшифровал и опубликовал в MQTT: счётчик 1, DR5 — SF7 на 125 кГц
тот же кадр ещё раз — перехвачен и повторён отбросил: «счётчик не вырос», событие в журнале прибора
счётчик 2, в подписи изменён один бит отбросил: «ни одна сессия с адресом 01abcdef не дала верной подписи»
счётчик 2, правильный кадр принял t=21.75: испорченный кадр счётчик не сдвинул

Байты обоих кадров независимо пересобраны на Python и совпали.

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


94.6 Классы A, B, C

Класс Когда прибор слушает Команда дойдёт Питание
A только два окна после своей передачи при следующей передаче прибора батарейка, годы
B ещё и в окна, назначенные по маякам шлюзов в назначенное окно батарейка
C всё время, кроме своих передач почти сразу от сети

Класс A обязаны поддерживать все приборы. Окна приёма в регионе стенда: RX1 — через 1 с после конца передачи, на частоте кадра, RX2 — через 2 с на 869,525 МГц на DR0 (SF12). Это значения региона по умолчанию: сеть может задать свои, часто SF9 для RX2. При OTAA прибор получает их в ответе на подключение, при ABP их вписывают в прибор вручную. Команда для прибора класса A ждёт на сервере в очереди, пока прибор сам не выйдет на связь.

Окно открывают по таймеру от момента «передача окончена» — это DIO0 из раздела 93, — а не циклом задержки (раздел 13). Если окно открыть поздно, ответы сервера не приходят никогда, а прибор выглядит «глухим».

Точность окна по описанию LoRaWAN — ±20 мкс, поэтому таймер окна берут от кварца: LSE или HSE. LSI у STM32 ошибается на десятки процентов (раздел 12), HSI — около 1 % (раздел 6), а 1 % от секунды — это 10 мс, больше всей преамбулы SF7: 8 символов по 1,024 мс — 8,2 мс. Стеки открывают окно чуть раньше и держат его дольше, чтобы покрыть уход часов, и платят за это током приёма.

Класс A: передача 61,7 мс, окна RX1 через 1 с и RX2 через 2 с после её конца

Ответ дорого стоит. Шлюз работает в полудуплексе: пока он передаёт ответ одному прибору, он не слышит никого. Кадры с подтверждением требуют ответа на каждый кадр. В сети из тысяч приборов их включают только для редких важных событий, а не для телеметрии.


94.7 ADR: скорость по SNR

Сетевой сервер помнит SNR последних кадров прибора и считает запас: лучший SNR минус порог для текущей скорости (раздел 93.2) минус запас на установку, обычно 10 дБ. Каждые 3 дБ запаса — ступень скорости вверх. Когда быстрее уже некуда, сервер снижает мощность.

DR 0 1 2 3 4 5
SF на 125 кГц 12 11 10 9 8 7
порог SNR, дБ −20 −17,5 −15 −12,5 −10 −7,5

Пример: прибор на SF12 с лучшим SNR +3 дБ имеет запас 3 − (−20) − 10 = 13 дБ. Это 4 ступени, и сервер переводит прибор на DR4 — SF8. Пакет становится короче в несколько раз, батарейка живёт дольше, а эфир освобождается для других. ADR хорош для неподвижных приборов. У движущихся SNR скачет, поэтому ADR у них выключают и скорость задают сами.

ADR: запас 13 дБ — четыре ступени по 3 дБ, прибор переходит с SF12 на SF8


94.8 Прибор на STM32: что хранить и что слать


94.9 Грабли

Сервер молча не видит прибор. Адрес или ключ введён не в том порядке байт. В журнале сервера написано, что ни одна сессия не дала верной подписи.

После перезагрузки прибор с ABP замолчал. Счётчик начался с нуля, а сервер ждёт больше прежнего: «счётчик не вырос».

Счётчик сбросили вместе с базой сервера, ключи оставили. Гамма повторяется, и данные можно восстановить без ключа.

Команды до прибора не доходят. Окно RX1 открыто позже, чем через 1 с: цикл задержки съехал из-за прерываний или таймер окна идёт от LSI.

После перезагрузки прибор с OTAA не подключается. DevNonce начат заново, а сервер помнит использованные номера.

Сеть захлёбывается. Все приборы шлют кадры с подтверждением, и шлюз больше передаёт, чем слушает.

Батарейка садится за месяцы вместо лет. Прибор шлёт JSON на SF12.

Ключи утекли из прошивки. Они лежали в образе открытым массивом.


94.10 Итоги

Дальше: раздел 94а «NFC-метка ST25DV» — как телефон и прошивка делят одну память: адреса на шине, пароль, почтовый ящик и прерывание по записи.

Открыть в интерактивном справочнике