Интернет — советчик

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

Принцип работы устройства следующий: микроконтроллер подключается к WiFi сети, далее отправляет запрос к серверу (источнику сетевой мудрости), получает в ответ данные в формате json, далее разбирает ответ (парсит), перерабатывает в удобный вид и выводит на экран. Запрос нового совета по умолчанию происходит раз в десять минут, это время можно изменить в прошивке. Так же, если не терпится, можно получить свежий совет, нажав на кнопку. 

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

Для изготовления устройства нам понадобятся следующие компоненты:

  • Микроконтроллер ESP8266 (Wemos d1 mini pro в моем случае)
  • Светодиодная матрица (8*8)*4 на базе MAX7219
  • Тактовая кнопка

Передавать данные на матрицу мы будем по интерфейсу SPI, значит надо разобраться к каким пинам нужно подключаться, взглянем на распиновку макетной платы:

 Для реализации подключения одного устройства по SPI необходимо четыре контакта: MOSI — передача данных ОТ УПРАВЛЯЮЩЕГО устройства к управляемому, MISO — от УПРАВЛЯЕМОГО к управляющему, CLK — линия тактирования, CS — выбор управляемого устройства. Так как нам нужно только отправлять данные на матрицу и не нужно ничего получать от нее обратно, то контакт MISO мы не используем.

Схема подключения панели предельно проста:

Итак, мы подключаем панель к микроконтроллеру следующим образом: CLK — D5, CS — D1, DIN (MOSI) — D7 ну и землю и питание к земле и +5в соответственно. Кнопка подключена к пину D2 и к земле. Потребление устройства небольшое, питать можно как от USB так и от внешнего источника питания. 

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

После объявления переменных и необходимых объектов, переходим к секции setup. Программа транслирует полученные данные как на светодиодный матричный экран, так и в монитор последовательного порта (на скорости 115200 бод), это удобно как для отладки, так и, например, для отслеживания истории полученной информации, а так как особого быстродействия от программы не требуется, то и работа контроллера по передаче данных в порт абсолютно не мешает. 

Не забываем перед прошивкой указать имя Вашей WiFi сети и пароль:

 const char* ssid     = "Имя Вашей WiFi сети";     //ssid WiFi const char* password = "Пароль Вашей WiFi сети";  //Пароль WiFi

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

  WiFi.begin(ssid, password);   matrix.fillScreen(LOW);   while (WiFi.status() != WL_CONNECTED) {     delay(500);     Serial.print(".");     service_message ("Link");                     //Индикация установки соединения     wdt_count ++;     if (wdt_count > 21) while (1);                //Перезагрузка через софтверный WDT если не удалось подключиться в течение 10 секунд 

Во время процесса подключения в монитор серийного порта раз в пол секунда выводится точка, а на матрице отображается сообщение Link. Т.к. при подключении иногда возникают непонятные проблемы (У ESP8266 они в принципе есть, и с ними приходится мириться, от этого никуда не деться), то после примерно 10 секунд, если подключение так и не произошло, запускается бесконечный цикл, что приводит к срабатыванию сторожевого таймера (WDT) и перезагрузке устройства.

Когда соединение наконец установлено, рапортуем об этом в порт и на светодиодный экран, и вызываем функцию jsonGet(); запрашивая первый совет:

   Serial.println("WiFi connected");   service_message ("NetOK");                      //Есть соединение с сетью   jsonGet();                                      //Запрашиваем первый совет

Рассмотрим саму эту функцию:

   WiFiClient client;   const int httpPort = 80;   if (!client.connect(host, httpPort)) {          //Если сайт не отвечает, сообщаем об этом     Serial.println("connection failed");     service_message ("NoRes");     delay (3000);     return;   }

Начинаем с того, что создаем объект WiFiClient и пытаемся подключится к серверу указанному в переменной host по 80 порту. Если это не удалось, сообщаем об этом в серийный порт и на дисплей. И выходим из функции до следующего раза (через 10 минут или до нажатия на кнопку). 

Если же подключение удалось, идем дальше и формируем и отправляем GET запрос для сервера:

   client.println("GET http://fucking-great-advice.ru/api/random"); //GET запрос   client.println("Host: fucking-great-advice.ru");   client.println("Connection: close");   client.println();    delay(1500);

У данного сервиса в описании указано несколько вариантов запросов:

/* Cлучайный совет */
http://fucking-great-advice.ru/api/random

/* Цензурная версия совета */
http://fucking-great-advice.ru/api/random/censored/

/* Советы по тегу */
http://fucking-great-advice.ru/api/random_by_tag/дизайн

/* Сегодняшний совет */
http://fucking-great-advice.ru/api/latest

/* Последние несколько советов */
http://fucking-great-advice.ru/api/latest/5

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

После получения ответа сервера читаем его в переменную raw_string:
 

   while (client.available()) {     raw_string = client.readStringUntil('r');   }

Разбираем ответ сервера с помощью метода библиотеки ArduinoJson (6 версии):

   StaticJsonDocument<512>  doc;                                  //Библитека ArduinoJson версии 6+   DeserializationError error = deserializeJson(doc, raw_string); //десереализация из raw_string в doc   if (error) {     Serial.print(F("deserializeJson() failed: "));     Serial.println(error.f_str());     return;   }

Если разобрать не удалось, в монитор порта выводится сообщение об ошибке. Если все хорошо, идем дальше. Достаем поле «text» из ответа сервера, оно и содержит собственно сам совет, и сохраняем в переменную sovet типа String для дальнейшей обработки:

   String  sovet = doc["text"];                                  //Символы "—", "«", "»",отображаются некорректно, поэтому заменяем их если они есть.    sovet.replace("—", "-");   sovet.replace("«", R"(")");   sovet.replace("»", R"(")");

Как видно из кода, первоначальная обработка заключается в замене символов кавычек и тире, так как они некорректно отображаются при выводе на светодиодную панель (нюансы работы библиотеки Adafruit_GFX).

Далее совет подвергается безжалостной цензуре..

При желании эти строки можно удалить или закомментировать, а может дописать чего-то, что я мог пропустить, тут уже дело вкуса и эстетики. После чего меняется кодировка текста из UTF-8 в Windows-1251 посредством работы функции utf8rus, это необходимо для корректной работы библиотеки Adafruit_GFX и нормального отображения на экране кириллических символов. Готовый к отображению текст сохраняется в переменную raw_string, и отправляется в последовательный порт.

   raw_string = utf8rus(sovet);   Serial.println(sovet);

Логика основного цикла программы довольно проста. После объявления всех переменных, создания необходимых объектов и подключения к сети, попадаем в основной цикл, где происходит следующее:
Обработка события нажатия кнопки (Нажатие обнаруживается через прерывание, чтобы избежать пропусков нажатия)

   butt1.tick();   if (butt1.isClick()) {                           //При нажатии запрашиваем новые данные     string_count = 0;     jsonGet();   }

Сбрасываем переменную string_count, чтобы новый совет отображался с начала фразы, и вызываем функцию jsonGet(); которая этот совет, собственно, запрашивает, получает и сохраняет в строковую переменную для вывода на экран.

Если нажатия не было, то то же самое делаем периодически, через заданный интервал времени (600000мс = 10 минут, по умолчанию ).

   if (millis() - tmr1 >= BASE_PERIOD) {            //Запрашиваем новые данные через установленный интервал времени     tmr1 = millis();      string_count = 0;                                          jsonGet();   }

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

   if (millis() - tmr2 >= WAIT) {                   //Вывод совета бегущей строкой с установленной скоростью (задержкой)     tmr2 = millis();                                           output_on_display (raw_string);   }

На этом все. При желании скетч легко перенастраивается на другой похожий сервис, коих в сети не мало (Цитаты, афоризмы, погода, курсы валют.. и т.д.) Спасибо за внимание!

Список радиоэлементов

Обозначение Тип Номинал Количество Примечание Магазин Мой блокнот
LED дисплей MAX7219 (8×8) x 4 1
Микроконтроллер Wemos D1 Mini Pro 1
Кнопка тактовая 1