Improve GUI configuration and plotting tooling
This commit is contained in:
@@ -10,6 +10,11 @@ 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(
|
||||
@@ -98,44 +103,56 @@ def build_mixed_plot_frame(
|
||||
)
|
||||
return bytes([PLOT_ESCAPE, PLOT_MARKER]) + cobs_encode(packet) + b"\x00"
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="xserial plot test server")
|
||||
p.add_argument("--host", default="127.0.0.1")
|
||||
p.add_argument("--port", type=int, default=8080)
|
||||
p.add_argument("--channels", type=int, default=1)
|
||||
p.add_argument(
|
||||
|
||||
def is_disconnect_error(err: OSError) -> bool:
|
||||
return isinstance(
|
||||
err,
|
||||
(
|
||||
BrokenPipeError,
|
||||
ConnectionAbortedError,
|
||||
ConnectionResetError,
|
||||
),
|
||||
) or getattr(err, "winerror", None) in DISCONNECT_WINERRORS
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="xserial plot test server")
|
||||
parser.add_argument("--host", default="127.0.0.1")
|
||||
parser.add_argument("--port", type=int, default=8091)
|
||||
parser.add_argument("--channels", type=int, default=1)
|
||||
parser.add_argument(
|
||||
"--format",
|
||||
choices=("interleaved", "xy"),
|
||||
default="interleaved",
|
||||
help="plot format (default: interleaved)",
|
||||
)
|
||||
p.add_argument(
|
||||
parser.add_argument(
|
||||
"--rate",
|
||||
type=float,
|
||||
default=1024,
|
||||
help="samples/sec per channel (default: 1024)",
|
||||
)
|
||||
p.add_argument("--freq", type=float, default=1.0, help="sine Hz")
|
||||
p.add_argument("--amp", type=float, default=100)
|
||||
p.add_argument(
|
||||
parser.add_argument("--freq", type=float, default=1.0, help="sine Hz")
|
||||
parser.add_argument("--amp", type=float, default=100)
|
||||
parser.add_argument(
|
||||
"--text-interval",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="seconds between status text messages (default: 1.0)",
|
||||
)
|
||||
p.add_argument(
|
||||
parser.add_argument(
|
||||
"--framelen",
|
||||
type=int,
|
||||
default=0,
|
||||
help="fixed frame bytes (0=one sample per channel per frame)",
|
||||
)
|
||||
p.add_argument(
|
||||
parser.add_argument(
|
||||
"--wire-format",
|
||||
choices=("mixed", "raw"),
|
||||
default="mixed",
|
||||
help="mixed sends text+plot on one connection; raw sends plot only",
|
||||
)
|
||||
args = p.parse_args()
|
||||
args = parser.parse_args()
|
||||
|
||||
sample_size = 4 # f32
|
||||
|
||||
@@ -176,7 +193,7 @@ def main():
|
||||
values.append(args.amp * math.sin(2 * math.pi * args.freq * t + phase))
|
||||
return values
|
||||
|
||||
print(f"Listening {args.host}:{args.port} — Ctrl+C to stop")
|
||||
print(f"Listening {args.host}:{args.port} - Ctrl+C to stop")
|
||||
print("GUI:")
|
||||
print(f" transport = TCP {args.host}:{args.port}")
|
||||
if args.wire_format == "mixed":
|
||||
@@ -241,8 +258,8 @@ def main():
|
||||
for index, value in enumerate(values)
|
||||
)
|
||||
line = (
|
||||
f"时间={now - started_at:7.3f}s "
|
||||
f"样本={sample_index:7d} {joined}\n"
|
||||
f"time={now - started_at:7.3f}s "
|
||||
f"sample={sample_index:7d} {joined}\n"
|
||||
)
|
||||
conn.sendall(line.encode("utf-8"))
|
||||
text_count += 1
|
||||
@@ -255,8 +272,13 @@ def main():
|
||||
wait = frame_count * frame_interval - elapsed
|
||||
if wait > 0:
|
||||
time.sleep(wait)
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
print(f"[conn -] {addr} ({frame_count} plot frames, {text_count} text lines)")
|
||||
except OSError as err:
|
||||
if not is_disconnect_error(err):
|
||||
raise
|
||||
print(
|
||||
f"[conn -] {addr} "
|
||||
f"({frame_count} plot frames, {text_count} text lines)"
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
finally:
|
||||
@@ -274,5 +296,6 @@ def main():
|
||||
stop_event.set()
|
||||
stream_thread.join(timeout=1.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
237
tools/xs_mixed_plot.h
Normal file
237
tools/xs_mixed_plot.h
Normal file
@@ -0,0 +1,237 @@
|
||||
#ifndef XS_MIXED_PLOT_H
|
||||
#define XS_MIXED_PLOT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* MixedTextPlot plot frame:
|
||||
* 0x1E 'P' + COBS(packet) + 0x00
|
||||
*
|
||||
* Plot packet:
|
||||
* "XP" +
|
||||
* version:u8 +
|
||||
* format:u8 +
|
||||
* sample_type:u8 +
|
||||
* endian:u8 +
|
||||
* channels:u8 +
|
||||
* samples_per_channel:u16le +
|
||||
* payload_len:u32le +
|
||||
* payload
|
||||
*/
|
||||
|
||||
#define XS_MIXED_PLOT_ESCAPE 0x1E
|
||||
#define XS_MIXED_PLOT_MARKER 0x50 /* 'P' */
|
||||
|
||||
#define XS_PLOT_MAGIC_0 0x58 /* 'X' */
|
||||
#define XS_PLOT_MAGIC_1 0x50 /* 'P' */
|
||||
#define XS_PLOT_VERSION 1
|
||||
|
||||
#define XS_PLOT_FORMAT_INTERLEAVED 0
|
||||
#define XS_PLOT_FORMAT_BLOCK 1
|
||||
#define XS_PLOT_FORMAT_XY 2
|
||||
|
||||
#define XS_SAMPLE_TYPE_F32 8
|
||||
#define XS_ENDIAN_LITTLE 0
|
||||
|
||||
#define XS_PLOT_HEADER_SIZE 13
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
XS_OK = 0,
|
||||
XS_ERR_ARG = -1,
|
||||
XS_ERR_BUF = -2
|
||||
} xs_status_t;
|
||||
|
||||
static size_t xs_cobs_max_encoded_size(size_t input_len) {
|
||||
return input_len + (input_len / 254u) + 1u;
|
||||
}
|
||||
|
||||
static void xs_write_u16_le(uint8_t *dst, uint16_t v) {
|
||||
dst[0] = (uint8_t)(v & 0xFFu);
|
||||
dst[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||||
}
|
||||
|
||||
static void xs_write_u32_le(uint8_t *dst, uint32_t v) {
|
||||
dst[0] = (uint8_t)(v & 0xFFu);
|
||||
dst[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||||
dst[2] = (uint8_t)((v >> 16) & 0xFFu);
|
||||
dst[3] = (uint8_t)((v >> 24) & 0xFFu);
|
||||
}
|
||||
|
||||
static void xs_write_f32_le(uint8_t *dst, float v) {
|
||||
union {
|
||||
float f;
|
||||
uint8_t b[4];
|
||||
} u;
|
||||
u.f = v;
|
||||
|
||||
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
dst[0] = u.b[3];
|
||||
dst[1] = u.b[2];
|
||||
dst[2] = u.b[1];
|
||||
dst[3] = u.b[0];
|
||||
#else
|
||||
dst[0] = u.b[0];
|
||||
dst[1] = u.b[1];
|
||||
dst[2] = u.b[2];
|
||||
dst[3] = u.b[3];
|
||||
#endif
|
||||
}
|
||||
|
||||
/* output must have at least xs_cobs_max_encoded_size(input_len) bytes */
|
||||
static size_t xs_cobs_encode(const uint8_t *input, size_t input_len, uint8_t *output) {
|
||||
size_t read_index = 0;
|
||||
size_t write_index = 1;
|
||||
size_t code_index = 0;
|
||||
uint8_t code = 1;
|
||||
|
||||
while (read_index < input_len) {
|
||||
if (input[read_index] == 0) {
|
||||
output[code_index] = code;
|
||||
code = 1;
|
||||
code_index = write_index++;
|
||||
read_index++;
|
||||
} else {
|
||||
output[write_index++] = input[read_index++];
|
||||
code++;
|
||||
if (code == 0xFFu) {
|
||||
output[code_index] = code;
|
||||
code = 1;
|
||||
code_index = write_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output[code_index] = code;
|
||||
return write_index;
|
||||
}
|
||||
|
||||
static xs_status_t xs_plot_build_packet_f32(
|
||||
const float *samples,
|
||||
uint8_t channels,
|
||||
uint16_t samples_per_channel,
|
||||
uint8_t plot_format,
|
||||
uint8_t *out,
|
||||
size_t out_cap,
|
||||
size_t *out_len
|
||||
) {
|
||||
size_t i;
|
||||
uint32_t value_count;
|
||||
uint32_t payload_len;
|
||||
size_t packet_len;
|
||||
uint8_t *p;
|
||||
|
||||
if (!samples || !out || !out_len) {
|
||||
return XS_ERR_ARG;
|
||||
}
|
||||
if (channels == 0) {
|
||||
return XS_ERR_ARG;
|
||||
}
|
||||
if (plot_format == XS_PLOT_FORMAT_XY && channels != 2) {
|
||||
return XS_ERR_ARG;
|
||||
}
|
||||
if (plot_format > XS_PLOT_FORMAT_XY) {
|
||||
return XS_ERR_ARG;
|
||||
}
|
||||
|
||||
value_count = (uint32_t)channels * (uint32_t)samples_per_channel;
|
||||
payload_len = value_count * 4u;
|
||||
packet_len = XS_PLOT_HEADER_SIZE + (size_t)payload_len;
|
||||
|
||||
if (out_cap < packet_len) {
|
||||
return XS_ERR_BUF;
|
||||
}
|
||||
|
||||
out[0] = XS_PLOT_MAGIC_0;
|
||||
out[1] = XS_PLOT_MAGIC_1;
|
||||
out[2] = XS_PLOT_VERSION;
|
||||
out[3] = plot_format;
|
||||
out[4] = XS_SAMPLE_TYPE_F32;
|
||||
out[5] = XS_ENDIAN_LITTLE;
|
||||
out[6] = channels;
|
||||
xs_write_u16_le(&out[7], samples_per_channel);
|
||||
xs_write_u32_le(&out[9], payload_len);
|
||||
|
||||
p = &out[13];
|
||||
for (i = 0; i < (size_t)value_count; ++i) {
|
||||
xs_write_f32_le(p, samples[i]);
|
||||
p += 4;
|
||||
}
|
||||
|
||||
*out_len = packet_len;
|
||||
return XS_OK;
|
||||
}
|
||||
|
||||
static xs_status_t xs_mixed_build_plot_frame_from_packet(
|
||||
const uint8_t *packet,
|
||||
size_t packet_len,
|
||||
uint8_t *out,
|
||||
size_t out_cap,
|
||||
size_t *out_len
|
||||
) {
|
||||
size_t encoded_len;
|
||||
|
||||
if (!packet || !out || !out_len) {
|
||||
return XS_ERR_ARG;
|
||||
}
|
||||
if (out_cap < 2u + xs_cobs_max_encoded_size(packet_len) + 1u) {
|
||||
return XS_ERR_BUF;
|
||||
}
|
||||
|
||||
out[0] = XS_MIXED_PLOT_ESCAPE;
|
||||
out[1] = XS_MIXED_PLOT_MARKER;
|
||||
encoded_len = xs_cobs_encode(packet, packet_len, &out[2]);
|
||||
out[2 + encoded_len] = 0x00;
|
||||
|
||||
*out_len = 2u + encoded_len + 1u;
|
||||
return XS_OK;
|
||||
}
|
||||
|
||||
static xs_status_t xs_mixed_build_plot_frame_f32(
|
||||
const float *samples,
|
||||
uint8_t channels,
|
||||
uint16_t samples_per_channel,
|
||||
uint8_t plot_format,
|
||||
uint8_t *packet_buf,
|
||||
size_t packet_buf_cap,
|
||||
uint8_t *frame_buf,
|
||||
size_t frame_buf_cap,
|
||||
size_t *frame_len
|
||||
) {
|
||||
xs_status_t rc;
|
||||
size_t packet_len = 0;
|
||||
|
||||
if (!packet_buf || !frame_buf || !frame_len) {
|
||||
return XS_ERR_ARG;
|
||||
}
|
||||
|
||||
rc = xs_plot_build_packet_f32(
|
||||
samples,
|
||||
channels,
|
||||
samples_per_channel,
|
||||
plot_format,
|
||||
packet_buf,
|
||||
packet_buf_cap,
|
||||
&packet_len
|
||||
);
|
||||
if (rc != XS_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return xs_mixed_build_plot_frame_from_packet(
|
||||
packet_buf,
|
||||
packet_len,
|
||||
frame_buf,
|
||||
frame_buf_cap,
|
||||
frame_len
|
||||
);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* XS_MIXED_PLOT_H */
|
||||
Reference in New Issue
Block a user