В данном руководстве рассмотрено создание чат-бот помощника в Viber на языке программирования Python. Чат-бот имеет доступ к базе данных MySQL, которая в свою очередь связана с АСУ ТП (автоматизированной системой управления технологическим процессом), разработанной на базе логического контроллера Siemens серии S7-1500.

Чат-бот автоматизирует передачу информации о течение технологического процесса конечному пользователю, позволяет получать доступ к собранным данным с любого устройства (на котором, конечно, установлен Viber) и в любом месте.

Руководство рассчитано на инженеров-программистов, работающих в сфере автоматизации (АСУ ТП или АСУП).

Стек технологий
Стек технологий

Для реализации данной идеи необходимо знать:

  1. Основы программирования контроллеров Siemens серии S7-1200/1500

  2. Базовые принципы работы баз данных и языка запросов SQL

  3. Язык программирования Python

Итак, весь материал будет разбит на две части:

  1. Установка и конфигурация всех необходимых приложений

  2. Написание программ

Установка и конфигурация приложений

  1. Установить TIA Portal v16 из официального источника можно по ссылке.

    В разделе TRIAL Download STEP 7 Basic/Professional, STEP 7 Safety Basic/Advanced and WinCC Basic/Comfort/Advanced and WinCC Unified скачиваем DVD 1 Setup и при установке выбираем Step 7 Professional и WinCC Advanced. А также в разделе TRIAL Download STEP 7 PLCSIM скачиваем и устанавливаем DVD 1 Setup - это симулятор, который позволяет тестировать ПО без физического контроллера.

    После установки в новом проекте TIA Portal необходимо сконфигурировать ПЛК серии S7-1200/1500 (можно выбрать любой, главное не резервируемый) и панель оператора. Конфигурация, используемая мной на рисунке ниже.

    Конфигурация "железа"
    Конфигурация "железа"

    Далее создаётся глобальный дата блок, например, "test" с переменной "test". Переменную добавляем в HMI Tags, так как связь с базой данных MySQL будет реализована на стороне HMI клиента, в данном примере это панель оператора TP1200 Comfort. Программное обеспечение HMI разрабатывается в WinCC Advanced, интегрированной в TIA Portal (да-да, немного запутано). Средства WinCC позволяют писать код на VBScrpt, который и будет реализовывать обмен данными между ПЛК и БД MySQL.

    Создание переменных
    Создание переменных
  2. Установка сервера MySQL подробно описана на других ресурсах (например, здесь). Однако стоит отметить, что необходимо помимо MySQL Server 8.0.XX также установить драйвер для Python и ODBC.

    Установка MySQL
    Установка MySQL

    Для подключения БД к TIA Portal необходимо дополнительно скачать и установить 32-ух разрядный ODBC драйвер по ссылке.

    После установки открываем командную строку (cmd) и запускаем MySQL:

    mysql -u root -p

    После ввода пароля и успешного входа необходимо создать и настроить базу данных с таблицей:

    CREATE DATABASE Siemens;

    USE Siemens;

    CREATE TABLE Data
    (
    Id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Value INT
    );

    Для настройки драйвера ODBC запускаем приложение "Источник данных ODBC" -32-ух разрядную версию и по кнопке "Добавить" необходимо выбрать MySQL ODBS 8.0 Unicode Driver.

    Конфигурация ODBC коннектора
    Конфигурация ODBC коннектора

    В настройках вводим данные пользователя, сконфигурированные при установке MySQL и выбираем созданную базу данных siemens. В качестве TCP/IP Server необходимо выбрать localhost. Кнопкой Test можно проверить корректность соединения. DSN ( Data Sourse Name) вводим также, как на скриншоте.

    Настройка DSN пользователя
    Настройка DSN пользователя
  3. Установка Python версии 3 осуществляется с официального сайта.

  4. Пожалуй, финальный подготовительный шаг - создание чат-бота и получение токена, который мы будем использовать в нашем python-приложении.

    Создание чат-бота производится через панель администрирования по ссылке. регистрация и получение токена бесплатны.

ПО обмена данными между БД и Siemens

'Объявление переменных
Dim TableName, Uname, DBPassword, DataBaseName, SQLServer
Dim SQLConnection
Dim SQLConnectionString
Dim SQLFields,SQLValues
Dim SQLQueryString
Dim SQLCommand

'Конфигурация для подключения к базе данных, все данные - строго в соотвествии с ODBC connector
DataBaseName = "siemens"
Uname = "ilya"
DBPassword = "qwerty123"
SQLServer = "127.0.0.1"
TableName = "data"

'Конфигурационная строка
SQLConnectionString = "Provider=MSDASQL;Initial Catalog=siemens; DSN=siemens"

'Создание объектов (команда set создаёт объект)
Set SQLConnection = CreateObject("ADODB.Connection")
SQLConnection.ConnectionString = SQLConnectionString
'Открытие соединения
SQLConnection.Open
Set SQLCommand = CreateObject("ADODB.Command")
SQLCommand.ActiveConnection = SQLConnection

'Формирование запроса
SQLFields = " (Value)"
SQLValues = " Values('" & SmartTags("test_test") & "') "
SQLQueryString = "INSERT INTO " & TableName & SQLFields & SQLValues

'Выполнение SQL запроса
SQLCommand.CommandText = SQLQueryString
SQLCommand.Execute

'Закрытие соединения с базой данных
Set SQLCommand = Nothing
SQLConnection.Close
Set SQLConnection = Nothing

Данный код открывает соединение с базой данных, созданной выше. Записывает переменную "test_test" в таблицу "data". Также стоит обратить внимание, что обращение к тэгам HMI происходит через нотацию SmartTags():

VBS скрипт создаётся в дереве HMI в разделе Scripts:

Чтобы скрипт периодически исполнялся, необходимо создать для него триггер. В качестве триггера выбираем изменение переменной test_test. Во вкладке HMI находим тэг test_test и во вкладке Properties -> Events по параметру Value change добавляем вызов VBS скрипта:

Для тестирования работоспособности кода необходимо запустить PLC-SIM, выгрузить в него проект с конфигурированным контроллером, перейти в онлайн режим, активировать симуляцию HMI. Затем изменить переменную test_test. Чтобы убедиться, что запись в базу данных работает можно отправить к ней SQL запрос вручную с помощью командной строки:

use siemens
SELECT * FROM data;

ПО Viber чат-бота

Для обеспечения работоспособности кода необходимо установить следующие модули:

viberbot==1.0.11
flask
mysql-connector-python

Ядро чат-бота - приложение Flask, работающее с API Viber. Для локального тестирования приложения необходимо использовать ngrok. После его запуска вводим команду:

ngrok http 8443

В окне ngrok копируем ссылку https и сохраняем её, она нам скоро пригодится. Ngrok создаёт туннель, связывающий наш порт, в данном случае 8443, с https ссылкой, доступной по всему интернету. Этот https будет служить вебхуком для нашего приложения. Также пригодится токен viber чат-бота, который был получен в результате регистрации на Viber Admin Panel.

А вот и сам код чат-бота на Python 3:

#Импорт всех необходимых библиотек
from flask import Flask, request, Response, session
from viberbot import Api
from viberbot.api.bot_configuration import BotConfiguration
from viberbot.api.messages import (
    TextMessage,
    KeyboardMessage
)
from viberbot.api.viber_requests import ViberMessageRequest, ViberConversationStartedRequest
import sched
import threading
import time
import mysql.connector

token = 'ВАШ ТОКЕН'

class UseDataBase:
'''Менеджер контекста для подключения к базе данных'''
    def __init__(self, config: dict) -> None:
        self.configuration = config

    def __enter__(self):
        self.conn = mysql.connector.connect(**self.configuration)
        self.cursor = self.conn.cursor()
        return self.cursor

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.commit()
        self.cursor.close()
        self.conn.close()


class ViberSay():
'''Чат-бот'''	
    def __init__(self, token):
    	#Инициализация чат-бота данными, полученными при регистрации на Admin Panel
        self.viber = Api(BotConfiguration(
            name='TestChatBotByIlya',
            avatar='http://viber.com/avatar.jpg',
            auth_token=token))
        self.app = Flask(__name__)	#Создание экземпляра приложения Flask
        self.scheduler = sched.scheduler(time.time, time.sleep)
        self.scheduler.enter(5, 1, self.set_webhook, ())
        self.t = threading.Thread(target=self.scheduler.run)
        self.t.start() #запуск потока
        self.app.secret_key = 'YouWillNeverGuess'
        #конфигурация базы данных
        self.app.config['dbconfig'] = {'host': '127.0.0.1',
                          				'user': 'ilya',
                          				'password': 'qwerty123',
                          				'database': 'siemens', }
        self.index = 0
        self.app.add_url_rule('/', 'home', self.poll, methods=['POST', 'GET'])
        self.app.run(host='0.0.0.0', port=8443, debug=True) #пуск приложения


    def set_webhook(self):
    	#установка вебхука
        self.viber.set_webhook('https://06841b02dbf3.ngrok.io')

    def poll(self):
       # приём входящих сообщений, обработка и декодирование
        viber_request = self.viber.parse_request(request.get_data().decode('utf8'))  
        if isinstance(viber_request, ViberConversationStartedRequest):
            #Определяем начало диалога с ботом
            session['uid'] = viber_request.user.id

            self.viber.send_messages(session['uid'], [
                TextMessage(text='Добро пожаловать!')
            ])
            #отправляем кнопку с предложением получить данные
            self.send_buttons(uid=session['uid'], text=['Получить данные'], btns=1)
            
        elif isinstance(viber_request, ViberMessageRequest):
        	#если не начало диалога, то ждём запроса данных
            session['uid'] = viber_request.sender.id
            if viber_request.message.text == 'Получить данные':
                with UseDataBase(self.app.config['dbconfig']) as cursor:
                	#ыполнение запроса к базу данных
                    _SQL = """select Id, Value from data"""
                    cursor.execute(_SQL)
                    contents = cursor.fetchall()
                    #Выбираем последнее значение из массива чисел и отправляем пользователю
                    if self.index !=  contents[-1][0]:
                        self.index = contents[-1][0]
                        text_mes = 'Значение = {}'.format(contents[-1][1])
                    
                        self.viber.send_messages(to=session['uid'], messages=[TextMessage(text=text_mes)])
            #снова отправляем кнопку с предложением получить данные
            self.send_buttons(uid=session['uid'], text=['Получить данные'], btns=1)            

        return Response(status=200)

    def send_buttons(self, uid, text, btns):
    	#метод формирует кнопку
        KEYBOARD = {"Type": "keyboard", "Buttons": []}

        for i in range(btns):
            KEYBOARD["Buttons"].append(
                {
                    "Columns": 6,
                    "Rows": 1,
                    "BgColor": '#4169E1',
                    "BgMedia": None,
                    "BgMediaType": None,
                    "BgLoop": True,
                    "ActionType": "reply",
                    "ActionBody": text[i],
                    "ReplyType": "message",
                    "Text": text[i]
                }
            )

        message = KeyboardMessage(tracking_data='tracking_data', keyboard=KEYBOARD)
        self.viber.send_messages(session['uid'], [message])


if __name__ == "__main__":
    ViberSay(token)

Для лучшего понимая кода можно прочитать документацию по Flask и Viber API. Для того, чтобы веб-приложение заработало необходимо:

  1. Cкопировать и вставить в код ваш токен от чат-бота

  2. Правильно проинициализировать конфигурацию чат-бота

  3. Указать верные настройки для подключения к базе данных

  4. Установить вебхук (метод set_webhook) - та самая https ссылка из ngrok

А результат всей проделанной работы таков:

Заключение

Таким образом, данный проект реализует удалённый доступ к данным, которые контролируются АСУ ТП на базе ПЛК Siemens, через привычные интерфейс повседневного мессенджера. Доступность данных позволяет качественнее управлять технологическим процессом и анализировать его на более высоком уровне.

Буду рад увидеть комментарии читателей habr, и подчерпнуть для себя пользу в свежих взглядах со стороны.

Благодарю за то, что прочитали данную статью!

Комментарии (25)


  1. endlessnights
    25.07.2021 23:47

    Честно говоря, не думал, что Viber'ом еще пользуются люди. По скорее бы от Whatsapp отвыкли, но, похоже не видать даты прекращения массового использования сих приложений.


    1. Paranoich
      26.07.2021 08:20
      +2

      А зачем прекращать ими пользоваться, если и вайбер и ватсап исполняют свои функции и полностью покрывают потребности большинства? Надевать шапочку из фольги и уговаривать знакомых пересесть на суперзащищённо-анонимный мессенджер, где ноль контактов — так себе идея.
      Данных о себе и следов пребывания в интернетах люди и так предостаточно оставляют.
      Вайбером пользуется миллиард человек (данные примерные, сужу по 2017 году), а значит он устраивает их.


  1. 1Fedor
    25.07.2021 23:59

    Таким образом, данный проект реализует удалённый доступ к данным, которые контролируются АСУ ТП на базе ПЛК Siemens, через привычные интерфейс повседневного мессенджера. Доступность данных позволяет качественнее управлять технологическим процессом и анализировать его на более высоком уровне.

    Если это не приглашение к преступным деяниям, то не очень очевидно зачем это нужно.
    Человек не в состоянии ни заменить, ни отследить, ни понять слёту алгоритм работы АСУТП. А уж на экранчике смартфона в мессенджере качественнее управлять технологическим процессом, чем АСУТП? Вы серьёзно так думаете? Или я слишком наивен?
    Значит открывается перспектива банального воровства данных.


    1. EnerelStain
      26.07.2021 07:29
      +1

      Кхм, при разработке объем передаваемых данных жёстко ограничен. Отдав во внешний мир уставки тех процесса (например, какую температуру поддерживать в приточке) и задав границы в программе, чтобы при выходе за допустимый диапазон она загонялась обратно в допустимые рамки, вы при всем желании не сможете что-то сломать, украсть или испортить. А уж "украсть" или злонамерно сломать оборудование в данной структуре обмена данными - это вовсе за гранью фантастики.


      1. ilianov Автор
        26.07.2021 07:29
        +1

        поддерживаю


  1. jaiprakash
    26.07.2021 00:11

    А чем это лучше простой вебморды?


    1. ilianov Автор
      26.07.2021 07:29

      может быть и ничем


  1. fcoder
    26.07.2021 01:57

    А зачем в этой цепочке приседания с mysql и вообще вот этот весь велосипед на VBA? Любая вменяемая скада должна уметь из коробки в более стандартные (для управления технологическими процессами) штуки вроде протокола OPC. Тогда останется лишь короткий скрипт на python который напрямую читает из скады значение и транслирует его в вайбер.


    1. ilianov Автор
      26.07.2021 07:28

      Чтобы приложение можно было написать и без скады, а только на средствах среднего уровня. Потому что, как правило, верхний и средний уровень АСУ ТП разрабатывают разные компании


    1. AndreyUA
      26.07.2021 09:48

      ИМХО, данные лучше хранить в базе. Потом из нее можно вытащить графики и историю. Другое дело, что в базу писать можно прямо из контроллера https://support.industry.siemens.com/cs/document/109779336/connecting-a-s7-1200-s7-1500-to-a-sql-database-?dti=0&dl=en&lc=ru-RU


  1. NoOne
    26.07.2021 02:54
    +1

    А потом как на Colonial Pipeline остановится производственная деятельность..

    Может не стоит так АСУТП выставлять в инет?


    1. ilianov Автор
      26.07.2021 07:30

      Это вполне и без чат-бота может произойти)


    1. AndreyUA
      26.07.2021 09:54

      Скрипт в панели только читает данные и передает их в базу, чат-бот не имеет доступа к оборудованию АСУТП, он физически не сможет ничего сделать.


      1. ilianov Автор
        26.07.2021 10:05

        В точку!


  1. tsdrive2211
    26.07.2021 10:20

    TableName,
    Uname,
    DBPassword,
    DataBaseName,
    SQLServer
    - не подставляются в сonectionString.
    Это -зарезервированные константы для ODBC?


    1. ilianov Автор
      26.07.2021 10:25

      В моём случае эти переменные излишни. Мне их следовало удалить.
      В текущей версии ПО подключение к базе данных происходит через ODBC коннектор.
      В строчке: SQLConnectionString = "Provider=MSDASQL;Initial Catalog=siemens; DSN=siemens"

      Где в DSN указывается имя, настроенное в пункте 2

      А здесь уже как раз прописаны IP , пользователь, пароль и сама база данных, в коде остаётся только сделать запрос к нужной таблице, имя которой хранится в переменной TableName


      1. tsdrive2211
        30.07.2021 10:09

        Спасибо. Хорошая статья, к сожалению VB-скрипт не отрабатывет в версии PRO для PC-станций.


  1. Procyon_lotor
    26.07.2021 17:35

    Тут вроде единственная неубираемая вещь в цепочке - сервер на python, который взаимодействует с Вайбером. Можно данные запрашивать с контроллера через TCP или через modbus-TCP и избавиться от панели, базы и скриптов если они реально для чего-то не нужны. Разве нет?


    1. ilianov Автор
      26.07.2021 17:53

      Можно, видел, что python поддерживает modbus tcp. Но при таком подходе потеряется доступ к историческим данным


  1. Weron2
    26.07.2021 23:46

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

    В принципе разницы нет как делать главное чтобы работало и не ломалось)


    1. ilianov Автор
      28.07.2021 13:23

      Спасибо, действительно, главное, чтобы работало


  1. Lolita1001
    28.07.2021 09:57

    В реальности это малоприменимо, Вы используете HMI симуляцию в роле «скрипт-шлюза» для доступа к ПЛК, что ну очень странно выглядит.
    Посмотрите в направление Snap7, она вполне закроет потребность в получении данных с неоптимизированных областей памяти ПЛК S7-1200/1500. Если нужен доступ более «легальный», в последних прошивках Siemens Simatic S7-1200/1500 реализован json RPC 2.0, там и через аутентификацию, и определением конкретного набора данных с ограничением к другим «чувствительным» данным.


    1. ilianov Автор
      28.07.2021 13:28

      HMI, скорее, обычные клиент базы данных, а не "скрипт-шлюз". А доступ к переменным ПЛК у панели оператора будет в любом случае, независимо от подключённой базы данных. А если это так, то зачем тратить ресурсы ПЛК на обмен данными с внешним миром, пусть он решает свою непосредственную задачу - управление процессом.
      В данном подходе также конкретно определен набор данных, отдаваемых во внешний мир. и очень легко реализуем доступ только для конкретных пользователей.
      Спасибо за комментарий, обязательно, почитаю про технологии, описанные вами


      1. Lolita1001
        28.07.2021 14:09
        +1

        Я переформулирую, то что хотел донести.
        Сейчас структура следующая: Viber — Python — MySQL — HMI — PLC.
        Я же предлагаю Вам: Viber — Python — PLC.

        Аргументирую, почему я считаю это решение странным:
        1) HMI не должна быть цепочкой какой либо автоматизации или коммуникации. HMI является самым уязвимым объектом на любом предприятии. В нее тычат пальцам, отвертками и бог знает чем. HMI может подвисать, к ним просто ниже требования к бесперебойной работе чем к ПЛК;
        2) В Вашем случае HMI каждый раз при изменении тега подключается и отключается от БД. Повесьте скрипт на 50 — 100 тегов, будет печаль. И производительность HMI, даже RunTime, оставляет желать лучшего, 100+ скриптов будут нагружать достаточно сильно;
        3) В данном случае Вы ограничены частотой обновления тега в HMI, а TP1200 это 0.1с. И то, если такой тайминг включить на все теги, фактическая частота обновления такой не будет;
        4) АСУТП всегда предполагает самодиагностику. Если по какой либо причине HMI не будет передавать данные в БД, Вы об этом не узнаете (исходя из Вашего текущего решения). Вы будете также получать «Значение = 153», а то, что значение просто старое и не актуальное, нет;

        На счет ресурсов, S7-1200/1500 и HMI просто несравнимы по коммуникационным ресурсам, в первых их запас в разы больше. В ПЛК Вы можете всегда иметь ввиду для масштабирования решения установки коммуникационного процессора (например, 6GK7543-1AX00-0XE0 SIEMENS CP 1543-1), он может выполнять функции интерфейсом между технологической и корпоративной сетью (что конечно не все предприятия допускают, но именно этот CP для этого и сделан Сименсом и снабжен соответствующими решения по ИБ).
        Честно говоря я могу еще продолжить, но мне кажется, этого уже достаточно.

        И Вам спасибо за оригинальный подход к решению задачи.


        1. Korvus
          28.07.2021 15:17

          Кстати да, библиотека Snap7 на Python вполне себе работает. Я на ее основе создавал простенькое отображение температурных датчиков на производстве. И бота на для телеграмм писал тоже с помощью этой библиотеки. Только проблема была в том что бот периодически отваливался от серверов телеграмма. И даже консольный вариант мониторинга и отображения писал.
          Единственное что меня не сильно устраивало, так это то что чтение ДБ было долгим. Примерно 0.1-0.2 секунды. Но может это и мой косяк, я так и не понял.