#!/usr/bin/env python3
import time
import sys
import serial
import fcntl
import termios

TIOCSBRK = getattr(termios, "TIOCSBRK", 0x5427)  # set break
TIOCCBRK = getattr(termios, "TIOCCBRK", 0x5428)  # clear break

PORT = "/dev/ttyUSB0"
VALUE = 128
FPS = 41

BREAK_US = 0  # Hardware introduces a Break of ~110 us automatically
MAB_US = 0  # Hardware introduces a Mark After Break of ~170us automatically


def busy_wait_us(us: int):
    end = time.perf_counter_ns() + us * 1000
    while time.perf_counter_ns() < end:
        pass


def send_break(fd: int, break_us: int):
    fcntl.ioctl(fd, TIOCSBRK, 0)
    busy_wait_us(break_us)
    fcntl.ioctl(fd, TIOCCBRK, 0)


def send_dmx_frame(ser: serial.Serial, frame: bytes):
    fd = ser.fileno()
    send_break(fd, BREAK_US)
    busy_wait_us(MAB_US)
    ser.write(frame)
    ser.flush()


def main():
    port = sys.argv[1] if len(sys.argv) > 1 else PORT
    value = int(sys.argv[2]) if len(sys.argv) > 2 else VALUE
    fps = float(sys.argv[3]) if len(sys.argv) > 3 else FPS

    frame = bytes([0x00]) + bytes([value]) * 512

    ser = serial.Serial(
        port=port,
        baudrate=250000,
        bytesize=serial.EIGHTBITS,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_TWO,
        timeout=0,
    )

    period = 1.0 / fps
    print(
        f"DMX TX on {port}: all channels={value}, target={fps} FPS, BREAK={BREAK_US}us MAB={MAB_US}us"
    )

    try:
        next_t = time.perf_counter()
        while True:
            send_dmx_frame(ser, frame)
            next_t += period
            sleep = next_t - time.perf_counter()
            if sleep > 0:
                time.sleep(sleep)
    except KeyboardInterrupt:
        pass
    finally:
        ser.close()


if __name__ == "__main__":
    main()
