Сценарии для гостиниц: Статусы DND/MUR
Введение в логику DND/MUR: концепция и аппаратная часть
В современной гостиничной индустрии комфорт и приватность гостя являются ключевыми факторами успеха. Сценарии DND/MUR (Do Not Disturb / Make Up Room) — это базовый, но критически важный элемент системы автоматизации номера, позволяющий гостю легко и невербально коммуницировать свои потребности персоналу отеля.
- DND (Не беспокоить): Гость активирует этот статус, когда желает полной приватности. При активном DND система должна блокировать внешние сигналы (например, дверной звонок) и информировать персонал (уборка, рум-сервис) о том, что входить в номер запрещено.
- MUR (Убрать номер): Активировав этот статус, гость сообщает службе хаускипинга, что номер свободен и готов к уборке. Это оптимизирует работу персонала и повышает качество обслуживания.
> 📋 Ключевые понятия:
> * DND (Do Not Disturb): Статус, запрещающий персоналу входить в номер и беспокоить гостя.
> * MUR (Make Up Room): Статус-запрос на уборку номера.
> * Приоритет DND: Логическое правило, согласно которому активация статуса DND автоматически отменяет статус MUR. Приватность гостя всегда в приоритете.
Типовые аппаратные компоненты
Для реализации этого функционала требуется минимальный набор специализированного оборудования, работающего в связке:
Обзор протоколов для реализации
Связь между панелями, индикаторами и контроллером может быть организована по разным протоколам. Наиболее распространены в гостиничном сегменте:
- Modbus/RS-485: Самый популярный вариант для бюджетных и среднеценовых проектов. Устройства (панели, индикаторы) подключаются к общей двухпроводной шине RS-485. Это надежное и простое в монтаже решение, идеально подходящее для нашей платформы, оснащенной портами RS-485.
- KNX: Более дорогой и децентрализованный стандарт, популярный в Европе. В KNX-системах логика может быть запрограммирована непосредственно в устройствах, что снижает нагрузку на центральный контроллер, но усложняет и удорожает систему.
Логическая цепочка: от нажатия до отображения
Весь процесс от действия гостя до реакции системы можно представить в виде последовательной цепочки событий:
* `/devices/room_101_corridor_indicator/controls/dnd_led/on` со значением `1`.
* `/devices/room_101_corridor_indicator/controls/mur_led/on` со значением `0`.
* `hi/hotel/floor1/room101/status/dnd` со значением `true` для центральной системы мониторинга (PMS/iRidium).
Эта, на первый взгляд, сложная цепочка обеспечивает невероятную гибкость, масштабируемость и простоту диагностики, поскольку каждый этап можно отследить через стандартные инструменты, такие как MQTT-клиент.
---
Реализация на Modbus: Wirenboard и кнопочные панели
Практическая реализация DND/MUR на нашей платформе HI (аналогичной Wirenboard) начинается с физического подключения и настройки программного моста между шиной Modbus RTU и брокером MQTT. Эту роль выполняет служба `wb-mqtt-serial`.
Подключение панелей по RS-485
* Номер 101, прикроватная панель: ID 21.
* Номер 101, коридорный индикатор: ID 22.
> 💡 Подсказка: Используйте утилиту `modbus_client` в консоли контроллера для быстрой проверки связи с устройством и отладки карты регистров без участия `wb-mqtt-serial`. Это позволяет убедиться, что физическое подключение и параметры шины верны.
>
>
> # Прочитать состояние 1-го Discrete Input (адрес 0) с устройства с ID 21
> modbus_client --debug -m rtu -p /dev/ttyRS485-1 -b 9600 -s 1 -a 21 -t 0x02 -r 0 1
>
Настройка wb-mqtt-serial
Конфигурация драйвера `wb-mqtt-serial` происходит в текстовом файле `/etc/wb-mqtt-serial.conf`. Здесь мы описываем наши устройства, их Modbus-адреса и карту регистров, чтобы драйвер знал, что опрашивать и как это публиковать в MQTT.
Ниже представлен пример конфигурации для связки "прикроватная панель + коридорный индикатор" для номера 101.
Пример `wb-mqtt-serial.conf`:{
"ports": [
{
"path": "/dev/ttyRS485-1",
"baud_rate": 9600,
"parity": "N",
"data_bits": 8,
"stop_bits": 1,
"enabled": true,
"devices": [
{
"name": "Room_101_Panel",
"id": "room_101_panel",
"slave_id": "21",
"device_type": "DND_MUR_Panel",
"enabled": true,
"channels": [
{
"name": "DND Button",
"reg_type": "discrete",
"address": 0,
"type": "switch",
"readonly": true
},
{
"name": "MUR Button",
"reg_type": "discrete",
"address": 1,
"type": "switch",
"readonly": true
},
{
"name": "DND LED Feedback",
"reg_type": "coil",
"address": 0,
"type": "switch"
},
{
"name": "MUR LED Feedback",
"reg_type": "coil",
"address": 1,
"type": "switch"
}
]
},
{
"name": "Room_101_Corridor_Indicator",
"id": "room_101_corridor_indicator",
"slave_id": "22",
"device_type": "Corridor_Display",
"enabled": true,
"channels": [
{
"name": "DND LED",
"reg_type": "coil",
"address": 0,
"type": "switch"
},
{
"name": "MUR LED",
"reg_type": "coil",
"address": 1,
"type": "switch"
},
{
"name": "Doorbell Button",
"reg_type": "discrete",
"address": 0,
"type": "switch",
"readonly": true
}
]
}
]
}
]
}
Структура MQTT-топиков
После сохранения конфигурации и перезапуска службы `systemctl restart wb-mqtt-serial` драйвер начнет опрос устройств и создаст в MQTT-брокере следующую структуру топиков:
Для чтения состояния кнопок (входящие в Node-RED):- ` /devices/room_101_panel/controls/dnd_button ` — сюда будет приходить `1` при нажатии на кнопку DND и `0` при отпускании.
- ` /devices/room_101_panel/controls/mur_button ` — аналогично для кнопки MUR.
- ` /devices/room_101_corridor_indicator/controls/doorbell_button ` — для кнопки звонка.
- ` /devices/room_101_panel/controls/dnd_led_feedback/on ` — сюда нужно отправить `1` или `0` для управления подсветкой на прикроватной панели.
- ` /devices/room_101_panel/controls/mur_led_feedback/on `
- ` /devices/room_101_corridor_indicator/controls/dnd_led/on ` — для управления индикатором в коридоре.
- ` /devices/room_101_corridor_indicator/controls/mur_led/on `
Имея эту четкую и предсказуемую структуру топиков, мы можем перейти к созданию логики в Node-RED.
---
Обработка статусов в Node-RED: базовая логика и состояния
Теперь, когда аппаратная часть настроена и данные с кнопок поступают в MQTT, мы можем реализовать саму логику управления статусами DND/MUR в Node-RED. Главная задача — создать надежный конечный автомат (FSM), который будет хранить состояние номера и корректно реагировать на действия гостя.
> 🔗 Связанный материал: Как мы рассмотрели в уроке `COURSE-01-M05-L09: Логика ключ-карты`, статусы DND/MUR должны сбрасываться при выезде гостя, что определяется по отсутствию ключ-карты. Мы интегрируем этот сигнал в наш поток.
Создание потока в Node-RED
Поток будет состоять из нескольких ключевых узлов:
// Входящие сигналы
[mqtt in: DND button] --(msg.payload == '1')--> [function: DND/MUR Logic] --+
[mqtt in: MUR button] --(msg.payload == '1')--> [function: DND/MUR Logic] |
[link in: Guest Status] ----------------------> [function: DND/MUR Logic] |
|
// Исходящие команды |
+--> [switch: route by topic] --+-- [mqtt out: Corridor DND LED]
|
+-- [mqtt out: Corridor MUR LED]
|
+-- [mqtt out: Bedside DND LED]
|
+-- [mqtt out: Bedside MUR LED]
|
+-- [mqtt out: Publish Global Status]
Использование персистентного контекста
Состояние номера (DND, MUR или IDLE) должно сохраняться между перезагрузками контроллера. Для этого мы будем использовать контекст потока (flow context) с настроенным файловым хранилищем.
В файле `settings.js` вашего Node-RED убедитесь, что `contextStorage` настроен на использование файловой системы:
contextStorage: {
default: { module: "memory" },
file: { module: "localfilesystem" }
},
Это позволит нам использовать `flow.set('status', newStatus, 'file')` для надежного сохранения данных.
Реализация логики в узле Function
Основная магия происходит в центральном узле `Function`. Он принимает сообщения от кнопок и от системы статуса присутствия, обновляет состояние и генерирует команды.
// Инициализация. Выполняется один раз при старте потока.
// Получаем текущий статус из персистентного хранилища или устанавливаем 'IDLE'.
let status = flow.get('room_status', 'file') || 'IDLE';
// Входящее сообщение msg содержит topic, по которому мы определяем источник
let topic = msg.topic;
// 1. ОБРАБОТКА ВХОДЯЩИХ СИГНАЛОВ
if (topic.includes('dnd_button')) {
// Нажата кнопка DND
if (status === 'DND') {
// Повторное нажатие - отключаем DND
status = 'IDLE';
} else {
// Включаем DND (приоритет над MUR)
status = 'DND';
}
} else if (topic.includes('mur_button')) {
// Нажата кнопка MUR
if (status === 'MUR') {
// Повторное нажатие - отключаем MUR
status = 'IDLE';
} else if (status !== 'DND') {
// Включаем MUR, если не активен DND
status = 'MUR';
}
} else if (topic.includes('guest_status')) {
// Получили статус от логики ключ-карты
// msg.payload должен быть "CHECK_OUT"
if (msg.payload === 'CHECK_OUT') {
status = 'IDLE'; // Сбрасываем все статусы при выезде
}
}
// Сохраняем новое состояние в персистентный контекст
flow.set('room_status', status, 'file');
// Обновляем визуальный статус узла для отладки
node.status({ fill: "blue", shape: "dot", text: "Status: " + status });
// 2. ФОРМИРОВАНИЕ ИСХОДЯЩИХ КОМАНД
// Создаем массив сообщений для отправки в узел switch
// Это позволяет одним узлом function управлять множеством выходов
let commands = [];
// Команда для коридорного индикатора DND
commands.push({
topic: '/devices/room_101_corridor_indicator/controls/dnd_led/on',
payload: (status === 'DND') ? 1 : 0
});
// Команда для коридорного индикатора MUR
commands.push({
topic: '/devices/room_101_corridor_indicator/controls/mur_led/on',
payload: (status === 'MUR') ? 1 : 0
});
// Команды для подсветки кнопок на прикроватной панели
commands.push({
topic: '/devices/room_101_panel/controls/dnd_led_feedback/on',
payload: (status === 'DND') ? 1 : 0
});
commands.push({
topic: '/devices/room_101_panel/controls/mur_led_feedback/on',
payload: (status === 'MUR') ? 1 : 0
});
// Публикация обобщенного статуса для PMS/iRidium
commands.push({
topic: 'hi/hotel/floor1/room101/status/service',
payload: JSON.stringify({
"status": status, // "DND", "MUR", "IDLE"
"ts": Date.now()
})
});
// Возвращаем массив сообщений
return [commands];
Этот код реализует полный цикл: получает событие, обновляет состояние, сохраняет его и рассылает команды всем связанным устройствам и системам. Использование массива сообщений на выходе позволяет элегантно управлять всеми выходами из одного места.
---
Расширенная логика: Учет статуса присутствия и интеграция с PMS
Базовая логика DND/MUR — это только первый шаг. Для создания действительно интеллектуальной и удобной системы для гостя и персонала, эти статусы необходимо интегрировать с другими подсистемами номера, в первую очередь со статусом присутствия и центральной системой управления отелем (PMS).
> ⚠️ Внимание: Передача и хранение информации о статусах DND/MUR затрагивает приватность гостя. Убедитесь, что ваше решение соответствует локальным законам о защите персональных данных, таким как GDPR. Например, данные о DND не должны храниться дольше, чем это необходимо, и доступ к ним должен быть строго регламентирован.
Взаимосвязь со статусом присутствия
Как мы знаем из урока `COURSE-01-M05-L09`, статус присутствия гостя определяется наличием ключ-карты в карточном приемнике. Это дает нам два основных состояния: «Гость в номере» (CHECK_IN) и «Гость покинул номер» (GUEST_OUT).
Интеграция с этими состояниями позволяет реализовать умные правила:
* Подход 1 (сбрасывать): Гость ушел — приватность не нужна. Просто и понятно.
* Подход 2 (не сбрасывать): Гость мог выйти ненадолго и не хочет, чтобы в его отсутствие входили в номер (например, для пополнения мини-бара). Этот подход более ориентирован на гостя. При возвращении (вставке карты) статус DND продолжает действовать. Полный сброс происходит только при полном выселении (CHECK_OUT). Выбор подхода зависит от политики отеля.
Интеграция с PMS (Property Management System)
PMS — это сердце IT-инфраструктуры отеля. Она управляет бронированиями, счетами, регистрацией гостей и работой персонала. Интеграция нашей системы автоматизации с PMS выводит управление на новый уровень, позволяя:
- Централизованно отображать статусы: Персонал на ресепшене видит статусы DND/MUR для всех номеров в интерфейсе PMS.
- Оптимизировать работу хаускипинга: Система может автоматически формировать задания на уборку в PMS на основе статусов MUR.
- Синхронизировать статус заселения: При регистрации гостя (Check-in) в PMS, система автоматизации может автоматически переводить номер в приветственный сценарий. При выселении (Check-out) — сбрасывать все статусы (DND, MUR) и переводить номер в энергосберегающий режим.
Протоколы для обмена с PMS
Интеграция с PМS — сложная задача, так как каждая система имеет свой API или протокол. Наиболее известным является FIAS (Fidelio Interface Application Specification), разработанный компанией Micros-Fidelio (сейчас Oracle Hospitality).
- Принцип работы FIAS: Обмен данными обычно происходит через TCP/IP соединение. Наша система автоматизации выступает клиентом, который подключается к серверу FIAS (части PMS) и обменивается строго форматированными текстовыми сообщениями.
- Пример сообщения:
* Запрос от PMS на Check-in: `GI|RN101|...` (Guest In)
Для реализации такой интеграции в Node-RED обычно используют узлы `tcp request` или пишут кастомный код в `function` для поддержания постоянного соединения и парсинга сообщений. Часто для этого требуется специализированный шлюз или коннектор, предоставляемый производителем PMS.
---
Визуализация и управление в iRidium pro
Для эффективной работы персонала, особенно службы хаускипинга, необходим наглядный и удобный инструмент для мониторинга статусов всех номеров. Платформа iRidium pro отлично подходит для создания таких панелей управления, которые могут работать на планшетах, стационарных ПК или специализированных панелях.
Создание интерфейса для персонала в Iridium Studio
В среде разработки Iridium Studio создается графический интерфейс. Для нашего случая это может быть план этажа или просто список номеров. Для каждого номера на экране размещаются иконки, отображающие статусы DND и MUR.
- Элемент "Номер 101"
* Иконка "MUR" (например, зеленая щетка)
* Текстовое поле для отображения статуса гостя (Заселен / Свободен)
Подписка сервера iRidium на MQTT-топики
Сервер iRidium выступает в роли MQTT-клиента. В настройках проекта необходимо настроить подключение к нашему MQTT-брокеру и создать каналы (Channels) и теги (Tags), которые будут соответствовать MQTT-топикам.
Мы будем использовать обобщенный топик состояния, который наш поток Node-RED уже публикует: `hi/hotel/floor1/room101/status/service`.
* Channel: `Room_101_Service_Status_Topic`
* Topic: `hi/hotel/floor1/room101/status/service`
* Type: `Subscribe`
* Tag (Feedback): `Room_101_Service_Status`
* Channel: `Room_101_Service_Status_Topic`
Теперь тег `Room_101_Service_Status` на сервере iRidium будет содержать JSON-строку, которую отправляет Node-RED: `{"status": "DND", "ts": 1678886400000}`.
Привязка графических элементов к тегам
Ключевой этап — это "оживление" интерфейса.
* Event: `In Value Changed` (При изменении значения тега)
* Driver: `Server Tags`
* Feedback: `Room_101_Service_Status`
* Action: `Set Visibility` (Установить видимость)
// Сделать иконку видимой, если в JSON поле status равно 'DND'
return JSON.parse(value).status === 'DND';
Аналогичная логика настраивается для иконки MUR (условие `JSON.parse(value).status === 'MUR'`). Таким образом, интерфейс будет автоматически обновляться в реальном времени при изменении статуса номера.
Пример скрипта на iRidium Script (JS)
Для более сложной логики, например, подсчета количества номеров, требующих уборки, можно использовать скрипты iRidium.
Этот скрипт может запускаться по таймеру или при изменении любого статуса номера.
// iRidium Script для подсчета номеров с MUR
// Предполагается, что у нас есть теги вида Room_101_Service_Status, Room_102_Service_Status и т.д.
function countRoomsToClean() {
var roomCount = 10; // Общее количество номеров на этаже
var cleanRequestCount = 0;
for (var i = 1; i <= roomCount; i++) {
var roomNumber = 100 + i;
var tagName = 'Room_' + roomNumber + '_Service_Status';
var tagValue = IR.GetVariable('Server.Tags.' + tagName);
if (tagValue) {
try {
var statusData = JSON.parse(tagValue);
if (statusData.status === 'MUR') {
cleanRequestCount++;
}
} catch (e) {
IR.Log('Error parsing status for room ' + roomNumber);
}
}
}
// Обновляем тег, к которому привязан виджет-счетчик на панели
IR.SetVariable('Server.Tags.Rooms_To_Clean_Count', cleanRequestCount);
}
// Запускаем функцию при старте и подписываемся на изменения
IR.AddListener(IR.EVENT_START, 0, countRoomsToClean);
// Также нужно настроить вызов этой функции при изменении любого из тегов статусов
---
Итоги и лучшие практики
В этом уроке мы детально разобрали весь жизненный цикл одного из самых важных сценариев гостиничной автоматизации — управления статусами DND (Не беспокоить) и MUR (Убрать номер). Мы прошли путь от физического подключения оборудования до создания интеллектуальной логики и ее визуализации для персонала.
Краткий обзор всей цепочки реализации
Важность отказоустойчивости
Статус, установленный гостем, не должен теряться. Ключевым элементом надежности является использование персистентного контекста, настроенного на хранение в файловой системе (`localfilesystem`). Это гарантирует, что даже после перезагрузки контроллера из-за сбоя питания или обновления ПО, все статусы номеров будут восстановлены, и система продолжит работать корректно. Обязательно используйте узлы `Catch` для перехвата ошибок (например, отказ Modbus-устройства) и их логирования.
Рекомендации по проектированию
- Масштабируемая структура MQTT-топиков: С самого начала используйте иерархическую структуру топиков, которая легко расширяется. Например: `[проект]/[здание]/[этаж]/[номер_комнаты]/[подсистема]/[параметр]`.
* Это упрощает подписки, управление правами доступа и интеграцию со сторонними системами.
- Документирование логики: Всегда оставляйте комментарии в узлах `Function` и `Comment` в Node-RED. Описывайте, почему приняты те или иные логические решения (например, "DND имеет приоритет над MUR"). Это сэкономит часы времени при дальнейшей поддержке и модификации системы.
Альтернативные подходы
Стоит отметить, что существуют и другие способы реализации. Например, в системах на базе KNX подобная логика часто программируется непосредственно на уровне самих устройств. Кнопка и исполнительное устройство (реле, диммер) могут быть связаны групповым адресом и работать автономно. Центральный контроллер в такой архитектуре выполняет скорее функции мониторинга и шлюза в IP-сеть, а не основной логической обработки. Этот подход более отказоустойчив на уровне отдельного номера, но значительно дороже и требует специализированных навыков для программирования.
Выбор между централизованной логикой (как в нашем случае с Node-RED) и децентрализованной (KNX) зависит от бюджета проекта, требований к отказоустойчивости и квалификации инженеров.
Что дальше
В следующем уроке мы рассмотрим еще один важный аспект гостиничной автоматизации — управление доступом, но уже не на уровне номера, а для служебных помещений, и его интеграцию с расписанием работы персонала.