А не запилить ли нам хардварный чат?

в 9:00, , рубрики: diy или сделай сам, esp32, IoT, ruvds_статьи, Блог компании RUVDS.com, Интернет вещей, Разработка для интернета вещей, Разработка под Arduino, хардварный чат, чат

А не запилить ли нам хардварный чат? - 1

Картинка rawpixel.com, Freepik

— Пациент, мучают ли вас навязчивые странные идеи?
— Почему мучают, доктор, я ими наслаждаюсь!

Ввиду того, что я достаточно часто работаю с протоколом mqtt, мне периодически приходят в голову своеобразные мысли, одна из которых меня и посетила недавно: а что если сделать свой чат на этом протоколе?

Именно этим мы и займёмся в нашей статье.

Вообще говоря, подобная организация чата через mqtt-брокера является достаточно любопытной, так как позволяет разрозненным людям общаться между собой, используя для этого общедоступные сервера в интернете, называемые mqtt-брокерами.

Изначально подобного типа сервера были предназначены для общения машин между собой и построения машинных сетей, частным случаем которых являются и широко распространившиеся в последнее время сетевые устройства, в рамках концепции «умного дома», и Интернета вещей (IoT).

Мы же, попробуем использовать подобные сети для общения людей между собой, то есть, несколько необычным способом, для чего подобные сети изначально не задумывались.

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

В целом, изучение подобного вопроса мы произведём исключительно «искусства ради», с точки зрения, исследования вопроса «если машины общаются между собой, почему бы людям не общаться также?»

▍ Что такое Mqtt-брокер

Я сейчас достаточно подробно не буду распространяться на всех тонкостях работы брокеров, так как, наверняка многие об этом хорошо знают, просто в двух словах скажу для тех, кто с этим ещё не сталкивался. Ниже вы можете увидеть схему, что представляет собой брокер и схема общения с ним:

image
Картинка r-iot.org

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

Также программное обеспечение позволяет подключившемся клиенту создавать произвольное количество, с произвольными названиями, так называемых «топиков», которые для лучшего понимания, можно представить себе как отдельные telegram-каналы, в которых можно общаться.

Так же как и в telegram-каналах, можно оставлять сообщения в определённых каналах и подписаться на обновления в ряде каналов, чтобы видеть, что в них происходит (какие сообщения появляются).

Это, так сказать, преамбула, а сейчас будет «амбула» :-)

▍ Пишем свой Mqtt-чат

Так как серверную часть вопросов полностью закрывает своим функционалом mqtt-брокер, мы поговорим о клиентской части.

Самым простым способом организации ввода и отсылки сообщений является отправка их в стандартный монитор порта в среде разработки Arduino IDE (хотя если сильно надо, можно попробовать написать и свою графическую часть, то бишь GUI, но, мне кажется, это будет лишним).

Для последующей работы мы будем использовать побайтное считывание из монитора порта, используя функцию Serial.read(). И это, кстати говоря, даст нам возможность быстро понять, какой символ перед нами: латинский/знак или кириллический (правда, нам это не нужно, ибо есть способ проще. Это просто для сведения). А теперь ниже немного деталей.

Ниже небольшая «познавательная страничка». Кто в курсе — может закрыть страницу браузера листать ниже :-). Для первичного понимания, как происходит ввод и на что это похоже, возьмём простенькую программу, которая будет считывать вводимые нами символы и отображать их в мониторе порта:

byte incomingByte;

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (Serial.available() > 0) {
    incomingByte = Serial.read();
    Serial.println(incomingByte, DEC);
}
}

Так как это у нас чат, необходимо чтобы каждая вводимая строка, отображалась на новой строке (это, помимо того, что подобный вывод должен содержаться ещё и в коде). Для этого нам нужно переключить в мониторе порта режим его работы для отображения вводимых строк, каждой на новой строке:

А не запилить ли нам хардварный чат? - 3

Кроме того, это сильно поможет нам в анализе отображаемой информации.
Далее, если мы введём, например, английскую букву «М», в мониторе порта покажутся следующие цифры:

А не запилить ли нам хардварный чат? - 4

Что это за цифры, и откуда они берутся: дело в том, что вся вводимая информация в монитор порта Arduino IDE вводится в кодировке UTF-8 (по крайней мере, это справедливо для версии 1.8.13, которая у меня). Таким образом, если мы обратимся к таблице кодировки UTF-8, то там мы увидим искомый код буквы «М» английской:

А не запилить ли нам хардварный чат? - 5

Но что же такое вот эта цифра 10, и откуда она взялась? Как можно было видеть по коду выше, мы печатали в монитор порта в десятичном формате (DEC), переключив, как ранее и говорилось, отображение строк — с новой строки. Помимо удобства наблюдения подобных строк в мониторе порта, здесь есть ещё и практическая польза: каждая отправляемая строка будет теперь содержать благодаря нашей настройке дополнительно цифру 10, которая является управляющим символом ASCII, в десятичной системе счисления. То есть, при анализе введённой пользователем строки, мы эту десятку будем учитывать как символ конца строки!

Это что касается ввода большинства символов и латинских букв. А как же обстоят дела с русским языком? Чтобы проверить это, попробуем ввести ту же самую заглавную букву «М», только на русском языке:

А не запилить ли нам хардварный чат? - 6

Как мы видим, отобразились три цифры, одну из которых мы уже знаем,- это условно говоря, символ конца строки (10), а также ещё какие-то две цифры. Как мы могли уже догадаться, эти цифры также означают кодировку буквы «М» кириллической, в системе UTF-8, в той же самой таблице, которая была приведена выше:

А не запилить ли нам хардварный чат? - 7

Только почему же цифры здесь две? А дело здесь в том, что буквы кириллицей в этой кодировке, кодируются в виде двух байтов, а буквы латиницей, — в виде одного байта. Ну вот такие правила были приняты когда-то. То есть, если сказать по-другому, для хранения большинства символов латинских букв в этой кодировке требуется для каждой из них сочетание из восьми нулей и единиц, а для кириллических букв — целых 16.

Кроме того, если мы попробуем проанализировать зависимости, то легко обнаружить, что максимальный по цифре латинский символ находится под номером 127, соответственно, все оставшиеся двухбайтные символы находятся под номерами от 128 и выше.

С учётом всего этого напишем свою функцию, которая будет обрабатывать наш ввод в монитор порта (ситуация у нас, на самом деле, довольно простая: «всё, что не 10 — всё наше»).

Обработчик сообщений

void MyMessageHandler ()
{
  byte incomingByte;
  String out;
 
  //всё, что ниже - обрабатывает наш ввод букв в монитор порта:  
  while (Serial.available() > 0)
  {
    incomingByte = Serial.read();

    //пробуем читать надписи - из монитора порта:

    //если это не символ конца строки-то выполняем, то что ниже
    if ( incomingByte != 10 )
    {
      char data = incomingByte;
      out = out + String (data);           
    }
    else//если это символ конца строки (то есть, пришла десятка (10) )
    {
      //добавили имя (чтобы все в чате знали,
      //кто написал сообщение)
      out = MyMQTT_NAME + out;
      //отправили на экран в
      //монитор порта "накопленную" строку
      Serial.print (out);
      char arr[out.length()+1];
      out.toCharArray(arr, out.length()+1);      
      publishSerialData(arr);     
      Serial.println ();
    }
  }
}

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

      out = MyMQTT_NAME + out;

Для работы с брокером мы будем использовать известную библиотеку PubSubClient.h.

До этого для начала нам придётся определить ряд переменных, которые можно видеть в самом начале программы, которым даны достаточно хорошо читаемые названия:

#define mqtt_port 1883
#define BROKER_USER "" //в нашем случае не нужно - это с запасом
#define BROKER_PASSWORD "" //в нашем случае не нужно - это с запасом
#define BROKER_CHAT_TOPIC "our_chat"
#define MyMQTT_NAME "Дедушко Морозъ: " //перед сообщением мы будем ставить своё имя
#define WELCOME "Фсем приведы, ох-хо-хо!"

Основной код программы состоит из четырёх функций библиотеки, местами модифицированных для достижения наших целей:

void setup_wifi()
void reconnect()
void callback(char* topic, byte *payload, unsigned int length)
void publishSerialData(char *serialData)

  • Функция setup_wifi() служит для подключения к точке доступа,
  • функция reconnect() предназначена для установления соединения с mqtt-брокером,
  • функция обратного вызова callback служит для обработки полученного сообщения с брокера,
  • publishSerialData — служит для постинга сообщений.

Кроме того, имеется закомментированная строчка с вызовом обработчикаTaskHandler(), — это такой своеобразный бонус в коде от автора статьи (находится внутри функции callback(char* topic, byte *payload, unsigned int length) ;-).

Этот обработчик реализован в новом окне, и служит примером, как можно обрабатывать полученные сообщения, если среди них должны быть какие-либо, для управления некими устройствами. В качестве примера в этом коде включается и выключается встроенный в esp32 светодиод:

void TaskHandler (String s)
{
  //Проверяем, нет ли команды на
  //включение встроенного в esp32 светодиода:
 
  for (int i=0; i<s.length(); i++)
  {
       String temp = s.substring(i, i+2);

       if (temp.equals("ON") )
       {
          if (digitalRead(LED==LOW) )
          {
              digitalWrite(LED, HIGH);            
          }
       }
  }

  //Проверяем, нет ли команды на
  //выключение встроенного в esp32 светодиода:
  for (int i=0; i<s.length(); i++)
  {
       String temp = s.substring(i, i+3);

       if (temp.equals("OFF") )
       {
          if (digitalRead(LED==HIGH)
          {
              digitalWrite(LED, LOW);            
          }          
       }
  }
}

Из интересного в упомянутом коде можно отметить, что библиотека предоставляет две функции отправки сообщений в топики, одна из которых применена внутри другой функции reconnect():

#define BROKER_CHAT_TOPIC "our_chat"
#define MyMQTT_NAME "Дедушко Морозъ: "
#define WELCOME "Фсем приведы, ох-хо-хо!"

//--------------.........-------------------------------

//После подключения - публикуем приветствие:
String temp = String (MyMQTT_NAME) + String (WELCOME);
client.publish(BROKER_CHAT_TOPIC, (char*) temp.c_str());

Которая может принять сообщение в виде явной строки ещё вот таким образом:


client.publish("our_chat", "фсем приведы");

Второй же функцией библиотеки, позволяющей отправлять данные, является использованная в нашем обработчике сообщений MyMessageHandler():

      //добавили имя (чтобы все в чате знали,
      //кто написал сообщение)
      out = MyMQTT_NAME + out;
      //отправили на экран в
      //монитор порта "накопленную" строку
      Serial.print (out);
      char arr[out.length()+1];
      out.toCharArray(arr, out.length()+1);      
      publishSerialData(arr);     
      Serial.println ();

Полный код всего проекта вы можете найти вот здесь.

Код отлично работает (по крайней мере, на моей версии Arduino IDE 1.8.13) и протестирован.

Для того чтобы проверить работу кода, особенно если у вас не несколько esp32, а всего лишь одна, вы можете воспользоваться публичным браузерным клиентом mqtt-брокера, который находится вот по этому адресу.

После перехода по ссылке — у вас откроется окно, в котором не нужно изначально ничего вводить, а нужно сразу нажать на кнопку «Connect», после чего у вас произойдёт подключение к брокеру:

А не запилить ли нам хардварный чат? - 8

После подключения вам нужно нажать на кнопку Add New Topic Subscription:

А не запилить ли нам хардварный чат? - 9

В появившемся окне вам нужно ввести название топика, который у нас используется esp32 для постинга сообщений (это нужно для того, чтобы мы в принципе видели, что наши сообщения постятся в топике, на брокере):

А не запилить ли нам хардварный чат? - 10

После того как мы подписались, мы увидим, что наша подписка появилась внизу под кнопкой Add New Topic Subscription.

Теперь нам останется только ввести название топика, куда мы будем постить сообщение (его название совпадает с тем, на который мы подписались) и, собственно, запостить сообщение. Отправленное сообщение появится внизу, в разделе messages:

А не запилить ли нам хардварный чат? - 11

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

А не запилить ли нам хардварный чат? - 12

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

Как говорит нам спецификация esp32, — плата поддерживает целый ряд протоколов шифрования на аппаратном уровне.

Имеется подозрение, что для наиболее полного раскрытия потенциала платы, необходимо будет использовать в этом смысле родной API среды разработки от Espressif (разработчика платы esp32), а не сторонние решения. Тем не менее задачи у нас скромные, для нас сгодится.

То есть теоретически, мы можем использовать для своих целей даже публичные брокеры и общаться на них вполне себе безопасно, если будем шифровать свой трафик.

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

Например, вот здесь, приводится достаточно подробное описание использования библиотеки mbedtls/aes.h, которая позволяет организовать шифрование с использованием AES алгоритма, с применением 128-битного ключа.

Насколько можно судить, использование библиотеки достаточно простое, в ней используется функция, которая на вход принимает контекст алгоритма, константу, которая означает, что нужно делать с переданной информацией, а также собственно строку на шифрование.

Таким образом, константа у нас может принимать два значения:

  • зашифровать данные переданной строки (MBEDTLS_AES_ENCRYPT);
  • расшифровать данные переданной строки (MBEDTLS_AES_DECRYPT).

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

Только при включении шифрования следует иметь в виду, что используемая библиотека для общения с mqtt-брокером, поддерживает максимальную длину сообщения в 256 байт (то есть, грубо говоря, 128 букв для кириллической, двухбайтной кодировки). Однако этот параметр можно менять, как заявляют создатели библиотеки, поэтому в приведённом выше коде чата была оставлена закомментированной строчка:

//размер сообщения в байтах (по умолчанию 256,
//но мы можем его менять и поставить свой размер,
//например, 512 и т.д., как здесь):
//  client.setBufferSize(512);

Которую можно раскомментировать и назначить своё значение, — как однократно, так и динамически, при каждой отправке сообщения (однако с этим могут возникнуть проблемы, так как ваш визави тоже должен знать, какой длины будет сообщение и какой параметр ему выставить). Теоретически может ждать ещё одна засада — ограничение на длину сообщения в бесплатной версии mqtt-брокера, надо изучать этот вопрос, чтобы выяснить. Подобная плата esp32 с шифрованием может выступать как своеобразный hardware usb secure key, например, выполненный в виде флешки, с помощью которого организуется защищённый канал связи (стартап?), с использованием которого, сообщение передаётся в зашифрованном виде, на целом ряде псевдослучайно генерируемых «каналов» — топиков.

Подытоживая рассказ, по приведённому выше можно видеть, что использование брокеров сообщений из сферы IoT, вполне себе позволяет организовать и общение людей, а не только электронных устройств, что предоставляет новые возможности в сфере коммуникации, особенно при организации защищённых соединений.

Автор:
DAN_SEA

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js