Основы программирования в экосистеме HI: от кода к потокам
COURSE-16-M02-L04 — Основы программирования в экосистеме HI: от кода к потокам
Введение в программирование на платформе HI
Программирование является ядром любой системы автоматизации. Оно определяет логику, по которой контроллер реагирует на события, обрабатывает данные с датчиков и управляет исполнительными устройствами. В отличие от классических систем, требующих глубоких знаний языков вроде C++ или Python, платформа HI использует современный, визуальный подход к программированию — Node-RED.
Этот подход позволяет инженерам создавать сложные сценарии автоматизации, соединяя готовые функциональные блоки (узлы) в логические цепочки (потоки). Это не только на порядок ускоряет разработку, но и делает систему прозрачной, легко читаемой и простой в обслуживании. 95% всех задач на платформе HI решаются именно с помощью визуального программирования.
В этом уроке мы изучим фундаментальные принципы программирования на контроллере HI, освоим ключевые концепции Node-RED и создадим свой первый рабочий сценарий автоматизации.
Уровни программирования на платформе HI
Платформа HI предоставляет несколько уровней для реализации логики, позволяя гибко подходить к задачам разной сложности.
Уровень 1: Визуальное программирование (Node-RED)
Это основной и наиболее предпочтительный метод разработки для инженеров всех уровней.
- Суть: Вы не пишете код с нуля, а конструируете логику из готовых блоков в графическом редакторе. Каждый блок («узел») выполняет конкретную функцию: читает данные с Modbus-устройства, отправляет MQTT-сообщение, управляет реле или выполняет простую логическую операцию.
- Преимущества:
* Наглядность: Логика работы системы видна сразу на схеме потока. Отладка и поиск неисправностей значительно упрощаются.
* Надежность: Используются проверенные, отлаженные узлы из обширной библиотеки. Риск допустить синтаксическую ошибку сведен к минимуму.
Уровень 2: Скриптинг (JavaScript в узле `Function`)
Когда стандартных узлов недостаточно для реализации сложной логики, на помощь приходит узел `Function`.
- Суть: Внутри этого узла вы можете писать код на языке JavaScript для обработки данных, выполнения расчетов или реализации нестандартных алгоритмов.
- Применение:
* Реализация сложных математических вычислений.
* Формирование сообщений сложной структуры для интеграции с внешними системами.
💡 Совет: Узел `Function` — мощный инструмент, но его следует использовать дозированно. Всегда сначала ищите готовый узел для вашей задачи. Если логика внутри узла `Function` становится слишком сложной (более 20-30 строк), это верный признак того, что ее нужно декомпозировать на несколько стандартных узлов или вынести в субпоток (Subflow).
Уровень 3: Системный уровень (Linux/Debian)
Для задач, выходящих за рамки стандартной автоматизации, инженеры уровня Architect могут использовать всю мощь операционной системы Linux (Debian), на которой работает контроллер.
- Суть: Написание shell-скриптов, установка дополнительного ПО, создание собственных сервисов.
- Применение:
* Задачи архивирования данных, ротации логов.
* Запуск сложных вычислительных моделей.
⚠️ Предупреждение: Работа на системном уровне требует глубоких знаний Linux и сопряжена с риском нарушения стабильности работы контроллера. Этот уровень не рассматривается в курсах Foundation и Installer.
Рабочая среда инженера: Редактор Node-RED
Ваша основная среда разработки — это веб-интерфейс Node-RED, доступный по IP-адресу контроллера. Он состоит из трех ключевых областей:
Ключевые концепции программирования в Node-RED
Для создания надежных сценариев необходимо понимать четыре фундаментальные концепции.
1. Событийно-ориентированное программирование
Потоки в Node-RED не выполняются постоянно. Они "спят" и активируются только в ответ на какое-либо событие. Событием может быть:
- Нажатие кнопки, подключенной к дискретному входу.
- Поступление нового сообщения из MQTT-топика.
- Срабатывание таймера в узле `Inject`.
- Получение данных от Modbus-устройства.
После активации поток выполняет последовательность действий и снова переходит в режим ожидания.
2. Поток данных и "Контракт сообщения"
Узлы обмениваются информацией с помощью объектов-сообщений (`msg`). Каждый `msg` — это JavaScript-объект, который имеет различные свойства. Самое важное свойство — `msg.payload`, в котором обычно передаются основные данные.
Для создания предсказуемой и надежной системы в Академии HI принят строгий "Контракт сообщения". Все данные, передаваемые между узлами, должны соответствовать стандартному JSON-формату.
Пример правильного `msg.payload` от датчика температуры:{
"value": 24.5,
"source": "temp-sensor-livingroom-01",
"ts": 1678886400000,
"unit": "°C"
}
⚠️ Критически важно: Никогда не передавайте "голые" значения (например, просто число `24.5`). Всегда оборачивайте их в стандартизированный JSON-объект. Это упрощает отладку, логирование и дальнейшую обработку.
3. Управление состоянием (Контекст)
Часто сценарию нужно "помнить" предыдущее состояние. Например, чтобы переключить свет по одному нажатию кнопки, нужно знать, был ли он включен до этого. Для этого используется контекст (Context).
- `flow context`: Переменные, доступные всем узлам в рамках одной вкладки (flow). Идеально для хранения состояния группы устройств (например, `flow.get("is_heating_active")`).
- `global context`: Переменные, доступные всем узлам на всех вкладках. Использование не рекомендуется из-за риска создания неочевидных зависимостей.
На контроллерах HI контекст можно настроить на сохранение в файловой системе или в базе данных MySQL. Это гарантирует, что система восстановит свое состояние после перезагрузки или сбоя питания.
4. Обработка ошибок и визуальный статус
Любая система может дать сбой: обрыв связи с датчиком, некорректный ответ от устройства. Профессионально спроектированный поток должен предвидеть и обрабатывать такие ситуации.
- Узел `Catch`: Ловит ошибки, сгенерированные другими узлами на той же вкладке. Его необходимо подключать к потоку логирования для записи информации об инциденте.
- Узел `Status`: Позволяет узлу отображать свой текущий статус (цвет и текст) прямо в редакторе. Это мощный инструмент для визуальной диагностики. Например, узел, работающий с Modbus-датчиком, может показывать `OK: 25.1°C` (зеленый) или `Error: Timeout` (красный).
Практический пример: Считывание данных с датчика температуры
Создадим сценарий, который каждые 10 секунд считывает показания с датчика температуры DS18B20 (подключен к универсальному входу контроллера) и отправляет их в MQTT в стандартизированном формате.
ID Схемы подключения: `WIRING-SENS-001` ID Потока: `FLOW-SENS-TEMP-001`Схема подключения (ASCII)
//========= WIRING-SENS-001: DS18B20 Temperature Sensor =========
[CTRL:HI-Core] (SENS:Temp:DS18B20)
(UI-01)
Клемма Шина Цвет Клемма
+5V --------- (Красный) ---- VDD
DATA --------- (Желтый) ----- DQ
GND --------- (Черный) ----- GND
Поток в Node-RED (ASCII)
+---------------------+ +--------------------------+ +----------------------+
[Inject] -> | ds18b20 (1-Wire In) | -> | Function: Validate/Format| -> | mqtt out |
+---------------------+ +--------------------------+ +----------------------+
| (on error) ^
v |
+---------------------+ |
| Catch Node | -> [Function: Log Error] -> [Debug/MySQL] |
+---------------------+-------------------------------------------+
Конфигурация узлов:
* Настроить на повторение (`repeat`) каждые `10 секунд`.
* Установить узел из палитры `node-red-contrib-ds18b20-sensor`.
* В настройках указать ID датчика 1-Wire (например, `28-01234567abcd`).
* Код:
// Получаем сырое значение температуры из msg.payload
let temp = parseFloat(msg.payload);
// 1. Валидация
if (isNaN(temp) || temp < -55 || temp > 125) {
node.status({fill:"red", shape:"dot", text:"Invalid reading: " + msg.payload});
// Генерируем ошибку, которую поймает узел Catch
node.error("Некорректное значение температуры: " + msg.payload, msg);
return null; // Останавливаем дальнейшее движение сообщения
}
// 2. Формирование сообщения по "Контракту"
msg.payload = {
value: temp,
source: "28-01234567abcd", // ID датчика
ts: Date.now(),
unit: "°C"
};
// 3. Установка топика для MQTT
msg.topic = "hi/office/room101/temperature";
// 4. Обновление визуального статуса
node.status({fill:"green", shape:"dot", text:"OK: " + temp + " °C"});
return msg;
* Настроить подключение к MQTT-брокеру контроллера (обычно `localhost:1883`).
* Топик оставить пустым, так как он устанавливается в `msg.topic`.
Этот поток демонстрирует все ключевые концепции: он запускается по событию от таймера, обрабатывает данные, приводит их к стандартному контракту, управляет своим визуальным статусом и готов к обработке ошибок.
---
Лабораторные работы
COURSE-16-M02-LAB07: "Здравствуй, мир автоматизации!" — Управление реле по кнопке
Цель: Создать базовый сценарий, в котором нажатие настенного выключателя (подключен как "сухой контакт") включает или выключает свет (подключен через реле контроллера). Оборудование:- Контроллер HI.
- Настенный выключатель (кнопка без фиксации), подключенный к универсальному входу `UI-05`.
- Лампа, подключенная через реле `RL-01`.
`[rpi gpio in: UI-05]` -> `[Function: Toggle Logic]` -> `[rpi gpio out: RL-01]`
Задание:- [ ] Поток создан на отдельной вкладке.
- [ ] Используется `flow context` для хранения состояния.
- [ ] Логика переключения работает корректно.
- [ ] Узлы имеют осмысленные имена ("Кнопка в коридоре", "Логика света", "Реле света").
COURSE-16-M02-LAB08: Обработка данных датчика и публикация в MQTT
Цель: Реализовать поток из практического примера урока: опрос датчика температуры DS18B20, валидация, форматирование по контракту и отправка в MQTT. Оборудование:- Контроллер HI.
- Датчик температуры DS18B20, подключенный к `UI-01`.
* В панели отладки должны каждые 10 секунд появляться корректно сформированные JSON-объекты.
* Узел `Function` должен показывать зеленый статус с текущей температурой.
* С помощью MQTT-клиента (например, MQTT Explorer) подпишитесь на топик `hi/office/room101/temperature` и убедитесь, что данные поступают.
Рубрика оценивания:- [ ] Поток собран в соответствии со схемой.
- [ ] Данные в `Debug` и MQTT соответствуют "Контракту сообщения".
- [ ] Узел `Function` корректно отображает визуальный статус.
- [ ] Поток содержит узел `Catch`, подключенный к узлу `Debug` для отлова ошибок.
---
Тест для самопроверки
ID Квиза: `COURSE-16-M02-QUIZ`* a) Объектно-ориентированное программирование на C++.
* b) Визуальное программирование потоков в Node-RED.
* c) Функциональное программирование на Rust.
* d) Написание скриптов на Python.
* a) Способ шифрования данных между узлами.
* b) Стандартный формат JSON для `msg.payload` для обеспечения предсказуемости данных.
* c) Лицензионное соглашение на использование узлов.
* d) Документ, описывающий логику потока.
* a) Для написания сложной логики на JavaScript, когда стандартных узлов недостаточно.
* b) Для определения математических функций.
* c) Для запуска внешних программ.
* d) Для объявления глобальных переменных.
* a) Вкладка "Информация".
* b) Внешний отладчик Visual Studio Code.
* c) Панель отладки (Debug) в боковой панели.
* d) Консоль браузера.
* a) В `msg.payload`.
* b) В `global context` без персистентности.
* c) В `flow context`, настроенном на сохранение в файловую систему.
* d) В переменной внутри узла `Function`.
* a) `Try`.
* b) `Error`.
* c) `Status`.
* d) `Catch`.
* a) Узел неактивен.
* b) Узел выполняет операцию.
* c) В узле произошла ошибка.
* d) Узел успешно завершил работу.
* a) Произойдет ошибка, которую поймает `Catch`.
* b) Сообщение `msg` остановит свое движение по потоку.
* c) Весь поток будет перезапущен.
* d) Будет отправлено пустое сообщение.
* a) Чтобы убедиться, что датчик не перегрелся.
* b) Чтобы отсеять заведомо ложные или ошибочные показания (например, при обрыве связи).
* c) Это требование протокола MQTT.
* d) Для преобразования градусов Цельсия в Фаренгейты.
* a) `flow.save("key", value)`
* b) `context.set("key", value)`
* c) `flow.set("key", value)`
* d) `msg.context.key = value`
---
Мини-runbook "Если что-то не работает"
📋 Чек-лист для быстрой диагностики:
* Проверьте конфигурацию узла-триггера (`Inject`, `rpi gpio in`, `mqtt in`). Правильно ли указан пин, топик, интервал?
* Нажмите кнопку "Deploy" (Развернуть). Возможно, вы забыли применить изменения.
* Проверьте "Контракт сообщения". Убедитесь, что узел, который вы отлаживаете, получает `msg` в ожидаемом формате.
* Если это узел `Function`, добавьте `node.warn(msg)` в начало кода, чтобы увидеть, что приходит на вход.
* Проверьте физическое подключение датчика. Возможно, он неисправен или плохо подключен.
* Подключите узел `Catch` к узлу `Debug` (настроенному на вывод полного объекта `msg`). Разверните поток и спровоцируйте ошибку. В панели отладки вы увидите подробное описание проблемы.
* Частые причины: неверный IP-адрес в узле MQTT/Modbus, обрыв связи, некорректный ID датчика.
* Проверьте правильность пина, указанного в узле `rpi gpio out`.
* Проверьте схему подключения (`WIRING-`...). Подается ли питание на исполнительное устройство? Не перепутаны ли клеммы?
* Временно замените узел `rpi gpio out` на `Debug`. Убедитесь, что на его вход приходит правильное значение (`true`/`false` или `1`/`0`).
* Убедитесь, что вы используете `flow context` или `global context`.
* Проверьте файл `settings.js` в директории Node-RED. Убедитесь, что `contextStorage` настроен на использование файловой системы (`localfilesystem`) или MySQL.