tan-cli 0.7.0__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.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan reset` -- ONE bare nRESET pulse through J-Link (tan-cli#1452).
|
|
3
|
+
|
|
4
|
+
Bench recovery from a non-waking STOP is a single clean pulse: `r0`, a pulse
|
|
5
|
+
width (default 100 ms), `r1`. This is not the pin reset `tan flash` runs
|
|
6
|
+
(`RSetType 2; r; g`, with retries): no connect, no halt, no connect-under-reset, no retry
|
|
7
|
+
storm, so the target is not woken out of STOP and the VBAT/BKRAM evidence
|
|
8
|
+
survives. See `tan.core.reset_plan` for the script rules.
|
|
9
|
+
|
|
10
|
+
Probe handling is `tan probe`'s: the trusted J-Link binary (`resolve_jlink`),
|
|
11
|
+
`--probe-serial` / `--probe-usb-path` and the `ShowEmuList` verification before
|
|
12
|
+
the spawn, through `flash_cmd._execute`. `JLINK_RUN_PLACE` is read from the
|
|
13
|
+
environment of THIS invocation, so the board-farm shim that wraps `JLinkExe`
|
|
14
|
+
picks the place per command, exactly as for `tan flash`; the envelope reports
|
|
15
|
+
the value in `data.place`.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import time
|
|
22
|
+
import sys
|
|
23
|
+
from typing import Any, Callable
|
|
24
|
+
|
|
25
|
+
import typer
|
|
26
|
+
|
|
27
|
+
from tan.commands import flash_cmd as fc
|
|
28
|
+
from tan.commands import flash_raw
|
|
29
|
+
from tan.commands import reset_confirm as rc_mod
|
|
30
|
+
from tan.commands.monitor_cmd import MonitorError
|
|
31
|
+
from tan.core import reset_plan as rp
|
|
32
|
+
from tan.core import trusted_path
|
|
33
|
+
from tan.core.flash_plan import FlashPlan, FlashPlanError
|
|
34
|
+
from tan.core.global_flags import accept_global_flags
|
|
35
|
+
from tan.core.jlink_probe import HANDSHAKE_PREFIX, USB_PATH_ENV, parse_handshakes
|
|
36
|
+
from tan.core.jlink_binary import ENV_OVERRIDE as JLINK_ENV, resolve_jlink
|
|
37
|
+
from tan.envelope import Envelope, Issue
|
|
38
|
+
from tan.exit_codes import ExitCode
|
|
39
|
+
from tan.output_format import FORMAT_HELP, OutputFormat, resolve_format
|
|
40
|
+
|
|
41
|
+
COMMAND = "reset"
|
|
42
|
+
PLACE_ENV = "JLINK_RUN_PLACE"
|
|
43
|
+
_SCHEMA_VERSION = fc._DATA_SCHEMA_VERSION
|
|
44
|
+
_SCRIPT_PREFIX = "tan-reset-"
|
|
45
|
+
#: Commander output that means the pulse did not happen, even when the closing
|
|
46
|
+
#: "Script processing completed" banner is also printed.
|
|
47
|
+
_FAILURE = re.compile(r"FAILED|Cannot connect|Could not open")
|
|
48
|
+
_Result = tuple[ExitCode, dict[str, Any], list[Issue], list[str]]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _fail(data: dict[str, Any], issue: Issue, rc=ExitCode.RUNTIME_FAILURE) -> _Result:
|
|
52
|
+
return rc, data, [issue], [f"reset: {issue.message}"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _select(data, probe_serial, probe_usb_path, jlink_path, project_dir, enumerate_probes):
|
|
56
|
+
"""`(guard, echo, serial)` or a refusal `_Result`."""
|
|
57
|
+
ctx = fc._Context(
|
|
58
|
+
sku="", build_root="", sdk_root="", dry_run=False, skip_missing_tools=False,
|
|
59
|
+
force_confirm=False, capture=True, probe_serial=probe_serial,
|
|
60
|
+
probe_usb_path=probe_usb_path, project_dir=project_dir, jlink_path=jlink_path,
|
|
61
|
+
**({"enumerate_probes": enumerate_probes} if enumerate_probes is not None else {}),
|
|
62
|
+
)
|
|
63
|
+
flash_args, selection, snapshot = fc._flow_d_probe_selection({}, ctx)
|
|
64
|
+
echo = fc._probe_echo(selection, ctx)
|
|
65
|
+
if echo:
|
|
66
|
+
data["probe"] = echo
|
|
67
|
+
if selection is not None and selection.refusal_code is not None:
|
|
68
|
+
return _fail(data, Issue(f"flash.probe-{selection.refusal_code}", "error", selection.refusal or ""))
|
|
69
|
+
guard = None
|
|
70
|
+
if selection is not None and (selection.serial is not None or selection.visible is None):
|
|
71
|
+
guard = fc._ProbeGuard(selection, snapshot, ctx.enumerate_probes, echo,
|
|
72
|
+
script_prefix=_SCRIPT_PREFIX)
|
|
73
|
+
return guard, flash_args.get("jlink_serial")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _spawn(guard, script, exe, usb_path, fast):
|
|
77
|
+
"""One J-Link Commander spawn. `fast` (a masking wrapper is in use, named by
|
|
78
|
+
JLINK_RUN_PLACE, and --probe-usb-path was given): no ShowEmuList pass before
|
|
79
|
+
the pulse -- the wrapper itself refuses a TAN_PROBE_USB_PATH that is not its
|
|
80
|
+
place's port (exit 96) before opening any probe, and the pulse is judged
|
|
81
|
+
afterwards by the TAN_PROBE_ISOLATED_USB_PATH handshake. Otherwise the
|
|
82
|
+
pre-spawn probe guard of `tan flash` runs. The argv is the proven one
|
|
83
|
+
(`-NoGui 1 -CommanderScript`, plus -ExitOnError 1): no -if, no -autoconnect,
|
|
84
|
+
no -device; Commander opens the probe lazily on `r0`."""
|
|
85
|
+
argv = ("JLinkExe", "-NoGui", "1", "-ExitOnError", "1", "-CommanderScript")
|
|
86
|
+
if not fast:
|
|
87
|
+
return fc._execute(
|
|
88
|
+
FlashPlan(argv=argv, ok_message="", jlink_script=script),
|
|
89
|
+
True, None, None, guard, jlink_exe=exe, script_prefix=_SCRIPT_PREFIX,
|
|
90
|
+
)
|
|
91
|
+
return fc._spawn_jlink(
|
|
92
|
+
list(argv), script, True, fc._FLASH_TIMEOUT_S, None, None, exe,
|
|
93
|
+
extra_env={USB_PATH_ENV: usb_path}, script_prefix=_SCRIPT_PREFIX,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _verdict(data, out, guard, fast, usb_path) -> Issue | None:
|
|
98
|
+
"""The refusal / failure for a finished spawn, else `None`."""
|
|
99
|
+
if guard is not None and guard.tripped and not fast:
|
|
100
|
+
return Issue(f"flash.probe-{guard.tripped_code}", "error", guard.tripped)
|
|
101
|
+
text = f"{out.stdout}\n{out.stderr}"
|
|
102
|
+
if not out.success or "Script processing completed" not in text or _FAILURE.search(text):
|
|
103
|
+
tail = fc._capture_tail(out) or "J-Link did not complete the script"
|
|
104
|
+
return Issue("reset.failed", "error", f"the nRESET pulse did not complete: {tail}")
|
|
105
|
+
if fast:
|
|
106
|
+
seen = parse_handshakes(text)
|
|
107
|
+
if seen != [usb_path]:
|
|
108
|
+
return Issue(
|
|
109
|
+
"flash.probe-verify-failed", "error",
|
|
110
|
+
f"the pulse ran, but the wrapper did not attest isolation to {usb_path} "
|
|
111
|
+
f"({HANDSHAKE_PREFIX} lines: {', '.join(seen) or 'none'}); it may have reached "
|
|
112
|
+
"the wrong board",
|
|
113
|
+
)
|
|
114
|
+
data.setdefault("probe", {})["isolation"] = f"wrapper-attested:{usb_path}"
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _finish_pulse(data, ser, confirm, pulse_ms, initial=b"", exit_ts=None, stuck=None) -> _Result:
|
|
119
|
+
place = f" on place {data['place']}" if data["place"] else ""
|
|
120
|
+
line = f"reset: nRESET pulsed for {pulse_ms} ms{place}"
|
|
121
|
+
if ser is None:
|
|
122
|
+
note = Issue("reset.boot-not-confirmed", "info",
|
|
123
|
+
"pulse sent; boot not confirmed (pass --confirm-console PORT --expect REGEX)")
|
|
124
|
+
return ExitCode.SUCCESS, data, [note], [line + "; boot not confirmed"]
|
|
125
|
+
if stuck is not None: # never run a second reader beside a stuck one
|
|
126
|
+
data["console"] = {"port": confirm.port, "observed": False, "error": str(stuck)}
|
|
127
|
+
else:
|
|
128
|
+
data["console"] = rc_mod.observe(ser, confirm, initial, exit_ts)
|
|
129
|
+
data["resetObserved"] = bool(data["console"]["observed"])
|
|
130
|
+
if "error" in data["console"]:
|
|
131
|
+
data["resetObserved"] = "unknown"
|
|
132
|
+
issue = Issue("reset.console-read-failed", "error",
|
|
133
|
+
f"pulse sent, but reading {confirm.port} failed ({data['console']['error']}): "
|
|
134
|
+
"reset not confirmed either way")
|
|
135
|
+
return _fail(data, issue)
|
|
136
|
+
if not data["resetObserved"]:
|
|
137
|
+
issue = Issue("reset.boot-not-observed", "error",
|
|
138
|
+
f"pulse sent, but no console line matched --expect within the {confirm.window_s}s "
|
|
139
|
+
f"confirm window on {confirm.port}: the board did not visibly reboot"
|
|
140
|
+
+ (f" (a late match at {data['console']['lateMatchAtSeconds']}s is not a reset)"
|
|
141
|
+
if "lateMatchAtSeconds" in data["console"] else ""))
|
|
142
|
+
return _fail(data, issue)
|
|
143
|
+
return ExitCode.SUCCESS, data, [], [line + "; boot observed"]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _run(
|
|
147
|
+
pulse_ms: int,
|
|
148
|
+
probe_serial: str | None,
|
|
149
|
+
probe_usb_path: str | None,
|
|
150
|
+
jlink_path: str | None,
|
|
151
|
+
project_dir: str,
|
|
152
|
+
enumerate_probes: Callable[[], Any] | None = None,
|
|
153
|
+
confirm: "rc_mod.ConfirmSpec | None" = None,
|
|
154
|
+
) -> _Result:
|
|
155
|
+
data: dict[str, Any] = {
|
|
156
|
+
"schemaVersion": _SCHEMA_VERSION, "pulseMs": pulse_ms,
|
|
157
|
+
"place": os.environ.get(PLACE_ENV) or None, "writes": False,
|
|
158
|
+
"resetObserved": "unknown",
|
|
159
|
+
}
|
|
160
|
+
try:
|
|
161
|
+
rp.check_pulse_ms(pulse_ms)
|
|
162
|
+
except rp.ResetArgError as err:
|
|
163
|
+
return _fail(data, Issue("reset.bad-argument", "error", str(err)), ExitCode.VALIDATION_FAILURE)
|
|
164
|
+
place = data["place"]
|
|
165
|
+
if place:
|
|
166
|
+
# A reservation place is named: the ONLY J-Link program tan may spawn is the trusted
|
|
167
|
+
# wrapper. Refuse before any spawn rather than run a raw ShowEmuList that enumerates
|
|
168
|
+
# other places' probes.
|
|
169
|
+
wrapper, why = trusted_path.configured_wrapper()
|
|
170
|
+
if wrapper is None:
|
|
171
|
+
data["wrapper"] = {"trusted": False, "reason": why}
|
|
172
|
+
return _fail(data, Issue(
|
|
173
|
+
"reset.wrapper-required", "error",
|
|
174
|
+
f"{PLACE_ENV} is set but no trusted wrapper is configured ({why}): set "
|
|
175
|
+
"TAN_JLINK_WRAPPER to the absolute path of the board-farm JLinkExe shim. "
|
|
176
|
+
"Nothing was spawned."))
|
|
177
|
+
from_wrapper_env = not jlink_path
|
|
178
|
+
jlink_path = jlink_path or wrapper # an explicit --jlink wins, and is checked below
|
|
179
|
+
else:
|
|
180
|
+
from_wrapper_env = False
|
|
181
|
+
found = resolve_jlink(jlink_path, project_dir=project_dir)
|
|
182
|
+
exe = found.path if found is not None else None
|
|
183
|
+
# resolve_jlink() labels any path handed to it as "the --jlink flag"; name the real
|
|
184
|
+
# origin when tan itself defaulted to TAN_JLINK_WRAPPER (tan-cli#1467).
|
|
185
|
+
source = "TAN_JLINK_WRAPPER" if (found and from_wrapper_env) else (found.source if found else None)
|
|
186
|
+
data["jlink"] = {"binary": exe, "binarySource": source}
|
|
187
|
+
if exe is None:
|
|
188
|
+
return _fail(data, Issue("reset.failed", "error", fc._NO_TRUSTED_JLINK))
|
|
189
|
+
trusted, reason = trusted_path.is_configured_wrapper(exe)
|
|
190
|
+
data["wrapper"] = {"trusted": trusted, "reason": reason}
|
|
191
|
+
if place and not trusted:
|
|
192
|
+
return _fail(data, Issue(
|
|
193
|
+
"reset.wrapper-required", "error",
|
|
194
|
+
f"{PLACE_ENV} is set but the J-Link program is not the configured wrapper ({reason}); "
|
|
195
|
+
"nothing was spawned."))
|
|
196
|
+
if place:
|
|
197
|
+
# The wrapper's holder check is `<host>/<user>`, identical for every session of one
|
|
198
|
+
# user, so a pulse here could reset a board ANOTHER session holds (tan-cli#1457). Demand
|
|
199
|
+
# exactly what `tan flash --raw` demands: this session's lease nonce, labgrid showing
|
|
200
|
+
# us as the holder of this acquisition, and --probe-usb-path equal to the place's swd
|
|
201
|
+
# port. Refused before any spawn.
|
|
202
|
+
refusal, _verified = flash_raw._reservation_refusal(place, exe, probe_usb_path)
|
|
203
|
+
if refusal is not None:
|
|
204
|
+
return _fail(data, Issue(
|
|
205
|
+
"reset.reservation-required", "error",
|
|
206
|
+
f"refusing to pulse nRESET without a provably held bench reservation: {refusal} "
|
|
207
|
+
"Nothing was spawned."))
|
|
208
|
+
# Single-spawn mode only when the J-Link program IS the configured wrapper: a real SEGGER
|
|
209
|
+
# JLinkExe given JLINK_RUN_PLACE would skip ShowEmuList and pulse whichever probe
|
|
210
|
+
# enumerates first.
|
|
211
|
+
fast = bool(data["place"]) and probe_usb_path is not None and trusted
|
|
212
|
+
try:
|
|
213
|
+
picked = _select(data, probe_serial, probe_usb_path, jlink_path, project_dir, enumerate_probes)
|
|
214
|
+
if isinstance(picked, tuple) and len(picked) == 4:
|
|
215
|
+
return picked
|
|
216
|
+
guard, serial = picked
|
|
217
|
+
script = rp.pulse_script(None if fast else serial, pulse_ms)
|
|
218
|
+
except (FlashPlanError, rp.ResetArgError) as err:
|
|
219
|
+
return _fail(data, Issue("reset.failed", "error", str(err)))
|
|
220
|
+
data["script"] = fc._DISABLE_FW_UPDATE.splitlines() + script.splitlines()
|
|
221
|
+
data["singleSpawn"] = fast
|
|
222
|
+
ser = None
|
|
223
|
+
started = time.monotonic()
|
|
224
|
+
if confirm is not None:
|
|
225
|
+
try:
|
|
226
|
+
ser = rc_mod.open_console(confirm)
|
|
227
|
+
except MonitorError as err:
|
|
228
|
+
return _fail(data, Issue("reset.console-open-failed", "error", err.message))
|
|
229
|
+
drain = rc_mod.Drain(ser) if ser is not None else None
|
|
230
|
+
try:
|
|
231
|
+
spawn_started = time.monotonic()
|
|
232
|
+
out = _spawn(guard, script, exe, probe_usb_path, fast)
|
|
233
|
+
exit_ts = time.monotonic()
|
|
234
|
+
stuck = None
|
|
235
|
+
initial: list[bytes] = []
|
|
236
|
+
if drain is not None:
|
|
237
|
+
try:
|
|
238
|
+
initial = drain.stop(exit_ts)
|
|
239
|
+
except rc_mod.DrainStuck as err:
|
|
240
|
+
stuck = err
|
|
241
|
+
data["timing"] = {
|
|
242
|
+
"prepSeconds": round(spawn_started - started, 3),
|
|
243
|
+
"jlinkSpawnSeconds": round(time.monotonic() - spawn_started, 3),
|
|
244
|
+
"note": "jlinkSpawnSeconds includes any wrapper preamble before the pulse itself",
|
|
245
|
+
}
|
|
246
|
+
problem = _verdict(data, out, guard, fast, probe_usb_path)
|
|
247
|
+
if problem is not None:
|
|
248
|
+
return _fail(data, problem)
|
|
249
|
+
return _finish_pulse(data, ser, confirm, pulse_ms, b"".join(initial), exit_ts, stuck)
|
|
250
|
+
finally:
|
|
251
|
+
if drain is not None:
|
|
252
|
+
try:
|
|
253
|
+
drain.stop()
|
|
254
|
+
except rc_mod.DrainStuck:
|
|
255
|
+
pass
|
|
256
|
+
if ser is not None:
|
|
257
|
+
try:
|
|
258
|
+
ser.close()
|
|
259
|
+
except Exception: # noqa: BLE001 -- observe() already closed it
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def reset(
|
|
264
|
+
ctx: typer.Context,
|
|
265
|
+
project: str = typer.Option(None, "--project", metavar="PATH", help="Project root (defaults to '.')."),
|
|
266
|
+
pulse_ms: int = typer.Option(
|
|
267
|
+
rp.DEFAULT_PULSE_MS, "--pulse-ms", metavar="MS", show_default=True,
|
|
268
|
+
help=f"How long nRESET is held low ({rp.MIN_PULSE_MS}..{rp.MAX_PULSE_MS})."),
|
|
269
|
+
probe_serial: str = typer.Option(None, "--probe-serial", metavar="SN", help="J-Link serial for this run."),
|
|
270
|
+
probe_usb_path: str = typer.Option(
|
|
271
|
+
None, "--probe-usb-path", metavar="BUS-PORT",
|
|
272
|
+
help="Select the J-Link at this USB port path (e.g. 3-4.2). Without JLINK_RUN_PLACE it is "
|
|
273
|
+
"verified (ShowEmuList) before the JLinkExe spawn, as `tan flash` does. With JLINK_RUN_PLACE it "
|
|
274
|
+
"must equal the leased place's swd port (checked against labgrid before any spawn), there is "
|
|
275
|
+
"no ShowEmuList, and the wrapper's TAN_PROBE_ISOLATED_USB_PATH handshake is checked only "
|
|
276
|
+
"AFTER the pulse has run (TAN_PROBE_USB_PATH is exported for the masking wrapper)."),
|
|
277
|
+
confirm_console: str = typer.Option(
|
|
278
|
+
None, "--confirm-console", metavar="PORT",
|
|
279
|
+
help="Serial port (or rfc2217:// URL) to watch for the reboot; opened before the pulse. "
|
|
280
|
+
"Needs --expect. Without it the result is resetObserved: unknown."),
|
|
281
|
+
expect: str = typer.Option(
|
|
282
|
+
None, "--expect", metavar="REGEX",
|
|
283
|
+
help="With --confirm-console: a line printed after reset; only bytes that arrive after "
|
|
284
|
+
"J-Link exits count."),
|
|
285
|
+
confirm_baud: int = typer.Option(None, "--confirm-baud", metavar="BAUD", help="Console baud (default 115200)."),
|
|
286
|
+
confirm_timeout: float = typer.Option(
|
|
287
|
+
None, "--confirm-timeout", metavar="SECONDS", help="Overall read limit for --expect (default 30)."),
|
|
288
|
+
confirm_window: float = typer.Option(
|
|
289
|
+
None, "--confirm-window", metavar="SECONDS",
|
|
290
|
+
help="The FIRST --expect match must arrive this soon after J-Link exits (default 3); a later "
|
|
291
|
+
"one is recorded as lateMatchAtSeconds and is not a reset."),
|
|
292
|
+
jlink: str = typer.Option(
|
|
293
|
+
None, "--jlink", metavar="PATH",
|
|
294
|
+
help=f"The J-Link Commander binary (otherwise {JLINK_ENV}, PATH, then a SEGGER install root; "
|
|
295
|
+
"never the project .venv). With JLINK_RUN_PLACE set, TAN_JLINK_WRAPPER (the absolute path of "
|
|
296
|
+
"the board-farm shim) is used instead and any other program is refused (reset.wrapper-required)."),
|
|
297
|
+
output_format: OutputFormat = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
298
|
+
) -> None:
|
|
299
|
+
"""ONE bare nRESET pulse through J-Link: `r0`, wait --pulse-ms, `r1`. No connect, no halt, no
|
|
300
|
+
connect-under-reset, no retry; it does not flash. Set JLINK_RUN_PLACE (with --probe-usb-path) in the
|
|
301
|
+
environment to pick the board-farm place for this command; that requires the session lease
|
|
302
|
+
(reset.reservation-required otherwise).
|
|
303
|
+
|
|
304
|
+
\b
|
|
305
|
+
tan reset --probe-usb-path 3-4.2
|
|
306
|
+
JLINK_RUN_PLACE=aen-evk-02 tan reset --probe-usb-path 3-4.2 --pulse-ms 200 --format json
|
|
307
|
+
tan reset --probe-usb-path 3-4.2 --confirm-console rfc2217://gw:4001 --expect 'Booting Zephyr'
|
|
308
|
+
(pick a banner printed some time AFTER reset: text emitted before J-Link exits is not counted)
|
|
309
|
+
(with JLINK_RUN_PLACE: also needs TAN_JLINK_WRAPPER, this session's TAN_LEASE_NONCE from
|
|
310
|
+
scripts/bench/tan-lease.sh, and labgrid-client showing you as the holder)
|
|
311
|
+
(the reset is only claimed with --confirm-console; otherwise resetObserved is "unknown")
|
|
312
|
+
"""
|
|
313
|
+
json_mode = resolve_format(output_format, ctx.obj, choices=OutputFormat) == "json"
|
|
314
|
+
probe_serial = probe_serial if isinstance(probe_serial, str) else None
|
|
315
|
+
probe_usb_path = probe_usb_path if isinstance(probe_usb_path, str) else None
|
|
316
|
+
if probe_usb_path is not None and not fc.is_valid_usb_path(probe_usb_path):
|
|
317
|
+
raise typer.BadParameter(
|
|
318
|
+
f"{probe_usb_path!r} is not a USB port path like 3-4.2 (<bus>-<port>[.<port>...])",
|
|
319
|
+
param_hint="--probe-usb-path",
|
|
320
|
+
)
|
|
321
|
+
bad_port = None
|
|
322
|
+
confirm = None
|
|
323
|
+
try:
|
|
324
|
+
confirm = rc_mod.confirm_spec(confirm_console, expect, confirm_baud, confirm_timeout, confirm_window)
|
|
325
|
+
except rc_mod.BadPort as err:
|
|
326
|
+
bad_port = err
|
|
327
|
+
except rc_mod.ConfirmError as err:
|
|
328
|
+
raise typer.BadParameter(str(err)) from err
|
|
329
|
+
cwd = fc.workspace_root(project)
|
|
330
|
+
project_obj = fc._resolve_project(cwd, None)
|
|
331
|
+
try:
|
|
332
|
+
if bad_port is not None:
|
|
333
|
+
exit_code, data, issues, lines = _fail(
|
|
334
|
+
{"schemaVersion": _SCHEMA_VERSION, "port": confirm_console},
|
|
335
|
+
Issue("reset.bad-port", "error", str(bad_port)), ExitCode.VALIDATION_FAILURE)
|
|
336
|
+
else:
|
|
337
|
+
exit_code, data, issues, lines = _run(
|
|
338
|
+
pulse_ms, probe_serial, probe_usb_path,
|
|
339
|
+
jlink if isinstance(jlink, str) else None, cwd, confirm=confirm,
|
|
340
|
+
)
|
|
341
|
+
except Exception as err: # noqa: BLE001 -- a tan bug is reported as one, with an envelope
|
|
342
|
+
exit_code = ExitCode.INTERNAL_FAILURE
|
|
343
|
+
data = {"schemaVersion": _SCHEMA_VERSION}
|
|
344
|
+
issues = [Issue("reset.internal-failure", "error", f"{type(err).__name__}: {err}")]
|
|
345
|
+
lines = ["reset: internal failure"]
|
|
346
|
+
if json_mode:
|
|
347
|
+
fc.emit(Envelope(COMMAND, project_obj, data, issues, exit_code))
|
|
348
|
+
else:
|
|
349
|
+
for line in lines:
|
|
350
|
+
print(line, file=sys.stderr if exit_code else sys.stdout)
|
|
351
|
+
raise typer.Exit(int(exit_code))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
reset = accept_global_flags(reset)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan reset --confirm-console PORT --expect REGEX` (tan-cli#1452).
|
|
3
|
+
|
|
4
|
+
A J-Link `r0`/`r1` pulse that exits 0 proves only that Commander ran; it says
|
|
5
|
+
nothing about the board rebooting (a target in STOP can ignore it). This is the
|
|
6
|
+
evidence: the console is opened BEFORE the pulse (so an `rfc2217://` session is
|
|
7
|
+
already up), and once J-Link has exited the input queue is dropped and the port
|
|
8
|
+
is read until a line matches `--expect` or `--confirm-timeout` passes. Only
|
|
9
|
+
bytes that arrive after J-Link exits count, so a line the board printed before
|
|
10
|
+
the pulse cannot confirm it. Boot text emitted in the second between `r1` and
|
|
11
|
+
Commander's exit is therefore not seen: pick a banner printed some time after
|
|
12
|
+
reset.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from tan.core import serial_capture, serial_url
|
|
22
|
+
|
|
23
|
+
DEFAULT_BAUD = 115200
|
|
24
|
+
DEFAULT_TIMEOUT_S = 30.0
|
|
25
|
+
DEFAULT_WINDOW_S = 3.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfirmError(ValueError):
|
|
29
|
+
"""A bad `--confirm-*` / `--expect` combination."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class ConfirmSpec:
|
|
34
|
+
port: str
|
|
35
|
+
expect: Any # compiled regex
|
|
36
|
+
baud: int
|
|
37
|
+
timeout_s: float
|
|
38
|
+
window_s: float = DEFAULT_WINDOW_S
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BadPort(ConfirmError):
|
|
42
|
+
"""`--confirm-console` is a malformed serial URL."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def confirm_spec(
|
|
46
|
+
port: str | None,
|
|
47
|
+
expect: str | None,
|
|
48
|
+
baud: int | None,
|
|
49
|
+
timeout_s: float | None,
|
|
50
|
+
window_s: float | None = None,
|
|
51
|
+
):
|
|
52
|
+
"""`None` when no confirmation was asked for; `ConfirmError` on a bad combination."""
|
|
53
|
+
if all(v is None for v in (port, expect, baud, timeout_s, window_s)):
|
|
54
|
+
return None
|
|
55
|
+
if port is None or expect is None:
|
|
56
|
+
raise ConfirmError("--confirm-console and --expect must be given together")
|
|
57
|
+
problem = serial_url.port_url_problem(port)
|
|
58
|
+
if problem is not None:
|
|
59
|
+
raise BadPort(f"bad --confirm-console: {problem}")
|
|
60
|
+
try:
|
|
61
|
+
pattern = serial_capture.compile_until(expect)
|
|
62
|
+
except ValueError as err:
|
|
63
|
+
raise ConfirmError(str(err).replace("--until", "--expect")) from err
|
|
64
|
+
baud = DEFAULT_BAUD if baud is None else baud
|
|
65
|
+
timeout_s = DEFAULT_TIMEOUT_S if timeout_s is None else timeout_s
|
|
66
|
+
window_s = DEFAULT_WINDOW_S if window_s is None else window_s
|
|
67
|
+
if baud <= 0 or not timeout_s > 0 or not window_s > 0:
|
|
68
|
+
raise ConfirmError(
|
|
69
|
+
"--confirm-baud, --confirm-timeout and --confirm-window must be greater than 0"
|
|
70
|
+
)
|
|
71
|
+
return ConfirmSpec(port, pattern, baud, timeout_s, window_s)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DrainStuck(RuntimeError):
|
|
75
|
+
"""The background reader did not return from its read; the console is left alone."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Drain:
|
|
79
|
+
"""The ONLY reader of the console while J-Link runs.
|
|
80
|
+
|
|
81
|
+
Everything it reads is time-stamped. Stale rfc2217 lines in flight when the
|
|
82
|
+
pulse is sent arrive before J-Link exits, so they are dropped; `stop(exit_ts)`
|
|
83
|
+
hands back only the chunks stamped at or after `exit_ts`, which the caller
|
|
84
|
+
feeds to the capture as its first data. The thread may be inside a 0.1 s
|
|
85
|
+
`read()` when `stop()` is called: it is joined, never abandoned, so a banner
|
|
86
|
+
that arrives (measured: 0.088 s after exit) during that read is kept, and two
|
|
87
|
+
readers never run at once."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, ser, clock=time.monotonic) -> None:
|
|
90
|
+
self._ser = ser
|
|
91
|
+
self._clock = clock # injectable so tests need no wall-clock races
|
|
92
|
+
self._halt = threading.Event()
|
|
93
|
+
self._chunks: list[tuple[float, bytes]] = []
|
|
94
|
+
self._stopped = False
|
|
95
|
+
self._thread = threading.Thread(target=self._run, daemon=True)
|
|
96
|
+
self._thread.start()
|
|
97
|
+
|
|
98
|
+
def _run(self) -> None:
|
|
99
|
+
while not self._halt.is_set():
|
|
100
|
+
try:
|
|
101
|
+
got = self._ser.read(max(1, int(getattr(self._ser, "in_waiting", 0) or 0)))
|
|
102
|
+
except Exception: # noqa: BLE001 -- observe() reports a dead link
|
|
103
|
+
return
|
|
104
|
+
if got:
|
|
105
|
+
self._chunks.append((self._clock(), got))
|
|
106
|
+
else:
|
|
107
|
+
time.sleep(0.005)
|
|
108
|
+
|
|
109
|
+
def stop(self, exit_ts: float | None = None, timeout: float = 2.0) -> list[bytes]:
|
|
110
|
+
"""Halt the reader and return the chunks received at/after `exit_ts`
|
|
111
|
+
(none when `exit_ts` is None). Idempotent. `DrainStuck` if the reader is
|
|
112
|
+
still inside `read()` after `timeout`."""
|
|
113
|
+
if self._stopped:
|
|
114
|
+
return []
|
|
115
|
+
self._halt.set()
|
|
116
|
+
self._thread.join(timeout=timeout)
|
|
117
|
+
if self._thread.is_alive():
|
|
118
|
+
raise DrainStuck(f"the console reader did not stop within {timeout}s")
|
|
119
|
+
self._stopped = True
|
|
120
|
+
if exit_ts is None:
|
|
121
|
+
return []
|
|
122
|
+
return [data for ts, data in self._chunks if ts >= exit_ts]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def open_console(spec: ConfirmSpec):
|
|
126
|
+
"""Open the console (before the pulse). `MonitorError` on failure."""
|
|
127
|
+
from tan.commands import monitor_session # noqa: PLC0415 (pyserial is optional)
|
|
128
|
+
|
|
129
|
+
return monitor_session.open_port(spec.port, spec.baud, capture=True)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def observe(
|
|
133
|
+
ser, spec: ConfirmSpec, initial: bytes = b"", exit_ts: float | None = None, clock=time.monotonic
|
|
134
|
+
) -> dict[str, Any]:
|
|
135
|
+
"""After J-Link exited: wait for `--expect`, starting with `initial` (what the
|
|
136
|
+
`Drain` stamped at or after `exit_ts`). The queue is NOT flushed here: a flush
|
|
137
|
+
could discard a banner that is already waiting.
|
|
138
|
+
Closes `ser`. Returns the `data.console` block. The FIRST matching line must arrive within
|
|
139
|
+
`window_s` of J-Link exiting: a later one (e.g. an RTC-alarm wake out of STOP) is recorded as
|
|
140
|
+
`lateMatchAtSeconds` and is not a reset."""
|
|
141
|
+
try:
|
|
142
|
+
t0 = clock()
|
|
143
|
+
res = serial_capture.capture(
|
|
144
|
+
ser, duration_s=max(spec.timeout_s, spec.window_s), until=spec.expect, initial=initial,
|
|
145
|
+
clock=clock,
|
|
146
|
+
)
|
|
147
|
+
# Latency from J-Link's exit, not from when this capture began.
|
|
148
|
+
total = res.elapsed_s + (t0 - exit_ts if exit_ts is not None else 0.0)
|
|
149
|
+
latency = round(total, 3) if res.matched else None
|
|
150
|
+
in_window = res.matched and total <= spec.window_s
|
|
151
|
+
block = {
|
|
152
|
+
"port": spec.port, "baud": spec.baud, "observed": in_window,
|
|
153
|
+
"windowSeconds": spec.window_s, "matchLatencySeconds": latency if in_window else None,
|
|
154
|
+
"matchedLine": res.matched_line if in_window else None,
|
|
155
|
+
"elapsedSeconds": round(total, 3),
|
|
156
|
+
"bytesSeen": res.bytes_seen, "bytesSeenTail": res.tail,
|
|
157
|
+
}
|
|
158
|
+
if res.matched and not in_window:
|
|
159
|
+
block["lateMatchAtSeconds"] = latency
|
|
160
|
+
block["lateMatchedLine"] = res.matched_line
|
|
161
|
+
return block
|
|
162
|
+
except OSError as err:
|
|
163
|
+
return {"port": spec.port, "baud": spec.baud, "observed": False, "error": str(err)}
|
|
164
|
+
finally:
|
|
165
|
+
try:
|
|
166
|
+
ser.close()
|
|
167
|
+
except OSError:
|
|
168
|
+
pass
|