ESP32 от Espressif. Часть 1. Определяем присутствие человека

11 июля

учёт ресурсовпотребительская электроникаавтоматизацияинтернет вещейEspressif Systemsстатьяинтегральные микросхемыбеспроводные технологии

Представляем пример быстрого развертывания IoT-проекта на основе контроллера ESP32 от Espressif и стеков технологий Mongoose OS и Cloud IoT Core.

Перед вами проект, цель которого – разработка устройства с автоматической калибровкой датчиков присутствия, вывода информации на удаленный индикатор, сбора статистики для проверки наличия людей в нескольких кабинках. В статьях этого цикла мы дадим пошаговое руководство по установке операционной системы Mongoose OS на контроллер семейства ESP32, подключению устройства к облачному сервису Google – Cloud IoT Core, сохранению и обработке данных в базе данных Firebase, передаче на устройство и на индикатор конфигурации и управляющих команд. Для определения присутствия человека используются инфракрасные дальномеры Sharp c аналоговыми выходами.

Начнем с того, почему для реализации выбраны именно эти компоненты.

Wi-Fi-контроллер ESP32

Чип ESP32 является преемником известного ESP8266 – одного из самых бюджетных Wi-Fi-контроллеров. По сравнению с предшественником, в контроллере была повышена тактовая частота до 240 МГц, добавлено второе ядро, размер ОЗУ увеличен до 520 кбайт, увеличено количество выводов GPIO (в том числе – добавлены входы 12-битного АЦП, 18 каналов), Bluetooth, возможность шифрования памяти, аппаратное криптографическое ускорение: AES, SHA-2, RSA, ECC, RNG. Для разработки был использован модуль ESP32-PICO-KIT V4, расположение выводов которого приведено на рисунке 1.

Рис. 1. Контроллер ESP32-PICO-KIT V4

Рис. 1. Контроллер ESP32-PICO-KIT V4

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

 Mongoose OS

Обычно разработка логики приложения занимает порядка 20% общего времени разработки программы. Остальное время уходит на обеспечение работы с сетью, управление устройством, OTA-обновление прошивки, обеспечение безопасности и так далее. Операционная система Mongoose OS позволяет, сосредоточившись только на логике, быстро разработать прототип приложения на Javascript (JS) (конечно, если вы предпочитаете семейство языков С/С++, то можно писать и на нем, но, возможно, это будет несколько дольше), предоставляя множество API-интерфейсов для конфигурирования, работы с таймерами, протоколом MQTT, обработчики событий, средства для доступа к аппаратным ресурсам контроллера, периферии. Кроме того, программа на Javascript в большой степени кроссплатформенная – ее можно будет загрузить и в другие контроллеры с поддержкой Mongoose OS: STM32L4, STM32F4, STM32F7, CC3220, CC3200, ESP32, ESP8266. Mongoose OS поставляется с инструментом разработки mos, позволяющим легко взаимодействовать с контроллером, загружать файлы, обновлять прошивку, осуществлять вызовы RPC-функций.

Google Cloud

Перед вами – обширная облачная экосистема, включающая в себя необходимые для реализации проекта компоненты: MQTT-брокер Cloud IoT Core, хранилища данных Firebase и Big Query, хостинг, облачные функции для реализации логики обработки сообщений, конфигурирования датчиков и многое другое. Ко всему есть готовые, хорошо документированные API, единая система контроля доступа IAM (Cloud Identity & Access Management) повышает безопасность приложения.

Датчик присутствия

В качестве датчика присутствия выбраны оптические датчики Sharp GP2Y0A710K0F с рабочим расстоянием 100…550 см (в зависимости от геометрии пространства, в котором нужно проводить измерения, датчик может быть заменен на любой аналогичный с требуемым диапазоном). В отличие от ультразвуковых дальномеров, этот датчик имеет относительно узкую апертуру, что исключает отражение от стен. Также, в отличие от пирометрических, данный датчик срабатывает, в том числе, на неподвижные объекты.

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

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

Блок датчиков состоит из ESP-32, трех аналоговых оптических дальномеров Sharp GP2Y0A710K0F и опционального датчика освещения. При изменении состояния одного из датчиков (выходе измеренного напряжения за заданные пределы или возврате в исходное состояние) в Cloud IoT Core отправляется сообщение по протоколу MQTT с текущим состоянием датчиков. В облачных функциях Firebase реализован триггер, срабатывающий при поступлении такого сообщения. По этому триггеру происходит сохранение данных в Google Sheet, Firebase, и Big Query (естественно, так много хранилищ данных в реальном проекте использовать не нужно, в этом проекте они используются в целях демонстрации).

В Firebase формируется и отправляется в Cloud IoT Core настройка для блока датчиков, которая загружается в устройство после его перезагрузки и подключения.

Схема устройства достаточно проста – нужно подключить датчики к аналоговым входам ESP32 и подать питание.

Обратите внимание на два момента: при работе Wi-Fi становится недоступен второй порт АЦП (видимо, проблема в SDK от Espressif). Многие модули с ESP32 требовательны к питанию и при использовании длинных тонких проводов и маломощных источников могут перезагружаться по Brownout.

Облачные технологии Google

Архитектура проекта на первом этапе будет аналогична проекту погодной станции (рисунок 2).

Рис. 2. Архитектура проекта

Рис. 2. Архитектура проекта

Блок датчиков публикует данные телеметрии по протоколу MQTT в Cloud IoT Core. Сервис Cloud Pub/Sub организует подписки и отправляет сообщения получателям. В Firebase функция-триггер, подписанная на сообщения, сохраняет данные во всех хранилищах.

Cloud IoT Core

Cloud IoT Core – это сервис облачной экосистемы Google Cloud Platform, в который каждое из зарегистрированных в нем устройств может отправлять данные датчиков. Отправка данных называется публикацией события телеметрии. В принципе, за каждое опубликованное событие Google может захотеть получить оплату, но для небольших проектов, обычно, достаточно бесплатной квоты. Кроме того, при подключении на счет кладется $300 бонусов, которые можно использовать в первый год работы.

MQTT

Публикация сообщений осуществляется по протоколу MQTT. Сообщение телеметрии публикуется устройством (в терминологии MQTT – «издателем») в Cloud IoT Core (MQTT bridge, брокер MQTT) в тему, имя которой должно соответствовать формату:


/devices/{device-id}/events

Публиковать сообщение можно не только в саму папку, но и в ее подпапки.

Идентификатор {device-id} должен быть уникален для каждого устройства. В случае использования Mongoose OS идентификатор создается из последних 3 байтов MAC-адреса контроллера ESP32, например esp32_ABCDEF.

 Качество обслуживания (QoS)

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

  • QoS 0 – сообщение доставляется не более одного раза;
  • QoS 1 – сообщение доставляется как минимум один раз;
  • QoS 2 – сообщение доставляется ровно один раз.

Google IoT Core поддерживает QoS 0 и QoS 1. Уровень качества обслуживания можно указать при публикации сообщения.

Безопасность

Связь между устройством и Cloud IoT Core осуществляется через шифрованное соединение TLS, поэтому можно гарантировать, что устройство подключено к серверу Cloud IoT Core MQTT (сертификаты создаются и хранятся в файловой системе Mongoose OS), обмен данными защищен и целостность данных проверена. Аутентификация устройства в Cloud IoT Core выполняется с помощью открытого/закрытого ключа для каждого контроллера с использованием JSON Web Token (JWT). Контроллер подписывает JWT своим закрытым ключом, а Cloud IoT Core проверяет его, используя связанный открытый ключ. Ключи можно сгенерировать и опубликовать с помощью инструмента mos. Для исключения компрометации закрытого ключа память контроллера ESP32 может быть зашифрована.

Хранилище (registries)

 Хранилище – это условный способ объединения однотипных устройств. В хранилище задаются топики для публикации телеметрии.

Cloud Pub/Sub

Телеметрические данные со всех устройств, принадлежащих одному и тому же хранилищу, пересылаются в топик Cloud Pub/Sub (элемент экосистемы Google Cloud). Названия топиков (Cloud Pub/Sub topic) должны иметь следующий вид:


projects/ИМЯ_ПРОЕКТА/topics/ИМЯ_ТОПИКА

В общем случае, контроллер публикует сообщения телеметрии и статуса и получает сообщения конфигурации (если он, конечно, на них подписан). Функции Firebase получают сообщения телеметрии и публикуют новые конфигурации (или команды) для контроллера.

Хранение и визуализация данных

Рис. 3. Хранение и визуализация данных

Рис. 3. Хранение и визуализация данных

Публикация данных телеметрии в разделе Cloud Pub/Sub будет запускать облачную функцию Firebase, которая будет сохранять полученные результаты в выбранных базах данных: Firebase Realtime Database, Google Spread Sheet, BigQuery. Сайт, размещенный на хостинге Firebase, будет динамически отображать (рисунок 3) данные из базы данных Firebase Realtime Database (или другого источника).

BigQuery – альтернативный способ хранения данных. Это хранилище позволяет извлекать данные с помощью SQL-запросов. Вместе с Data Studio помогает быстро визуализировать необходимую информацию и оформить ее в профессионально выполненный dashboard.

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

Сообщения «Config» и «State»

На диаграмме архитектуры проекта, приведенной на рисунке 1, помимо телеметрии, есть два дополнительных потока данных: Config и State (рисунок 4).

Рис. 4. Сообщения Config (Конфигурации) и State (Состояния)

Рис. 4. Сообщения Config (Конфигурации) и State (Состояния)

Это два служебных топика, существующих по умолчанию в каждом хранилище Cloud IoT Core. В некоторых других облачных сервисах аналогичную роль выполняет Shadow.

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

Google рекомендует отправлять не более одного сообщения такого типа в секунду на устройство. Каждое сообщение может быть размером до 64 кбайт. Название темы конфигурации следующее:


/devices/{device-id}/config

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

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


/devices/{device-id}/state

Примечание. Кроме конфигурации и состояния, Cloud IoT Core предоставляет еще один служебный топик – Команды. Работа с ним аналогична работе с топиком конфигурации.

Установка ОС Mongoose на контроллер

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

Mongoose OS – это IoT-ориентированная операционная система, работающая на контроллерах STM32L4, STM32F4, STM32F7, CC3220, CC3200, ESP32, ESP8266. Mongoose OS имеет API к основным облачным сервисам IoT: Amazon AWS IoT, Google IoT Core, IBM Watson, Microsoft Azure IoT, Samsung Artik. Версия Mongoose OS Community Edition бесплатная и распространяется под лицензией Apache 2.0. Входящий в состав ОС инструмент разработки mos позволяет сформировать и загрузить прошивку, обновить файлы, подготовить устройство для работы с платформой IoT. Mos работает либо в пользовательском интерфейсе, либо в командной строке, либо в браузере. Mongoose OS имеет многочисленные API, упрощающие доступ к сетевым сервисам и датчикам. Программы для операционной системы можно писать как на C/C++, так и на JS (JS позволит быстро написать прототип приложения, а C/C++ обеспечат экономию памяти и более глубокий доступ к возможностям контроллера). Впрочем, в JS предусмотрен функционал для вызова функций C, и ничто не мешает обращаться из JS напрямую к Espressif SDK.

В экосистеме Mongoose есть облачное приложение для управления устройствами под названием mDash (с ограничением на три устройства с бесплатной лицензией; платная лицензия стоит около $2, интерес представляет возможность «коробочного» обновления прошивки «по воздуху» и изменения файлов) и еще несколько сервисов, доступных только при покупке платной лицензии.

Установка Mongoose на ESP32

Инструкция приведена на сайте Mongoose OS и занимает примерно 11 минут. Для наших целей выполнять шаги с 8 по 10 не требуется, так что время установки сократится до 6 минут (рисунок 5).

Рис. 5. Инструкция установки Mongoose на ESP32

Рис. 5. Инструкция установки Mongoose на ESP32

Последовательность установки операционной системы на контроллер:

1…3. Нужно скачать и запустить программу, подключить плату с контроллером и, при необходимости, для подключения к контроллеру установить драйвер;

  1. Создание тестового приложения в папке appTest. С помощью команды mos clone https://github.com/mongoose-os-apps/demo-js appTest тестовое приложение будет клонировано в папку appTest, и на эту же папку изменится рабочая папка приложения mos (рисунок 6).

Рис. 6. Создание тестового приложения в папке appTest

Рис. 6. Создание тестового приложения в папке appTest

В папке appTest/fs/ расположен исходный файл приложения с именем init.js. Приложение умеет взаимодействовать с различными платформами IoT (если они, конечно, сконфигурированы).

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

  1. Для сборки прошивки в приложении mos нужно в выпадающем списке вверху выбрать целевую платформу (ESP32). Сборка запускается командой mos build (если вы работаете из терминала, добавьте ключ arch esp32 к этой строке). Сборка выполняется в облаке и занимает некоторое время. Если конфигурация программы не изменяется, то сборку больше выполнять не требуется. Достаточно будет только обновить файл в файловой системе контроллера (если вам необходимо выполнять сборку на локальном компьютере – воспользуйтесь инструкцией на сайте mongoose). Результатом выполнения этого шага будет создание множества файлов в папке appTest/. Один из созданных файлов – appTest/build/fw.zip – содержит прошивку микроконтроллера. На следующем шаге эта прошивка будет загружена в целевое устройство.
  2. Загрузка прошивки выполняется командой mos flash. Как и сборку, в большинстве случаев эту команду нужно выполнить только один раз. При изменении программы init.js или одного из рабочих файлов изменения можно будет загрузить командой mos put. Процесс прошивки отображается в консоли mos. После завершения прошивки контроллер автоматически перезагрузится (ход загрузки и возможные ошибки будут видны в терминале) и запустится скрипт init.js.

В терминале mos (или в любом другом терминале, подключенном на скорости 115 кбайт/с) каждую секунду будет появляться строка состояния.


[Jun 17 19:11:09.543] online: false
{ram_free”: 31200, “uptime”:6.516889, “btnCount”:0, “on”:false}
  1. Подключение к Wi-Fi выполняется командой:

mos wifi WIFI_NETWORK_NAME WIFI_PASSWORD

Контроллер сам попытается подключиться к сети, получит IP-адрес, выполнит синхронизацию времени. Если доступ к сети не сконфигурирован, контроллер по умолчанию будет работать в режиме точки доступа с запущенным веб-сервером, после подключения к этой точке доступа (пароль: Mongoose) страницу устройства, – файл appTest/fs/index.html, – можно открыть по адресу: 192.168.4.1. В дальнейшем для конфигурации беспроводной сети можно будет добавить на эту страницу форму для задания логина и пароля сети. Изменить имя точки доступа, пароль и ip-адрес можно, перезаписав конфигурацию в настройках приложения:


"wifi": {
	"ap": {
		"enable": true,        // Разрешение работы точки доступа
		"ssid": "Mongoose_??????",  // Имя SSID. Символы ?? заменяются MAC адресом
		"pass": "Mongoose",      // Пароль
		"hidden": false,       // Скрывать WiFi сеть
		"channel": 6,         // Канал WiFi
		"max_connections": 10,    // Максимальное количество подключений
		"ip": "192.168.4.1",     // Статический IP Адрес
		"netmask": "255.255.255.0",  // Маска подсети
		"gw": "192.168.4.1",     // Шлюз по умолчанию
	}
}

Управление конфигурацией подробно описано в документации. Обратите внимание, что после любого изменения конфигурации нужно заново собирать и загружать прошивку. Если не принять специальных мер, после загрузки прошивки будут сброшены настройки Wi-Fi, подключения к облаку и так далее. Узнать настройки конфигурации можно с помощью команды mos config-get KEY, а изменить – с помощью mos config-set KEY=VALUE. Команда получения настроек может вернуть JSON или значение конкретного ключа, командой установки можно задать только простые типы значений – строки, числа.

Из консоли mos с помощью вызовов RPC можно получить информацию об устройстве и узнать состояние подключения, например, IP-адрес. Делается это комбинацией клавиш CTRL + i в mos или командой mos call Sys.GetInfo.

В mos есть еще несколько полезных сочетаний клавиш, они видны в консоли команд приложения после загрузки.

Шаги 5…7 должны выполняться для каждого настраиваемого устройства. Конечно, для однотипных платформ, например, только для ESP32, шаг 5 повторять не нужно.

Hello, word

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



/*
 Мигание встроенным светодиодом и вывод на консоль
 Светодиод подключен к GPIO2.
 Управление таймером:
 - https://mongoose-os.com/docs/mos/api/core/mgos_timers.h.md
*/

load('api_config.js');
load('api_gpio.js');
load('api_timer.js');

let pin = 2; // вывод, к которому подключен светодиод

GPIO.set_mode(pin, GPIO.MODE_OUTPUT);

// вызов каждые две секунды
Timer.set(2000, Timer.REPEAT, function() {
	let value = GPIO.toggle(pin);
	print(value ? 'Tick' : 'Tack');
}, null);

Этот код нужно сохранить вместо загруженного ранее демоскрипта по адресу appTest/fs/init.js.

Для редактирования можно использовать любую IDE, поддерживающую язык JS. Из программы mos загрузим новый скрипт в файловую систему ОС Mongoose:


mos put fs/init.js

и выполним перезагрузку:


mos call Sys.Reboot

Светодиод должен мигать, а мы – должны увидеть сообщения Tick и Tack в консоли.

Подключение I2C-датчика

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

  1. Проверить, что в состав прошивки включена библиотека I2С. Для этого нужно открыть файл конфигурации mos.yml и проверить наличие в разделе libs строки – origin: https://mongoose-os-libs/i2c. Если такой строки нет, то ее нужно добавить, и после этого заново собрать и загрузить прошивку.
  2. Проверить/создать/изменить конфигурацию выводов I2С. Конфигурация I2C должна выглядеть следующим образом:

 "i2c": {
  "unit_no": 0,
  "enable": true,
  "freq": 100000,
  "debug": false,
  "sda_gpio": 32,
  "scl_gpio": 33
 } 

Нужно обратить внимание на то, что работа I2C разрешена «enable»: true и на используемые номера портов для подключения устройств. Если что-то сконфигурировано неправильно, нужно это поправить в файле конфигурации mos.yml и обновить прошивку.

  1. Подключить библиотеку к скрипту. Для этого достаточно добавить строку load(‘api_i2c.js’); в начало файла fs/init.js.
  2. Инициализировать шину I2C и датчик. Для измерения освещенности использован датчик MAX44009. Инициализация шины и датчика выполняются командами:

let bus = I2C.get();

let result = I2C.writeRegB(bus, 0x4A, 0x02, 0x40);
  1. Добавить таймер для регулярного получения данных с датчика. Обработчик таймера должен считать данные из регистров 0x03 и 0x04.

Полностью код скрипта для регулярного опроса I2C-датчика следующий:



/*
 Опрос датчика MAX44009, вывод на консоль
 I2C: 
 - https://mongoose-os.com/docs/mongoose-os/api/core/i2c.md
*/
load('api_config.js');
load('api_timer.js');
load('api_i2c.js'); 

let bus = I2C.get(); 
let result = I2C.writeRegB(bus, 0x4A, 0x02, 0x40); 

// вызов каждые две секунды
Timer.set(2000, Timer.REPEAT, function() {
	let val = I2C.readRegB(bus, 0x4A, 0x03);
	let val1 = I2C.readRegB(bus, 0x4A, 0x04);
	let exponent = (val & 0xF0) >> 4;
	let mantissa = ((val & 0x0F) << 4) | (val1 & 0x0F);
	let lightLevel = Math.pow(2, exponent) * mantissa * 0.045;
	print(lightLevel);
}, null);

После сохранения файла нужно его загрузить в контроллер и перезагрузить устройство:


mos put fs/init.js
mos call Sys.Reboot

После этого в консоли появятся данные датчика:


[Jun 17 21:47:06.528] 142.560000

[Jun 17 21:47:08.521] 142.560000

[Jun 17 21:47:10.520] 143.280000

[Jun 17 21:47:12.521] 142.560000

Подключение аналогового датчика

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

Для информации:

  • библиотека – https://github.com/mongoose-os-libs/adc;
  • строка подключения – load(‘api_adc.js’);
  • инициализация порта – ADC.enable(port);
  • измерение ADC.read(port).

Обратите внимание: второй порт АЦП в принципе не работает одновременно с Wi-Fi; для уменьшения шума рекомендуется отключать Wi-Fi перед измерением.

 Публикация сообщений телеметрии (MQTT)

Объединим результаты двух предыдущих скриптов и добавим публикацию телеметрии в Google Cloud (или любом другом настроенном mqtt-сервере).

Топик для публикации задается шаблоном: /devices/{device-id}/events.

Сообщения публикуем в формате JSON, чтобы облегчить их дальнейшую обработку с помощью облачной функции Firebase, подписанной на эти сообщения:



/*
Простой датчик с отправкой данных в Google Cloud
 */

load('api_config.js');
load('api_timer.js');
load('api_sys.js');
load('api_i2c.js');
load('api_mqtt.js');
load('api_adc.js');

let bus = I2C.get();
let lightAdr = 0x4A; //I2C-адрес датчика освещенности
let port=35; 
let topic = '/devices/' + Cfg.get('device.id') + '/';
//добавить events | config | state для получения реального адреса

let Sensors = {
	lightLevel: 0,
	dist:0,
	report: function () {
		return {
			dst: this.dist,
			l: this.lightLevel
		};
	},
	// инициализация датчиков
	init: function () {
		ADC.enable(port);
		let result = I2C.writeRegB(bus, lightAdr, 0x02, 0x40);
        // ручной режим работы по запросу
		return;
	},
	// проведение измерения
	measure: function () {
		let tempDist = 0;
		for (let j = 0; j < 5; j++) {
                    tempDist += ADC.read(port);
                    Sys.usleep(17000);
        // задержка между измерениями в датчике расстояния
                }
                this.dist = tempDist / 5; // измерение освещенности
                let val = I2C.readRegB(bus, lightAdr, 0x03);
                let val1 = I2C.readRegB(bus, lightAdr, 0x04);
                let exponent = (val & 0xF0) >> 4;
		let mantissa = ((val & 0x0F) << 4) | (val1 & 0x0F);
		this.lightLevel = Math.pow(2, exponent) * mantissa * 0.045;
		return;
	}
};
// инициализация датчиков
Sensors.init();
// функция для отправки объекта в облако
let MQTTSend = function (suff, obj) {
	let msg = JSON.stringify(obj);
	if (MQTT.isConnected()) {
		let ok = MQTT.pub(topic + suff, msg, 1);
		print(ok, suff, msg);
	} else {
		print(suff + "= Not connected! ", msg);
	}
};

// таймер для запуска измерений и публикации в теме /devices/{device-id}/events
Timer.set(2000, Timer.REPEAT, function () {
	Sensors.measure();
	MQTTSend("events", Sensors.report());
}, null); 

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


[Jun 19 22:35:54.810] events= Not connected!

{“1”:1146.240000, “dst”:155.800000}

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

Настройка проекта в Google IoT Core

Установка Google Cloud SDK

Во-первых, нам нужно установить Google Cloud SDK, так как многие действия удобнее выполнять из терминала, вводя команды gcloud в командной строке. Скачать SDK можно со страницы загрузки Google Cloud SDK.

Для работы с Cloud IoT Core потребуются бета-версии gcloudкоманд. Их можно установить следующим образом:


gcloud components install beta

В принципе, большинство необходимых действий в Google IoT Core можно выполнить любым из трех способов:

  • с помощью Google Cloud Console;
  • с помощью API;
  • из командной строки командами gcloud.

Мы будем использовать последний способ, проверяя результат с помощью Google Cloud Console.

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

Последовательность настройки проекта Google Cloud приведена на сайте Mongoose OS:


# Приведенные ниже команды нужно выполнить всего один раз для настройки Google
# Cloud project! Они могут быть выполнены из любой папки

# Запрос авторизации в Google Cloud. В открывшемся окне нужно будет выбрать нужную
# учетную запись Google и разрешить доступ:

gcloud auth login

# Создание проекта. Например, выберем cabine-sensor-project как PROJECT_ID

gcloud projects create cabine-sensor-project

# Предоставление Cloud IoT Core прав на публикацию в Pub/Sub-теме:

gcloud projects add-iam-policy-binding cabine-sensor-project \
--member=serviceAccount:cloud-iot@system.gserviceaccount.com --role=roles/pubsub.publisher

# Установка проекта по умолчанию для gcloud:

gcloud config set project cabine-sensor-project

# Создание тем Pub/Sub для отправки телеметрии:

gcloud beta pubsub topics

create main-telemetry-topicgcloud beta pubsub topics create registry-topic

# Создание подписки Pub/Sub на созданную тему:

gcloud beta pubsub subscriptions create --topic main-telemetry-topic \
main-telemetry-subscription

# Создание хранилища для устройств (cabine-devices-registry)

# Определение топика Pub/Sub для публикации сообщений, в том числе для подпапки

# Запрет подключения по протоколу HTTP:

gcloud iot registries create cabine-devices-registry --project=cabine-sensor-project \
--region=europe-west1 --event-notification-config=topic=registry-topic,subfolder=registry \
--event-notification-config=topic=main-telemetry-topic --no-enable-http-config

# На запрос разрешения API нужно отвечать «да»

# Команда не будет работать, если на аккаунте не настроен биллинг

# В этом случае нужно перейти по ссылке, настроить биллинг и повторить команду

Регистрация устройства в проекте Cloud IoT Core

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


# Регистрация устройства в Cloud IoT Core (выполнить для каждого устройства!):
mos gcp-iot-setup --gcp-project cabine-sensor-project --gcp-region europe-west1 \
--gcp-registry cabine-devices-registry

Эта команда сама, внутри программы mos, будет исполнять нужные gcloud-команды. Для выполнения команды требуется, чтобы регистрируемое устройство было подключено через последовательный порт к компьютеру, так как в результате выполнения команды на устройство будут загружены ключи, настроены адреса MQTT и так далее.

Результаты работы команды выводятся на консоль mos. В папке проекта появятся два ключа – закрытый и открытый. Закрытый ключ (xxx.key.pem) – для ESP32, а открытый (xxx.pub.pem) – для Google IoT Core (рисунок 7). Они будут использоваться во время процесса аутентификации с использованием JSON Web Token в Google IoT Core.

Рис. 7. Сгенерированная пара ключей

Рис. 7. Сгенерированная пара ключей

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

После перезагрузки устройства в консоли видно, что оно успешно подключается к Google MQTT-брокеру и публикует сообщения телеметрии (MQTT.pub()возвращает 1):


[Jun 19 22:36:01.913] events= Not connected! {“1”:1152, “dst”:434.600000}

[Jun 19 22:36:03.006] 1 events {“1”:1152, “dst”: 17.400000}

[Jun 19 22:36:05.081] 1 events {“1”:1152, “dst”: 4.600000}

[Jun 19 22:36:07.134] 1 events {“1”:1152, “dst”: 1.200000}

Проверка настроек проекта в Google Cloud Console

На панели управления Google Cloud можно убедиться, что все настроено правильно (рисунок 8).

Рис. 8. Создан проект, хранилище, топики для публикации сообщений

Рис. 8. Создан проект, хранилище, топики для публикации сообщений

При нажатии на идентификатор хранилища cabine-devices-registry откроется список подключенных к этому хранилищу устройств. Открыв одно из устройств, можно увидеть, когда это устройство загружало конфигурацию, обновляло состояние, передавало телеметрию (рисунок 9).

Рис. 9. Список устройств в хранилище

Рис. 9. Список устройств в хранилище

В меню Pub/Sub виден список топиков и созданных подписок (рисунок 10).

Рис. 10. Названия тем и связанных подписок в Google Cloud Console (Pub/Sub)

Рис. 10. Названия тем и связанных подписок в Google Cloud Console (Pub/Sub)

Просмотр данных телеметрии

Передаваемые данные можно посмотреть с помощью команды gcloud:


gcloud beta pubsub subscriptions pull --auto-ack main-telemetry-subscription --limit=2

Эта команда получает до двух сообщений Pub/Sub из подписки main-telemetry-subscription. На рисунке 11 видна полезная нагрузка в формате JSON и список атрибутов, в том числе идентификатор устройства.

Рис. 11. Результат извлечения телеметрических сообщений из подписки

Рис. 11. Результат извлечения телеметрических сообщений из подписки

Теперь можно перейти к следующему шагу – обработке и сохранению данных в Firebase.

Регистрация, хранение и визуализация данных в Firebase

Для реализации проекта нам потребуются несколько продуктов Firebase:

  • Облачные функции (Cloud Function for Firebase) реагируют на события (публикации в топике, запросы HTTP и так далее) и выполняют заданную логику (сохранение или извлечение данных, их обработка и передача).
  • База данных Firebase (Firebase Realtime Database) – NoSQL-хранилище для произвольной информации. Она позволяет сохранить или загрузить данные в произвольном формате, например JSON.

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

  • Хостинг (Firebase Hosting) хранит скрипты, используемые для формирования web-страниц для визуализации данных.

Конфигурация Firebase

Все продукты Google, в том числе и Firebase, объединяются в едином проекте. Чтобы начать пользоваться возможностями Firebase, включите его в созданном на прошлом этапе проекте cabine-sensor-project в консоли Firebase (рисунки 12 и 13).

Рис. 12. Firebase Console – Добавить проект

Рис. 12. Firebase Console – Добавить проект

Рис. 13. Выбор проекта из существующих

Рис. 13. Выбор проекта из существующих

Примечание. После подключения проекта от вас потребуется выбрать тарифный план Firebase. В большинстве случаев для небольших и тестовых проектов план Blaze с оплатой по факту использования ресурсов даст достаточно бесплатных квот.

Инициализация Firebase

Установите Node.js и инструменты Firebase.

Теперь нужно авторизоваться командой firebase login (откроется страница в браузере, где можно выбрать учетную запись Google) и выполнить команду инициализации firebase init из той папки, где расположен проект Firebase (файлы проекта находятся в общем репозитории в папке Firebase).

На первом шаге выберите продукты Firebase, которые будут использоваться (рисунок 14).

Рис. 14. Выбор продуктов Firebase

Рис. 14. Выбор продуктов Firebase

Второй шаг – установка проекта по умолчанию (рисунок 15) для текущей папки (cabine-sensor-project).

Рис. 15. Установка связи между проектом и папкой

Рис. 15. Установка связи между проектом и папкой

Примечание. Если проект не виден – попробуйте выйти из учетной записи Google firebase logout, а затем авторизоваться заново (firebase login).

Третий шаг – определение файла с правилами доступа к базе данных. Будем использовать наименование файла по умолчанию (рисунок 16).

Рис. 16. Правила базы данных по умолчанию

Рис. 16. Правила базы данных по умолчанию

Конфигурация облачных функций и хостинга показана на рисунке 17.

Рис. 17. Конфигурация облачных функций и хостинга

Рис. 17. Конфигурация облачных функций и хостинга

Обратите внимание: загруженные из репозитария GitHub файлы firebase/functions/index.js и firebase/public/index.html перезаписывать не нужно.

Триггер подписки на топик телеметрии

Firebase позволяет написать функцию-триггер для подписки на сообщения, публикуемые в топике Cloud Pub/Sub. Соответственно, в этой функции должна быть реализована вся логика по обработке и сохранению полученных данных.

Код функции следующий:



exports.detectTelemetryEvents = functions.pubsub.topic('main-telemetry-topic')
.onPublish((message, context) => {
	const l = message.json.l.toFixed(1);
	const dst = message.json.dst.toFixed(1);
	const deviceId = message.attributes.deviceId; 
	const timestamp = context.timestamp;
	// отправка лога в журнал:
	console.log(`Device=${deviceId}, light=${l}lux, distance=${dst},
                    Timestamp=${timestamp}`);
	const data = {
		t: timestamp,
		l: l,
		d: dst
	};
	// отправка в Firebase Realtime Database 
	return  db.ref(`devices-telemetry-simple/${deviceId}`).push(data);
});

Функция получает две переменные – полезную нагрузку сообщения message (содержащую в себе два объекта json с самим сообщением и attributes с его атрибутами) и контекст, содержащий свойства окружения – context.

Примечание. В журнал сообщений Google Cloud Firebase выводит информацию о запуске облачной функции и времени выполнения (и возникших ошибках). Эту информацию можно дополнить данными для отладки с помощью команды console.log(ИМЯ_ПЕРЕМЕННОЙ). Она создает сообщение в журналах облачных функций и позволяет контролировать их работу.

Еще одно важное замечание: в работе функций активно используется API Google. Это API по своей сути асинхронно, и для работы с ним удобно использовать Promise. Promise (обычно их так и называют – «промисы») предоставляют удобный способ организации асинхронного кода, позволяющий, в том числе, последовательно выполнить несколько асинхронных вызовов, передавая результаты из одного вызова в другой.

Развертывание облачной функции

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


firebase deploy --only functions

Проверка облачной функции

Для проверки работы функции нужно зайти в закладку «журналы» раздела «Облачные функции» консоли Firebase (рисунок 18).

Рис. 18. Firebase Console – Журналы облачных функций Firebase

Рис. 18. Firebase Console – Журналы облачных функций Firebase

Кроме того, в консоли Google Cloud, как показано на рисунке 19, можно не только посмотреть журналы функции, но и изменить код, протестировать функцию с заданными параметрами и удалить функцию (функции стоит удалять, так как производимый ими трафик и используемое для хранения место могут превысить бесплатные квоты).

Рис. 19. Google Cloud Console – облачные функции

Рис. 19. Google Cloud Console – облачные функции

В хранилище Firebase можно увидеть сохраненные данные телеметрии, путь сохранения (devices-telemetry-simple/${deviceId}) задан в функции detectTelemetryEvents (рисунок 20).

Рис. 20. Firebase Console – База данных в реальном времени

Рис. 20. Firebase Console – База данных в реальном времени

Примечание. Облачная функция имеет права администратора для доступа к базе данных, независимо от содержимого файла конфигурации database.rules.json. Для доступа из веб-приложения нужно прописать правила, но об этом ниже.

Веб-приложение для визуализации данных

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

Для построения графиков будем использовать библиотеку plotly.

Для получения данных из Firebase используется следующий код:



// определение объекта базы данных:
const db = firebase.database();
// подписка на последние 100 записей по пути devices-telemetry/${devicesId}
db.ref(`devices-telemetry-simple/${devicesId}`).limitToLast(100).on('value', ts_measures => {
	// полезная нагрузка
});

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

Чтобы скрипт смог извлечь данные из базы, нужно определить правила доступа в файле database.rules.json. Правила можно задать для конкретных узлов, в нашем случае для узла devices-telemetry-simple нужно дать разрешение на чтение:



{"rules": {"devices-telemetry-simple": { ".read": true, ".write": false}}}

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

База Firebase устроена таким образом, что при запросе конкретного узла будут извлечены все данные из вложенных узлов. Поэтому не стоит читать корневой узел, это очень накладно (помним, что оплата идет за трафик). Данные телеметрии группируются по узлам с именами, совпадающими с именами устройств. Чтобы скрипт мог получить список устройств, на данном этапе создадим вручную в консоли Firebase узел devices-ids, в который занесем идентификаторы устройств. Скрипт веб-приложения предварительно прочитает этот узел и только потом начнет выгружать данные телеметрии для конкретного датчика. (В следующей статье будет рассказано, как сделать процесс добавления идентификаторов автоматическим и совместить его с конфигурированием вновь подключаемых устройств).

Соответственно, для узла devices-ids тоже нужно добавить правила в файл database.rules.json. В конечном итоге набор правил в файле должен стать таким:



{
	"rules": {
		"devices-ids": {
			".read": true,
			".write": false
		},
		"devices-telemetry-simple": {
			".read": true,
			".write": false
		}
	}
}

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


firebase deploy --only database

Исходный код веб-приложения

Исходные коды скриптов веб-страниц находятся в папке firebase/public клонированного приложения. Там должно быть достаточно комментариев для понимания работы кода.

Локальное развертывание и тестирование

Для тестирования приложение Firebase можно запустить на локальном сервере:


firebase serve --only hosting

После развертывания по адресу http://localhost:5000 станет доступна страница с показаниями датчиков (рисунок 21).

Рис. 21. Данные датчика в реальном времени

Рис. 21. Данные датчика в реальном времени

Проверив, что все работает, можно перенести код на хостинг Firebase:


firebase deploy --only hosting

В результате работы этой команды мы получаем общедоступный URL-адрес веб-приложения, который можно увидеть в консоли Firebase на закладке Hosting (рисунок 22).

Рис. 22. Адреса приложения

Рис. 22. Адреса приложения

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

Заключение

В статье приведен пример быстрого развертывания жизнеспособного IoT-проекта на основе контроллера ESP32 и стека технологий Mongoose OS и Cloud IoT Core. В следующей статье этого цикла будет рассказано о генерации и обработке событий в Mongoose OS на ESP32, создании и вызове RPC-функций, шифровании памяти и обеспечении безопасности контроллера, автоматизации регистрации устройств в Firebase, их конфигурировании и отслеживании состояния, передаче состояния на удаленный индикатор. В облачные функции будет добавлен функционал сохранения данных в альтернативных хранилищах – Big Query и Google Sheet.

Дополнительные материалы для реализации проекта

  1. Погодная станция с Mongoose-os и Google Cloud IoT
  2. Быстрый запуск Mongoose
  3. Ограничения реализации JS в Mongoose
  4. Использование BigQuery и DataStudio
  5. Безопасность в Mongoose-OS
  6. Использование Promise в JS
  7. Файлы проекта на GitHub
•••

Наши информационные каналы

О компании Espressif Systems

Компания Espressif Systems была основана еще в 2008 году в Китае. Только после шести лет упорной работы, компания представила первую беспроводную SoC-микросхему для Wi-Fi-приложений ESP8266EX, которая стала чрезвычайно популярной среди разработчиков. В 2016 году, закрепляя успех, Espressif Systems представила новое флагманское семейство ESP32, которое стало одним из первых интегрированных решений с одновременной поддержкой Wi-Fi и Bluetooth. Таким образом, за десять лет компания выросла из небол ...читать далее

Товары
Наименование
ESP32-PICO-KIT (ESPRES)
ESP32-PICO-D4 (ESPRES)
ESP32-D0WD (ESPRES)
ESP32-S0WD (ESPRES)
ESP32-D2WD (ESPRES)
ESP32-D0WDQ6 (ESPRES)