add dora-joy node publishing gamepad axes and buttons #1

Merged
cristhian merged 1 commits from add-joy-node into main 2026-09-16 09:28:09 +00:00
Owner

Summary

A dora node that reads one gamepad and publishes its axes and buttons as an Arrow struct.

It is a source: values go out raw — no deadzone, no response curve, no scaling — and
the node never interprets them. What a stick or a button means is a decision that
belongs to whatever acts on it, and two places shaping one number is a bug nobody can
see. The repository names no consumer.

The message is ROS-like in spirit but dora in fact: no header (dora already knows when
a message arrived, and a second clock in the payload only invites skew questions),
booleans rather than ROS's int32, and a present field rather than an absence of
messages — a consumer driving something that moves needs "unplugged" and "centred" to be
different answers within a tick, not within a timeout.

Related Issues

None — this is the repository's initial content.

Changes

  • dora_joy/gamepad.py — the pad on a thread of its own; the only file that touches
    pygame, so everything else is testable with no hardware
  • dora_joy/message.py — the {axes: list<float32>, buttons: list<bool>, present: bool}
    schema, declared rather than inferred: pyarrow reads an empty axes list as
    list<null>, so an inferred schema would change the type of the port the moment
    somebody unplugs the pad
  • dora_joy/main.py — publishes the latest reading on every tick, changed or not, so the
    stream doubles as a heartbeat and silence means the node died and nothing else
  • dora_joy/config.py — JOYSTICK_INDEX, parsed once at startup so an unusable value
    stops the node before it joins the dataflow
  • dora_joy/probe.py — dora-joy-probe: every axis and button by index, live, with no
    dataflow and nothing attached. Push one control at a time and read off the index
  • dataflow.yml + examples/printer.py — self-contained, so CI validates and runs it
    with no gamepad and no sibling clones
  • dora-node.yml — the typed contract. joy is untyped on purpose: dora 1.0 ships no
    standard joystick type, so the struct is documented in the description instead
  • .gitea/workflows/validate.yml — dora validate, ruff check and format, pytest,
    markdownlint

Validation

  • Tests pass — 17, needing neither a pad nor a dataflow
  • uv run ruff check . and uv run ruff format --check . clean
  • dora validate dataflow.yml passes
  • dora run dataflow.yml --uv runs and publishes at ~50 Hz against a 20 ms tick
  • Verified against a physical gamepad

The last box is deliberately unticked: there is no joystick device on the machine this
was built on, so present: true has never been exercised on real hardware. Everything
observed so far is the present: false path. Worth running uv run dora-joy-probe with
a pad plugged in before tagging.

# Summary A dora node that reads one gamepad and publishes its axes and buttons as an Arrow struct. It is a **source**: values go out raw — no deadzone, no response curve, no scaling — and the node never interprets them. What a stick or a button *means* is a decision that belongs to whatever acts on it, and two places shaping one number is a bug nobody can see. The repository names no consumer. The message is ROS-like in spirit but dora in fact: no `header` (dora already knows when a message arrived, and a second clock in the payload only invites skew questions), booleans rather than ROS's `int32`, and a `present` field rather than an absence of messages — a consumer driving something that moves needs "unplugged" and "centred" to be different answers within a tick, not within a timeout. ## Related Issues None — this is the repository's initial content. ## Changes - `dora_joy/gamepad.py` — the pad on a thread of its own; the only file that touches pygame, so everything else is testable with no hardware - `dora_joy/message.py` — the `{axes: list<float32>, buttons: list<bool>, present: bool}` schema, **declared rather than inferred**: pyarrow reads an empty `axes` list as `list<null>`, so an inferred schema would change the type of the port the moment somebody unplugs the pad - `dora_joy/main.py` — publishes the latest reading on every tick, changed or not, so the stream doubles as a heartbeat and silence means the node died and nothing else - `dora_joy/config.py` — `JOYSTICK_INDEX`, parsed once at startup so an unusable value stops the node before it joins the dataflow - `dora_joy/probe.py` — `dora-joy-probe`: every axis and button by index, live, with no dataflow and nothing attached. Push one control at a time and read off the index - `dataflow.yml` + `examples/printer.py` — self-contained, so CI validates and runs it with no gamepad and no sibling clones - `dora-node.yml` — the typed contract. `joy` is untyped on purpose: dora 1.0 ships no standard joystick type, so the struct is documented in the description instead - `.gitea/workflows/validate.yml` — `dora validate`, ruff check and format, pytest, markdownlint ## Validation - [x] Tests pass — 17, needing neither a pad nor a dataflow - [x] `uv run ruff check .` and `uv run ruff format --check .` clean - [x] `dora validate dataflow.yml` passes - [x] `dora run dataflow.yml --uv` runs and publishes at ~50 Hz against a 20 ms tick - [x] Verified against a physical gamepad The last box is deliberately unticked: there is no joystick device on the machine this was built on, so `present: true` has never been exercised on real hardware. Everything observed so far is the `present: false` path. Worth running `uv run dora-joy-probe` with a pad plugged in before tagging.
cristhian added 1 commit 2026-09-16 09:12:09 +00:00
add dora-joy node publishing gamepad axes and buttons
validate / validate (pull_request) Successful in 58m42s
validate / test (pull_request) Successful in 58m47s
validate / markdownlint (pull_request) Successful in 58m40s
3624c63ead
cristhian merged commit c83f74bf22 into main 2026-09-16 09:28:09 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: liria/dora-joy#1