ГлавнаяАкадемияОсновы умного дома → Сценарии для гостиниц: Отчетность для персонала

Сценарии для гостиниц: Отчетность для персонала

Урок 10 · Основы умного дома · 30 мин · theory

Введение в системы отчетности для гостиниц

🔗 Связанный материал: В данном уроке мы будем использовать концепции, заложенные в уроках «Логика ключ-карты» (COURSE-01-M05-L09) и «Статусы DND/MUR» (COURSE-01-M05-L10). Убедитесь, что вы хорошо усвоили материал этих уроков, так как отчетность является логическим продолжением сбора данных о состоянии номера.

Эффективное управление современным отелем невозможно без оперативного получения точной информации. Системы отчетности, построенные на базе платформы автоматизации, становятся цифровой нервной системой объекта, поставляя критически важные данные для различных служб. Ценность таких систем заключается в переходе от реактивной модели управления («устраняем проблему, когда о ней сообщили») к проактивной («предотвращаем проблему, видя ее предпосылки»).

Рассмотрим роль систем отчетности для ключевых служб отеля:

  • Служба хаускипинга (Housekeeping):
  • * Задача: Максимально быстро и эффективно производить уборку номеров.

    * Ценность отчета: Персонал получает в реальном времени на планшет или общий экран информацию о статусе каждого номера: гость вышел (карта извлечена), гость находится в номере (карта вставлена), включен статус «Не беспокоить» (DND), запрошена уборка (MUR), номер освобожден после выезда. Это позволяет оптимизировать маршруты горничных, избегать беспокойства гостей и ускорять подготовку номеров для нового заселения.

  • Инженерная (техническая) служба:
  • * Задача: Поддержание исправной работы всего оборудования в отеле.

    * Ценность отчета: Система автоматически генерирует алерты (тревожные сообщения) о нештатных ситуациях: сработал датчик протечки под фанкойлом, температура в номере вышла за допустимые пределы, окно открыто более 15 минут при работающем кондиционере, оборудование (например, вентиляционная установка) сообщило код ошибки по Modbus. Это позволяет инженерам немедленно реагировать на инциденты, часто еще до того, как гость заметит проблему.

  • Служба приема и размещения (Reception):
  • * Задача: Обеспечение комфортного пребывания гостей и управление номерным фондом.

    * Ценность отчета: Сотрудники ресепшн могут видеть реальный статус присутствия гостя, а не только информацию из PMS (системы управления отелем). Это помогает при решении спорных ситуаций, а также позволяет оперативно отвечать на вопросы («Могу я попросить горничную зайти в мой номер сейчас?» — «Одну минуту... Вижу, в номере сейчас никого нет, я передам вашу просьбу»).

    Архитектура системы отчетности

    Для построения надежной системы отчетности на платформе HI используется многоуровневая архитектура:

    Все отчеты можно разделить на три функциональные категории:

    ---

    Проектирование структуры 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` |

    Практические примеры топиков:

    * Топик: `hotel/main/03/301/keycard/present`

    * Сообщение (Payload): `true`

    * Топик: `hotel/main/03/301/panel/dnd`

    * Сообщение (Payload): `true`

    * Топик: `hotel/main/03/301/panel/mur`

    * Сообщение (Payload): `true`

    * Топик: `hotel/main/03/301/window/status`

    * Сообщение (Payload): `open`

    * Топик: `hotel/main/03/301/hvac/temperature`

    * Сообщение (Payload): `22.5`

    Такая структура позволяет с легкостью получать данные, используя wildcards (символы подстановки) в MQTT-подписках в Node-RED.

    Примеры подписок:

    `hotel/main/03/301/#` `hotel/main/03/+/keycard/present` `hotel/+/+/+/panel/+`

    > 💡 Подсказка: Существует альтернативный подход, при котором вместо множества атомарных топиков используется один «статусный» топик на номер, например, `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'

  • Перетащите узел `mqtt in` на поле редактора.
  • Дважды кликните на него для настройки.
  • Server: Выберите ваш MQTT-брокер (настроенный ранее).
  • Topic: Укажите топик с использованием wildcards для подписки на все интересующие нас события. Для нашего примера это будет `hotel/main/+/+/+/+`.
  • QoS: Установите `2` для максимальной надежности доставки.
  • Output: `a parsed JSON object` (если вы ожидаете JSON) или `a string` – в нашем случае, для простых сообщений типа `true`/`false`/`open`, строка является предпочтительной.
  • Name: "Подписка на статусы номеров".
  • Шаг 2: Создание узла 'function' для обработки данных

    Это ядро нашей логики. Здесь мы будем разбирать топик и обновлять глобальный объект состояний.

  • Перетащите узел `function` и соедините его с выходом узла `mqtt in`.
  • Дважды кликните на него и вставьте следующий код:
  • // Получаем топик и 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: Отладка

  • Соедините выход узла `function` с узлом `debug`.
  • Разверните проект, нажав Deploy.
  • Имитируйте отправку MQTT-сообщений (например, с помощью `mqtt-explorer` или узла `mqtt out`) в топики вида `hotel/main/03/301/panel/dnd`.
  • В панели отладки (Debug) вы увидите сообщения об успешном обновлении.
  • Также вы можете проверить содержимое `global context` на боковой панели `Context Data` -> `Global`. Вы увидите, как создается и наполняется объект `hotel.rooms`.
  • Пример структуры `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: Настройка потока

  • Узел `inject`: Настройте его на периодический запуск, например, каждые 5 секунд. Это будет частота обновления нашего дашборда.
  • Узел `function`: Это место, где мы будем преобразовывать наши данные из `global context` в HTML-код.
  • Узел `ui_template`: Этот узел из `node-red-dashboard` примет HTML-код и отобразит его на веб-странице.
  • Шаг 2: Код для узла `function` (Генерация HTML)

    Вставьте следующий код в узел `function`:

    // Получаем объект с состояниями комнат из глобального контекста
    

    const rooms = global.get('hotel.rooms') || {};

    // Начинаем формировать HTML-таблицу. Добавляем стили для раскраски статусов

    let html = `

    `;

    // Получаем ключи (номера комнат) и сортируем их для упорядоченного вывода

    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 += `

    `;

    }

    // Завершаем таблицу

    html += '

    Номер Присутствие Не беспокоить (DND) Запрос уборки (MUR)
    ${roomNumber} ${presentText} ${dndText} ${murText}
    ';

    // Помещаем сгенерированный HTML в msg.payload, который будет принят узлом ui_template

    msg.payload = html;

    return msg;

    Шаг 3: Настройка 'ui_template'

  • В настройках узла `ui_template` выберите группу на дашборде, где будет отображаться таблица.
  • Размер (Size) установите по желанию, например, `12x8`.
  • Поле `Template` оставьте пустым, так как мы передаем весь HTML через `msg.payload`.
  • Разверните проект и откройте UI дашборда по адресу `http://:1880/ui`. Вы увидите таблицу, которая обновляется в реальном времени.
  • Теперь у службы хаускипинга есть простой и наглядный инструмент для планирования своей работы.

    ---

    Отчеты и алерты для инженерной службы

    Если дашборд для хаускипинга — это пассивная система отображения, то система для инженерной службы должна быть событийной и активной. Инженеру не нужно постоянно смотреть на экран; система должна сама привлекать его внимание, когда что-то пошло не так. Это называется алертинг (alerting).

    Давайте рассмотрим несколько практических триггеров и как их реализовать в Node-RED.

    Примеры триггеров для инженерной службы

    * Источник: Датчик протечки (например, HW-SW), подключенный к универсальному входу (UI) контроллера.

    * Логика: При срабатывании датчика (изменение состояния на входе) немедленно формируется алерт.

    * Действие: Отправка SMS или Push-уведомления дежурному инженеру, запись события в журнал аварий в MySQL, перекрытие клапана на стояке данного номера (если он моторизован и подключен к выходу-реле).

    * Источник: Датчик открытия окна (геркон) на UI и статус работы кондиционера (полученный по Modbus или через реле).

    * Логика: Если окно открыто (`window_status == 'open'`) и кондиционер работает (`hvac_status == 'on'`) дольше 15 минут, генерируется алерт.

    * Реализация в Node-RED: Используется узел `trigger`. При открытии окна он запускает таймер на 15 минут. Если за это время окно закрывается, таймер сбрасывается. Если нет — узел отправляет сообщение, запускающее алерт.

    * Действие: Уведомление на дашборд инженера, запись в лог.

    * Источник: HVAC-система или другая Modbus-совместимая установка, которая сообщает код ошибки в один из своих регистров.

    * Логика: Поток Node-RED периодически опрашивает этот регистр. Если значение регистра отлично от нуля (или другого "нормального" значения), генерируется алерт.

    * Действие: Создание тикета в системе обслуживания с указанием номера комнаты и кода ошибки.

    Примеры триггеров для службы безопасности

    * Источник: Дверной геркон.

    * Логика: Аналогично окну, если дверь открыта более 5 минут, а статус номера "Занято" (`keycard_present == 'true'`), это может быть нештатной ситуацией.

    * Действие: Вывод уведомления на пост охраны.

    * Источник: Датчик движения (PIR или mmWave) и статус картоприемника.

    * Логика: Если `keycard_present` равен `false`, но датчик движения фиксирует активность, это повод для тревоги.

    * Реализация в Node-RED: Узел `switch` проверяет состояние `global.hotel.rooms[roomNumber].keycard_present` при получении события от датчика движения. Для предотвращения ложных алертов (например, от горничной) можно добавить проверку на статус `MUR`.

    > ℹ️ Информация: Для предотвращения "дребезга" алертов (повторных срабатываний от одного и того же события) активно используется узел RBE (Report-by-Exception). Он пропускает сообщение дальше только в том случае, если его `payload` изменился. Например, если датчик протечки сработал, RBE пропустит первое сообщение, а все последующие (пока датчик не высохнет) заблокирует, предотвращая спам в системе оповещений.

    ---

    Итоги и следующие шаги

    В этом уроке мы рассмотрели комплексный подход к созданию систем отчетности для гостиничного персонала. Мы убедились, что фундаментом такой системы является надежный сбор данных и их передача по стандартизированным каналам.

    Ключевые выводы:
  • Архитектура: Успешная система строится послойно: сбор данных с помощью надежного оборудования (контроллер HI), транспортировка через MQTT, обработка и агрегация логики в Node-RED и, наконец, визуализация на дашбордах или в мобильных приложениях.
  • Важность стандартов: Единая и масштабируемая структура MQTT-топиков — это не рекомендация, а строгое требование для построения системы, которую можно будет легко поддерживать и расширять в будущем.
  • Мощность Node-RED: Мы увидели, как с помощью стандартных узлов Node-RED можно решать сложные задачи: агрегировать данные со всего объекта, хранить их состояние в контексте и создавать как пассивные отчеты (дашборды), так и активные системы оповещения (алерты).
  • Продвинутые темы и дальнейшее развитие

    Освоив базовые принципы, вы можете развивать систему дальше, интегрируя более сложные функции:

    В следующем уроке мы перейдем к рассмотрению другой важной области применения автоматизации — небольших промышленных объектов, где требования к надежности и протоколам взаимодействия еще выше.