Fix/windows setup#7
Open
prathamVaidya wants to merge 29 commits into
Open
Conversation
Skill file (skills/tamagotchi/SKILL.md, skills/README.md):
- All 'tamagotchi <verb>' CLI examples -> 'gochi <verb>'
- TAMAGOTCHI_URL -> GOCHI_URL
- 'tamagotchi server start/install' (stale anyway) -> 'gochi setup'
- Trigger phrase example: 'tamagotchi face happy' -> 'gochi face happy'
- Device noun ('physical tamagotchi', 'show text on the tamagotchi')
and the skill name itself stay as 'tamagotchi' — the device is
still a Tamagotchi; only the CLI tool driving it changed.
CLI user-facing strings (logs and error messages):
- daemon log: 'Tamagotchi daemon listening on …' -> 'gochi daemon …'
- HTTP log: 'Tamagotchi HTTP frontend on …' -> 'gochi HTTP frontend …'
- client error: 'Tamagotchi daemon isn't running' -> 'gochi daemon …'
- client error: 'Tamagotchi HTTP frontend at …' -> 'gochi HTTP …'
- setup success: 'Tamagotchi installed and running:' -> 'gochi …'
- systemd Unit Description (visible in 'systemctl status'):
'Tamagotchi daemon/HTTP frontend' -> 'gochi daemon/HTTP frontend'
- Task Scheduler <Description>: 'Tamagotchi background service' ->
'gochi background service'
Kept as 'Tamagotchi' (product / firmware / infrastructure):
- .description('Drive the Tamagotchi desk pet over USB serial')
- firmware boot-banner string match in transport.ts
- launchd labels, systemd unit filenames, Windows task folders,
and ~/.tamagotchi/ storage dir (no migration step in this commit).
The repo moved from prathamVaidya/tamagotchi to devfolioco/gochi.
Updates:
- cli/package.json: repository.url, homepage, bugs.url
- skills/README.md: source link, 'npx skills add' invocations,
git clone URL + local clone path (~/src/tamagotchi -> ~/src/gochi)
Unscoped 'gochi' was deemed too similar to existing packages on npm. Scope it under the existing @0xPV namespace to keep the short bin name ('gochi') while making the package name distinct. - cli/package.json: name -> @0xpv/gochi; re-add publishConfig.access public (scoped packages default to restricted otherwise) - cli/README.md: install command -> 'npm i -g @0xpv/gochi' - cli/src/service/common.ts: reinstall hint -> '@0xpv/gochi'
Walks the user through the three host-observable components — serial
link (PING), OLED ('SHOW text Hello'), and buzzer (two distinct faces
back-to-back to force a jingle-triggering transition) — asking y/n
after each and dropping a tailored troubleshooting list on 'no'.
Buttons are intentionally not covered: the firmware has no host-
readable button state, so the CLI can't observe a press. A dedicated
on-device sketch would be needed for that.
gochi test # menu
gochi test oled # one component
gochi test all # all three, bails early if serial fails
Add an MPU-6050 IMU on a software-I2C bus (GPIO7/8) so the pet reacts to being handled: - Pickup (sustained |a| > 1.25 g for 250 ms) → surprised face. - Shake (3 sign-flipping peaks > 1.2 g within 800 ms) → sad face. - Each motion-triggered face auto-reverts to neutral after 5 s, unless a host command or BOOT-button tap has changed it in the meantime. The MPU sits on its own bit-banged bus rather than sharing the OLED's hardware I2C — the ESP32-C3 has only one I2C controller, and stacking both devices on it caused address-byte corruption (parallel pull-ups made rise time exceed the 400 kHz fast-mode budget). Bit-banging GPIO7/8 at 100 kHz gives full bus isolation with no library dependencies. ~150 lines for the driver, ~75 for the gesture detector. Also: - Free Mode disabled by default (FREE_MODE_ENABLED 0). The pet sits in Desktop Mode and only reacts to commands / BOOT / gestures. - When Free Mode is on, expression interval bumped from 9-17 s to 14-16 min and mood drift from 45-90 s to ~110-130 min (≈ 2 h). - gochi test imu — interactive lift+shake check that asks y/n on the visible reaction. Hardware wiring: MPU VCC→3V3, GND→GND, SDA→GPIO7, SCL→GPIO8. Plug the MPU in before powering the board: GPIO8 is a strapping pin and must idle HIGH at boot (the MPU's pull-up handles this only when connected).
The 3 external button pins (PIN_BTN_A/B/C on GPIO2/3/4) were wired in the config but never used anywhere — only the on-board BOOT button (GPIO9) is actually driving expressions. Dropping the constants and the matching pinMode calls; the GPIOs go back to being free. Also tidies the MPU comment to drop a stale 'Wire1' reference (the C3 has only one hardware I2C controller — Wire1 doesn't exist on this chip).
Flip FREE_MODE_ENABLED back to 1 so the pet lives on its own again (boots into Free Mode, expression refreshes every 14-16 min, mood drifts every ~2 h, all per the existing timings). IMU gestures still take priority over Free Mode — setMode(desktopMode) preempts the active mode the moment a pickup or shake fires, and Free Mode's update() doesn't run while in Desktop, so nothing can overwrite the surprised/sad face during the 5 s hold. Improve the calm-down: when Free Mode is enabled, the calm-down now hands control back to Free Mode (which picks a fresh mood-appropriate face) instead of leaving the pet stuck on neutral until the 60 s idle timer fires. With Free Mode off the original 'revert to neutral' behaviour is preserved. Also lower SHAKE_PEAK_G to 0.6 g — gentler shakes now register.
New protocol command 'SCAN i2c' returns a JSON list of devices ACKing
on each I2C bus:
{"A":["0x3C"],"B":["0x68"]}
Bus A is the hardware Wire bus (OLED on GPIO5/6); bus B is the
MPU's bit-banged bus (GPIO7/8). DesktopMode handles the command,
walking the standard 7-bit range (0x08..0x77) on both buses.
CLI: 'gochi i2c' formats the scan with known-device labels:
Bus A (hardware I2C, GPIO5/6):
0x3C SSD1306 OLED
Bus B (software I2C, GPIO7/8):
0x68 MPU-6050 IMU
'gochi test oled' now runs a bus-level check first (does 0x3C ACK?)
before the panel-level 'do you see Hello' question. Catches the
wiring failures (loose jumper, wrong pin, no power) up front instead
of asking the user about pixels that were never going to light up.
Re-add imu::probe(addr) which was removed in the earlier cleanup —
needed for the bit-banged bus scan.
Also fix two stale comments in cli/src/test.ts: the IMU test header
and troubleshooting tips still claimed the MPU shared the OLED's
I2C bus, but it has been on its own bit-banged bus (GPIO7/8) for a
while now.
The daemon process is long-lived — it only restarts at login. When
daemon.ts (or anything it imports) changes, the running process keeps
serving the old code, which is how 'gochi i2c' returned 'not found'
right after that endpoint was added.
'gochi kill' terminates the running daemon and lets the platform
service unit auto-respawn it with the current source. Three backends:
darwin launchctl kickstart -k gui/UID/com.tamagotchi.daemon
(falls back to bootout+bootstrap on pre-10.10 / odd setups)
linux systemctl --user restart tamagotchi-daemon.service
windows schtasks /End + /Run (no single 'restart' verb)
All three check that 'gochi setup' was run first and print a clear
error otherwise. Documented in cli/README.md under daemon lifecycle.
Single source of truth for 'gochi --version' / 'gochi -v'. Bumping package.json's 'version' field now flows through automatically — no more risk of cli.ts drifting from the published version. Resolved at startup via readFileSync + import.meta.url so the relative path works whether the CLI is run from source (tsx), via a bun link, or after 'npm install -g'. Avoids ESM JSON-import assertions which would require Node 20+ (engines.node is >=18).
The CLI uses tsx at runtime and previously had no tsconfig.json or type declarations for Node built-ins. tsserver was silent until it indexed cli.ts, then started complaining about 'node:path', 'node:fs' etc. — the imports themselves were fine, just no types. Minimal tsconfig (purely for the IDE — noEmit, since tsx runs the .ts files directly): moduleResolution: Bundler matches the runtime; lets imports omit .js resolveJsonModule: true supports import ../package.json skipLibCheck: true keeps tsserver fast types: [node] via @types/node devDep Not setting strict yet — that would surface a handful of pre-existing implicit-any patterns elsewhere that aren't related to this fix.
Same procedural face (heavy-lidded eyes + bite + blush) and same
jingle — just renamed across the registry, serial protocol, CLI
face list, and docs:
firmware C++ ExpressionId::Horny → ::Sexy, HORNY tone array → SEXY,
"horny" name → "sexy", every case label updated
firmware docs face list + Free-Mode manual-only note
CLI FACES array; daemon + protocol stay identical
docs cli/README + skills/tamagotchi/SKILL.md face list
Free Mode's 'manual-only' carve-out still applies — 'sexy' and 'dead'
stay out of the autonomous rotation, so it only shows up via an
explicit 'gochi face sexy' or 'SHOW face sexy'.
The detector was reliably firing a false Shake right after boot, showing the sad/cry face. Three things lined up to cause it: 1. The MPU had just come out of its 100 ms reset, so the first few accel reads were transient. 2. The gravity low-pass was seeded from that first sample, so its estimate was wrong; subsequent samples diverged from it and looked like real linear acceleration even when the device was still. 3. The user is almost always handling the device at boot — plugging USB, picking it up to look at the OLED — which loaded the shake sliding window with real motion hits before the detector had any business firing. Add a STARTUP_GRACE_MS = 1500 window from the first update() call. During grace: - gravity estimate keeps converging (so it's settled by grace end) - shake buffer is not updated (so it stays at hits=0) - all firing is suppressed After grace ends the buffer is empty, so it needs another ~400 ms to refill to SHAKE_MIN_FILLED before shake can fire. Effective dead window from boot is ~1.9 s.
…akes in 60s A single shake now sets the face to 'angry' (the pet is annoyed) rather than 'sad'. Once the pet has been shaken SHAKE_CRY_COUNT (3) times within SHAKE_CRY_WINDOW_MS (60 s), the next shake escalates to 'sad' — it actually cries. Implemented as a 3-slot ring buffer of timestamps. On each shake we overwrite the oldest entry, then count how many of the three are within the last 60 s. The window is rolling, so the pet 'calms down' naturally as old shakes age out; no explicit reset needed. The 0 sentinel for empty slots gets filtered by the same window check. Docs + test prompt updated: 'gochi test imu' now asks about the angry face for a single shake (the cry escalation needs three, which the single-shot test doesn't exercise).
When the daemon's serial port was released ('gochi stop') and the
i2c command ran after, the daemon returned ok:true with a null
'response' field. The CLI then tried to JSON.parse(undefined),
got back null, and crashed with:
TypeError: Cannot read properties of undefined (reading 'A')
at the 'parsed.A' line — and dumped a Node-internal stack trace
instead of telling the user what to do.
Three fixes in the i2c action:
1. Treat 'result.connected === false' as an error too, not just
'!result.ok'. The daemon sometimes returns ok:true / connected:false
with no payload when the port has been released.
2. Null-guard 'result.response' before the typeof check, and after
JSON.parse(). Tighten the parsed type to '... | null' so TypeScript
forces us through the check.
3. Each failure path now points at the specific fix: 'gochi start' to
reconnect, 'gochi setup' if the daemon isn't installed, or check
'gochi daemon status' for general issues.
Same hardening could be applied to the other daemon-consuming
commands ('get state', 'list faces', etc.) — left for a follow-up.
Walks through wiring the four components onto a breadboard one at a
time, verifying each with a 'gochi' command before moving on:
1. Power rails (3V3 + GND from the SuperMini to the breadboard)
2. OLED → 'gochi i2c' should show 0x3C; 'gochi test oled'
3. Buzzer → 'gochi test buzzer'
4. MPU-6050 → 'gochi i2c' should show 0x68 on Bus B;
'gochi test imu'
Calls out the gotchas:
- 3V3 not 5V (every device here is 3.3 V)
- OLED silkscreen pin order varies — always read the labels
- Buzzer must be passive, not active
- GPIO8 is a strapping pin — plug the MPU in *before* USB
- Why the MPU is on its own bit-banged bus (parallel pull-up
issue we hit when sharing the OLED's bus at 400 kHz)
Includes a final ASCII layout and a one-page troubleshooting table.
Codebase installation guide is deferred to a separate doc.
Companion to HOW-TO-BUILD.md. Walks through getting the firmware
build and CLI working *before* any hardware is involved, so the
'dev env is ready' milestone is verifiable on a host with no board
attached.
Covers four hosts with their actual install commands (winget,
homebrew, apt/dnf, bun/npm):
macOS brew install arduino-cli bun, Xcode CLT
Linux apt/dnf install, arduino-cli script, bun script,
dialout group for USB serial permissions
Windows winget for git/arduino-cli/bun, Device Manager check
for the COM port, native USB-CDC driver
WSL 2 Linux setup + usbipd-win for USB passthrough so
arduino-cli upload reaches the board
Shared section after the platform splits:
1. git clone https://github.com/devfolioco/gochi.git
2. arduino-cli core install esp32:esp32@3.3.8 (versions pinned
in firmware/sketch.yaml)
3. make build → expected output shows the firmware compiled
4. cd cli && bun install
5. bun src/cli.ts --version → confirms CLI runs
6. optional bunx tsc --noEmit → confirms the tsconfig + @types/node
are wired up
Plus a troubleshooting table for the platform-specific gotchas (PATH
issues, dialout group, Windows driver missing, WSL usbipd needing
re-attach after reboot, etc.) and a clear pointer to HOW-TO-BUILD.md
once the dev setup is verified working.
I had clang-format down as 'optional but recommended' for macOS only
and didn't mention it on Linux / Windows / WSL at all. But the
Makefile lists 'make format' and 'make format-check' as top-level
targets, so a fresh dev hitting 'clang-format: command not found' is
a near-certainty — and that's exactly what someone hit on WSL.
Prerequisites section: promoted from missing → listed alongside
git / arduino-cli / bun.
macOS: moved out of 'optional but recommended' into the main
install block.
Linux: added 'clang-format' (Debian/Ubuntu) and
'clang-tools-extra' (Fedora — same binary, different
package name) to the apt/dnf one-liners.
Windows: added 'winget install LLVM.LLVM' since clang-format
ships as part of the LLVM toolchain.
WSL: covered automatically (it follows the Linux section).
Troubleshooting table: new row for the 'command not found'
message with the per-OS install command.
Four standalone sketches under firmware/tests/ that share the main firmware's src/config.h pin map so wiring stays in sync: led/ blinks LED_BUILTIN at 1 Hz oled/ cycles text on the SSD1306 (PIN_SDA / PIN_SCL) buzzer/ plays a C5 major scale via LEDC on PIN_BUZZER mpu/ streams MPU-6050 accel + gyro at 50 Hz over USB serial The MPU sketch uses hardware Wire on PIN_MPU_SDA / PIN_MPU_SCL — no contention here since the OLED isn't on the bus in this test — and mirrors the main firmware's init sequence + ±4 g / ±500 °/s ranges. Paired with firmware/tests/mpu/visualize.html: a self-contained single page (no build, no deps) that talks to the device through the Web Serial API in Chrome / Edge. Shows a top-down SVG plane in a CSS-3D perspective scene that banks with roll and pitches forward/back, plus live numeric values + bipolar bars for all six axes and a derived pitch / roll readout (accel-only, low-pass filtered). Make targets: make test-led / test-oled / test-buzzer compile + flash make test-mpu compile + flash + open viewer OPEN is picked per OS (open on macOS, xdg-open on Linux, cmd //c start on Git Bash / MSYS2) so the same target works on Mac, Linux, and Windows. PORT auto-detection stays Mac-only (/dev/cu.usbmodem*); other platforms override it via PORT=… as already documented at the top of the Makefile.
- README.md: add a 'Hardware bring-up tests' subsection to the workflow table with the four new make targets. - firmware/README.md: link the IMU section to the new `make test-mpu` + browser viewer as an alternative to `gochi test imu`, and add a 'Bring-up tests' table pointing at firmware/tests/. - HOW-TO-BUILD.md: in Prerequisites, note that the test sketches give builders a verification path that doesn't need the gochi CLI / daemon installed yet — useful when wiring before the host-side install is done.
The ESP32 Arduino core 3.3.8 esp32c3 variant defines LED_BUILTIN as `SOC_GPIO_PIN_COUNT + PIN_RGB_LED` — a sentinel meant for the new rgbLedWrite() neopixel API on boards that have an RGB LED. The SuperMini has a plain single-colour LED, so the sentinel maps to no real GPIO and digitalWrite() silently no-ops on it. That's why `make test-led` flashed cleanly but nothing on the board lit up. Hardcode PIN_LED = 8 (which HOW-TO-BUILD.md / README.md already document as the SuperMini's onboard LED, active-low). Also add a three-pulse startup pattern so the LED is unmistakably alive before the steady-state 1 Hz blink takes over.
Some users mount the OLED upside-down (panel pins at the bottom of the enclosure instead of the top). Rather than hard-editing the U8g2 constructor or maintaining a local diff, the Makefile now sources an optional, gitignored .env at the project root and translates supported KEY=VALUE pairs into -D flags passed through to every arduino-cli compile (main firmware + all four bring-up tests). The first knob: ROTATED_DISPLAY=1 picks U8G2_R2 instead of U8G2_R0 in firmware/src/display/display.cpp and firmware/tests/oled/oled.ino, flipping the panel 180° at compile time (no runtime cost). Copy .env.example to .env and edit; verify orientation with `make test-oled` before flashing the main firmware. The Makefile machinery is generic (EXTRA_DEFINES is built up from ifeq blocks), so future knobs slot in alongside ROTATED_DISPLAY without re-plumbing.
- README.md: new 'Build-time configuration (.env)' section after the bring-up tests, with the usage workflow (copy template → edit → verify with test-oled → flash) and a one-row table for ROTATED_DISPLAY. Future knobs slot into the same table. - HOW-TO-BUILD.md: practical callout at the OLED verification step — when the panel reads inverted, this is the moment a builder reaches for the flag. - firmware/README.md: cross-reference under 'The face' so the firmware doc points at the same source of truth. The single source for the .env format itself stays .env.example; docs just link to it.
feat: added hardware tests
…discovery, port-busy fix - Makefile: PORT auto-detect now branches on $(OS). macOS keeps /dev/cu.usbmodem*; Linux gets /dev/ttyACM* (was silently broken); Windows leaves it empty (user passes PORT=COMx). - cli/src/test.ts: the 'is the board plugged in?' hint in `gochi test` was hardcoded to `ls /dev/cu.usbmodem*`. Now branches per process.platform so Windows users get `arduino-cli board list` + Device Manager and Linux users get /dev/ttyACM*. - HOW-TO-SETUP.md (Windows native): document that the Makefile recipes are POSIX-sh and need Git Bash / MSYS2 to run cleanly; add COM-port discovery (Device Manager + `arduino-cli board list` with VID 303a); add the raw `arduino-cli compile` / `upload` commands in PowerShell syntax; add an 'Access is denied' troubleshooting block covering the gochi daemon, Serial Monitor / PuTTY / VS Code, the unplug-replug, and the BOOT/RESET force-download trick. - HOW-TO-SETUP.md (troubleshooting table): three new Windows rows for port-busy, make-flash quoting errors, and the missing auto-detect. - README.md: daily-workflow port example shows all three OSes.
arduino-cli has no built-in erase verb, so we shell out to the esptool that ships inside the pinned ESP32 core. The path is resolved via `arduino-cli config get directories.data` so it works on macOS (~/Library/Arduino15), Linux/WSL (~/.arduino15), and Windows (%LOCALAPPDATA%\Arduino15) without hardcoding. Mirrors the upload recipe's daemon-pause / port-reacquire dance so the gochi daemon doesn't fight for the COM port mid-erase. Errors with a clear three-OS hint when PORT is missing, and a 'reinstall the core' hint when esptool can't be located. Uses the v5 `erase-flash` subcommand (with a comment about the v4 `erase_flash` rename) — safe because the project pins esp32:esp32@3.3.8 which ships esptool 5.x.
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.