Mqtt брокер на esp8266 пошаговая инструкция

Время на прочтение
3 мин

Количество просмотров 47K

Как говорится, лень — двигатель прогресса. Для облегчения жизни сейчас делаю себе небольшое устройство в виде модуля ESP8266 и преобразователя RS485 для связи с датчиками-газоанализаторами по протоколу Modbus. В производстве постоянно возникает необходимость подключаться к приборам для выполнения различного рода диагностики по 485 интерфейсу, но каждый раз тащить с собой ноутбук неудобно, а вот мобильный телефон всегда в кармане.

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

Приложение для телефона

В качестве приложения я выбрал IoTmanager. Основная фича — это очень гибкая настройка виджетов с помощью HTML5+CSS, все настройки производятся в устройствах, а не в приложении. Топики отправляются в JSON формате и содержат в себе имена заголовков, значения и стили отображения. Возможно кому-то это будет неудобно, но мне такой подход понравился.

Приложение может работать на двух MQTT библиотеках: Paho.js и MQTT.js. С ходу у меня получилось установить WebSocket соединение через библиотеку Paho, на ней я и остался работать. Если выбрать в настройках MQTT, соединения не происходит, подозреваю, что нужно поковырять библиотеку WebSocketServer.

Я долго презирал Arduino, но все же сдался

Не хочу разводить дискуссию по поводу выбора среды, просто скажу, что для меня важную роль в выборе Arduino IDE для написания прошивки ESP8266 сыграло наличие тонны готовых библиотек и документации. Все просто и быстро, благо проект обещает быть не сложным.

Оффтопик

Хотите верьте, хотите нет, но даже ракеты улетают в космос с микроконтроллерами, запрограммированными в среде Arduino. Решающую роль очень часто играет скорость разработки — подключил готовые библиотеки и в путь.

Open Source наше все

Репозиторий на GitHub. Библиотека пока сыровата, в некоторых случаях настройки виджетов вызывают реконнект IoTmanager’a, причину которого я найти пока не могу, возможно совместная разработка пойдет быстрее.

Проект содержит в себе две реализации библиотеки:

MQTTbroker.h это попытка реализовать реальный брокер с контролем подписок. Т.е когда приходит сообщение в топик, брокер проходит по всем клиентам и их подпискам и ищет совпадения (в том числе по маскам /+/) и рассылает сообщения только тем, у кого подписки соответствуют имени топика, не забывая про самого себя.

MQTTbroker_lite.h работает немного быстрее за счет того, что в ней отсутствует автоматическая логика обработки подписок. Все приходящие сообщения перенаправляются в callback функцию основной программе, а там уже, если надо, обрабатываем их сами. Для конкретно моего случая нужна именно такая реализация: одно устройство — один подключаемый клиент, я знаю на что он подписан и чего от меня ждет.

Пример работы

Берем любой модуль с ESP8266 (у меня это NodeMcu) и загружаем в него скетч примера из библиотеки. Порт для отладки в инструментах Arduino IDE предлагаю отключить, иначе библиотека будет слать кучу отладочных сообщений, потом на них посмотрите. Открываем последовательный порт и наблюдаем IP адрес.

На телефоне подключаемся к созданной Wi-Fi точке доступа. В приложении IoTmanager заходим в настройки подключения: выбираем движок PAHO, вбиваем IP адрес модуля, 80 порт, префикс топиков /IoTmanager и чуть ниже отключаем SSL/TLS.

Жмем на спидометр в углу, после небольшой задержки должно подключиться и отобразить переключатели. Если не подключается, попробуйте убить и заново запустить приложение. На первом переключателе у меня настроен светодиод (см.видео).

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

Короткое описание

Обе части библиотеки очень похожи, приведу описание версии _lite:

typedef void(*callback_t)(uint8_t num, Events_t event , String topic_name, uint8_t * payload, uint8_t length_payload); //функция callback'a должна иметь такой вид

MQTTbroker_lite(WebSocketsServer * webSocket); //конструктору класса передаем указатель на WebSocket
void setCallback(callback_t cb); //установка функции callback'a
void begin(void); //начальная инициализация (обнуление массивов)
void parsing(uint8_t num, uint8_t * payload, uint8_t length); //парсинг пришедшего в WS сообщения
void publish(uint8_t num, String topic, uint8_t* payload, uint8_t length); //публикация сообщения клиенту num
void disconnect(uint8_t num); //отключение клиента
bool clientIsConnected(uint8_t num); //проверка клиента на соединение 

Видео демонстрация

Спасибо за внимание. Присоединяйтесь к разработке, библиотека еще сыровата.
За помощью можно обращаться в Telegram oWart

Ссылки:

1. Проект на GitHub
2. Описание виджетов IoTmanager
3. Спецификация протокола MQTT v.3.1.1

ESP8266 as a MQTT Broker

In this tutorial i am telling to you ” How To use ESP8266 as a MQTT Broker”. uMQTTBroker is a MQTT Broker library for ESP8266 Arduino, available on GitHub.

Now we Arduino IDE, If you don’t familier with ESP8266 using Arduino IDE. Please Visit given below post.

Arduino Support for ESP8266 with simple test code

First Download uMQTT Broker library from Github. Download Now.  Create new sketch and paste given below code.

/*
 * uMQTTBroker demo for Arduino
 * 
 * Minimal Demo: the program simply starts a broker and waits for any client to connect.
 */

#include 
#include "uMQTTBroker.h"

uMQTTBroker myBroker;

/*
 * Your WiFi config here
 */
char ssid[] = "iotbyhvm";      // Replace with your network SSID (name)
char pass[] = "asdf7890"; // Replace with your network password

/*
 * WiFi init stuff
 */
void startWiFiClient()
{
  Serial.println("Connecting to "+(String)ssid);
  WiFi.begin(ssid, pass);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  
  Serial.println("WiFi connected");
  Serial.println("IP address: " + WiFi.localIP().toString());
}

void startWiFiAP()
{
  WiFi.softAP(ssid, pass);
  Serial.println("AP started");
  Serial.println("IP address: " + WiFi.softAPIP().toString());
}

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

  // Connect to a WiFi network
  startWiFiClient();

  // Or start the ESP as AP
//startWiFiAP();

  // Start the broker
  Serial.println("Starting MQTT broker");
  myBroker.init();
}

void loop()
{   
  // do anything here
  delay(1000);
}

Now connect your ESP8266 development board with your PC. Select Board and Port. Flash this code.

Open Your Serial Monitor, Here you can found a IP address. this IP address is your MQTT Broker address.

If Serial Monitor is not displaying IP address then press RST button of ESP8266 Board.

What is uMQTTBroker ?

This is a MQTT Broker library for ESP8266 Arduino. You can start an MQTT broker in any ESP Arduino project. Just clone (or download the zip-file and extract it) into the libraries directory of your Arduino ESP8266 installation.

The broker does support:

  • MQTT protocoll versions v3.1 and v3.1.1 simultaniously
  • a smaller number of clients (at least 8 have been tested, memory is the issue)
  • retained messages
  • LWT
  • QoS level 0
  • username/password authentication

The broker does not yet support:

  • QoS levels other than 0
  • many TCP(=MQTT) clients
  • non-clear sessions
  • TLS

For API information Visit this: https://github.com/martin-ger/uMQTTBroker


If you are searching for a complete ready-to-run MQTT broker for the ESP8266 with additional features (persistent configuration, scripting support and much more) have a look at https://github.com/martin-ger/esp_mqtt .

esp_uMQTT_broker

This is a MQTT Broker/Client with scripting support on the ESP8266. This program enables the ESP8266 to become the central node in a small distributed IoT system. It implements an MQTT Broker and a simple scripted rule engine with event/action statements that links together the MQTT sensors and actors. It can act as STA, as AP, or as both and it can connect to another MQTT broker (i.e. in the cloud). Here it can also be bridge that forwards and rewrites topics in both directions. Also it can parse JSON structures, send basic HTTP GET requests and do basic I/O: i.e. read and write to local GPIO pins, react on timers and GPIO interrupts, drive GPIO pins with PWM, and read the ADC.


I hope you’ve found this post “ESP8266 as a MQTT Broker″useful. If have any query, Please write in comment box. You can share this post “ESP8266 as a MQTT Broker “.


You may also like:

  • Dynamic WLAN configuration for ESP32 Board | AutoConnect
  • ESP32 BLE on Arduino IDE with UART Test
  • ESP32 Bluetooth Low Energy (BLE) on Arduino IDE
  • ArduinoOTA ESP32: Wi-Fi (OTA) Wireless Update from the Arduino IDE
  • ESP32 with LoRa using Arduino IDE
  • How To Use Grove-LCD RGB Backlight with NodeMCU
  • NodeMcu to DHT Interface in Blynk app
  • How To ON/OFF a bulb by Google voice assistant
  • Arduino IDE | Arduino | Open Source Hardware/Softawre | Arduino Vs RPi
  • WiFi LoRA 32 (V2) ESP32 | Overview | Introduction
  • DHT11 sensor with ESP8266/NodeMCU using Arduino IDE
  • Arduino Support for ESP8266 with simple test code

Harshvardhan Mishra

Hi, I’m Harshvardhan Mishra. I am a tech blogger and an IoT Enthusiast. I am eager to learn and explore tech related stuff! also, I wanted to deliver you the same as much as the simpler way with more informative content. I generally appreciate learning by doing, rather than only learning. If you want to help support me on my journey, consider sharing my articles, or Buy me a Coffee!
Thank you for reading my blog! Happy learning!

Технологии интернета вещей (Internet of Things, IoT) с каждым годом все более прочно входят в нашу жизнь, позволяя устройствам объединяться в единую глобальную сеть передачи данных. В настоящее время одним из самых популярных протоколов обмена данными между устройствами является протокол MQTT (Message Queuing Telemetry Transport – телеметрический транспорт с очередями сообщений). В данной статье мы рассмотрим подключение и осуществление взаимодействия между платой NodeMCU ESP8266 и популярным брокером Eclipse, работающим по протоколу MQTT.

Внешний вид проекта подключения NodeMCU ESP8266 к MQTT брокеру

Ранее на нашем сайте мы рассматривали следующие проекты с использованием протокола MQTT:

  • установка Mosquitto MQTT брокера на Raspberry Pi;
  • автоматизация дома на основе Raspberry Pi и облака MQTT.

MQTT – это «легкий» (малозатратный) протокол передачи сообщений, использующий модель издатель/подписчик (publish/subscribe) и позволяющий осуществлять передачу сообщений между множественными устройствами. Также с помощью протокола MQTT можно передавать/принимать данные и управлять различными устройствами, например, считывать информацию с датчиков. Он разработан с учетом протокола TCP, поэтому он работает существенно быстрее чем протоколы подобные HTTP. К его достоинствам можно отнести крайне низкие требования к ресурсам, малый объемом используемой памяти устройств и встроенный устойчивый (робастный) протокол обеспечения безопасности. Благодаря этим достоинствам протокол MQTT получил широкое распространение в современном мире.

Структурная схема работы протокола MQTT показана на следующем рисунке.

Структурная схема работы протокола MQTT

Кратко рассмотрим основные функции «участников» протокола MQTT.

MQTT клиент (Client)

MQTT клиент – это любое устройство (к примеру, микроконтроллер или сервер), которое выполняет функции MQTT и взаимодействует с центральным сервером, который в данном случае называют брокером (“broker”). Брокер занимается обработкой процессов обмена данными между подключенными устройствами.

MQTT издатель (Publisher)

Клиенты, которые осуществляют передачу данных (например, датчики) называются издателями (publisher). Издатели публикуют свою информацию в определенной теме. Тема (topic) – это путь, по которому осуществляет публикация сообщений и подписка на них. После того как издатели опубликовали свою информацию в определенных темах, брокер осуществляет передачу этой информации (сообщений) подписчикам (клиентам), которые подписались на получение информации по этим темам.

MQTT подписчик (Subscriber)

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

Брокер Eclipse Mosquitto

Eclipse Mosquitto – это легковесный MQTT брокер с открытым исходным кодом, удобный для применения в различных проектах интернета вещей. Протокол MQTT обеспечивает низко затратную передачу информации в соответствии с моделью издатель/подписчик. Более подробно про принципы работы данного брокера вы можете прочитать на его официальном веб-сайте.

Установка брокера Eclipse Mosquitto

Для того чтобы иметь возможность обмена данными с брокером его нужно сначала установить. В данном проекте мы будем использовать приложение Android для публикации и подписки информации от брокера. Для установки данного брокера выполните следующую последовательность шагов.

Шаг 1. Сначала скачайте приложение “MQTT client”, доступное в магазине Google Play Store/App Store и установите его. Главный экран данного приложения выглядит следующим образом.

Главный экран приложения MQTT client

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

Добавление нового брокера в приложении MQTT client

Шаг 3. Далее нам необходимо настроить работу брокера. Сначала выверите опцию “Enabled”. После этого заполните следующие поля.

Nick Name: заполните по своему желанию.
Host: mqtt.eclipse.org
Port: 1883
Client ID: введите ID по своему усмотрению.

После успешного заполнения этих полей нажмите на кнопку save чтобы сохранить настройки брокера.

Процесс добавления нового брокера в приложении MQTT client завершен

На этом процесс настройки приложения будет завершен.

Необходимые компоненты

  1. NodeMCU ESP8266 (купить на AliExpress).
  2. Светодиод (купить на AliExpress).
  3. Макетная плата.
  4. Кнопка.
  5. Соединительные провода.
  6. Кабель для программирования.

Схема проекта

Схема MQTT клиента на основе платы NodeMCU ESP8266 представлена на следующем рисунке.

Схема MQTT клиента на основе платы NodeMCU ESP8266Объяснение программы для NodeMCU ESP8266

В коде нашей программы мы будем осуществлять взаимосвязь между MQTT брокером и платой для NodeMCU ESP8266.

Если вы будете загружать программу в NodeMCU ESP8266 в первый раз, то сначала вам нужно будет установить Arduino IDE. После ее установки откройте в ней пункт меню File–>Preferences–>Settings.

Выбор в Arduino IDE пункта меню File–>Preferences–>Settings

Далее скопируйте ниже приведенный URL и вставьте его в поле the ‘Additional Board Manager URL’, после чего нажмите ‘Ok’.

URL: https://arduino.esp8266.com/stable/package_esp8266com_index.json

Добавление поддержки модуля ESP8266 в Arduino IDE

Затем откройте пункт меню Tools > Board > Boards Manager. В этом окне наберите в строке поиска ESP8266 и нажмите enter. После этого выберите самую последнюю версию из отображенного списка и нажмите install (установить).

Установка компонентов для работы с модулем ESP8266

Затем, когда процесс установки необходимых компонентов будет завершен, выберите в Arduino IDE пункт меню Tools ->Board -> и в нем выберите NodeMCU 1.0 (ESP-12E Module). После этого вы сможете программировать вашу плату NodeMCU ESP8266 с помощью Arduino IDE.

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

Первым делом в программе подключим используемые библиотеки: “ESP8266WiFi.h” для использования модуля ESP8266 и “PubSubClient.h” для работы с протоколом MQTT.

Библиотека для работы с модулем ESP8266 встроена в Arduino IDE, а библиотеку PubSubClient необходимо скачать с репозитория GitHub.

#include <ESP8266WiFi.h>

#include <PubSubClient.h>

Далее в коде программы укажите имя и пароль для своей сети Wi-Fi. В нашем случае это “admin” и “12345678”.

const char* ssid = «admin»;

const char* password =  «12345678»;

Далее нам необходимо сконфигурировать сервер MQTT. В нашем проекте мы используем сервер Eclipse MQTT, поэтому адресом сервера у нас является “mqtt.eclipse.org”. Но вместо Eclipse вы можете использовать и других брокеров, например, Mosquitto, Adafruit. Тогда для них в программе нужно будет соответствующим образом изменить адрес сервера и номер порта.

const char* mqtt_server = «mqtt.eclipse.org»;

const int mqtt_port = 1883;

Далее создадим объекты классов WiFiClient и PubSubClient.

WiFiClient espClient;

PubSubClient client(espClient);

Далее, в функции setup(), мы будем вызывать функцию WiFi.begin() чтобы подключить наш модуль ESP8266 к точке доступа WiFi.

WiFi.begin(ssid, password);

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

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

    delay(500);

    Serial.println(«Connecting to WiFi..»);

  }

  Serial.print(«Connected to WiFi :»);

  Serial.println(WiFi.SSID());

После этого нам необходимо создать (инициализировать) брокера. Для этого мы будем использовать функцию setServer, в ней два аргумента, которые мы определили ранее. Теперь, если вы хотите получать сообщения с сервера, то необходимо использовать функцию setCallback(callback).

client.setServer(mqtt_server, mqtt_port);

client.setCallback(MQTTcallback);

После этого мы будем использовать функцию client.connect(«ESP8266») чтобы соединиться с клиентом ESP8266.

if (client.connect(«ESP8266»))

    {

      Serial.println(«connected»);

    }

    else

    {

      Serial.print(«failed with state «)

      Serial.println(client.state());

      delay(2000);

    }

Затем мы будем использовать встроенную в протокол MQTT функцию client.subscribe(), которая используется для подписки на определенную тему. В нашем проекте мы в качестве имени подписчика использовали “esp/test”.

client.subscribe(«esp/test»);

Далее мы будем вызывать функцию MQTTcallback чтобы проверить есть ли для нас какая либо обновленная информация или нет. Если новые данные доступны, эта функция обрабатывает принимаемые данные и печатает сообщение в окне монитора последовательной связи, в котором будут указаны текст сообщения и имя темы (topic name), из которого это сообщение было принято.

После этого мы преобразуем данное сообщение в строку, чтобы его можно было сравнить с заранее определенными сообщениями и на основании результатов сравнения выполнить определенные действия. В данном проекте мы будем включать/выключать (ON/OFF) светодиод с помощью MQTT команд.

for (int i = 0; i < length; i++)

  {

    message = message + (char)payload[i];

  }

  Serial.print(message);

  if (message == «on»)

  {

    digitalWrite(LED, HIGH);

  }

Далее, мы будем публиковать сообщение в теме – для этого мы будем использовать функцию client.publish(). В нашем проекте мы будем проверять состояние кнопки, если кнопка нажата, то сообщение будет публиковаться в теме “esp/test1”.

if(digitalRead(D1)==0))

  {

  client.publish(«esp/test1», «Hello from ESP8266»);

  }

  else;

client.loop();

Тестирование работы проекта

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

Откройте приложение MQTT и убедитесь в том, что ваш смартфон имеет соединению с сетью Интернет. Точка доступа WiFi, к которой подключается наша NodeMCU, также должна иметь соединение с сетью Интернет. Когда все соединения установлены, мы должны передать строку “Hello from ESP8266” с нашего модуля ESP8266, которая должна отобразиться в Android, после этого мы должны получить подтверждение приема. Затем мы будем передавать из Android приложения строку, которая будет включать светодиод, подключенный к NodeMCU.

Тестирование работы проекта

Шаг 1. Подписка на тему.

Нажмите на сохраненную опцию MQTT в приложении, которую мы настроили ранее. После этого у вас на экране должно появиться всплывающее окно, в котором будет кнопка “Subscribe to a Topic”. В программе мы дали теме название “esp/test1”. Поэтому и в Android приложении нам необходимо написать “esp/test1”.

Нажмите на Subscribe (подписаться), после чего вы увидите экран, показанный на рисунке ниже. На нем будет написано что не принято никакого сообщения (“No message received”) из темы, на которую вы подписаны.

Сообщения для нас отсутствуют

После этого нажмите на кнопку, которая подключена к NodeMCU в нашем проекте. После этого сообщение “Hello from ESP8266” будет опубликовано в нашей теме и на экране вы увидите извещение о том, что сообщение было принято.

Получено сообщение “Hello from ESP8266”

Шаг 2. Публикация сообщения в теме.

Для этого нажмите на кнопку вверх (UP ARROW button) в Android приложении, после чего вы увидите следующий экран.

Публикация сообщения в теме

После этого в поле ввода названия темы напишите “esp/test”, а в поле ввода текста сообщения — “on” или “off” чтобы включить или выключить светодиод.

Исходный код программы (скетча)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

#include <ESP8266WiFi.h>

#include <PubSubClient.h>

#define LED D0

const char* ssid = «admin»;

const char* password =  «12345678»;

const char* mqtt_server = «mqtt.eclipse.org»;

const int mqtt_port = 1883;

WiFiClient espClient;

PubSubClient client(espClient);

void setup()

{

  pinMode(LED, OUTPUT);

  pinMode(D1,INPUT_PULLUP);

  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)

  {

    delay(500);

    Serial.println(«Connecting to WiFi..»);

  }

  Serial.print(«Connected to WiFi :»);

  Serial.println(WiFi.SSID());

  client.setServer(mqtt_server, mqtt_port);

  client.setCallback(MQTTcallback);

  while (!client.connected())

  {

    Serial.println(«Connecting to MQTT…»);

    if (client.connect(«ESP8266»))

    {

      Serial.println(«connected»);

    }

    else

    {

      Serial.print(«failed with state «);

      Serial.println(client.state());

      delay(2000);

    }

  }

  client.subscribe(«esp/test»);

}

void MQTTcallback(char* topic, byte* payload, unsigned int length)

{

  Serial.print(«Message received in topic: «);

  Serial.println(topic);

  Serial.print(«Message:»);

  String message;

  for (int i = 0; i < length; i++)

  {

    message = message + (char)payload[i];

  }

  Serial.print(message);

  if (message == «on»)

  {

    digitalWrite(LED, HIGH);

  }

  else if (message == «off»)

  {

    digitalWrite(LED, LOW);

  }

  Serial.println();

  Serial.println(«————————«);

}

void loop()

{

  if(digitalRead(D1)==0)

  {

  client.publish(«esp/test1», «Hello from ESP8266»);

  delay(1000);

  }

  else;

  client.loop();

}

Видео, демонстрирующее работу проекта

Загрузка…

12 355 просмотров

Добрый день, уважаемый читатель!

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

Повторив этот проект вы можете сделать с помощью микроконтроллера ESP8266, WiFi, MQTT и некоторой доли творчества:

  • Дистанционный контроль и управление вашими устройствам, причем не только в локальной сетке, но и из любой точки планеты, где есть интернет
  • Удаленное управление реле и нагрузкой, например включение и отключение освещения или котла
  • Обратный контроль состояния нагрузки с устройства – получило ли оно вашу команду или нет
  • Удаленный мониторинг логических состояний на входах микроконтроллера, что позволяет создать простенькую охранную систему например
  • Удаленный мониторинг температуры и влажности

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

Вам понадобятся:

  • Микроконтроллер ESP8266: Node Mcu, ESP01 … ESP-12 и иже с ними
  • WiFi роутер и доступ к сети интернет
  • Любой публичный MQTT брокер
  • VSCode + PlatformIO. Ну или Arduino IDE – в ней в принципе все делается абсолютно также, только не так удобно.

В данной статье я подробно и по шагам расскажу как:

  1. Создать Arduino – проект для ESP8266 в более продвинутой и удобной среде разработки Visual Studio Code + PlatformIO.
  2. Подключить ESP8266 к вашей сети WiFi.
  3. Подключить необходимые библиотеки к проекту.
  4. Получить точное текущее время с NTP-сервера в интернете.
  5. Подключиться к MQTT-брокеру, в том числе и по TLS/SSL-протоколу
  6. Создать устройство для дистанционного управления реле через MQTT
  7. Опубликовать состояние цифровых входов (например нажатие на кнопку) на MQTT-брокере
  8. Прочитать и опубликовать температуру с датчика температуры и влажности, например DHT22

Осторожно – многа букав…

В конце статьи, как обычно, есть ссылка на готовый проект на GitHub (вам останется только изменить данные для подключения на свои), так что если Вам лень читать – листайте в конец статьи.


Подготовка среды разработки

Если вы заглянете в демо-проекты kotyara12/arduino, то, наверное, заметите, что в архиве нет никаких файлов .ino, а есть main.cpp и platformio.ini. Это означает, что проекты собраны не в классической Arduino IDE, а в Visual Studio Code + PlatformIO.

Конечно, вы можете легко перенести код из main.cpp в классическую Arduino IDE. Но я не буду рассказывать, как это сделать, вот такой уж я вредный. Вместо этого я расскажу, как сделать все в ненавистной для многих IDE Visual Studio Code и плагине к ней PlatformIO. А перенести в Arduino IDE вы и без меня смогёте. 

Итак, для начала нам понадобится собственно статья разработки. Как установить эту связку я уже рассказывал на канале и на сайте, повторяться не стоит. Стоит только отметить, что на этапе 8. Устанавливаем платформы… нужно поставить Espressif 8266:

Для этого:

  1. Нажмите “домик” PIO Home на нижней панели VS Code или значок PlatformIO на левой боковой панели, а затем выберите в списке Open.
  2. Перейдите на вкладку “Platforms
  3. В строке поиска введите 8266 и нажмите Enter
  4. Поиск выдаст единственный результат – Espressif 8266, нажмите кнопку Install (у меня это уже Uninstall).
  5. Подождите пару минут…

Создаем проект

Вновь возвращаемся на страницу PIO Home (“домик” PIO Home на нижней панели VS Code или значок PlatformIO на левой боковой панели, а затем выберите в списке Open). Но на этот раз нажмите New Project:

Выбираем название проекта (Name), плату (Board) и Framework Arduino, и нажимаем Finish:

Затем придется немного подождать, пока PlatformIO создаст необходимые файлы и папки…


Каталоги проекта

PlatformIO создаст в выбранном каталоге несколько подкаталогов (подпапок)

  • .pio\ – папка для “служебного пользования” PlatformIO, здесь будут создаваться файлы, здесь можно будет найти скомпилированный бинарник для загрузки в устройство “вручную”.
  • .vscode\ – ещё одна папка для “служебного пользования” PlatformIO, здесь хранятся настройки VSCode. Здесь есть что можно поправить, в некоторых случаях.
  • include\ – этот каталог предназначен для заголовочных файлов проекта. Надеюсь, вы знаете, что это такое 😉.
  • lib\ – сюда можно поместить локальные библиотеки проекта. “Локальные” в данном случае я понимаю как использующиеся только в данном проекте, но не в других. Сюда можно поместить части проекта, отвечающие за ту или иную функциональность проекта. Я, например, помещаю сюда файлы “прикладных” задач проекта, то есть конкретно ту автоматизацию, которую и будет выполнять данное устройство. Если у Вас есть библиотеки, которые используются сразу в нескольких ваших проектах, их лучше поместить в каталог “вне” каталога проекта для совместного использования. Я знаю, есть любители кидать в каждый проектов свои “локальные” копии общих библиотек, но я категорически не поддерживаю подобную практику, так как это приводит к огромным проблемам при дальнейшем обновлении кода.
  • src\ – здесь, собственно, и находится исходники вашего проекта. У меня, обычно, здесь только файл main.c,которые только запускает все нужные задачи и сервисы. Но ничто не мешает накидать сюда прикладных файлов, игнорируя каталог lib (см. выше), но тогда заголовочные файлы прикладных задач вам придется положить в папку include.
  • test\ – здесь будут ваши автоматизированные тесты вашего же кода, когда вы их создадите.

Кроме этого, в каталоге проекта появится и несколько файлов:

  • .gitignore – файл, в котором вы можете указать, какие из файлов не нужно загружать в GitHub.
  • platformio.ini – файл настроек проекта PlatformIO. Именно по этому файлу VSCode “понимает”, что это проект PlatformIO, а не какой-либо ещё. Этот файл будет открыт по умолчанию, давайте его сразу и рассмотрим.

Настройка проекта

platformio.ini – основной файл параметров проекта на PlatformIO в стандартном формате, придуманном ещё во времена Windows 3.1. В нем хранятся все данные о проекте – платы, порты, скорости, фильтры, скрипты и много чего ещё. Таким образом, открывая очередной проект, вам не потребуется заново выбирать плату и её параметры, как это делается в Arduino IDE.

В новом проекте он выглядит просто и незамысловато:

Совет: Если нажать на ссылку https://docs.platformio.org/page/projectconf.html в комментариях к файлу, то вы попадете в справочную систему PlatformIO, где вы сможете узнать обо всех доступных опциях. Здесь я упомяну только некоторые из них, которыми я чаще всего пользуюсь.

Единственная пока секция [env:nodemcuv2] задает параметры компиляции проекта:

  • platform = espressif8266 указывает, что для компиляции проекта будем использовать платформу espressif8266
  • board = nodemcuv2 определяет, что проект будет собран для платы Node MCU v2 (ESP-12F)
  • framework = arduino ну и собственно здесь мы должны указать, какой фреймворк будем использовать

Файл platformio.ini позволяет указать сразу несколько таких секций. Например, вы можете собирать одновременно один и тот же проект сразу для нескольких разных плат одновременно. Примеры смотрите в справочной системе https://docs.platformio.org/page/projectconf.html

Кроме секции [env:nodemcuv2] вы можете создать отдельную секцию [env] (то есть без указания целевой платформы) – в этом случае параметры этой секции будут считаться “общими” для всех указанных целей.

Скорость COM-порта

Что здесь стоит сразу же добавить? Во первых, нужно указать скорость обмена данными через USB / COM-порт в двух режимах: режиме монитора и режиме загрузки прошивки в плату. Номер COM-порта можно не указывать – если вы используете только одно подключение (одну плату), то автовыбор порта делает свою работу замечательно.

По умолчанию для большинства проектов Arduino используется скорость 9600 бод, поэтому в опции monitor_speed ставим цифру 9600. А вот для загрузки прошивки upload_speed можно выбрать скорость и повыше (чтобы снизить время прошивки); ESP8266 прошиваются нормально на скорости 921600 бод.

Фильтры COM-монитора.

Следующее, что можно поправить – настроить фильтры для монитора COM-порта. Доступны такие фильтры (подробнее смотри здесь):

  • default – удалить типичные коды управления терминалом из ввода
  • colorize – применение разных цветов для разного типа сообщений (хм, на самом деле это не работает)
  • debug – выводить и отправленное и полученное
  • direct – пересылать все данные без обработок, нужен для вывода цветных отладочных логов (ошибки – красным, предупреждения – желтым и т.д.)
  • hexlify – печать данных в шестнадцатеричном представлении для каждого символа
  • log2file – записывать данные в файл «platformio-device-monitor-%date%.log», расположенный в текущем рабочем каталоге
  • nocontrol – удалить все контрольные коды, в т.ч. CR+LF
  • printable – показать десятичный код для всех символов, отличных от ASCII, и заменить большинство управляющих кодов
  • time – добавить временную метку с миллисекундами для каждой новой строки (но это будет временная метка компьютера, а не самого устройства)
  • send_on_enter – отправить текст на устройство на ENTER
  • esp8266_exception_decoder – декодер исключений Espressif 8266, удобно, но уж очень медленно, я не использую
  • esp32_exception_decoder – декодер исключений то же самое, но для Espressif 32

Желаемые фильтры можно перечислить либо через запятую, либо в каждой строке, как вам удобнее (мне – в каждой строке, так как в таким случае их очень удобно “комментарить”). Сделать это можно в секции [env:nodemcuv2] для конкретного устройства, либо в секции [env] сразу для всех устройств, сколько бы секций вы не создали.

Я пользуюсь в основном direct и log2file – иногда очень записывать протоколы вывода в файл для последующего анализа.

На этом пока оставим этот файл, но вкладку редактора закрывать ещё рано, он нам ещё понадобится. Не забудьте сохранить изменения с помощью Ctrl+S.


Редактор кода

Ок, теперь можно начинать программировать. Для начала создадим подключение к точке доступа WiFi. Откройте главный файл проекта – main.cpp, он находится в папке src. Как видите, он ничем не отличается от типичного шаблона скетча в Arduino IDE:

Первое, что потребуется сделать – настроить COM-порт для вывода отладочных сообщений:

Цифры должны соответствовать тому, что вы указали ранее в platformio.ini. Пустой Serial.println(); нужен просто для того, чтобы перевести указатель на новую строку, так как при старте MCU в порт часто попадает мусор.


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

  • Добавьте #include <ESP8266WiFi.h> в начало файла, чтобы подключить стандартную библиотеку ESP8266WiFi к проекту.
  • Добавьте пару строк const char*, в которых укажите имя вашей сети (SSID) и пароль подключения к ней (да, да, в открытом виде, но вы же собираетесь публиковать это где попало)

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

Способы подключения к WiFi

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

1. Можно запихнуть весь код подключения к WiFi в функцию setup(). Так часто делается в примерах для esp8266:

На первый взгляд, всё это очень просто и хорошо.

Можно заподозрить, что подключение к WiFi осуществляется только один раз. То есть при любом отключении или перезагрузке роутера устройство не сможет вновь подключиться к сети. На самом деле это не так. Дело в том, что класс WiFi (с которым мы работаем) имеет “свойство” AutoReconnect (на самом деле физически его нет, но так проще для понимания) и оно по умолчанию включено. Управлять этим “свойством” можно с помощью методов getAutoReconnect() и setAutoReconnect(bool). То есть наше устройство должно автоматически попытаться восстановить подключение при его потере.

Но как мы узнаем о том, что подключение было потеряно и вновь установлено? Ведь после переподключения нам потребуется вновь подключиться к MQTT брокеру и т.д. и т.п. Для того, что бы отловить изменения состояния WiFi подключения можно предложить как минимум два способа:

  • правильный способ – создать и зарегистрировать функции обратного вызова (callbacks) для событий onStationModeDisconnected (соединение потеряно) и onStationModeGotIP (получен IP-адрес, это финальная стадия подключения). Но функции обратного вызова вызывают затруднение у новичков, поэтому мы пока не будем рассматривать этот способ (если будет необходимо – пишите в комментариях)
  • простой способ – просто проверять состояние в каждой итерации главного цикла, чтобы предпринять необходимые действия. Например так:

И это действительно работает, хотя и не очень надёжно. Почему-то этот алгоритм “пропускает” некоторые отключения и восстановления от WiFi (и дело тут не в большой задержке). Зато это очень просто.

Но если вдуматься – такой подход имеет очень серьезный изъян. Если на момент запуска подключение невозможно установить по любой причине, то устройство никогда не выйдет на главный цикл и “зависнет” на ожидании подключения. И даже принудительный выход из цикла ожидания ничего толкового не дает – ведь подключение так и не было установлено. Лично для меня это категорически неприемлемо. И я никому не посоветую так делать. Так что же делать?

2. Перенесем подключение к сети в цикл loop(). Для этого просто каждый раз проверяем состояние подключения, и если оно отсутствует, “вручную” попытаемся восстановить его.

В этом случае в каждом цикле loop() мы будем точно знать, есть ли подключение к сети, и, в случае необходимости, сможем легко предпринять дополнительные действия без callback-ов, например заново переподключиться к MQTT-брокеру.

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

Догадались? Нет? Дело в том, что если нет подключения к WiFi, ваша программа никогда не выйдет из цикла, обведенного красным. А, следовательно, программа не выполнит что-то ещё, пока нет подключения к сети. Другими словами, температура не будет измерена, реле не будет выключено и т.д. Допустим в процессе работы вы включили насос, а затем… сгорел роутер, досадно, обидно, но… насос останется работать, пока вы не выключите устройство вместе с насосом.

Это очень опасный момент, когда вы доверяете своему устройству управление потенциально опасными устройствами – котлом, поливом и т.д.

Что же делать, как же быть? Есть два пути решения этой проблемы.

3. Принудительно ограничить время работы цикла ожидания подключения.

Всё тоже самое, но цикл ожидания немного изменим.

Вуаля, теперь ожидание не будет превышать 30 секунд. А за 30 секунд, надеюсь, ничего страшного не произойдет. Хотя это и “костыль”, но задача решена.

4. Переделать всю работу с WiFi на систему по событиям. Самый “правильный” вариант. В этом случае вам потребуется создать кучку функций обратного вызова (callbacks), которые будут асинхронно вызываться в нужные вам моменты времени: при установке соединения и при потере оного. И вы сможете сделать всё, что требуется в нужный момент времени без каких-либо циклов ожидания. В своих проектах я использую эту технику. Но в виду некоторой сложности данного подхода для начинающих (а эта графомания всё-таки для начинающих ардуинщиков), пока не будем рассматривать этот вариант. А если у вас есть некоторый опыт, вы сможете соорудить такое и без моей помощи.

Функция проверки подключения к WiFi

Теперь уже таки можно написать достаточно простую функцию подключения к AP. Лично я избегаю включать “чистый” код в setup() и loop(), а выношу все специфичные операции в отдельные функции, так зачастую удобнее. Например так может выглядеть функция, которая проверяет и восстанавливает подключение:

Тогда основные функции setup() и loop() будут выглядеть так:

Компилируем build, загружаем в плату upload. Плата должна корректно отрабатывать все отключения от WiFi сети.

В первый раз подключение успешно установлено за ~4сек, затем была перезагрузка роутера, вначале за 30 секунд установить соединение не удалось, был выход из цикла ожидания, затем удалось


MQTT протокол

Для начала давайте чуть-чуть об теории MQTT протокола. MQTT — это протокол обмена сообщениями по шаблону издатель-подписчик (pub/sub). Первоначальную версию в 1999 году опубликовали Энди Стэнфорд-Кларк из IBM и Арлен Ниппер из Cirrus Link. Они рассматривали MQTT как способ поддержания связи между машинами в сетях с ограниченной пропускной способностью или непредсказуемой связью.

Система связи, построенная на MQTT, состоит из клиентов-издателей publisher, сервера-брокера broker и одного или нескольких клиентов-подписчиков subsriber. В обычных условиях клиенты не могут общаться напрямую друг с другом, и весь обмен данными происходит только через какого-либо брокера. Издатель и подписчик ничего не знают друг о друге, но должны знать о том, по какому адресу / каналу topic передавать или ждать данные. Одно и то же устройство может быть одновременно и издателем и подписчиком (но на разные топики). Это сильно облегчает задачу передачи данных из-за NAT-а (то есть из обычных локальных сетей).

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

Кратенько рассмотрим термины, которые нам просто необходимо знать для работы с MQTT протоколом.

Broker

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

Message

Сообщения (message) содержат в себе информацию, которую один участник сети на базе протокола MQTT (издатель) хочет передать другим (своим подписчикам). В качестве сообщения может быть всё, что угодно – текст, значения температуры, состояние датчиков и т.д. и т.п. И даже двоичные данные.

Publish

Это процесс передачи сообщения брокеру. То есть простым языком, я подошел к брокеру и сказал “кнопка нажата“. Брокер теоретически должен услышать это сообщение, записать (при необходимости) и передать дальше подписчикам. Почему теоретически? Потому что есть особенности протокола (QoS), которые мы разберем чуть ниже. Пока берем за данность, что я сказал что-то брокеру используя механизм publish и он это услышал и обработал как положено.

Subscribe

Сообщение было отправлено брокеру, но как его получить клиенту? Правильно – подписаться. Тогда сервер передаст вам все поступающие сообщения на интересующую вас тему. Чтобы определить на какую тему мы хотим получать эти сообщения, используется механизм Topic

Topic

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

Издатель печатает несколько газет (топиков) – “Аргументы и факты“, “Ведомости” и “Гудок“. Подписчик может подписаться на один из указанных топиков или несколько. А может подписаться на журнал “Новости адруинщика” (почему нет?). Почтальон (брокер) принесет подписчику только те газеты (сообщения), которые напечатал (отправил) издатель, и на которые подписался подписчик. То есть вы должны заранее знать, на что подписываться.

Топик может быть простым (например temperature) и состоящим из нескольких отдельных частей, разделенных слешем “/” (например home/kitchen/temperature). Для чего нужны эти “/“? А для того, дабы выстроить определенную иерархию данных. По аналогии с приведенным выше примером с почтой это могут быть топики “Газеты/Аргументы и факты” и “Журналы/Мурзилка“. Чем то это очень похоже на структуру каталогов и файлов на дисках вашего контупера.

Во-первых строках письма это даёт более простое понимание множества данных:

Но самое главное – это сильно облегчает подписку на данные путем использования подстановочных знаков wildcards. MQTT протокол предусматривает два типа wildcards при подписках на топики:

  • # (твой дом тюрьма решетка) позволяет элегантно подписаться на все субтопики одном махом
  • + (плюсик) позволяет подписаться на все топики одного уровня

Ну например: у нас есть в кухне и в спальне по два выключателя и один в гараже. Для этого мы формируем на выключателях в топики в виде:

В гостиной:

home/kitchen/switch1
home/kitchen/switch2

В спальне:

home/bedroom/switch1
home/bedroom/switch2

В гараже:

garage/switch1

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

mqttClient.subscribe("home/kitchen/switch1");
mqttClient.subscribe("home/kitchen/switch2");
mqttClient.subscribe("home/bedroom/switch1");
mqttClient.subscribe("home/bedroom/switch2");

Геморой? Не то слово! Гораздо проще и надежнее сделать это так:

mqttClient.subscribe("home/#");

Бонусом мы автоматически получаем подписку на все еще заранее неизвестные топики в доме (впрочем иногда это лишнее).

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

mqttClient.subscribe("home/+/switch1");

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

<вредный совет>Таки если вы желаете сделать себе нервы и выесть моск, создавайте топики максимально простыми и случайным образом. Это очень поможет вам в дальнейшем. Тормоза придумали трусы, а иерархию – ботаники.</вредный совет>

Служебные сообщения

В принципе есть несколько типов служебных сообщений MQTT протокола:

  • Birth Message – которое сообщает миру что “я родился и живой”
  • Last Will and Testament (LWT) которое сообщает что после этого сообщения считать меня мертвым
  • Keep Alive – которые сообщают брокеру что “я все еще живой” и стандартно посылаются каждые 60 секунд. Если брокер не получил это сообщение от клиента, то он принудительно пингует его чтобы выяснить жив ли тот, и если выясняется что он неживой, то брокер публикует за клиента LWT сообщение, чтобы все узнали что тот скончался.

Соответственно получение брокером Birth Message от устройства, переводит устройство в режим ONLINE, а после того как брокер получает от устройства LWT сообщение, либо когда сам принимает решение что тот скончался (проверив устройство на доступность), то переводит статус устройства в режим OFFLINE.

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

Retain или Retained

Протокол MQTT никоим образом не предназначен для накопления данных на сервере с последующим отображением в виде графиков. Максимум что может храниться на сервере – последнее опубликованное издателем сообщение, если оно было отправлено с флагом retain или retained (в разных версиях может называться по разному, но суть одна и та же). Это позволяет клиенту-подписчику при подключении к брокеру сразу же получить последние интересующие его данные, а не ждать когда их отправит издатель в очередной раз. Однако использование этого флага требует известной аккуратности, неверное его использование может привести к невразумительным проблемам.

QoS

QoS расшифровывается как Quality Of Service, то есть качество предоставляемой услуги. Для MQTT этот показатель отвечает за вероятность прохождения пакета между двумя точками сети. Флаг QoS может принимать следующие значения, основанные на надежности передачи сообщения:

  • 0 = не более одного раза: отправитель пересылает сообщение получателю и забывает об этом. Сообщения могут быть потеряны в канале связи или продублированы (если отправитель посчитал что пакет потерян и отправил его повторно, но первый пакет таки дошёл до получателя).
  • 1 = по крайней мере один раз: получатель подтверждает доставку сообщения. Если подтверждение не было получено, отправитель должен отправить его еще раз и так до получения подтверждения о получении. Сообщения могут дублироваться, но доставка гарантирована
  • 2 = ровно один раз: сервер обеспечивает гарантированную доставку. Сообщения поступают точно один раз без потери или дублирования. Самый медленный и машинотрудозатратный вариант, так как отправитель и получатель дополнительно обмениваются подтверждениями.

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


Подключаем сторонние библиотеки

Для работы с MQTT протоколом нам понадобится сторонняя библиотека, например PubSubClient. Существует две популярные версии данной библиотеки (хотя может быть и больше):

  • основная версия от knolleary, именно она доступна через менеджер библиотек Arduino IDE
  • альтернативная версия от lmroy, которая является ответвлением от основной версии

Чем они отличаются? В основной версии значение QoS можно указать только при подписке. А при публикации – нет! Я считаю, что это не есть хорошо (к слову, разработчики MQTT клиента для ESP-IDF считают так же, поскольку в ESP32 реализован “по второму типу”).

Лично я предпочитаю пользоваться версией от Imroy. Но в данном примере я буду использовать основную ветку. Просто потому что во 99.9999999% примеров в сети используется именно она.

Если вы работали с Arduino IDE, то, наверное, знаете, что там библиотеки можно достаточно легко и просто скачать и установить с помощью встроенного менеджера библиотек. В PlatformIO всё на первый взгляд сложнее, но на самом деле всё не так страшно, да и возможностей гораздо больше. Я настоятельно рекомендую ознакомиться с документацией по менеджеру библиотек, но для начала достаточно усвоить самые простые способы. Итак, если очень кратенько, существует несколько способов подключить сторонние библиотеки:

  • Локальные библиотеки проекта. Для этого потребуется скачать архив библиотеки с GitHub и распаковать его содержимое в каталог \lib вашего проекта. В этом случае PIO автоматически “подхватит” эти библиотеки и ничего нигде настраивать не нужно. Но я настоятельно не рекомендую так делать. Почему? Потому что вам придется хранить отдельную копию библиотек для каждого из своих проектов. Это занимает дополнительное место на дисках, но самое главное – если необходимо обновить библиотеки, вам придется сделать это в каждом из своих проектах. Ручками. Как всегда – за свою лень в начале мы заплатим многократным трудом впоследствии, чудес не бывает.
  • Общие локальные библиотеки. Этот способ очень удобно использовать, когда одни и те же библиотеки используется сразу в нескольких проектах. Допустим, все общие библиотеки мы будем складывать в папку C:\PlatformIO\libs. Создаем такой каталог, вручную скачиваем архив с GitHub, и вручную распаковываем содержимое в только что созданный каталог. Всё как и в предыдущем случае, только целевая папка изменилась. Теперь нам нужно “подключить” каталог C:\PlatformIO\libs к нашему проекту через параметр lib_extra_dirs. Для этого потребуется указать только каталог lib_extra_dirs = C:\PlatformIO\libs, все вложенные библиотеки не более одного уровня вложенности “подхватятся” автоматически. Открываем файл platformio.ini (помните, я вас предупреждал не закрывать его) и добавляем такие строчки:

Этот способ идеален для “своих” общих библиотек, но для “чужих” лучше использовать следующий

  • Публичные библиотеки. Я настолько ленив, что мне даже лень скачивать и распаковывать библиотеки вручную. Да ещё потом и обновлять их придется в случае чего. Данунафиг. PlatformIO предоставляет очень удобный способ подключения публичных библиотек напрямую с GitHub или из каталога библиотек PlatfortmIO. Просто укажите прямые ссылки на них в параметре lib_deps – и вам не придется следить за скачиванием и обновлением этих библиотек – PlatformIO всё возьмет на себя:

Вот так гораздо лучше

Просто? Очень! Нужно только знать, где найти эту самую нужную библиотеку. На гитхабе конечно! Ну да гуголь вам в помощь.

Теперь можно добавить библиотеку в исходный код нашего проекта: #include <PubSubClient.h> и начинать писать код подключения к брокеру.


Подключение к MQTT-серверу без шифрования

Для начала давайте попробуем подключиться к брокеру в открытом виде, без TLS-шифрования. Я использую такой вариант только для подключения к своему локальному брокеру, который находится в моей локальной сети, относительно защищенной от интернета NAT-ом. Но для примера вполне сойдет.

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

  • адрес сервера в сети
  • порт сервера (обычно 1883, но вполне может быть и другой)
  • имя пользователя
  • пароль пользователя

Все это вы должны заранее узнать на том сервере, к которому вы собираетесь подключиться. Я достаточно давно уже пользуюсь wqtt.ru (он платный, но стоит относительно недорого и без существенных ограничений на текущий момент !!!не реклама!!!). В данном примере нам понадобятся:

Порт берем пока самый простой, первый по списку. Не перепутайте, иначе ничего не выйдет. Заведем их в программу в виде констант:

Не забудьте подставить свои значения

Далее, нам потребуется завести две глобальные переменные:

Первая – это экземпляр WiFi клиента, который требуется для работы MQTT-клиента. Вторая – MQTT-клиент собственной персоной.

Далее пишем такую функцию:

По аналогии с предыдущим случаем, здесь мы проверяем, есть ли подключение к брокеру; и если нет – указываем параметры сервера и подключаемся. Всё предельно просто.

Во многих примерах ClientID генерируется “на лету” из части MAC-адреса устройства, но я не вижу необходимости тратить на это свободную память кучи. Ибо нефиг. Только не забывайте менять его от проекта к проекту (он должен быть уникальным).

Затем дополняем основной цикл loop() следующими строками:

Если соединение с брокером есть, в каждой итерации loop() обязательно вызываем mqttClient.loop(), дабы отправлять сообщения на сервер и получать входящие сообщения от сервера.

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

Прелэстно, прелэстно… К MQTT брокеру мы подключились, но пока мы не научили наше устройство что-либо отправлять и получать с него.


Добавляем LWT и статус устройства

Добавим топик, который позволит нам отслеживать состояние устройства – в сети оно или нет. Для этого воспользуемся функционалом, предоставляемым нам MQTT протоколом, который называется LWT (Last Will and Testament, «последняя воля и завещание») для уведомления заинтересованных сторон об отключении клиента.

  • Пусть для примера топик статуса будет “demo/status” (хотя на практике тут должно бы быть что-то вроде “home/boiler/status” или “garage/status“).
  • В рабочем состоянии в этом топике должно быть “online“, когда устройство выключено или связи нет, то “offline” (хотя вполне можно использовать и просто “1” и “0“).
  • Для такого типа информации оптимальнее всего использовать Qos = 1 (так как повторное получение никому не помешает), а вот retain лучше поставить 1 или true.

Определим константы:

Совет: Я всегда стараюсь использовать константы, а не прописываю постоянные значения прямо “в коде”. Ибо прямое написание ведет к многократным потерям в производительности труда при модификациях кода и многочисленным ошибкам. Надо ли объяснять почему так? Хотя для компилятора, конечно же, всё равно. Это очень плохая практика, для меня это практически табу. Если вы будете так делать, то вы сами себе злобный Буратино.

Осталось немного модифицировать код подключения к брокеру:

То есть сразу же при подключении мы отправляем на сервер параметры LWT сообщения. Но сразу после подключения в этом топике будет пусто, так как “offline” там появится только после того, как устройство будет отключено. Дабы исправить это недоразумение, мы сразу же публикуем в этот же самый топик состояние “online“. Вот и всё, что требовалось.


Добавим SSL / TLS

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

К чему это я? Давайте модифицируем подключение к брокеру с использованием SSL или TLS шифрования. Это дает не только полное шифрование всех передаваемых данных, но и гарантирует, что вы подключаетесь к настоящему серверу, а не подставному, созданному специально для перехвата (хотя вряд ли кто-то когда либо будет создавать сервер-перехватчик специально для вас, если только вы не подпольный миллионер на пенсии).

Я не буду углубляться в теорию TLS-соединений. В сети найдется немало материалов на эту тему, а я не являюсь специалистом в криптографии. Одно я знаю наверняка – для того чтобы установить зашифрованное TLS-соединение, вашему устройству как минимум понадобится сертификат сервера (впрочем не самого сервера, но об этом ниже). Сертификат сервера несет в себе несколько функций:

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

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

Все сертификаты сайтов выданы и подписаны какими-либо центрами сертификации, которые так же имеют свой сертификат. Центр сертификации не обязательно должен быть один, их может быть несколько в цепочке, подписанных один за другим. Например: сертификат для сайта wqtt.ru был выдан ЦС R3, а тому, в свою очередь, выдал сертификат ISRG Root X1.

Первый сертификат в списке называется “корневым”. Если корневому сертификату мы доверяем целиком и полностью, то остальные “вложенные” сертификаты мы сможем проверить “по цепочке”. Поэтому для проверки сертификата любого сайта мы должны иметь “всего лишь” список доверенных корневых сертификатов, которым мы доверяем. Это намного меньше, чем хранить сертификаты всех сайтов, но всё равно это внушительный общем информации, который просто не влезет в память микроконтроллера (впрочем в ESP-IDF имеется возможность подключить к проекту такой список, который называется tls bundle, но речь сейчас не о нём). Поэтому на Arduino обычно ограничиваются подключением с проекту одного или нескольких корневых сертификатов, в зависимости от того, к каким сайтам требуется доступ. Например для подключения к брокеру wqtt.ru нам потребуется подключить сертификат ISRG Root X1.

Но у всех сертификатов (и корневых и не только) имеется встроенная головная боль для ардуино-программистов, и называется она “срок действия сертификата”. После заранее определенного в сертификате срока он считается “испорченным”. Кроме того, сертификат может быть отозван досрочно, если есть подозрения на его компрометацию. И тогда таким “порченным” корневым сертификатом ничего проверить уже будет нельзя. И нам потребуется оперативно заменить его в прошивке. Поэтому для критичных устройств необходимо предусмотреть вариант резервного варианта подключения в открытом виде, дабы не потерять контроль над устройством. Обычно для корневых доверенных сертификатов срок действия достаточно велик, чтобы не очень беспокоиться по этому поводу, но событие в календарик добавить всё-таки стоит.

Как получить файл корневого сертификата

Итак, нам нужен файл корневого сертификата. Самое простое в windows – использовать обычный браузер, например хром. Если у вас установлен антивирус, придется его отключить на время, так как антивирусные программы подменяют сертификаты ЦС своими собственными и это ни к чему хорошему не приведет. Затем откройте нужный вам сайт и найдите замочек в адресной строке, затем кликните на этот замочек:

Затем кликните на строку “Безопасное подключение”, а затем на “Действительный сертификат”:

Откроется окошечко просмотра сертификата, где мы должны перейти на вкладку “Подробнее”, выделить в иерархии верхний сертификат и нажать “Экспорт”:

Затем просто указываем, где бы мы хотели сохранить файл и нажимаем ОК. В итоге у вас в выбранной папке должен нарисоваться новый файлик с вот таким примерно содержанием:

Это и есть то, что нам нужно!

Добавим к нашему проекту константу static const char ISRG_Root_x1[] PROGMEM = R”EOF()EOF”; а затем скопируйте содержимое сертификата и вставьте между круглых скобок. У вас должно получиться нечто вроде этого:

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

Но и это ещё не всё! Вспомните про срок действия сертификата – нам нужно время! Причем в прямом смысле слова. Если к вашему устройству подключены аппаратные часы реального времени, то можно получить время с них. Иначе нам придется получить время с SNTP-сервера. Как это сделать, я уже писал на этом сайте ранее, повторяться не буду. Здесь приведу лишь готовую функцию получения актуального времени, я добавил этот блок в wifiConnected() сразу после подключения:

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

После этого уже можно смело добавить корневой сертификат в список доверенных:

Вот теперь всё готово к TLS-соединению!

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

Ну вот и всё, этого достаточно, можно пробовать:

Как видите, это совсем не сложно. А страху-то шо было! Крип-то-гра-фия! О как!


Дистанционное управление реле

У нас всё готово для решения практических задач. Вначале давайте сделаем так называемую “умную розетку” или “умную лампочку“, а по сути “тупой пульт дистанционного управления с телефона“, гы… Хотя что нам одна лампочка или розетка. Давайте сразу три или четыре. Пощёлкаем орешки релюшками “по полной программе”.

Перво-наперво нужно определить выводы, к которым подключены наши реле, например так:

И не забыть настроить эти GPIO на режим “на выход”:

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

Я не зря добавил в конце каждого топика /control – не спешите обрезать, он нам ещё пригодится. И определим текстовые команды для этих топиков. Я обозначил здесь аж сразу два варианта – можно будет отправить в топик “1” или “on” и результат будет одинаков.

Теперь добавляем подписку на эти топики. Проще всего сделать это сразу после подключения к брокеру:

Подписаться то мы подписались, а как мы узнаем, что с сервера пришла команда на включение или выключение реле? Правильно – нужно таки создать функцию-обработчик, используя прототип std::function<void(char*, uint8_t*, unsigned int)>

Я написал такую функцию:

Вначале нам необходимо выделить полезную информацию из входящего потока байт. Для пущей надежности я добавил функции обрезки “лишних” пробелов из текстовых строк и приведение их к нижнему регистру.

Затем можно сравнить топик и данные с заранее определенными и решить как поступить в данном случае:

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

Прошиваем контроллер, проверяем работу.


Добавим обратную связь на управление реле

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

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

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

Затем добавим глобальные переменные для хранения текущих состояний реле и для хранения полученных “команд”:

После этого модифицируем наш обработчик следующим образом:

Отлично. Осталось написать код, который будет сравнивать “старое” и “новое” состояние реле и в случае необходимости щёлкать ими:

Если значение relayCommandX и relayStatusX отличаются друг от друга, то переключаем выход, к которому подключено реле и публикуем новое состояние реле в “ответный” топик.

Но это ещё не всё. Хорошо бы при подключении к MQTT серверу опубликовать текущие состояния реле, так как во время временного отключения от сервера состояния реле могло и измениться (например если у вас будет какой-либо автоматический сценарий). Это просто:

Ну и не забудьте добавить эту функцию в основной цикл:

Ещё раз всё компилируем:

А на брокере в это время:

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


Добавим публикацию состояния цифрового входа

А что, если нам нужно периодически считывать состояние входа и публиковать его состояние при изменении? Давайте попробуем…

Конечно, оптимальнее было бы использовать прерывания, но не будем пока о них. Сделаем “по простому”, выжмем из процессора все соки…

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

Я специально оставил её с номером, дабы было понятно, что таких входов вы можете сделать столько, сколько позволят свободные выводы ESP.

Ну и константы, топики и прочее, куда же без них:

Сама функция чтения не сложнее предыдущих:

Осталось вызывать её из главного цикла:

И таки да, не забудьте настроить используемый вывод:

В результат вы должны прочить следующее:

и на брокере:


Измеряем температуру и влажность в помещении

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

Его я подключил к выводу D1 или GPIO5 (кстати, отличный справочник по выводам ESP8266).

Для работы с данным сенсоров так же потребуется подключить сторонние библиотеки, например от Adafruit:

#include <Adafruit_Sensor.h>
#include <DHT.h>

Но перед этим добавим ссылки на них в файл platformio.ini

Ну а дальше просто копипастим код из примера к драйверу DHT22. Ну и добавляем код для публикации на брокере:

  • Создадим глобальную переменную для DHT22:

  • Определим необходимые топики:

  • Проинициализируем сенсор в функции setup():

  • И можно, наконец, прочитать и опубликовать данные:

  • Но вот вызывать эту функцию каждую итерацию цикла loop() не следует, поэтому добавим простейший псевдотаймер на millis():

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

А на сервере в это же самое время можно увидеть следующее:


Ссылки

Как всегда, пример можно скачать в готовом виде с GitHub: github.com/kotyara12/arduino/tree/master/arduino_eps8266_dzen

На этом пока всё, до встречи на сайте и на telegram-канале!. Если Вам понравилась статья – кликните на любое рекламное объявление, этого будет вполне достаточно для поддержки автора.

🔶 Архив статей, упорядоченный по категориям 🔶

Платы ESP8266 стали очень популярны из-за своей низкой цены и совместимостью с Arduino IDE. Для решения задач устройств «Умного дома» они просто идеальны. ESP требует только подключения питания, а сбор и отправку данных она может осуществлять по Wi-Fi через домашний роутер.

Одним из популярных открытых протоколов связи для Умного дома является MQTT. Его основа — обычные http(s) сокет соединение. Протокол по сути дела описывает лишь язык, а не способ доставки данных. MQTT требует наличие сервера — брокера, который объединяет подключенные устройства в сеть. Сами же устройства при подключении подписываются на топики в которых отсылаются данные и брокер отсылает только те топики устройству, которые ему нужны.

ESP + MQTT как основа умного дома

Получить к возможность подключиться к брокер серверу можно двумя способами: воспользоваться бесплатными MGTT брокерами или поднять свой собственный сервер (путь самурая).

Первый вариант самый простой. Из бесплатных я бы рекомендовал cloudmqtt.com. Там есть ограничения по количеству устройств и запросов, но для домашнего использования этого более чем достаточно. При регистрации выдается логин, пароль, сервер к которому нужно делать коннект, тип соединения (с/без ssl).

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

Вернемся к нашей ESP. Из этих устройств мы соберем периферию по сбору данных. Для реализации устройств удобно использовать модуль ESP8266 ESP-12. Он имеет встроенный стабилизатор на 3.2 вольта и множество свободных GPIO для подключения периферии. Цена так себе, если паяльник не вызывает страха, то можно замутить на 01/07 модулях. На али такие меньше чем за 2$ за штуку можно отхватить.

ESP + MQTT как основа умного дома

Настройка Arduino IDE

Для прошивки ESP будем использовать Arduino IDE. Просто так взять и залить скетч нельзя. Для начала нужно подготовить настроить IDE для работы с платой ESP.

Запускаем наше IDE и идем в Файл — Настройки.

ESP + MQTT как основа умного дома

Прописываем ссылку на менеджер плат http://arduino.esp8266.com/stable/package_esp8266com_index.json
Теперь выбираем Инструменты — Плата — Менеджер плат. В конце списка мы увидим esp8266 by esp8266 Community.

ESP + MQTT как основа умного дома

Выбираем последнюю версию и жмем Установить.

Esp прошивает на другой скорости чем чипы Atmega и по тому нам нужно в первую очередь изменить скорость uart. Инструменты — Upload Speed:-115200:

ESP + MQTT как основа умного дома

Не забываем задать тип платы: Инструменты — Generic ESP8266 Module.

ESP + MQTT как основа умного дома

Ну и все, наше IDE готово загружать скетч в ESP. Если вы счастливый обладатель ESP8266 ESP-12 — переключаем в режим программирования, тыкаем usb, выбираем COM порт и погнали. Если плата попроще (ESP8266-01), не беда. Подключаем GPIO0 на землю, USB-UART «свисток», подаем стабильные 3.2 вольт и наша дешевая плата тоже готова принять скетч. Распиновка подключения для 01:

ESP + MQTT как основа умного дома

Скетч для MQTT

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

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char *ssid = "xxxxxx"; // Имя роутера
const char *pass = "xxxxxx"; // Пароль роутера

const char *mqtt_server = "m13.cloudmqtt.com"; // Имя сервера MQTT
const int mqtt_port = 14483; // Порт для подключения к серверу MQTT
const char *mqtt_user = "xxxxxx"; // Логи для подключения к серверу MQTT
const char *mqtt_pass = "xxxxxx"; // Пароль для подключения к серверу MQTT

const int led = 5; // диод на плате

#define BUFFER_SIZE 100

int tm = 300;
float temp = 0;

WiFiClient wclient; 
PubSubClient client(wclient, mqtt_server, mqtt_port);

void setup() {
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();

  pinMode(led, OUTPUT);
}

// Функция отправки показаний
void refreshData() {
  if (tm == 0) {
    time = millis();
    client.publish("test/1", time);
    tm = 3000; // пауза меду отправками 3 секунды
  }
  tm--;
  
  delay(1); 
}

// Функция получения данных от сервера
void callback(const MQTT::Publish& pub)
{
  String payload = pub.payload_string();
  String topic = pub.topic();
  
  Serial.print(pub.topic()); // выводим в сериал порт название топика
  Serial.print(" => ");
  Serial.println(payload); // выводим в сериал порт значение полученных данных

  // проверяем из нужного ли нам топика пришли данные 
  if(topic == "test/2")
  {
     Serial.println("test/2 OK"); // выводим в сериал порт подтверждение, что мы получили топик test/2
  }
}

void loop() {
  // подключаемся к wi-fi
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, pass);
    
    if (WiFi.waitForConnectResult() != WL_CONNECTED) return;
    Serial.println("WiFi connected");
  }
  
  // подключаемся к MQTT серверу
  if (WiFi.status() == WL_CONNECTED) {
    if (!client.connected()) {
      Serial.print("Connecting to MQTT server ");
      Serial.print(mqtt_server);
      Serial.println("...");
      if (client.connect(MQTT::Connect("arduinoClient2").set_auth(mqtt_user, mqtt_pass))) {
        Serial.println("Connected to MQTT server ");
        client.set_callback(callback);
        // подписываемся под топики
        client.subscribe("test/1");
        client.subscribe("test/2");
      } else {
        Serial.println("Could not connect to MQTT server"); 
      }
    }
    
    if (client.connected()){
      client.loop();
      refreshData();
    }
  
  }
}

Код выполняет соединение к Wi-Fi точке доступа в квартире, через интернет соединяется к брокер серверу. Подписывается на топики test/1 и test/2. Отсылает раз в 3 секунды значение системного счетчика. В общем, ничего лишнего.

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

Настройка клиента на телефоне

Скачиваем приложение в маркетплейсе Android/iPhone — IoT MQTT Dashboard или любое аналогичное. Они все работают одинаково.

В запущенном приложении нужно задать хост, логин, пароль, порт брокер-сервера.

ESP + MQTT как основа умного дома

Далее мы попадаем на страницу подписок. Задаем наши два топика «test/1» и «test/2» — теперь мы всегда будем видеть последние присланные значения этих топиков. Это может быть температура воздуха или влажность земли в теплице. Есть поддержка передачи изображения в base64, но это уже совсем другая история.

На втором экране мы можем создать управляемые контролы: кнопки, переключатели, выбор цвета, мультиселек, поле ввода текста. Добавляем переключатели и вбиваем в них наши топики «test/1» и «test/2». Запускаем и видим как переключение топиков сразу же вываливает в консоль UART микроконтроллера ответы. Колдунство!

ESP + MQTT как основа умного дома

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

Понравилась статья? Поделить с друзьями:
  • Ручной массажер с инфракрасной лампой as 0111 инструкция по применению
  • Кофемашина bosch benvenuto classic tca 5309 инструкция на русском
  • Аскорил инструкция по применению для детей сироп инструкция по применению
  • Понятие руководства в теории управления
  • Руководство пользования пылесосом