如何提高 Python 脚本中用于 DMX 控制的计时精度

How to Improve Timing Precision in Python Script for DMX Control

提问人:Losbello 提问时间:10/12/2023 最后编辑:Alan BirtlesLosbello 更新时间:10/12/2023 访问量:45

问:

我有一个 Python 脚本,它通过 OLA 库控制 DMX 照明。该脚本从 CSV 文件中读取颜色值,并以 120 毫秒的固定间隔将它们发送到 DMX 控制器。

我使用 time.perf_counter() 进行计时,但随着时间的推移,我遇到了漂移。具体来说,当脚本发送超过 1000 个值的大序列时,它在传递 100 个值后开始漂移超过 40 毫秒。我的应用需要极高的精度。

import array
import csv
import time
import warnings
from ola.ClientWrapper import ClientWrapper
from tqdm import tqdm

# Suppress DeprecationWarning
warnings.filterwarnings("ignore", category=DeprecationWarning)

def custom_sleep(duration, get_now=time.perf_counter):
    now = get_now()
    end = now + duration
    while now < end:
        now = get_now()

def read_csv(filename):
    with open(filename, newline='') as file:
        reader = csv.reader(file)
        next(reader)  # Skip the header row
        return [list(map(int, row)) for row in reader]

def DmxSent(state):
    pass

def main():
    universe = 1
    dmx_values = read_csv('dmx_values.csv')

    wrapper = ClientWrapper()
    client = wrapper.Client()

    with open('log_file.txt', 'w') as log_file:
        for values in tqdm(dmx_values, desc="Processing DMX Values"):
            start_time = time.perf_counter()
            data = array.array('B', values + [0] * (512 - len(values)))
            client.SendDmx(universe, data, DmxSent)
            custom_sleep(0.120)  # Hold for 120ms
            elapsed_time = (time.perf_counter() - start_time) * 1000
            log_file.write(f"Elapsed Time: {elapsed_time:.2f} ms\n")

if __name__ == "__main__":
    main()

我实现了一个custom_sleep函数,该函数使用 time.perf_counter() 来实现更精确的计时,但仍然会发生漂移。

我添加了日志记录来测量每个操作所花费的实际时间,但偏差仍然存在。我用摄像机以 25fps 的速度记录光源,并观察到值 100 左右的漂移,从而证实了这一点。

我希望时序具有高精度,并且随时间推移的漂移最小。

我在 MacBook Air M1 上运行脚本。我使用的 DMX 接口是 Enttec USB Pro。

我正在考虑为时间关键部分集成 C/C++,但希望社区就最佳方法提供建议。

python 标签: precision timing

评论

0赞 Alan Birtles 10/12/2023
尝试记录程序的开始时间并睡眠直到开始时间 + 120 毫秒 * 操作编号,而不是固定的 120 毫秒
0赞 Scott Hunter 10/12/2023
你的 C/C++ 想法听起来不错。

答:

0赞 C L 10/12/2023 #1

Pyglet.clock 不应该随时间漂移,但在滴答声之后,您的函数仍然会有延迟。每次调用函数时,这应该是相对恒定的,因此在您的情况下可能不是问题。

0赞 jediclustethp 11/26/2023 #2

问题是您必须注意通过驱动程序发送的命令控制设备所需的时间。因此,您不能在发送命令后“开始计时”。

更具体地说,您必须致电您的custom_sleep开始时间

custom_sleep(0.120, lambda: start_time)

它将避免漂移,但您必须注意间隙是否足以用于命令。

您还可以通过将所有操作的时间包含在计时器中来提高精度和一致性,例如

   time_interval = 0.120
   gap = 0
   start_time = time.perf_counter()
   for values in tqdm(dmx_values, desc="Processing DMX Values"):
        data = array.array('B', values + [0] * (512 - len(values)))
        client.SendDmx(universe, data, DmxSent)
        elapsed_time = (time.perf_counter() - start_time) * 1000
        log_file.write(f"Elapsed Time: {elapsed_time:.2f} ms\n")
        gap = gap + time_interval
        custom_sleep(gap, start_time)  # Hold for 120ms

我不认为你的睡眠功能会导致漂移,但你正在使用繁忙循环来检查时间。我不详细介绍,因为方法太多了。您可以参考执行定期操作

对于实时应用程序,您可以考虑使用 RTOS。它可以像设置计时器并调用您正在使用的 ola 库的 api 一样简单,但这实际上取决于您的情况。