====== Работа с Telegram API внутри OpenSCADA. ======
Для чего это надо? Я бы использовал только для уведомлений. Некоторые, возможно, захотят и управление реализовывать через команды боту. Минимально для работы нужно реализовать два метода: отправка сообщения и прием.
Всю базовую информацию по созданию ботов в Telegram можно прочитать в тысяче статей по всему интернету, но если парой слов, то для создания бота вам нужно написать @BotFather и получить ключ API.
===== Подготовка к работе =====
Требуется создать транспорт для общения с серверами Telegram. Для этого в дереве проекта переходим в раздел ''Транспорты > SSL'' и создаем выходной транспорт. Имя можно выбрать любое, но в библиотеке, приложенной к статье, используется имя по умолчанию ''Telegram''. Требуется именно SSL, а не просто сокет.
В транспорте нельзя указывать протокол HTTPS, но известно, что для обмена через HTTPS используется порт 443. Адрес у транспорта указываем следующий:
api.telegram.org:443
===== Отправка сообщения =====
Для отправки используется метод ''sendMessage'' [[https://core.telegram.org/method/messages.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'' [[https://telegram-bot-sdk.readme.io/reference/getupdates|Telegram API]]. Ответы от сервера приходят в формате JSON. Данный формат не поддерживается "из коробки", однако сообществом разработана [[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. В поле "Описание" можно хранить все, что необходимо. Если таких параметров конфигурации требуется больше чем один, то не проблема. По одному в строку в формате '':''. В моем случае в данном поле хранятся телефон, адрес почты и 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);
}