Companion callout#20
Open
skitzo2000 wants to merge 26 commits into
Open
Conversation
Rewrite platformio.ini per PORT.md §4 for ESP32-2432S028R (CYD): TFT_eSPI pin map in build_flags (ILI9341 driver, MISO=12 MOSI=13 SCLK=14 CS=15 DC=2 BL=21), 240 MHz CPU, XPT2046_Touchscreen + AnimatedGIF + ArduinoJson deps. Restrict build_src_filter to +<main.cpp> until HAL exists in M2. Two deviations from PORT.md §4 (noted inline): - platform pinned to espressif32 @ ~6.10.0 (Arduino-ESP32 2.x). The unpinned 7.x core needs the framework-arduinoespressif32-libs sidecar package and PIO did not install it; esp32dev variant folder also gone in that layout. 6.10 is the last single-package release. - paulstoffregen/XPT2046_Touchscreen version constraint dropped; PIO registry only carries 0.0.0-alpha+sha for that author/lib. Preserve upstream main.cpp as main.cpp.upstream for line-by-line reference during M2 HAL swap. New main.cpp is empty setup/loop + boot banner. Verified on user's CYD: clean build (RAM 6.5%, Flash 12.5%), flashable over /dev/ttyUSB0, serial prints boot banner. Display sanity sketch (ad-hoc M1.5, reverted) confirmed ILI9341 driver + pin map render correctly — no need for the ILI9341_2 fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add src/hal/board.{h,cpp} exporting `tft`, `ts`, boardBegin(), boardLoop()
per PORT.md §3 pin map + §4 dual-SPI pattern. Display lives on the default
SPI instance (VSPI) — pin map comes from build_flags. Touch uses a
separate HSPI bus (SCK=25, MISO=39, MOSI=32) bound via SPIClass; TFT_eSPI's
built-in TOUCH_CS sharing does NOT apply on the CYD.
Widen build_src_filter to +<main.cpp> +<hal/> (widens again in M2.3 once
the main.cpp swap and buddies/ port land).
XPT2046_Touchscreen lib source: switched from PIO registry to GitHub HEAD
(PaulStoffregen). The registry alpha predates the begin(SPIClass&)
overload required for binding touch to HSPI rather than the default SPI.
Verified on hardware: boardBegin() returns without crash; serial confirms
`cyd-claudepet: boardBegin() returned`. Bottleneck cleared for M2.2's
6-way parallel HAL fan-out (input/imu/rtc/audio/led/power).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six HAL modules in src/hal/ providing thin wrappers per PORT.md §5A so
main.cpp can be swapped from M5.* to hal/* in M2.3 without caring about
board specifics.
- input.{h,cpp}: stub InputButton (wasPressed/wasReleased/isPressed/
pressedFor) returning false; inputA/inputB externs. Real touch-zone
hit-test arrives in M3.
- imu.{h,cpp}: stub imuRead → (0, 0, 1.0). Face-up forever, never
shaken. Real MPU6050 path TBD behind -DHAS_IMU.
- rtc.{h,cpp}: real software RTC. time_t + millis() drift, NVS-backed
in namespace "rtc"/key "epoch". Saves on minute rollover only
(~1440 writes/day max). Drop-in RTC_TimeTypeDef / RTC_DateTypeDef
matching M5 field layout. Default epoch 2025-01-01.
- audio.{h,cpp}: real LEDC tone scheduler on GPIO 26 (DAC2), channel 2.
Non-blocking; auto-stop after dur ms via audioLoop().
- led.{h,cpp}: real RGB LED on GPIO 4/16/17, active-LOW. Direct
digitalWrite — no PWM (the CYD onboard LED isn't PWM-wired).
- power.{h,cpp}: real backlight on GPIO 21 via LEDC channel 0 (5 kHz,
8-bit); powerScreen toggles backlight + screenOff flag; powerSleep
deep-sleeps; powerStatus returns fixed {100, 5000, 0, true}.
LEDC channel allocation: 0=power(backlight), 2=audio(tone). led/ does
not use LEDC. Avoids collision.
Pulls in Preferences (built-in Arduino-ESP32 lib) as a transitive dep
for rtc's NVS layer.
Build: RAM 6.6%, Flash 13.8%, clean link.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port the upstream M5StickC firmware to drive the CYD entirely through the
hal/ surface defined in M2.1/M2.2. Verified on hardware: boot shows the
"Hello! / a buddy appears" splash, transitions to a sleeping capybara
(ASCII species index 0), serial logs `buddy: ASCII mode`, and BLE
advertises as `Claude-B522`.
main.cpp (the big swap, 15 enumerated edits per PORT.md §5B):
- #include <M5StickCPlus.h> → hal/{board,power,led,audio,input,imu,rtc}.h
- TFT_eSprite spr(&M5.Lcd) → spr(&tft)
- W=135 H=240 → W=240 H=320, CY_BASE 120→160
- M5.begin/Imu.Init/Beep.begin/setRotation → boardBegin() + per-module
Begin() calls; explicit Serial.begin(115200) (M5 used to do this)
- M5.update() + M5.Beep.update() → boardLoop() (which ticks
inputUpdate/audioLoop/rtcLoop internally)
- M5.Lcd.* → tft.* (16 sites)
- M5.BtnA/B → inputA/B (7 sites) — stubbed, real touch in M3
- M5.Imu.getAccelData → imuRead — face-up stub
- M5.Axp.ScreenBreath → powerBacklight
- M5.Axp.SetLDO2 → powerScreen
- M5.Axp.PowerOff → powerSleep, menu item "turn off" → "sleep"
- M5.Axp.GetBtnPress block deleted (no left power button on CYD); a
touch-corner gesture is wired in M3 per PORT.md §6
- M5.Beep.tone → boardTone
- M5.Rtc.{Get,Set}{Time,Date} → rtc{Get,Set}{Time,Date}
- drawInfo page 3 rewritten: drop battery/temp telemetry (no fuel-gauge
or temp sensor on CYD), show USB-powered + uptime/heap/bright/bt
- _onUsb = powerStatus().usb
- bare GREEN/RED color constants → TFT_GREEN/TFT_RED (M5 lib aliased
these; TFT_eSPI requires the TFT_ prefix)
- Removed duplicate `bool screenOff = false;` (extern from hal/power.h)
- Sprite forced to 8bpp colour depth (240*320*1B = 76800 vs 153600 at
16bpp) — internal heap can't fit a full 16bpp sprite after BLE +
LittleFS load; CYD standard variant has no PSRAM. Auto-palette
approximates RGB565 values.
- Clear sprite after splash delay so loop's incremental pet render
doesn't leave residue on the wider canvas.
data.h (M2.4): M5.Rtc.SetTime/SetDate → rtcSet*; include hal/rtc.h.
xfer.h (M2.5): status response now reads via powerStatus() instead of
M5.Axp.GetBatVoltage/Current/VBusVoltage; include hal/power.h.
buddy.cpp: BUDDY_X_CENTER 67→120, BUDDY_CANVAS_W 135→240 to centre
species art on the wider CYD canvas (PORT.md §5B).
buddy.cpp + character.cpp + 18 buddies/*.cpp: include swap only,
M5StickCPlus.h → hal/board.h.
hal/board.cpp: boardLoop() now ticks inputUpdate + audioLoop + rtcLoop
so main.cpp's single call covers all per-tick HAL housekeeping.
platformio.ini: build_src_filter widened to +<*> +<buddies/> +<hal/>.
Flash: 63% (1.32 MB), RAM: ~74% at runtime after sprite alloc. M3 +
M4 layout retune still needed before this is usable end-to-end —
buttons (touch zones) and panel geometry land next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the M2.2 input stub with a working touch-zone implementation
and a first-boot calibration UI per PORT.md §6.
hal/input.{h,cpp}:
- InputButton now owns a rectangular hit-test (x,y,w,h) and a small
edge-detection state machine (was/is pressed, was released, pressed
for N ms). wasPressed()/wasReleased() are consumed-on-read so a single
tap fires exactly once.
- Three zones defined:
- inputA (ZONE_APPROVE): bottom-right half, lower 80 px
- inputB (ZONE_DENY): bottom-left half, lower 80 px
- inputSleep: top-right 50×50 corner
- inputUpdate() polls XPT2046 via the existing `ts` global on HSPI,
applies the saved calibration (raw 0..4095 → display 0..240/0..320),
debounces the down-state at 30 ms, then dispatches inside/outside to
each zone.
- inputReadDisplay() exposed publicly so future code (per-pixel touch
features, gesture detectors) can read calibrated coords too.
hal/touch_calibration.{h,cpp} (new):
- touchCalibrateIfNeeded() runs the 4-corner crosshair UI iff NVS
namespace "tcal" has no "xMin" key. Idempotent on later boots.
- touchCalibrateForce() exposed for a future settings-menu entry.
- Per-corner: draws crosshair at MARGIN=20 from each edge, waits for
finger up → finger down → 60 ms settle → re-sample → finger up.
Re-sampling stabilises the reading after the initial press transient.
- Auto-detects axis orientation: if raw "x" varies more between top-left
and bottom-left than between top-left and top-right, the panel is
rotated relative to the display → swap axes. Flips inferred from the
TL→BR direction. Persists swap/flipX/flipY + the four extrema to NVS.
- Extrapolates from the 20 px margins to the 0/edge bounds so input.cpp's
linear remap doesn't need to know the margin.
hal/board.cpp: boardBegin() now calls touchCalibrateIfNeeded() after
ts.setRotation() — first boot stops to run the UI; subsequent boots
proceed straight to the pet flow.
main.cpp: re-wire the screen-sleep gesture deleted in M2.3. Tap on
inputSleep (top-right corner) toggles screenOff via the existing
powerScreen()/wake() path. The original wake-on-A/B-press logic is
unchanged.
Verified on hardware: 4-corner calibration UI runs on first boot;
"calibrated" ack appears; firmware proceeds to splash + sleeping pet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Widen panel layouts and bump font sizes per PORT.md §5C. Minimum-viable retune: dimensions adjusted, text bumped only where the visual win is clear (clock, passkey, approval tool name, menu/reset items). drawHUD's SHOW/LH/WIDTH retargeted so 4× wrapped transcript lines fit at the 40-char width the wider screen affords. main.cpp: - drawMenu: mw 118→200, mh = 24+N*24+hint, item text size 1→2, line spacing 14→24. drawMenuHints reset to size 1 after. - drawSettings: mw 118→220, line spacing 14→18 (size 1 kept — 10 settings need to fit). Value column shifted from mw-36 to mw-56 for the wider panel. - drawReset: mw 118→200, size 1→2, line spacing 14→24. - drawClock portrait: HH:MM size 4→6 at y=180; seconds size 2→3 at y=230; date size 1→2 at y=268. Clear-region starts at y=110 so the larger buddy zone isn't disturbed. - drawPasskey: digits size 3→5, recentered at (W-180)/2, label at y=80, hint at y=260. Bigger, easier to read across the room. - drawApproval: AREA 78→120. Tool name size up to 3 (was 2) when it fits ≤13 chars (was ≤10). Hint truncations bumped to 40 chars (was 21). Button labels (A: approve / B: deny) reflow to the wider bar. - drawHUD: SHOW=3→4, LH=8→16, WIDTH=21→40; disp[32][24]→[32][48] buffer (matches the wrapInto signature change below). 64 px tall HUD band shows 4 transcript lines at 40-char width vs 3 lines at 21. - drawPet/drawPetStats: TOP 70→100 (was 70 to clear below the smaller M5 buddy; now 100 to clear below the centred-on-240 buddy art). - drawInfo: TOP 70→100. BUTTONS page renamed to TOUCH ZONES with text reflecting CYD's gesture model: bottom-right = A, bottom-left = B, top-right corner = sleep. Removed the M5 "Power left side" entry. - drawInfo CREDITS page: hardware line "M5StickC Plus / ESP32 + AXP192" → "CYD ESP32-2432S028R / ESP32-D0WD-V3". - wrapInto signature: char out[][24] → char out[][48]. The DEEP retunes from PORT.md §5C (per-pet-page Y positions, settings landscape clock font sizes, drawInfo content reflowed to use the full 240px width) are deferred — current rendering uses ~half the horizontal real estate but is readable and non-overlapping. Iterate after M5 if the layout still feels off. Build: Flash 63.9% (same), RAM 22.9%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PORT.md §6 defines ZONE_DENY = bottom-LEFT (B) and ZONE_APPROVE = bottom-RIGHT (A) on the CYD, but drawApproval was inherited verbatim from the upstream M5StickC where BtnA was the front button (visually on the left) and BtnB the side button — so the labels "A: approve" sat LEFT and "B: deny" sat RIGHT. End-to-end test on Linux via the claude-hardware-buddy plugin: tapping where "A: approve" was rendered actually fired the bottom-LEFT zone = inputB = deny. Caller saw decision="deny" despite intending to approve. Swap: "B: deny" now renders at x=4 (left), "A: approve" at x=W-70 (right). Matches the actual touch zones in hal/input.cpp.
Two related fixes to the approval flow that surfaced once the claude-hardware-buddy MCP plugin started driving the device on Linux. drawApproval button blocks Bottom 40px of the approval bar now renders as two filled rounded rects — red `DENY` on the left, green `APPROVE` on the right — with size-2 white text centred on each. Replaces the tiny size-1 labels that were invisible at any distance on the 240×320 panel, especially with the 8bpp sprite's colour quantisation. Layout matches the touch zones in hal/input.cpp (bottom-LEFT = inputB, bottom-RIGHT = inputA). _applyJson `clear_prompt` sentinel The previous behaviour cleared `tama.promptId` whenever an incoming JSON message did NOT contain a `prompt` field. That meant every keepalive (~12s) and every status push from the host wiped any active approval prompt off the screen before the user could tap. New behaviour: prompts get set only when an explicit `prompt` object arrives; they get cleared only on the explicit `clear_prompt:true` sentinel. Messages without either field leave the active prompt alone. Daemon-side companion change lives in claude-hardware-buddy.git.
PORT.md and MILESTONES.md were the spec + execution plan that produced the cyd branch. They lived outside the firmware repo during development in a sibling working directory (~/Projects/CYD-ClaudePet/) which has now been collapsed; folding them in here so the docs live with the code they describe. `docs/upstream-snapshot/` (a 4 MB clone of anthropics/claude-desktop-buddy used as a reference during the port) is gitignored — the firmware repo's `main` branch already tracks upstream identically, so anyone wanting the reference can `git checkout main` here. docs/README.md adds an entry point pointing readers at the port spec, the milestone plan, and the companion claude-hardware-buddy plugin.
Add include/M5StickCPlus.h + src/m5_shim.cpp providing the small M5
API surface (Rtc, Axp) used by upstream firmware code, routed through
the HAL. This lets 20 upstream files (18 buddies + character.cpp +
xfer.h) revert to byte-identical with upstream/main.
Move docs/{PORT,MILESTONES,README}.md to the hardware-buddy plugin
repo where the rest of the bridge docs live.
Delete src/main.cpp.upstream — git history retains the reference
snapshot.
Scaffold src/cyd/ + add +<cyd/> to build_src_filter so a future
move of the CYD main.cpp under that dir (leaving src/main.cpp
upstream-identical) is a one-step git mv.
Overlay shrinks: 45 → 21 files changed vs upstream, +854/-139.
Modified upstream files now: platformio.ini (irreducible board
config), src/main.cpp (planned future revert), src/buddy.cpp
(2-line 240-wide canvas geometry), src/data.h (5-line clear_prompt
sentinel), .gitignore. Build verified on env:cyd.
main now mirrors Anthropic byte-for-byte and visitors landing on skitzo2000/claude-desktop-buddy hit the cyd branch as the default. The upstream README assumed an M5StickC Plus, button controls, and the closed-source Anthropic Hardware Buddy GUI as the only client. Rewrite cover: CYD hardware specifics (2432S028R, USB-only, no IMU/ battery/RTC), two integration paths (Claude Desktop GUI vs the claude-hardware-buddy plugin for Claude Code), touch zone diagram replacing button controls, branching strategy (main = upstream, cyd = port), and pointers into the deep design docs (PORT.md, MILESTONES.md) that now live in the plugin repo. GIF format and the seven pet states are unchanged from upstream, so those sections link out to main's README rather than duplicating.
Earlier draft listed "no IMU, no battery, no RTC" as gaps. That misread the HAL design — those features have safe stubs and the firmware boots fine on a bare CYD. The HAL leaves clean slots for optional add-ons but none of them are needed to run the buddy. Lead the section with the bare-CYD path. Move the add-on table behind a "skip on a first build" note so newcomers don't think they need to source an MPU6050 to use this.
Slim CYD overlay vs upstream: M5 shim + plugin-docs move
The bufo GIFs were redistributed third-party artwork carved out of the MIT license. Removing them lets the whole repo be cleanly MIT, which matters for binary redistribution (e.g. a web flasher hosted from this repo). The firmware does not bundle character packs in the build — they're pushed over BLE at runtime — so removing the example pack has no functional impact. Users can flash their own packs via tools/flash_character.py. Also fixes the README's license line, which incorrectly said Apache 2.0.
Users can flash a CYD by opening a webpage in Chrome/Edge and clicking Install — no PlatformIO, no Python, no command line. Uses ESP Web Tools over Web Serial. Pieces: - .github/workflows/release.yml builds firmware with PlatformIO on every v* tag, merges bootloader + partitions + boot_app0 + firmware into a single merged.bin via esptool, and attaches it (plus the individual .bin files for power users) to a GitHub Release. - docs/index.html is the install page — esp-web-tools install button plus a graceful fallback message for browsers without Web Serial. - docs/manifest.json points the flasher at releases/latest/download/merged.bin. Activation: tag a v* release to trigger the first build, and enable GitHub Pages on the repo serving from /docs on the cyd branch.
Drop bufo character pack and clarify license as MIT
Without a pull_request or branch trigger the workflow only fired on v* tag push or manual dispatch — neither of which work before the file lands on the default branch. That meant the workflow couldn't be smoke-tested from a PR. Now: - runs on every PR (build only, doesn't publish) - runs on every push to cyd (build only) - still publishes a release on v* tag push - merged.bin and the individual .bins are uploaded as downloadable workflow artifacts on every run (14-day retention), so you can grab and test a build without cutting a release
setup-python@v5 with cache: pip needs a requirements.txt or pyproject.toml to compute the cache key. We don't ship one — pip install platformio happens inline. The cache lookup was failing before the build step could run.
PlatformIO bundles esptool inside its toolchain (used during upload), but it's not on the system Python path, so 'python -m esptool merge_bin' in the merge step couldn't find it. Pip-install it alongside platformio.
Forward the web flasher onto cyd (PR #3 landed on the wrong base)
…ection End users now flash from the browser — that should be the first thing they see. The pio command is still here for contributors but lives below the pairing and details sections, framed as the source-build path rather than the default.
README: lead with web flasher, demote pio
The companion Claude Code plugin moved from an internal gitea instance to GitHub and was renamed from claude-hardware-buddy to ai-hardware-buddy. Updates: - README link in 'Pair it with Claude' (plugin install pointer) - README link to PORT.md (deep design doc lives in the plugin repo) - docs/index.html link on the web flasher page No other content changes — this is a pure link migration so the public buddy repo stops pointing at the private gitea host.
Point plugin links at the new GitHub repo (ai-hardware-buddy)
…o release The CYD firmware (this repo) and the ai-hardware-buddy plugin (separate repo) just both went public. Each works standalone, but together they cover every combination of (Claude Desktop | Claude Code) x (M5StickC Plus | CYD). Pulling that story into a top-level section with a support matrix so the relationship is obvious — no buried plugin link in the middle of the pairing section.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.