Основы работы с данными: симуляция датчиков и обработка в Node-RED
COURSE-16-M01-LAB02 — Основы работы с данными: симуляция датчиков и обработка в Node-RED
Введение
Данная лабораторная работа является вашим первым практическим шагом в мир обработки данных Интернета вещей (IoT) на платформе HI. В предыдущих занятиях вы познакомились с концепцией IoT и изучили интерфейс контроллера. Теперь настало время создать первый функциональный поток (flow) в среде Node-RED.
Мы симулируем получение данных от нескольких виртуальных датчиков, обработаем эти данные в соответствии со стандартами Академии и выведем результат для анализа. Эта работа заложит фундамент для понимания ключевых паттернов проектирования, таких как "Контракт сообщения", "Визуальный статус" и "Обработка ошибок", которые являются обязательными для всех сертифицированных инженеров HI.
Цели обучения
По завершении этой лабораторной работы вы сможете:
- Создавать базовый поток в Node-RED с использованием узлов `Inject`, `Function`, `Switch`, `Catch` и `Debug`.
- Симулировать отправку данных от различных IoT-датчиков, используя `msg.topic` для идентификации источника.
- Применять паттерн "Контракт сообщения" для стандартизации данных в формате JSON.
- Выполнять базовые операции с данными внутри узла `Function` с использованием JavaScript.
- Использовать узел `Debug` для анализа и отладки потока данных.
- Применять паттерн "Визуальный статус" для мониторинга состояния узла.
- Реализовывать базовую обработку ошибок с помощью узла `Catch`.
Необходимое оборудование и программное обеспечение
- Рабочая станция (ПК или ноутбук) с веб-браузером (рекомендуется Chrome или Firefox).
- Доступ к веб-интерфейсу контроллера HI с учетными данными инженера.
Подготовительные задания
Перед началом выполнения практической части, ответьте на следующие вопросы. Это поможет вам закрепить теоретические знания, необходимые для успешного выполнения работы.
1. Расчет трафика данныхРассчитайте, какой объем данных (в Мегабайтах) будет передан за 24 часа от 20 датчиков, если каждый датчик отправляет пакет размером 250 Байт (не КБ) каждые 30 секунд. Укажите формулу расчета.
- Решение:
Количество пакетов в 24 часа от одного датчика: `120 пакетов/час 24 часа = 2880 пакетов`
Общий объем данных от одного датчика за 24 часа: `2880 пакетов 250 Байт/пакет = 720 000 Байт`
Общий объем данных от 20 датчиков за 24 часа: `720 000 Байт/датчик 20 датчиков = 14 400 000 Байт`
Перевод в Мегабайты: `14 400 000 Байт / (1024 1024) Байт/МБ ≈ 13.73 МБ`
2. Применение IoT в сельском хозяйствеОпишите 2-3 конкретных примера использования IoT в сельском хозяйстве (агропромышленности), указав, какие типы датчиков могут применяться в каждом примере. (Объем: 50-100 слов).
- Пример 1: Мониторинг состояния почвы. Датчики влажности, температуры, pH и уровня питательных веществ в почве позволяют фермерам оптимизировать полив и внесение удобрений, снижая затраты и повышая урожайность.
- Пример 2: Умные теплицы. Датчики температуры, влажности воздуха, освещенности и CO2 в теплицах позволяют автоматически регулировать микроклимат, открывать/закрывать форточки, включать досветку и системы полива для создания идеальных условий для роста растений.
- Пример 3: Отслеживание скота. GPS-трекеры и биометрические датчики на животных помогают отслеживать их местоположение, состояние здоровья и активность, предотвращая потери и своевременно выявляя заболевания.
Заполните пропуски в определении:
Интернет вещей (IoT) — это концепция вычислительной сети, позволяющая физическим объектам ("вещам") обмениваться данными и взаимодействовать друг с другом или с внешней средой через интернет без участия человека.
Пошаговое руководство по выполнению
Сценарий: Вы настраиваете систему мониторинга микроклимата в офисном помещении. Ваша задача — создать поток в Node-RED, который принимает показания от датчиков температуры и CO2, приводит их к стандартному формату, обрабатывает потенциальные ошибки и выводит результат для анализа. На данном этапе мы будем симулировать работу датчиков с помощью узлов `Inject`, используя `msg.topic` для идентификации источника данных.Шаг 1: Создание нового потока
Шаг 2: Проектирование потока (Flow Diagram)
Прежде чем размещать узлы, изучите логическую схему нашего будущего потока. Мы создадим два симулятора датчиков, один центральный обработчик, узел маршрутизации и узел обработки ошибок.
Идентификатор потока: `FLOW-FOUND-MONITOR-001`// FLOW-FOUND-MONITOR-001: Office Monitoring Simulation
// Симулятор датчика температуры
[Inject: "23.5"] --+
|
// Симулятор датчика CO2
[Inject: "850"] ---+--> [Function: "Форматирование данных"] --> [Switch: "Маршрутизация"] --> [Debug: "Температура"]
| |
| +--> [Debug: "CO2"]
|
+-----------------------------------------------------------------> [Catch: "Ошибки"] --> [Debug: "Лог ошибок"]
Шаг 3: Размещение и настройка узлов
* Перетащите узел `inject` из левой палитры на рабочую область.
* Дважды кликните по нему, чтобы открыть окно настроек.
* В поле `Payload` выберите тип `string` (строка) и введите значение `23.5`.
* В поле `Topic` введите `telemetry/office/temperature`.
* В поле `Name` введите `Sim: Температура (°C)`.
* Нажмите Done.
* Перетащите еще один узел `inject`.
* Настройте его: `Payload` (string) -> `850`, `Topic` -> `telemetry/office/co2`, `Name` -> `Sim: CO2 (ppm)`.
* Нажмите Done.
* Перетащите еще один узел `inject`.
* Настройте его: `Payload` (string) -> `abc`, `Topic` -> `telemetry/office/temperature`, `Name` -> `Sim: Ошибка (текст)`.
* Нажмите Done.
* Перетащите узел `function` на рабочую область.
* В поле `Name` введите `Форматирование по контракту`.
* Соедините выходы всех трех узлов `inject` со входом узла `function`.
* Перетащите узел `switch` на рабочую область.
* В поле `Name` введите `Маршрутизация по типу`.
* Соедините выход узла `function` со входом узла `switch`.
* Перетащите два узла `debug` на рабочую область.
* Первый `debug`: `Output` -> `complete msg object`, `Name` -> `Debug: Температура`. Соедините его с первым выходом узла `switch`.
* Второй `debug`: `Output` -> `complete msg object`, `Name` -> `Debug: CO2`. Соедините его со вторым выходом узла `switch`.
* Перетащите узел `catch` на рабочую область.
* Дважды кликните по нему. Убедитесь, что выбрано `All nodes on current flow`.
* В поле `Name` введите `Catch: Ошибки потока`.
* Перетащите еще один узел `debug`. Настройте его: `Output` -> `complete msg object`, `Name` -> `Debug: Лог ошибок`. Соедините выход узла `catch` со входом этого `debug` узла.
Шаг 4: Написание кода для узла `Function`
Дважды кликните по узлу `function` "Форматирование по контракту" и вставьте следующий код на вкладку `On Message`. Этот код реализует Паттерн "Контракт сообщения" и Паттерн "Визуальный статус".
// Получаем сырое значение из msg.payload. Оно приходит в виде строки.
const rawValue = msg.payload;
const topic = msg.topic; // Используем msg.topic для определения источника
let value;
let unit;
let sourceId;
let dataType; // Для маршрутизации
// 1. Определение типа данных и парсинг
if (topic === "telemetry/office/temperature") {
value = parseFloat(rawValue);
unit = "°C";
sourceId = "sim-temp-sensor-office1";
dataType = "temperature";
} else if (topic === "telemetry/office/co2") {
value = parseInt(rawValue, 10);
unit = "ppm";
sourceId = "sim-co2-sensor-office1";
dataType = "co2";
} else {
// Если topic неизвестен, генерируем ошибку
node.status({ fill: "red", shape: "dot", text: "Ошибка: Неизвестный топик" });
node.error("Неизвестный топик сообщения: " + topic, msg);
return null; // Прерываем выполнение потока для этого сообщения
}
// 2. ВАЛИДАЦИЯ: Проверяем, что данные корректны
if (isNaN(value)) {
node.status({ fill: "red", shape: "dot", text: `Ошибка: нечисловое значение (${dataType})` });
// Генерируем ошибку, которая будет поймана узлом Catch
node.error(`Получено нечисловое значение для ${dataType}: ${rawValue}`, msg);
return null; // Прерываем выполнение потока для этого сообщения
}
// Дополнительная валидация по диапазонам
if (dataType === "temperature" && (value < -20 || value > 50)) {
node.status({ fill: "yellow", shape: "dot", text: `Предупреждение: Температура вне диапазона (${value} ${unit})` });
// Можно не генерировать ошибку, а просто логировать предупреждение
// node.warn(`Температура вне допустимого диапазона: ${value} ${unit}`, msg);
} else if (dataType === "co2" && (value < 300 || value > 5000)) {
node.status({ fill: "yellow", shape: "dot", text: `Предупреждение: CO2 вне диапазона (${value} ${unit})` });
// node.warn(`CO2 вне допустимого диапазона: ${value} ${unit}`, msg);
}
// 3. ФОРМИРОВАНИЕ КОНТРАКТА СООБЩЕНИЯ
// Создаем новый объект payload в строгом соответствии со стандартом Академии.
msg.payload = {
value: value,
unit: unit,
source: sourceId,
ts: Date.now(), // Добавляем временную метку в формате Unix epoch (ms)
dataType: dataType // Добавляем тип данных для маршрутизации
};
// 4. ВИЗУАЛЬНЫЙ СТАТУС: Обновляем статус узла
node.status({ fill: "green", shape: "dot", text: `${dataType}: ${value} ${unit}` });
return msg; // Отправляем сформированное сообщение дальше по потоку
Нажмите Done.
Шаг 5: Настройка узла `Switch` для маршрутизации
Дважды кликните по узлу `switch` "Маршрутизация по типу".
- В поле `Property` выберите `msg.payload.dataType`.
- Добавьте два правила:
2. `==` (string) `co2` -> Выход 2
Нажмите Done.
Шаг 6: Развертывание и тестирование
* Вы должны увидеть отформатированные сообщения в `Debug: Температура` и `Debug: CO2` соответственно.
* Обратите внимание на статус узла `Форматирование по контракту` – он должен отображать текущие показания.
* Вы должны увидеть сообщение об ошибке в `Debug: Лог ошибок`.
* Статус узла `Форматирование по контракту` должен измениться на красный, указывая на ошибку.
Лабораторная работа: Усложнение и расширение
COURSE-16-M01-LAB02-LAB01 — Расширение мониторинга и логирование ошибок
Задача: Расширить систему мониторинга, добавив симуляцию датчика влажности. Кроме того, реализовать централизованное логирование всех ошибок, перехваченных узлом `Catch`, в контекст потока. Требования:* `Payload`: `string` со значением `55.2`.
* `Topic`: `telemetry/office/humidity`.
* `Name`: `Sim: Влажность (%)`.
* `dataType`: `humidity`.
* `unit`: `%`.
* `sourceId`: `sim-humidity-sensor-office1`.
* Добавить валидацию для влажности (например, от 0 до 100).
* Получать сообщение об ошибке от `Catch`.
* Формировать стандартизированный объект ошибки (JSON) с полями `timestamp`, `error_message`, `source_node_id`, `original_payload`.
* Добавлять этот объект в массив `flow.error_log` (используя `flow.set()`). Если `flow.error_log` не существует, создать его.
* Ограничить размер `flow.error_log` до 10 последних ошибок (удалять старые, если массив превышает 10 элементов).
* Использовать Паттерн "Визуальный статус" для отображения количества ошибок в логе.
// FLOW-FOUND-MONITOR-001: Office Monitoring Simulation (Расширенный)
// Симулятор датчика температуры
[Inject: "23.5"] --+
|
// Симулятор датчика CO2
[Inject: "850"] ---+
|
// Симулятор датчика влажности
[Inject: "55.2"] --+
|
// Симулятор некорректных данных
[Inject: "abc"] ---+--> [Function: "Форматирование данных"] --> [Switch: "Маршрутизация"] --> [Debug: "Температура"]
|
+--> [Debug: "CO2"]
|
+--> [Debug: "Влажность"]
|
+--> [Catch: "Ошибки потока"] --> [Function: "Логирование ошибок"] --> [Debug: "Лог ошибок"]
Skeleton (для узла `Function: Форматирование по контракту`):
const rawValue = msg.payload;
const topic = msg.topic;
let value;
let unit;
let sourceId;
let dataType;
// --- Добавьте логику для обработки humidity ---
if (topic === "telemetry/office/temperature") {
value = parseFloat(rawValue);
unit = "°C";
sourceId = "sim-temp-sensor-office1";
dataType = "temperature";
} else if (topic === "telemetry/office/co2") {
value = parseInt(rawValue, 10);
unit = "ppm";
sourceId = "sim-co2-sensor-office1";
dataType = "co2";
} else if (topic === "telemetry/office/humidity") {
// --- ВАШ КОД ЗДЕСЬ: Обработка влажности ---
value = parseFloat(rawValue);
unit = "%";
sourceId = "sim-humidity-sensor-office1";
dataType = "humidity";
}
else {
node.status({ fill: "red", shape: "dot", text: "Ошибка: Неизвестный топик" });
node.error("Неизвестный топик сообщения: " + topic, msg);
return null;
}
// --- Добавьте валидацию для humidity ---
if (isNaN(value)) {
node.status({ fill: "red", shape: "dot", text: `Ошибка: нечисловое значение (${dataType})` });
node.error(`Получено нечисловое значение для ${dataType}: ${rawValue}`, msg);
return null;
}
// Дополнительная валидация по диапазонам
if (dataType === "temperature" && (value < -20 || value > 50)) {
node.status({ fill: "yellow", shape: "dot", text: `Предупреждение: Температура вне диапазона (${value} ${unit})` });
} else if (dataType === "co2" && (value < 300 || value > 5000)) {
node.status({ fill: "yellow", shape: "dot", text: `Предупреждение: CO2 вне диапазона (${value} ${unit})` });
} else if (dataType === "humidity" && (value < 0 || value > 100)) {
node.status({ fill: "yellow", shape: "dot", text: `Предупреждение: Влажность вне диапазона (${value} ${unit})` });
}
msg.payload = {
value: value,
unit: unit,
source: sourceId,
ts: Date.now(),
dataType: dataType
};
node.status({ fill: "green", shape: "dot", text: `${dataType}: ${value} ${unit}` });
return msg;
Skeleton (для узла `Function: Логирование ошибок`):
// msg.error содержит информацию об ошибке
// msg.payload содержит оригинальное сообщение, вызвавшее ошибку
let errorLog = flow.get('error_log') || []; // Получаем текущий лог или создаем пустой массив
const errorEntry = {
timestamp: Date.now(),
error_message: msg.error.message,
source_node_id: msg.error.source.id,
source_node_name: msg.error.source.name,
original_payload: msg.payload // Сохраняем оригинальное сообщение
};
errorLog.push(errorEntry);
// Ограничиваем размер лога до 10 последних ошибок
if (errorLog.length > 10) {
errorLog = errorLog.slice(errorLog.length - 10);
}
flow.set('error_log', errorLog); // Сохраняем обновленный лог в контекст потока
// Обновляем визуальный статус узла
node.status({ fill: "red", shape: "dot", text: `Ошибок: ${errorLog.length}` });
// Отправляем сообщение об ошибке для отладки
msg.payload = errorEntry;
return msg;
Рубрика оценивания:
- 1 балл: Добавлен симулятор влажности и соответствующий `Debug` узел.
- 2 балла: Узел `Function: Форматирование по контракту` корректно обрабатывает данные влажности, включая валидацию.
- 1 балл: Узел `Switch: Маршрутизация по типу` имеет три выхода и корректно маршрутизирует все типы данных.
- 3 балла: Создан узел `Function: Логирование ошибок`, который корректно сохраняет ошибки в `flow.error_log` в стандартизированном формате.
- 1 балл: Лог ошибок ограничен 10 последними записями.
- 1 балл: Узел `Function: Логирование ошибок` отображает количество ошибок в своем статусе.
- 1 балл: Все узлы имеют осмысленные имена.
COURSE-16-M01-LAB02-LAB02 — Управление пороговыми значениями и уведомления
Задача: Добавить логику для отслеживания превышения пороговых значений температуры и CO2, а также отправлять уведомления при их нарушении. Требования:* Для температуры: если `value > 28` или `value < 18`, сгенерировать сообщение-уведомление.
* Для CO2: если `value > 1000`, сгенерировать сообщение-уведомление.
* Для влажности: если `value > 70` или `value < 30`, сгенерировать сообщение-уведомление.
* Сообщение-уведомление должно быть в формате:
{
"type": "alert",
"level": "warning",
"message": "Температура в офисе превысила порог: 29.1 °C",
"sensor_type": "temperature",
"current_value": 29.1,
"threshold": { "min": 18, "max": 28 },
"ts": 1678886400000
}
* Использовать Паттерн "Визуальный статус" для отображения текущего состояния (OK / ALERT).
// FLOW-FOUND-MONITOR-001: Office Monitoring Simulation (с порогами)
// Симулятор датчика температуры
[Inject: "23.5"] --+
|
// Симулятор датчика CO2
[Inject: "850"] ---+
|
// Симулятор датчика влажности
[Inject: "55.2"] --+
|
// Симулятор некорректных данных
[Inject: "abc"] ---+--> [Function: "Форматирование данных"] --> [Switch: "Маршрутизация"] --> [Function: "Проверка порогов"] --> [Debug: "Уведомления"]
|
+--> [Debug: "Температура"]
|
+--> [Debug: "CO2"]
|
+--> [Debug: "Влажность"]
|
+--> [Catch: "Ошибки потока"] --> [Function: "Логирование ошибок"] --> [Debug: "Лог ошибок"]
Skeleton (для узла `Function: Проверка порогов`):
const data = msg.payload; // Сообщение уже в стандартизированном формате
let alertMessage = null;
let alertLevel = "info"; // По умолчанию, если нет нарушений
if (data.dataType === "temperature") {
if (data.value > 28) {
alertMessage = `Температура в офисе превысила порог: ${data.value} ${data.unit}`;
alertLevel = "warning";
} else if (data.value < 18) {
alertMessage = `Температура в офисе ниже порога: ${data.value} ${data.unit}`;
alertLevel = "warning";
}
} else if (data.dataType === "co2") {
if (data.value > 1000) {
alertMessage = `Уровень CO2 в офисе превысил порог: ${data.value} ${data.unit}`;
alertLevel = "warning";
}
} else if (data.dataType === "humidity") {
if (data.value > 70) {
alertMessage = `Влажность в офисе превысила порог: ${data.value} ${data.unit}`;
alertLevel = "warning";
} else if (data.value < 30) {
alertMessage = `Влажность в офисе ниже порога: ${data.value} ${data.unit}`;
alertLevel = "warning";
}
}
if (alertMessage) {
// Формируем сообщение-уведомление
const alertMsg = {
type: "alert",
level: alertLevel,
message: alertMessage,
sensor_type: data.dataType,
current_value: data.value,
threshold: { min: (data.dataType === "temperature" ? 18 : (data.dataType === "humidity" ? 30 : null)), max: (data.dataType === "temperature" ? 28 : (data.dataType === "co2" ? 1000 : (data.dataType === "humidity" ? 70 : null))) },
ts: Date.now()
};
node.status({ fill: "red", shape: "dot", text: `ALERT: ${data.dataType} ${data.value}${data.unit}` });
return [msg, { payload: alertMsg }]; // Отправляем оригинальное сообщение и уведомление
} else {
node.status({ fill: "green", shape: "dot", text: `OK: ${data.dataType} ${data.value}${data.unit}` });
return [msg, null]; // Отправляем только оригинальное сообщение
}
Рубрика оценивания:
- 2 балла: Создан узел `Function: Проверка порогов` и подключен к потоку.
- 3 балла: Реализована корректная проверка порогов для температуры, CO2 и влажности.
- 3 балла: Сообщения-уведомления формируются в стандартизированном JSON-формате.
- 1 балл: Узел `Function: Проверка порогов` отображает свой статус (OK/ALERT).
- 1 балл: Добавлены `Inject` сообщения, демонстрирующие срабатывание порогов.
Тест по модулю COURSE-16-M01-LAB02
a) `Debug`
b) `Function`
c) `Inject`
d) `Switch`
a) `msg.payload`
b) `msg.id`
c) `msg.topic`
d) `msg.source`
a) `source`
b) `value`
c) `unit`
d) `ts`
a) Для задержки сообщений
b) Для перехвата и обработки ошибок в потоке
c) Для изменения типа данных
d) Для отправки сообщений по расписанию
a) `node.log()`
b) `node.warn()`
c) `node.status()`
d) `node.error()`
a) Строка
b) Целое число
c) Число с плавающей точкой
d) Булево
a) Сообщение будет отправлено в узел `Debug`.
b) Сообщение будет отправлено на все выходы узла.
c) Сообщение будет отброшено и не пойдет дальше по потоку.
d) Произойдет ошибка, которую поймает узел `Catch`.
a) Для хранения глобальных настроек Node-RED.
b) Для временного хранения данных, доступных только в пределах одного потока (вкладки).
c) Для хранения данных, доступных между всеми потоками.
d) Для хранения данных, которые сохраняются после перезагрузки контроллера.
a) `time`
b) `timestamp`
c) `ts`
d) `date`
a) `0`
b) `NaN`
c) `undefined`
d) `null`
Мини-runbook "Если не работает"
Проблема 1: Сообщения не появляются в отладочной панели.- Диагностика:
* Проверьте, что узлы `debug` активированы (зеленый квадрат рядом с ними).
* Убедитесь, что вы нажали кнопку `inject` для запуска сообщений.
* Проверьте соединения между узлами – они должны быть сплошными линиями.
- Решение: Нажмите `Deploy`, активируйте `debug` узлы, нажмите `inject`. Проверьте правильность соединений.
- Диагностика:
* Проверьте настройки узла `Switch: Маршрутизация по типу`. Убедитесь, что `Property` (`msg.payload.dataType`) и правила сравнения (`== string`) настроены корректно.
* Используйте `Debug` узел, подключенный непосредственно к выходу `Function` узла, чтобы увидеть, что именно он отправляет.
- Решение: Внимательно перепроверьте код `Function` и настройки `Switch`. Используйте промежуточные `Debug` узлы для пошаговой отладки.
- Диагностика:
* Внимательно прочитайте сообщение об ошибке. Оно часто указывает на номер строки в коде `Function`, где произошла проблема.
- Решение: Исправьте ошибку в коде `Function` согласно сообщению в `Debug` панели. Частые ошибки: опечатки, неверное имя переменной, попытка доступа к несуществующему свойству объекта.
- Диагностика:
* Проверьте, что узел, который должен генерировать ошибку (например, `Function`), действительно вызывает `node.error()`. Простое `return null` не генерирует ошибку, а лишь останавливает поток для данного сообщения.
- Решение: Проверьте настройки `Catch` и убедитесь, что узлы-источники ошибок используют `node.error()`.
- Диагностика:
- Решение: Исправьте вызовы `node.status()` в коде.
- Диагностика:
* Проверьте, что массив `errorLog` не перезаписывается пустым массивом при каждом вызове, если он уже существует.
- Решение: Убедитесь, что логика чтения и записи контекста потока корректна, особенно инициализация `flow.get('error_log') || []`.