【问题标题】:Minimalmodbus: read multiple slaves on same serial portMinimalmodbus:在同一个串口上读取多个从机
【发布时间】:2021-07-07 05:38:45
【问题描述】:

我有 2 个 SDM120 kWh 电能表以菊花链形式连接在同一个串行端口上(将来我想添加一个 SDM630)。我在 MinimalModbus 通信中找到了“Using multiple instruments”。我成功读取地址 1 上 SDM120 上的寄存器,但读取地址 2 时出现错误。错误:minimalmodbus.NoResponseError: No communication with the instrument (no answer)

我可以通过添加time.sleep(0.1) 来解决它,但我认为RS485 允许在第一个地址完成后立即读取第二个地址的寄存器。我也尝试了较低的值,但是例如。 time.sleep(0.01)也给了NoResponseError

我个人认为设置instrument.serial.timeout = 1 已经可以达到预期的效果,但显然我真的需要time.sleep。 time.sleep(0.1) 是正确的做法吗?如果是这样:我怎么知道最低值,所以我没有NoResponseError?试错?我的脚本可以优化吗?特别是当时间很重要时,例如。避免注入电网(光伏分流器,...)。提前致谢!

脚本:

#!/usr/bin/env python3
import minimalmodbus
import time

instrumentA = minimalmodbus.Instrument('/dev/ttyUSB0', 1, debug = True)  # port name, slave address (in decimal)
instrumentA.serial.baudrate = 9600
instrumentA.serial.timeout  = 1          # seconds
instrumentA.serial.bytesize = 8
instrumentA.serial.parity   = minimalmodbus.serial.PARITY_NONE
instrumentA.serial.stopbits = 1
instrumentA.mode = minimalmodbus.MODE_RTU

instrumentB = minimalmodbus.Instrument('/dev/ttyUSB0', 2, debug = True)
instrumentB.mode = minimalmodbus.MODE_RTU

print ("====== SDM120 instrumentA on addres 1 ======")
print (instrumentA)
P = instrumentA.read_float(12, 4, 2)
print ("Active Power in Watts:", P)

#time.sleep(0.1)  #workaround to avoid NoResponseError 

print ("====== SDM120 instrumentB on addres 2 ======")
print (instrumentB)
P = instrumentB.read_float(12, 4, 2)
print ("Active Power in Watts:", P)

不带time.sleep(0.1)的输出:

MinimalModbus debug mode. Create serial port /dev/ttyUSB0
MinimalModbus debug mode. Serial port /dev/ttyUSB0 already exists
====== SDM120 instrumentA on addres 1 ======
minimalmodbus.Instrument<id=0x7f36e3dc0df0, address=1, mode=rtu, close_port_after_each_call=False, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=True, serial=Serial<id=0x7f36e3dd90d0, open=True>(port='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
MinimalModbus debug mode. Will write to instrument (expecting 9 bytes back): '\x01\x04\x00\x0c\x00\x02±È' (01 04 00 0C 00 02 B1 C8)
MinimalModbus debug mode. Clearing serial buffers for port /dev/ttyUSB0
MinimalModbus debug mode. No sleep required before write. Time since previous read: 190954.73 ms, minimum silent period: 4.01 ms.
MinimalModbus debug mode. Response from instrument: '\x01\x04\x04\x00\x00\x00\x00û\x84' (01 04 04 00 00 00 00 FB 84) (9 bytes), roundtrip time: 53.3 ms. Timeout for reading: 1000.0 ms.

Active Power in Watts: 0.0
====== SDM120 instrumentB on addres 2 ======
minimalmodbus.Instrument<id=0x7f36e3c55940, address=2, mode=rtu, close_port_after_each_call=False, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=True, serial=Serial<id=0x7f36e3dd90d0, open=True>(port='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
MinimalModbus debug mode. Will write to instrument (expecting 9 bytes back): '\x02\x04\x00\x0c\x00\x02±û' (02 04 00 0C 00 02 B1 FB)
MinimalModbus debug mode. Clearing serial buffers for port /dev/ttyUSB0
MinimalModbus debug mode. Sleeping 2.31 ms before sending. Minimum silent period: 4.01 ms, time since read: 1.70 ms.
MinimalModbus debug mode. Response from instrument: '' () (0 bytes), roundtrip time: 1001.3 ms. Timeout for reading: 1000.0 ms.

Traceback (most recent call last):
  File "./sdm120-daisychain_v3.py", line 25, in <module>
    P = instrumentB.read_float(12, 4, 2)
  File "/home/mattias/.local/lib/python3.8/site-packages/minimalmodbus.py", line 662, in read_float
    return self._generic_command(
  File "/home/mattias/.local/lib/python3.8/site-packages/minimalmodbus.py", line 1170, in _generic_command
    payload_from_slave = self._perform_command(functioncode, payload_to_slave)
  File "/home/mattias/.local/lib/python3.8/site-packages/minimalmodbus.py", line 1240, in _perform_command
    response = self._communicate(request, number_of_bytes_to_read)
  File "/home/mattias/.local/lib/python3.8/site-packages/minimalmodbus.py", line 1406, in _communicate
    raise NoResponseError("No communication with the instrument (no answer)")
minimalmodbus.NoResponseError: No communication with the instrument (no answer)

使用time.sleep(0.1) 输出:

MinimalModbus debug mode. Create serial port /dev/ttyUSB0
MinimalModbus debug mode. Serial port /dev/ttyUSB0 already exists
====== SDM120 instrumentA on addres 1 ======
minimalmodbus.Instrument<id=0x7f91feddcdf0, address=1, mode=rtu, close_port_after_each_call=False, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=True, serial=Serial<id=0x7f91fedf50d0, open=True>(port='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
MinimalModbus debug mode. Will write to instrument (expecting 9 bytes back): '\x01\x04\x00\x0c\x00\x02±È' (01 04 00 0C 00 02 B1 C8)
MinimalModbus debug mode. Clearing serial buffers for port /dev/ttyUSB0
MinimalModbus debug mode. No sleep required before write. Time since previous read: 176619.62 ms, minimum silent period: 4.01 ms.
MinimalModbus debug mode. Response from instrument: '\x01\x04\x04\x00\x00\x00\x00û\x84' (01 04 04 00 00 00 00 FB 84) (9 bytes), roundtrip time: 53.3 ms. Timeout for reading: 1000.0 ms.

Active Power in Watts: 0.0
====== SDM120 instrumentB on addres 2 ======
minimalmodbus.Instrument<id=0x7f91fec70940, address=2, mode=rtu, close_port_after_each_call=False, precalculate_read_size=True, clear_buffers_before_each_transaction=True, handle_local_echo=False, debug=True, serial=Serial<id=0x7f91fedf50d0, open=True>(port='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
MinimalModbus debug mode. Will write to instrument (expecting 9 bytes back): '\x02\x04\x00\x0c\x00\x02±û' (02 04 00 0C 00 02 B1 FB)
MinimalModbus debug mode. Clearing serial buffers for port /dev/ttyUSB0
MinimalModbus debug mode. No sleep required before write. Time since previous read: 102.09 ms, minimum silent period: 4.01 ms.
MinimalModbus debug mode. Response from instrument: '\x02\x04\x04\x00\x00\x00\x00È\x84' (02 04 04 00 00 00 00 C8 84) (9 bytes), roundtrip time: 52.8 ms. Timeout for reading: 1000.0 ms.

Active Power in Watts: 0.0

【问题讨论】:

  • MinimalModbus multiple instruments documentation 建议为每个设备创建一个新的仪器对象,即使它们在同一个端口/总线上。
  • 感谢@Bosz 的提示,但没有time.sleep(0.1) 我得到同样的错误。我使用的脚本是:
  • 我已将我尝试过的其他脚本添加到我的初始帖子中。
  • 我觉得这很令人惊讶。您可以从一种仪器连续读取,但当您尝试切换到另一种仪器时,它会失败。它看起来像一个错误,但这个库非常简单,我没有看到任何明显的错误。您可以在仪器的实例化中添加debug=True,再次运行您的代码,然后发布完整的调试日志吗?请注意,minimalmodbus 已经非常宽松地使用time.sleep() 来避免总线上的冲突(请参阅here)所以如果你想在你的代码中使用它,你不应该为此感到难过...
  • @MarcosG.,我添加了debug=True 并相应地编辑了我的帖子(我已经删除了旧脚本)。我对time.sleep() 没意见,但我仍然想了解为什么需要它以及为什么 0.1 值是可以的,但 0.01 值不是。它是否取决于库、python、串行链接、SDM120,我需要 SDM630 的另一个值吗?那将是什么价值?有很多问题:-),但已经很高兴 time.sleep() 至少可以工作:-)。

标签: modbus minimalmodbus


【解决方案1】:

您的代码或您正在使用的库(minimalmodbus)似乎没有任何问题。

您可能知道,Modbus 在半双工链路上以查询-响应模式工作。简而言之:您首先发送一个查询,该查询的设备将使用您要求的数据来回答。

事务的两个部分(查询和响应)通过同一总线传输。但是总线是一种共享介质,任何时候都只允许一个设备控制总线(通话)。

当您有一个主设备和一个或多个从设备时,只要您保证在任何设备写入总线后有一个短暂的静默期,这个过程就不会出现任何问题。 Modbus 规范将此值确定为 3.5 个字符(以您使用的波特率在总线上串行发送 3 个半字符所需的时间)。

不幸的是,一些制造商不遵守这条规则。因此,其中一些设备只需超过 3.5 个字符的时间即可释放对总线的控制。

至少您的一台设备似乎是这种情况。 This manual可以给你一些线索:

我敢打赌,在您的两台设备中,其中一台释放总线所需的时间明显少于另一台,但您必须通过调试详细信息确认这一点。如果您查询 20 或 40 个寄存器而不是 4 或 8 个寄存器,甚至可能设备需要更长的时间来释放总线...

你能做些什么呢?好吧,从设备方面来说,不多,就是这样。在您的软件上,您可以做许多不同的事情。正如我在上面的 cmets 中所说,您不应该对使用 time.sleep() 感到难过,因为这是 minimummodbus 尝试处理总线争用问题的方式。

为了使您的代码更加健壮,您可以添加try: ... except:。这种方法在documentation 中有解释。您可以在一个循环内继续重试读取多次尝试,或者向except 块添加一个小的延迟。也许是like this

【讨论】:

    【解决方案2】:

    Marcos G. 的回答(4 月 13 日 9:24 回答)包括一些背景细节。简而言之:

    1. 经过反复试验,可以为time.sleep 赋值,以便 minimummodbus 可以处理总线争用问题。
    2. 您可以使用try: ... except: 获得更健壮的代码。最好只尝试多次以避免无限循环。

    我包含两个脚本,它们使用这些方法来解决我发布的问题。与我原来的问题相比,使用了 for 循环和地址数组。

    第一个是time.sleep

    #!/usr/bin/env python3
    import minimalmodbus
    import time
    
    addr = 1
    instrument = minimalmodbus.Instrument('/dev/ttyUSB0', addr)  # port name, slave address (in decimal)
    
    instrument.serial.baudrate = 9600         # Baud
    instrument.serial.bytesize = 8
    instrument.serial.parity   = minimalmodbus.serial.PARITY_NONE
    instrument.serial.stopbits = 1
    instrument.serial.timeout  = 1          # seconds
    instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
    
    addresses = [1,2]
    registers = [ 0,  6, 12, 18,    24,  30, 70,   72,   74,     76,     78,   84,   86,  88,    90,   92,   94, 258,  264,  342,    344]
    names =     ["V","I","P","S",   "Q","PF","f","IAE","EAE",  "IRE",  "ERE","TSP","MSP","ISP","MIP","ESP","MEP","ID","MID","TAE",  "TRE"]
    units =     ["V","A","W","VA","var", "","Hz","kWh","kWh","kvarh","kvarh",  "W",  "W",  "W",  "W",  "W",  "W", "A",  "A","kWh","kvarh"]
    info = [
    "(V for Voltage in volt)",
    "(I for Current in ampere)",
    "(P for Active Power in watt)",
    "(S for Apparent power in volt-ampere)",
    "(Q for Reactive power in volt-ampere reactive)",
    "(PF for Power Factor)",
    "(f for Frequency in hertz)",
    "(IAE for Import active energy in kilowatt-hour)",
    "(EAE for Export active energy in kilowatt-hour)",
    "(IRE for Import reactive energy in kilovolt-ampere reactive hours)",
    "(ERE for Export reactive energy in kilovolt-ampere reactive hours)",
    "(TSP for Total system power demand in watt)",
    "(MSP for Maximum total system power demand in watt)",
    "(ISP for Import system power demand in watt)",
    "(MIP for Maximum import system power demand in watt)",
    "(ESP for Export system power demand in watt)",
    "(MEP for MaximumExport system power demand in watt)",
    "(ID for current demand in ampere)",
    "(MID for Maximum current demand in ampere)",
    "(TAE for Total active energy in kilowatt-hour)",
    "(TRE for Total reactive energy in kilovolt-ampere reactive hours)",
    ]
    for addr in addresses:
        instrument.address = addr
        print ("=== General info about address", addr, "===")
        print (instrument)
        print ("=== The registers for address", addr, "===")
        for i in range(len(registers)):
            value = instrument.read_float(registers[i], 4, 2)
            print (str(registers[i]).rjust(3), str(value).rjust(20), units[i].ljust(5), info[i])
        time.sleep(0.1) # To avoid minimalmodbus.NoResponseError
        print ("")
    

    第二个是try: ... except:

    #!/usr/bin/env python3
    import minimalmodbus
    
    # This alternative script `sdm120-daisy-alt.py` will try to reread a device an extra 9 times before giving up and continuing with the other addresses in the array.
    
    addr = 1
    instrument = minimalmodbus.Instrument('/dev/ttyUSB0', addr)  # port name, slave address (in decimal)
    
    instrument.serial.baudrate = 9600         # Baud
    instrument.serial.bytesize = 8
    instrument.serial.parity   = minimalmodbus.serial.PARITY_NONE
    instrument.serial.stopbits = 1
    instrument.serial.timeout  = 1          # seconds
    instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
    
    addresses = [1,2]
    registers = [ 0,  6, 12, 18,    24,  30, 70,   72,   74,     76,     78,   84,   86,  88,    90,   92,   94, 258,  264,  342,    344]
    names =     ["V","I","P","S",   "Q","PF","f","IAE","EAE",  "IRE",  "ERE","TSP","MSP","ISP","MIP","ESP","MEP","ID","MID","TAE",  "TRE"]
    units =     ["V","A","W","VA","var", "","Hz","kWh","kWh","kvarh","kvarh",  "W",  "W",  "W",  "W",  "W",  "W", "A",  "A","kWh","kvarh"]
    info = [
    "(V for Voltage in volt)",
    "(I for Current in ampere)",
    "(P for Active Power in watt)",
    "(S for Apparent power in volt-ampere)",
    "(Q for Reactive power in volt-ampere reactive)",
    "(PF for Power Factor)",
    "(f for Frequency in hertz)",
    "(IAE for Import active energy in kilowatt-hour)",
    "(EAE for Export active energy in kilowatt-hour)",
    "(IRE for Import reactive energy in kilovolt-ampere reactive hours)",
    "(ERE for Export reactive energy in kilovolt-ampere reactive hours)",
    "(TSP for Total system power demand in watt)",
    "(MSP for Maximum total system power demand in watt)",
    "(ISP for Import system power demand in watt)",
    "(MIP for Maximum import system power demand in watt)",
    "(ESP for Export system power demand in watt)",
    "(MEP for MaximumExport system power demand in watt)",
    "(ID for current demand in ampere)",
    "(MID for Maximum current demand in ampere)",
    "(TAE for Total active energy in kilowatt-hour)",
    "(TRE for Total reactive energy in kilovolt-ampere reactive hours)",
    ]
    for addr in addresses:
        instrument.address = addr
        print ("=== General info about address", addr, "===")
        print (instrument)
        print ("=== The registers for address", addr, "===")
        for attempt in range(10):
            try:
                for i in range(len(registers)):
                    value = instrument.read_float(registers[i], 4, 2)
                    print (str(registers[i]).rjust(3), str(value).rjust(20), units[i].ljust(5), info[i])
            except minimalmodbus.NoResponseError:
                print("NoResponseError: did't work on attempt ", attempt+1, "I will retry")
            else:
                print ("Succeeded on attempt", attempt+1)
                break
        print ("")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多