Как считывать данные с сервера Modbus точно каждые 0,05 секунды с помощью Threading.Таймер?

#python #multithreading #timer #modbus

#python #многопоточность #таймер #modbus

Вопрос:

Чего я хочу добиться?

  • Считывать данные с сервера Modbus точно каждые 0,05 секунды.

Общая картина заключается в том, что я создаю приложение PyQt5, с помощью которого я хочу сохранить и отобразить данные Modbus. Поэтому позже я смогу использовать их для автоматической настройки PID. Автонастройка PID требует, чтобы данные измерялись с точностью не менее 0,05 секунды. А также точки данных должны быть распределены одинаково следующим образом: M wait 0.05s then M wait 0.05s then M и не так: M wait 0.08s then M wait 0.03s then M (M = measure data) .

Что я пробовал до сих пор?

  • Я попытался реализовать Threading.Timer чтение данных каждые 0,05 секунды.
  • Проблема в том, что точность таймера слишком низкая.

Это код, на котором я тестировал Threading.Timer точность:

 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 time difference is : 0.07623
The time difference is : 0.07707
The time difference is : 0.07684
The time difference is : 0.07557
...
 
  • Если бы я должен был считывать данные с такой точностью. В долгосрочной перспективе это создало бы огромную разницу во времени.
  • В идеале код будет считывать данные, каждый 0.05s из которых 20 считывается в секунду. Но с такой точностью он будет считывать данные в среднем каждый 0.07s и интерпретировать их как 0.05s .
  • Разница во времени каждую секунду будет 0.07 * 20 - 0.05 * 20 = 0.4 seconds неприемлемой, потому что через одну минуту временной сдвиг составит 24 секунды.

Как я могу повысить точность Threading.Timer объекта? Или какие другие методы / инструменты я должен использовать?


Другая информация:

Я измерил, сколько времени требуется, чтобы прочитать одно значение с сервера Modbus, используя этот код:

 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)
 

Вывод:

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

Что я использую:

  • Windows 10
  • Python 3.7 (32-разрядная версия)

Ответ №1:

Вы переоцениваете. Просто сделайте блокировку FPS. Если чтение занимает более 0,05, вам нужно пересчитать в обратном направлении (не выполняется). Если вы измеряете быстро, пересчитайте против желаемых 0,05 с и подождите это время. С помощью этого метода вы можете добиться точных интервалов 0,05 с. Это не может работать, если чтение регистра занимает больше времени, чем ваш период

Это рабочий пример с блокировкой FPS. Ничего особенного. Установите точность, чтобы создать ложную задержку чтения. Установите период для ваших целей (я установил 1 с, вы хотите 0,05 с)

 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)
 

ПРИМЕЧАНИЕ:
Я советую вам не использовать многопоточность в отношении modbus. По моему собственному опыту, modbus и threading не друзья, и, например, чтение регистра через потоки приводит только к катастрофе (на всякий случай, у вас будет идея создавать потоки каждые 0,5 для чтения)

Комментарии:

1. Хорошо, спасибо, постараюсь реализовать. Вернется, когда я узнаю, работает он или нет 🙂

2. Но, как я уже сказал, это всего лишь пример. На вашем месте я бы сохранил значение чтения в паре с объективным временем, чтобы вы всегда могли интерполировать пропущенные значения и всегда калибровать время по объективному времени. Но, может быть, поскольку вы вводите какое-то дифференциальное уравнение, тогда, я думаю, достаточно просто значений шага