ГлавнаяАкадемияСценарии умного дома: режимы, состояния, приоритеты → Симуляция событий для тестирования (виртуальные датчики)

Симуляция событий для тестирования (виртуальные датчики)

Урок 2 · Сценарии умного дома: режимы, состояния, приоритеты · 30 мин · theory

Введение в симуляцию: Зачем нужны виртуальные устройства?

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

Роль симуляции в жизненном цикле проекта невозможно переоценить. Это не просто инструмент для отладки; это фундаментальная методология, обеспечивающая качество и надёжность конечного решения.

🔗 Связанный материал: Этот урок тесно связан с концепциями, изложенными в уроке COURSE-07-M09-L02 'Разработка Тест-плана сдачи объекта'. Симуляция — это ваш главный инструмент для выполнения пунктов разработанного тест-плана, превращающий теоретические проверки в практические, воспроизводимые тесты.

Ключевые преимущества симуляции:

Симуляция напрямую связана с типами тестирования, которые мы рассмотрели ранее:

На платформе контроллеров HI основным инструментом для создания симуляций является среда Node-RED. Её гибкость, визуальный интерфейс и мощные узлы `Inject` и `Function` позволяют создавать виртуальные устройства любой сложности — от простой кнопки до полноценной погодной станции.

---

Простая симуляция: Узел Inject для ручного триггера событий

Самый быстрый и простой способ сымитировать событие в Node-RED — использовать узел `Inject`. Этот узел, по сути, является "виртуальной кнопкой", которая при нажатии генерирует сообщение (`msg`) с заранее определенным содержимым. Это идеальный инструмент для ручного модульного тестирования отдельных участков логики.

Узел `Inject` позволяет настроить `msg.payload` для отправки данных любого типа, что дает возможность эмулировать практически любое простое устройство.

| Тип данных | Пример полезной нагрузки (`msg.payload`) | Эмулируемое устройство |

| --------------- | ------------------------------------------ | ---------------------------------------------------- |

| Boolean | `true` / `false` | Датчик движения, геркон (датчик открытия), кнопка |

| Number | `23.5` (число) | Датчик температуры, влажности; уставка термостата |

| String | `"NIGHT"` (строка) | Переключатель режимов (День/Ночь/Вечер) |

| JSON | `{"value": true, "source": "virtual-01"}` | Комплексное устройство, отправляющее данные в стандартном контракте |

Практический пример: Создание виртуального геркона

Задача: Протестировать сценарий "При открытии входной двери включить свет в холле на 3 минуты". У нас еще нет физического геркона, поэтому мы создадим его виртуальный аналог. Реализация:
  • Создайте два узла `Inject`. Разместите их на своей тестовой вкладке в Node-RED.
  • Настройте первый узел `Inject` ("Дверь открыта"):
  • * Name: `[V] Door Opened`

    * Payload: выберите тип `JSON`.

    * В редактор JSON вставьте следующий код, соответствующий контракту сообщений:

            {

    "value": true,

    "source": "virtual-door-main",

    "ts": 0

    }

    * В поле `Topic` укажите `hi/virtual/hall/door_sensor/state`. Это MQTT-топик, который будет "слушать" ваш сценарий управления светом.

    * > 💡 Подсказка: Установка `ts` (timestamp) в `0` здесь допустима для простого теста, но для более реалистичных симуляций лучше использовать `timestamp` (текущее время). Узел `Inject` умеет это делать автоматически.

  • Настройте второй узел `Inject` ("Дверь закрыта"):
  • * Name: `[V] Door Closed`

    * Payload: выберите тип `JSON`.

    * В редактор JSON вставьте:

            {

    "value": false,

    "source": "virtual-door-main",

    "ts": 0

    }

    * В поле `Topic` укажите тот же самый топик: `hi/virtual/hall/door_sensor/state`.

  • Подключите оба узла `Inject` к узлу `mqtt out`. Убедитесь, что узел `mqtt out` настроен на ваш MQTT-брокер.
  • Сценарий использования:

    Теперь у вас есть панель ручного управления виртуальным датчиком.

    Таким образом, узел `Inject` позволяет быстро и эффективно проводить ручное тестирование, имитируя простые дискретные события и проверяя реакцию системы в реальном времени.

    ---

    Динамическая симуляция: Узел Function для сложных сценариев

    Хотя узел `Inject` отлично подходит для ручных проверок, у него есть фундаментальное ограничение: он статичен. Вы не можете с его помощью легко сымитировать плавное изменение температуры или случайные колебания потребляемой мощности. Для создания динамических симуляций нам потребуется вся мощь JavaScript, доступная в узле `Function`.

    Узел `Function` позволяет нам писать код, который будет генерировать данные "на лету", делая симуляцию гораздо более реалистичной и открывая возможности для автоматизированного тестирования.

    > 💡 Подсказка: Используйте переменные контекста (`flow` или `global`) для хранения состояния ваших виртуальных устройств. Например, чтобы симулятор температуры 'помнил' текущее значение между вызовами, сохраняйте его в `flow.set('current_temp', new_temp)`. Это ключевой механизм для создания "живых" симуляций.

    Пример 1: Эмуляция датчика температуры с плавным ростом

    Задача: Создать виртуальный датчик уличной температуры, который имитирует утренний прогрев воздуха. Каждые 10 секунд температура должна немного повышаться. Реализация:
  • Триггер: Используйте узел `Inject`, настроенный на периодический запуск каждые `10` секунд. Он будет служить "тактовым генератором" для нашего симулятора.
  • Узел `Function`: Подключите выход `Inject` к входу узла `Function`. Назовите его `[V] Simulate Temperature Rise` и вставьте следующий код:
  •     // Получаем текущее значение температуры из контекста потока (flow context).

    // Если значения нет (первый запуск), начинаем с 10.0 градусов.

    let currentTemp = flow.get("virtualOutdoorTemp") || 10.0;

    // Симулируем небольшой, слегка случайный прирост температуры.

    // Math.random() * 0.3 добавляет случайности от 0.0 до 0.3.

    let newTemp = currentTemp + (Math.random() * 0.3);

    // Округляем до одного знака после запятой для реалистичности.

    newTemp = Math.round(newTemp * 10) / 10;

    // Если температура превысила 25 градусов, сбрасываем ее обратно, чтобы симуляция не ушла в бесконечность.

    if (newTemp > 25.0) {

    newTemp = 10.0;

    }

    // Сохраняем новое значение обратно в контекст для следующего запуска.

    flow.set("virtualOutdoorTemp", newTemp);

    // Формируем сообщение в соответствии с контрактом.

    msg.payload = {

    "value": newTemp,

    "unit": "°C",

    "source": "virtual-weather-station",

    "ts": Date.now()

    };

    msg.topic = "hi/virtual/outdoor/temperature";

    // Обновляем статус узла для наглядности.

    node.status({ fill: "blue", shape: "dot", text: "Temp: " + newTemp + " °C" });

    // Отправляем сообщение дальше по потоку.

    return msg;

  • Выход: Подключите выход узла `Function` к узлу `mqtt out` для публикации данных.
  • Теперь у вас есть автоматический симулятор, который будет непрерывно генерировать реалистичные данные о росте температуры, позволяя тестировать сценарии, зависящие от плавного изменения этого параметра (например, управление системой отопления или кондиционирования).

    Пример 2: Эмуляция счетчика электроэнергии

    Задача: Создать симулятор, который генерирует показания потребляемой мощности, имитируя работу бытовых приборов в доме — с базовым потреблением и случайными пиками. Реализация (в узле `Function`):
    // Базовое потребление в доме (холодильник, дежурный режим техники).
    

    const basePower = 150; // Ватт

    // Вероятность включения мощного прибора (чайник, микроволновка).

    const highLoadProbability = 0.1; // 10%

    let currentPower = basePower;

    // Добавляем небольшие случайные колебания к базовой мощности.

    currentPower += (Math.random() * 50) - 25; // от -25 до +25 Вт

    // Проверяем, не включился ли мощный прибор.

    if (Math.random() < highLoadProbability) {

    // Симулируем пик потребления от 1500 до 2200 Вт.

    currentPower += 1500 + (Math.random() * 700);

    }

    // Формируем payload.

    msg.payload = {

    "value": Math.round(currentPower),

    "unit": "W",

    "source": "virtual-power-meter",

    "ts": Date.now()

    };

    msg.topic = "hi/virtual/home/main_power/value";

    node.status({ fill: "green", shape: "dot", text: "Power: " + msg.payload.value + " W" });

    return msg;

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

    ---

    Практический пример: Создание виртуальной погодной станции

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

    > ⚠️ Внимание: Неконтролируемая генерация сообщений (например, каждые 100 мс) может создать высокую нагрузку на MQTT-брокер и сеть контроллера. Всегда выбирайте разумный интервал для симуляции, соответствующий реальному физическому устройству. Для погодных данных интервал в 1-5 минут является более чем достаточным.

    Задача: Создать "виртуальную метеостанцию", которая с интервалом в 1 минуту генерирует и публикует в разные MQTT-топики три параметра:
  • Температура (`hi/virtual/outdoor/temperature`)
  • Влажность (`hi/virtual/outdoor/humidity`)
  • Уровень освещенности (`hi/virtual/outdoor/lux`)
  • Реализация:

    Для этого нам понадобится всего три узла: `Inject` -> `Function` -> `mqtt out`.

  • Узел `Inject`: Настройте его на автоматический запуск с интервалом в `1 minute`.
  • Узел `Function` (`[V] Weather Station`): Этот узел будет сердцем нашей симуляции. Он сгенерирует все три значения и, что важно, создаст три независимых сообщения для отправки. Для этого мы вернем из функции массив объектов `msg`.
  •     // === Симуляция температуры ===

    // Используем синусоиду для имитации суточных колебаний температуры.

    const hours = (new Date()).getHours();

    const baseTemp = 15; // Средняя температура

    const amplitude = 8; // Амплитуда колебаний

    let temp = baseTemp + amplitude Math.sin((hours - 9) (Math.PI / 12));

    temp += (Math.random() - 0.5); // Добавляем немного шума

    temp = Math.round(temp * 10) / 10;

    // === Симуляция влажности ===

    // Влажность часто обратно коррелирует с температурой

    let humidity = 70 - (temp - baseTemp) * 2.5;

    humidity += (Math.random() * 10 - 5);

    humidity = Math.max(30, Math.min(99, Math.round(humidity))); // Ограничиваем диапазон 30-99%

    // === Симуляция освещенности (lux) ===

    let lux;

    if (hours > 7 && hours < 20) { // День

    // Максимальная освещенность в полдень

    lux = 50000 Math.sin((hours - 7) (Math.PI / 13));

    } else { // Ночь

    lux = 5 + (Math.random() * 10); // Очень низкая освещенность

    }

    lux = Math.round(lux);

    // --- Создание трех независимых сообщений ---

    // Сообщение для температуры

    const msgTemp = {

    topic: "hi/virtual/outdoor/temperature",

    payload: {

    "value": temp,

    "unit": "°C",

    "source": "virtual-weather-station",

    "ts": Date.now()

    }

    };

    // Сообщение для влажности

    const msgHumidity = {

    topic: "hi/virtual/outdoor/humidity",

    payload: {

    "value": humidity,

    "unit": "%",

    "source": "virtual-weather-station",

    "ts": Date.now()

    }

    };

    // Сообщение для освещенности

    const msgLux = {

    topic: "hi/virtual/outdoor/lux",

    payload: {

    "value": lux,

    "unit": "lux",

    "source": "virtual-weather-station",

    "ts": Date.now()

    }

    };

    // Обновляем статус узла для удобства отладки

    node.status({ text: `T: ${temp}°C, H: ${humidity}%, L: ${lux} lx` });

    // Возвращаем массив сообщений. Node-RED отправит каждое из них как отдельное.

    return [msgTemp, msgHumidity, msgLux];

  • Узел `mqtt out`: Подключите выход узла `Function` к нему. Поскольку топик задан внутри каждого сообщения (`msg.topic`), узел `mqtt out` будет автоматически публиковать данные по трем разным адресам.
  • Демонстрация использования для тестирования:

    Имея такую виртуальную станцию, вы можете легко протестировать сложный сценарий, например, "Управление фасадной подсветкой":

    Условие: «Если уровень освещенности на улице упал ниже 50 lux, И сейчас не активен режим "Безопасность", И время после заката, то включить фасадную подсветку».*

    ---

    Итоги и интеграция симуляций в Тест-план

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

    Теперь давайте вернемся к Тест-плану, который вы разработали в уроке COURSE-07-M09-L02. Симуляция позволяет сделать его пункты гораздо более конкретными и выполнимыми.

    Пример модификации пункта в Тест-плане: > 12.1. Проверить реакцию на датчик движения в гостиной. Сценарий "Свет". > 12.1. Сценарный тест "Движение в гостиной":

    > - Шаг 1 (Симуляция): С помощью узла `Inject` отправить сообщение `{"value": true}` в MQTT-топик `hi/virtual/living_room/motion_sensor/state`.

    > - Шаг 2 (Проверка логики): Убедиться, что в журнале событий появилась запись об активации сценария "Свет в гостиной". Проверить, что статус сценария изменился на "АКТИВЕН".

    > - Шаг 3 (Проверка исполнения): Проверить, что физическое реле `RL-08 (Гостиная, основной свет)` включилось (визуально или по статусу в Node-RED).

    > - Шаг 4 (Симуляция): Через `N` минут (согласно настройке сценария) убедиться, что свет погас. Либо симулировать повторное движение для проверки сброса таймера.

    Этот подход превращает абстрактную проверку в четкий алгоритм, который может выполнить любой инженер.

    Лучшие практики работы с симуляциями:

  • Изоляция: Всегда используйте префикс `virtual/` (или аналогичный) в MQTT-топиках для ваших симуляторов (`hi/virtual/...`). Это позволяет легко отличить реальные данные от симулированных и отключать все симуляции одной MQTT-подпиской.
  • Документирование: Создайте отдельную вкладку в Node-RED под названием `Simulations` или `Virtual Devices`. Каждый симулятор должен быть подписан, а в узлах `Comment` рядом с ними должно быть описание того, что они делают и как их использовать.
  • Отключение перед сдачей: Это критически важно! Перед сдачей объекта заказчику убедитесь, что все симуляционные потоки отключены (через `disable flow` в интерфейсе Node-RED) или полностью удалены. Система должна работать только на основе данных с реальных физических устройств.
  • Создание "Панели управления тестами": Для крупных объектов можно создать отдельный UI (например, с помощью `node-red-dashboard`), где будут собраны все ваши "виртуальные кнопки" и слайдеры для удобного ручного тестирования ключевых сценариев.
  • Что дальше?

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