lr-shuttle 0.2.3__py3-none-any.whl → 0.2.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lr-shuttle
3
- Version: 0.2.3
3
+ Version: 0.2.4
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=i75UW_r_v2AbVTMA1X4u_LnVf3fQz5Xc00IjbxNZwN8,93515
1
+ shuttle/cli.py,sha256=lou_JvWNoMXxoyXyz5MSIyAUZEUAe0CmXaBnl_gqLMY,93507
2
2
  shuttle/constants.py,sha256=GUlAg3iEuPxLQ2mDCvlv5gVXHnlawl_YeLtaUSqsnPM,757
3
3
  shuttle/flash.py,sha256=9ph23MHL40SjKZoL38Sbd3JbykGb-ECxvzBzCIjAues,4492
4
- shuttle/prodtest.py,sha256=1q-1dZYtrWBpI_e0jPgROVGbb_42Y0q0DIxDoo4MWUk,8020
5
- shuttle/serial_client.py,sha256=WjGanUAL16qw2RZcCjHjYMKHsk-B6zY3cMeS0gPtPHE,17650
4
+ shuttle/prodtest.py,sha256=nI8k2OndhqsOv8BMtXwfcpGEdmHU7ywbIgMuW49EULU,8006
5
+ shuttle/serial_client.py,sha256=bUpTs6MmJkpYBgtNYZZ0EYaybkLlrM7MlhWxHLQPh3U,18185
6
6
  shuttle/timo.py,sha256=1K18y0QtDF2lw2Abeok9PgrpPUiCEbQdGQXOQik75Hw,16481
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=eCkSHtcBBbLRvHQmkvXR1jRoMMqMTMEXaXMv8GMIsD8,1098256
10
+ shuttle/firmware/esp32c5/devboard.ino.bin,sha256=CxWJQYlbL7zu9nkUzBQ0PQQET4XHZKFoxMzm9tZonqk,1099040
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.3.dist-info/METADATA,sha256=_hp5-NmIdtKB2wZIQww4oPpXCe3mDHqevScN3G9uj4Y,13611
15
- lr_shuttle-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- lr_shuttle-0.2.3.dist-info/entry_points.txt,sha256=obqdFPgvQLB1_EWcnD9ch8HjQRlNVT_pdB_EidDRDco,44
17
- lr_shuttle-0.2.3.dist-info/top_level.txt,sha256=PtNxNQQdya-Xs8DYublNTBTa8c1TrtfEpQ0lUd_OeZY,8
18
- lr_shuttle-0.2.3.dist-info/RECORD,,
14
+ lr_shuttle-0.2.4.dist-info/METADATA,sha256=EEgzPGFjeZ3L04elKT8VcUkiRjm0CyUn9dAN5Cq4_IQ,13611
15
+ lr_shuttle-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ lr_shuttle-0.2.4.dist-info/entry_points.txt,sha256=obqdFPgvQLB1_EWcnD9ch8HjQRlNVT_pdB_EidDRDco,44
17
+ lr_shuttle-0.2.4.dist-info/top_level.txt,sha256=PtNxNQQdya-Xs8DYublNTBTa8c1TrtfEpQ0lUd_OeZY,8
18
+ lr_shuttle-0.2.4.dist-info/RECORD,,
shuttle/cli.py CHANGED
@@ -107,7 +107,9 @@ for entry in PRODTEST_TX_POWER_LEVELS:
107
107
  for alias in entry["aliases"]:
108
108
  PRODTEST_TX_POWER_ALIASES[alias.lower()] = entry["value"]
109
109
  PRODTEST_TX_POWER_ALIASES[str(entry["value"])] = entry["value"]
110
- PRODTEST_TX_POWER_CANONICAL = [entry["aliases"][0] for entry in PRODTEST_TX_POWER_LEVELS]
110
+ PRODTEST_TX_POWER_CANONICAL = [
111
+ entry["aliases"][0] for entry in PRODTEST_TX_POWER_LEVELS
112
+ ]
111
113
 
112
114
  _HOST_PORT_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+:\d+$")
113
115
  _IPV6_HOST_PORT_PATTERN = re.compile(r"^\[[0-9A-Fa-f:]+\]:\d+$")
@@ -159,9 +161,7 @@ def _resolve_prodtest_power_choice(value: str) -> Tuple[int, Dict[str, str]]:
159
161
  resolved = parsed
160
162
  if resolved is None:
161
163
  allowed = ", ".join(PRODTEST_TX_POWER_CANONICAL)
162
- raise typer.BadParameter(
163
- f"Power must be one of: {allowed} or an index 0-7"
164
- )
164
+ raise typer.BadParameter(f"Power must be one of: {allowed} or an index 0-7")
165
165
  return resolved, PRODTEST_TX_POWER_META[resolved]
166
166
 
167
167
 
@@ -259,9 +259,7 @@ def _resolve_uart_payload(
259
259
  def _normalize_port(port: str) -> str:
260
260
  trimmed = port.strip()
261
261
  if not trimmed:
262
- raise typer.BadParameter(
263
- "Serial port is required (use --port or SHUTTLE_PORT)"
264
- )
262
+ raise typer.BadParameter("Serial port is required (use --port or SHUTTLE_PORT)")
265
263
  if "://" in trimmed:
266
264
  return trimmed
267
265
  if trimmed.startswith("/") or trimmed.startswith("\\"):
@@ -1568,7 +1566,9 @@ def prodtest_ping(
1568
1566
  raise typer.Exit(1)
1569
1567
 
1570
1568
  if len(responses) != len(sequence):
1571
- console.print("[red]Prodtest command halted before completing all SPI phases[/]")
1569
+ console.print(
1570
+ "[red]Prodtest command halted before completing all SPI phases[/]"
1571
+ )
1572
1572
  raise typer.Exit(1)
1573
1573
 
1574
1574
  command_response, payload_response = responses
@@ -1583,9 +1583,7 @@ def prodtest_ping(
1583
1583
  command_label="spi.xfer (prodtest payload)",
1584
1584
  )
1585
1585
 
1586
- rx_bytes = _decode_hex_response(
1587
- payload_response, label="prodtest ping (payload)"
1588
- )
1586
+ rx_bytes = _decode_hex_response(payload_response, label="prodtest ping (payload)")
1589
1587
  if not rx_bytes or rx_bytes[0] != 0x2D: # ord('-')
1590
1588
  console.print(
1591
1589
  "[red]Ping failed: expected '-' (0x2D), got: "
@@ -1623,9 +1621,7 @@ def prodtest_antenna(
1623
1621
  antenna_value = PRODTEST_ANTENNA_CHOICES[normalized]
1624
1622
  except KeyError as exc:
1625
1623
  allowed = ", ".join(sorted(PRODTEST_ANTENNA_CHOICES))
1626
- raise typer.BadParameter(
1627
- f"Antenna must be one of: {allowed}"
1628
- ) from exc
1624
+ raise typer.BadParameter(f"Antenna must be one of: {allowed}") from exc
1629
1625
 
1630
1626
  sequence = [prodtest.select_antenna(antenna_value)]
1631
1627
  responses = _execute_timo_sequence(
@@ -1767,7 +1763,9 @@ def prodtest_hw_device_id(
1767
1763
  raise typer.Exit(1)
1768
1764
 
1769
1765
  if len(responses) != len(sequence):
1770
- console.print("[red]Prodtest command halted before completing all SPI phases[/]")
1766
+ console.print(
1767
+ "[red]Prodtest command halted before completing all SPI phases[/]"
1768
+ )
1771
1769
  raise typer.Exit(1)
1772
1770
 
1773
1771
  result_response = responses[-1]
@@ -1858,11 +1856,11 @@ def prodtest_serial_number(
1858
1856
  result_response,
1859
1857
  command_label="spi.xfer (prodtest payload)",
1860
1858
  )
1861
- rx_bytes = _decode_hex_response(
1862
- result_response, label="prodtest serial-number"
1863
- )
1859
+ rx_bytes = _decode_hex_response(result_response, label="prodtest serial-number")
1864
1860
  if len(rx_bytes) < prodtest.SERIAL_NUMBER_LEN:
1865
- console.print("[red]Prodtest serial-number response shorter than expected[/]")
1861
+ console.print(
1862
+ "[red]Prodtest serial-number response shorter than expected[/]"
1863
+ )
1866
1864
  raise typer.Exit(1)
1867
1865
  serial_bytes = rx_bytes[-prodtest.SERIAL_NUMBER_LEN :]
1868
1866
  console.print(f"Serial number: {_format_hex(serial_bytes.hex())}")
@@ -2476,7 +2474,9 @@ def wifi_cfg_command(
2476
2474
  if parsed_gateway is not None:
2477
2475
  network_payload["gateway"] = parsed_gateway
2478
2476
 
2479
- dns_entries = [entry for entry in (parsed_dns_primary, parsed_dns_secondary) if entry]
2477
+ dns_entries = [
2478
+ entry for entry in (parsed_dns_primary, parsed_dns_secondary) if entry
2479
+ ]
2480
2480
  if dns_entries:
2481
2481
  network_payload["dns"] = dns_entries
2482
2482
 
Binary file
shuttle/prodtest.py CHANGED
@@ -62,9 +62,7 @@ def command(
62
62
  ) -> dict:
63
63
  """Build an NDJSON-ready spi.xfer payload for a prodtest command."""
64
64
 
65
- return timo.command_payload(
66
- _build_command_bytes(opcode, arguments), params=params
67
- )
65
+ return timo.command_payload(_build_command_bytes(opcode, arguments), params=params)
68
66
 
69
67
 
70
68
  def reset() -> dict:
shuttle/serial_client.py CHANGED
@@ -229,10 +229,23 @@ class NDJSONSerialClient:
229
229
  ):
230
230
  try:
231
231
  self._serial = serial.serial_for_url(
232
- url=port, baudrate=baudrate, timeout=timeout
232
+ url=port,
233
+ baudrate=baudrate,
234
+ timeout=timeout,
235
+ do_not_open=True,
233
236
  )
237
+ except SerialException as exc: # pragma: no cover - hardware specific
238
+ raise ShuttleSerialError(f"Unable to initialize {port}: {exc}") from exc
239
+
240
+ try:
241
+ if getattr(self._serial, "open", None) is not None:
242
+ if not getattr(self._serial, "is_open", False):
243
+ self._serial.open()
234
244
  except SerialException as exc: # pragma: no cover - hardware specific
235
245
  raise ShuttleSerialError(f"Unable to open {port}: {exc}") from exc
246
+ except AttributeError:
247
+ # Test stubs without an open() method are already "connected"
248
+ pass
236
249
  self._serial.reset_input_buffer()
237
250
  self._lock = threading.Lock()
238
251
  self._pending: Dict[int, CommandFuture] = {}