Лабораторная работа: Создание flow управления светом по датчику освещенности с гистерезисом
Цели и подготовка к лабораторной работе
Добро пожаловать в практическую лабораторную работу, где мы объединим теоретические знания, полученные в предыдущих уроках, для создания полноценного и надежного сценария автоматизации. Основная проблема, которую мы решаем сегодня, — нестабильное поведение системы управления светом на основе датчика освещенности, известное как флаттеринг (flapping). Это происходит, когда уровень освещенности колеблется вблизи одного порогового значения, заставляя реле многократно и бессмысленно включаться и выключаться.
Цель настоящей лабораторной работы: создать отказоустойчивый flow в Node-RED для автоматического управления группой освещения. Этот flow должен игнорировать кратковременные колебания и "шум" от датчика освещенности, принимая решения о включении и выключении света на основе устойчивых, усредненных данных и с использованием логики гистерезиса.Архитектура решения
Мы построим наш flow как последовательную цепочку обработки данных. Такой подход, где каждый узел выполняет одну четкую функцию, значительно упрощает разработку, отладку и дальнейшую поддержку.
> 💡 Подсказка: Перед началом работы убедитесь, что ваш MQTT-брокер активен, и вы можете получать данные с датчика и управлять реле. Используйте сторонний MQTT-клиент (например, MQTT Explorer) для проверки топиков. Это базовый навык диагностики, который сэкономит вам массу времени.
Подготовка рабочего пространства
* Топик датчика освещенности (вход): Например, `/devices/wb-msw-v3_25/controls/Illuminance`.
* Топик управления реле (выход): Например, `/devices/wb-mr6c_12/controls/K1/on`.
* `mqtt in`, `mqtt out`
* `smooth`
* `function`
* `switch`
* `change`
* `debug`
Теперь, когда цели ясны и рабочее место готово, приступим к созданию нашего flow шаг за шагом.
---
Шаг 1: Получение и сглаживание аналогового сигнала
Первый и самый важный этап — получение стабильных данных. Как мы обсуждали в уроке `COURSE-04-M05-L06`, сырые данные с аналоговых датчиков часто бывают "зашумленными". Наша задача — отфильтровать этот шум, прежде чем передавать данные в узел принятия решений.
Добавление и настройка `mqtt in`
После настройки подключите к выходу узла `mqtt in` узел `debug`, чтобы убедиться, что данные поступают корректно. Вы должны видеть в панели отладки сообщения, `msg.payload` которых содержит числовое значение освещенности.
Интеграция узла `smooth`
Теперь добавим сглаживание. Узел `smooth` реализует несколько алгоритмов усреднения, но для нашей задачи идеально подходит простое скользящее среднее (SMA).
| Параметр | Описание | Рекомендуемое значение |
| :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------- |
| Action | Алгоритм сглаживания. Выберите `simple moving average`. | `simple moving average` |
| Count | Ключевой параметр. Это количество последних значений, которые будут использоваться для расчета среднего. Чем выше это число, тем более гладким будет результат, но и более инертной станет реакция системы. | `10` |
| Round to| Округление результата до указанного количества знаков после запятой. Помогает избежать излишней точности. | `0` (целое число) |
| Name | Имя узла. | "SMA (10)" |
Анализ эффекта сглаживания
Чтобы наглядно увидеть результат работы узла `smooth`, используйте два узла `debug`:
Теперь симулируйте изменение освещенности (например, медленно поднося и убирая фонарик телефона к датчику). В панели отладки вы увидите две последовательности чисел.
- `msg` от "Сырые данные" будет показывать резкие скачки.
- `msg` от "Сглаженные данные" будет меняться гораздо более плавно, без резких выбросов.
// Сообщение от "Сырые данные"
msg.payload: 152
// Сообщение от "Сглаженные данные"
msg.payload: 125
// Сообщение от "Сырые данные"
msg.payload: 98
// Сообщение от "Сглаженные данные"
msg.payload: 120
// Сообщение от "Сырые данные"
msg.payload: 155
// Сообщение от "Сглаженные данные"
msg.payload: 126
Выбор значения `count` — это компромисс. Для управления светом значение от 5 до 15 обычно является оптимальным. Оно достаточно велико, чтобы отсечь шум (например, от промелькнувшей тени человека), но достаточно мало, чтобы система не реагировала на реальное изменение освещенности (например, на закат) с большой задержкой.
---
Шаг 2: Реализация логики гистерезиса в ноде Function
Теперь, когда у нас есть стабильный, сглаженный поток данных, мы можем реализовать основную логику управления с помощью узла `function`. Этот узел позволит нам написать небольшой JavaScript-код, реализующий алгоритм "двойного порога", который мы детально рассмотрели в уроке `LESSON-04-M05-L03`.
> ⚠️ Внимание: Всегда инициализируйте переменные контекста в начале! Если при первом запуске `flow.get('isLightOn')` вернет `undefined`, это может привести к непредсказуемому поведению или ошибке. Добавьте проверку `if (current_state === undefined) { ... }` для установки начального значения.
Перетащите узел `function` на холст и соедините его с выходом узла `smooth`. Вставьте в него следующий код:
/*
- ЛОГИКА УПРАВЛЕНИЯ СВЕТОМ С ГИСТЕРЕЗИСОМ
- Этот узел принимает сглаженное значение освещенности (lux) и решает,
- нужно ли изменить состояние света.
*/
// --- 1. Определение порогов ---
// Порог для ВКЛЮЧЕНИЯ света. Свет включится, когда станет ТЕМНЕЕ этого значения.
const thresholdOn = 100; // lux
// Порог для ВЫКЛЮЧЕНИЯ света. Свет выключится, когда станет СВЕТЛЕЕ этого значения.
const thresholdOff = 150; // lux
// --- 2. Получение текущего состояния ---
// Считываем текущее состояние света из контекста уровня Flow.
// Это позволяет "помнить" состояние между вызовами функции.
let isLightOn = flow.get('isLightOn');
// --- 3. Инициализация состояния (критически важный шаг!) ---
// Если узел запускается впервые, переменная isLightOn будет `undefined`.
// Устанавливаем ей начальное значение (например, `false` - свет выключен).
if (isLightOn === undefined) {
isLightOn = false;
flow.set('isLightOn', isLightOn);
node.status({fill:"yellow", shape:"dot", text:"Init: OFF"});
// Возвращаем null, чтобы не отправлять команду при инициализации
return null;
}
// --- 4. Получение входных данных ---
// `msg.payload` содержит сглаженное значение освещенности от узла `smooth`.
const currentLux = msg.payload;
// --- 5. Основная логика гистерезиса ---
// Переменная для хранения новой команды. null означает "ничего не делать".
let command = null;
// Проверяем, нужно ли ВКЛЮЧИТЬ свет
// Условия: сейчас темно (lux < thresholdOn) И свет сейчас выключен (!isLightOn)
if (currentLux < thresholdOn && !isLightOn) {
command = true; // Команда на включение
flow.set('isLightOn', true); // Запоминаем новое состояние
node.status({fill:"blue", shape:"dot", text:"ON (lux: " + currentLux + ")"});
}
// Проверяем, нужно ли ВЫКЛЮЧИТЬ свет
// Условия: сейчас светло (lux > thresholdOff) И свет сейчас включен (isLightOn)
else if (currentLux > thresholdOff && isLightOn) {
command = false; // Команда на выключение
flow.set('isLightOn', false); // Запоминаем новое состояние
node.status({fill:"green", shape:"dot", text:"OFF (lux: " + currentLux + ")"});
}
// Если ни одно из условий не выполнено, значит мы находимся в "мертвой зоне"
// гистерезиса (между thresholdOn и thresholdOff). Ничего не меняем.
else {
// Обновляем статус для наглядности, но не отправляем команду
let statusText = isLightOn ? "State: ON" : "State: OFF";
statusText += " (lux: " + currentLux + ")";
node.status({fill:"grey", shape:"dot", text:statusText});
// Очень важно вернуть null, чтобы остановить дальнейшее движение сообщения
// и не отправлять дублирующие команды.
return null;
}
// --- 6. Формирование исходящего сообщения ---
// Отправляем в следующий узел только если было принято решение об изменении состояния.
msg.payload = command;
return msg;
Разбор кода:
---
Шаг 3: Формирование и отправка управляющих команд
Наш узел `function` теперь выдает на выход `msg` с `payload`, равным `true` (включить), `false` (выключить), или не выдает ничего (`null`). Следующий шаг — преобразовать эти логические значения в команды, которые понимает наше исполнительное устройство (реле), и отправить их.
Такой подход, разделяющий логику, маршрутизацию и формирование данных, является примером хорошей практики проектирования в Node-RED. Он делает flow модульным и легко читаемым.
Маршрутизация с помощью `switch`
* Правило 1: `is true` (тип `boolean`). Это будет выход 1.
* Правило 2: `is false` (тип `boolean`). Это будет выход 2.
Теперь узел `switch` будет направлять сообщения с `msg.payload: true` на свой первый выход, а сообщения с `msg.payload: false` — на второй.
Формирование команд с помощью `change`
Многие MQTT-устройства ожидают в качестве команды не булевы значения `true`/`false`, а строки `"1"`/`"0"` или `"ON"`/`"OFF"`. Узлы `change` идеально подходят для этого простого преобразования.
* Добавьте узел `change` и подключите его к первому выходу узла `switch`.
* В настройках узла `change`, установите `msg.payload` в значение (string) `1`.
* Назовите узел "Set Payload: 1".
* Добавьте второй узел `change` и подключите его к второму выходу узла `switch`.
* В настройках установите `msg.payload` в значение (string) `0`.
* Назовите узел "Set Payload: 0".
Теперь, когда `function` вернет `true`, сообщение пойдет по верхней ветке, и `msg.payload` станет равным `"1"`. Если `function` вернет `false`, `msg.payload` станет равным `"0"`.
Отправка команды в MQTT
Последний шаг — отправить сформированную команду на реле.
* Server: Выберите тот же MQTT-брокер.
* Topic: Укажите топик управления вашим реле. Например, `/devices/wb-mr6c_12/controls/K1/on`.
* QoS: `1` или `2` для гарантированной доставки управляющих команд.
* Retain: Оставьте `false`. Как правило, не нужно сохранять команду на брокере.
* Name: "Реле света (K1)".
Итоговый flow на этом шаге должен выглядеть так: выход из `function` идет на вход `switch`, а два выхода из `switch` идут в два соответствующих узла `change`, выходы которых объединяются на входе `mqtt out`.
---
Итоги, отладка и проверка результата
Поздравляем! Вы создали полноценный, надежный и профессиональный сценарий автоматизации, объединив несколько ключевых техник: сглаживание сигнала и гистерезис.
Итоговая схема flow
Ваш итоговый flow реализует следующую логическую цепочку:
[mqtt in] -> [smooth] -> [function] -> [switch] --+--> [change: "1"] --+--> [mqtt out]
(Датчик) (SMA) (Логика) (ON/OFF) | |
+--> [change: "0"] --+
Ключевые методы отладки
Если что-то работает не так, как ожидалось, используйте системный подход к диагностике, который мы рассматривали ранее:
* Убедитесь, что данные с датчика приходят в ожидаемом формате.
* Посмотрите, какие именно команды (`"1"`, `"0"`, `true`, `false`?) ваш flow отправляет в топик реле. Возможно, реле ожидает другой формат.
Практическое тестирование
Теперь самый интересный этап — проверка в реальных условиях.
- Тест на включение: Медленно закройте датчик освещенности рукой или плотной бумагой. Наблюдайте за значением "Сглаженные данные" в панели отладки. Когда оно опустится ниже `thresholdOn` (100 люкс), свет должен включиться.
- Тест на "мертвую зону": Слегка приоткройте датчик так, чтобы освещенность была между `thresholdOn` и `thresholdOff` (например, 120 люкс). Состояние света не должно измениться.
- Тест на выключение: Полностью уберите руку от датчика. Когда сглаженная освещенность поднимется выше `thresholdOff` (150 люкс), свет должен выключиться.
- Тест на помехоустойчивость: Быстро проведите рукой над датчиком. Благодаря узлу `smooth`, сглаженное значение не успеет сильно измениться, и свет не будет "моргать".
Возможные улучшения
Данный flow является отличной базой для дальнейших усложнений:
- Задержка на выключение: Можно добавить узел `trigger` перед узлом `change` для выключения, чтобы свет не выключался сразу, а только через 1-2 минуты после того, как стало достаточно светло.
- Интеграция с датчиком движения: Можно добавить логику, чтобы свет включался по этому сценарию только в том случае, если в помещении есть движение.
- Учет времени суток: С помощью узлов `within-time` или `cron-plus` можно сделать так, чтобы этот сценарий работал только в темное время суток, а днем был неактивен.
> 🔗 Связанный материал: Для более сложных сценариев, зависящих от времени, изучите наш модуль по работе с таймерами и расписаниями: `COURSE-06-M02: Сценарии автоматизации на основе времени и событий`.
В следующем модуле мы перейдем к рассмотрению дискретных входов и методам борьбы с "дребезгом контактов".