Широковещательные сообщения игнорируются ведомыми устройствами

Описание

В настоящее время у меня есть шина RS-485 с 3 Raspberry Pi, на каждой из которых работает pymodbus (синхронизация - 1 мастер / клиент и 2 подчиненных устройства / сервера). Отправка команд на индивидуальный адрес каждого сервера работает должным образом. Однако широковещательные сообщения на адрес 0 дают неверное сообщение отладки идентификатора устройства на серверах, поэтому каждый сервер отклоняет команду от мастера. Прочитав предыдущие выпуски и документацию, я попытался добавить константу enable_broadcast в каждую конфигурацию, но безуспешно. Что-то не так с моей конфигурацией?

Версии

  • Python: 2.7.16
  • Pymodbus: 2.3.0

Код и журналы

Код

Раб 1

#!/usr/bin/env python

import serial
from pymodbus.server.sync import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusBinaryFramer

# --------------------------------------------------------------------------- #
# Logging
# --------------------------------------------------------------------------- #
import logging
FORMAT = (' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)


def run_server():

    block = ModbusSequentialDataBlock(0, [17]*100)
    slave = {
        0x01: ModbusSlaveContext(di=block, co=block, hr=block, ir=block),
    }
    context = ModbusServerContext(slaves=slave, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'URL'
    identity.ProductName = '#1 Test Slave'
    identity.ModelName = 'Research Testbed'
    identity.MajorMinorRevision = '2.3.0'

    StartSerialServer(context, framer=ModbusRtuFramer, identity=identity,
                       port='/dev/ttySC0', timeout=1, baudrate=115200, enable_broadcast=True)


if __name__ == "__main__":
    run_server()

Раб 2:

#!/usr/bin/env python

import serial
from pymodbus.server.sync import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusBinaryFramer

# --------------------------------------------------------------------------- #
# Logging
# --------------------------------------------------------------------------- #
import logging
FORMAT = (' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)


def run_server():

    block = ModbusSequentialDataBlock(0, [17]*100)
    slave = {
        0x0A: ModbusSlaveContext(di=block, co=block, hr=block, ir=block, zero_mode=True),
    }
    context = ModbusServerContext(slaves=slave, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'URL'
    identity.ProductName = '#3 Test Slave'
    identity.ModelName = 'Research Testbed'
    identity.MajorMinorRevision = '2.3.0'

    StartSerialServer(context, framer=ModbusRtuFramer, identity=identity,
                       port='/dev/ttySC0', timeout=1, baudrate=115200, enable_broadcast = True)


if __name__ == "__main__":
    run_server()

Мастер:

#!/usr/bin/env python

import serial
from pymodbus.repl.client import ModbusSerialClient as ModbusClient
import logging

#-------------------------------------------#
# Logging
#-------------------------------------------#
FORMAT = ('%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

#-------------------------------------------#
# Client Initialization
#-------------------------------------------#
client = ModbusClient(method='rtu', port='/dev/ttySC0', timeout=2,
                       baudrate=115200, broadcast_enable=True)

#-------------------------------------------#
# Command Interface
#-------------------------------------------#
def command_switch(UNIT , command):
    #Command Switch
    if command == 1:
        print(client.read_device_information(read_code=0x02, object_id=4, unit = UNIT))
    elif command == 2:
        print(client.read_coils(0,8, unit=UNIT))
    elif command == 3:
    response = client.read_holding_registers(0,4,unit=UNIT)
        print(response)
    elif command == 4:
    start = input("Enter starting address of coil (0-7 available): ")
        number = input("Select number of coils to write: ")
        value = input("Enter value to write (0-False, 1-True): ")
    write_coil(UNIT, start, number, value)
    elif command == 5:
       rq =  client.write_registers(1, [2, 3, 4], unit = 0)
       assert(not rq.isError())


def write_coil(UNIT, start, number, value):
    #Verify coil address is valid
    if (start + number) >= 8:
        number = start + number % 8;
    #Write intended value
    holder = False
    if value:
        holder = True
    #if multiple use write_coils else write_coil
    if number > 1:
        rq = client.write_coils(start,  [holder]*number, unit=UNIT)
    else:
        rq = client.write_coil(start, holder, unit=UNIT)


if __name__ == "__main__":
    client.connect()
    command = 0
    while command != 8:
        command = input("Enter Command to run:\n1. Device ID\n2. Read Coils\n3. Read Register\n4. Write Coils\n8. Exit\n")
    if command != 8:
            UNIT = input("Enter Device Address:")
            command_switch(UNIT, command)
    client.close()

Журналы:

Подчиненный 1 - адрес-1

 DEBUG    sync           :46       Client Connected [/dev/ttySC0:/dev/ttySC0]
 DEBUG    sync           :580      Started thread to serve client
 DEBUG    rtu_framer     :180      Getting Frame - 0x5 0x0 0x0 0xff 0x0
 DEBUG    factory        :137      Factory Request[WriteSingleCoilRequest: 5]
 DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
 DEBUG    context        :64       validate: fc-[5] address-1: count-1
 DEBUG    context        :90       setValues[5] 1:1
 DEBUG    context        :78       getValues fc-[5] address-1: count-1
 DEBUG    sync           :144      send: [WriteCoilResponse(0) => 1]- 01050000ff008c3a
 DEBUG    rtu_framer     :229      Not a valid unit id - 10, ignoring!!
 DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0xa 0x1 0x0 0x0 0x0 0x8 0x3c 0xb7
 DEBUG    rtu_framer     :232      Frame check failed, ignoring!!
 DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0xa 0x1 0x1 0xff 0x13 0xec
 DEBUG    rtu_framer     :229      Not a valid unit id - 0, ignoring!!
 DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0x0 0x5 0x0 0x0 0xff 0x0 0x8d 0xeb

Подчиненный 2: адрес-10

 DEBUG    sync           :46       Client Connected [/dev/ttySC0:/dev/ttySC0]
 DEBUG    sync           :580      Started thread to serve client
 DEBUG    rtu_framer     :229      Not a valid unit id - 1, ignoring!!
 DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0x1 0x5 0x0 0x0 0xff 0x0 0x8c 0x3a 0x1 0x5 0x0 0x0 0xff 0x0 0x8c 0x3a
 DEBUG    rtu_framer     :180      Getting Frame - 0x1 0x0 0x0 0x0 0x8
 DEBUG    factory        :137      Factory Request[ReadCoilsRequest: 1]
 DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
 DEBUG    context        :64       validate: fc-[1] address-0: count-8
 DEBUG    context        :78       getValues fc-[1] address-0: count-8
 DEBUG    sync           :144      send: [ReadBitResponse(8)]- 0a0101ff13ec
 DEBUG    rtu_framer     :229      Not a valid unit id - 0, ignoring!!
 DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0x0 0x5 0x0 0x0 0xff 0x0 0x8d 0xeb

Мастер:

Enter Command to run:
1. Device ID
2. Read Coils
3. Read Register
4. Write Coils
8. Exit
4
Enter Device Address:1
Enter starting address of coil (0-7 available): 0
Select number of coils to write: 1
Enter value to write (0-False, 1-True: 1
DEBUG    transaction    :115      Current transaction state - IDLE
DEBUG    transaction    :120      Running transaction 1
DEBUG    transaction    :219      SEND: 0x1 0x5 0x0 0x0 0xff 0x0 0x8c 0x3a
DEBUG    sync           :75       New Transaction state 'SENDING'
DEBUG    transaction    :228      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG    transaction    :304      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG    transaction    :233      RECV: 0x1 0x5 0x0 0x0 0xff 0x0 0x8c 0x3a
DEBUG    rtu_framer     :180      Getting Frame - 0x5 0x0 0x0 0xff 0x0
DEBUG    factory        :266      Factory Response[WriteSingleCoilResponse: 5]
DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
DEBUG    transaction    :383      Adding transaction 1
DEBUG    transaction    :394      Getting transaction 1
DEBUG    transaction    :193      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Enter Command to run:
1. Device ID
2. Read Coils
3. Read Register
4. Write Coils
8. Exit
2
Enter Device Address:10
DEBUG    transaction    :115      Current transaction state - TRANSACTION_COMPLETE
DEBUG    transaction    :120      Running transaction 2
DEBUG    transaction    :219      SEND: 0xa 0x1 0x0 0x0 0x0 0x8 0x3c 0xb7
DEBUG    rtu_framer     :264      Changing state to IDLE - Last Frame End - 1595270440.95, Current Time stamp - 1595270458.87
DEBUG    sync           :75       New Transaction state 'SENDING'
DEBUG    transaction    :228      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG    transaction    :304      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG    transaction    :233      RECV: 0xa 0x1 0x1 0xff 0x13 0xec
DEBUG    rtu_framer     :180      Getting Frame - 0x1 0x1 0xff
DEBUG    factory        :266      Factory Response[ReadCoilsResponse: 1]
DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
DEBUG    transaction    :383      Adding transaction 10
DEBUG    transaction    :394      Getting transaction 10
DEBUG    transaction    :193      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
{u'function_code': 1, u'bits': [True, True, True, True, True, True, True, True]}
Enter Command to run:
1. Device ID
2. Read Coils
3. Read Register
4. Write Coils
8. Exit
4
Enter Device Address:0
Enter starting address of coil (0-7 available): 0
Select number of coils to write: 1
Enter value to write (0-False, 1-True: 1
DEBUG    transaction    :115      Current transaction state - TRANSACTION_COMPLETE
DEBUG    transaction    :120      Running transaction 3
DEBUG    transaction    :219      SEND: 0x0 0x5 0x0 0x0 0xff 0x0 0x8d 0xeb
DEBUG    rtu_framer     :264      Changing state to IDLE - Last Frame End - 1595270459.16, Current Time stamp - 1595270469.71
DEBUG    sync           :75       New Transaction state 'SENDING'
DEBUG    transaction    :223      Changing transaction state from 'SENDING' to 'TRANSACTION_COMPLETE'
Traceback (most recent call last):
  File "modbus-client-rtu.py", line 62, in <module>
    command_switch(UNIT, command)
  File "modbus-client-rtu.py", line 36, in command_switch
    write_coil(UNIT, start, number, value)
  File "modbus-client-rtu.py", line 52, in write_coil
    rq = client.write_coil(start, holder, unit=UNIT)
  File "/home/pi/.local/lib/python2.7/site-packages/pymodbus/repl/client.py", line 112, in write_coil
    if not resp.isError():
AttributeError: 'str' object has no attribute 'isError'

person MattsInsomnia    schedule 20.07.2020    source источник


Ответы (1)


Ответ был довольно прост: в настройках ведомого я имел enable_broadcast = True, а не broadcast_enable = True. Хороший тест для индивидуальных настроек можно найти в этом запросе на вытягивание в github. Как только это было исправлено, ведомые устройства отвечают на широковещательные сообщения. Для ignore_missing_slaves необходимо установить значение true, чтобы ведомые устройства не отвечали на каждый запрос, поэтому настройка ведомых устройств теперь показывает:


    block = ModbusSequentialDataBlock(0, [17]*100)
    slave = {
        0x0A: ModbusSlaveContext(di=block, co=block, hr=block, ir=block),
    }
    context = ModbusServerContext(slaves=slave, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'URL'
    identity.ProductName = '#3 Test Slave'
    identity.ModelName = 'Research Testbed'
    identity.MajorMinorRevision = '2.3.0'

    StartSerialServer(context, framer=ModbusRtuFramer, identity=identity,ignore_missing_slaves=True,
                       port='/dev/ttySC0', timeout=0.1, baudrate=115200, broadcast_enable=True)

Для основной ошибки отладки AttributeError: объект 'str' не имеет атрибута 'isError'. Похоже, что поддержка широковещательной передачи не была реализована в клиенте repl. Для моего конкретного приложения я хочу иметь возможность читать информацию о подчиненном устройстве, а также отправлять широковещательные сообщения, поэтому я использую два клиента и подключаюсь, когда это необходимо, в главном коде:

from pymodbus.client.sync import ModbusSerialClient as ModbusClient
from pymodbus.repl.client import ModbusSerialClient as IdClient

#-------------------------------------------#
# Client Initialization
#-------------------------------------------#
client = ModbusClient(method='rtu', port='/dev/ttySC0', timeout=2,
                       baudrate=115200, broadcast_enable=True)
# Client sync has not implemented the device ID, may put in a request for broad$
# support on repl client
idClient = IdClient(method='rtu', port='/dev/ttySC0', timeout=2,
                       baudrate=115200, broadcast_enable=True)
client.connect()

При запросе идентификации устройства:

        client.close()
        time.sleep(0.1)
        idClient.connect()
        print(idClient.read_device_information(read_code=0x02, object_id=4, unit=UNIT)
        idClient.close()
        time.sleep(0.1)
        client.connect()

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

person MattsInsomnia    schedule 21.07.2020