0

I am trying to connect to multiple (20) separate devices using Modbus TCP. Since it is beforehand not known what the modbusaddresses are (devices are changed regularly), I want to implement a modbus address scanner. Usually, the addresses are consecutive. So for example [80,81,82,83,...].

The 'simple' way is to try addresses in the expected range one by one to see if there is a response, although this is quite slow. I am wandering if there is a more algorithmic way of efficiently finding devices in a specified range

MZij
  • 76
  • 5
  • "multithreading" is not going to help with modbus RTU. – Brits Jul 15 '23 at 07:26
  • You are right, along the way I realized that the bottleneck is not waiting for a response but that modbus can only handle one request at a time. Little error, but doesn't change the actual question. I have edited the post, thanks. – MZij Jul 17 '23 at 06:39
  • 1
    If you are connecting to multiple modbus TCP devices (with different IP addresses) then making simultaneous requests will help. However it looked like you were making multiple requests to one server (changing the unit identifier) and that will not generally work concurrently (as this is most frequently accessing modbus RTU units behind a gateway). – Brits Jul 17 '23 at 08:15

1 Answers1

1

After an afternoon of trying several algorithm, I came up with the following function (of a class i made):

    def modbus_address_scan(self, number_of_devices: int) -> list[int]:
    """
    Function to find the modbusadresses of the connected devies.

    Rather than trying all potential addresses sequentially one-by-one (1->2->3->etc.)
    it does an initial search with steps of 10. When addresses are expected to be close together, this is much more efficient

    It starts with a list of all possible modbus addresses (1-100), and removes an address
    every time it tried to connect to that address. If connection was successfull, the
    address will also be appended to the list of connected devices.

    Returns: list of the modbusaddress of all found devices
    """
    potential_addresses = list(range(1, 101))  # list of all potential addresses it will try to connect to
    connected_devices = []  # empty list which will keep track of all found devices
    i = 0
    with tqdm(total=number_of_devices) as progress_bar:
        while (len(connected_devices) < number_of_devices and len(potential_addresses) != 0):
            print(potential_addresses[i])

            # try to connect to the modbus address <i> and  remove it from the list of potential_addresses
            result = self.try_address(potential_addresses[i])
            potential_addresses.remove(potential_addresses[i])

            # connection succesfull
            if result is not None:
                connected_devices.append(result)
                progress_bar.update(1)

            # connection failed
            elif i < len(potential_addresses) - 11:
                i += 10  # move 10 steps up in the list to try the next address
            else:
                i = 0  # go back to the first entry in potential_addresses list to try that one

    return connected_devices

In my case, this reduced finding 20 devices in a range from 1 to 100 from ~30s to ~15. To test this I made a list of random generated device addresses and checking creating a mockup function try_address:

def try_address(self, x) -> int:
   if x in devices:
        time.sleep(0.05)
        return x
    else:
        time.sleep(0.3)
        return None

If anyone has a faster or more efficient method, please let me know. I'm very interested.

MZij
  • 76
  • 5
  • "search with steps of 10" well, steps of 11 given that you remove an item from the array each iteration. If your ranges generally start with a multiple of 10 then this would make a significant difference. – Brits Jul 16 '23 at 02:03