ГлавнаяАкадемияCOURSE-16: Основы Интернета Вещей и практическое применение → Применение IoT в 'Умном городе': Практические сценарии на платформе HI

Применение IoT в 'Умном городе': Практические сценарии на платформе HI

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

COURSE-16-M05-L06 — Применение IoT в 'Умном городе': Практические сценарии на платформе HI

Введение в концепцию 'Умного города' на платформе HI

'Умный город' (Smart City) — это городская среда, в которой для повышения качества жизни граждан, оптимизации использования ресурсов и обеспечения безопасности применяются интегрированные информационно-коммуникационные технологии. В основе этой концепции лежит сбор, обработка и анализ данных от множества устройств, объединенных в единую экосистему.

В контексте платформы HI, 'Умный город' — это не абстрактная идея, а набор конкретных, воспроизводимых инженерных решений. Наш контроллер, работающий на Debian Linux с Node-RED, является идеальным инструментом для реализации таких решений на уровне зданий, кварталов или небольших муниципальных объектов. Он позволяет объединять различные системы — от освещения и климата до безопасности и учета ресурсов — с помощью стандартных промышленных протоколов, таких как Modbus, MQTT и CAN.

Этот урок демонстрирует, как перейти от концепции к практике, реализуя три типовых сценария 'Умного города' с использованием стандартных возможностей контроллера HI.

Сценарий 1: Адаптивное уличное освещение (SCN-LIGHT-031)

Задача: Автоматизировать управление уличным или парковым освещением для снижения энергопотребления и повышения безопасности. Освещение должно включаться при снижении естественной освещенности и может быть принудительно активировано по команде диспетчера. Архитектура решения на платформе HI:

Схема подключения: WIRING-LIGHT-018

📋 Чеклист подключения:

  • Подключите фазовый провод (~L) через реле `RL-01` контроллера к светильнику.
  • Подключите нейтральный провод (~N) напрямую к светильнику.
  • Подключите датчик освещенности к универсальному входу `UI-01` и `GND`.
  • Используйте провода с цветовой кодировкой согласно стандарту Академии.
  • //========= WIRING-LIGHT-018: Adaptive Street Light Control =========
    
    

    // AC Power Supply to Controller and Light

    Щит АВР [CTRL:HI-Core]

    ~L~ --------- L (CTRL)

    ~N~ --------- N (CTRL)

    ~PE~ -------- PE (CTRL)

    ~L~ --- C (RL-01)

    \-- NO (RL-01) --- ~L~ ----------------- L (Lamp)

    ~N~ ----------------------------------------- N (Lamp)

    // Analog Light Sensor

    (SENS:Light:ALS-01) [CTRL:HI-Core]

    VCC (+5V) --------- +5V (от контроллера)

    GND --------- GND

    Signal --------- UI-01

    Поток Node-RED: FLOW-AUTO-LIGHT-015

    Логика потока:
  • Раз в минуту поток проверяет текущий уровень освещенности.
  • Параллельно поток слушает MQTT-топик для получения ручных команд.
  • Узел `Function` реализует конечный автомат (FSM) с состояниями: `AUTO`, `MANUAL_ON`, `MANUAL_OFF`.
  • В режиме `AUTO` свет включается, если уровень освещенности ниже порогового значения (например, 100 люкс).
  • Ручная команда переводит автомат в соответствующее состояние, игнорируя показания датчика.
  • Состояние автомата сохраняется в `flow context` для устойчивости к перезагрузкам контроллера (используя `contextStorage` в `settings.js` для сохранения в файл).
  • //================ FLOW-AUTO-LIGHT-015 ==================
    
    

    // Path 1: Automatic control based on light sensor

    [Inject: 1 min] -> [Analog In: UI-01] -> |

    |

    // Path 2: Manual override from MQTT |

    [MQTT In: cmd/light/street-01/set] ----> | -> [Function: Light FSM] -> [Relay Out: RL-01] -> [MQTT Out: state/light/street-01]

    | |

    // Path 3: Error and Status Handling | v

    [Catch: All] ---------------------------> | -> [Function: Format Error] -> [Log to MySQL]

    [Status: Relay Out] --------------------> |

    Контракты сообщений и реализация

    Контракт для MQTT-команд (`cmd/light/street-01/set`):

    Сообщение должно быть в формате JSON для унификации.

    {
    

    "command": "ON", // "ON", "OFF", "AUTO"

    "source": "dispatcher-console-01",

    "ts": 1678886400000

    }

    Код для узла `Function: Light FSM`:
    // Получаем текущее состояние FSM из контекста потока
    

    // 'file' в contextStorage обеспечит сохранение при перезагрузке

    let state = flow.get("light_fsm_state") || "AUTO";

    let lightLevel = flow.get("current_light_level") || 1000; // Уровень освещенности по умолчанию

    // Обработка входящих сообщений

    if (msg.topic && msg.topic.includes("cmd/light")) {

    // Пришла команда по MQTT

    try {

    let cmd_payload = JSON.parse(msg.payload);

    if (typeof cmd_payload.command !== 'string') {

    throw new Error("Command must be a string.");

    }

    switch (cmd_payload.command.toUpperCase()) {

    case "ON": state = "MANUAL_ON"; break;

    case "OFF": state = "MANUAL_OFF"; break;

    case "AUTO": state = "AUTO"; break;

    default: throw new Error("Unknown command: " + cmd_payload.command);

    }

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

    node.status({fill:"blue", shape:"dot", text:`Command: ${cmd_payload.command}`});

    } catch (e) {

    node.error("Invalid JSON or command in MQTT message: " + e.message, msg);

    node.status({fill:"red", shape:"dot", text:"MQTT Cmd Error"});

    return null;

    }

    } else if (msg.payload !== undefined) {

    // Пришли данные с датчика

    // Предполагаем, что узел входа уже выдает число

    let sensorValue = parseFloat(msg.payload);

    if (isNaN(sensorValue) || sensorValue < 0 || sensorValue > 65535) { // Примерный диапазон для аналогового датчика

    node.error("Invalid light level value from sensor: " + msg.payload, msg);

    node.status({fill:"red", shape:"dot", text:"Sensor Error"});

    return null;

    }

    lightLevel = sensorValue;

    flow.set("current_light_level", lightLevel);

    } else {

    // Если сообщение не от MQTT и не от датчика, игнорируем

    return null;

    }

    flow.set("light_fsm_state", state);

    // Логика принятия решения

    let turnOn = false;

    const THRESHOLD = 100; // Порог освещенности в люксах

    switch (state) {

    case "MANUAL_ON":

    turnOn = true;

    break;

    case "MANUAL_OFF":

    turnOn = false;

    break;

    case "AUTO":

    if (lightLevel < THRESHOLD) {

    turnOn = true;

    }

    break;

    }

    // Формирование команды для реле и статуса

    let outputValue = turnOn ? 1 : 0; // Для узла Relay Out (0 или 1)

    let outputState = turnOn ? "ON" : "OFF";

    // Обновляем статус узла Function

    node.status({

    fill: "green",

    shape: "dot",

    text: `State: ${state} | Light: ${lightLevel.toFixed(1)} lux | Output: ${outputState}`

    });

    // Формируем исходящее сообщение для реле

    let msgToRelay = { payload: outputValue };

    // Формируем исходящее сообщение для MQTT (обратная связь)

    let msgToMqtt = {

    topic: "state/light/street-01",

    payload: JSON.stringify({

    value: outputState,

    state: state,

    light_level: parseFloat(lightLevel.toFixed(1)), // Округляем для контракта

    source: "light-fsm-01",

    ts: Date.now()

    })

    };

    return [msgToRelay, msgToMqtt]; // Отправляем на два выхода

    💡 Совет: Для узла `Analog In: UI-01` убедитесь, что он настроен на выдачу числового значения. Если он выдает строку, используйте `parseFloat(msg.payload)` для преобразования. Убедитесь, что `contextStorage` в `settings.js` настроен на `file` для сохранения состояния FSM при перезагрузке контроллера.

    Сценарий 2: Мониторинг качества воздуха (SCN-ENV-007)

    Задача: Организовать сбор данных о концентрации CO2 или мелкодисперсных частиц PM2.5 в оживленном городском районе. Данные должны передаваться в центральную систему мониторинга и сохраняться в локальной базе данных для анализа. Архитектура решения на платформе HI:

    Схема подключения: WIRING-SENS-011

    ⚠️ Предупреждение: Шина RS-485 требует соблюдения полярности (A/B) и установки терминирующих резисторов (120 Ом) на крайних устройствах.

    //============ WIRING-SENS-011: Air Quality Modbus Sensor ==============
    

    // Используется экранированная витая пара. Экран заземляется только со стороны контроллера.

    [CTRL:HI-Core] (SENS:CO2:Sensor-01)

    (RS485-1)

    Клемма Шина Цвет Клемма

    +24V --------- (Красный) ---- V+

    A ---A------ (Зеленый) ---- A

    B ---B------ (Белый) ----- B

    GND ---GND---- (Черный) ----- GND

    |

    (Экран) -------- GND

    Поток Node-RED: FLOW-INTEG-MODBUS-009

    Логика потока:
  • Каждые 5 минут узел `Inject` инициирует опрос датчика.
  • Узел `Modbus-Getter` считывает значение из нужного регистра (например, Holding Register `40001`).
  • Узел `Function` валидирует, преобразует и форматирует данные согласно контракту сообщения.
  • Данные одновременно отправляются в MQTT-топик и в таблицу `sensor_data` базы данных MySQL.
  • Узел `Catch` перехватывает ошибки связи по Modbus (timeout, CRC error) и логирует их в `audit_log` MySQL.
  • //================ FLOW-INTEG-MODBUS-009 ==================
    
    

    [Inject: 5 min] -> [Modbus-Getter] -> [Function: Parse & Format] --+--> [MQTT Out: telemetry/air/co2]

    |

    +--> [MySQL: insert into sensor_data]

    // Error Handling

    [Catch: Modbus Nodes] -> [Function: Format Modbus Error] -> [Log to MySQL: audit_log]

    Контракты сообщений и реализация

    Контракт для телеметрии (`telemetry/air/co2`):
    {
    

    "value": 450,

    "unit": "ppm",

    "source": "modbus-sensor-co2-01",

    "ts": 1678886400000,

    "meta": {

    "location": "city-center-square",

    "sensor_id": 15

    }

    }

    Код для узла `Function: Parse & Format`:
    // Входящее сообщение от Modbus-Getter: msg.payload.data = [450]
    

    // Проверяем наличие данных и их формат

    if (!msg.payload || !Array.isArray(msg.payload.data) || msg.payload.data.length === 0) {

    node.error("Modbus-Getter returned invalid or empty data.", msg);

    node.status({fill:"red", shape:"dot", text:"No Modbus data"});

    return null;

    }

    let rawValue = msg.payload.data[0];

    // 1. Валидация

    // Предполагаем, что CO2 в ppm обычно находится в диапазоне 300-5000

    if (rawValue < 300 || rawValue > 5000) {

    node.status({fill:"red", shape:"dot", text:"Invalid value: " + rawValue});

    node.error("Invalid CO2 value received: " + rawValue, msg);

    // Отправляем ошибку в централизованный логгер

    msg.error = {

    message: "Invalid CO2 value",

    value: rawValue,

    source_node: node.name || "Parse & Format"

    };

    return [null, null, msg]; // Отправляем на третий выход для ошибок

    }

    // 2. Формирование по контракту для MQTT

    let mqttPayload = {

    value: rawValue,

    unit: "ppm",

    source: "modbus-sensor-co2-01",

    ts: Date.now(),

    meta: {

    location: "city-center-square",

    sensor_id: 15 // Modbus Slave ID

    }

    };

    // 3. Подготовка SQL-запроса для узла MySQL

    // Узел mysql ожидает запрос в msg.topic

    let sqlQuery = `INSERT INTO sensor_data (ts, source, value, unit) VALUES (${mqttPayload.ts}, '${mqttPayload.source}', ${mqttPayload.value}, '${mqttPayload.unit}')`;

    // Создаем два отдельных сообщения для разных выходов

    let msgToMqtt = {

    topic: "telemetry/air/co2",

    payload: JSON.stringify(mqttPayload)

    };

    let msgToMysql = {

    topic: sqlQuery,

    payload: mqttPayload // Можно передать весь объект для логирования, если нужно

    };

    node.status({fill:"green", shape:"dot", text:`OK: ${rawValue} ppm`});

    return [msgToMqtt, msgToMysql, null]; // Отправляем на два выхода, третий для ошибок пустой

    💡 Совет: Для узла `MySQL` убедитесь, что он настроен на подключение к локальной базе данных на контроллере HI. Таблица `sensor_data` должна быть предварительно создана со столбцами `ts` (BIGINT), `source` (VARCHAR), `value` (FLOAT), `unit` (VARCHAR). Также создайте таблицу `audit_log` для ошибок.

    Сценарий 3: Мониторинг занятости парковочных мест (SCN-TRAFFIC-004)

    Задача: Предоставлять информацию о свободных/занятых парковочных местах в режиме реального времени. Архитектура решения на платформе HI:

    Схема подключения: WIRING-SENS-012

    💡 Совет: Для надежной работы на улице используйте промышленные ультразвуковые датчики с классом защиты IP67.

    //============ WIRING-SENS-012: Ultrasonic Parking Sensor ============
    
    

    (SENS:Ultrasonic:PK-01) [CTRL:HI-Core]

    VCC (+5V) --------------- +5V

    GND --------------------- GND

    Trig -------------------- UI-05 (сконфигурирован как выход)

    Echo -------------------- UI-06 (сконфигурирован как вход)

    Поток Node-RED: FLOW-SENS-USONIC-002

    Логика потока:
  • Каждые 10 секунд инициируется измерение.
  • Поток отправляет короткий импульс на `Trig` пин датчика.
  • Поток измеряет длительность ответного импульса на `Echo` пине.
  • Длительность импульса преобразуется в расстояние.
  • Если расстояние меньше порогового (например, 1 метр), место считается занятым.
  • Статус (`OCCUPIED`/`FREE`) отправляется по MQTT.
  • //================ FLOW-SENS-USONIC-002 ==================
    
    

    [Inject: 10 sec] -> [Function: Trigger Pulse] -> [RPI GPIO Out: UI-05 (Trig)]

    |

    v

    [RPI GPIO In: UI-06 (Echo)] -> [Function: Calculate Distance] -> [Function: Determine Status] -> [MQTT Out: telemetry/parking/spot-01]

    |

    +--> [Debug]

    // Error Handling

    [Catch: All] -> [Function: Format Error] -> [Log to MySQL]

    Реализация

    Контракт для телеметрии (`telemetry/parking/spot-01`):
    {
    

    "status": "OCCUPIED", // "OCCUPIED" или "FREE"

    "distance_cm": 50,

    "source": "ultrasonic-pk-01",

    "ts": 1678886400000,

    "meta": {

    "spot_id": "P1-A01",

    "threshold_cm": 100

    }

    }

    Код для узла `Function: Trigger Pulse`:

    Этот узел генерирует короткий импульс на пине `Trig`.

    // Конфигурация пина UI-05 как выхода
    

    // Для HC-SR04 нужен импульс 10 мкс

    // Node-RED не позволяет напрямую управлять микросекундами в setTimeout.

    // Для точного управления GPIO на микросекундном уровне потребуется

    // использовать внешний модуль или C-binding.

    // В данном примере используется приближение, которое может работать

    // для некоторых датчиков, но не гарантирует точность.

    // Для промышленных датчиков часто используется Modbus или другие протоколы,

    // где генерация импульса не требуется от контроллера.

    // Отправляем 1, затем 0 через минимальную задержку

    node.send({ payload: 1, topic: "trig_pulse" }); // Отправляем высокий уровень

    setTimeout(() => {

    node.send({ payload: 0, topic: "trig_pulse" }); // Отправляем низкий уровень

    }, 1); // Минимальная задержка в мс, имитирующая короткий импульс

    // Важно: узел RPI GPIO Out должен быть настроен на пин UI-05 и работать в режиме "Digital Output".

    // Имя узла RPI GPIO Out должно быть "UI-05 (Trig)"

    return null; // Не отправляем msg дальше по основному пути

    ⚠️ Предупреждение: Узел `RPI GPIO Out` должен быть настроен на пин `UI-05` и работать в режиме "Digital Output". Для точного управления микросекундными импульсами на GPIO в Linux/Node-RED могут потребоваться дополнительные библиотеки или использование ARM32 для критичных сценариев.

    Код для узла `Function: Calculate Distance`:

    Этот узел измеряет длительность импульса на пине `Echo` и преобразует ее в расстояние.

    Предполагается, что `RPI GPIO In: UI-06` выдает `msg.payload` как `1` (начало импульса) и `0` (конец импульса).

    Для этого потребуется более сложная логика с использованием `flow context` для измерения времени.

    // Сохраняем время начала импульса в контексте потока
    

    if (msg.payload === 1) { // Начало импульса Echo

    flow.set('echoStartTime', process.hrtime.bigint());

    node.status({fill:"yellow", shape:"dot", text:"Echo started"});

    return null; // Не отправляем сообщение дальше, ждем конца импульса

    } else if (msg.payload === 0) { // Конец импульса Echo

    let startTime = flow.get('echoStartTime');

    if (startTime) {

    let endTime = process.hrtime.bigint();

    let duration_ns = endTime - startTime; // Длительность в наносекундах

    let duration_us = Number(duration_ns) / 1000; // Длительность в микросекундах

    // Скорость звука в воздухе ~343 м/с или 0.0343 см/мкс

    // Расстояние = (длительность * скорость звука) / 2 (туда и обратно)

    let distance_cm = (duration_us * 0.0343) / 2;

    // Валидация расстояния (например, от 2 см до 400 см для HC-SR04)

    if (distance_cm < 2 || distance_cm > 400) {

    node.status({fill:"red", shape:"dot", text:"Out of range: " + distance_cm.toFixed(1) + " cm"});

    node.error("Distance out of valid range: " + distance_cm.toFixed(1) + " cm", msg);

    return null;

    }

    msg.payload = distance_cm; // Передаем расстояние в см

    node.status({fill:"green", shape:"dot", text:`Distance: ${distance_cm.toFixed(1)} cm`});

    return msg;

    } else {

    node.warn("Echo end without start time. Possible missed start pulse.", msg);

    node.status({fill:"red", shape:"dot", text:"Echo error"});

    return null;

    }

    }

    return null;

    ⚠️ Предупреждение: Узел `RPI GPIO In` должен быть настроен на пин `UI-06` и работать в режиме "Digital Input" с прерываниями по обоим фронтам (rising and falling edge). Это критично для точного измерения длительности импульса.

    Код для узла `Function: Determine Status`:

    Этот узел определяет статус парковочного места на основе расстояния.

    const THRESHOLD_CM = 100; // Порог в сантиметрах: если ближе, то занято
    
    

    let distance_cm = parseFloat(msg.payload);

    if (isNaN(distance_cm)) {

    node.error("Invalid distance value: " + msg.payload, msg);

    node.status({fill:"red", shape:"dot", text:"Invalid input"});

    return null;

    }

    let status = (distance_cm < THRESHOLD_CM) ? "OCCUPIED" : "FREE";

    // Формируем исходящее сообщение по контракту

    msg.payload = JSON.stringify({

    status: status,

    distance_cm: parseFloat(distance_cm.toFixed(1)), // Округляем для читаемости

    source: "ultrasonic-pk-01",

    ts: Date.now(),

    meta: {

    spot_id: "P1-A01",

    threshold_cm: THRESHOLD_CM

    }

    });

    msg.topic = "telemetry/parking/spot-01";

    node.status({fill:"green", shape:"dot", text:`Status: ${status} (${distance_cm.toFixed(1)} cm)`});

    return msg;

    Лабораторные работы

    Лабораторная работа 1: COURSE-16-M05-LAB11 — Развертывание адаптивного уличного освещения

    Цель: Собрать и настроить поток Node-RED для управления реле на основе показаний симулированного датчика освещенности и MQTT-команд. Оборудование: Контроллер HI-Core, светодиод для индикации, ПК. Задачи:
  • Подключите светодиод через резистор к релейному выходу `RL-01` контроллера.
  • Создайте поток Node-RED `FLOW-AUTO-LIGHT-015` согласно схеме.
  • Вместо реального датчика используйте узел `Inject` с `msg.payload`, содержащим число (например, 50 для "темно" и 1000 для "светло"). Настройте его на повторение каждые 10 секунд.
  • Настройте MQTT-клиент (например, MQTT Explorer) для отправки команд в топик `cmd/light/street-01/set`.
  • * Пример команды "Включить вручную": `{"command": "ON", "source": "test", "ts": 1678886400000}`

    * Пример команды "Выключить вручную": `{"command": "OFF", "source": "test", "ts": 1678886400000}`

    * Пример команды "Автоматический режим": `{"command": "AUTO", "source": "test", "ts": 1678886400000}`

  • Проверьте все три режима работы FSM: `AUTO`, `MANUAL_ON`, `MANUAL_OFF`.
  • Убедитесь, что узел `Function: Light FSM` имеет два выхода: один для реле, другой для MQTT обратной связи.
  • Проверьте обработку ошибок: отправьте некорректный JSON в MQTT-топик и убедитесь, что ошибка логируется (например, в `Debug` или `audit_log`).
  • Критерии оценки:

    Лабораторная работа 2: COURSE-16-M05-LAB12 — Настройка симулятора Modbus-сенсора

    Цель: Создать поток для опроса Modbus-устройства, обработки и отправки данных. Оборудование: Контроллер HI-Core, ПК. Задачи:
  • Установите палитру `node-red-contrib-modbus` в Node-RED.
  • Создайте поток-симулятор Modbus Slave с помощью узла `Modbus-Server` (TCP), который будет хранить значение (например, 450) в Holding Register 0. Настройте его на порт 502, Slave ID 1.
  • Создайте основной поток `FLOW-INTEG-MODBUS-009`.
  • Настройте узел `Modbus-Getter` для опроса вашего симулятора (адрес `127.0.0.1`, порт `502`, Slave ID `1`, FC 3: Read Holding Registers, Address 0, Quantity 1).
  • Реализуйте узел `Function: Parse & Format` для парсинга, валидации и форматирования данных. Убедитесь, что у него три выхода: один для MQTT, другой для MySQL, третий для ошибок.
  • Подключите `Debug` к выходу MQTT и убедитесь, что отформатированные данные появляются и соответствуют контракту сообщения.
  • Подключите `Debug` к выходу MySQL и убедитесь, что `msg.topic` содержит корректный SQL-запрос.
  • Проверьте, что при изменении значения в `Modbus-Server` (симуляторе) данные корректно обновляются в `Debug`.
  • Проверьте обработку ошибок: измените значение в `Modbus-Server` на некорректное (например, 10000) и убедитесь, что узел `Function` выдает ошибку, не отправляет данные дальше по основным выходам, но отправляет сообщение об ошибке на третий выход, который должен быть подключен к `Log to MySQL: audit_log`.
  • Критерии оценки:

    ---

    Тест для самопроверки: COURSE-16-M05-QUIZ

  • Какой протокол является промышленным стандартом для опроса датчиков по шине RS-485?
  • * a) MQTT

    * b) TCP/IP

    * c) Modbus

    * d) DALI

  • Какова цель использования "контракта сообщения" в Node-RED?
  • * a) Шифрование данных

    * b) Стандартизация формата данных для предсказуемости и совместимости

    * c) Увеличение скорости передачи

    * d) Сжатие сообщений

  • Какой узел Node-RED используется для централизованного перехвата ошибок в потоке?
  • * a) `Status`

    * b) `Link Out`

    * c) `Function`

    * d) `Catch`

  • Что необходимо установить на физических концах шины RS-485 для стабильной работы?
  • * a) Усилитель сигнала

    * b) Терминирующий резистор 120 Ом

    * c) Фильтр питания

    * d) Опторазвязку

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

    * b) Для обмена данными между разными контроллерами

    * c) Для сохранения состояния переменных (например, FSM) между перезагрузками контроллера

    * d) Для временного хранения больших файлов

  • В сценарии с адаптивным освещением, какой компонент отвечает за принятие решения о включении света?
  • * a) Реле

    * b) Датчик освещенности

    * c) Узел `Function` с логикой FSM

    * d) MQTT-брокер

  • Какая информация НЕ является обязательной в стандартном контракте сообщения Академии HI?
  • * a) `value`

    * b) `source`

    * c) `ts` (timestamp)

    * d) `color`

  • Что произойдет, если в сценарии мониторинга воздуха Modbus-датчик отключится от шины?
  • * a) Поток Node-RED аварийно завершит работу.

    * b) Узел `Modbus-Getter` сгенерирует ошибку (timeout), которая будет поймана узлом `Catch`.

    * c) Система будет использовать последнее успешное значение бесконечно.

    * d) Контроллер автоматически перезагрузится.

  • Согласно стандарту `WIRING`, экран кабеля витой пары для шины данных следует подключать:
  • * a) К клемме `GND` с обеих сторон.

    * b) К клемме `GND` только со стороны контроллера.

    * c) К отдельной шине заземления `PE`.

    * d) Не подключать вообще.

  • Какую топологию сети следует использовать для подключения устройств по шине RS-485?
  • * a) Звезда

    * b) Кольцо

    * c) Шина (линейная)

    * d) Дерево

    ---

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

    | Симптом | Возможная причина | Решение |

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

    | Сценарий 1: Реле не включается/выключается. | 1. Ошибка в схеме подключения.
    2. Неверный номер реле в узле `Relay Out`.
    3. Ошибка в логике узла `Function`.
    4. Некорректный формат команды MQTT. | 1. Проверить схему `WIRING-LIGHT-018` и соответствие цветов проводников стандарту Академии. Проверить напряжение на реле.
    2. Убедиться, что номер реле в узле Node-RED соответствует физическому подключению на контроллере HI.
    3. Подключить узел `Debug` после `Function` и проверить `msg.payload` (должно быть 0 или 1) и `node.status()`. Проверить логику FSM.
    4. Проверить JSON-формат команды MQTT, включая `command`, `source`, `ts`. |

    | Сценарий 2: Ошибки "Timeout" от `Modbus-Getter`. | 1. Обрыв или неверная полярность шины RS-485.
    2. Несовпадение настроек (скорость, ID, четность, стоп-биты).
    3. Отсутствие питания на датчике.
    4. Отсутствие терминирующих резисторов. | 1. Проверить целостность кабеля и полярность A/B. Попробовать поменять местами A и B на контроллере. Использовать мультиметр для проверки напряжения на шине.
    2. Убедиться, что Slave ID, скорость, четность, стоп-биты совпадают на контроллере и датчике. Проверить настройки Modbus-Client в Node-RED.
    3. Проверить питание датчика (обычно 24VDC).
    4. Установить терминирующие резисторы 120 Ом на крайних устройствах шины RS-485. |

    | Сценарий 2: Modbus возвращает неверные данные. | 1. Ошибка "off-by-one" в адресе регистра.
    2. Неправильная интерпретация формата данных (порядок байт, тип данных).
    3. Неверный код функции (FC). | 1. Внимательно изучить карту регистров датчика. Если в документации указан регистр `40001`, в узле `Modbus-Getter` нужно указать адрес `0`.
    2. Изучить документацию на датчик, при необходимости использовать узел `Function` для манипуляции с буфером (`msg.payload.buffer`) и правильной сборки числа (например, для 32-битных float с учетом порядка байт/слов).
    3. Убедиться, что выбран правильный FC (например, FC3 для Holding Registers, FC4 для Input Registers). |

    | Сценарий 3: Датчик расстояния не работает/выдает 0. | 1. Неверное подключение Trig/Echo.
    2. Ошибка в логике измерения времени.
    3. Недостаточное питание датчика.
    4. Некорректная настройка GPIO пинов. | 1. Проверить схему `WIRING-SENS-012`. Убедиться, что `UI-05` подключен к `Trig`, а `UI-06` к `Echo`.
    2. Подключить `Debug` после `Function: Calculate Distance` и проверить `msg.payload`. Убедиться, что `flow.get('echoStartTime')` корректно сохраняется и `process.hrtime.bigint()` работает.
    3. Проверить стабильность питания +5V на датчике. Убедиться, что контроллер HI может обеспечить достаточный ток.
    4. Убедиться, что `UI-05` настроен как "Digital Output", а `UI-06` как "Digital Input" с прерываниями по обоим фронтам (rising and falling edge) в настройках узла `RPI GPIO In`. |

    | Общее: Команды по MQTT не доходят. | 1. Неверный топик.
    2. Ошибка в настройках MQTT-брокера.
    3. Некорректный формат JSON в сообщении.
    4. Проблемы с сетевым подключением контроллера. | 1. Проверить, что топик в `MQTT In` узле и в клиенте (например, MQTT Explorer) совпадают до символа. Использовать wildcard `#` для отладки.
    2. Проверить доступность брокера с контроллера (например, командой `ping ` или `mosquitto_pub`/`mosquitto_sub` из терминала контроллера). Проверить логи брокера.
    3. Валидировать JSON сообщения, убедиться, что он соответствует контракту. Использовать `JSON.parse()` с `try-catch` в узле `Function` для отладки.
    4. Проверить сетевые настройки контроллера (IP-адрес, шлюз, DNS). Убедиться, что контроллер имеет доступ к MQTT-брокеру. |