refactor: reorganize tests/tools, share python protocol helpers
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
@@ -4,115 +4,10 @@
|
||||
import argparse
|
||||
import math
|
||||
import socket
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
|
||||
PLOT_ESCAPE = 0x1E
|
||||
PLOT_MARKER = ord("P")
|
||||
DISCONNECT_WINERRORS = {
|
||||
10053, # Software caused connection abort
|
||||
10054, # Connection reset by peer
|
||||
10058, # Socket shutdown race on Windows
|
||||
}
|
||||
|
||||
|
||||
def build_frame(
|
||||
sample_index: int,
|
||||
channels: int,
|
||||
plot_format: str,
|
||||
samples_per_channel: int,
|
||||
amplitude: float,
|
||||
frequency_hz: float,
|
||||
sample_rate_hz: float,
|
||||
) -> bytes:
|
||||
values = []
|
||||
if plot_format == "xy":
|
||||
for offset in range(samples_per_channel):
|
||||
t = (sample_index + offset) / sample_rate_hz
|
||||
x = amplitude * math.cos(2 * math.pi * frequency_hz * t)
|
||||
y = amplitude * math.sin(2 * math.pi * frequency_hz * t)
|
||||
values.extend([x, y])
|
||||
return struct.pack(f"<{len(values)}f", *values)
|
||||
|
||||
for offset in range(samples_per_channel):
|
||||
t = (sample_index + offset) / sample_rate_hz
|
||||
for ch in range(channels):
|
||||
phase = 2 * math.pi * ch / max(channels, 1)
|
||||
value = amplitude * math.sin(2 * math.pi * frequency_hz * t + phase)
|
||||
values.append(value)
|
||||
return struct.pack(f"<{len(values)}f", *values)
|
||||
|
||||
|
||||
def cobs_encode(payload: bytes) -> bytes:
|
||||
if not payload:
|
||||
return b"\x01"
|
||||
|
||||
out = bytearray([0])
|
||||
code_index = 0
|
||||
code = 1
|
||||
for byte in payload:
|
||||
if byte == 0:
|
||||
out[code_index] = code
|
||||
code_index = len(out)
|
||||
out.append(0)
|
||||
code = 1
|
||||
else:
|
||||
out.append(byte)
|
||||
code += 1
|
||||
if code == 0xFF:
|
||||
out[code_index] = code
|
||||
code_index = len(out)
|
||||
out.append(0)
|
||||
code = 1
|
||||
out[code_index] = code
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def build_plot_packet(
|
||||
payload: bytes,
|
||||
channels: int,
|
||||
samples_per_channel: int,
|
||||
plot_format: str,
|
||||
) -> bytes:
|
||||
format_id = {"interleaved": 0, "block": 1, "xy": 2}[plot_format]
|
||||
header = bytearray()
|
||||
header.extend(b"XP")
|
||||
header.append(1) # version
|
||||
header.append(format_id)
|
||||
header.append(8) # f32
|
||||
header.append(0) # little-endian
|
||||
header.append(channels)
|
||||
header.extend(struct.pack("<H", samples_per_channel))
|
||||
header.extend(struct.pack("<I", len(payload)))
|
||||
header.extend(payload)
|
||||
return bytes(header)
|
||||
|
||||
|
||||
def build_mixed_plot_frame(
|
||||
payload: bytes,
|
||||
channels: int,
|
||||
samples_per_channel: int,
|
||||
plot_format: str,
|
||||
) -> bytes:
|
||||
packet = build_plot_packet(
|
||||
payload=payload,
|
||||
channels=channels,
|
||||
samples_per_channel=samples_per_channel,
|
||||
plot_format=plot_format,
|
||||
)
|
||||
return bytes([PLOT_ESCAPE, PLOT_MARKER]) + cobs_encode(packet) + b"\x00"
|
||||
|
||||
|
||||
def is_disconnect_error(err: OSError) -> bool:
|
||||
return isinstance(
|
||||
err,
|
||||
(
|
||||
BrokenPipeError,
|
||||
ConnectionAbortedError,
|
||||
ConnectionResetError,
|
||||
),
|
||||
) or getattr(err, "winerror", None) in DISCONNECT_WINERRORS
|
||||
import pv_protocol
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -217,8 +112,8 @@ def main() -> None:
|
||||
else:
|
||||
print(" framer = Line")
|
||||
print(" decoder = Text")
|
||||
print(" lua test = Lua framer tests/lua_line_framer.lua")
|
||||
print(" Lua decoder tests/lua_text_decoder.lua\n")
|
||||
print(" lua test = Lua framer crates/pipeview-client/tests/fixtures/lua_line_framer.lua")
|
||||
print(" Lua decoder crates/pipeview-client/tests/fixtures/lua_text_decoder.lua\n")
|
||||
if args.wire_format == "text":
|
||||
print(f"sending text only at {args.text_interval:.3f}s intervals")
|
||||
print(f"sample clock: {args.rate:.1f} samples/sec\n")
|
||||
@@ -268,7 +163,7 @@ def main() -> None:
|
||||
next_text_at += args.text_interval
|
||||
continue
|
||||
|
||||
plot_payload = build_frame(
|
||||
plot_payload = pv_protocol.build_plot_payload(
|
||||
sample_index=sample_index,
|
||||
channels=args.channels,
|
||||
plot_format=args.format,
|
||||
@@ -278,7 +173,7 @@ def main() -> None:
|
||||
sample_rate_hz=args.rate,
|
||||
)
|
||||
if args.wire_format == "mixed":
|
||||
frame = build_mixed_plot_frame(
|
||||
frame = pv_protocol.build_mixed_plot_frame(
|
||||
payload=plot_payload,
|
||||
channels=args.channels,
|
||||
samples_per_channel=samples_per_channel,
|
||||
@@ -308,7 +203,7 @@ def main() -> None:
|
||||
if wait > 0:
|
||||
time.sleep(wait)
|
||||
except OSError as err:
|
||||
if not is_disconnect_error(err):
|
||||
if not pv_protocol.is_disconnect_error(err):
|
||||
raise
|
||||
if args.wire_format == "text":
|
||||
print(f"[conn -] {addr} ({text_count} text lines)")
|
||||
|
||||
Reference in New Issue
Block a user