3.1 Общее знакомство

Платы Arduino в свое время стали настоящей находкой для радиолюбителей - просто и без особых сложностей можно использовать различные компоненты, датчики, писать логику программы. Но увы, во-первых, 8-битный процессор, работающий на частоте 16МГц, не позволяет запускать более-менее сложные алгоритмы. Во-вторых, современный мир - это мир Интернета. Практически для любого устройства возможность обмена данными становится критически важной.

Итак, настала пора предоставить героя нашей следующей главы - плату ESP32.

Всего за 6$ мы получаем 32-разрядный процессор, работающий на частоте 240МГц, 520Кб памяти, 4Мб флеш-памяти и поддержку Bluetooth и WiFi.

Писать программы для ESP32 можно также с помощью Arduino IDE, необходимо лишь доустановить компилятор для этого типа процессоров.

Для этого нужно:

- Скачать по адресу https://git-scm.com/download/win и установить программу контроля версий Git.

- В папке Документы\Arduino создать папку hardware\espressif\.

- Скачать библиотеки и компилятор для ESP32. Для этого нужно открыть консоль (Win+R - cmd), перейти в папку (cd C:\Users\Имя пользователя\Documents\Arduino\hardware\espressif\) и выполнить команду

git clone --recurse-submodules -j8 https://github.com/espressif/arduino-esp32.git (вместо консоли можно также воспользоваться Git GUI). Библиотеки будут устанавливаться довольно долго, около 10 минут.

- По окончании установки, нужно зайти в папку hardware\espressif\arduino-esp32\tools и запустить файл get.exe, который скачает недостающие файлы. Процесс также может занять несколько минут.

После этого заново запустим Arduino IDE - мы увидим, что количество доступных типов плат увеличилось, выберем ESP32 Dev Module.

Теперь можно подключить плату, и испытать какие-нибудь примеры. Поскольку, возможность работы с WiFi и Bluetooth является одним из основных преимуществ платы, это мы и проверим. Выберем в меню File-Examples-WiFi scan. Запускаем компиляцию… и получаем сообщение об ошибке. Оказывается, библиотека WiFi.h, идущая в комплекте с ESP32 SDK, конфликтует с уже имеющейся в папке "C:\Program Files (x86)\Arduino\libraries" библиотекой с тем же названием. Удаляем папку WiFi оттуда, перезапускаем Arduino IDE, после этого компиляция происходит нормально.

Код сканирования WiFi-сетей приведен ниже, как можно видеть, он весьма прост, и по стилю практически ничем не отличается от рассматриваемых ранее примеров для Arduino.

#include "WiFi.h"

void setup() {

Serial.begin(115200);

WiFi.mode(WIFI_STA);

WiFi.disconnect();

delay(100);

}

void loop() {

Serial.println("scan start");

int n = WiFi.scanNetworks();

Serial.println("scan done");

if (n == 0) {

Serial.println("no networks found");

} else {

Serial.print(n);

Serial.println(" networks found");

for (int i = 0; i < n; ++i) {

Serial.print(i + 1);

Serial.print(": ");

Serial.print(WiFi.SSID(i));

Serial.print(" (");

Serial.print(WiFi.RSSI(i));

Serial.print(")");

Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");

delay(10);

}

}

Serial.println("");

delay(5000);

}

Запускаем программу, и получаем список доступных WiFi-сетей:

Ура - мы проверили возможность новой платы работать с сетевым окружением, то, что не было доступно для Arduino.

3.2 Порты ввода-вывода

Перед использованием любой платы нужно разобраться, где и какие выводы расположены. Самый простой способ в нашем случае - набрать в поиске гугла фразу “ESP32 dev board pinout”. Для используемой в опытах платы ESP32 расположение пинов показано на картинке, впрочем для разных модификаций оно может отличаться.

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

// Встроенный светодиод на плате

int ledPin = 2;

// Кнопка

int inputPin = 23;

// Тач-панель

int touchPin = 4;

void setup() {

Serial.begin(115200);

pinMode(ledPin, OUTPUT);

pinMode(inputPin, INPUT_PULLUP);

}

void loop() {

// Читаем значение touchPin

int touch_value = touchRead(touchPin);

Serial.println(touch_value);

// Читаем значение inputPin

int buttonState = digitalRead(inputPin);

Serial.println(buttonState);

// Мигаем светодиодом

digitalWrite(ledPin, HIGH);

delay(1000);

digitalWrite(ledPin, LOW);

delay(1000);

}

Итак, несколько отличий. Как говорится в старом анекдоте, есть 2 новости - плохая и хорошая. Точнее, хороших новостей даже несколько.

1. Ввод данных с кнопки. Точно также, нужно настроить порт как “вход” (input). Но обратим внимание на новый параметр INPUT_PULLUP. Да, теперь “подтягивающий резистор” на плату можно не ставить, он уже есть в процессоре и его можно активировать программно.

2. Аппаратная поддержка касания (touch). Обратим внимание на выводы, отмеченные на картинке как Touch0...Touch9. Их можно использовать как тач-панель, вместо кнопок. Достаточно просто подключить к такому выводу кусок провода, и его касание рукой будет изменять значение переменной. Для примера выведем в порт значения touch_value:

Как можно видеть, в момент нажатия возвращаемые значения уменьшаются. Это можно использовать в коде с помощью обычной проверки вроде if (touch_value < 32) {...}.

3. Что касается вывода, то по сравнению с Arduino, он ничем не изменился, те же функции digitalWrite(ledPin, HIGH) и digitalWrite(ledPin, LOW).

Теперь обещанная плохая новость. Если мы возьмем плату с мигающим светодиодом, и тестером померяем напряжение на выходе, то увидим не 5В, а только 3.3В. Действительно, новые процессоры используют более низкое напряжение, по сравнению со старыми платами Arduino. Это не является какой-то глобальной проблемой, но при покупке датчиков или аксессуаров (например ЖК-экрана), стоит обратить внимание на то, чтобы датчик был совместим с 3.3В.

К примеру, посмотрим описание экрана на eBay:

Последняя строка Driving voltage - это то что нам нужно знать, чтобы убедиться что экран или датчик будет корректно работать с напряжением 3.3В.

Однако, что делать если есть какой-то нужный нам сенсор, работающий только от 5В? Выход 5В для питания сенсора на плате есть, он помечен как VIn, это не проблема. Проблема в вводе-выводе данных.

Здесь есть три способа:

1. Подключить “как есть”. При этом на входные пины платы будет подаваться напряжение 5В вместо 3.3В, скорее всего такое подключение работать будет, но это не гарантируется, да и не рекомендуется. Повышенное напряжение может привести к сокращению срока службы процессора.

2. Если данные поступают только на вход, то можно использовать делитель напряжения, который понизит напряжение с 5 до 3.3В:

3. Если нужен двухсторонний обмен данными, то можно использовать специальные платы, которые называются “logic level shifter”.

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

3.3 Подключаемся к WiFi

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

Код, делающий это, весьма прост, нужно знать лишь имя и пароль.

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

void setup() {

Serial.begin(115200);

Serial.print("Connecting to ");

Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(1000);

Serial.print(".");

WiFi.begin(ssid, password);

}

Serial.println("");

Serial.println("WiFi connected!");

Serial.print("IP address: ");

Serial.println(WiFi.localIP());

Serial.print("ESP Mac Address: ");

Serial.println(WiFi.macAddress());

Serial.print("Subnet Mask: ");

Serial.println(WiFi.subnetMask());

Serial.print("Gateway IP: ");

Serial.println(WiFi.gatewayIP());

Serial.print("DNS: ");

Serial.println(WiFi.dnsIP());

}

void loop() {

}

Как можно видеть, мы вызываем функцию WiFi.begin для установки соединения, и проверяем WiFi.status() для проверки, активно ли соединение. Параметры ssid и password хранят данные, необходимые для подключения. Когда подключение установлено, мы можем получить параметры соединения: IP-адрес, маску подсети и пр. Включив Serial monitor, мы можем увидеть следующие данные:

В логе можно видеть, что соединение установлено, и плата получила IP-адрес 192.168.0.102. Каждое устройство в сети имеет свой собственный IP-адрес, по которому к нему можно обратиться. То что адрес доступен, можно проверить командой ping: открываем консоль (Win+R - cmd), набираем ping 192.168.0.102. В ответ мы должны получить примерно такой результат, если плата отвечает на запрос:

Итак, мы успешно подключились к Сети, и можем двигаться дальше.

Важно: Как можно видеть в вышеприведенном коде, соединение с сетью WiFi происходит лишь в функции setup, которая активируется только один раз при включении платы. Соответственно, при перезагрузке маршрутизатора или потере WiFi-сигнала соединение не будет восстановлено. Чтобы этого избежать, нужно добавить в функцию loop проверку на необходимость восстановления коннекта.

while (WiFi.status() != WL_CONNECTED) {

delay(1000);

WiFi.begin(ssid, password);

}

Для наших тестовых программ это не требуется, но в реально действующем коде такая проверка необходима.

3.4 Подключаем дисплей

Мы уже подключали OLED-дисплей к Arduino, сделаем то же самое и для ESP32. Дисплей пригодится нам в следующих проектах, когда мы будем получать данные по WiFi из Интернет.

Посмотрим еще раз на картинку выводов платы (напомню, для разных плат они могут отличаться).

Нам нужны выводы GPIO21 и GPIO22, которые подписаны как I2C_SCL и I2C_SDA. Для тех кто забыл предыдущую часть, напомним что дисплей подключается по I2C и выглядит вот так:

Подключаем выводы дисплея к контактам 3.3V, GND, SCL и SDA. Этот дисплей может работать и от 5В и от 3.3В, так что здесь проблем нет. Затем скачиваем и устанавливаем библиотеки для OLED-дисплея по адресу https://github.com/ThingPulse/esp8266-oled-ssd1306.

Код для работы дисплея приведен ниже. Как можно видеть, он практически не отличается по сути от предыдущей рассмотренной версии для Arduino. Значения 21 и 22 - это номера выводов, соответствующие GPIO21 и GPIO22.

#include "SSD1306.h"

SSD1306 display(0x3c, 21, 22);

void setup() {

display.init();

display.flipScreenVertically();

}

void loop() {

display.clear();

display.setFont(ArialMT_Plain_10);

display.setTextAlignment(TEXT_ALIGN_LEFT);

display.drawString(0, 0, "Hello world");

display.setFont(ArialMT_Plain_16);

display.drawString(0, 10, "Hello world");

display.setFont(ArialMT_Plain_24);

display.drawString(0, 26, String(42));

display.display();

delay(5000);

}

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

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

Самостоятельная работа: изучить исходный текст библиотеки рисования по адресу https://github.com/ThingPulse/esp8266-oled-ssd1306/blob/master/OLEDDisplay.h. Помимо функции drawString, там описано множество других полезных функций, например drawCircle или drawProgressBar. Испытать их в программе.

3.5 Получаем время от атомных часов

Раз уж мы подключились к Интернет, можно сделать много чего полезного. Например, получить точное время с атомных часов от NTP-сервера, тем более что библиотеки для этого уже есть.

Добавим в программу соединения с WiFi код вывода точного времени.

#include

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

const char* ntpServer = "pool.ntp.org";

const long gmtOffset_sec = 3600;

const int daylightOffset_sec = 3600;

void setup() {

Serial.begin(115200);

while (WiFi.status() != WL_CONNECTED) {

delay(1000);

Serial.print(".");

WiFi.begin(ssid, password);

}

Serial.println("WiFi connected!");

Serial.print("IP address: ");

Serial.println(WiFi.localIP());

// Настройки службы времени

configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

}

void loop() {

// Получение времени

struct tm timeinfo;

if (!getLocalTime(&timeinfo)){

Serial.println("getLocalTime: error");

delay(10000);

return;

}

Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

delay(30000);

}

Как можно видеть, мы добавили вызов двух функций: configTime и getLocalTime. Первая функция настраивает нужные параметры (например часовой пояс), вторая получает время. Кстати, само время хранится в структуре tm, которая хранится в файле time.h и имеет следующий вид:

struct tm {

int tm_sec; // Seconds after the minute [0, 59]

int tm_min; // Minutes after the hour [0, 59]

int tm_hour; // Hours since midnight [0, 23]

int tm_mday; // Day of the month [1, 31]

int tm_mon; // Months since January [0, 11]

int tm_year; // Years since 1900

int tm_wday; // Days since Sunday [0, 6]

int tm_yday; // Days since January 1 [0, 365]

int tm_isdst; // Daylight Saving Time flag

int tm_gmtoff; // Seconds east of UTC

char *tm_zone; // Timezone abbreviation

};

Все эти поля можно использовать. Например, можно зажечь светодиод (или запустить зуммер) в 8 часов утра:

if (timeinfo.tm_hour == 8 && timeinfo.tm_min == 0) {

...

}

Мы также используем строку “%A, %B %d %Y %H:%M:%S”, которая указывает в каком формате выводить дату и время. Например, чтобы вывести только время, достаточно написать “%H:%M:%S”. Список разных форматов можно найти здесь.

Запустим программу, и в Serial Monitor увидим на экране точное время.

Самостоятельная работа: вывести точное время на экран дисплея.

3.6 Выводим количество друзей в “Контакте”

Продолжим углубляться в тонкости Web. На этот раз мы выведем количество друзей “ВКонтакте” для заданного пользователя. Подобная задача довольно часто встречается в профессиональном программировании, когда нужно получить и отобразить данные с какого-то сайта. Поэтому рассмотрим подробнее, как это делается.

Шаг-1. Находим, как получить список друзей

Каждый крупный сайт имеет так называемый “интерфейс разработчика”, или API - Application Program Interface, набор функций, доступный программистам для доступа к данным. Набираем в гугле “vk.com get friends API” (международным языком в программировании является английский), и первая же ссылка возвращает нам нужную страницу с описанием параметров: https://vk.com/dev/friends.get.

Еще немного ищем в интернете примеры использования, и в итоге получаем, что нам нужно выполнить такой запрос: https://api.vk.com/method/friends.get?user_id=ID, где ID - это идентификатор пользователя, который можно посмотреть в подсказке к ссылке на каждой странице. Берем первый попавшийся идентификатор, например 29744451, и открываем ссылку https://api.vk.com/method/friends.get?user_id=29744451 в браузере.

Работает! Мы получили страницу, которая выглядит примерно так:

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

Шаг-2. Получение данных с сервера.

Для получения данных в Arduino есть специальный класс HTTPClient.

Код для загрузки данных выглядит так:

const char *url = "https://api.vk.com/method/friends.get?user_id=29744451";

HTTPClient http;

http.begin(url);

int http_code = http.GET();

if (http_code > 0) {

String json = http.getString();

...

}

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

Шаг-3. Обработка данных

Нам не нужен весь список целиком, достаточно лишь узнать количество друзей. Для этого используем библиотеку обработки (парсинга) Json, скачать которую можно по ссылке https://github.com/bblanchon/ArduinoJson. Как обычно, файлы необходимо скачать и распаковать в папку Документы\Arduino\libraries.

Чтение данных из массива и получение его количества с помощью этой библиотеки выглядит так.

DynamicJsonBuffer jsonBuffer(16*1024);

JsonObject& parsed = jsonBuffer.parseObject(json);

if (parsed.success()) {

JsonArray& response = parsed["response"];

int count = response.size();

Serial.print("Number of friends: ");

Serial.println(count);

} else {

Serial.println("Parsing error");

}

Как можно видеть, мы получаем объект типа JsonArray, у которого узнаем размер вызовом метода size(). Также мы создаем объект DynamicJsonBuffer для хранения распакованных данных, при этом выделяется 16Кб памяти для временного буфера в памяти.

Результат готов! Мы получили данные в виде переменной count, теперь мы можем вывести данные в serial port.

Код программы полностью выглядит так.

#include

#include

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

void setup() {

Serial.begin(115200);

Serial.print("Connecting to ");

Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(1000);

Serial.print(".");

WiFi.begin(ssid, password);

}

Serial.println("");

Serial.println("WiFi connected!");

}

void loop() {

const char *url = "https://api.vk.com/method/friends.get?user_id=134212064";

Serial.println("Connecting to api.vk.com");

HTTPClient http;

http.begin(url);

int httpCode = http.GET();

if (httpCode > 0) {

String json = http.getString();

// Обработка Json

DynamicJsonBuffer jsonBuffer(16*1024);

JsonObject& parsed = jsonBuffer.parseObject(json);

if (parsed.success()) {

JsonArray& response = parsed["response"];

int count = response.size();

Serial.print("Number of friends: ");

Serial.println(count);

} else {

Serial.println("Json parsing error");

}

} else {

Serial.println("HTTP request error");

}

http.end();

// Пауза 2 мин

delay(120000);

}

Важно: при создании любых запросов к серверу рекомендуется делать их как можно реже, чтобы не перегружать сервер. Именно поэтому в конце функции стоит пауза на 2 минуты. Слишком частые запросы могут стать даже причиной блокировки (бана) по IP-адресу, особенно это касается плат типа ESP32, которые могут работать круглые сутки. Поэтому стоит делать запросы так редко как возможно, исходя из логики работы программы. Например, если мы делаем устройство, показывающее количество друзей в “Контакте”, то нет смысла запрашивать число друзей каждую минуту, скорее всего и интервала проверки раз в 10 минут или даже 1 час будет вполне достаточно.

Наконец, все готово. Запустим программу на ESP32, и в окне Serial monitor мы можем увидеть количество друзей у заданного нами пользователя.

Может возникнуть вопрос, как вывести не только число пользователей, но и более личные данные, например число входящих сообщений. Увы, такие запросы требуют аутентификации (логина) по имени и паролю, и так просто, одним запросом их не сделать. Желающие могут изучить документацию к API самостоятельно на сайте https://vk.com/dev/manuals.

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

3.7 Выводим число подписчиков, просмотров и лайков в Youtube

У популярных ведущих youtube-каналов иногда можно увидеть на столе устройство, показывающее число подписчиков канала. Сделаем такое же, с платой ESP32 это не сложно. Те, кто читал предыдущую главу, уже примерно представляют что нужно делать. Действительно, принцип точно такой же. Приступим.

Шаг-1. Узнаем идентификатор канала.

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

Идентификатор UCzz4CoEgSgWNs9ZAvRMhW2A в заголовке - это и есть идентификатор канала, запомним его.

Шаг-2. Находим API для получения информации

Функцию для получения информации о канале можно найти на странице для разработчиков https://developers.google.com/youtube/v3/docs/channels/list (да, там все на английском, английский - это язык современной науки и технологии, и его надо знать). Там же есть примеры использования.

Нужный нам запрос имеет следующий вид:

https://www.googleapis.com/youtube/v3/channels?id=ID&part=statistics&key=KEY

Как получить ID канала, мы уже знаем. С ключом чуть сложнее. Ключ для доступа к API бесплатный, получить его можно на странице https://console.developers.google.com в разделе “учетные данные”. Сам ключ - это обычная текстовая строка, которая выглядит примерно так: AIzaSyC26UJw-ubU6N6ukrbZ_C_nBaxxxxxxxxx.

Инструкция по получению ключа также есть на странице подсказки google.

Go to the API Console.

From the projects list, select a project or create a new one.

If the APIs & services page isn't already open, open the left side menu and select APIs & services.

On the left, choose Credentials.

Click Create credentials and then select API key.

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

https://www.googleapis.com/youtube/v3/channels?id=UCzz4CoEgSgWNs9ZAvRMhW2A&part=statistics&key=AIzaSyC26UJw-ubU6N6ukrbZ_C_nBaxxxxxxxxx

Здесь UCzz4CoEgSgWNs9ZAvRMhW2A - это идентификатор канала, а AIzaSyC26UJw-ubU6N6ukrbZ_C_nBaxxxxxxxxx - это ключ доступа. Мы можем вставить эту строку в браузер, и получить информацию о канале.

Как можно видеть, нужные нам данные хранятся в разделе items/statistics. Запомним это, когда будем делать обработку json.

Шаг-3. Чтение данных

Здесь мы повторяем фактически то же, что мы делали для чтения с сайта “ВКонтакте”.

Код для запроса к сайту:

const char *url = "https://www.googleapis.com/youtube/v3/channels?id=UCzz4CoEgSgWNs9ZAvRMhW2A&part=statistics&key=XXXXX";

HTTPClient http;

http.begin(url);

int httpCode = http.GET();

if (httpCode > 0) {

Serial.print("httpCode: "); Serial.println(httpCode);

String json = http.getString();

} else {

Serial.println("HTTP request error");

Serial.println(httpCode);

}

Кстати, коды ошибок сервера - очень важны для анализа, если что-то не так. Например, код 404 говорит о том, что страница не найдена, 403 - “доступ запрещен” и пр. Полный список можно почитать в Википедии.

Шаг-4. Обработка Json

Еще раз внимательно посмотрим на json, который мы получили:

{

"kind": "youtube#channelListResponse",

"items": [

{

"kind": "youtube#channel",

"statistics": {

"viewCount": "30177125",

"commentCount": "313",

"subscriberCount": "251966",

"videoCount": "301"

}

}

]

}

Как можно видеть, items - это массив элементов, т.к. он содержит скобки []. Внутри есть объект statistics, а внутри него нужное нам поле subscriberCount.

Код чтения Json будет выглядеть вот так:

DynamicJsonBuffer jsonBuffer(16*1024);

JsonObject& parsed = jsonBuffer.parseObject(json);

if (parsed.success()) {

JsonArray& items = parsed["items"];

if (items.size()> 0) {

JsonObject& statistics = items[0]["statistics"];

long subscriberCount = statistics["subscriberCount"];

Serial.print("Number of subscribers: ");

Serial.println(subscriberCount);

}

} else {

Serial.println("Json parsing error");

}

Мы создаем объект типа JsonArray, и если он не пуст, обращаемся к элементу с первым номером (в Си массивы нумеруются с нуля, items[0] это первый элемент).

Задача решена! Код полностью выглядит так:

#include

#include

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

void setup() {

Serial.begin(115200);

Serial.print("Connecting to ");

Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(1000);

Serial.print(".");

WiFi.begin(ssid, password);

}

Serial.println("");

Serial.println("WiFi connected!");

}

void loop() {

const char *url = "https://www.googleapis.com/youtube/v3/channels?id=UCzz4CoEgSgWNs9ZAvRMhW2A&part=statistics&key=XXXXXXXXXX";

Serial.println("Connecting to www.googleapis.com");

HTTPClient http;

http.begin(url);

int httpCode = http.GET();

if (httpCode > 0) {

Serial.print("httpCode: "); Serial.println(httpCode);

String json = http.getString();

// Парсинг Json

DynamicJsonBuffer jsonBuffer(16*1024);

JsonObject& parsed = jsonBuffer.parseObject(json);

if (parsed.success()) {

JsonArray& items = parsed["items"];

if (items.size()> 0) {

JsonObject& statistics = items[0]["statistics"];

long subscriberCount = statistics["subscriberCount"];

Serial.print("Number of subscribers: ");

Serial.println(subscriberCount);

}

} else {

Serial.println("Json parsing error");

}

} else {

Serial.println("HTTP request error");

Serial.println(httpCode);

}

http.end();

// Пауза

delay(120000);

}

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

При запуске программы данные будут выведены в Serial Monitor:

Самостоятельная работа: По аналогии с вышенаписанным, получить число просмотров для видеоролика на youtube. Для этого воспользоваться следующей функцией из Google API:

https://www.googleapis.com/youtube/v3/videos?part=statistics&id=ID&key=XXXX

(здесь ID - это идентификатор видеоролика, XXXX - Google API Key)

Функция вернет json, который выглядит примерно так:

{

"kind": "youtube#videoListResponse",

"etag": "\"_gJQceDMxJ8gP-8T2HLXUoURK8c/mISbZAYZi79hOI4WFXbLXPUphmg\"",

"items": [

{

"kind": "youtube#video",

"etag": "\"_gJQceDMxJ8gP-8T2HLXUoURK8c/PoZyfbkQEtIkGNfjDsAafjr_R08\"",

"id": "CizKnuwvXXg",

"statistics": {

"viewCount": "184277",

"likeCount": "8585",

"dislikeCount": "212",

"favoriteCount": "0",

"commentCount": "1349"

}

}

]

}

Необходимо исправить в коде обработку json, чтобы получить интересующие нас поля.

При желании можно вывести данные на OLED-дисплей, сделав автономно работающее устройство.

3.8 Запускаем собственный Web-сервер

Мы уже умеем получать и обрабатывать данные с различных серверов. Настала пора двигаться дальше - мы запустим на ESP32 собственный сервер. Его можно будет открыть в браузере, введя IP-адрес платы, подключенной к WiFi-сети. А если настроить статический IP-адрес и перенаправление портов на маршрутизаторе, то можно будет получить доступ к нашему серверу через Интернет, с любой точки земного шара!

Итак, приступим. Для использования ESP32 в качестве сервера мы будем использовать уже готовый класс WiFiServer. Он “слушает” входящие соединения, а его единственным параметром является номер порта (мы будем использовать порт 8000).

Код сервера приведен ниже.

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

WiFiServer server(8000);

void setup()

{

Serial.begin(115200);

delay(10);

// We start by connecting to a WiFi network

Serial.print("Connecting to "); Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

WiFi.begin(ssid, password);

Serial.print(".");

}

Serial.println("");

Serial.println("IP address: ");

Serial.println(WiFi.localIP());

server.begin();

}

void loop() {

WiFiClient client = server.available(); // Получение входящего “клиента”

if (client) {

Serial.println("New Client.");

String currentLine = "";

while (client.connected()) {

if (client.available()) { // Есть непрочитанные данные

char c = client.read(); // читаем 1 байт

Serial.write(c);

if (c == '\n') { // Символ перевода строки

// 2 пустые строки - признак конца запроса, посылаем ответ

if (currentLine.length() == 0) {

client.println("HTTP/1.1 200 OK");

client.println("Content-type:text/html");

client.println();

client.print(" ESP32 Server ”);

client.print("Hello world ");

client.println();

break;

} else {

// Очистить строку

currentLine = "";

}

} else if (c != '\r') { // if you got anything else but a carriage return character,

currentLine += c; // add it to the end of the currentLine

}

}

}

// Закрыть соединение:

client.stop();

}

}

Рассмотрим код подробнее.

Мы создаем объект WiFiServer, который “слушает” входящие соединения на определенном порту (в нашем случае 8000). Тот, кто подсоединяется к нашему серверу, называется “клиентом”. За взаимодействие с ним отвечает класс WiFiClient.

Далее, мы побайтно читаем данные с помощью вызова функции client.read(), и складываем данные в строку currentLine. К примеру, строка запроса может быть такой - GET / HTTP/1.1, здесь “GET” это тип запроса, “/” - это адрес главной страницы, 1.1 - тип стандарта. Но сам запрос мы не анализируем, а просто посылаем всегда один и тот же ответ - заглавную страницу с HTML-текстом “Hello world”. Строки типа “HTTP/1.1 200 OK” - это служебная информация, которая нужна браузеру (клиенту) чтобы понять, что мы ответили (200 - это код возврата OK, говорящий о том что все нормально). Строка “Content-type:text/html” говорит клиенту о том, что возвращаться будет HTML-страница.

Наша страница имеет следующий вид:

ESP32 Server

Hello world

Как можно видеть, она содержит “заголовок” (header) и “тело” (body). Заголовок содержит тег title - в нем хранится строка, которая будет выведена в заголовке браузера.

Итак, компилируем данный код, загружаем его в плату, и в Serial Monitor смотрим какой IP-адрес получила ESP32 при доступе к WiFi-сети. Вводим этот адрес в строку браузера, не забыв указать номер порта, например http://192.168.0.104:8000.

Все готово! Через небольшое время ожидания мы видим страницу браузера с нашим текстом Hello world. Обращаем внимание на то, что и заголовок и текст соответствуют тому, что мы ввели.

Самостоятельная работа: изучить команды HTML, поэкспериментировать с разными вариантами разметки и форматирования текста.

3.9 Управляем светодиодом через Web

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

Принцип простой - мы добавим в HTML-страницу 2 ссылки, нажатие которых и будет отслеживаться сервером.

Новая HTML-страница будет выглядеть так:

ESP32 Server

Turn LED ON

Turn LED OFF

В HTML-странице можно видеть 2 ссылки с значением адреса /H и /L, когда пользователь нажмет на них, на сервер будет отправлен GET-запрос с соответствующим адресом. Это нам и нужно.

Код программы целиком приведен ниже.

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

int ledPin = 2;

WiFiServer server(8000);

void setup() {

Serial.begin(115200);

pinMode(ledPin, OUTPUT);

delay(10);

Serial.print("Connecting to ");

Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

WiFi.begin(ssid, password);

Serial.print(".");

}

Serial.println("WiFi connected.");

Serial.println("IP address: ");

Serial.println(WiFi.localIP());

server.begin();

}

void loop(){

WiFiClient client = server.available();

if (client) {

Serial.println("New Client.");

String currentLine = "";

while (client.connected()) {

if (client.available()) {

char c = client.read();

if (c == '\n') {

if (currentLine.length() == 0) {

client.println("HTTP/1.1 200 OK");

client.println("Content-type:text/html");

client.println();

client.print("");

client.print("ESP32 Server");

client.print("");

client.print("Turn LED on
");

client.print("Turn LED off
");

client.print("");

client.println();

break;

} else {

currentLine = "";

}

} else if (c != '\r') {

currentLine += c;

}

// Проверка ссылок от клиента, может быть "GET /H" или "GET /L":

if (currentLine.endsWith("GET /H")) {

digitalWrite(ledPin, HIGH); // GET /H - включить светодиод

}

if (currentLine.endsWith("GET /L")) {

digitalWrite(ledPin, LOW); // GET /L выключить светодиод

}

}

}

// Закрыть соединение

client.stop();

}

}

Запускаем программу, обновляем страницу, и видим нашу “панель управления”:

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

Кстати, если у интернет-провайдера доступна услуга “статический IP”, то можно настроить перенаправление портов в роутере, и управлять светодиодом через Интернет, даже из другого города или другой страны.

Самостоятельная работа #1: изучить разные варианты форматирования текста в HTML, например, таблицы, шрифты, выравнивание. Это позволит делать более сложный и более красивый интерфейс.

Самостоятельная работа #1: подключить к плате “пищалку”, и настроить управление ею через web. Это позволит посылать сигналы родственникам или друзьям.

3.10 Выводим изображения на web-сервере

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

Чтобы вставить картинку в HTML, нужно.

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

2. Сконвертируем изображение в base64-формат, для этого можно воспользоваться любым онлайн-конвертором, например этим: https://www.base64-image.de.

Мы получим строку такого вида:

data:image/jpeg;base64,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

3. Создадим текстовую переменную, которая будет хранить наше изображение и скопируем в нее всю строку:

const PROGMEM char *image01 = "data:image/jpeg;base64,/9j/4A….5n/2Q==”;

Тег PROGMEM указывает компилятору, что это константа, которую можно хранить во флеш-памяти, сэкономив тем самым место для других переменных. Это особенно актуально, если картинок много.

4. Теперь мы можем использовать наше изображение с помощью тега img следующим образом:

Для этого добавим следующий код в следующую строку после “Turn LED off”:

client.print("");

Наш веб-сервер вряд ли получит премию года за лучший дизайн, но по крайней мере, пользователь теперь видит, что он управляет именно светодиодами:

Кстати, в реальном проекте все изображения целесообразно вынести в отдельный файл, который можно назвать например, “images.h”. Тогда в основной программе можно будет написать #include "images.h". Это позволит отделить ресурсы от кода, и сделать текст программы более читаемым.

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

3.11 Удаленный мониторинг

С помощью web-сервера можно не только включать и выключать светодиод, но и получать информацию с удаленного объекта. Например, можно узнать температуру в квартире, находясь на каникулах или в отпуске. Как нетрудно догадаться, для этого достаточно вывести нужные нам параметры в HTML, который и отобразится на веб-странице.

К примеру, достаточно добавить такую строку в код формирования HTML:

if (digitalRead(buttonPin) == HIGH) {

client.print("Button state: ON
");

} else {

client.print("Button state: OFF
");

}

При обновлении страницы в браузере мы увидим соответствующий текст. Разумеется, это может быть не только кнопка, но и например, датчик закрывания двери или датчик освещенности (желающие могут перечитать главу 2.6 “Ввод аналоговых величин”).

Рассмотрим пример подробнее. Допустим, у нас есть данные с разных сенсоров (подробнее можно прочитать в главе 2.7-2.10). Чтобы не загромождать тестовый код, представим данные в виде уже готовых переменных, реальный код чтения желающие могут добавить самостоятельно.

float temperature = 22.5;

int humidity = 60;

int pressure = 1010;

bool doorClosed = true;

bool windowClosed = false;

Наша задача - отобразить эти данные на веб-сервере, для чего проще всего воспользоваться HTML-тегом “Table” (таблица).

Пример таблицы в HTML:

Row 1, Column 1 Row 1, Column 2
Row 2, Column 1 Row 2, Column 2

В браузере это будет выглядеть вот так:

Здесь tr - это table row, строка таблицы, а td - это элемент ячейки. Таким образом, мы можем группировать данные так, как нам удобно. Добавим таблицу в наш сервер. Для экономии места будет приведена только функция loop, остальное не изменилось.

void loop() {

// Возобновление соединения при необходимости

while (WiFi.status() != WL_CONNECTED) {

delay(500);

WiFi.begin(ssid, password);

Serial.print(".");

}

// Данные с датчиков

float temperature = 22.5;

int humidity = 60;

int pressure = 1010;

bool doorClosed = true;

bool windowClosed = false;

// Здесь можно добавить код чтения

// ...

WiFiClient client = server.available();

if (client) {

Serial.println("New Client.");

String currentLine = "";

while (client.connected()) {

if (client.available()) {

char c = client.read();

if (c == '\n') {

if (currentLine.length() == 0) {

client.println("HTTP/1.1 200 OK");

client.println("Content-type:text/html");

client.println();

client.print("");

client.print("ESP32 Server");

client.print("");

client.print("

ESP32 sensors data

");

client.print("

");

client.print("

");

client.print("

");

client.print("

");

client.print("

");

client.print("

");

client.print("

Temperature, C");

client.print(String(temperature)); client.print("

Humidity, percent");

client.print(String(humidity)); client.print("

Pressure, hPa");

client.print(String(pressure)); client.print("

Door closed:");

client.print(doorClosed ? "yes" : "no"); client.print("

Window closed:");

client.print(windowClosed ? "yes" : "no"); client.print("

");

client.print("");

client.print("");

client.println();

break;

} else {

currentLine = "";

}

} else if (c != '\r') {

currentLine += c;

}

}

}

client.stop();

Serial.println("Client Disconnected.");

}

}

Как можно видеть, код довольно прост. Мы сформировали таблицу с нужными нам полями. Для вывода строк yes/no мы использовали конструкцию языка С doorClosed ? "yes" : "no", она позволяет выбрать одно из двух значений в зависимости от условия.

Запускаем браузер, и видим данные с нашего сервера:

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

Самостоятельная работа #1: подключить реальные датчики, например DS1820.

Самостоятельная работа #2: Поэкспериментировать с раскраской и стилями таблицы. Например, так можно добавить красный цвет к ячейке:

Row 1, Column 1

3.12 Делаем односторонний мессенджер

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

На языке HTML код для отправки выглядит просто, достаточно добавить следующие строки:

Enter your message:

При этом страница в браузере будет иметь следующий вид:

При этом, если пользователь введет текст, например “123 456”, и нажмет кнопку “Submit”, браузером будет отправлен запрос “GET /send?msg=123+456 HTTP/1.1”, который мы можем обработать на сервере. Дальше, как говорится, дело техники - надо извлечить из строки подстроку, убрав начальные и конечные части.

Добавим код там, где проверяется начало строки:

if (currentLine.startsWith("GET /send?msg=") && currentLine.endsWith(" HTTP/1.1")) {

// String looks like GET /send?msg=123+456 HTTP/1.1

String msg = currentLine.substring(14, currentLine.length()-8);

msg.replace("+", " ");

Serial.println(msg);

}

Как можно видеть, с помощью функции substring мы взяли подстроку, пропустив 14 символов сначала (длина “GET /send?msg=”) и 8 символов с конца (длина “ HTTP/1.1”). Функция replace заменяет символы “+” на пробелы, чтобы из строки “123+456” получить “123 456”.

Добавим вместо Serial.println вывод на дисплей, который мы рассмотрели в главе 3.4.

Готовый код “мессенджера” целиком приведен ниже.

#include

#include "SSD1306.h"

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

int ledPin = 2;

WiFiServer server(8000);

SSD1306 display(0x3c, 21, 22);

void setup()

{

Serial.begin(115200);

pinMode(ledPin, OUTPUT);

// Инициализация дисплея

display.init();

display.flipScreenVertically();

display.clear();

display.setFont(ArialMT_Plain_10);

display.setTextAlignment(TEXT_ALIGN_LEFT);

display.drawString(0, 0, "App started");

display.display();

delay(10);

// Запуск WiFi

Serial.print("Connecting to ");

Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

WiFi.begin(ssid, password);

Serial.print(".");

}

Serial.println("");

Serial.println("WiFi connected.");

Serial.println("IP address: ");

Serial.println(WiFi.localIP());

server.begin();

}

void loop(){

while (WiFi.status() != WL_CONNECTED) {

delay(500);

WiFi.begin(ssid, password);

Serial.print(".");

}

WiFiClient client = server.available();

if (client) {

Serial.println("New Client.");

String currentLine = "";

while (client.connected()) {

if (client.available()) {

char c = client.read();

if (c == '\n') { // if the byte is a newline character

if (currentLine.length() == 0) {

client.println("HTTP/1.1 200 OK");

client.println("Content-type:text/html");

client.println();

client.print("");

client.print("ESP32 Server");

client.print("");

client.print("

ESP32 sensors data

");

client.print("

");

client.print("Enter the message:
");

client.print("");

client.print("

");

client.print("");

client.print("");

client.println();

break;

} else {

if (currentLine.startsWith("GET /send?msg=") &&

currentLine.endsWith(" HTTP/1.1")) {

// Строка выглядит как “GET /send?msg=123+456 HTTP/1.1”

String msg = currentLine.substring(14, currentLine.length()-8);

msg.replace("+", " ");

// Вывод строки на дисплей

display.clear();

display.setFont(ArialMT_Plain_10);

display.setTextAlignment(TEXT_ALIGN_LEFT);

display.drawString(0, 0, msg);

display.display();

}

currentLine = "";

}

} else if (c != '\r') {

currentLine += c;

}

}

}

client.stop();

Serial.println("Client Disconnected.");

}

}

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

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

Самостоятельная работа #1: добавить к плате “пищалку” или светодиод, которая будет сигнализировать о наличии нового сообщения. При желании также можно добавить на плату кнопку “Сообщение прочитано”, которой получатель подтвердит получение. Для этого достаточно добавить переменную messageIsRead, и при получении нового сообщения устанавливать ее в False, а при нажатии кнопки менять значение на True. В общем, простор для творчества тут весьма велик.

Самостоятельная работа #2: Добавить вывод нескольких сообщений. Для этого можно добавить несколько переменных, например “String msg1, msg2, msg3”, а при получении нового сообщения добавлять его в “конец очереди”:

msg1 = msg2;

msg2 = msg3;

msg3 = msg;

Функцию вывода на экран также придется изменить.

3.13 Отправляем данные через Dropbox

Мы уже умеем выводить с ESP32 данные через встроенный веб-сервер. К сожалению, главный его недостаток - необходимость наличия статического IP-адреса, если мы хотим получать доступ извне. Это удобно, но статический IP-адрес - это обычно платная услуга, этого хотелось бы избежать.

Есть альтернативный способ - сохранять данные в бесплатном сервисе Dropbox. Это позволит ESP32 периодически сохранять логи или какую-либо другую информацию в файлах Dropbox, и они будет автоматически синхронизироваться и появляться на домашнем компьютере или смартфоне.

Сначала нужно создать так называемое “приложение dropbox”, сделать это можно по ссылке https://www.dropbox.com/developers/apps/create:

Когда приложение создано, мы можем выбрать его в разделе My apps на странице https://www.dropbox.com/developers/apps.

Приложение Dropbox имеет доступ только к определенной папке, которая будет синхронизироваться с домашним или рабочим компьютером, и будет иметь путь “Имя_пользователя\Dropbox\Приложения\Имя_приложения”.

Внутри настроек приложения есть параметр “access token”, он будет нужен нам для получения доступа.

Там же выбираем пункт Generate access token:

Нажимаем кнопку “Generate” рядом с “access token”, и получаем ключ вида V3z9NpYlRxEAAAAAAACG5cjBXXXXXXXXXXXXXX, его надо сохранить, мы будем использовать его в дальнейшем.

На этом подготовительная часть закончена. Сам сервис Dropbox имеет разнообразное API для работы с данными, посмотреть список функций можно по адресу https://www.dropbox.com/developers/documentation/http/documentation. Нам для отправки файлов нужна будет всего лишь одна функция upload.

Для того, чтобы отличать файлы друг от друга, мы будем создавать файлы по шаблону “год-месяц-день-чч-мм-сс.txt”. Dropbox требует защищенного соединения по https, поэтому мы используем класс WiFiClientSecure.

Код программы целиком приведен ниже.

#include

#include

#include

const char* ssid = "TP-LINK_AB11";

const char* password = "12345678";

WiFiClientSecure client;

void uploadData(String content) {

Serial.println("Dropbox connecting...");

if (client.connect("content.dropboxapi.com", 443)) {

Serial.println("Dropbox connected");

// Сформировать имя файла по шаблону времени

time_t now = time(nullptr);

struct tm timeinfo;

gmtime_r(&now, &timeinfo);

char file_name[64] = {0};

strftime(file_name, 64, "log-%Y-%m-%d-%H-%M-%S.txt", &timeinfo);

Serial.print("Upload "); Serial.println(file_name);

// Отправка запроса

client.println("POST /2/files/upload HTTP/1.1");

client.println("Host: content.dropboxapi.com");

client.println("Authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXX");

char dropbox_args[255] = {0};

sprintf(dropbox_args,

"{\"path\": \"/%s\", \"mode\": \"overwrite\", \"autorename\": true, \"mute\": false}", file_name);

client.print("Dropbox-API-Arg: "); client.println(dropbox_args);

client.println("Content-Type: application/octet-stream");

client.print("Content-Length: "); client.println(content.length());

client.println();

client.println(content);

delay(5000);

client.stop();

Serial.println("Disconnect");

Serial.println();

} else {

Serial.println("Error: cannot connect");

Serial.println();

}

}

void setup() {

Serial.begin(115200);

delay(10);

Serial.print("Connecting to "); Serial.println(ssid);

while (WiFi.status() != WL_CONNECTED) {

delay(500);

WiFi.begin(ssid, password);

Serial.print(".");

}

Serial.println("");

Serial.println("IP address: ");

Serial.println(WiFi.localIP());

// Установка времени через SMTP

Serial.print("Setting time using SNTP");

configTime(8*3600, 0, "pool.ntp.org", "time.nist.gov");

time_t now = time(nullptr);

while (now < 8 * 3600 * 2) {

delay(500);

Serial.print(".");

now = time(nullptr);

}

Serial.println("done");

// Загрузка данных #1

uploadData(String("Data from ESP32 - this is a test 1"));

delay(5000);

// Загрузка данных #2

uploadData(String("Data from ESP32 - this is a test 2"));

delay(5000);

// Загрузка данных #3

uploadData(String("Data from ESP32 - this is a test 3"));

}

void loop(){

}

Как можно видеть, мы создали функцию uploadData, которую можно вызывать нужное число раз (имеет смысл делать это не очень часто, например раз в 5 или 30 минут). В данном примере функция вызывается только из setup(), чтобы файлы не создавались постоянно.

Вместо XXXXXXXXXXXXXXXXXXXXXXXXXXXX нужно будет вставить свой токен, полученный в панели управления https://www.dropbox.com/developers/apps.

Результат - запускаем программу, и через некоторое время видим в папке Dropbox 3 файла.

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

Важно. Это наверное должно быть и так очевидно, но лучше повторить. Функции любого сервиса, такого как Dropbox, Google, Amazon, всегда поставляются на условиях “как есть” - создатели не гарантируют их абсолютно бесперебойной работы (тем более, если речь идет о бесплатных услугах). Также эти функции могут периодически совершенствоваться и меняться, так что гарантий многолетней и бесперебойной работы устройства, увы, нет. Поэтому подходы, аналогичные описанным выше, не стоит использовать там, где от этого зависит безопасность людей или имущества.

На этом мы закончим описание работы с платой ESP32. Как можно видеть, свою цену в 8$ она отрабатывает на 110%. В следующей части мы рассмотрим работу с Linux на базе одноплатного компьютера Raspberry Pi.