被奴隶忽略的广播消息

Broadcast messages ignored by slaves

描述

我目前有一个 RS-485 总线,每个总线有 3 个 Raspberry Pi 运行 pymodbus(同步 - 1 master/client 和 2 slaves/servers)。向每个服务器的单独地址发送命令按预期工作。但是,地址 0 的广播消息会在服务器上给出无效的单元 ID 调试消息,因此每个服务器都会丢弃来自主服务器的命令。通过阅读以前的问题和文档,我尝试将 enable_broadcast 常量添加到每个配置,但没有成功。我的配置有问题吗?

版本

代码和日志

代码

从机 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'

答案很简单,在从站设置中我有“enable_broadcast = True”而不是“broadcast_enable=True”。可以发现这个 pull request on the 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)

对于Master调试报错“AttributeError: 'str' object has no attribute '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()

我很可能会 post 关于 github 请求 repl 客户端广播支持的问题。