
Возможно ли в промышленной панели оператора (HMI) создать своего Телеграм бота?
В HMI от Weintek это реализуемо! В данном туториале мы научим нашу панельку работать с Telegram Bot API, напишем Echo-бот и реализуем отправку сообщений по событию.
Когда появляется необходимость удаленного контроля за работой автоматизированной системы, например опрос показаний с каких-либо внешних датчиков по запросу или в случае аварии получение уведомлений - в таких ситуациях, возможно, пригодится дружба Telegram c HMI от Weintek.
Создание проектов для оборудования от Weintek осуществляется в среде разработки EasyBuilder Pro (далее EB Pro). Начиная с версии EB Pro V6.05.01 появилась возможность писать скрипты на JavaScript по стандарту ECMAScript 2017, правда данная опция присутствует пока в стандартных и расширенных моделях cMT серии X. Скачать самую свежую версию EB Pro можно тут.
Для начала нам необходимо создать проект в EB Pro, выбрать вашу модель HMI, в моем случае это cMT2078X.

После добавления “Объект JS” через вкладку “Объекты” в наш проект, появляется возможность использования JavaScript.

Чтобы наш “Объект JS” не сливался с фоном - добавим изображение. В дальнейшем нам это пригодится для обработки события, например нажатие по объекту. Изображение добавляется в настройках “Объекта JS” через “Библиотеку изображений”. Есть возможность выбрать картинку среди стандартных, либо загрузить свою.

От производителя оборудования есть прекрасная документация про "JS Object" . В одном из примеров можно найти библиотеку для отправки POST или GET запросов, она нам понадобится для работы с Telegram Bot API. Скачиваем ее и подключаем через модуль “JS Ресурс”.


Давайте создадим класс и опишем методы отправки и приема сообщения, а также добавим функционал для создания кнопок в боте.
const request = require('./request-0.0.2.js');
export class TelegramBot {
constructor(token) {
this.token = token;
this.url = `https://api.telegram.org/bot${this.token}`;
this.keyboard = Array();
this.resultUpdates = {};
}
В request мы предварительно подключаем скачанную ранее из документации библиотеку, а в конструкторе класса TelegramBot присваиваем первоначальные значения свойствам.
● token - токен, полученный от BotFather, передаваемый при создании экземпляра класса в качестве аргумента
● url - будет хранить ссылку API Telegram
● keyboard - массив для функциональных кнопок, которые будут передаваться вместе с сообщением пользователю
● resultUpdates - ассоциативный массив для хранения последнего сообщения
Получение токена с BotFather я описывать не буду, на Хабре и так полно статей на тему чат-ботов в Telegram (вот к примеру отличная подробная статья).
Далее описываем методы созданного нами класса:
makeButton(button){
this.keyboard.push(button)
}
makeButton принимает на вход в качестве аргументов массив из названий кнопок и добавляет их в один ряд. Например код:
makeButton(['Температура','Давление']);
При передаче сообщения добавит нам такие кнопочки:

Чтобы добавить кнопки рядом ниже необходимо снова использовать метод makeButton и передать ему названия кнопки.
makeButton(['Скорость']);
Результат будет таким:

Далее опишем метод отправки сообщения:
sendMessage(message, chatid){
var final_url = `${this.url}/sendmessage?`;
var json = {
"chat_id": chatid,
"text": message
};
if (this.keyboard != 0) {
json['reply_markup'] = {
"resize_keyboard": true,
"keyboard": this.keyboard
}
};
var jsonData = JSON.stringify(json);
request.post({
url: final_url,
header: {
"content-type": "application/json; charset=utf-8",
},
data: jsonData
},
(error, response, body) => {
if (error == "No error") {
console.log("body:", body);
}
else {
console.log("error:", error);
console.log("response:", response);
console.log("body:", body);
}
}
);
}
sendMessage отправляет сообщение пользователю методом запроса POST, в качестве аргументов принимает message (текст сообщения) и chatid (id пользователя). Свой id можно например узнать через данного бота. Если данные успешно доставлены - выводим в консоль тело ответа от сервера, в случае ошибки совместно с телом в консоль дополнительно производится вывод информация о ошибке.
getUpdates(){
var final_url = `${this.url}/getUpdates?offset=-1`;
request.get({
url: final_url
},
(error, response, body) => {
if (error == "No error") {
var body_json = JSON.parse(body);
this.resultUpdates = body_json.result[0];
}
else {
console.log("error:", error);
console.log("response:", response);
console.log("body:", body);
}
}
);
return this.resultUpdates
}
}
С помощью метода getUpdates получаем все обновления от сервера Telegram и возвращаем последнее сообщение.
Минимальный необходимый функционал реализован, сохраняем данный класс в отдельный файл и подключаем его в EBPro через инструмент “JS ресурс”, как делали это ранее с библиотекой для отправки POST и GET запросов.

Для описания логики работы "Объект JS" в EBPro необходимо перейти в его параметры, на вкладку “Исходный код”.

Заполняем содержимое.
const telegramBot = await import('/weintek_telebot.js');
var bot = new telegramBot.TelegramBot('TOKEN')
var chats = {}
var mouseArea = new MouseArea();
this.widget.add(mouseArea);
В telegramBot импортируем файл с ранее написанным классом.
bot - экземпляр класса telegramBot с переданным в качестве аргумента токеном от BotFather.
chats - именованный массив, понадобиться для хранения ID пользователей написавших нашему боту и ID последнего сообщения
mouseArea - экземпляр, входящего в стандартную библиотеку класса MouseArea, подробнее о нем можно почитать в документации, с помощью него мы будем обрабатывать событие “нажатия по области” и осуществлять рассылку сообщений.
Добавляем кнопки:
bot.makeButton(['Температура','Давление']);
bot.makeButton(['Скорость']);
Теперь, по традиции всех примеров Телеграм ботов, реализуем функцию ECHO бота (при получении сообщения бот будет отправлять его копию в ответ). Для этого будем использовать функцию setInterval, которая вызывает callback функцию с определенным интервалом времени, в нашем случае 500 мс.
setInterval(() => {
var message = bot.getUpdates();
var chatId = message.message.from.id
var messageId = message.message.message_id
var text = message.message.text
if (!(chatId in chats)){
chats[chatId]=[messageId, text];
bot.sendMessage(text, chatId);
}
else {
if (chats[chatId][0] != messageId){
chats[chatId][0] = messageId;
bot.sendMessage(text, chatId);
}
}
} , 500);
В message мы сохраняем объект последнего сообщения, а дальше уже из него вытягиваем необходимые в дальнейшем данные
● chatId - хранит ID пользователя.
● messageId - ID сообщения, по которому мы будем определять произошло ли его обновление
● text - текст сообщения
Далее мы проверяем есть ли в нашем списке такой пользователь, если его нет, то запоминаем его в chats для использования его в будущей рассылке :) также не забываем отправить ему копию его же сообщения.
Ну что? Осталось только написать код для рассылки сообщений?
Для этого к mouseArea подключаем обработчик события 'click' и описываем для него callback функцию, которая перебирает значения с массива chats, но если он еще пустой просто ругаемся в консоль. ¯\_(ツ)_/¯
mouseArea.on('click', (mouseEvent) => {
if (!(Object.keys(chats).length == 0)){
for (var key in chats) {
var mes = bot.sendMessage('Привет! Я Weintek', key);
}
}
else {
console.log('Боту еще никто не отписал!!');
}
});
Кстати, для проверки нашего проекта не обязательно иметь панель физически, его можно запустить прямо в среде разработки EBPro с помощью инструмента “Оффлайн симуляция”.

Запускаем симуляцию:

Все работает! Бот нам отвечает и отправляет сообщение, когда происходит нажатие по объекту JS.
Для просмотра диагностической информации или то что мы отправляем в консоль с помощью функции console.log() - в режиме оффлайн симуляции запускаем инструмент “Диагностировать”:

Остается только протестировать проект на реальном устройстве. =)
Весь код и пример проекта для EBPro лежит на гитхабе.