Сценарии для гостиниц: Отчетность для персонала
Введение в системы отчетности для гостиниц
🔗 Связанный материал: В данном уроке мы будем использовать концепции, заложенные в уроках «Логика ключ-карты» (COURSE-01-M05-L09) и «Статусы DND/MUR» (COURSE-01-M05-L10). Убедитесь, что вы хорошо усвоили материал этих уроков, так как отчетность является логическим продолжением сбора данных о состоянии номера.
Эффективное управление современным отелем невозможно без оперативного получения точной информации. Системы отчетности, построенные на базе платформы автоматизации, становятся цифровой нервной системой объекта, поставляя критически важные данные для различных служб. Ценность таких систем заключается в переходе от реактивной модели управления («устраняем проблему, когда о ней сообщили») к проактивной («предотвращаем проблему, видя ее предпосылки»).
Рассмотрим роль систем отчетности для ключевых служб отеля:
* Задача: Максимально быстро и эффективно производить уборку номеров.
* Ценность отчета: Персонал получает в реальном времени на планшет или общий экран информацию о статусе каждого номера: гость вышел (карта извлечена), гость находится в номере (карта вставлена), включен статус «Не беспокоить» (DND), запрошена уборка (MUR), номер освобожден после выезда. Это позволяет оптимизировать маршруты горничных, избегать беспокойства гостей и ускорять подготовку номеров для нового заселения.
* Задача: Поддержание исправной работы всего оборудования в отеле.
* Ценность отчета: Система автоматически генерирует алерты (тревожные сообщения) о нештатных ситуациях: сработал датчик протечки под фанкойлом, температура в номере вышла за допустимые пределы, окно открыто более 15 минут при работающем кондиционере, оборудование (например, вентиляционная установка) сообщило код ошибки по Modbus. Это позволяет инженерам немедленно реагировать на инциденты, часто еще до того, как гость заметит проблему.
* Задача: Обеспечение комфортного пребывания гостей и управление номерным фондом.
* Ценность отчета: Сотрудники ресепшн могут видеть реальный статус присутствия гостя, а не только информацию из PMS (системы управления отелем). Это помогает при решении спорных ситуаций, а также позволяет оперативно отвечать на вопросы («Могу я попросить горничную зайти в мой номер сейчас?» — «Одну минуту... Вижу, в номере сейчас никого нет, я передам вашу просьбу»).
Архитектура системы отчетности
Для построения надежной системы отчетности на платформе HI используется многоуровневая архитектура:
- Уровень устройств: Физические датчики и исполнительные механизмы, являющиеся источниками данных. На нашем контроллере это универсальные входы для датчиков открытия окон, «сухих контактов» от картоприемников, шина RS-485 (Modbus) для панелей DND/MUR и климатических систем.
- Транспортный уровень: Протокол MQTT служит единой шиной данных, по которой все события от устройств передаются в стандартизированном формате. Каждое событие (вставка карты, нажатие кнопки DND) публикуется в свой уникальный топик.
- Логический уровень: Ядром системы является контроллер с Node-RED. Здесь происходит агрегация данных: Node-RED подписывается на все нужные MQTT-топики, обрабатывает события, хранит актуальное состояние всех номеров и принимает решения о генерации отчетов или алертов.
- Уровень представления: Данные, обработанные в Node-RED, передаются в системы визуализации для персонала. Это могут быть простые веб-дашборды (созданные с помощью `node-red-dashboard`), специализированные панели визуализации (например, iRidium) или мобильные приложения.
Все отчеты можно разделить на три функциональные категории:
- Оперативные: Отображают текущий срез состояния («что происходит прямо сейчас»). Пример: дашборд для хаускипинга.
- Эксплуатационные: Сигнализируют о событиях, требующих немедленного вмешательства. Пример: SMS-оповещение инженеру о протечке.
- Аналитические: Собирают исторические данные для последующего анализа. Пример: отчет по среднесуточному энергопотреблению номеров за месяц для выявления аномалий.
---
Проектирование структуры MQTT-топиков для отчетности
Основой любой масштабируемой системы автоматизации на базе MQTT является хорошо продуманная и стандартизированная иерархия топиков. Неверно спроектированная структура на начальном этапе приводит к хаосу и сложностям в поддержке системы по мере ее роста. Для гостиничных проектов мы рекомендуем придерживаться следующей структуры:
`hotel/{building_id}/{floor_id}/{room_number}/{device_type}/{parameter}`
Разберем каждый компонент:
| Уровень | Назначение | Пример |
| ----------------- | ------------------------------------------------------------------------------------------------------ | -------------------------- |
| `hotel` | Корневой элемент, идентифицирующий проект как относящийся к гостинице. | `hotel` |
| `{building_id}` | Идентификатор корпуса или здания. Полезно для крупных комплексов. Для одного здания можно использовать `main`. | `main`, `spa_building` |
| `{floor_id}` | Номер этажа, обязательно с ведущим нулем для корректной сортировки (`01`, `02`, ... `12`). | `03`, `11` |
| `{room_number}` | Номер комнаты. | `301`, `1105` |
| `{device_type}` | Тип устройства или логической сущности, генерирующей событие. | `keycard`, `panel`, `window` |
| `{parameter}` | Конкретный параметр или команда. | `present`, `dnd`, `status` |
Практические примеры топиков:
- Гость вставил карту в картоприемник номера 301:
* Сообщение (Payload): `true`
- Гость нажал кнопку "Не беспокоить" (DND) на панели у входа:
* Сообщение (Payload): `true`
- Горничная нажала кнопку "Запросить уборку" (MUR):
* Сообщение (Payload): `true`
- Датчик на окне в номере 301 сообщил, что окно открыто:
* Сообщение (Payload): `open`
- Климатическая система сообщила текущую температуру:
* Сообщение (Payload): `22.5`
Такая структура позволяет с легкостью получать данные, используя wildcards (символы подстановки) в MQTT-подписках в Node-RED.
- `+` (плюс) — заменяет один уровень в иерархии.
- `#` (решетка) — заменяет один или несколько уровней в конце топика.
Примеры подписок:
- Получать все события от всех устройств в номере 301:
- Получать информацию о состоянии ключ-карт во всех номерах на 3-м этаже:
- Получать информацию о статусах DND и MUR со всех панелей во всем отеле:
> 💡 Подсказка: Существует альтернативный подход, при котором вместо множества атомарных топиков используется один «статусный» топик на номер, например, `hotel/main/03/301/state`. В `payload` такого топика передается полный JSON-объект, описывающий всё состояние номера: `{ "dnd": false, "mur": true, "present": true, "window": "closed" }`.
> Плюсы: Снижается количество MQTT-сообщений и нагрузка на брокер. Упрощается логика получения полного состояния номера одним сообщением.
> Минусы: Усложняется логика обработки (необходимо парсить JSON и определять, какое именно поле изменилось). Увеличивается размер каждого сообщения.
> Выбор между этими двумя подходами зависит от масштаба проекта и требований к производительности. Для большинства объектов до 200-300 номеров оба подхода являются рабочими.
---
Практика: Агрегация данных о номерах в Node-RED
Теперь, когда мы спроектировали структуру топиков, создадим в Node-RED поток, который будет собирать все эти данные и хранить их в удобном для дальнейшей работы виде. Этот процесс называется агрегацией данных. Мы будем использовать контекст Node-RED (`global context`) в качестве временной базы данных для хранения актуального состояния всех номеров.
Задача: Создать поток, который подписывается на все события от всех номеров, парсит топик, чтобы определить номер комнаты и тип события, и обновляет соответствующую информацию в `global context`. Flow Diagram (ASCII): +-----------+ +--------------------------+ +----------------------------+
[mqtt in]----->| function |----->| debug |
(Subscribe) (Parse Topic & (Show Parsed & Stored Data)
| Update Context) |
+-----------+ +----------------------------+
Шаг 1: Настройка узла 'mqtt in'
Шаг 2: Создание узла 'function' для обработки данных
Это ядро нашей логики. Здесь мы будем разбирать топик и обновлять глобальный объект состояний.
// Получаем топик и payload из входящего сообщения
const topic = msg.topic;
const payload = msg.payload;
// Разбиваем топик на части по символу '/'
const parts = topic.split('/');
// Проверяем, что топик соответствует нашей структуре (hotel/main/floor/room/device/param)
if (parts.length !== 6 || parts[0] !== 'hotel') {
// Если структура неверна, логируем предупреждение и прекращаем обработку
node.warn(`Некорректный формат топика: ${topic}`);
return null;
}
// Извлекаем нужные нам данные из топика
const floorId = parts[2];
const roomNumber = parts[3];
const deviceType = parts[4];
const parameter = parts[5];
// Инициализируем глобальный объект для хранения состояний, если он еще не существует
// Это защищает от ошибок при первом запуске
global.set('hotel.rooms', global.get('hotel.rooms') || {});
// Получаем текущий объект всех комнат
let rooms = global.get('hotel.rooms');
// Инициализируем объект для конкретной комнаты, если он еще не существует
if (!rooms[roomNumber]) {
rooms[roomNumber] = {
last_update: null
};
}
// Обновляем конкретный параметр для конкретной комнаты
// Например, если пришел топик hotel/main/03/301/panel/dnd с payload 'true',
// то в rooms['301'] будет создано/обновлено свойство 'dnd' со значением true
// Мы используем комбинацию deviceType и parameter для создания уникального ключа
const stateKey = `${deviceType}_${parameter}`; // например, 'panel_dnd' или 'keycard_present'
rooms[roomNumber][stateKey] = payload;
// Добавляем временную метку последнего обновления
rooms[roomNumber].last_update = new Date().toISOString();
// Сохраняем обновленный объект комнат обратно в global context
global.set('hotel.rooms', rooms);
// Для отладки, передаем дальше информацию о том, что было сделано
msg.payload = {
room: roomNumber,
key: stateKey,
value: payload,
info: "Контекст успешно обновлен"
};
// Отображаем статус на самом узле для быстрой визуальной диагностики
node.status({ fill: "green", shape: "dot", text: `Обновлен ${roomNumber}: ${stateKey}=${payload}` });
return msg;
Шаг 3: Отладка
Пример структуры `global.hotel.rooms` после получения нескольких сообщений:
{
"301": {
"last_update": "2023-10-27T12:30:05.123Z",
"keycard_present": "true",
"panel_dnd": "false",
"panel_mur": "true"
},
"302": {
"last_update": "2023-10-27T12:28:15.456Z",
"keycard_present": "false",
"panel_dnd": "false",
"panel_mur": "false",
"window_status": "open"
}
}
Теперь у нас есть единый источник правды о состоянии всех номеров, который обновляется в реальном времени.
---
Пример: Создание отчета для службы хаускипинга
Имея агрегированные данные, мы можем легко создать простой, но эффективный дашборд для персонала. Мы будем использовать узлы из пакета `node-red-dashboard`. Если он у вас не установлен, установите его через `Manage Palette`.
Задача: Создать веб-страницу, на которой будет отображаться таблица со статусами всех номеров на этаже: Занятость (по ключ-карте), DND и MUR.> ⚠️ Внимание: Стандартный контекст Node-RED (`global context`) хранится в оперативной памяти и будет утерян при перезапуске контроллера или Node-RED. Для реальных проектов критически важно настроить персистентный контекст (persistent context). На контроллере HI это делается в файле `settings.js`, где можно указать `contextStorage: { default: "file" }`. Это заставит Node-RED сохранять контекст в файловой системе, обеспечивая его восстановление после перезагрузки.
Flow Diagram (ASCII): +-----------+ +---------------------+ +----------------+
[inject]------>| function |------>| ui_template |
(Каждые 5 сек) | (Генерация HTML) | (Дашборд хаускипинга) |
+-----------+ +---------------------+
Шаг 1: Настройка потока
Шаг 2: Код для узла `function` (Генерация HTML)
Вставьте следующий код в узел `function`:
// Получаем объект с состояниями комнат из глобального контекста
const rooms = global.get('hotel.rooms') || {};
// Начинаем формировать HTML-таблицу. Добавляем стили для раскраски статусов
let html = `
Номер
Присутствие
Не беспокоить (DND)
Запрос уборки (MUR)
`;
// Получаем ключи (номера комнат) и сортируем их для упорядоченного вывода
const roomNumbers = Object.keys(rooms).sort();
// Проходим по каждой комнате
for (const roomNumber of roomNumbers) {
const room = rooms[roomNumber];
// Определяем статусы. Используем '??' для значения по умолчанию, если данных еще нет.
const isPresent = (room.keycard_present === 'true');
const isDnd = (room.panel_dnd === 'true');
const isMur = (room.panel_mur === 'true');
// Формируем HTML-код для статусов
const presentText = isPresent ? 'Гость в номере' : 'Свободно';
const dndText = isDnd ? 'ДА' : 'Нет';
const murText = isMur ? 'ДА' : 'Нет';
// Добавляем строку в таблицу. Если гость в номере, подсвечиваем всю строку.
html += `
${roomNumber}
${presentText}
${dndText}
${murText}
`;
}
// Завершаем таблицу
html += '
';
// Помещаем сгенерированный HTML в msg.payload, который будет принят узлом ui_template
msg.payload = html;
return msg;
Шаг 3: Настройка 'ui_template'
Теперь у службы хаускипинга есть простой и наглядный инструмент для планирования своей работы.
---
Отчеты и алерты для инженерной службы
Если дашборд для хаускипинга — это пассивная система отображения, то система для инженерной службы должна быть событийной и активной. Инженеру не нужно постоянно смотреть на экран; система должна сама привлекать его внимание, когда что-то пошло не так. Это называется алертинг (alerting).
Давайте рассмотрим несколько практических триггеров и как их реализовать в Node-RED.
Примеры триггеров для инженерной службы
- Протечка воды:
* Логика: При срабатывании датчика (изменение состояния на входе) немедленно формируется алерт.
* Действие: Отправка SMS или Push-уведомления дежурному инженеру, запись события в журнал аварий в MySQL, перекрытие клапана на стояке данного номера (если он моторизован и подключен к выходу-реле).
- Неэффективное кондиционирование:
* Логика: Если окно открыто (`window_status == 'open'`) и кондиционер работает (`hvac_status == 'on'`) дольше 15 минут, генерируется алерт.
* Реализация в Node-RED: Используется узел `trigger`. При открытии окна он запускает таймер на 15 минут. Если за это время окно закрывается, таймер сбрасывается. Если нет — узел отправляет сообщение, запускающее алерт.
* Действие: Уведомление на дашборд инженера, запись в лог.
- Ошибка оборудования:
* Логика: Поток Node-RED периодически опрашивает этот регистр. Если значение регистра отлично от нуля (или другого "нормального" значения), генерируется алерт.
* Действие: Создание тикета в системе обслуживания с указанием номера комнаты и кода ошибки.
Примеры триггеров для службы безопасности
- Дверь в номер открыта слишком долго:
* Логика: Аналогично окну, если дверь открыта более 5 минут, а статус номера "Занято" (`keycard_present == 'true'`), это может быть нештатной ситуацией.
* Действие: Вывод уведомления на пост охраны.
- Движение в пустом номере:
* Логика: Если `keycard_present` равен `false`, но датчик движения фиксирует активность, это повод для тревоги.
* Реализация в Node-RED: Узел `switch` проверяет состояние `global.hotel.rooms[roomNumber].keycard_present` при получении события от датчика движения. Для предотвращения ложных алертов (например, от горничной) можно добавить проверку на статус `MUR`.
> ℹ️ Информация: Для предотвращения "дребезга" алертов (повторных срабатываний от одного и того же события) активно используется узел RBE (Report-by-Exception). Он пропускает сообщение дальше только в том случае, если его `payload` изменился. Например, если датчик протечки сработал, RBE пропустит первое сообщение, а все последующие (пока датчик не высохнет) заблокирует, предотвращая спам в системе оповещений.
---
Итоги и следующие шаги
В этом уроке мы рассмотрели комплексный подход к созданию систем отчетности для гостиничного персонала. Мы убедились, что фундаментом такой системы является надежный сбор данных и их передача по стандартизированным каналам.
Ключевые выводы:Продвинутые темы и дальнейшее развитие
Освоив базовые принципы, вы можете развивать систему дальше, интегрируя более сложные функции:
- Интеграция с PMS (Property Management System): Это следующий логический шаг. Связав вашу систему автоматизации с PMS отеля (например, Fidelio, Opera), вы сможете автоматически активировать сценарии "Добро пожаловать" при заселении гостя и "Никого нет" при его выписке, а также синхронизировать статусы уборки.
- Хранение истории в базах данных: Для аналитических отчетов (`global context` не подходит, так как хранит только текущее состояние) данные о событиях следует сохранять во временных (`time-series`) базах данных, таких как InfluxDB, или реляционных, как MySQL/PostgreSQL (доступна на контроллере HI "из коробки"). Это позволит строить графики энергопотребления, анализировать среднее время уборки номеров, частоту возникновения ошибок и т.д.
- Предиктивное обслуживание: Анализируя исторические данные (например, время работы насоса, количество циклов включения компрессора), можно прогнозировать вероятный выход оборудования из строя и планировать его обслуживание заблаговременно, а не после аварии.
В следующем уроке мы перейдем к рассмотрению другой важной области применения автоматизации — небольших промышленных объектов, где требования к надежности и протоколам взаимодействия еще выше.