Преобразование RS232 Ascii в Modbus TCP с помощью Pymodbus

Я пытаюсь преобразовать строковые данные RS252 Ascii с датчика в регистры ввода / хранения Modbus TCP с помощью pymodbus Callback Sever, сервер является основными данными отчета, когда запрашивается клиентским регистратором, и я не уверен, что мне нужно сделать, чтобы это работало. В настоящее время я могу читать данные и записывать их в файл csv, используя этот

#!/usr/bin/env python
# Log data from serial port


import argparse
import serial
import datetime
import time
import os


parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--device", help="device to read from", default="/dev/ttyUSB0")
parser.add_argument("-s", "--speed", help="speed in bps", default=9600, type=int)
args = parser.parse_args()

outputFilePath = os.path.join(os.path.dirname(__file__),
                 datetime.datetime.now().strftime("%Y-%m-%d") + ".csv")

with serial.Serial(args.device, args.speed) as ser, open(outputFilePath,'w') as outputFile:
    print("Logging started. Ctrl-C to stop.") 
    try:
        while True:
            time.sleep(0.2) 
            x = (ser.read(ser.inWaiting())) 
            data = x.decode('UTF-8')
            if data !="":
                outputFile.write(time.strftime("%Y/%m/%d %H:%M ") + " " + data  )
                outputFile.flush()

    except KeyboardInterrupt:
        print("Logging stopped")

Строка от датчика выходит из устройства в виде:

 0.00 0.0 0.0 346.70 25.14

Мне нужно, чтобы каждая часть была отдельным регистром Modbus, и я пытаюсь использовать pymodbus на Raspberry Pi Zero. Датчик обновляется 4 раза в секунду, и я могу разбить данные на части, я просто еще не дошел до этого, потому что я не уверен, что мне нужно делать в сценарии обратного вызова, я еще не разбираюсь в Python Я все еще учусь. У меня есть понимание Modbus TCP и я использовал его раньше в системах Arduino. Любая помощь будет оценена.


person Michael H.    schedule 11.03.2019    source источник


Ответы (1)


Вам нужен сервер обновления, который вы могли бы используйте для заполнения регистров. Вам нужно будет сосредоточиться на функции def updating_writer и выполнять последовательное чтение, обрабатывать их и записывать в регистры по вашему выбору. Пример трудно прочитать и понять с первого взгляда. Я изменил пример в соответствии с вашими потребностями. Но вот несколько ключевых концепций, которые будут полезны для понимания кода.

ModbusSlaveContext

BinaryPayloadBuilder

Также обратите внимание, что в примере используется асинхронный сервер, основанный на twisted. Если вы новичок в twisted или имеете некоторые ограничения, которые не позволят вам использовать twisted на своей цели, вы можете добиться того же и с простыми потоками. Дизайн будет примерно таким.

  1. Запустите функцию обновления в отдельном потоке
  2. Запустите свой TCP-сервер в конце (блокировка)
# Complete gist here --> https://gist.github.com/dhoomakethu/540b15781c62de6d1f7c318c3fc8ae22
def updating_writer(context, device, baudrate):
    """ A worker process that runs every so often and
    updates live values of the context. It should be noted
    that there is a race condition for the update.
    :param arguments: The input arguments to the call
    """
    log.debug("updating the context")
    log.debug("device - {}, baudrate-{}".format(device, baudrate))
    data = serial_reader(device, baudrate)  # Implement your serial reads, return list of floats.
    if data:
        # We can not directly write float values to registers, Use BinaryPayloadBuilder to convert float to IEEE-754 hex integer
        for d in data:
            builder.add_32bit_float(d)
        registers = builder.to_registers()
        context = context
        register = 3  # Modbus function code (3) read holding registers. Just to uniquely identify what we are reading from /writing in to.
        slave_id = 0x01 # Device Unit address , refer ModbusSlaveContext below
        address = 0x00 # starting offset of register to write (0 --> 40001)
        log.debug("new values: " + str(registers))
        context[slave_id].setValues(register, address, registers)

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

from pymodbus.client.sync import ModbusTcpClient as Client
from pymodbus.payload import BinaryPayloadDecoder, Endian
client = Client(<IP-ADDRESS>, port=5020)

# Each 32 bit float is stored in 2 words, so we will read 10 registers 
raw_values = client.read_holding_registers(0, 10, unit=1)
if not registers.isError():
    registers = raw_values.registers
    decoder = BinaryPayloadDecoder.fromRegisters(registers, 
    wordorder=Endian.Big, byteorder=Endian.Big)

    for _ in range(5):
        print(decoder.decode_32bit_float())

person Sanju    schedule 12.03.2019
comment
Большое вам спасибо, я протестирую это, как только смогу. Я боролся с этим больше недели, и это лучший ответ, который я когда-либо получил. - person Michael H.; 12.03.2019
comment
ОТЛАДКА: pymodbus.server.async: Клиент подключен [IPv4Address (type = 'TCP', host = '192.168.1.102', port = 5020)] ОТЛАДКА: pymodbus.server.async: Полученные данные: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x3 0x9c 0x41 0x0 0xa ОТЛАДКА: pymodbus.framer.socket_framer: Обработка: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x3 0x9c 0x41 0x0 0xa DEBUG: pymodbus.factory: Не удалось выполнить заводской запрос [3mod] DEBUG.сервера: pymodbus. запрос: объект 'dict' не имеет атрибута 'validate' ОШИБКА: pymodbus.pdu: Исключительный ответ (131, 3, SlaveFailure) ОТЛАДКА: pymodbus.server.async: отправить: 000100000003008304 - person Michael H.; 12.03.2019
comment
Отслеживание (последний вызов последним): файл /usr/local/lib/python2.7/dist-packages/twisted/internet/base.py, строка 1267, при запуске self.mainLoop () Файл / usr / local / lib / python2.7 / dist-packages / twisted / internet / base.py, строка 1276, в файле mainLoop self.runUntilCurrent () /usr/local/lib/python2.7/dist-packages/twisted/internet/base.py, строка 902, в runUntilCurrent call.func (* call.args, ** call.kw) (Part1 / 2) - person Michael H.; 12.03.2019
comment
Файл /usr/local/lib/python2.7/dist-packages/twisted/internet/task.py, строка 239, в call d = defer.maybeDeferred (self.f, * self.a , ** self.kw) --- ‹здесь обнаружено исключение› --- Файл /usr/local/lib/python2.7/dist-packages/twisted/internet/defer.py, строка 151, в файле MaybeDeferred result = f (* args, ** kw) Файл update_server.py, строка 82, в контексте update_writer [slave_id] .setValues ​​(регистр, адрес, регистры) исключения. AttributeError: объект 'dict' не имеет атрибута 'setValues' (часть 2/2 ) - person Michael H.; 12.03.2019
comment
С какой версией pymodbus вы работаете, это должно работать на pymodbus == 2.2.0rc4 - person Sanju; 12.03.2019
comment
Python вер. 2.7.13 - person Michael H.; 13.03.2019
comment
Pymodbus версии 2.1.0 - person Michael H.; 13.03.2019
comment
Хорошо, я обновил скрипт, чтобы он был совместим с 2.1.0, попробуйте. - person Sanju; 13.03.2019
comment
Хорошо, теперь я могу подключиться и получить данные, но он, похоже, не очищает и не обновляет одни и те же регистры, но добавляет новые в конце, в основном добавляя их? - person Michael H.; 13.03.2019
comment
Поскольку это основано на builder.to_registers на 32-битном значении d, чтобы оно было правильно только в первых 10 регистрах, мне нужно было убедиться, что d равно ‹= 10? - person Michael H.; 13.03.2019
comment
Нет, значения первых 10 регистров будут перезаписываться при каждом последовательном чтении. Если вы можете поделиться журналами сервера и прочитать, будет полезно продолжить отладку - person Sanju; 13.03.2019
comment
Кроме того, построитель двоичной полезной нагрузки используется только для преобразования необработанных значений с плавающей запятой в шестнадцатеричный, фактическая запись в блок данных происходит с помощью метода setValues . - person Sanju; 13.03.2019
comment
Позвольте нам продолжить это обсуждение в чате. - person Michael H.; 13.03.2019
comment
Я не могу добавить в комментарии фрагмент разумного размера. - person Michael H.; 13.03.2019
comment
добавил builder.reset () после строки 82, и проблема исчезла. Возможно, я ошибаюсь, но похоже, что builder.add_32bit_float (d) в строке 75 добавил его в буфер, чтобы новые значения записывались в более высокие регистры вместо перезаписи исходного 10. - person Michael H.; 14.03.2019