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

This commit is contained in:
2026-09-16 05:10:33 -05:00
parent 9f56aa8f75
commit 3624c63ead
24 changed files with 1478 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
"""The wire shape of a `joy` message.
dora 1.0 has no standard joystick type, so this schema is the whole contract a
consumer gets. It is **declared, never inferred**: pyarrow reads an empty
`axes` list as `list<null>`, which would mean the port silently changed type the
moment somebody unplugged the pad.
It is not `sensor_msgs/Joy`. There is no header, because dora already knows when
a message arrived and a second clock in the payload only invites skew questions;
buttons are booleans rather than ROS's `int32`, because a button is down or it is
not; and `present` is a field rather than an absence of messages, because a
consumer stopping a robot needs to tell "the pad is centred" from "the pad is
gone" within a tick rather than within a timeout.
"""
import pyarrow as pa
from dora_joy.gamepad import Sample
JOY = pa.struct(
[
# Raw, exactly as SDL reports it: no deadzone, no expo, no rescaling.
# Shaping is a decision about what the pad means, which belongs to
# whoever is driving something with it.
("axes", pa.list_(pa.float32())),
("buttons", pa.list_(pa.bool_())),
("present", pa.bool_()),
]
)
def pack(sample: Sample) -> pa.Array:
"""Build one `joy` message from a reading."""
return pa.array(
[
{
"axes": [float(value) for value in sample.axes],
"buttons": [bool(down) for down in sample.buttons],
"present": sample.present,
}
],
type=JOY,
)