From 8b1d424cbe7d58964d7eed5a6653245a45d0815a Mon Sep 17 00:00:00 2001 From: Theodor Chikin Date: Tue, 17 Feb 2026 18:51:12 +0300 Subject: [PATCH] New tty parser: accepts binary format. Enable arg: --bin --- calib_envelope.npy | Bin 3164 -> 3164 bytes rfg_adc_plotter/gui/matplotlib_backend.py | 9 +- rfg_adc_plotter/gui/pyqtgraph_backend.py | 9 +- rfg_adc_plotter/io/sweep_reader.py | 212 +++++++++++++++------- rfg_adc_plotter/main.py | 6 + 5 files changed, 171 insertions(+), 65 deletions(-) mode change 100644 => 100755 rfg_adc_plotter/main.py diff --git a/calib_envelope.npy b/calib_envelope.npy index 9053d4f604ebba00a6d93003e6ac2ff91ef38a58..abc000b6ba0143bb2284f1bb9f8df48da3b3e3db 100644 GIT binary patch literal 3164 zcmeH{PiP!v7{*5wu|XjqEtd8$m$17Y0XilM05xn?&W=6ZaMMXWDg&*^N&-c9V z_kHudvsa$;o}D^vPl9S;oUpj!%p{HsZeO{_Ed>}{dch61OJmASmK;jn1fQ=g88<2i}~66<~091 zo`?4o<081=K&NF+0(xqU?j6K8kk9{sejPTU2|kpd*s{jymW|b0w)J+)${(U7eJE!g z)}RSjVHw^8zr~(g?47to>=BgX(5Ki#`>eb639 z;Q>&t%M(vpcNH&Z>whO{UK2gZeCe2+bieXzZVlF96Xfeaevg0L?=NVB+~+V9xnK1G zJ=6bx5ww}x*1ZSj-Nv&^+(A=35U^)-FJQ043GpiNJaMs?$>#%GnF;L1g}{d13aoN1 zu#G6oQJ$Fm4$lsrpYa?Bt#Tx^jmJZ3h4ewlI>e>WwiZHbTnw%JMrdPK(3-FY>#zw4 z?13BEjYE-bJQUf=eUVju4Q=vPXhT0T*XhOEW{q#q$W7y2VkhDq0d<^FD8dw!U=FIV z1pZ%emAQ5J8aj9IZb!VonA)*z9gVGVIJWYk*v1}48$^2$?Eu>SX!oKySu9;1`IW1u z#?s}LuN&`RY{4CtQ1T4~QD#Oj?UECV@d&z170#?qNrp7K>k^;B2&|8^a6)B1n%TzQ{bEow&8 z{)9C?W34a9N$w{2JbVAzv+mLRAg`@6$bTI=|B*fN)mQ3E7u2Ukn1T|V1Zj@|K7AYI zNBK@B_(q;VlSf|V$mhZ!;O+iaf)k9#GmpL>Zz%T;XPR@Ir)sEH398`dPmQH(eiYn1 qmhQ*gOU(Vm+*8bb#oSxW{V~^s<;;`T!rQpW8S3nPfpe6ZqsDJwtg3nd literal 3164 zcmbW3e~4676vwYW(Zy=}y@=Id+HT951J7TH)SLIPRQj=mFH(YL$sI&YhusP>ilI_A zu^L^CouOce%w$cvHx{nud3AC5`QbB%e@HKi@E!!?nNYf}4N6Yphw$u;)l z*zZApldOW+fC3;DIKH@q7CqwpZy4~thfF63KT{~U6T7+aASB3IF`4Pz?C3DEwOBOS|T zcEv2KH#S(=6H6rCLf6N-cS8SubSJQ##%}_(H&R~&F5;)X?|~Hlo%pXpj{5V!rkcn0 ztUane)tQDq$bn+WwgnoY1#FlD(_qBL4_-yCLKz0gl_%#uycR z1CGI2m_Z#iM#Yrh0Vw)$3;Es1TJtg}_jc&1BgwoSq99o{$u|y5pb>`Hdy4+=usv^Y zSc~crC8&Z4eIw+aOKq*pqoF+0wHlwbB5}sHtgphgT0e$jM8Y_d6}Mz(GauuB z&>pNDVD0Pur~0Ozfc{<--Bgc!0(*Dm%z8?+k$VWevFgN ztmJIDoS$W!qea-FumIGbWnfe92H1g|gDmJw={u?4tDy;s$^kkLTC4BU&${FE>3if$ za1!(l^{s4J2l^g5)BP|2MJR#3c^b5zdfL$?iM7{$UjVe-3Q|Y`f6y`d>TxIOXx0#KAKPF?VHFOI04x(PG_#SZx-AJ zx5FKvx8hc~7N&sWOk!++>!FEtGg+@S@|_7cf&7(wDoh6DH25~)do%KtzS>VA|9iOi zU&H-se_B7zVC6FB_-FcjhJK&IPd!vGRQIUg3(xxrSv?vJ^|H%PeH^Ed|CavASG`hy zRNqCY`TC%*iXokRRFB@BYd|$#2YORgtL)0Def$;aWRs72r#$CDd;O6-I{(_=4{#dv zEo-4|7`cqBd-4f#5spIzbUu{lJJ5Qa3)Lu}9Q1(h;U3Vv)LW}}_b_q^4naS>4LZkr zu?K$j_gMEx_bIp^$+~ep^EkAF4N-`|5|B>z`rnuqbn&EzeCDsSKk|83P9YFXXrY>0z=lqVOy zuiJflHnRqueEutnlYm~1`84!xpk7BndE}>aq5o-gr$>+npa*P_kEz3Eo`T#}(QlA`W7M}Ed0VeU* z&EP)XLR{tlXSn~=(tw{~JWCIT$yWlci$fapwra0>Yg3?}B*Rhju{T(<^jg;rHbnmw z&znc?R{UD%zkai4BS*q@`c2ch(f?l2P+pF#_$xp;mOzyE=T5)wY-YOu+Zyz>MlA-r gzDpxWuA;k=p#EQcf5ot@8|mdNV&26X)nQ_P0u2rk9smFU diff --git a/rfg_adc_plotter/gui/matplotlib_backend.py b/rfg_adc_plotter/gui/matplotlib_backend.py index 19e859a..b768553 100644 --- a/rfg_adc_plotter/gui/matplotlib_backend.py +++ b/rfg_adc_plotter/gui/matplotlib_backend.py @@ -89,7 +89,14 @@ def run_matplotlib(args): q: Queue[SweepPacket] = Queue(maxsize=1000) stop_event = threading.Event() - reader = SweepReader(args.port, args.baud, q, stop_event, fancy=bool(args.fancy)) + reader = SweepReader( + args.port, + args.baud, + q, + stop_event, + fancy=bool(args.fancy), + bin_mode=bool(getattr(args, "bin_mode", False)), + ) reader.start() max_sweeps = int(max(10, args.max_sweeps)) diff --git a/rfg_adc_plotter/gui/pyqtgraph_backend.py b/rfg_adc_plotter/gui/pyqtgraph_backend.py index 9ac4174..a9069b5 100644 --- a/rfg_adc_plotter/gui/pyqtgraph_backend.py +++ b/rfg_adc_plotter/gui/pyqtgraph_backend.py @@ -106,7 +106,14 @@ def run_pyqtgraph(args): q: Queue[SweepPacket] = Queue(maxsize=1000) stop_event = threading.Event() - reader = SweepReader(args.port, args.baud, q, stop_event, fancy=bool(args.fancy)) + reader = SweepReader( + args.port, + args.baud, + q, + stop_event, + fancy=bool(args.fancy), + bin_mode=bool(getattr(args, "bin_mode", False)), + ) reader.start() max_sweeps = int(max(10, args.max_sweeps)) diff --git a/rfg_adc_plotter/io/sweep_reader.py b/rfg_adc_plotter/io/sweep_reader.py index 31768a6..674cd4b 100644 --- a/rfg_adc_plotter/io/sweep_reader.py +++ b/rfg_adc_plotter/io/sweep_reader.py @@ -24,6 +24,7 @@ class SweepReader(threading.Thread): out_queue: "Queue[SweepPacket]", stop_event: threading.Event, fancy: bool = False, + bin_mode: bool = False, ): super().__init__(daemon=True) self._port_path = port_path @@ -32,11 +33,17 @@ class SweepReader(threading.Thread): self._stop = stop_event self._src: Optional[SerialLineSource] = None self._fancy = bool(fancy) + self._bin_mode = bool(bin_mode) self._max_width: int = 0 self._sweep_idx: int = 0 self._last_sweep_ts: Optional[float] = None self._n_valid_hist = deque() + @staticmethod + def _u16_to_i16(v: int) -> int: + """Преобразование 16-bit слова в знаковое значение.""" + return v - 0x10000 if (v & 0x8000) else v + def _finalize_current(self, xs, ys, channels: Optional[set]): if not xs: return @@ -135,11 +142,145 @@ class SweepReader(threading.Thread): except Exception: pass - def run(self): - xs: list = [] - ys: list = [] + def _run_ascii_stream(self, chunk_reader: SerialChunkReader): + xs: list[int] = [] + ys: list[int] = [] cur_channel: Optional[int] = None - cur_channels: set = set() + cur_channels: set[int] = set() + + buf = bytearray() + while not self._stop.is_set(): + data = chunk_reader.read_available() + if data: + buf += data + else: + time.sleep(0.0005) + continue + + while True: + nl = buf.find(b"\n") + if nl == -1: + break + line = bytes(buf[:nl]) + del buf[: nl + 1] + if line.endswith(b"\r"): + line = line[:-1] + if not line: + continue + + if line.startswith(b"Sweep_start"): + self._finalize_current(xs, ys, cur_channels) + xs.clear() + ys.clear() + cur_channel = None + cur_channels.clear() + continue + + if len(line) >= 3: + parts = line.split() + if len(parts) >= 3 and (parts[0].lower() == b"s" or parts[0].lower().startswith(b"s")): + try: + if parts[0].lower() == b"s": + if len(parts) >= 4: + ch = int(parts[1], 10) + x = int(parts[2], 10) + y = int(parts[3], 10) + else: + ch = 0 + x = int(parts[1], 10) + y = int(parts[2], 10) + else: + ch = int(parts[0][1:], 10) + x = int(parts[1], 10) + y = int(parts[2], 10) + except Exception: + continue + if cur_channel is None: + cur_channel = ch + cur_channels.add(ch) + xs.append(x) + ys.append(y) + + if len(buf) > 1_000_000: + del buf[:-262144] + + self._finalize_current(xs, ys, cur_channels) + + def _run_binary_stream(self, chunk_reader: SerialChunkReader): + xs: list[int] = [] + ys: list[int] = [] + cur_channel: Optional[int] = None + cur_channels: set[int] = set() + waiting_channel = False + waiting_first_point = False + point_word: Optional[int] = None + value_word: Optional[int] = None + + buf = bytearray() + while not self._stop.is_set(): + data = chunk_reader.read_available() + if data: + buf += data + else: + time.sleep(0.0005) + continue + + usable = len(buf) & ~1 + if usable == 0: + continue + + i = 0 + while i < usable: + w = int(buf[i]) | (int(buf[i + 1]) << 8) + i += 2 + + if waiting_channel: + cur_channel = int(w) + cur_channels.add(cur_channel) + waiting_channel = False + waiting_first_point = True + continue + + if w == 0xFFFF: + self._finalize_current(xs, ys, cur_channels) + xs.clear() + ys.clear() + cur_channel = None + cur_channels.clear() + waiting_channel = True + waiting_first_point = False + point_word = None + value_word = None + continue + + if point_word is None: + if waiting_first_point and (w == 0x0A0A or w == 0x000A): + continue + point_word = int(w) + waiting_first_point = False + continue + + if value_word is None: + value_word = int(w) + continue + + is_point_end = (w == 0x000A) or ((w & 0x00FF) == 0x0A) or ((w >> 8) == 0x0A) + if is_point_end: + if cur_channel is not None: + cur_channels.add(cur_channel) + xs.append(point_word) + ys.append(self._u16_to_i16(value_word)) + + point_word = None + value_word = None + + del buf[:usable] + if len(buf) > 1_000_000: + del buf[:-262144] + + self._finalize_current(xs, ys, cur_channels) + + def run(self): try: self._src = SerialLineSource(self._port_path, self._baud, timeout=1.0) @@ -150,66 +291,11 @@ class SweepReader(threading.Thread): try: chunk_reader = SerialChunkReader(self._src) - buf = bytearray() - while not self._stop.is_set(): - data = chunk_reader.read_available() - if data: - buf += data - else: - time.sleep(0.0005) - continue - - while True: - nl = buf.find(b"\n") - if nl == -1: - break - line = bytes(buf[:nl]) - del buf[: nl + 1] - if line.endswith(b"\r"): - line = line[:-1] - if not line: - continue - - if line.startswith(b"Sweep_start"): - self._finalize_current(xs, ys, cur_channels) - xs.clear() - ys.clear() - cur_channel = None - cur_channels.clear() - continue - - if len(line) >= 3: - parts = line.split() - if len(parts) >= 3 and (parts[0].lower() == b"s" or parts[0].lower().startswith(b"s")): - try: - if parts[0].lower() == b"s": - if len(parts) >= 4: - ch = int(parts[1], 10) - x = int(parts[2], 10) - y = int(parts[3], 10) - else: - ch = 0 - x = int(parts[1], 10) - y = int(parts[2], 10) - else: - ch = int(parts[0][1:], 10) - x = int(parts[1], 10) - y = int(parts[2], 10) - except Exception: - continue - if cur_channel is None: - cur_channel = ch - cur_channels.add(ch) - xs.append(x) - ys.append(y) - - if len(buf) > 1_000_000: - del buf[:-262144] + if self._bin_mode: + self._run_binary_stream(chunk_reader) + else: + self._run_ascii_stream(chunk_reader) finally: - try: - self._finalize_current(xs, ys, cur_channels) - except Exception: - pass try: if self._src is not None: self._src.close() diff --git a/rfg_adc_plotter/main.py b/rfg_adc_plotter/main.py old mode 100644 new mode 100755 index 78e4e02..85d4df7 --- a/rfg_adc_plotter/main.py +++ b/rfg_adc_plotter/main.py @@ -77,6 +77,12 @@ def build_parser() -> argparse.ArgumentParser: default="projector", help="Тип нормировки: projector (по огибающим в [-1000,+1000]) или simple (raw/calib)", ) + parser.add_argument( + "--bin", + dest="bin_mode", + action="store_true", + help="Бинарный протокол: 16-bit поток, 0xFFFF+канал для старта свипа, точки point,value,'\\n'", + ) return parser