Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*.{py,c,h,lua,md,json,toml,yml,yaml}]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4

[Makefile]
indent_style = tab
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ dkms.conf
*.dwo
/host/sparam/__pycache__
/host/tests/__pycache__
/host/gui/__pycache__
/host/gui/styles/__pycache__
/host/gui/widgets/__pycache__
.vscode/c_cpp_properties.json
.vscode/compile_commands.json
/host/__pycache__
/host/.pytest_cache/
/host/.venv/
/.xmake
82 changes: 82 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,88 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "sparam: host format",
"type": "shell",
"command": "uv",
"args": [
"run",
"--extra",
"dev",
"ruff",
"format",
"."
],
"options": {
"cwd": "${workspaceFolder}/host"
},
"problemMatcher": []
},
{
"label": "sparam: host lint",
"type": "shell",
"command": "uv",
"args": [
"run",
"--extra",
"dev",
"ruff",
"check",
"."
],
"options": {
"cwd": "${workspaceFolder}/host"
},
"problemMatcher": []
},
{
"label": "sparam: host type-check",
"type": "shell",
"command": "uv",
"args": [
"run",
"--extra",
"dev",
"mypy",
"cli.py",
"gui",
"sparam",
"tests"
],
"options": {
"cwd": "${workspaceFolder}/host"
},
"problemMatcher": []
},
{
"label": "sparam: host quality",
"type": "shell",
"command": "echo Host quality checks done",
Comment on lines +59 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当前 sparam: host quality 任务的 command 只是打印一条消息,并没有实际执行质量检查。为了确保此任务能够完整运行所有质量检查,建议将其 command 更新为按顺序执行 formatlinttype-checktest 任务的命令。

Suggested change
"type": "shell",
"command": "echo Host quality checks done",
"command": "uv run --extra dev ruff format . && uv run --extra dev ruff check . && uv run --extra dev mypy cli.py gui sparam tests && QT_QPA_PLATFORM=offscreen uv run --extra gui --extra test pytest -q",

"dependsOn": [
"sparam: host format",
"sparam: host lint",
"sparam: host type-check",
"sparam: host test"
],
"dependsOrder": "sequence",
"problemMatcher": []
},
{
"label": "sparam: host test",
"type": "shell",
"command": "uv",
"args": [
"run",
"--extra",
"test",
"pytest",
"-q"
],
"options": {
"cwd": "${workspaceFolder}/host"
},
"problemMatcher": []
},
{
"label": "sparam: demo build",
"type": "shell",
Expand Down
8 changes: 7 additions & 1 deletion host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ Python host tools for sparam, including protocol encoding/decoding, device commu

## Development

- Install dependencies (core + test + gui): `uv sync --extra test --extra gui`
- Install dependencies (core + dev + test + gui): `uv sync --extra dev --extra test --extra gui`
- Format code: `uv run --extra dev ruff format .`
- Run linter: `uv run --extra dev ruff check .`
- Run type checks: `uv run --extra dev mypy cli.py gui sparam tests`
- Run tests: `uv run --extra test pytest -q`
- Run the full local quality gate:
`uv run --extra dev ruff format . && uv run --extra dev ruff check . && uv run --extra dev mypy cli.py gui sparam tests && uv run --extra test pytest -q`

## GUI (PySide6)

- If you already ran `uv sync` only, add GUI deps with: `uv sync --extra gui`
- Launch GUI by script: `uv run sparam-gui`
- Or launch from CLI command: `uv run sparam gui`
- Launch the synthetic preview window: `uv run --extra gui sparam-gui-mock`

The GUI currently supports:

Expand Down
57 changes: 32 additions & 25 deletions host/cli.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import click
import csv
import struct
import sys
from typing import Optional
import time
from typing import Optional, TextIO, Tuple

import click

from sparam import (
SerialConnection,
DataType,
Device,
ElfParser,
DataType,
Protocol,
SerialConnection,
)


@click.group()
@click.pass_context
def main(ctx):
def main(ctx: click.Context) -> None:
ctx.ensure_object(dict)


def launch_gui():
def launch_gui() -> None:
try:
from gui import run_gui
except ImportError as exc:
click.echo(
"GUI dependencies are missing: "
f"{exc}. Install with `uv sync --extra gui`.",
f"GUI dependencies are missing: {exc}. Install with `uv sync --extra gui`.",
err=True,
)
sys.exit(1)
Expand All @@ -33,7 +35,7 @@ def launch_gui():


@main.command()
def list_ports():
def list_ports() -> None:
ports = SerialConnection.list_ports()
for port in ports:
click.echo(port)
Expand All @@ -43,7 +45,7 @@ def list_ports():
@click.argument("filepath", type=click.Path(exists=True))
@click.option("--prefix", "-p", default=None, help="Filter variables by prefix")
@click.option("--size", "-s", default=0, help="Filter by minimum size")
def parse_elf(filepath: str, prefix: Optional[str], size: int):
def parse_elf(filepath: str, prefix: Optional[str], size: int) -> None:
parser = ElfParser()
variables = parser.parse(filepath)

Expand All @@ -64,7 +66,7 @@ def parse_elf(filepath: str, prefix: Optional[str], size: int):
@click.option("--baud", "-b", default=115200, help="Baud rate")
@click.option("--device-id", "-d", default=1, help="Device ID")
@click.option("--timeout", "-t", default=1.0, help="Timeout in seconds")
def ping(port: str, baud: int, device_id: int, timeout: float):
def ping(port: str, baud: int, device_id: int, timeout: float) -> None:
conn = SerialConnection(port, baud, timeout)
if not conn.open():
click.echo("Failed to open port", err=True)
Expand All @@ -89,7 +91,14 @@ def ping(port: str, baud: int, device_id: int, timeout: float):
"--var", "-v", multiple=True, required=True, help="Variable names to read"
)
@click.option("--timeout", "-t", default=1.0, help="Timeout in seconds")
def read(port: str, baud: int, device_id: int, elf: str, var: tuple, timeout: float):
def read(
port: str,
baud: int,
device_id: int,
elf: str,
var: Tuple[str, ...],
timeout: float,
) -> None:
conn = SerialConnection(port, baud, timeout)
if not conn.open():
click.echo("Failed to open port", err=True)
Expand Down Expand Up @@ -120,7 +129,7 @@ def read(port: str, baud: int, device_id: int, elf: str, var: tuple, timeout: fl
dtype = DataType(v.dtype_code)
val = struct.unpack(dtype.format_char, value)[0]
click.echo(f"{name} = {val}")
except:
except (struct.error, ValueError):
click.echo(f"{name} = {value.hex()}")
else:
click.echo(f"{name} = {value.hex()}")
Expand Down Expand Up @@ -153,7 +162,7 @@ def write(
value: float,
var_type: str,
timeout: float,
):
) -> None:
type_map = {
"uint8": DataType.UINT8,
"int8": DataType.INT8,
Expand Down Expand Up @@ -198,7 +207,7 @@ def write(
@click.option("--baud", "-b", default=115200, help="Baud rate")
@click.option("--device-id", "-d", default=1, help="Device ID")
@click.option("--timeout", default=1.0, help="Timeout in seconds")
def stop(port: str, baud: int, device_id: int, timeout: float):
def stop(port: str, baud: int, device_id: int, timeout: float) -> None:
conn = SerialConnection(port, baud, timeout)
if not conn.open():
click.echo("Failed to open port", err=True)
Expand All @@ -222,7 +231,9 @@ def stop(port: str, baud: int, device_id: int, timeout: float):
"-r",
default=3,
type=click.IntRange(1, 8),
help="Sample rate (1=1ms, 2=5ms, 3=10ms, 4=20ms, 5=50ms, 6=100ms, 7=200ms, 8=500ms)",
help=(
"Sample rate (1=1ms, 2=5ms, 3=10ms, 4=20ms, 5=50ms, 6=100ms, 7=200ms, 8=500ms)"
),
)
@click.option("--output", "-o", default=None, help="Output CSV file")
@click.option("--count", "-c", default=0, help="Number of samples (0 for infinite)")
Expand All @@ -235,10 +246,7 @@ def monitor(
rate: int,
output: Optional[str],
count: int,
):
import time
import csv

) -> None:
conn = SerialConnection(port, baud, 1.0)
if not conn.open():
click.echo("Failed to open port", err=True)
Expand All @@ -260,8 +268,7 @@ def monitor(
conn.close()
sys.exit(1)

csv_file = None
csv_writer = None
csv_file: Optional[TextIO] = None
if output:
csv_file = open(output, "w", newline="")
csv_writer = csv.writer(csv_file)
Expand All @@ -270,15 +277,15 @@ def monitor(
sample_count = 0
running = True

def on_data(name: str, value: bytes):
def on_data(name: str, value: bytes) -> None:
nonlocal sample_count, running
v = device.get_variable(name)
if v and v.dtype_code:
try:
dtype = DataType(v.dtype_code)
val = struct.unpack(dtype.format_char, value)[0]
click.echo(f"{name} = {val}")
except:
except (struct.error, ValueError):
click.echo(f"{name} = {value.hex()}")
else:
click.echo(f"{name} = {value.hex()}")
Expand Down Expand Up @@ -306,7 +313,7 @@ def on_data(name: str, value: bytes):


@main.command()
def gui():
def gui() -> None:
"""Launch the desktop GUI."""
launch_gui()

Expand Down
Loading