mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
additional fields in curses
This commit is contained in:
parent
47620dfe22
commit
61a8fd9434
1 changed files with 82 additions and 36 deletions
118
timeturner.py
118
timeturner.py
|
|
@ -1,71 +1,117 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
import curses
|
import curses
|
||||||
import serial
|
import serial
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
# Configurable parameters
|
# Serial config
|
||||||
SERIAL_PORT = "/dev/ttyACM0"
|
SERIAL_PORT = "/dev/ttyACM0"
|
||||||
BAUD_RATE = 115200
|
BAUD_RATE = 115200
|
||||||
REFRESH_INTERVAL = 0.5 # seconds
|
REFRESH_INTERVAL = 0.5 # seconds
|
||||||
|
|
||||||
# Regex to match LTC lines
|
# Regex pattern
|
||||||
ltc_pattern = re.compile(
|
ltc_pattern = re.compile(
|
||||||
r"\[(LOCK|FREE)\]\s+(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+\|\s+([\d.]+fps)", re.IGNORECASE
|
r"\[(LOCK|FREE)\]\s+(\d{2}:\d{2}:\d{2}[:;]\d{2})\s+\|\s+([\d.]+fps)", re.IGNORECASE
|
||||||
)
|
)
|
||||||
|
|
||||||
def read_ltc(ser):
|
# Stats
|
||||||
"""Reads and parses one line of LTC from the serial interface"""
|
lock_count = 0
|
||||||
try:
|
free_count = 0
|
||||||
line = ser.readline().decode(errors='ignore').strip()
|
last_frame = None
|
||||||
match = ltc_pattern.match(line)
|
drift_warnings = []
|
||||||
if match:
|
|
||||||
status, timecode, framerate = match.groups()
|
def parse_timecode(tc_str):
|
||||||
return f"{status} {timecode} ({framerate.upper()})"
|
sep = ":" if ":" in tc_str else ";"
|
||||||
return None
|
h, m, s, f = map(int, tc_str.replace(";", ":").split(":"))
|
||||||
except:
|
return h, m, s, f, sep
|
||||||
return None
|
|
||||||
|
def timecode_to_milliseconds(h, m, s, f, fps):
|
||||||
|
return int(((h * 3600 + m * 60 + s) * 1000) + (f * (1000 / fps)))
|
||||||
|
|
||||||
|
def get_offset(system_dt, h, m, s, f, fps):
|
||||||
|
sys_ms = (system_dt.hour * 3600 + system_dt.minute * 60 + system_dt.second) * 1000 + system_dt.microsecond // 1000
|
||||||
|
ltc_ms = timecode_to_milliseconds(h, m, s, f, fps)
|
||||||
|
return sys_ms - ltc_ms
|
||||||
|
|
||||||
|
def format_offset(ms, fps):
|
||||||
|
frame_duration = 1000 / fps
|
||||||
|
frame_offset = int(round(ms / frame_duration))
|
||||||
|
return f"{ms:+} ms ({frame_offset:+} frames)"
|
||||||
|
|
||||||
def draw_ui(stdscr):
|
def draw_ui(stdscr):
|
||||||
curses.curs_set(0) # Hide cursor
|
global lock_count, free_count, last_frame, drift_warnings
|
||||||
stdscr.nodelay(True)
|
|
||||||
stdscr.timeout(int(REFRESH_INTERVAL * 1000))
|
curses.curs_set(0)
|
||||||
|
stdscr.nodelay(True)
|
||||||
|
|
||||||
# Open serial connection
|
|
||||||
try:
|
try:
|
||||||
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
|
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
stdscr.addstr(0, 0, f"[ERROR] Failed to open serial: {e}")
|
stdscr.addstr(0, 0, f"[ERROR] Couldn't open {SERIAL_PORT}: {e}")
|
||||||
stdscr.getch()
|
stdscr.getch()
|
||||||
return
|
return
|
||||||
|
|
||||||
ltc_string = "Waiting for LTC…"
|
# Init variables
|
||||||
|
ltc_status = "--"
|
||||||
|
ltc_timecode = "--:--:--:--"
|
||||||
|
framerate = "--"
|
||||||
|
offset_str = "--"
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
line = ser.readline().decode(errors='ignore').strip()
|
||||||
|
match = ltc_pattern.match(line)
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
if match:
|
||||||
|
status, tc_str, fps_str = match.groups()
|
||||||
|
ltc_status = status
|
||||||
|
ltc_timecode = tc_str
|
||||||
|
framerate = fps_str
|
||||||
|
fps = float(fps_str.lower().replace("fps", ""))
|
||||||
|
|
||||||
|
h, m, s, f, sep = parse_timecode(tc_str)
|
||||||
|
current_frame = timecode_to_milliseconds(h, m, s, f, fps)
|
||||||
|
|
||||||
|
# Drift detection
|
||||||
|
if last_frame is not None:
|
||||||
|
expected = last_frame + int(1000 / fps)
|
||||||
|
if abs(current_frame - expected) > int(2 * (1000 / fps)):
|
||||||
|
drift_warnings.append(f"Drift: Δ{current_frame - last_frame} ms")
|
||||||
|
if len(drift_warnings) > 3:
|
||||||
|
drift_warnings.pop(0)
|
||||||
|
last_frame = current_frame
|
||||||
|
|
||||||
|
# Sync offset
|
||||||
|
offset_ms = get_offset(now, h, m, s, f, fps)
|
||||||
|
offset_str = format_offset(offset_ms, fps)
|
||||||
|
|
||||||
|
# Stats
|
||||||
|
if ltc_status == "LOCK":
|
||||||
|
lock_count += 1
|
||||||
|
else:
|
||||||
|
free_count += 1
|
||||||
|
|
||||||
|
# UI
|
||||||
stdscr.clear()
|
stdscr.clear()
|
||||||
|
stdscr.addstr(0, 0, "🕰 NTP Timeturner v0.3")
|
||||||
# Read LTC if available
|
stdscr.addstr(2, 0, f"LTC Status : {ltc_status}")
|
||||||
new_ltc = read_ltc(ser)
|
stdscr.addstr(3, 0, f"LTC Timecode : {ltc_timecode}")
|
||||||
if new_ltc:
|
stdscr.addstr(4, 0, f"Frame Rate : {framerate}")
|
||||||
ltc_string = new_ltc
|
stdscr.addstr(5, 0, f"System Clock : {now.strftime('%H:%M:%S.%f')[:-3]}")
|
||||||
|
stdscr.addstr(6, 0, f"Sync Offset : {offset_str}")
|
||||||
# Get system time
|
stdscr.addstr(7, 0, f"Lock Ratio : {lock_count} LOCK / {free_count} FREE")
|
||||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
if drift_warnings:
|
||||||
|
stdscr.addstr(9, 0, f"⚠️ {drift_warnings[-1]}")
|
||||||
# Draw UI
|
stdscr.addstr(11, 0, "Press Ctrl+C to exit.")
|
||||||
stdscr.addstr(0, 0, "🕰️ NTP Timeturner v0.1")
|
|
||||||
stdscr.addstr(2, 0, f"LTC Timecode: {ltc_string}")
|
|
||||||
stdscr.addstr(3, 0, f"System Clock: {now}")
|
|
||||||
stdscr.addstr(5, 0, "Press Ctrl+C to quit.")
|
|
||||||
|
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
time.sleep(REFRESH_INTERVAL)
|
time.sleep(REFRESH_INTERVAL)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stdscr.addstr(7, 0, f"[EXCEPTION] {e}")
|
stdscr.addstr(13, 0, f"[EXCEPTION] {e}")
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue