pythonpython-3.xmodbusmodbus-tcppymodbus

Efficient algorithm for modbusaddress scan on multiple connected devices in Python


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


Solution

  • 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.