Выгрузка исторических данных во внешнее приложение

Коллеги, здравствуйте.

Перед нам стоит задача обработки исторических данных, получаемых со счетчика WB-MAP3E через контроллер Wiren Board 7.

Есть несколько вопросов:

  1. Подскажите, какие мы можем использовать интерфейсы для передачи собираемых контроллером данных во внешние системы (с учетом временной недоступности каналов связи)? Где можно посмотреть их описание?
  2. При обращении через протокол OPC UA мы можем выгружать только текущие показания на момент обращения или так же и исторические данные?
  3. Исторические данные на контроллере хранятся в базе данных. Есть ли возможность обращения к этим данным через публичный интерфейс (htttp)? Какие есть интерфейсы для получения данных из этой БД?

Заранее спасибо!
С уважением,
Тамара.

Добрый день

Можно выгружать, а дальше хранить и обрабатывать:

или

есть неплохая статья на эту тему, все подробно описано:

Леонид, спасибо за ответ!

Насколько такая схема может быть применима в случае нестабильного интернет-соединения на предприятии, где ведутся замеры? Идея была в том, чтобы брать данные с контроллера, который не зависит от подобных “сюрпризов”. Есть ли рабочие варианты того, как брать уже сохраненные на контроллере данные?

Я бы сделал так: контроллер с MQTT собирает оперативные данные (но не хранит историю), на нем стоит exporter (любой). Рядом, в той же сети есть еще один хост (например Raspberry Pi), который собирает данные через этот exporter и хранит их в специализированной БД (InfluxDB, Prometheus, VictoriaMetrics). Пользователи работают только со вторым хостом и не беспокоят контроллер своими запросами, доступ извне и разграничение доступа делаются на этом втором хосте.
Но в любом случае вы сами выбираете решение исходя из своих требований и ограничений.

А выгрузку можно попробовать сделать prometheus-mqtt-transport.

А если мы решим вот одним из подобных решений брать данные из контроллера раз в минуту, например. А нам нужна их итоговая точность с шагом в 1 сек. Где-то из контроллера мы сможем брать детальные данные за прошедшую минуту? Или “оперативные” это только за текущий момент?

Извиняюсь, если, возможно, вопрос как-то не совсем корректно сформулирован. Но постаралась описать нашу проблематику :slight_smile: Идея в том, чтобы контроллер за нас собирал детально раз в секунду, а мы с него могли брать раз в минуту, плюс если вдруг где-то был у нас "пропуск, то смогли бы достучаться и до истории прошедших нескольких минут тоже

Зачем?
На контроллере уже есть GitHub - wirenboard/wb-mqtt-db: Wiren Board database logger с базой данных и доступом по RPC.

Вот именно про это и пишу.

Посмотрите внимательно выше, я изначально ответил на этот вопрос. Я считаю что гонять контроллер запросами к БД неизвестно откуда (интернет) - плохая затея.

Вот это то, что нам нужно, похоже. Спасибо за ответ! Будем смотреть детальнее теперь.

1 лайк

Ну, тут смешиваете сразу два мотива: “неизвестно откуда” - решается защитой канала (VPN туннель, например) и авторизацией и нагрузку - а она что от моста на другой брокер что от выполнения запросов примерно одна.

Конечно, все можно решить, все можно запихать в одну железку — она справится. Я же говорю о том что не надо делать из контроллера универсальный инструмент, у него другая задача. Это критически важный элемент системы и к нему нужен особый подход, это не рядовой хост. Контроллер отвечает за управление своими подопечными, а вот запросы по истории гонять, заниматься мониторингом и алертингом - это не его задача. Представьте что у вас 10 пользователей и каждый мониторит что-то, делает запросы, выборки - зачем это все рассчитывать на контроллере? Зачем заниматься организацией доступа извне к контроллеру. Я бы закрыл контроллер белым списком, а вот БД с данными вынес бы наружу. Так даже резервные копии проще делать и играться с данными, не нагружая ничем лишним сам контроллер.

Неверно. Тут обсуждается задача синхронизации данных после отсутствия связи. Ну либо я не так понял саму задачу.
Предполагал что вопрос про то чтобы в (возможно удаленной) БД не было пропусков.

Да, такой вопрос был задан по ходу рассуждений.

Леонид,

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

Спасибо за ваши ответы и за мысли о теме - это тоже поможет продумать корректно итоговый вариант работы с контроллером.

VPN есть, да. Запросы, конечно, будем стараться делать разумно, не отнимая весь ресурс контроллера.

Здравствуйте.
Мы с Тамарой работаем вместе над текущей задачей, так что я не буду создавать новый топик и продолжу обсуждение проблемы здесь.
Я написал тестовую прогу на C# для получения списка каналов из wb-mqtt-db согласно вашей документации ссылка. И она не выдаёт результат. Каждый шаг, как то соединение, подписка на очередь с ответом и публикация запроса проходят нормально, ошибок нет, статусы я проверяю, но результат в reply очередь не посылается. Прога просто висит минуту и закрывается. В логах wb-mqtt-db на самом контроллере вообще ничего, никаких признаков того, что кто-то пытался пообщаться с wb-mqtt-db. Что я делаю не так?

using MQTTnet.Client;
using MQTTnet;
using MQTTnet.Protocol;

string broker = "192.168.0.1";
int port = 1883;
string clientId = Guid.NewGuid().ToString();
string getChannelsRequestTopic = $"/rpc/v1/db_logger/history/get-channels/{clientId}";
string getChannelsResponseTopic = getChannelsRequestTopic + "/reply";

// Create a MQTT client factory
var factory = new MqttFactory();

// Create a MQTT client instance
using var mqttClient = factory.CreateMqttClient();

// Create MQTT client options
var options = new MqttClientOptionsBuilder()
    .WithTcpServer(broker, port) // MQTT broker address and port
    .WithClientId(clientId)
    .WithCleanSession()
    .Build();

var result = await mqttClient.ConnectAsync(options);
if(result.ResultCode != MqttClientConnectResultCode.Success)
{
    Console.WriteLine("Unable to connect to queue");
    return 1;
}

var subResult = await mqttClient.SubscribeAsync(getChannelsResponseTopic).ConfigureAwait(false);
if(subResult.Items.First().ResultCode != MqttClientSubscribeResultCode.GrantedQoS0)
{
    Console.WriteLine("Unable to subscribe");

    return 3;
}

mqttClient.ApplicationMessageReceivedAsync += e =>
{
    Console.WriteLine("received");

    return Task.CompletedTask;
};

var payload = "{\"id\": \"2730b391-d1ac-4041-888a-1cb05940c03d\", \"params\": { } }"; 
var message = new MqttApplicationMessageBuilder()
                    .WithTopic(getChannelsRequestTopic)
                    .WithPayload(payload)
                    .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce)
                    .Build();

var publishResult = await mqttClient.PublishAsync(message).ConfigureAwait(false);
if(publishResult.IsSuccess == false)
{
    Console.WriteLine("Unable to send message");

    return 2;
}

await Task.Delay(TimeSpan.FromMinutes(1)).ConfigureAwait(false);

await mqttClient.UnsubscribeAsync(getChannelsResponseTopic).ConfigureAwait(false);
await mqttClient.DisconnectAsync().ConfigureAwait(false);

return 0;

Публикую, ожидая ответ:

mosquitto_pub -t /rpc/v1/db_logger/history/get_channels/someTest -m '{ "id": 1, "params": {} }'

Получаю:

mosquitto_sub -v -t /rpc/v1/db_logger/history/get_channels/someTest/#
/rpc/v1/db_logger/history/get_channels/someTest { "id": 1, "params": {} }
/rpc/v1/db_logger/history/get_channels/someTest/reply {"error":null,"id":1,"result":{"channels":{"DevTest/Channel 1":{"items":0,"last_ts":0},"DevTest/enabled":{"items":0,"last_ts":0},"DevTest/kp1":{"items":0,"last_ts":0},"DevTest/log":{"items":0,"last_ts":0},"R0.01.01/state":{"items":0,"last_ts":0},"R0.01.02/state":{"items":0,"last_ts":0},"R0.01.03/state":{"items":0,"last_ts":0},"R0.01.04/state":{"items":0,"last_ts":0},"R0.02.01/state":{"items":0,"last_ts":0},"R0.04.01/state":{"items":0,"last_ts":0},"R1.02.01/state":{"items":0,"last_ts":0},"R1.03.01/state":{"items":0,"last_ts":0},"R1.05.01/state":{"items":0,"last_ts":0},"alarms/alarm_Просто тест":{"items":12,"last_ts":1712577817},"alarms/log":{"items":8,"last_ts":1712577817},"battery/Charging":{"items":210,"last_ts":1712577845},"battery/Current":{"items":7,"last_ts":1712577817},"battery/Percentage":{"items":7,"last_ts":1712577817},"battery/Power":{"items":7,"last_ts":1712577817},"battery/Voltage":{"items":10,"last_ts":1712580217},"buzzer/enabled":{"items":8,"last_ts":1712577817},"buzzer/frequency":{"items":8,"last_ts":1712577817},"buzzer/volume":{"items":8,"last_ts":1712577817},"hwmon/Board Temperature":{"items":1181,"last_ts":1712670706},"hwmon/CPU Temperature":{"items":7703,"last_ts":1712671657},"knx/data":{"items":7,"last_ts":1712577817},"metrics/data_total_space":{"items":7,"last_ts":1712577817},"metrics/data_used_space":{"items":68,"last_ts":1712651939},"metrics/dev_root_linked_on":{"items":7,"last_ts":1712577817},"metrics/dev_root_total_space":{"items":7,"last_ts":1712577817},"metrics/dev_root_used_space":{"items":525,"last_ts":1712671072},"metrics/load_average_15min":{"items":7703,"last_ts":1712671657},"metrics/load_average_1min":{"items":7703,"last_ts":1712671657},"metrics/load_average_5min":{"items":7703,"last_ts":1712671657},"metrics/ram_available":{"items":7703,"last_ts":1712671657},"metrics/ram_total":{"items":7,"last_ts":1712577817},"metrics/ram_used":{"items":7703,"last_ts":1712671657},"metrics/swap_total":{"items":7,"last_ts":1712577817},"metrics/swap_used":{"items":7,"last_ts":1712577817},"network/Active Connections":{"items":7,"last_ts":1712577817},"network/Default Interface":{"items":7,"last_ts":1712577817},"network/Ethernet 2 IP Online Status":{"items":135,"last_ts":1712579517},"network/Ethernet IP":{"items":7,"last_ts":1712577817},"network/Ethernet IP Online Status":{"items":900,"last_ts":1712671417},"network/GPRS IP":{"items":0,"last_ts":0},"network/GPRS IP Online Status":{"items":134,"last_ts":1712579514},"network/Internet Connection":{"items":7,"last_ts":1712577817},"network/Wi-Fi 2 IP":{"items":0,"last_ts":0},"network/Wi-Fi 2 IP Online Status":{"items":135,"last_ts":1712579517},"network/Wi-Fi IP":{"items":0,"last_ts":0},"network/Wi-Fi IP Online Status":{"items":135,"last_ts":1712579517},"pemp1/sw1":{"items":0,"last_ts":0},"pemp1/tesr1":{"items":0,"last_ts":0},"power_status/Vin":{"items":7703,"last_ts":1712671657},"power_status/working on battery":{"items":210,"last_ts":1712577845},"system/Batch No":{"items":9,"last_ts":1712577817},"system/Current uptime":{"items":7702,"last_ts":1712671615},"system/DTS Version":{"items":9,"last_ts":1712577817},"system/HW Revision":{"items":9,"last_ts":1712577817},"system/Manufacturing Date":{"items":9,"last_ts":1712577817},"system/Release name":

...

Я разобрался с этой проблемой, спасибо.
У меня следующий вопрос. Я изучаю метод get_values. Нулевая версия метода возвращает имя девайса, имя канала и timestamp записи. Timestamp всегда возвращается без милисекунд. Версия 1 этого метода возвращает идентификатор канала вместо имён и может возвратить милисекунды для timestamp.
Я правильно понимаю, что я не могу получить одновременно имена каналов и timestamp c милисекундами? Потому что метод get_channels не возвращает идентификатор канала и чтобы мне узнать идентификатор надо идти в UI?
Или может есть какой-нибудь api для получения каналов с именами и идентификаторами?

Какое значение у “with_milliseconds”?