Search code examples
pythonmultithreadingtimermodbus

How to read data from Modbus Server precisely every 0.05 seconds using Threading.Timer?


What I want to achieve?

  • Read data from Modbus Server precisely every 0.05 seconds.

The bigger picture is that I am creating a PyQt5 app through which I want to save and plot the Modbus data. So later I can use them for PID auto-tuning. PID auto-tuner requires that the data are measured with precision of at least 0.05 seconds. And also data points need to be spread equally like this: M wait 0.05s then M wait 0.05s then M and not like this: M wait 0.08s then M wait 0.03s then M (M = measure data).

What I have tried so far?

  • I have tried implementing Threading.Timer to read data every 0.05 seconds.

  • The problem is that the precision of the timer is too low.

This is the code on which I were testing the Threading.Timer precision:

from threading import Timer
import timeit

starttime = timeit.default_timer()

def f():
    Timer(0.05, f).start()

    global starttime

    print("The time difference is :", timeit.default_timer() - starttime)
    starttime = timeit.default_timer()

    #Here I would read the data

f() 

The code produces this output:

...
The time difference is : 0.07623
The time difference is : 0.07707
The time difference is : 0.07684
The time difference is : 0.07557
...
  • If I were to read data with this precision. It would create huge time difference in the long run.

  • Ideally the code would read data every 0.05s which is 20 reads per seconds. But with this precision it will read data on average every 0.07s and interpret it as 0.05s.

  • The time difference each second would be 0.07 * 20 - 0.05 * 20 = 0.4 seconds which is unacceptable because after one minute the time shift would be 24 seconds.

How can I improve the accuracy of Threading.Timer object? Or what other methods / tools should I use?


Other info:

I have measured how long does it take to read one value from Modbus Server using this code:

from pyModbusTCP.client import ModbusClient
import timeit

c = ModbusClient("localhost")
c.open()

for i in range(0,10):
    starttime = timeit.default_timer()
    data_now = c.read_holding_registers(0, 1)
    print("Reading 1 register takes:", timeit.default_timer() - starttime)

Output:

...
Reading 1 register takes: 0.000297100
Reading 1 register takes: 0.000307600
Reading 1 register takes: 0.000271699
...

What I am using:

  • Windows 10
  • Python 3.7 (32 bit verison)

Solution

  • You are overengineering. Just make an FPS lock. If reading takes more than 0.05, you have to recalculate backward (notimplemented). If you measure quick, recalculate against your desired 0.05s and wait that time. With this method, you can achieve exact 0.05s intervals. It cannot work if reading the register takes longer than your period

    This is working example with FPS lock. Nothing fancy. Set precision to create fake reading latency. Set period for your purpouses (i set 1s, you want 0.05s)

    import time
    import random
    
    def time_keeper(lost_by_reading,period=1):
        if lost_by_reading>period:
            pass # a problem because reading takes longer than period
        elif lost_by_reading<period:
            time.sleep(period-lost_by_reading)
    
    
    def mock_register_reading(precision=1000):
        rand_sleep = random.randint(0,10)/precision
        time.sleep(rand_sleep)
    
    
    _measure = time.time()
    
    period = 1
    
    for i in range(1000):
    
        start = time.time()
        mock_register_reading()
    
        ## just for log
        print('measured after',time.time()-_measure, ', ERROR: ',period-(time.time()-_measure))
        _measure = time.time()
        ### end
    
        finish = time.time()
        reading_time = finish-start
        time_keeper(reading_time,period = period)
    

    NOTE: I advise you not to use threading in regard to modbus. From my own experience, modbus and threading are not friends and for example reading register via threads only leads to catastrophy (just in case you will have an idea to make threads every 0.5 to read)