Skip to content

amyboard: unified amyboard.encoder() API across all rotary encoders#1063

Merged
bwhitman merged 2 commits into
mainfrom
claude/brave-shamir-c27da1
Jun 30, 2026
Merged

amyboard: unified amyboard.encoder() API across all rotary encoders#1063
bwhitman merged 2 commits into
mainfrom
claude/brave-shamir-c27da1

Conversation

@bwhitman

Copy link
Copy Markdown
Collaborator

What

Adds a single, hardware-agnostic rotary-encoder API to AMYboard so sketches — and the "prompt to sketch" generator — no longer have to special-case each accessory.

The three supported devices differ in encoder count, LED layout, and I2C protocol:

Device Addr Encoders LEDs
Adafruit I2C QT Rotary Encoder 0x36 1 1
Adafruit Quad Rotary Encoder Breakout 0x49 4 4
M5Stack Unit 8Encoder 0x41 8 8 (+ toggle switch)
enc = amyboard.encoder()   # autodetects via I2C scan (or the web simulator)
enc.type        # "adafruit_single" | "adafruit_quad" | "m5stack" | "web" | None
enc.encoders    # 1, 4, or 8
enc.leds        # addressable LED count
enc.read(i)     # cumulative position, zeroed at construction so it starts at 0
enc.button(i)   # True while held (normalized across devices)
enc.led(i,r,g,b)# 0..255 each, applied immediately
enc.reset(i)    # zero one encoder (or all if i omitted)
enc.switch()    # M5Stack toggle (False on other devices)

Every method returns a safe default when no hardware is present (enc.encoders == 0), so a sketch written for 8 knobs still runs on 1 knob or none. The legacy per-device helpers (read_encoder(), read_buttons(), m5_8encoder, patch_selector()) are untouched.

Correctness note

The M5Stack 8Encoder button register is active-low (0 = pressed), verified against the M5 vendor library (if (!btn_status[i])). The unified button() normalizes everything to True == pressed. The repo's own accessories.md previously documented this backwards — fixed here.

Sketch generator

  • amyboardworld_db_api.py — system prompt now documents amyboard.encoder() and tells the model to loop over range(enc.encoders), guard LEDs with enc.leds, and drive params from the delta.
  • AMY_AGENTS.md — new section with a verified example (appended to the generator prompt at request time).

No refdocs sync needed — the generator's reference tools read docs/amyboard/ and tulip/shared/amyboard-py/ straight from the repo.

Testing

A CPython harness with stubbed I2C exercised all four device types (detection, offset-zeroed reads, button polarity, RGB-vs-GRB LED routing, reset, switch, out-of-range safety, forced type) — 31/31 pass.

🤖 Generated with Claude Code

Add a single, hardware-agnostic rotary-encoder API so sketches (and the
sketch generator) no longer have to special-case each accessory. The three
supported devices have different encoder counts, LED layouts and I2C
protocols:

  - Adafruit I2C QT Rotary Encoder (0x36): 1 encoder, 1 LED
  - Adafruit Quad Rotary Encoder Breakout (0x49): 4 encoders, 4 LEDs
  - M5Stack Unit 8Encoder (0x41): 8 encoders, 8 LEDs + toggle switch

amyboard.encoder() autodetects whichever is connected (or the web
simulator's emulated encoder) and returns an Encoder with one API:
read(i)/button(i)/led(i,r,g,b)/reset(i)/switch(), plus .type/.encoders/.leds.
Positions are zeroed at construction, buttons are normalized to True==pressed
(the M5 register is active-low: 0==pressed), and every method returns a safe
default when no hardware is present, so a sketch written for one device runs
unchanged on another or on none. The legacy per-device helpers are untouched.

Steer the sketch generator toward it: document encoder() in the system prompt
and add an AMY_AGENTS.md section with a verified example. Update the user docs
(accessories.md, python.md) and fix the wrong M5 button-polarity comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🔌 AMYboard PR preview

Editor + flasher: https://amyboard-pr-1063.vercel.app/editor/

This preview bundles this PR's firmware — its flasher only flashes this build (not the release). Rebuilt on every push; removed when the PR closes.

The hardware CI has been kicked off and should return within a few minutes, stand by!

@github-actions

Copy link
Copy Markdown

🌷 Tulip Web PR preview

Tulip Web: https://tulip-pr-1063.vercel.app/run/

Flash a Tulip to this build: on-device run tulip.upgrade(pr=1063).

Rebuilt on every push; removed when the PR closes.

…27da1

# Conflicts:
#	tulip/server/amyboardworld_db_api.py
@github-actions

Copy link
Copy Markdown

🎛️ HW CI (physical bench)

AMYboard (USB-MIDI + AMY zP → audio): ✅ PASS — flashed this PR’s firmware; all checks matched the references.

Tulip (TULIP4_R11; serial-REPL audio + WiFi screenshot): ✅ PASS — flashed this PR’s firmware; all checks matched the references.

⬇️ Artifacts: recordings · screenshot · serial logs · run logs

Self-hosted bench. Audio spectral-compared to ref/hwci_basic.wav + ref/tulip_basic.wav; Tulip screenshot pixel-compared to ref/tulip_screenshot.png. Both analog outs share one capture card, so the tests run sequentially.

@bwhitman

Copy link
Copy Markdown
Collaborator Author

Tested on hardware, works

@bwhitman bwhitman merged commit b924008 into main Jun 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant