Skip to content

Группировка и слияние телеметрии ​

1. Концепция технологических групп оборудования ​

В условиях реального промышленного производства тепловой агрегат (печь или сушильный барабан) представляет собой сложный распределенный комплекс. Технологические датчики подключаются к различным шкафам автоматики и модулям ввода-вывода:

  • Термопары рабочих зон опрашиваются многоканальными модулями аналогового ввода ПЛК (AI 8xTC);
  • Расходомер природного газа передает объемный расход через искробезопасный барьер по интерфейсу RS-485 Modbus RTU;
  • Весовой терминал вагонеток передает массу садки по протоколу Modbus TCP через цеховой Ethernet;
  • Электросчетчики ТЭНов включены в автоматизированную систему коммерческого учета электроэнергии (АСКУЭ);
  • Оптический газоанализатор дымовых газов выдает концентрации CO2,O2,CO\text{CO}_2, \text{O}_2, \text{CO} через собственный цифровой интерфейс.

Для объединения разрозненных потоков телеметрии в микросервисе HeatBalance.IoTService реализована концепция Технологических групп (Device Groups). Группа устройств выступает цифровым двойником физической печи.


2. Модель данных технологической группы ​

Сущность DeviceGroup описывается следующей схемой:

csharp
public sealed class DeviceGroup
{
    public Guid Id { get; set; }
    public string OwnerId { get; set; } = string.Empty;       // Изоляция данных пользователя
    public string Name { get; set; } = string.Empty;          // Наименование печи/участка
    public CalculationType CalculationType { get; set; }       // 1: Conveyor, 2: Chamber, 3: Electric, 4: Drum
    public Guid DatasetId { get; set; }                        // Эталонный шаблон геометрии и констант
    public List<Guid> DeviceIds { get; set; } = new();         // Идентификаторы связанных датчиков
    public DateTimeOffset CreatedAt { get; set; }              // Дата регистрации группы
}

Связанные сущности и ограничения: ​

  • Изоляция арендаторов (OwnerId): пользователи имеют доступ исключительно к своим группам и связанным датчикам;
  • Привязка к датасету (DatasetId): группа жестко связана с конфигурационным датасетом, содержащим статические константы агрегата (теплоемкости материалов, коэффициенты излучения, размеры рабочей камеры);
  • Список датчиков (DeviceIds): динамический массив UUID, определяющий, какие зарегистрированные измерительные каналы входят в состав агрегата.

3. Алгоритм динамического слияния (JSON Deep Merge) ​

Математическая постановка задачи ​

Пусть Dbase\mathcal{D}_{base} — базовый JSON-документ технологического датасета, представленный в виде множества пар «ключ-значение»:

Dbase={(k,v)∣k∈Kbase}\mathcal{D}_{base} = \{ (k, v) \mid k \in \mathcal{K}_{base} \}

Пусть агрегат объединяет множество датчиков G={d1,d2,…,dm}\mathcal{G} = \{ d_1, d_2, \dots, d_m \}. Для каждого датчика dj∈Gd_j \in \mathcal{G} в базе данных хранится история показаний. Обозначим через Pj(tj)\mathcal{P}_j(t_j) самый последний по временной метке tjt_j валидный телеметрический пакет датчика djd_j:

Pj(tj)={(k,v)∣k∈Kdj}\mathcal{P}_j(t_j) = \{ (k, v) \mid k \in \mathcal{K}_{d_j} \}

Результирующий расчетный вектор M\mathcal{M} формируется оператором правостороннего слияния:

M=Dbase⊕P1(t1)⊕P2(t2)⋯⊕Pm(tm)\mathcal{M} = \mathcal{D}_{base} \oplus \mathcal{P}_1(t_1) \oplus \mathcal{P}_2(t_2) \dots \oplus \mathcal{P}_m(t_m)

Правило разрешения коллизий для любого ключа kk:

M(k)={Pj(k),если ∃dj∈G:k∈Kdj и tj=max⁡(t)Dbase(k),если ∀dj∈G:k∉Kdj\mathcal{M}(k) = \begin{cases} \mathcal{P}_j(k), & \text{если } \exists d_j \in \mathcal{G}: k \in \mathcal{K}_{d_j} \text{ и } t_j = \max(t) \\ \mathcal{D}_{base}(k), & \text{если } \forall d_j \in \mathcal{G}: k \notin \mathcal{K}_{d_j} \end{cases}

Таким образом, динамические оперативные показания КИПиА приоритетно перекрывают статические проектные уставки базового датасета.


Программная реализация слияния в DeviceGroupsController ​

Слияние выполняется на базе узлов DOM-дерева System.Text.Json.Nodes.JsonObject:

csharp
// 1. Извлечение базового шаблона датасета
var dataset = await _datasetApiClient.GetDatasetAsync(group.DatasetId);
var datasetNode = JsonNode.Parse(dataset.InputJson) as JsonObject ?? new JsonObject();

// 2. Итеративный обход всех датчиков группы
if (group.DeviceIds != null && group.DeviceIds.Count > 0)
{
    foreach (var deviceId in group.DeviceIds)
    {
        // Извлечение самого свежего пакета телеметрии для каждого датчика
        var latestDeviceLog = await _dbContext.TelemetryLogs
            .Where(l => l.DeviceId == deviceId && !string.IsNullOrWhiteSpace(l.PayloadJson))
            .OrderByDescending(l => l.Timestamp)
            .FirstOrDefaultAsync(ct);

        if (latestDeviceLog != null && !string.IsNullOrWhiteSpace(latestDeviceLog.PayloadJson))
        {
            var telemetryNode = JsonNode.Parse(latestDeviceLog.PayloadJson);
            if (telemetryNode is JsonObject telemetryObj)
            {
                // Наложение динамических полей поверх шаблона датасета
                foreach (var prop in telemetryObj)
                {
                    datasetNode[prop.Key] = prop.Value?.DeepClone();
                }
            }
        }
    }
}

// 3. Формирование монолитного расчётного вектора
var mergedInputJson = datasetNode.ToJsonString();
log.MergedPayloadJson = mergedInputJson;

4. Пример пошаговой трансформации данных ​

Исходный шаблон датасета (статические характеристики печи): ​

json
{
  "dryMaterialMass": 10000.0,
  "initialMoisture": 10.0,
  "finalMoisture": 2.0,
  "airTemperature": 20.0,
  "recirculateTemp": 200.0,
  "fuelCalorificValue": 35800.0,
  "wallsArea": 120.0,
  "vaultArea": 45.0
}

Пакет телеметрии от датчика влажности (ID 2): ​

json
{
  "initialMoisture": 14.5,
  "finalMoisture": 1.1
}

Пакет телеметрии от термопары рециркулята (ID 0): ​

json
{
  "recirculateTemp": 242.8,
  "airTemperature": 24.0
}

Результирующий расчетный вектор MergedPayloadJson: ​

json
{
  "dryMaterialMass": 10000.0,
  "initialMoisture": 14.5,
  "finalMoisture": 1.1,
  "airTemperature": 24.0,
  "recirculateTemp": 242.8,
  "fuelCalorificValue": 35800.0,
  "wallsArea": 120.0,
  "vaultArea": 45.0
}

Обратите внимание: конструктивные параметры (fuelCalorificValue, wallsArea, vaultArea, dryMaterialMass) сохранены из проектного шаблона, а динамические теплотехнические параметры обновлены до фактически измеренных физических значений.


5. Конвейер выполнения группового расчета ​

Инициация расчета теплового баланса группы выполняется через эндпоинт:

http
POST /api/v1/iot/groups/{id}/calculate
Authorization: Bearer <JWT-токен оператора>

6. Аудит и структура таблицы GroupTelemetryLogs ​

Все выполненные расчеты агрегатов сохраняются в таблице GroupTelemetryLogs для построения трендов и анализа энергоэффективности:

ПолеТип данныхНазначение
IdUUIDУникальный суррогатный ключ записи
GroupIdUUIDВнешний ключ привязки к технологической группе
TimestampDateTimeOffsetТочное астрономическое время инициации расчета (UTC)
StatusTelemetryStatusСтатус выполнения: 0: Pending, 1: Success, 3: CalculationError
MergedPayloadJsonTEXTПолный объединенный JSON входных параметров, переданный в расчет
ResultJsonTEXTПолный структурированный результат расчета статей теплового баланса
EfficiencyDOUBLE PRECISIONИтоговый эксергетический или тепловой КПД установки (η\eta, %)
ErrorMessageTEXTДиагностическое сообщение об ошибке (при сбое расчета)

Получение ретроспективной истории расчетов группы осуществляется методом:

http
GET /api/v1/iot/groups/{id}/history

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

Разработано в рамках проекта моделирования тепловых балансов и промышленного мониторинга