Skip to content
Merged
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
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,33 @@ jobs:
with:
python-version: "3.11"

- uses: actions/setup-node@v4
with:
node-version: "20"

- name: Build monitor dashboard (SPA)
run: |
cd damiao_motor/gui/webapp
npm ci
npm run build

- name: Install build tools
run: python -m pip install --upgrade build twine

- name: Build package
run: python -m build

- name: Verify SPA bundle is in the wheel
run: |
python - <<'PY'
import glob, zipfile, sys
whl = sorted(glob.glob("dist/*.whl"))[-1]
names = zipfile.ZipFile(whl).namelist()
assert any("gui/webapp/dist/index.html" in n for n in names), "SPA index.html missing from wheel"
assert any("monitor/server.py" in n for n in names), "monitor package missing from wheel"
print("OK: SPA + monitor present in", whl)
PY

- name: Upload to PyPI
run: python -m twine upload dist/*
env:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ dist/
*.egg
damiao_motor/_version.py

# ...but DO ship the built monitor SPA (end users have no node toolchain).
# These negations override the unanchored build/ and dist/ rules above.
!damiao_motor/gui/webapp/dist/
!damiao_motor/gui/webapp/dist/**
damiao_motor/gui/webapp/node_modules/

# IDE / editor
.vscode/
.idea/
Expand Down
73 changes: 73 additions & 0 deletions damiao_motor/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
cmd_send_cmd_vel,
cmd_send_cmd_force_pos,
cmd_gui,
cmd_monitor,
)
from .formatter import ColorizedHelpFormatter

Expand Down Expand Up @@ -161,6 +162,78 @@ def unified_main() -> None:
)
gui_parser.set_defaults(func=cmd_gui)

# monitor command (passive, listen-only dashboard)
monitor_parser = subparsers.add_parser(
"monitor",
help="Launch passive (listen-only) realtime monitoring dashboard",
description=(
"Launch a listen-only dashboard that decodes both the commands another "
"controller is sending and the motors' feedback, in realtime. Never transmits."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Monitor a socketcan bus while another controller drives the motors
damiao monitor --channel can0

# I2RT-style feedback id scheme is +16 (the default); override if needed
damiao monitor --channel can_arm_l --feedback-offset 16

# Try the dashboard with synthetic traffic (no CAN hardware needed)
damiao monitor --demo
""",
)
monitor_parser.add_argument(
"--host",
type=str,
default="127.0.0.1",
help="Host to bind to (default: 127.0.0.1)",
)
monitor_parser.add_argument(
"--port", type=int, default=5001, help="Port to bind to (default: 5001)"
)
monitor_parser.add_argument(
"--channel",
type=str,
default="can0",
help="CAN channel to listen on (default: can0)",
)
monitor_parser.add_argument(
"--bustype",
type=str,
default="socketcan",
help="CAN bus type (default: socketcan)",
)
monitor_parser.add_argument(
"--bitrate",
type=int,
default=None,
help="CAN bitrate (required for some interfaces, e.g. gs_usb)",
)
monitor_parser.add_argument(
"--feedback-offset",
type=int,
default=16,
dest="feedback_offset",
help="feedback arb id = motor id + offset (default: 16, the p16 scheme)",
)
monitor_parser.add_argument(
"--motor-type",
type=str,
default="DM4310",
dest="default_motor_type",
help="Default motor type for value scaling (default: DM4310)",
)
monitor_parser.add_argument(
"--demo",
action="store_true",
help="Synthesize traffic instead of opening a CAN bus",
)
monitor_parser.add_argument(
"--debug", action="store_true", help="Enable debug mode"
)
monitor_parser.set_defaults(func=cmd_monitor)

# Helper function to add global arguments to subcommands
def add_global_args(subparser, include_motor_type: bool = True):
"""Add global arguments to a subcommand parser."""
Expand Down
36 changes: 33 additions & 3 deletions damiao_motor/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import time

from damiao_motor.core.controller import DaMiaoController
from damiao_motor.gui import web_gui
from .display import (
BOX_CORNER_TL,
BOX_CORNER_TR,
Expand Down Expand Up @@ -718,8 +717,39 @@ def cmd_gui(args) -> None:
- debug: Enable debug mode (default: False)
- production: Use production WSGI server (default: False)
"""
web_gui.run_server(
host=args.host, port=args.port, debug=args.debug, production=args.production
# `gui` now launches the unified DaMiao Studio in control mode (active control +
# the realtime monitor in one UI). The legacy Flask GUI remains in web_gui.py.
from damiao_motor.monitor import server as studio_server

studio_server.run_server(
host=args.host, port=args.port, mode="control", debug=args.debug
)


def cmd_monitor(args) -> None:
"""
Handle 'monitor' subcommand.

Launches the passive (listen-only) realtime monitoring dashboard. It decodes both
the commands another controller is sending and the motors' feedback, and never
transmits on the bus.

Args:
args: Parsed command-line arguments containing host, port, channel, bustype,
bitrate, feedback_offset, default_motor_type, demo, debug.
"""
from damiao_motor.monitor import server as monitor_server

monitor_server.run_server(
host=args.host,
port=args.port,
channel=args.channel,
bustype=args.bustype,
bitrate=args.bitrate,
feedback_offset=args.feedback_offset,
default_motor_type=args.default_motor_type,
debug=args.debug,
demo=args.demo,
)


Expand Down
2 changes: 1 addition & 1 deletion damiao_motor/core/motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def __init__(
motor_id: int,
feedback_id: int,
bus: can.Bus,
fd: bool,
fd: bool = False,
*,
motor_type: str,
p_min: Optional[float] = None,
Expand Down
54 changes: 54 additions & 0 deletions damiao_motor/gui/webapp/dist/assets/index-CDw8n6KX.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions damiao_motor/gui/webapp/dist/assets/index-CrxIlrMA.css

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions damiao_motor/gui/webapp/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DaMiao Monitor</title>
<script type="module" crossorigin src="./assets/index-CDw8n6KX.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CrxIlrMA.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
12 changes: 12 additions & 0 deletions damiao_motor/gui/webapp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DaMiao Monitor</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading