SCN-SAFETY-020: Контроль доступа (уведомления об открытии дверей/окон в режиме 'Away')
Введение в сценарий 'Контроль доступа'
Цель данного урока — разработать и внедрить один из ключевых сценариев безопасности умного дома: `SCN-SAFETY-020: Контроль доступа`. Его основная задача — непрерывно отслеживать состояние дверей и окон и немедленно уведомлять владельца о любом несанкционированном открытии, когда система находится в режиме охраны. Этот сценарий является важнейшим элементом первого эшелона защиты, обеспечивая своевременную реакцию на попытку вторжения.Логика сценария проста и эффективна:
Этот сценарий не существует в вакууме. Он является частью комплексной экосистемы безопасности, которую мы выстраиваем в рамках нашей академии. Его эффективность многократно возрастает при взаимодействии с другими сценариями:
- Режимы дома: `Контроль доступа` активен только при установке режимов 'Вне дома' ('Away') или 'Отпуск' ('Vacation'). В обычном режиме 'Дома' ('Home') сценарий деактивирован, чтобы не создавать ложных тревог при повседневном использовании дверей и окон. Логика управления режимами была подробно рассмотрена в модуле `COURSE-07-M01`.
- Аварийное освещение (`SCN-SAFETY-015`): При обнаружении вторжения сценарий `SCN-SAFETY-020` может отправить команду на активацию сценария аварийного освещения. Это создаст эффект присутствия и дезориентирует злоумышленника, включая свет во всем доме или в определенных зонах.
- Имитация присутствия (`SCN-SAFETY-014`): В режиме 'Отпуск' тревога от датчика открытия может не просто отправить уведомление, но и активировать более агрессивный режим имитации присутствия: включить громкую музыку, начать хаотично включать и выключать свет, имитируя панику хозяев.
Для реализации сценария нам потребуется следующий набор компонентов:
- Датчики открытия: Магнитоконтактные датчики (герконы), установленные на всех окнах и входных дверях. Это могут быть как проводные, так и беспроводные (Zigbee, LoRaWAN) модели.
- Контроллер HI: Наше основное устройство, которое будет собирать данные с датчиков, обрабатывать логику и отправлять команды.
- MQTT-брокер: Программный компонент, обеспечивающий обмен сообщениями между датчиками, контроллером и другими системами.
- Система уведомлений: Настроенный сервис для отправки push-уведомлений, например, бот в Telegram.
Таким образом, `SCN-SAFETY-020` — это не просто уведомление, а триггер, запускающий цепную реакцию в системе безопасности вашего объекта.
---
Аппаратная часть и структура MQTT-топиков
Надежность сценария напрямую зависит от качества сбора данных с оконечных устройств. Рассмотрим физическое подключение датчиков и стандарт организации обмена данными через MQTT.
Физическое подключение датчиков
Проводные герконы (магнитоконтактные датчики) являются наиболее надежным и помехозащищенным решением. Они представляют собой простой "сухой контакт", который замыкается или размыкается при приближении или удалении магнита.
* Один контакт геркона подключается к общей клемме `GND` на контроллере HI.
* Второй контакт геркона подключается к одному из универсальных входов, сконфигурированному как дискретный вход (например, `UI-15`).
* Когда дверь или окно закрыты, контакт замкнут, и вход `UI-15` соединен с `GND` (логический '0').
* При открытии контакт размыкается, и на входе `UI-15` (благодаря внутреннему подтягивающему резистору контроллера) появляется высокий уровень (логическая '1').
* Датчики Zigbee или LoRaWAN могут быть более удобны для установки на уже готовых объектах. Они передают свое состояние на соответствующий шлюз (например, Zigbee2MQTT), который, в свою очередь, транслирует эти данные в общий MQTT-брокер. Логика в Node-RED при этом остается практически неизменной, меняется лишь источник данных (MQTT-топик).
Трансляция состояний в MQTT
После физического подключения необходимо настроить программную часть контроллера для публикации состояний входов в MQTT. Для контроллеров на базе Linux (Debian) часто используется утилита `wb-mqtt-serial`, которая автоматически опрашивает сконфигурированные порты и входы и публикует их состояния.
Типичная структура топика от `wb-mqtt-serial` выглядит следующим образом:
`/devices/wb-gpio/controls/DI1_15`
А сообщение о состоянии публикуется в дочерний топик `/on`:
`/devices/wb-gpio/controls/DI1_15/on`
- `msg.payload = '1'`: Дверь или окно открыты (контакт разомкнут).
- `msg.payload = '0'`: Дверь или окно закрыты (контакт замкнут).
> 💡 Подсказка: Для упрощения отладки и масштабирования именуйте MQTT-топики в соответствии с физическим расположением датчиков, например: `/house/floor_1/living_room/window_1/state`. Это можно сделать либо на уровне конфигурации шлюза (если он это позволяет), либо с помощью промежуточного потока в Node-RED, который перепубликует сообщения из технических топиков в семантические.
Важность флага 'Retain'
При публикации состояний датчиков критически важно устанавливать флаг `Retain`. Сообщение, опубликованное с этим флагом, сохраняется на MQTT-брокере. Когда новый клиент (в нашем случае — Node-RED после перезагрузки контроллера) подписывается на этот топик, он немедленно получает последнее сохраненное сообщение.
Почему это важно: Без флага `Retain`, после перезагрузки контроллера Node-RED не будет знать текущее состояние дверей и окон до тех пор, пока они не изменят свое состояние. Если окно было оставлено открытым, а затем произошла перезагрузка, система не поднимет тревогу, так как "пропустила" событие открытия. С флагом `Retain` Node-RED сразу после старта получит сообщение `'1'` (открыто) и сможет корректно отработать логику охраны.Пример публикации из терминала с флагом `Retain`:
mosquitto_pub -h localhost -t "/house/floor_1/main_door/state" -m "1" -r
В данном примере `-r` — это флаг, указывающий брокеру сохранить это сообщение.
---
Логика в Node-RED: Фильтрация по режиму дома
Теперь перейдем к созданию самого потока в Node-RED, который будет реализовывать логику сценария `SCN-SAFETY-020`. Основа потока — фильтрация событий от датчиков в зависимости от текущего режима дома.
> 🔗 Связанный материал: Логика работы с режимами 'Away', 'Home', 'Vacation' подробно рассмотрена в модуле `COURSE-07-M01`. Рекомендуем повторить урок `COURSE-07-M01-L01` о едином объекте состояния, так как мы будем использовать эту концепцию.
Шаг 1: Подписка на события от датчиков
Первым узлом в нашем потоке будет `mqtt in`. Он должен подписаться на топики всех релевантных датчиков. Чтобы не создавать отдельный узел для каждой двери и окна, мы используем подстановочные знаки (wildcards).
- Настройка узла `mqtt in`:
* Topic: Укажите топик с использованием wildcard `+` или `#`.
* Пример для технических топиков: `/devices/wb-gpio/controls/DI1_+/on` (подписка на все входы `DI1_` с 1 по N).
* Пример для семантических топиков: `/house/floor_1/+/+/state` (подписка на состояние всех устройств на первом этаже).
* QoS: `1` или `2` для гарантированной доставки.
* Output: `a parsed JSON object` (если датчики шлют JSON) или `a string` (если шлют '1'/'0').
+-----------+
[MQTT: /house/+/+/+/state] -> | |
+-----------+
Шаг 2: Фильтрация по событию "Открытие"
Нас интересуют только события, когда дверь или окно открываются. Закрытие не является тревожным событием. Для этой фильтрации используем узел `switch`.
- Настройка узла `switch` (Проверка открытия):
* Правило 1: `==` (строка) `1` -> Выход 1
Это правило сработает для датчиков, передающих строку '1'. Если ваши датчики передают `true` (boolean) или JSON, правило нужно скорректировать.*
+--------------------+ +----------------------+
[MQTT In] -> | Switch: payload==1 ? | -> | |
+--------------------+ +----------------------+
Шаг 3: Фильтрация по режиму дома
Это ключевой шаг, который активирует логику только тогда, когда в доме никого нет. Мы будем проверять значение, хранящееся в едином объекте состояния `global.states`.
- Настройка узла `switch` (Проверка режима дома):
* Правило 1: `==` (строка) `away` -> Выход 1
* Правило 2: `==` (строка) `vacation` -> Выход 1
* Правило 3: `otherwise` -> Выход 2 (этот выход никуда не подключаем)
Полная начальная цепочка выглядит так:
+--------------------+ +-------------------------------+ +----------------+
[MQTT In] -> | Switch: payload==1 ? | -> | Switch: global.states.system.mode | -> | Дальнейшая |
| (Проверка открытия) | | == "away" or "vacation"? | | обработка... |
+--------------------+ +-------------------------------+ +----------------+
Теперь любое сообщение об открытии в режиме охраны будет проходить дальше по потоку, а все остальные события будут отфильтрованы.
Шаг 4: Формирование контракта сообщения
Для дальнейшей обработки полезно привести сообщение к стандартному контракту сообщения, как было определено в методологии. Для этого после MQTT-узла ставится `function` узел.
- Код для узла `function` (Форматирование):
// Входящий msg.topic, например: /house/floor_1/living_room/window_1/state
// Входящий msg.payload: '1'
const parts = msg.topic.split('/');
const device_name = `${parts[2]}_${parts[3]}_${parts[4]}`; // -> floor_1_living_room_window_1
// Формируем стандартизированный payload
msg.payload = {
value: msg.payload === '1' ? true : false, // Приводим к boolean
source: device_name,
raw_topic: msg.topic,
ts: Date.now()
};
// Теперь msg.payload.value - это булево значение, которое легко проверять
return msg;
С этим узлом последующие `switch` узлы будут проверять `msg.payload.value == true`.
---
Уведомления и защита от ложных срабатываний
Когда событие прошло все фильтры, нашей задачей становится отправить надежное и информативное уведомление. Однако перед отправкой необходимо решить проблему ложных срабатываний.
Шаг 5: Защита от ложных срабатываний (Debounce)
Чтобы отфильтровать ложные срабатывания, вызванные "дребезгом контактов", мы используем узел `trigger` в режиме `debounce`. Он отправит тревожное уведомление только после того, как состояние датчика останется стабильным в течение заданного времени (например, 1 секунда). Это предотвратит отправку шквала сообщений на одно реальное событие.
> 🔗 Связанный материал: Подробная механика работы узла `trigger` для создания задержек и подавления дребезга (debounce) рассматривается в уроке `COURSE-07-M02-L01: Защита от ложных срабатываний`.
Для нашего сценария достаточно добавить узел `trigger` с задержкой в 1 секунду, который будет срабатывать индивидуально для каждого датчика (`for each msg.topic`).
+---------------------------+ +-------------------+
... [Switch] -> | Trigger: wait 1 sec/topic | -> | Формирование |
+---------------------------+ | уведомления |
+-------------------+
Шаг 6: Формирование информативного уведомления
Уведомление "Тревога!" малоинформативно. Пользователь должен сразу понять, где и когда произошло событие. Для этого используем узел `template`.
- Настройка узла `template`:
* Format: `Mustache Template`
* Template:
🚨 ТРЕВОГА: Контроль доступа! 🚨
Обнаружено несанкционированное открытие.
Объект: {{payload.source}}
Время: {{payload.ts}}
* Output as: `Plain Text`
Для форматирования времени в читаемый вид можно использовать JSONata или дополнительный `function` узел перед `template`.
- Код для узла `function` (Форматирование времени):
// Конвертируем timestamp в читаемую строку
const eventTime = new Date(msg.payload.ts).toLocaleString('ru-RU', { timeZone: 'Europe/Moscow' });
msg.payload.ts = eventTime; // Перезаписываем для использования в шаблоне
return msg;
Шаг 7: Отправка уведомления
Последний шаг — отправка сформированного сообщения. Наиболее популярным решением является Telegram из-за его надежности и удобного API. Для этого используется узел из палитры `node-red-contrib-telegrambot-plus` или аналогичной.
- Настройка узла `telegram sender`:
* Users/Chats: Укажите `Chat ID` пользователя или группы, куда отправлять уведомления.
* Content: `msg.payload` (будет взято из узла `template`).
* Options: можно включить `disable_notification: false`, чтобы уведомление пришло со звуком.
// Пример сообщения, которое получит узел Telegram
{
"payload": "🚨 ТРЕВОГА: Контроль доступа! 🚨\n\nОбнаружено несанкционированное открытие.\n\nОбъект: floor_1_living_room_window_1\nВремя: 15.10.2023, 03:15:22"
}
---
Итоги и комплексная интеграция сценария
Мы успешно спроектировали и пошагово реализовали сценарий `SCN-SAFETY-020`. Давайте соберем все элементы в единый, законченный поток и рассмотрим пути его дальнейшего развития.
Полный поток Node-RED
Финальная версия потока объединяет все рассмотренные шаги:
+-----------+ +-------------+ +--------------------+ +---------------------------------+
[MQTT In]--->| Function: |--->| Switch: |--->| Switch: global.states.system.mode |
| Формат | | payload.value | | == "away" or "vacation" ? |
| контракта | | == true ? | | |
+-----------+ +-------------+ +---------------------------------+
| (тревога)
v
+-------------------------+ +---------------------+ +--------------------+
...<-| Telegram Sender |<---| Template: |<---| Trigger: wait 1s |
+-------------------------+ | Сообщение тревоги | | (Debounce/topic) |
+---------------------+ +--------------------+
Возможности расширения сценария
Созданный нами поток является надежной базой, которую можно и нужно расширять:
- Активация сирены: Параллельно с отправкой уведомления можно послать команду на включение реле, к которому подключена сирена. Важно предусмотреть логику ее автоматического отключения через 1-2 минуты.
- Интеграция с видеонаблюдением: Если на объекте есть IP-камеры, можно по событию тревоги отправить камере команду сделать снапшот и прикрепить его к уведомлению в Telegram.
- Email-уведомления: В качестве резервного канала можно дублировать отправку тревоги на электронную почту с помощью узла `e-mail`.
- Логирование тревог: Каждое событие тревоги необходимо записывать в базу данных (MySQL) для последующего анализа и ведения журнала безопасности.
Масштабирование и финальное тестирование
Благодаря использованию MQTT-wildcards, добавление нового датчика в систему не требует изменения потока в Node-RED. Достаточно установить геркон, подключить его и убедиться, что его состояние публикуется в MQTT-топике, соответствующем маске подписки (например, `/house/floor_2/bedroom/window_1/state`).
Чек-лист финального тестирования:Что дальше
В следующем уроке мы рассмотрим сценарий `SCN-SAFETY-030: Паник-кнопка`, который позволяет пользователю вручную активировать режим тревоги, запуская сирену, освещение и отправку уведомлений всем членам семьи, даже если датчики не были сработаны.