Skip to content

Fix/windows setup#7

Open
prathamVaidya wants to merge 29 commits into
prathamVaidya:mainfrom
devfolioco:fix/windows-setup
Open

Fix/windows setup#7
prathamVaidya wants to merge 29 commits into
prathamVaidya:mainfrom
devfolioco:fix/windows-setup

Conversation

@prathamVaidya

Copy link
Copy Markdown
Owner

No description provided.

prathamVaidya and others added 29 commits May 28, 2026 18:07
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.
…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.
@prathamVaidya prathamVaidya self-assigned this May 29, 2026
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