docs: scaffold polisher-control foundation

This commit is contained in:
Nick Hermes
2026-05-26 16:23:04 +00:00
commit fa9c43fae8
52 changed files with 2224 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
"""Host-side scaffold for Fullum polisher-control."""
__version__ = "0.1.0"

View File

View File

@@ -0,0 +1,6 @@
CONTROLLER_SCHEMA_VERSION = "controller-job.v1"
RUN_LOG_SCHEMA_VERSION = "run-log.v1"
MANUAL_SESSION_SCHEMA_VERSION = "manual-session-log.v1"
MACHINE_CAPABILITIES_SCHEMA_VERSION = "machine-capabilities.v1"
MACHINE_ID = "fullum-alpha"
CONTROLLER_VERSION_PREFIX = "polisher-control/"

View File

@@ -0,0 +1,17 @@
from pathlib import Path
def manual_session_dir(data_root: Path, session_id: str) -> Path:
return data_root / "manual" / session_id
def run_dir(data_root: Path, run_id: str) -> Path:
return data_root / "runs" / run_id
def expected_manual_files(session_id: str) -> list[str]:
return [
"manual-session-log.v1.json",
"telemetry.csv",
"manifest.json",
]

View File

@@ -0,0 +1,30 @@
from enum import IntEnum
class MessageType(IntEnum):
HEARTBEAT = 1
MANUAL_START = 2
SETPOINT = 3
MANUAL_STOP = 4
SEGMENT_START = 5
PAUSE = 6
RESUME = 7
ABORT = 8
ESTOP = 9
ACK = 100
NACK = 101
TELEMETRY = 102
EVENT = 103
SEGMENT_DONE = 104
ABORT_COMPLETE = 105
class NackReason(IntEnum):
NONE = 0
BAD_CRC = 1
BAD_VERSION = 2
ILLEGAL_TRANSITION = 3
GEOMETRY_NOT_VALIDATED = 4
SAFETY_INTERLOCK_ACTIVE = 5
UNSUPPORTED_COMMAND = 6
VALUE_OUT_OF_RANGE = 7

View File

@@ -0,0 +1,65 @@
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
class MachineState(StrEnum):
IDLE = "IDLE"
JOB_LOADED = "JOB_LOADED"
READY = "READY"
RUNNING = "RUNNING"
PAUSED = "PAUSED"
ABORTING = "ABORTING"
COMPLETED = "COMPLETED"
ABORTED = "ABORTED"
FAULTED = "FAULTED"
MANUAL = "MANUAL"
TRANSITIONS: dict[tuple[MachineState, str], MachineState] = {
(MachineState.IDLE, "load_job"): MachineState.JOB_LOADED,
(MachineState.IDLE, "enter_manual"): MachineState.MANUAL,
(MachineState.JOB_LOADED, "operator_acknowledge"): MachineState.READY,
(MachineState.JOB_LOADED, "unload_job"): MachineState.IDLE,
(MachineState.READY, "start"): MachineState.RUNNING,
(MachineState.RUNNING, "pause"): MachineState.PAUSED,
(MachineState.RUNNING, "segment_complete"): MachineState.RUNNING,
(MachineState.RUNNING, "all_segments_complete"): MachineState.COMPLETED,
(MachineState.RUNNING, "abort"): MachineState.ABORTING,
(MachineState.RUNNING, "fault"): MachineState.FAULTED,
(MachineState.PAUSED, "resume"): MachineState.RUNNING,
(MachineState.PAUSED, "abort"): MachineState.ABORTING,
(MachineState.MANUAL, "exit_manual"): MachineState.IDLE,
(MachineState.MANUAL, "fault"): MachineState.FAULTED,
(MachineState.ABORTING, "abort_complete"): MachineState.ABORTED,
(MachineState.COMPLETED, "reset"): MachineState.IDLE,
(MachineState.ABORTED, "reset"): MachineState.IDLE,
(MachineState.FAULTED, "reset"): MachineState.IDLE,
}
class IllegalTransitionError(RuntimeError):
pass
@dataclass
class TransitionEvent:
from_state: MachineState
trigger: str
to_state: MachineState
class StateMachine:
def __init__(self, initial: MachineState = MachineState.IDLE) -> None:
self.state = initial
self.events: list[TransitionEvent] = []
def trigger(self, name: str) -> MachineState:
key = (self.state, name)
if key not in TRANSITIONS:
raise IllegalTransitionError(f"Illegal transition: {self.state} + {name}")
previous = self.state
self.state = TRANSITIONS[key]
self.events.append(TransitionEvent(previous, name, self.state))
return self.state

View File

@@ -0,0 +1,39 @@
CORE_CHANNELS = [
"timestamp_us",
"table_angle_deg",
"arm_angle_deg",
"fz_n",
"mx",
"my",
"mz",
"spindle_rpm_actual",
"table_rpm_actual",
"arm_amplitude_deg_derived",
"arm_center_deg_derived",
"machine_state",
"force_setpoint_n",
]
RECOMMENDED_CHANNELS = [
"fx_n",
"fy_n",
"ft_status",
"z_servo_iq_v",
"z_brake_engaged",
"spindle_drive_state",
"spindle_drive_error",
"spindle_bus_voltage_v",
"spindle_iq_a",
"spindle_motor_temp_c",
"arm_angle_linearized_deg",
"table_rpm_setpoint",
"spindle_rpm_setpoint",
"force_actuator_cmd",
"estop_active",
"interlock_state",
"mode",
"fz_raw_n",
"fz_contact_n",
]
CSV_HEADER = CORE_CHANNELS