diff --git a/README.md b/README.md index 8919696..117eb71 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ and verification commands. | `RobotCalibrationRecorder` | Safely records released-arm limits and a home pose for one physical robot | | `RobotDriverLauncher` | Starts/stops a driver process from the descriptor | | `RobotConnectionDashboard` | Shows USB, driver, ROS interface, live joint positions, home references, safe ranges, and calibration source in one view | +| `RobotMonitor` | Opens a read-only live canvas view for a registered robot's connection, motion state, telemetry, streams, and joints | | `RobotROSInterfaceCheck` | Matches a live ROS graph to a supported robot interface profile without publishing commands | ## Hiwonder ROSOrin Pro diff --git a/blacknode-package.toml b/blacknode-package.toml index c81bc58..51a159a 100644 --- a/blacknode-package.toml +++ b/blacknode-package.toml @@ -1,6 +1,6 @@ [package] name = "blacknode-robot" -version = "0.3.0" +version = "0.3.1" description = "Generic robot discovery, USB permission checks, driver launch, and robot profile contracts." requires-blacknode = ">=0.3.0" layer = "robot" @@ -64,7 +64,7 @@ capabilities = ["robot.capabilities"] # Declared for the package roadmap; sources land in components/authorization/nodes. node-types = [ - "RobotConnectionDashboard", "RobotDiscovery", "RobotROSInterfaceCheck", "RobotUSBDiscovery" + "RobotConnectionDashboard", "RobotDiscovery", "RobotMonitor", "RobotROSInterfaceCheck", "RobotUSBDiscovery" ] [components.authorization] diff --git a/nodes/__init__.py b/nodes/__init__.py index e86affa..a6950a1 100644 --- a/nodes/__init__.py +++ b/nodes/__init__.py @@ -1,3 +1,4 @@ from . import interfaces # noqa: F401 +from . import monitoring # noqa: F401 from . import presets # noqa: F401 from . import robot # noqa: F401 diff --git a/nodes/monitoring.py b/nodes/monitoring.py new file mode 100644 index 0000000..3030a91 --- /dev/null +++ b/nodes/monitoring.py @@ -0,0 +1,31 @@ +"""Read-only robot monitoring targets for the Blacknode canvas.""" + +from blacknode.node import Dict, Text, node + + +@node( + name="RobotMonitor", + component="capabilities", + category="Robot", + inputs={ + "robot_id": Text(default=""), + "robot_name": Text(default=""), + }, + outputs={"robot": Dict}, + primary_inputs=[], + primary_outputs=["robot"], + description="Show a registered robot's live state, telemetry, and streams on the canvas.", +) +def robot_monitor(robot_id: str = "", robot_name: str = "") -> dict: + """Describe the stable, read-only target used by the live canvas monitor.""" + target_id = str(robot_id or "").strip() + target_name = str(robot_name or "").strip() + return { + "robot": { + "kind": "blacknode.robot-monitor-target", + "schema_version": 1, + "robot_id": target_id, + "robot_name": target_name, + "configured": bool(target_id), + }, + } diff --git a/tests/test_robot_nodes.py b/tests/test_robot_nodes.py index d8b6fc4..9488d57 100644 --- a/tests/test_robot_nodes.py +++ b/tests/test_robot_nodes.py @@ -20,6 +20,7 @@ "RobotDriverPreset", "Robot", "RobotConnectionDashboard", + "RobotMonitor", "RobotROSInterfaceCheck", "RobotJointDefinition", "RobotJointList", @@ -39,6 +40,23 @@ def test_robot_nodes_registered_with_category(): assert _NODE_REGISTRY[name]._bn_package == "blacknode-robot" +def test_robot_monitor_exposes_a_read_only_portable_target(): + fn = _NODE_REGISTRY["RobotMonitor"] + + result = fn({"robot_id": "robot-2", "robot_name": "Workshop arm"}) + + assert result["robot"] == { + "kind": "blacknode.robot-monitor-target", + "schema_version": 1, + "robot_id": "robot-2", + "robot_name": "Workshop arm", + "configured": True, + } + assert fn._bn_component == "capabilities" + assert fn._bn_primary_inputs == [] + assert fn._bn_primary_outputs == ["robot"] + + def test_rosorin_interface_check_matches_documented_topic_variants(): result = _NODE_REGISTRY["RobotROSInterfaceCheck"]({ "preset": "hiwonder_rosorin_pro",