Files
cristhian 3624c63ead
validate / validate (pull_request) Successful in 58m42s
validate / test (pull_request) Successful in 58m47s
validate / markdownlint (pull_request) Successful in 58m40s
add dora-joy node publishing gamepad axes and buttons
2026-09-16 05:10:33 -05:00

86 lines
2.8 KiB
Python

"""Show what the pad is doing, with no dataflow and nothing attached to it.
`dora-joy-probe` opens the gamepad and prints, live: every axis SDL reports and
every button it sees pressed, by index.
It exists because a consumer's axis and button indices are almost always SDL's
documented layout rather than an observed one, and a pad that numbers its
controls differently is otherwise discovered by watching a real machine move the
wrong way. Push one control at a time and read off the index it moves.
This shows the reading and nothing else — it has no idea what any index is *for*,
which is the same thing that makes this node reusable.
Nothing here can move anything. There is no dora node and no connection to
anything; this only reads.
"""
import argparse
import time
from dora_joy.config import Config
from dora_joy.gamepad import Gamepad
CLEAR = "\033[H\033[J"
def bar(value: float, width: int = 21) -> str:
"""Draw a centred bar for a value between -1 and 1.
Seeing the bar sit off-centre with the stick released is how a worn pad
announces itself, which is most of the point of looking at raw numbers.
"""
middle = width // 2
position = middle + round(max(-1.0, min(1.0, value)) * middle)
cells = ["-"] * width
cells[middle] = "|"
cells[position] = "#"
return "".join(cells)
def render(sample, config: Config) -> str:
"""Build the whole display for one reading."""
lines = ["dora-joy probe — Ctrl-C to stop", ""]
if not sample.present:
lines += [
" no gamepad found.",
"",
f" looking for joystick index {config.joystick_index};",
" plug one in and this will pick it up.",
]
return "\n".join(lines)
lines.append(f" axes ({len(sample.axes)} reported)")
for index, value in enumerate(sample.axes):
lines.append(f" {index} {value:+.3f} {bar(value)}")
held = [str(index) for index, down in enumerate(sample.buttons) if down]
lines += ["", f" buttons ({len(sample.buttons)} reported)", f" {', '.join(held) or 'none pressed'}"]
lines += ["", " push one control at a time and note the index it moves."]
return "\n".join(lines)
def main() -> None:
"""Open the pad and print what it is doing until interrupted."""
parser = argparse.ArgumentParser(description="Show what the gamepad is doing. Moves nothing.")
parser.add_argument("--hz", type=float, default=10.0, help="Redraw rate. Defaults to 10.")
args = parser.parse_args()
config = Config.from_env()
gamepad = Gamepad(config.joystick_index)
gamepad.start()
try:
while True:
print(CLEAR + render(gamepad.read(), config), flush=True)
time.sleep(1.0 / args.hz)
except KeyboardInterrupt:
print()
finally:
gamepad.shutdown()
if __name__ == "__main__":
main()