python

Как снять данные с весового модуля со своей спецификацией протокола передачи данных и отправить на M

  • понедельник, 23 января 2023 г. в 00:48:26
https://habr.com/ru/post/712050/
  • Python
  • SCADA
  • DIY или Сделай сам


Данная статья написана для начинающих, тех кто на начальном уровне знает Python и немного разбирается в АСУ ТП. Задача достаточно распространенная, надо взять данные со старого, со своей спецификой оборудования и перевести ее в такой вид, что бы ее можно было легко достать (MQTT сервер) и обрабатывать (SCADA или любое ПО, которое умеет работать с MQTT).

Зачем это вообще нужно? да просто ходить до весоизмерительного модуля далеко, а контролировать его надо, да и необходима база в которой будут записаны его показания, такие как общее количество кг взвешенного им и общее количество нафасованных мешков и про хотелки производственников не забываем)

Мы имеем:

  • "Прибор весоизмерительный ЦЕНТА АД-К", связь с внешним миром через RS-485 с специфическим протоколом верхнего уровня немного похожий на MODBUS (Используется символьный протокол, с ASCII-HEX представлением данных. Числовые данные передаются в виде последовательности ASCII символов шестнадцатеричной записи числа, с выравниванием до чётного числа знакомест, так пишет разработчик данного прибора)

  • Одноплатный компьютер Raspberry Pi, в особом представлении не нуждается

  • Сервер, любой ПК с характеристиками, как минимум офисного ПК начального уровня, главное, побольше ОЗУ

Сама схема простая:
По RS-485 весоизмерительного прибора берем данные,
Переводим в читаемый вид,
Отправляем на MQTT сервер.
Эх, если бы все было так легко и непринужденно... но так не бывает, нас ожидают куча подводных камней, ограничения и т.д., все как обычно)

Что бы взять данные с весоизмерительного модуля надо понять, как он вообще общается с внешним миром, хорошо что по запросу в http://www.centa.ru/about.htm нам предоставили ПО для снятия данных и описание работы протокола с перечнем запросов. По хорошему, можно было обойтись и этой программой, но для этого пришлось бы тянуть кабель для подключения, сама ПО неудобная, связь с БД ограничена. Другой выход, это преобразовать сигнал RS-485 и отправлять его по имеющимся Ethernet, но опять же, танцы с бубном с настройками конвертации (помним, что верхний уровень, это своя версия MODBUS протокола), сами блоки конвертации стоят достаточно дорого, а смогут ли они нормально передать сигнал, да и кто на такой достаточно мутный проект (инициатива самого автора, который ранее подобного не делал) даст денег, так что для начала, надо собрать работающий прототип на том что есть в личных запасах

(1) Снифаем COM порт между весовым модулем и ПК

Берем ноут, ставим "Serial Port Monitor" (почему его? Все очень просто, он не занимает COM порт, можно спокойно снифать трафик и не мудрить с кабелями или программно эмулировать кучу COM портов), подключаем преобразователь интерфейса USB to RS-485 (народ, не жалейте денег на нормальный преобразователь, он должен быть с гальванической развязкой а не поделкой за 200 рублей с алика, пожалейте USB порт Вашего ПК или ноута), подключаемся к весоизмерительному прибору, запускаем "Serial Port Monitor" (вводим номер COM порта, далее все настройки скорости, четности и т.д. можно посмотреть в логах данной ПО), запускаем ПО от "centa" для снятия показаний и смотрим что нам показывает "Serial Port Monitor". А показывает он нам корректные запросы с адресом, настройками COM порта и кодировкой используемой в протоколе, далее нам эта информация потребуется для конвертации ответов на запросы к весоизмерительному модулю

(2) Пишем скрипт для переброски данных с весового модуля на "MQTT" сервер

В шаге (1) мы поняли как именно общается между собой ПК и весоизмерительный модуль. Теперь нам надо создать скрипт, так как автор знает немного python, то на нем и будем писать скрипт, так же не забываем, что надо написать и MQTT клиент (шаг (3)) который на сервер будет отправлять нужные нам данные. Еще одна загвостка, все это должно работать на "Raspberry Pi" под ее величием "Raspberry Pi OS" основанным на "Debian", так что все что мы пишем в консоле, обязательно приправляем незаменимой командой "sudo", звучит пафосно, но без "sudo", как и без синей изоленты в электрике, электронике ничего не работает)

Прежде чем дать текст скрипта, нам надо настроить среду, начнем с удаленного рабочего стола, "Xrdp" не хотит нормально работать, требуются танцы с бубном, очень жаль что с новыми ОС она криво работает, если кто то знает как ее заставить работать, напишите пожалуйста. Мы будем использовать "VNC", он уже установлен на "Raspberry Pi", его надо включить ( Menu > Preference > Raspberry configuration > Interfaces > Enable VNC ), а на другой стороне установить "VNC-viewer", она бесплатна. Далее, нам надо установить библиотеку "paho-mqtt", идем на "https://pypi.org/project/paho-mqtt/" смотрим, как это чудо устанавливается и вспоминаем про "sudo", без этого волшебного слова в консоле у меня криво встал "paho-mqtt", он виден в pip но корректно не запускается, по этому устанавливаем так:

sudo pip install paho-mqtt

Далее маленькое отступление, я не программист, только учусь, код не оптимизирован, так как там будет куча правок, добавление функционала, это рабочий прототип, но конструктивная критика приветствуется)

Далее текст скрипта:
import serial
import time
#подгружаем библиотеку для работы с QMTT
import paho.mqtt.publish as publish


#настройки на COM порт
with serial.Serial() as ser:
    ser.baudrate = 19200
    #ser.port = 'COM5'
    ser.port = '/dev/ttyUSB0'
    bytesize=8
    parity='N'
    stopbits=1
    timeout=1
    write_timeout=1


    #готовим запрос (надо hex преобразовать в byte)
values = bytearray([
    int('0x23', 16),    #[0:7] -запрос веса
    int('0x30', 16),
    int('0x31', 16),
    int('0x31', 16),
    int('0x30', 16),
    int('0x34', 16),
    int('0x30', 16),
    0,
    int('0x23', 16),    #[8:15] -запрос колличество мешков
    int('0x30', 16),
    int('0x31', 16),
    int('0x31', 16),
    int('0x30', 16),
    int('0x34', 16),
    int('0x34', 16),
    ])

podpiski_values =["fasovka/error","fasovka/obhii_ves","fasovka/koll_mehkov"]

#--------------------------------------------
#включение COM порт
#через обработчика ошибок
#подпрограмма для отправки сообщений QMTT
def QMTT_read(a,b):
    #дописать сообщения
    try:
        publish.single( a,  payload = b, hostname="192.168.20.213",auth = {'username':"pi", 'password':"1"})#
        #publish.single( a,  payload = b, hostname="192.168.137.1")#
        
    except:
        print('нет связи с сервером QMTT')
#--------------------------------------------
def COM_write(c,d):
    print(values[c:d])
    ser.write(values[c:d] + b'\r\n')
#--------------------------------------------
def COM_read():
    #запрос или эмулятор вывода
    #Далее интересная конструкция:
    #если на прямую читать ser.readline(), то если порт пустой
    #система будет ждать сообщения и дальше скрипт выполняться не будет
    #По этому мы читаем количество байт в буфере порта, если 
    #что то есть, значит весовой модуль что то отправил 
    x = ser.inWaiting() #сколько байт в входном буфере
    print('в буфере - ',x)
    if x > 0: #если в буфере более ноля байт, то можно читать readline, иначе нет
        line = ser.readline()# read a '\n' terminated line

        print(type(line))
        print(line)
        #---line = b'@01(PAR40(\x91\xe7\xa5\xe2\xe7\x8e\xa1\xe9\x82\xa5\xe1\xa01=0x017E34E0))\r\n'
        #настраиваем поиск
        nathalo=line.find(b'=0x')
        konets=line.find(b'))')
        #запихиваем срез
        srez_znathenia = line[nathalo+3:konets]
        print(srez_znathenia)

        #выводим на экран 
        print(int("".join(srez_znathenia.decode('cp866')),16))
        #отправляем QMTT значение
        return int("".join(srez_znathenia.decode('cp866')),16)
        
    else:
        print('во входном буфере 0')
        #отправляем QMTT что 0
        return int(0)

    

#-------------------------------------------- 
#открываем порт
try:
    ser.open()
except serial.SerialException:
    print('ошибка при запуске COM')
    QMTT_read(podpiski_values[0],"no_COM_port")
else: 
    #нам надо отправить 2 запроса
    for number in [0,1]:
    #запрос с переносом строки
        print(number)
        if number == 0:
            COM_write(0,7)
        elif number == 1:
            COM_write(8,15)
        else:
            print('ошибка цикла')
        #ответ же не сразу предет, поспим немного
        time.sleep(1)
        
        #проверяем на наличие соединения
        try:
            COM_otvet = COM_read()
            print(COM_otvet)
        except serial.SerialException:
            print('ошибка при запуске COM')
            QMTT_read(podpiski_values[0],"no_COM_port")
        else:
        #проверяем на наличие байт в порте
            if COM_otvet > 0: #если в буфере более ноля байт, то можно читать readline, иначе нет
                QMTT_read(podpiski_values[1+number],COM_otvet)
                QMTT_read(podpiski_values[0],"---")
            else:
                QMTT_read(podpiski_values[0],"no_values_RS485")
        #порт и так сильно потрудилась, пускай поспит
        time.sleep(1)
    ser.close()  



Корректно укажите ip адрес, настройки и номер COM

Так же в "paho" можно сообщения отправлять не подключаясь и отключаясь для каждого сообщения, а нормально подключится, но для нас это избыточно так как сообщений мало и они будут настроены на работу раз в минуту

Данный скрипт нужно запускать через определенное время, я для себя решил, что запускаться должен 1 раз в 1 минуту, наша цепочка будет такая: мы создадим исполняемый файл "/home/rpi/comand_COM_MQTT_bat.run"
делаем его исполняемым "chmod ugo+x /home/rpi/comand_COM_MQTT_bat.run"
запишем ф файл comand_COM_MQTT_bat.run следующее:
#!/bin/bash
sudo python3 /home/rpi/COM_MQTT.py

Затем мы должны настроить планировщик задач:
crontab -e
в нем добавляем строку:
/1 * * * * /home/rpi/comand_COM_MQTT_bat.run

Как видно из настройки, мы запускаем наш comand_COM_MQTT_bat.run раз в минуту

(3) Теперь нам надо установить и настроить "MQTT" сервер (у меня рабочая среда "Win10", настройки ниже проверенны на ней), устанавливаем "https://mosquitto.org/download/" для Вашей ОС, далее:

Простыня настроек

Для облегчения создаем testMQTT.cmd файл (командный файл) со следующим текстом:

start "QMTT_Server" /d "C:\Program Files\mosquitto\" mosquitto.exe -c "C:\Program Files\mosquitto\mosquitto.conf" -v
TIMEOUT /T 2
start "QMTT_podpiska" /d "C:\Program Files\mosquitto\" mosquitto_sub.exe -h 127.0.0.1 -t fasovka/# -u pi -P 1 --debug

При повторном запуске 1 строчка будет ругаться так как сервер уже запущен, не обращайте внимание, нам нужна 3 строчка, она покажет сообщения для отладки

Данным командным файлом мы в разных процессах запускаем сам сервер (строка 1), после перерыва в 2 секунды (строка 2) запускаем клиента который смотрит все топики fasovka/# поступающие на сервер "MQTT" (строка 3).

Не спешим запускать testMQTT.cmd, сначала настроим "MQTT" сервер

Настройка mosquitto.conf:

pid_file C:\mosquitto\mosquitto.pid
persistence true
persistence_location C:\mosquitto
log_dest file C:\mosquitto\mosquitto.log
#include_dir /etc/mosquitto/conf.d
password_file C:\mosquitto\passwd
listener 1883
persistence_file mosquitto.db
log_dest syslog
log_dest stdout
log_dest topic
log_type error
log_type warning
log_type notice
log_type information
connection_messages true
log_timestamp true
allow_anonymous false

К сожалению я не успел поэкспериментировать с настройками, единственное я точно могу сказать что с данными настройками сервер будет принимать не только локальные сообщения, но и удаленные

Создаем файл пароля:
"C:\Program Files\mosquitto\mosquitto_passwd.exe" -c -b C:\mosquitto\passwd pi 1
логин - pi
пароль - 1

Запускаем ранее созданный testMQTT.cmd файл

Проверяем что все работает, для этого в командной строке вводим:
"C:\Program Files\mosquitto\mosquitto_pub -h localhost -t "fasovka/error" -m "Privet" -u "pi" -P "1"
тем самым мы в топик "fasovka/error" отправили сообщение "Privet"

Более детально про настройки сервера, пароли, настройка mosquitto.conf можно прочитать здесь:
https://mosquitto.org/man/mosquitto-8.html
https://mosquitto.org/man/mosquitto_passwd-1.html
https://mosquitto.org/man/mosquitto-conf-5.html
Понимаю, что документация на английском языке, я сам английский не знаю, но через яндекс или гугл переводчик общий смысл понятен

Заключение

Для меня, как новичка, это реальный опыт воплощения проекта в жизнь, что бы это реализовать я пользовался собственным железом так как не был уверен в успехе, но теперь данный проект проверен и будет расширяться. Дальнейшие планы: подключить "MasterSCADA" к "MQTT" серверу и на ней обрабатывать и отображать информацию в том числе и от других источников (аварии на котельной, вентиляция и т.д.), так же хочу приделать "telegram" бота что бы он мог в чат отправлять оповещения, само собой что бы не плодить обработчиков, всем будет управлять "MasterSCADA".