Files
xserial/tools/gen_plot.py
FallenSigh 60c60a8044 feat: initial xserial-core with transport, protocol, frame, and pipeline layers
- transport: async Serial/TCP/UDP via Connection enum + Transport trait
- protocol: TextDecoder (UTF-8/Latin-1/ASCII), HexDecoder, PlotDecoder (binary samples)
- frame: LineFramer, FixedLengthFramer, LengthPrefixedFramer, CobsFramer
- pipeline: MultiPipeline for parallel multi-protocol parsing
- 212 tests (182 unit + 18 integration + 1 doctest), clippy clean
2026-06-07 19:20:19 +08:00

113 lines
4.0 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
SerialPlot 协议测试数据生成器
协议格式:
[Sync: AA 55] [Size: 2B LE] [Payload: N×2B i16 LE] [Checksum: 1B LSB sum of payload]
用法:
python3 gen_plot.py /dev/pts/X --channels 2 --wave sine
python3 gen_plot.py /dev/pts/X --channels 2 --wave sine --freq 0.3 # 慢波,易观察
python3 gen_plot.py /dev/pts/X --channels 3 --wave saw --freq 0.2
"""
import struct
import sys
import time
import math
import argparse
def checksum(data: bytes) -> int:
return sum(data) & 0xFF
def make_frame(channel_data: list[list[int]]) -> bytes:
num_channels = len(channel_data)
num_samples = len(channel_data[0])
payload = bytearray()
for i in range(num_samples):
for ch in range(num_channels):
payload.extend(struct.pack('<h', channel_data[ch][i]))
frame = bytearray([0xAA, 0x55])
frame.append(len(payload) & 0xFF)
frame.append((len(payload) >> 8) & 0xFF)
frame.extend(payload)
frame.append(checksum(payload))
return bytes(frame)
def generate_wave(
num_channels: int,
num_samples: int,
wave_type: str,
freq: float,
amplitude: int,
) -> list[list[int]]:
channels = []
for ch in range(num_channels):
phase_offset = ch * (2 * math.pi / num_channels)
samples = []
for i in range(num_samples):
t = i / num_samples
if wave_type == 'sine':
angle = 2 * math.pi * t * freq + phase_offset
value = math.sin(angle)
elif wave_type == 'saw':
value = 2 * ((t * freq) % 1.0) - 1.0
elif wave_type == 'square':
value = 1.0 if ((t * freq) % 2.0) < 1.0 else -1.0
else:
value = 0.0
samples.append(int(value * amplitude))
channels.append(samples)
return channels
def main():
parser = argparse.ArgumentParser(description='SerialPlot test data generator')
parser.add_argument('port', help='Serial port or file (e.g. /dev/pts/3)')
parser.add_argument('--channels', type=int, default=2, help='Number of channels')
parser.add_argument('--samples', type=int, default=200, help='Samples per frame')
parser.add_argument('--wave', choices=['sine', 'saw', 'square'], default='sine')
parser.add_argument('--freq', type=float, default=0.5,
help='Wave frequency (cycles per frame; 0.5 = half a sine wave, easy to observe)')
parser.add_argument('--amplitude', type=int, default=8000,
help='Amplitude (i16 range, max 32767)')
parser.add_argument('--interval', type=float, default=0.05,
help='Interval between frames (seconds)')
parser.add_argument('--frames', type=int, default=0,
help='Number of frames (0=infinite)')
args = parser.parse_args()
count = 0
try:
with open(args.port, 'wb') as f:
print(f"Generating {args.channels}-channel {args.wave} wave → {args.port}")
print(f" {args.samples} samples/frame, freq={args.freq} cycles/frame")
print(f" amplitude={args.amplitude}, interval={args.interval}s")
print(f" Protocol: sync=AA55, i16 LE, 1B size+checksum")
print()
while args.frames == 0 or count < args.frames:
count += 1
data = generate_wave(args.channels, args.samples, args.wave, args.freq, args.amplitude)
frame = make_frame(data)
f.write(frame)
f.flush()
vals = [data[ch][0] for ch in range(args.channels)]
vals_str = ", ".join(f"ch{ch}={v:+6d}" for ch, v in enumerate(vals))
print(f" Frame #{count}: {vals_str} ... ({len(frame)} bytes)", end='\r')
time.sleep(args.interval)
except KeyboardInterrupt:
print(f"\nDone. {count} frames sent.")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()