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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ PC/host end of the line-numbered, checksummed, `ok`-acknowledged G-code
conversation that hosts like OctoPrint and Pronterface speak to a printer or
CNC running Marlin. Neutral and standalone; not tied to any one application.

> **Status:** early scaffolding. Developed against the Marlin firmware source
> and official docs. Licensed Apache-2.0.
> **Status:** MVP. Connect, M115 negotiation, reliable framed send, streaming,
> queries, and out-of-band e-stop are implemented and **validated against real
> hardware** (RAMPS 1.4 / Marlin `bugfix-2.1.x`, logic level — motion/thermal
> validation under power is ongoing). Developed against the Marlin firmware
> source and official docs. Licensed Apache-2.0.

## Repository conventions

Expand Down
10 changes: 10 additions & 0 deletions marlin_host/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,16 @@ def capabilities(self) -> Profile:
firmware, caps, _ = self._query_m115()
return Profile(firmware=firmware, caps=caps)

def temperatures(self) -> Mapping[str, float]:
"""Query M105 and return the reported temperature fields, e.g.
``{'T': 20.6, 'B': 0.0, '@': 0.0}`` (hotend ``T``, bed ``B``, power ``@``).

Unlike M114, Marlin carries the M105 report on the ``ok`` line itself, so —
unlike :meth:`query` — the data is the terminal response's fields, not a
preceding report line. Returns an empty mapping if the board reports none.
"""
return self.send("M105").fields or {}

def stream(
self, program: Iterable[str], *, poll_interval: float = DEFAULT_PAUSE_POLL
) -> Iterator[StreamProgress]:
Expand Down
5 changes: 3 additions & 2 deletions marlin_host/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ class SerialTransport:
``reset_on_open=False`` to skip the pulse (native-USB boards that ignore DTR,
or to attach to a running controller).

NOTE: the DTR-pulse timing is board-specific and unvalidated against hardware;
expect to tune :meth:`reset` during the first real session — see issue #4.
NOTE: the DTR-pulse timing is board-specific. The default ``assert_hold=0.2``
is validated on RAMPS 1.4 / Marlin ``bugfix-2.1.x`` (reliably catches ``start``);
other adapters may need tuning — see issue #4.
"""

def __init__(
Expand Down
2 changes: 1 addition & 1 deletion tests/hardware/test_hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_connect_and_negotiate(rig: SimpleNamespace) -> None:

def test_queries(rig: SimpleNamespace) -> None:
assert rig.host.query("M114") # at least the position report line
assert rig.host.send("M105").is_ack # temperature rides on the `ok` line
assert "T" in rig.host.temperatures() # hotend temp, read off the `ok` line


def test_reliable_stream(rig: SimpleNamespace) -> None:
Expand Down
14 changes: 14 additions & 0 deletions tests/test_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,17 @@ def test_reliable_send_recovers_from_back_to_back_resends() -> None:
assert host.send("G1 X20").is_ack
assert sim.expected == 3
assert t.read_line() is None


def test_temperatures_reads_fields_from_the_ok_line() -> None:
# Marlin answers M105 on the `ok` line itself (real RAMPS: `ok T:20.63 /0.00 @:0`),
# so the temps are terminal-response fields, not a preceding report line.
host = MarlinHost(FakeTransport(responder=lambda _line: ["ok T:20.63 /0.00 @:0"]))
temps = host.temperatures()
assert temps["T"] == pytest.approx(20.63)
assert temps["@"] == pytest.approx(0.0)


def test_temperatures_empty_when_board_reports_none() -> None:
host = MarlinHost(FakeTransport(responder=lambda _line: ["ok"]))
assert host.temperatures() == {}
Loading