Skip to content
Closed
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
53 changes: 28 additions & 25 deletions parol6/server/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import sys
import threading
import time
from dataclasses import dataclass, replace
from dataclasses import dataclass
from typing import Any

import psutil
from waldoctl import ActionState

from parol6.ack_policy import AckPolicy
from parol6.commands.base import (
Expand All @@ -29,9 +31,18 @@
StopCommand,
)
from parol6.commands.utility_commands import ResetStateCommand
from parol6.server.command_executor import CommandExecutor, QueueFullError
from parol6.server.motion_planner import MotionPlanner, PlanCommand
from parol6.server.segment_player import SegmentPlayer
from parol6.config import (
INTERVAL_S,
MAX_POLL_COUNT,
MCAST_GROUP,
MCAST_IF,
MCAST_PORT,
MCAST_TTL,
STATUS_BROADCAST_INTERVAL,
STATUS_RATE_HZ,
STATUS_STALE_S,
TRACE,
)
from parol6.protocol.wire import (
CommandCode,
ToolActionCmd,
Expand All @@ -40,43 +51,31 @@
pack_ok_index,
unpack_rx_frame_into,
)
from parol6.utils.error_catalog import RobotError, extract_robot_error, make_error
from parol6.utils.error_codes import ErrorCode
from parol6.server.async_logging import AsyncLogHandler
from parol6.server.command_executor import CommandExecutor, QueueFullError
from parol6.server.command_registry import (
CommandCategory,
create_command,
create_command_from_struct,
discover_commands,
)
from parol6.server.state import ControllerState, StateManager
from waldoctl import ActionState
from parol6.server.status_broadcast import StatusBroadcaster
from parol6.server.async_logging import AsyncLogHandler
from parol6.server.loop_timer import (
EventRateMetrics,
GCTracker,
LoopTimer,
PhaseTimer,
format_hz_summary,
)
from parol6.server.motion_planner import MotionPlanner, PlanCommand
from parol6.server.segment_player import SegmentPlayer
from parol6.server.state import ControllerState, StateManager
from parol6.server.status_broadcast import StatusBroadcaster
from parol6.server.status_cache import close_cache, get_cache
from parol6.server.transport_manager import TransportManager
from parol6.server.transports.mock_serial_transport import MockSerialTransport
from parol6.server.transports.udp_transport import UDPTransport
from parol6.config import (
TRACE,
INTERVAL_S,
MAX_POLL_COUNT,
MCAST_GROUP,
MCAST_PORT,
MCAST_IF,
MCAST_TTL,
STATUS_RATE_HZ,
STATUS_STALE_S,
STATUS_BROADCAST_INTERVAL,
)

import psutil
from parol6.utils.error_catalog import RobotError, extract_robot_error, make_error
from parol6.utils.error_codes import ErrorCode

logger = logging.getLogger("parol6.server.controller")

Expand Down Expand Up @@ -415,7 +414,11 @@ def _tick_tool_cmd(self, state: ControllerState) -> None:
raw_error = self._tool_cmd.robot_error or make_error(
ErrorCode.MOTN_TICK_FAILED, detail=type(self._tool_cmd).__name__
)
state.error = replace(raw_error, command_index=self._tool_cmd_index)
# The refusal type is an exception now, not a dataclass, so
# re-attributing it is a rebuild from its own wire fields.
attributed = raw_error.to_wire()
attributed[0] = self._tool_cmd_index
state.error = RobotError.from_wire(attributed)
state.action_state = ActionState.ERROR
state.completed_command_index = max(
state.completed_command_index, self._tool_cmd_index
Expand Down
43 changes: 6 additions & 37 deletions parol6/utils/error_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,14 @@

from dataclasses import dataclass

from .error_codes import ErrorCode


@dataclass(frozen=True)
class RobotError:
"""Structured error with code, title, cause, effect, and remedy."""

command_index: int
code: int
title: str
cause: str
effect: str
remedy: str
from waldoctl.errors import RobotError as _RobotError

def to_wire(self) -> list:
"""Serialize to a list for ormsgpack packing."""
return [
self.command_index,
self.code,
self.title,
self.cause,
self.effect,
self.remedy,
]

@staticmethod
def from_wire(data: list) -> RobotError:
"""Reconstruct from a wire-format list."""
return RobotError(
command_index=data[0],
code=data[1],
title=data[2],
cause=data[3],
effect=data[4],
remedy=data[5],
)
from .error_codes import ErrorCode

def __str__(self) -> str:
return f"[{self.code}] {self.title}: {self.cause}"
# The refusal type is waldoctl's: a frontend represents a refused command
# the same way whichever backend raised it. Same six fields, same wire
# list in both directions, and an exception a client can raise as-is.
RobotError = _RobotError


@dataclass(frozen=True)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies = [
"psutil>=5.9",
"msgspec>=0.18",
"ormsgpack>=1.4.0",
"waldoctl @ git+https://github.com/Jepson2k/waldoctl.git@v0.7.0",
"waldoctl @ git+https://github.com/Jepson2k/waldoctl.git@v0.11.2",
]

[tool.setuptools.packages.find]
Expand Down
Loading