lr-shuttle 0.2.9__py3-none-any.whl → 0.2.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lr-shuttle might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lr-shuttle
3
- Version: 0.2.9
3
+ Version: 0.2.10
4
4
  Summary: CLI and Python client for host-side of json based serial communication with embedded device bridge.
5
5
  Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
6
6
  License: MIT
@@ -1,18 +1,18 @@
1
- shuttle/cli.py,sha256=3RkIJ4Z7RQiGObPTC_f4V2JqCTlwAaA_0PutqnYrwGU,109676
1
+ shuttle/cli.py,sha256=-Lb5d3dwmJ0a2pFC9vrApQPx92x4Vw68OAaab6fawp0,110024
2
2
  shuttle/constants.py,sha256=GUlAg3iEuPxLQ2mDCvlv5gVXHnlawl_YeLtaUSqsnPM,757
3
3
  shuttle/flash.py,sha256=9ph23MHL40SjKZoL38Sbd3JbykGb-ECxvzBzCIjAues,4492
4
4
  shuttle/prodtest.py,sha256=nI8k2OndhqsOv8BMtXwfcpGEdmHU7ywbIgMuW49EULU,8006
5
- shuttle/serial_client.py,sha256=Wkkih00yt4M97S-P5kW06a2bY1fdTxafNGsf3G9Hx2Y,20408
5
+ shuttle/serial_client.py,sha256=8VPLP2nrbXIb34pWsnuXBNJ4K82bkdy3H9QWD7n0TVk,21147
6
6
  shuttle/timo.py,sha256=SfWgiYUtPjSsUln5hgDLiYMYOt8zg1DLL5t07sgu2wY,18336
7
7
  shuttle/firmware/__init__.py,sha256=KRXyz3xJ2GIB473tCHAky3DdPIQb78gX64Qn-uu55To,120
8
8
  shuttle/firmware/esp32c5/__init__.py,sha256=U2xXnb80Wv8EJaJ6Tv9iev1mVlpoaEeqsNmjmEtxdFQ,41
9
9
  shuttle/firmware/esp32c5/boot_app0.bin,sha256=-UxdeGp6j6sGrF0Q4zvzdxGmaXY23AN1WeoZzEEKF_A,8192
10
- shuttle/firmware/esp32c5/devboard.ino.bin,sha256=HIS-3dQ_1BH0F-l0LJdkwVuFm8lG3IlYn2aHkEt0Y0g,1101248
10
+ shuttle/firmware/esp32c5/devboard.ino.bin,sha256=OIGcK7ZFonpK67YaYfQLzxCPzIikrj-KfgvgeJ5jGe8,1101248
11
11
  shuttle/firmware/esp32c5/devboard.ino.bootloader.bin,sha256=LPU51SdUwebYemCZb5Pya-wGe7RC4UXrkRmBnsHePp0,20784
12
12
  shuttle/firmware/esp32c5/devboard.ino.partitions.bin,sha256=FIuVnL_xw4qo4dXAup1hLFSZe5ReVqY_QSI-72UGU6E,3072
13
13
  shuttle/firmware/esp32c5/manifest.json,sha256=CPOegfEK4PTtI6UPeohuUKkJNeg0t8aWntEczpoxYt4,480
14
- lr_shuttle-0.2.9.dist-info/METADATA,sha256=vYewsrk-Da5kQrd8M5NRHe_vwbpdhThZ0V1E7fbLL90,15574
15
- lr_shuttle-0.2.9.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
- lr_shuttle-0.2.9.dist-info/entry_points.txt,sha256=obqdFPgvQLB1_EWcnD9ch8HjQRlNVT_pdB_EidDRDco,44
17
- lr_shuttle-0.2.9.dist-info/top_level.txt,sha256=PtNxNQQdya-Xs8DYublNTBTa8c1TrtfEpQ0lUd_OeZY,8
18
- lr_shuttle-0.2.9.dist-info/RECORD,,
14
+ lr_shuttle-0.2.10.dist-info/METADATA,sha256=iP9luZKiBfh8p52Ki419v-KB24CM54sbkjuerFim0hM,15575
15
+ lr_shuttle-0.2.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
+ lr_shuttle-0.2.10.dist-info/entry_points.txt,sha256=obqdFPgvQLB1_EWcnD9ch8HjQRlNVT_pdB_EidDRDco,44
17
+ lr_shuttle-0.2.10.dist-info/top_level.txt,sha256=PtNxNQQdya-Xs8DYublNTBTa8c1TrtfEpQ0lUd_OeZY,8
18
+ lr_shuttle-0.2.10.dist-info/RECORD,,
shuttle/cli.py CHANGED
@@ -151,6 +151,19 @@ def _handle_sys_error_event(event: Dict[str, Any]) -> None:
151
151
  console.print(f"[red]{' '.join(parts)}[/]")
152
152
 
153
153
 
154
+ def _flush_client_input(client) -> None:
155
+ """Best-effort drain any unread serial noise before issuing commands."""
156
+
157
+ flush = getattr(client, "flush_input_and_log", None)
158
+ if flush is None:
159
+ return
160
+ try:
161
+ flush()
162
+ except Exception:
163
+ # Flushing is opportunistic; failures should not leak into CLI flows.
164
+ pass
165
+
166
+
154
167
  @contextmanager
155
168
  def _open_serial_client(
156
169
  resolved_port: str,
@@ -769,7 +782,7 @@ def _execute_timo_sequence(
769
782
  ) as client:
770
783
  # Drain any pending serial noise before issuing commands, to avoid
771
784
  # mixing stale data into NDJSON responses.
772
- client.flush_input_and_log()
785
+ _flush_client_input(client)
773
786
  for transfer in sequence:
774
787
  response = client.spi_xfer(**transfer)
775
788
  responses.append(response)
@@ -2637,7 +2650,7 @@ def spi_enable_command(
2637
2650
  logger=resources.get("logger"),
2638
2651
  seq_tracker=resources.get("seq_tracker"),
2639
2652
  ) as client:
2640
- client.flush_input_and_log()
2653
+ _flush_client_input(client)
2641
2654
  response = client.spi_enable()
2642
2655
  except ShuttleSerialError as exc:
2643
2656
  console.print(f"[red]{exc}[/]")
@@ -2672,7 +2685,7 @@ def spi_disable_command(
2672
2685
  logger=resources.get("logger"),
2673
2686
  seq_tracker=resources.get("seq_tracker"),
2674
2687
  ) as client:
2675
- client.flush_input_and_log()
2688
+ _flush_client_input(client)
2676
2689
  response = client.spi_disable()
2677
2690
  except ShuttleSerialError as exc:
2678
2691
  console.print(f"[red]{exc}[/]")
@@ -3143,7 +3156,7 @@ def power_command(
3143
3156
  logger=resources.get("logger"),
3144
3157
  seq_tracker=resources.get("seq_tracker"),
3145
3158
  ) as client:
3146
- client.flush_input_and_log()
3159
+ _flush_client_input(client)
3147
3160
  method = getattr(client, method_name)
3148
3161
  response = method()
3149
3162
  except ShuttleSerialError as exc:
@@ -3219,7 +3232,7 @@ def flash_command(
3219
3232
  with NDJSONSerialClient(
3220
3233
  resolved_port, baudrate=baudrate, timeout=0.5, logger=logger
3221
3234
  ) as client:
3222
- client.flush_input_and_log()
3235
+ _flush_client_input(client)
3223
3236
  except Exception:
3224
3237
  pass
3225
3238
 
Binary file
shuttle/serial_client.py CHANGED
@@ -253,7 +253,7 @@ class NDJSONSerialClient:
253
253
  except AttributeError:
254
254
  # Test stubs without an open() method are already "connected"
255
255
  pass
256
- self._serial.reset_input_buffer()
256
+ self._reset_input_buffer()
257
257
  self._lock = threading.Lock()
258
258
  self._pending: Dict[int, CommandFuture] = {}
259
259
  self._response_backlog: Dict[int, Dict[str, Any]] = {}
@@ -281,9 +281,21 @@ class NDJSONSerialClient:
281
281
  if getattr(self, "_serial", None) and self._serial.is_open:
282
282
  self._serial.close()
283
283
 
284
+ def _reset_input_buffer(self) -> None:
285
+ serial_obj = getattr(self, "_serial", None)
286
+ if serial_obj is None:
287
+ return
288
+ reset = getattr(serial_obj, "reset_input_buffer", None)
289
+ if reset is None:
290
+ return
291
+ try:
292
+ reset()
293
+ except SerialException:
294
+ pass
295
+
284
296
  def flush_input_and_log(self):
285
297
  """Read and log all available data from the serial buffer before sending a command."""
286
- if not hasattr(self, "_serial") or not getattr(self._serial, "in_waiting", 0):
298
+ if not hasattr(self, "_serial"):
287
299
  return
288
300
  try:
289
301
  while True:
@@ -295,6 +307,8 @@ class NDJSONSerialClient:
295
307
  self._log_serial("RX", data)
296
308
  except Exception:
297
309
  pass
310
+ finally:
311
+ self._reset_input_buffer()
298
312
 
299
313
  def send_command(self, op: str, params: Dict[str, Any]) -> CommandFuture:
300
314
  """Send a command without blocking, returning a future for the response."""
@@ -475,28 +489,37 @@ class NDJSONSerialClient:
475
489
  self._log_serial("TX", payload)
476
490
 
477
491
  def _read(self) -> Optional[Dict[str, Any]]:
478
- try:
479
- line = self._serial.readline()
480
- except SerialException as exc: # pragma: no cover - hardware specific
481
- raise ShuttleSerialError(f"Serial read failed: {exc}") from exc
482
- if not line:
483
- return None
484
- self._log_serial("RX", line)
485
- stripped = line.strip()
486
- if not stripped:
487
- return None
488
- try:
489
- decoded = stripped.decode("utf-8")
490
- except UnicodeDecodeError as exc:
491
- raise ShuttleSerialError(f"Invalid UTF-8 from device: {exc}") from exc
492
- try:
493
- message = json.loads(decoded)
494
- except json.JSONDecodeError as exc:
495
- raise ShuttleSerialError(
496
- f"Invalid JSON from device: {decoded} ({exc})"
497
- ) from exc
498
- self._record_sequence(message)
499
- return message
492
+ while True:
493
+ try:
494
+ line = self._serial.readline()
495
+ except SerialException as exc: # pragma: no cover - hardware specific
496
+ raise ShuttleSerialError(f"Serial read failed: {exc}") from exc
497
+ if not line:
498
+ return None
499
+ self._log_serial("RX", line)
500
+ stripped = line.strip()
501
+ if not stripped:
502
+ return None
503
+ try:
504
+ decoded = stripped.decode("utf-8")
505
+ except UnicodeDecodeError as exc:
506
+ self._reset_input_buffer()
507
+ raise ShuttleSerialError(f"Invalid UTF-8 from device: {exc}") from exc
508
+ trimmed = decoded.lstrip()
509
+ if not trimmed:
510
+ continue
511
+ if trimmed[0] not in ("{", "["):
512
+ self._reset_input_buffer()
513
+ continue
514
+ try:
515
+ message = json.loads(decoded)
516
+ except json.JSONDecodeError as exc:
517
+ self._reset_input_buffer()
518
+ raise ShuttleSerialError(
519
+ f"Invalid JSON from device: {decoded} ({exc})"
520
+ ) from exc
521
+ self._record_sequence(message)
522
+ return message
500
523
 
501
524
  def _dispatch(self, message: Dict[str, Any]) -> None:
502
525
  mtype = message.get("type")