flexPTP-test/LinuxPtpObserver.py
2026-05-07 10:42:36 +02:00

155 lines
5.4 KiB
Python

import queue
import signal
import subprocess
import select
from threading import Thread
import time
from typing import Callable
import re
class LinuxPtpEvent:
def __init__(self, name: str, args: dict) -> None:
self.__name = name
self.__args = args
def get_name(self) -> str:
return self.__name
def get_args(self) -> dict:
return self.__args
class LinuxPtpObserver:
OFFSET_REGEX = r"ptp4l\[[0-9.]+\]: master offset[ ]+([-\d]+) s\d freq[ ]+([-\d]+) path delay[ ]+([\d]+)"
BMCA_REGEX = r"ptp4l\[[0-9.]+\]: port \d+ \([a-z0-9]+\): ([A-Z_]+) to ([A-Z_]+) on [A-Z_]+"
def __init__(self, ni: str) -> None:
self.__ni = ni
self.__process: subprocess.Popen
self.__observer_thread: Thread
self.__observer_cb: Callable | None = None
self.__event_queue = queue.Queue()
self.__error_queue = queue.Queue()
pass
def observer_routine(self) -> None:
pfd = select.poll()
if self.__process.stdout is not None:
pfd.register(self.__process.stdout.fileno(), select.POLLIN)
else:
return
if self.__process.stderr is not None:
pfd.register(self.__process.stderr.fileno(), select.POLLIN)
else:
return
while self.__process.poll() is None:
res = pfd.poll()
for fde in res:
if fde[0] == self.__process.stdout.fileno() and fde[1] & select.POLLIN: # STDOUT
orig_data = self.__process.stdout.readline()
data = orig_data.strip()
# SYNCLOG lines
match = re.match(LinuxPtpObserver.OFFSET_REGEX, data)
if match is not None:
args = {
"Dt": int(match.group(1)),
"mpd_ns": int(match.group(3))
}
event = LinuxPtpEvent("synclog-slave", args)
self.__event_queue.put(event)
# BMCA state change lines
match = re.match(LinuxPtpObserver.BMCA_REGEX, data)
if match is not None:
args = {
"from": match.group(1),
"to": match.group(2)
}
event = LinuxPtpEvent("bmca-statechange", args)
self.__event_queue.put(event)
if self.__observer_cb is not None:
self.__observer_cb(orig_data)
elif fde[0] == self.__process.stderr.fileno() and fde[1] & select.POLLIN: # STDERR
text = self.__process.stderr.read()
self.__error_queue.put(text)
event = LinuxPtpEvent("error", { "text": text })
self.__event_queue.put(event)
# thread has returned
event = LinuxPtpEvent("exit", {})
self.__event_queue.put(event)
def start_linuxptp(self, profile: str) -> None:
cmd = ["sudo", "ptp4l", "--priority1=127", "--priority2=255", "--gmCapable=1", "--neighborPropDelayThresh=100000",
"--min_neighbor_prop_delay=-20000000", "--assume_two_step=1", "--ptp_minor_version=0", "--summary_interval=-5",
"-i", self.__ni, "-f", "linuxptp_configs/{:s}.cfg".format(profile), "-m", "-l", "6"]
self.__process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
self.__observer_thread = Thread(target=self.observer_routine)
self.__observer_thread.start()
def stop_linuxptp(self) -> None:
self.__process.send_signal(signal.SIGKILL)
self.__observer_thread.join()
self.__event_queue = queue.Queue()
def register_observer_callback(self, cb : Callable) -> None:
self.__observer_cb = cb
def get_event(self, timeout: float) -> LinuxPtpEvent:
try:
return self.__event_queue.get(timeout=timeout)
except:
return LinuxPtpEvent("none", {})
def wait_for_event(self, expected_name: str, timeout: float, argcrits = {}) -> LinuxPtpEvent:
timeout_left = timeout
start = time.time_ns()
event = LinuxPtpEvent("none", {})
critera_met = False
while (event.get_name() != expected_name or not critera_met) and timeout_left > 0:
try:
event = self.__event_queue.get(timeout=timeout_left) # type: LinuxPtpEvent
# propagate exit event
if event.get_name() in ("exit", "error"):
return event
critera_met = True
for c_name, c_value in argcrits.items():
args = event.get_args()
if c_name in args:
if args[c_name] != c_value:
critera_met = False
break
except:
pass
now = time.time_ns()
timeout_left = timeout - ((now - start) / 1E+09)
return event
def get_errors(self) -> str:
error = ""
while not self.__error_queue.empty():
error += self.__error_queue.get()
return error