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
2 changes: 1 addition & 1 deletion debian/requirements-bundle.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
asyncudp==0.11.0
pyvips==3.2.0
raydriver==0.3.0
raydriver==0.5.1
raygeo==1.66.0
ruida-pa==0.22.0
trimesh==5.1.1
Expand Down
46 changes: 23 additions & 23 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ patchelf = "==0.19.2"

[feature.app.pypi-dependencies]
raygeo = "==1.66.0"
raydriver = "==0.3.0"
raydriver = "==0.5.1"
rayforge = { editable = true, path = "." }
ruida-pa = "==0.22.0"
aiohttp = "==3.14.5"
Expand Down
116 changes: 75 additions & 41 deletions rayforge/machine/driver/grbl/grbl_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
)

import serial
from raydriver.grbl import GrblSession
from raydriver.grbl import GrblSession, SessionEvent
from raydriver.grbl.parser import (
error_code_to_device_error,
extract_device_name_from_output,
Expand Down Expand Up @@ -54,6 +54,7 @@
)

if TYPE_CHECKING:
from raydriver.grbl.types import DeviceState as SessionDeviceState
from raygeo.ops import Ops

from ....core.doc import Doc
Expand Down Expand Up @@ -265,49 +266,82 @@ def _setup_implementation(self, **kwargs: Any) -> None:
event_callback=self._on_session_event,
)

def _on_session_event(self, name: str, payload: Any) -> None:
def _on_session_event(self, event: SessionEvent) -> None:
"""Re-emit Rust session events as Rayforge blinker signals."""
if name == "state_changed":
state = from_session_state(payload)
old_status = self.state.status
self.state = state
if state.status != old_status:
logger.info(
f"Device state changed: {state.status.name}",
extra=self._log_extra("STATE_CHANGE"),
)
self.state_changed.send(self, state=state)
elif name == "connection_status_changed":
status_str, message = payload
match event:
case SessionEvent.StateChanged():
self._on_state_changed(event.state)
case SessionEvent.ConnectionStatusChanged():
self._on_connection_status_changed(event.status, event.message)
case SessionEvent.CommandStatusChanged():
self._on_command_status_changed(event.status, event.message)
case SessionEvent.JobFinished():
self.job_finished.send(self)
case SessionEvent.ProbeStatusChanged():
self.probe_status_changed.send(self, message=event.message)
case SessionEvent.WcsUpdated():
self.wcs_updated.send(self, offsets=event.offsets)
case SessionEvent.ConfigChanged():
self._on_config_changed(event.key, event.value)
case SessionEvent.RawRx():
self._log_raw_line(event.line, received=True)
case SessionEvent.RawTx():
self._log_raw_line(event.line, received=False)
case _:
logger.debug(f"Ignoring session event: {event!r}")

def _on_state_changed(self, session_state: "SessionDeviceState") -> None:
state = from_session_state(session_state)
old_status = self.state.status
self.state = state
if state.status != old_status:
logger.info(
f"Connection status: {status_str}"
+ (f" - {message}" if message else ""),
extra=self._log_extra("MACHINE_EVENT"),
)
self.connection_status_changed.send(
self,
status=TransportStatus[status_str],
message=message,
f"Device state changed: {state.status.name}",
extra=self._log_extra("STATE_CHANGE"),
)
elif name == "command_status_changed":
status_str, message = payload
self.command_status_changed.send(
self,
status=TransportStatus[status_str],
message=message,
)
elif name == "job_finished":
self.job_finished.send(self)
elif name == "probe_status_changed":
self.probe_status_changed.send(self, message=payload)
elif name == "wcs_updated":
self.wcs_updated.send(self, offsets=payload)
elif name == "config_changed":
key, value = payload
self.config[key] = value
self.config_changed.send(self)
else: # pragma: no cover - unknown events are ignored
logger.debug(f"Ignoring session event: {name}")
self.state_changed.send(self, state=state)

def _on_connection_status_changed(
self, status_str: str, message: str | None
) -> None:
logger.info(
f"Connection status: {status_str}"
+ (f" - {message}" if message else ""),
extra=self._log_extra("MACHINE_EVENT"),
)
self.connection_status_changed.send(
self,
status=TransportStatus[status_str],
message=message,
)

def _on_command_status_changed(
self, status_str: str, message: str | None
) -> None:
self.command_status_changed.send(
self,
status=TransportStatus[status_str],
message=message,
)

def _on_config_changed(self, key: str, value: int) -> None:
self.config[key] = value
self.config_changed.send(self)

def _log_raw_line(self, line: str, received: bool) -> None:
"""Log a raw line from the session's communication so the
console (with 'Show verbose' enabled) shows the full
conversation with the device."""
if received:
if line.startswith("<") and line.endswith(">"):
category = "STATUS_POLL"
elif line == "ok" or line.startswith("error:"):
category = "MACHINE_RESPONSE"
else:
category = "MACHINE_EVENT"
else:
category = "RAW_IO"
logger.info(line, extra=self._log_extra(category))

async def cleanup(self):
logger.debug("Cleanup initiated.")
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
raydriver==0.3.0
raydriver==0.5.1
raygeo==1.66.0
aiohttp==3.14.5
asyncudp==0.11.0
Expand Down
Loading
Loading