ГлавнаяАкадемияCOURSE-16: Основы Интернета Вещей и практическое применение → Основы работы с данными: симуляция датчиков и обработка в Node-RED

Основы работы с данными: симуляция датчиков и обработка в Node-RED

Урок 4 · COURSE-16: Основы Интернета Вещей и практическое применение · theory

COURSE-16-M01-LAB02 — Основы работы с данными: симуляция датчиков и обработка в Node-RED

Введение

Данная лабораторная работа является вашим первым практическим шагом в мир обработки данных Интернета вещей (IoT) на платформе HI. В предыдущих занятиях вы познакомились с концепцией IoT и изучили интерфейс контроллера. Теперь настало время создать первый функциональный поток (flow) в среде Node-RED.

Мы симулируем получение данных от нескольких виртуальных датчиков, обработаем эти данные в соответствии со стандартами Академии и выведем результат для анализа. Эта работа заложит фундамент для понимания ключевых паттернов проектирования, таких как "Контракт сообщения", "Визуальный статус" и "Обработка ошибок", которые являются обязательными для всех сертифицированных инженеров HI.

Цели обучения

По завершении этой лабораторной работы вы сможете:

Необходимое оборудование и программное обеспечение

Подготовительные задания

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

1. Расчет трафика данных

Рассчитайте, какой объем данных (в Мегабайтах) будет передан за 24 часа от 20 датчиков, если каждый датчик отправляет пакет размером 250 Байт (не КБ) каждые 30 секунд. Укажите формулу расчета.

* Количество пакетов в час от одного датчика: `3600 секунд / 30 секунд/пакет = 120 пакетов/час`

Количество пакетов в 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 слов).

3. Заполнение пропусков

Заполните пропуски в определении:

Интернет вещей (IoT) — это концепция вычислительной сети, позволяющая физическим объектам ("вещам") обмениваться данными и взаимодействовать друг с другом или с внешней средой через интернет без участия человека.

Пошаговое руководство по выполнению

Сценарий: Вы настраиваете систему мониторинга микроклимата в офисном помещении. Ваша задача — создать поток в Node-RED, который принимает показания от датчиков температуры и CO2, приводит их к стандартному формату, обрабатывает потенциальные ошибки и выводит результат для анализа. На данном этапе мы будем симулировать работу датчиков с помощью узлов `Inject`, используя `msg.topic` для идентификации источника данных.

Шаг 1: Создание нового потока

  • Откройте интерфейс Node-RED на вашем контроллере.
  • Нажмите на кнопку `+` на панели вкладок, чтобы создать новый поток.
  • Дважды кликните по названию новой вкладки (`Flow 1`) и переименуйте ее в `M01-LAB02: Мониторинг офиса`.
  • Шаг 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.

  • Симулятор датчика CO2:
  • * Перетащите еще один узел `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`):
  • * Перетащите узел `function` на рабочую область.

    * В поле `Name` введите `Форматирование по контракту`.

    * Соедините выходы всех трех узлов `inject` со входом узла `function`.

  • Узел маршрутизации (`Switch`):
  • * Перетащите узел `switch` на рабочую область.

    * В поле `Name` введите `Маршрутизация по типу`.

    * Соедините выход узла `function` со входом узла `switch`.

  • Узлы отладки (`Debug`):
  • * Перетащите два узла `debug` на рабочую область.

    * Первый `debug`: `Output` -> `complete msg object`, `Name` -> `Debug: Температура`. Соедините его с первым выходом узла `switch`.

    * Второй `debug`: `Output` -> `complete msg object`, `Name` -> `Debug: CO2`. Соедините его со вторым выходом узла `switch`.

  • Узел обработки ошибок (`Catch`):
  • * Перетащите узел `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` "Маршрутизация по типу".

    1. `==` (string) `temperature` -> Выход 1

    2. `==` (string) `co2` -> Выход 2

    Нажмите Done.

    Шаг 6: Развертывание и тестирование

  • Нажмите кнопку Deploy (Развернуть) в правом верхнем углу Node-RED.
  • Откройте вкладку `Debug` на правой боковой панели.
  • Нажимайте кнопки `inject` рядом с узлами `Sim: Температура (°C)` и `Sim: CO2 (ppm)`.
  • * Вы должны увидеть отформатированные сообщения в `Debug: Температура` и `Debug: CO2` соответственно.

    * Обратите внимание на статус узла `Форматирование по контракту` – он должен отображать текущие показания.

  • Нажмите кнопку `inject` рядом с узлом `Sim: Ошибка (текст)`.
  • * Вы должны увидеть сообщение об ошибке в `Debug: Лог ошибок`.

    * Статус узла `Форматирование по контракту` должен измениться на красный, указывая на ошибку.

    Лабораторная работа: Усложнение и расширение

    COURSE-16-M01-LAB02-LAB01 — Расширение мониторинга и логирование ошибок

    Задача: Расширить систему мониторинга, добавив симуляцию датчика влажности. Кроме того, реализовать централизованное логирование всех ошибок, перехваченных узлом `Catch`, в контекст потока. Требования:
  • Добавить новый узел `inject` для симуляции датчика влажности.
  • * `Payload`: `string` со значением `55.2`.

    * `Topic`: `telemetry/office/humidity`.

    * `Name`: `Sim: Влажность (%)`.

  • Модифицировать узел `Function: Форматирование по контракту` для обработки данных влажности.
  • * `dataType`: `humidity`.

    * `unit`: `%`.

    * `sourceId`: `sim-humidity-sensor-office1`.

    * Добавить валидацию для влажности (например, от 0 до 100).

  • Модифицировать узел `Switch: Маршрутизация по типу`, добавив третий выход для влажности.
  • Добавить новый узел `Debug: Влажность` для вывода данных влажности.
  • Создать новый узел `Function: Логирование ошибок` после `Catch: Ошибки потока`. Этот узел должен:
  • * Получать сообщение об ошибке от `Catch`.

    * Формировать стандартизированный объект ошибки (JSON) с полями `timestamp`, `error_message`, `source_node_id`, `original_payload`.

    * Добавлять этот объект в массив `flow.error_log` (используя `flow.set()`). Если `flow.error_log` не существует, создать его.

    * Ограничить размер `flow.error_log` до 10 последних ошибок (удалять старые, если массив превышает 10 элементов).

    * Использовать Паттерн "Визуальный статус" для отображения количества ошибок в логе.

  • Подключить `Debug: Лог ошибок` к выходу узла `Function: Логирование ошибок`.
  • Flow Diagram (ASCII):
    // 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;

    Рубрика оценивания:

    COURSE-16-M01-LAB02-LAB02 — Управление пороговыми значениями и уведомления

    Задача: Добавить логику для отслеживания превышения пороговых значений температуры и CO2, а также отправлять уведомления при их нарушении. Требования:
  • Создать новый узел `Function: Проверка порогов` после узла `Switch: Маршрутизация по типу`.
  • В узле `Function: Проверка порогов` реализовать следующую логику:
  • * Для температуры: если `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).

  • Подключить выход узла `Function: Проверка порогов` к новому узлу `Debug: Уведомления`.
  • Добавить в `Inject` узлы симуляции значения, которые вызовут срабатывание порогов (например, `30.0` для температуры, `1200` для CO2, `80.0` для влажности).
  • Flow Diagram (ASCII):
    // 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]; // Отправляем только оригинальное сообщение

    }

    Рубрика оценивания:

    Тест по модулю COURSE-16-M01-LAB02

  • Какой узел в Node-RED используется для симуляции входящих данных?
  • a) `Debug`

    b) `Function`

    c) `Inject`

    d) `Switch`

  • Какое свойство сообщения `msg` рекомендуется использовать для идентификации источника данных и маршрутизации в Node-RED?
  • a) `msg.payload`

    b) `msg.id`

    c) `msg.topic`

    d) `msg.source`

  • Согласно Паттерну "Контракт сообщения", какое поле JSON-объекта `msg.payload` должно содержать основное значение датчика?
  • a) `source`

    b) `value`

    c) `unit`

    d) `ts`

  • Для чего используется узел `Catch` в Node-RED?
  • a) Для задержки сообщений

    b) Для перехвата и обработки ошибок в потоке

    c) Для изменения типа данных

    d) Для отправки сообщений по расписанию

  • Какой метод используется в узле `Function` для отображения статуса узла в редакторе Node-RED (Паттерн "Визуальный статус")?
  • a) `node.log()`

    b) `node.warn()`

    c) `node.status()`

    d) `node.error()`

  • Если `msg.payload` содержит `{"value": 25.5, "unit": "°C"}`, какой тип данных имеет `value`?
  • a) Строка

    b) Целое число

    c) Число с плавающей точкой

    d) Булево

  • Что произойдет, если узел `Function` вернет `null`?
  • a) Сообщение будет отправлено в узел `Debug`.

    b) Сообщение будет отправлено на все выходы узла.

    c) Сообщение будет отброшено и не пойдет дальше по потоку.

    d) Произойдет ошибка, которую поймает узел `Catch`.

  • Для чего в Node-RED используется контекст потока (`flow context`)?
  • a) Для хранения глобальных настроек Node-RED.

    b) Для временного хранения данных, доступных только в пределах одного потока (вкладки).

    c) Для хранения данных, доступных между всеми потоками.

    d) Для хранения данных, которые сохраняются после перезагрузки контроллера.

  • Какое поле в стандартном контракте сообщения (JSON) используется для указания времени генерации данных?
  • a) `time`

    b) `timestamp`

    c) `ts`

    d) `date`

  • Если узел `Function` получает `msg.payload = "hello"` и пытается выполнить `parseFloat(msg.payload)`, какой будет результат?
  • a) `0`

    b) `NaN`

    c) `undefined`

    d) `null`

    Мини-runbook "Если не работает"

    Проблема 1: Сообщения не появляются в отладочной панели. * Убедитесь, что вы нажали кнопку Deploy после всех изменений.

    * Проверьте, что узлы `debug` активированы (зеленый квадрат рядом с ними).

    * Убедитесь, что вы нажали кнопку `inject` для запуска сообщений.

    * Проверьте соединения между узлами – они должны быть сплошными линиями.

    Проблема 2: Сообщения появляются, но не в том формате или не на том выходе. * Проверьте код в узле `Function: Форматирование по контракту`. Возможно, есть опечатки или логические ошибки в парсинге `rawValue` или формировании `msg.payload`.

    * Проверьте настройки узла `Switch: Маршрутизация по типу`. Убедитесь, что `Property` (`msg.payload.dataType`) и правила сравнения (`== string`) настроены корректно.

    * Используйте `Debug` узел, подключенный непосредственно к выходу `Function` узла, чтобы увидеть, что именно он отправляет.

    Проблема 3: Узел `Function` показывает красный статус с ошибкой. * Проверьте консоль отладки (`Debug` панель) – там должно быть подробное сообщение об ошибке, сгенерированное `node.error()`.

    * Внимательно прочитайте сообщение об ошибке. Оно часто указывает на номер строки в коде `Function`, где произошла проблема.

    Проблема 4: Узел `Catch` не перехватывает ошибки. * Убедитесь, что узел `Catch` настроен на `All nodes on current flow`.

    * Проверьте, что узел, который должен генерировать ошибку (например, `Function`), действительно вызывает `node.error()`. Простое `return null` не генерирует ошибку, а лишь останавливает поток для данного сообщения.

    Проблема 5: Статус узла не обновляется или отображает неверную информацию. * Проверьте код `node.status()` в узле `Function`. Убедитесь, что он вызывается в нужных местах и с правильными параметрами (`fill`, `shape`, `text`). Проблема 6: Контекст потока (`flow.error_log`) не сохраняет данные или теряет их. * Убедитесь, что вы используете `flow.set('error_log', errorLog)` для сохранения и `flow.get('error_log')` для чтения.

    * Проверьте, что массив `errorLog` не перезаписывается пустым массивом при каждом вызове, если он уже существует.