Наврядли я ошибусь, утверждая то, что каждый искавший информацию по использованию связки Arduino+LoRa, встречал статьи и видеобзоры модулей от CDEBYTE (ранее - Ebyte).
Компактные модули, более популярные среди DIY-обзорщиков в исполнении UART, обмеряны, наверное, уже со всех сторон. Так что я не буду писать очередной пост на тему настройки параметров связи через 0xC2 или о способе легко и просто пульнуть millis() на 8 км. Вместо этого предлагаю слегка занырнуть в тёмные глубины разума разработчиков CDEBYTE и узнать, как и о чем чирпают их модули, а так же почирпать в унисон с ними.
Всё, мной разведанное и проверенное, относится к модулю E32-900T20D v8, а так же, полагаю, в той или иной мере, ко всей серии E32. Скорее всего и к остальным - E22/E220/тд, но таковых модулей у меня нет, поэтому возможность проверки гипотезы отсутствует.
Второй стороной радиообмена в моём эксперименте будет выступать плата на базе ESP32 - TTGO T3 v1.6.1 с модулем Lora32 (чип Semtech SX1276), подключенным по SPI.
Если кому-то интересен внутренний мир модуля, то он таков в рекламном буклете:
И в реальности (модель прошлых сезонов, E32-TTL-100):
Модуль E32 способен работать в прозрачном режиме, передавая полученные через UART данные другим таким же (или совместимым) модулям, из которых всё полученное выпадает прямо в UART микроконтроллеров. Проверка адреса получателя производится самим E32, поэтому сторонний LoRa-абонент, после несложных манипуляций, захватывает все передаваемые данные.
Настройки радиочасти чипа SX1276, находящегося внутри E32, напрямую недоступны и задаются опосредованно через параметр “Air datarate” - скорость радиобмена. Исследование выявило следующее соответствие:
- 300 Bps : BW = 500000, SF = 11, CR4 = 6 (*);
- 1200 Bps : BW = 500000, SF = 11, CR4 = 6 (*);
- 2400 Bps: BW = 500000, SF = 11, CR4 = 5 (*);
- 4800 Bps: BW = 250000, SF = 8, CR4 = 5;
- 9600 Bps: BW = 500000, SF = 8, CR4 = 5;
- 19200 Bps: BW = 500000, SF = 7, CR4 = 5.
(*) Примечательно, что при настройках, соответствующих 2400 Bps, TTGO T3 ловит пакеты, отправленные через E32 на 300 Bps и 1200 Bps, но вот второй E32 их принимает неустойчиво (хотя и принимает). К сожалению, я не обладаю инструментарием, который позволили бы прояснить данную особенность. Поэтому точные значения BW/SF/CR4 на данный момент под вопросом.
Передаваемый в радиоэфир пакет Lora на физическом уровне имеет тип Explicit Header. Размер преамбулы и sync word стандартные - 0x08 и 0x12 соответственно. CRC включён.
Инкапсулированный пакет данных CDEBYTE может быть как “простым”, так и “защищённым” с помощью технологии FEC - Forward Error Correction. Автоопределения типа пакета модули не осуществляют.
Структура “простого” пакета (FEC disabled):
| Header - 4 byte | User data - 1..58 bytes | Checksum - 1 byte |
Структура заголовка (header) “простого” пакета:
| Data length - 1 byte | Channel No - 1 byte | Target address high byte - 1 byte | Target address low byte - 1 byte |
- Data length - размер пользовательских данных. Формируется следующим образом: 0x40 + N, где N = 1…58;
- Channel No - номер канала. Используется для вычисления ключа шифрования, допускает значения: 0x00…0x45 (*);
- Target address - адрес получателя. Широковещательным адресом, согласно документации, является значения 0xFFFF и 0x0000.
При этом должен быть жестко задан канал 0x04(**); - User data - пользовательские данные. Зашифрованы однобайтным ключом методом XOR: data ^ key;
- Checksum - Проверочная сумма. Является дополнительным кодом восьмиразрядной контрольной суммы всех байт пакета.
(*) Примечание: установленный в настройках E32 номер канала дополнительно смещает рабочую частоту радиопередачи. Для модуля на 868MHz результирующая частота такова: 862MHz + Channel No.
(**) Бесстыдное враньё. Гадание по картинкам в даташите завело не в ту степь.
Функция вычисления контрольной суммы:
static uint8_t calcCDEByteChecksum(const uint8_t* _data, uint8_t _size) {
uint8_t checksum = 0x00;
while (_size--) { checksum += *_data++; }
checksum = 0x100 - checksum;
return checksum;
}
Функция вычисления ключа шифрования так же частично использует таблицу подстановок:
static uint8_t getCDEByteKey(const uint8_t _channelNo) {
const uint8_t keyLUT[] = {0x0A, 0x09, 0x08, 0x0F, 0x0E, 0x0D, 0x0C, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0B};
return ((_channelNo + 0x99) & 0xF0) | keyLUT[_channelNo & 0x0F]);
}
Пример захвата “простого” пакета:
[*] Received packet w/RSSI = -17
[*] Packet raw data: 42 06 00 00 9C AD 6F
[*] Decoded OK:
channelNo: 06
targetAddress 0000
size: 02
payload: 00 31
Структура “защищённого” пакета (FEC enabled):
| FEC-coded data - 12..126 bytes | CRC - 2 byte |
- FEC-coded data - “простой” пакет, закодированный с помощью таблицы (см. ниже) подстановок полубайтов, которая используется следующим образом: нечётный байт FEC-данных, через подстановку трансформируется в старший полубайт байта “простого” пакета, чётный байт после тех же операций становится младшим полубайтом. Например 0x65 0x59 => 0x04 0x02 => 0x42;
- CRC - 16-битная контрольная сумма, вычисленная по алгоритму CRC16/KERMIT, параметры: Poly 0x1021, Init 0x99C0, Final XOR 0x0000, Reverse input, Reverse output.
Таблица подстановок FEC:
static const uint8_t fecLUT[] = {0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA};
Пример захвата “защищённого” пакета:
[*] Received packet w/RSSI = -23
[*] Packet raw data: 65 59 55 69 55 56 55 5A 96 A5 99 A6 69 9A F5 D1
[*] Decoded OK:
channelNo: 06
targetAddress 0103
size: 02
payload: 00 31
Однако, всё это может не заработать без последнего ингридиента: флага LDO. Его следует выставить в true. Мне для этого пришлось внести модификацию в библиотеку arduino-LoRa by sandeepmistry, из issues которой я и получил стартовую информацию о перехвате траффика.
Итак, теперь, приложив полученные знания к чипу SX1278/SX1276 и знакомому микроконтроллеру, вы, как и я, сможете не только подслушать разговор модулей CDEBYTE, но и послать им горячий привет.
Продолжение следует. Надеюсь.