Table of Contents
Работа с Telegram API внутри OpenSCADA.
Для чего это надо? Я бы использовал только для уведомлений. Некоторые, возможно, захотят и управление реализовывать через команды боту. Минимально для работы нужно реализовать два метода: отправка сообщения и прием.
Всю базовую информацию по созданию ботов в Telegram можно прочитать в тысяче статей по всему интернету, но если парой слов, то для создания бота вам нужно написать @BotFather и получить ключ API.
Подготовка к работе
Требуется создать транспорт для общения с серверами Telegram. Для этого в дереве проекта переходим в раздел Транспорты > SSL и создаем выходной транспорт. Имя можно выбрать любое, но в библиотеке, приложенной к статье, используется имя по умолчанию Telegram. Требуется именно SSL, а не просто сокет.
В транспорте нельзя указывать протокол HTTPS, но известно, что для обмена через HTTPS используется порт 443. Адрес у транспорта указываем следующий:
api.telegram.org:443
Отправка сообщения
Для отправки используется метод sendMessage Telegram API. В качестве аргументов ему требуется передать chat_id и текст. Аргументов может быть и больше, но в базовом варианте, когда передаем только текст, этого достаточно.
В коде ниже есть 4 переменных.
| Имя | Тип | Описание |
| Transport | Строка | Имя транспорта в “Транспорты > SSL” |
| APIKey | Строка | Ключ, полученный от @BotFather |
| ChatID | Целочисленный | Уникальный идентификатор переписки |
| Message | Строка | Текст сообщения |
tr = SYS.Transport.SSL.nodeAt("out_" + Transport); req = SYS.XMLNode("POST"); req.setAttr("URI", "/bot" + APIKey + "/sendMessage"); req.childAdd("cnt").setAttr("name", "chat_id").setText(ChatID.toString()); req.childAdd("cnt").setAttr("name", "text").setText(Message); req.childAdd("cnt").setAttr("name", "disable_notification").setText("true"); tr.messIO(req,"HTTP");
Получение сообщений
Для получения сообщений используется метод getUpdates Telegram API. Ответы от сервера приходят в формате JSON. Данный формат не поддерживается “из коробки”, однако сообществом разработана библиотека, которая преобразует JSON строку в объект, с которым удобно работать в JavaLikeCalc.
В коде ниже используются 3 переменных.
| Имя | Тип | Описание |
| Transport | Строка | Имя транспорта в “Транспорты > SSL” |
| APIKey | Строка | Ключ, полученный от @BotFather |
| updateId | Целочисленный | ID последнего полученного сообщения, при первом запуске значение 0 |
jsonLib = SYS.DAQ.JavaLikeCalc.lib_Json; tr = SYS.Transport.SSL.nodeAt("out_" + Transport); messagesCount = 1; while(messagesCount){ updateId += 1; req = SYS.XMLNode("POST"); req.setAttr("URI", "/bot" + APIKey + "/getUpdates"); req.childAdd("cnt").setAttr("name", "offset").setText(updateId.toString()); tr.messIO(req,"HTTP"); deser_err = ""; outputObject = jsonLib.deserialize(req.text(), deser_err); messagesCount = outputObject.result.length; for (var i = 0; i < outputObject.result.length; i++){ item = outputObject.result[i]; updateId = max(updateId, item.update_id); username = item.message.from.username; chatID = item.message.from.id; messageText = item.message.text; } }
Данный код будет загружать все доступные сообщения. Между вызовами требуется хранить последнее значение updateId, так как этот параметр позволяет пропускать уже обработанные сообщения
Библиотека для работы
Данные примеры кода стоит оформить в виде библиотеки в Сбор данных → Вычислитель на Java-подобном языке → Библиотека. Так будет гораздо удобнее использовать код повторно.
Отправка оповещений группе пользователей
Обычно телеграм бот нужен для оповещений об аварийных ситуациях. И оповещать приходится группу сотрудников.
Где брать список абонентов, которым рассылать оповещения? Логично было бы использовать список пользователей OpenSCADA и добавлять их в дополнительную группу. Так будет проще управлять списком получателей.
Где хранить ID пользователя Telegram? Логично, что в описании пользователя OpenSCADA. В поле “Описание” можно хранить все, что необходимо. Если таких параметров конфигурации требуется больше чем один, то не проблема. По одному в строку в формате <TYPE>:<VALUE>. В моем случае в данном поле хранятся телефон, адрес почты и ID пользователя Telegram.
В коде ниже есть 4 переменных.
| Имя | Тип | Описание |
| Transport | Строка | Имя транспорта в “Транспорты > SSL” |
| Group | Строка | Имя группы пользователей для оповещения |
| APIKey | Строка | Ключ, полученный от @BotFather |
| Message | Строка | Сообщение |
TelegramLib = SYS.DAQ.JavaLikeCalc.lib_Telegram; users = SYS.Security.nodeList("usr_"); for (var j = 0; j < users.length; j++){ user = SYS.Security.nodeAt(users[j]); // Пропуск пользователей, которым не разрешены уведомления if (user.groups().indexOf(Group) == -1) continue; // Обработка сохраненных транспортов для уведомлений transports = user.cfg("LONGDESCR"); for(offset = 0; (transport = transports.parse(0, "\n", offset)).length; ){ tmp = transport.split(":"); TransportType = tmp[0]; TransportValue = tmp[1]; if (TransportType == "telegram"){ TelegramLib.SendMessage(Transport, APIKey, TransportValue, Message); break; } } }
Отправка журнала ошибок
Выше есть код для массовой отправки сообщений, но добавлять в каждый обработчик датчика код для отправки - плохая идея. Обработчик должен формировать сообщение в журнал, а отдельный виртуальный контроллер отправлять сообщения из журнала по какому-то каналу связи.
При помощи функции SYS.Archive.messGet запрашиваем все сообщения за определенный период, которые подходят под указанную категорию. Категорией может быть просто звездочка для всех сообщений или немного сложнее. Можно и регулярным выражением воспользоваться при необходимости.
В коде ниже есть 7 переменных.
| Имя | Тип | Описание |
| TelegramTransport | Строка | Имя транспорта в “Транспорты > SSL” |
| TelegramAPIKey | Строка | Ключ, полученный от @BotFather |
| TelegramGroup | Строка | Имя группы пользователей для оповещения через Telegram |
| MessagesLevel | Строка | Минимальный уровень сообщений из журнала |
| MessagesCat | Строка | Категория сообщений из журнала |
| TimeBegin | Целый | Время начала |
| utmLast | Целый | Микросекунды последнего сообщения |
using Special.FLibSYS; TelegramLib = SYS.DAQ.JavaLikeCalc.lib_Telegram; if (f_start){ TimeBegin = SYS.time(); } // Запрос сообщений по шаблону TimeFinish = SYS.time(); messages = SYS.Archive.messGet(TimeBegin, TimeFinish, MessagesCat, MessagesLevel); // Обновляем время для следующей итерации TimeBegin = TimeFinish; utmLast = -1; for (var i = 0; i < messages.length; i++){ message = messages[i]; tm = message.tm; if ((TimeBegin == tm) && (utmLast > message.utm)) continue; utmLast = message.utm; // Сохраняем микросекунды для следующей итерации TimeStr = tmFStr(tm,"%d.%m.%Y %H:%M:%S"); messageText = TimeStr + ": " + message.mess; TelegramLib.SendAll(TelegramTransport, TelegramGroup, TelegramAPIKey, messageText); }
