lr-shuttle 0.2.2__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.
- {lr_shuttle-0.2.2.dist-info → lr_shuttle-0.2.4.dist-info}/METADATA +1 -1
- {lr_shuttle-0.2.2.dist-info → lr_shuttle-0.2.4.dist-info}/RECORD +9 -9
- shuttle/cli.py +201 -22
- shuttle/firmware/esp32c5/devboard.ino.bin +0 -0
- shuttle/prodtest.py +1 -3
- shuttle/serial_client.py +22 -1
- {lr_shuttle-0.2.2.dist-info → lr_shuttle-0.2.4.dist-info}/WHEEL +0 -0
- {lr_shuttle-0.2.2.dist-info → lr_shuttle-0.2.4.dist-info}/entry_points.txt +0 -0
- {lr_shuttle-0.2.2.dist-info → lr_shuttle-0.2.4.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
shuttle/cli.py,sha256=
|
|
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=
|
|
5
|
-
shuttle/serial_client.py,sha256=
|
|
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=
|
|
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.
|
|
15
|
-
lr_shuttle-0.2.
|
|
16
|
-
lr_shuttle-0.2.
|
|
17
|
-
lr_shuttle-0.2.
|
|
18
|
-
lr_shuttle-0.2.
|
|
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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import ipaddress
|
|
6
|
+
import re
|
|
5
7
|
import string
|
|
6
8
|
import sys
|
|
7
9
|
import time
|
|
@@ -105,7 +107,12 @@ for entry in PRODTEST_TX_POWER_LEVELS:
|
|
|
105
107
|
for alias in entry["aliases"]:
|
|
106
108
|
PRODTEST_TX_POWER_ALIASES[alias.lower()] = entry["value"]
|
|
107
109
|
PRODTEST_TX_POWER_ALIASES[str(entry["value"])] = entry["value"]
|
|
108
|
-
PRODTEST_TX_POWER_CANONICAL = [
|
|
110
|
+
PRODTEST_TX_POWER_CANONICAL = [
|
|
111
|
+
entry["aliases"][0] for entry in PRODTEST_TX_POWER_LEVELS
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
_HOST_PORT_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+:\d+$")
|
|
115
|
+
_IPV6_HOST_PORT_PATTERN = re.compile(r"^\[[0-9A-Fa-f:]+\]:\d+$")
|
|
109
116
|
|
|
110
117
|
# Backwards-compatible aliases for tests and external callers
|
|
111
118
|
_SerialLogger = SerialLogger
|
|
@@ -154,9 +161,7 @@ def _resolve_prodtest_power_choice(value: str) -> Tuple[int, Dict[str, str]]:
|
|
|
154
161
|
resolved = parsed
|
|
155
162
|
if resolved is None:
|
|
156
163
|
allowed = ", ".join(PRODTEST_TX_POWER_CANONICAL)
|
|
157
|
-
raise typer.BadParameter(
|
|
158
|
-
f"Power must be one of: {allowed} or an index 0-7"
|
|
159
|
-
)
|
|
164
|
+
raise typer.BadParameter(f"Power must be one of: {allowed} or an index 0-7")
|
|
160
165
|
return resolved, PRODTEST_TX_POWER_META[resolved]
|
|
161
166
|
|
|
162
167
|
|
|
@@ -251,9 +256,22 @@ def _resolve_uart_payload(
|
|
|
251
256
|
return payload_bytes.hex(), len(payload_bytes)
|
|
252
257
|
|
|
253
258
|
|
|
259
|
+
def _normalize_port(port: str) -> str:
|
|
260
|
+
trimmed = port.strip()
|
|
261
|
+
if not trimmed:
|
|
262
|
+
raise typer.BadParameter("Serial port is required (use --port or SHUTTLE_PORT)")
|
|
263
|
+
if "://" in trimmed:
|
|
264
|
+
return trimmed
|
|
265
|
+
if trimmed.startswith("/") or trimmed.startswith("\\"):
|
|
266
|
+
return trimmed
|
|
267
|
+
if _HOST_PORT_PATTERN.match(trimmed) or _IPV6_HOST_PORT_PATTERN.match(trimmed):
|
|
268
|
+
return f"socket://{trimmed}"
|
|
269
|
+
return trimmed
|
|
270
|
+
|
|
271
|
+
|
|
254
272
|
def _require_port(port: Optional[str]) -> str:
|
|
255
273
|
if port:
|
|
256
|
-
return port
|
|
274
|
+
return _normalize_port(port)
|
|
257
275
|
raise typer.BadParameter("Serial port is required (use --port or SHUTTLE_PORT)")
|
|
258
276
|
|
|
259
277
|
|
|
@@ -269,6 +287,16 @@ def _parse_int_option(value: str, *, name: str) -> int:
|
|
|
269
287
|
return parsed
|
|
270
288
|
|
|
271
289
|
|
|
290
|
+
def _parse_ipv4(value: Optional[str], *, name: str) -> Optional[str]:
|
|
291
|
+
if value is None:
|
|
292
|
+
return None
|
|
293
|
+
try:
|
|
294
|
+
ipaddress.IPv4Address(value)
|
|
295
|
+
except ipaddress.AddressValueError as exc:
|
|
296
|
+
raise typer.BadParameter(f"{name} must be a valid IPv4 address") from exc
|
|
297
|
+
return value
|
|
298
|
+
|
|
299
|
+
|
|
272
300
|
def _parse_prodtest_mask(value: str) -> bytes:
|
|
273
301
|
try:
|
|
274
302
|
return prodtest.mask_from_hex(value)
|
|
@@ -1521,17 +1549,49 @@ def prodtest_ping(
|
|
|
1521
1549
|
logger=resources.get("logger"),
|
|
1522
1550
|
seq_tracker=resources.get("seq_tracker"),
|
|
1523
1551
|
)
|
|
1524
|
-
if not responses
|
|
1552
|
+
if not responses:
|
|
1525
1553
|
console.print("[red]Device returned no response[/]")
|
|
1526
1554
|
raise typer.Exit(1)
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1555
|
+
|
|
1556
|
+
failed_idx = next(
|
|
1557
|
+
(idx for idx, resp in enumerate(responses) if not resp.get("ok")), None
|
|
1558
|
+
)
|
|
1559
|
+
if failed_idx is not None:
|
|
1560
|
+
phase = "command" if failed_idx == 0 else "payload"
|
|
1561
|
+
_render_spi_response(
|
|
1562
|
+
f"prodtest ping ({phase})",
|
|
1563
|
+
responses[failed_idx],
|
|
1564
|
+
command_label=f"spi.xfer (prodtest {phase})",
|
|
1565
|
+
)
|
|
1566
|
+
raise typer.Exit(1)
|
|
1567
|
+
|
|
1568
|
+
if len(responses) != len(sequence):
|
|
1569
|
+
console.print(
|
|
1570
|
+
"[red]Prodtest command halted before completing all SPI phases[/]"
|
|
1571
|
+
)
|
|
1572
|
+
raise typer.Exit(1)
|
|
1573
|
+
|
|
1574
|
+
command_response, payload_response = responses
|
|
1575
|
+
_render_spi_response(
|
|
1576
|
+
"prodtest ping (command)",
|
|
1577
|
+
command_response,
|
|
1578
|
+
command_label="spi.xfer (prodtest command)",
|
|
1579
|
+
)
|
|
1580
|
+
_render_spi_response(
|
|
1581
|
+
"prodtest ping (payload)",
|
|
1582
|
+
payload_response,
|
|
1583
|
+
command_label="spi.xfer (prodtest payload)",
|
|
1584
|
+
)
|
|
1585
|
+
|
|
1586
|
+
rx_bytes = _decode_hex_response(payload_response, label="prodtest ping (payload)")
|
|
1587
|
+
if not rx_bytes or rx_bytes[0] != 0x2D: # ord('-')
|
|
1588
|
+
console.print(
|
|
1589
|
+
"[red]Ping failed: expected '-' (0x2D), got: "
|
|
1590
|
+
f"{_format_hex(payload_response.get('rx', ''))}[/]"
|
|
1591
|
+
)
|
|
1592
|
+
raise typer.Exit(1)
|
|
1593
|
+
|
|
1594
|
+
console.print("[green]Ping successful: got '-' response[/]")
|
|
1535
1595
|
|
|
1536
1596
|
|
|
1537
1597
|
@prodtest_app.command("antenna")
|
|
@@ -1561,9 +1621,7 @@ def prodtest_antenna(
|
|
|
1561
1621
|
antenna_value = PRODTEST_ANTENNA_CHOICES[normalized]
|
|
1562
1622
|
except KeyError as exc:
|
|
1563
1623
|
allowed = ", ".join(sorted(PRODTEST_ANTENNA_CHOICES))
|
|
1564
|
-
raise typer.BadParameter(
|
|
1565
|
-
f"Antenna must be one of: {allowed}"
|
|
1566
|
-
) from exc
|
|
1624
|
+
raise typer.BadParameter(f"Antenna must be one of: {allowed}") from exc
|
|
1567
1625
|
|
|
1568
1626
|
sequence = [prodtest.select_antenna(antenna_value)]
|
|
1569
1627
|
responses = _execute_timo_sequence(
|
|
@@ -1705,7 +1763,9 @@ def prodtest_hw_device_id(
|
|
|
1705
1763
|
raise typer.Exit(1)
|
|
1706
1764
|
|
|
1707
1765
|
if len(responses) != len(sequence):
|
|
1708
|
-
console.print(
|
|
1766
|
+
console.print(
|
|
1767
|
+
"[red]Prodtest command halted before completing all SPI phases[/]"
|
|
1768
|
+
)
|
|
1709
1769
|
raise typer.Exit(1)
|
|
1710
1770
|
|
|
1711
1771
|
result_response = responses[-1]
|
|
@@ -1796,11 +1856,11 @@ def prodtest_serial_number(
|
|
|
1796
1856
|
result_response,
|
|
1797
1857
|
command_label="spi.xfer (prodtest payload)",
|
|
1798
1858
|
)
|
|
1799
|
-
rx_bytes = _decode_hex_response(
|
|
1800
|
-
result_response, label="prodtest serial-number"
|
|
1801
|
-
)
|
|
1859
|
+
rx_bytes = _decode_hex_response(result_response, label="prodtest serial-number")
|
|
1802
1860
|
if len(rx_bytes) < prodtest.SERIAL_NUMBER_LEN:
|
|
1803
|
-
console.print(
|
|
1861
|
+
console.print(
|
|
1862
|
+
"[red]Prodtest serial-number response shorter than expected[/]"
|
|
1863
|
+
)
|
|
1804
1864
|
raise typer.Exit(1)
|
|
1805
1865
|
serial_bytes = rx_bytes[-prodtest.SERIAL_NUMBER_LEN :]
|
|
1806
1866
|
console.print(f"Serial number: {_format_hex(serial_bytes.hex())}")
|
|
@@ -2328,6 +2388,125 @@ def uart_sub_command(
|
|
|
2328
2388
|
_render_payload_response("uart.sub", response)
|
|
2329
2389
|
|
|
2330
2390
|
|
|
2391
|
+
@app.command("wifi-cfg")
|
|
2392
|
+
def wifi_cfg_command(
|
|
2393
|
+
ctx: typer.Context,
|
|
2394
|
+
port: Optional[str] = typer.Option(
|
|
2395
|
+
None,
|
|
2396
|
+
"--port",
|
|
2397
|
+
envvar="SHUTTLE_PORT",
|
|
2398
|
+
help="Serial port or host:port (e.g., /dev/ttyUSB0 or 192.168.1.10:5000)",
|
|
2399
|
+
),
|
|
2400
|
+
baudrate: int = typer.Option(DEFAULT_BAUD, "--baud", help="Serial baud rate"),
|
|
2401
|
+
timeout: float = typer.Option(
|
|
2402
|
+
DEFAULT_TIMEOUT, "--timeout", help="Read timeout in seconds"
|
|
2403
|
+
),
|
|
2404
|
+
ssid: Optional[str] = typer.Option(
|
|
2405
|
+
None,
|
|
2406
|
+
"--ssid",
|
|
2407
|
+
help="Set the station SSID",
|
|
2408
|
+
show_default=False,
|
|
2409
|
+
),
|
|
2410
|
+
psk: Optional[str] = typer.Option(
|
|
2411
|
+
None,
|
|
2412
|
+
"--psk",
|
|
2413
|
+
help="Set the WPA/WPA2/WPA3 passphrase",
|
|
2414
|
+
show_default=False,
|
|
2415
|
+
),
|
|
2416
|
+
dhcp: Optional[bool] = typer.Option(
|
|
2417
|
+
None,
|
|
2418
|
+
"--dhcp/--static",
|
|
2419
|
+
help="Enable DHCP or force static IPv4 addressing",
|
|
2420
|
+
),
|
|
2421
|
+
ip_addr: Optional[str] = typer.Option(
|
|
2422
|
+
None,
|
|
2423
|
+
"--ip",
|
|
2424
|
+
help="Static IPv4 address (requires --static or other static fields)",
|
|
2425
|
+
show_default=False,
|
|
2426
|
+
),
|
|
2427
|
+
netmask: Optional[str] = typer.Option(
|
|
2428
|
+
None,
|
|
2429
|
+
"--netmask",
|
|
2430
|
+
help="Static subnet mask (e.g., 255.255.255.0)",
|
|
2431
|
+
show_default=False,
|
|
2432
|
+
),
|
|
2433
|
+
gateway: Optional[str] = typer.Option(
|
|
2434
|
+
None,
|
|
2435
|
+
"--gateway",
|
|
2436
|
+
help="Static default gateway IPv4 address",
|
|
2437
|
+
show_default=False,
|
|
2438
|
+
),
|
|
2439
|
+
dns: Optional[str] = typer.Option(
|
|
2440
|
+
None,
|
|
2441
|
+
"--dns",
|
|
2442
|
+
help="Primary DNS server IPv4 address",
|
|
2443
|
+
show_default=False,
|
|
2444
|
+
),
|
|
2445
|
+
dns_alt: Optional[str] = typer.Option(
|
|
2446
|
+
None,
|
|
2447
|
+
"--dns-alt",
|
|
2448
|
+
help="Secondary DNS server IPv4 address",
|
|
2449
|
+
show_default=False,
|
|
2450
|
+
),
|
|
2451
|
+
):
|
|
2452
|
+
"""Query or update Wi-Fi credentials and network settings."""
|
|
2453
|
+
|
|
2454
|
+
resources = _ctx_resources(ctx)
|
|
2455
|
+
wifi_payload: Dict[str, Any] = {}
|
|
2456
|
+
if ssid is not None:
|
|
2457
|
+
wifi_payload["ssid"] = ssid
|
|
2458
|
+
if psk is not None:
|
|
2459
|
+
wifi_payload["psk"] = psk
|
|
2460
|
+
if dhcp is not None:
|
|
2461
|
+
wifi_payload["dhcp"] = dhcp
|
|
2462
|
+
|
|
2463
|
+
network_payload: Dict[str, Any] = {}
|
|
2464
|
+
parsed_ip = _parse_ipv4(ip_addr, name="--ip")
|
|
2465
|
+
parsed_mask = _parse_ipv4(netmask, name="--netmask")
|
|
2466
|
+
parsed_gateway = _parse_ipv4(gateway, name="--gateway")
|
|
2467
|
+
parsed_dns_primary = _parse_ipv4(dns, name="--dns")
|
|
2468
|
+
parsed_dns_secondary = _parse_ipv4(dns_alt, name="--dns-alt")
|
|
2469
|
+
|
|
2470
|
+
if parsed_ip is not None:
|
|
2471
|
+
network_payload["ip"] = parsed_ip
|
|
2472
|
+
if parsed_mask is not None:
|
|
2473
|
+
network_payload["netmask"] = parsed_mask
|
|
2474
|
+
if parsed_gateway is not None:
|
|
2475
|
+
network_payload["gateway"] = parsed_gateway
|
|
2476
|
+
|
|
2477
|
+
dns_entries = [
|
|
2478
|
+
entry for entry in (parsed_dns_primary, parsed_dns_secondary) if entry
|
|
2479
|
+
]
|
|
2480
|
+
if dns_entries:
|
|
2481
|
+
network_payload["dns"] = dns_entries
|
|
2482
|
+
|
|
2483
|
+
if network_payload:
|
|
2484
|
+
if wifi_payload.get("dhcp") is True:
|
|
2485
|
+
raise typer.BadParameter(
|
|
2486
|
+
"Static network options cannot be combined with --dhcp"
|
|
2487
|
+
)
|
|
2488
|
+
wifi_payload.setdefault("dhcp", False)
|
|
2489
|
+
wifi_payload["network"] = network_payload
|
|
2490
|
+
|
|
2491
|
+
resolved_port = _require_port(port)
|
|
2492
|
+
action = "Updating" if wifi_payload else "Querying"
|
|
2493
|
+
with spinner(f"{action} wifi.cfg over {resolved_port}"):
|
|
2494
|
+
try:
|
|
2495
|
+
with NDJSONSerialClient(
|
|
2496
|
+
resolved_port,
|
|
2497
|
+
baudrate=baudrate,
|
|
2498
|
+
timeout=timeout,
|
|
2499
|
+
logger=resources.get("logger"),
|
|
2500
|
+
seq_tracker=resources.get("seq_tracker"),
|
|
2501
|
+
) as client:
|
|
2502
|
+
response = client.wifi_cfg(wifi_payload if wifi_payload else None)
|
|
2503
|
+
except ShuttleSerialError as exc:
|
|
2504
|
+
console.print(f"[red]{exc}[/]")
|
|
2505
|
+
raise typer.Exit(1) from exc
|
|
2506
|
+
|
|
2507
|
+
_render_payload_response("wifi.cfg", response)
|
|
2508
|
+
|
|
2509
|
+
|
|
2331
2510
|
@app.command("uart-tx")
|
|
2332
2511
|
def uart_tx_command(
|
|
2333
2512
|
ctx: typer.Context,
|
|
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
|
@@ -228,9 +228,24 @@ class NDJSONSerialClient:
|
|
|
228
228
|
seq_tracker: Optional[SequenceTracker] = None,
|
|
229
229
|
):
|
|
230
230
|
try:
|
|
231
|
-
self._serial = serial.
|
|
231
|
+
self._serial = serial.serial_for_url(
|
|
232
|
+
url=port,
|
|
233
|
+
baudrate=baudrate,
|
|
234
|
+
timeout=timeout,
|
|
235
|
+
do_not_open=True,
|
|
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()
|
|
232
244
|
except SerialException as exc: # pragma: no cover - hardware specific
|
|
233
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
|
|
234
249
|
self._serial.reset_input_buffer()
|
|
235
250
|
self._lock = threading.Lock()
|
|
236
251
|
self._pending: Dict[int, CommandFuture] = {}
|
|
@@ -345,6 +360,12 @@ class NDJSONSerialClient:
|
|
|
345
360
|
payload["uart"] = uart
|
|
346
361
|
return self._command("uart.cfg", payload)
|
|
347
362
|
|
|
363
|
+
def wifi_cfg(self, wifi: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
364
|
+
payload: Dict[str, Any] = {}
|
|
365
|
+
if wifi:
|
|
366
|
+
payload["wifi"] = wifi
|
|
367
|
+
return self._command("wifi.cfg", payload)
|
|
368
|
+
|
|
348
369
|
def uart_tx(self, data: str, port: Optional[int] = None) -> Dict[str, Any]:
|
|
349
370
|
payload: Dict[str, Any] = {"data": data}
|
|
350
371
|
if port is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|