Продолжаем тему беспроводной передачи данных с использованием manchester.h. В это статье описан процесс передачи массива с контрольными суммами для контроля целостности данных, а так же решение связанных с этим проблем.

В конце прошлой статьи мы озадачились тем, чтобы научиться передавать массивы данных вместе с контрольными суммами при помощи библиотеки Manchester.h. Но для начала давайте вспомним, для чего могут понадобиться контрольные функции (хеши)?
При передачи информации по беспроводному каналу связи (любому) могут произойти изменения в передаваемом сообщении. Это может произойти как по причине зашумленности канала, так и по причине коллизий, образованных вашей же передачей данных. Для того, чтобы приёмник мог отфильтровать правильные данные от неправильных необходимо данные передавать вместе с хешами, которые позволят удостовериться в том, что данные не были повреждены или изменены в процессе передачи.
Проверка валидности полученных данных очень актуальна на частоте 433.920МГц, что используют наша пара приёмник-передатчик, ввиду того, что эта частота является крайне зашумлённой — на ней могут работать любительские радиостанции, автомобильные сигнализации, бытовая техника, шлагбаумы и многое другое.
При получении данных мы сравниваем значение хеш функции полученного файла с тем хешиком, что мы получили вместе с данными. Если хеши не совпадут — данные отфильтровываются, а по — хорошему и весь массив. Таким образом, это гарантирует нам практически 100% правильность передачи данных! И так, перейдём к сути задачи.
Два стула
С этой самой точки у нас появляются 2 стула! На одном Manchester.h, а на втором умный механизм формирования и передачи хешей. Как вы уже наверное догадались — мы сядем на первый стул :D
Дело всё в том, что в библиотеке Manchester для Arduino нет встроенных функций получения хеш значения всего массива, хотя есть функции для передачи массива и для формирования и передачи хеша со значением отдельного элемента массива. Мы, конечно, можем придумать (или позаимствовать) готовые алгоритмы вычисления хешей всего массива, но.. Мы будем использовать что есть (точно не мозг!).
Наша задача — сформировать массив, в котором каждому значению будет соответствовать его собственный уникальный хеш, и передать его. Ну и затем каждый элемент последовательно проверить. Немножко дичь, не так ли?
Необходимые функции шифрования
Как правило, ключом для вычисления хеш функции при использовании Manchester.h назначают уникальный идентификатор отправителя, который отправляется вместе с хешем в передаваемом пакете. Мы так и сделаем! Настоятельно рекомендую избегать идентификаторов 0 и 1, так как в ряде случаев они чаще изменялись при передаче.
В прошлой статье вы уже познакомились с функцией передачи массива данных и получения их. Сейчас же мы рассмотрим функцию, которая фактически зашифрует наше сообщение уникальным ключом отправителя:
#define SENDER_ID 12 //идентификатор устройства
uint8_t data = 15; //данные для передачи
uint16_t mes = man.encodeMessage(SENDER_ID, data); //зашифрованное сообщение
И вот тут нас ждёт разочарование. Дело всё в том, что Manchester может передавать массивы только с типом данных uint8_t (от 0 до 255), а функция encodeMessage() и вовсе возвращает значение в uint16_t (от 0 до 65535)! Более того, второй аргумент функции encodeMessage(), который отвечает за шифруемые данные, тоже должен иметь тип uint8_t, что ещё больше может затруднить задачу.
Для контроля целостности данных (контроль и исправление возникающих ошибок) в библиотеке manchester.h используется код Хэмминга.
Полный формат сообщения для передачи, включая хэш и id:
[0][1][2][3][4][5][6][7][8][9][a][b][c][d][e][f]
[ ID ][checksum][ data ]
Задача: передача данных
Данные, которые мне необходимо было передать, имели тип uint16_t. Для того, чтобы разбить uint16_t на несколько uint8_t существует масса годных решений с официального форума Arduino, но мне они никак жизнь не упростили, потому разбивать данные будем по-тупому — делением. Причём делать нам это при передаче придётся дважды — сначала разбить наши данные в массив для шифрования, затем результат шифрования снова разбить на массив, который мы уже и будем передавать.
Шаг 1
Разбиваем данные data с типом uint16_t на 3 элемента массива uint8_t.
dataOriginal[0] = data / 10000;
dataOriginal[1] = (data % 10000) / 100;
dataOriginal[2] = (data % 10000) % 100;
Шаг 2
В цикле шифруем каждый элемент массива с помощью ключа, которым в данном примере будет всё тот-же идентификатор устройства.
uint16_t mes = man.encodeMessage(SENDER_ID, dataOriginal[i]);
Шаг 3
В том же цикле разбить каждое зашифрованное сообщение с типом uint16_t на 3 элемента нового массива uint8_t (как в первом шаге). Таким образом, передаваемый массив будет в 3 раза больше исходного.
В нулевой элемент массива необходимо записать размер массива, чтобы приёмник знал сколько элементов ему необходимо обработать.
Шаг 4
Передать зашифрованный массив.
man.transmitArray(ARRAY_SIZE, dataEncoded);
Итого у нас получается примерно следующая картина подготовки данных для передачи массива с контрольными суммами с помощью manchester.h:
Задача: Приём и расшифровка данных
Получаем зашифрованные данные с типом uint8_t и записываем их в буфер с помощью следующих функций, повторяющихся в основной функции loop():
man.beginReceiveArray(BUFFER_SIZE, buffer);
Функции подключения и начала получения массива до функции loop() так же, как и в предыдущей статье описываем в setup(). Далее пройдёмся по шагам расшифровки полученных данных:
Шаг 1
В функции loop() создаём if-блок на окончание передачи сообщения. В нём у нас и будут происходить все манипуляции с полученным массивом.
if (man.receiveComplete()) {
/* Расшифровываем полученный массив*/
man.beginReceiveArray(BUFFER_SIZE, buffer);
}
После расшифровки начинаем сразу считывать новый массив (в нашей реализации).
Шаг 2
В цикле от 1 до размера массива проходимся по элементам, чтобы собрать по кусочкам каждое зашифрованное сообщение. Напомню, что сообщение состоит из 3х кусочков (для удобства вынесу их в отдельные переменные).
uint8_t mp1, mp2, mp3;
mp1=buffer[i + 0 + jj];
mp2=buffer[i + 1 + jj];
mp3=buffer[i + 2 + jj];
jj в данном случае увеличивается на два при каждой итерации.
Шаг 3
Собираем из полученных кусочков сообщение.
uint16_t mes;
unsigned short int mes_i = mp2 * 100+mp3;
mes = (uint16_t) mes_i;
mes = (uint16_t) mp1 * 10000 + mes;
Очень важно следить за правильностью сборки сообщения, особенно за типами данных.
Шаг 4
Создаём блок расшифровки сообщения, параметром к которому будет собранное выше сообщение. В id функция запишет идентификатор устройства-отправителя, а в data расшифрованное сообщение (с типом uint8_t).
if (man.decodeMessage(mes, id, data)) {
if(id < 1) break; //id начинается с 1!
if(i==1) dataDec[0]=id;
dataDec[i]=data;
}
На этом шаге прошу быть наиболее внимательными!
Сначала мы проверяем id на то, чтобы он был больше 0 — простая проверка на полноту данных. Как вы помните, мы договорились не использовать id равный 0. Простая проверка id на данном этапе избавила меня от многих случаев неправильного распознавания идентификатора.
Следующий момент — фиксируем идентификатор единожды при расшифровке первого сообщения. Это может быть очень полезно, если после расшифровки вам необходимо знать вне функции идентификатор отправителя. Опытным путём было выявлено, что при расшифровке последних 2-3 сообщений id мог изменяться на невалидные значения, особенно при высокой зашумлённости канала.
Вроде всё так просто, но на последнем шаге я много времени убил, чтобы понять закономерность изменения id, который возвращает функция decodeMessage().
Шаг 5
Как вы помните, мы расшифровали лишь кусочки полного полного сообщения, которые необходимо снова собрать воедино, но на этот раз уже из расшифрованного массива. Сделаем это так-же как и в шагах 2 и 3.
Готово! Мы наконец-то получили то что хотели, а именно:
- Мы передали массив с данными типа uint16_t с помощью библиотеки manchester.h
- Массив передавался с контрольными суммами для контроля целостности данных
Разумеется, что реализация далека от идеала. Потому прошу предложения об усовершенствовании писать по возможности в комментарии!
Надеюсь, что моя информация сможет кому-то помочь, тк пока информации в сети крайне мало и у меня ушло достаточно времени на то, чтобы всё работало так, как я хочу. Особенно много времени ушло на поиски причин изменения id в процессе расшифровки массива!
Friday, 11 November 2016