# CodexBar-ESP32 — Setup Guide

Complete from-scratch setup. Follow the sections in order.

---

## 1. Install the CH340 USB Driver

The IdeaSpark board uses a **CH340** USB-to-serial chip. macOS doesn't ship with this driver.

**Option A — Homebrew (easiest):**
```bash
brew install --cask wch-ch34x-usb-serial-driver
```

**Option B — Manual download:**
Download and run the `.pkg` installer from the official WCH page:
[wch-ic.com/downloads/CH34XSER_MAC_ZIP.html](https://www.wch-ic.com/downloads/CH34XSER_MAC_ZIP.html)

After installing, plug the board in via USB-C. You should see a new port appear:
```bash
ls /dev/cu.wch*
# → /dev/cu.wchusbserial1410  (the number will vary)
```

> **macOS 13+ note:** You may need to approve the kernel extension in System Settings → Privacy & Security → "Allow" after the install. Reboot if the port doesn't appear.

---

## 2. Install Arduino IDE 2

Download the latest Arduino IDE 2 for macOS (Apple Silicon or Intel):
[arduino.cc/en/software](https://www.arduino.cc/en/software)

Install and open it.

---

## 3. Add ESP32 Board Support

Arduino IDE doesn't know about ESP32 boards by default.

1. Open **Arduino IDE → Preferences** (⌘,)
2. Find **"Additional boards manager URLs"** and paste:
   ```
   https://espressif.github.io/arduino-esp32/package_esp32_index.json
   ```
3. Click OK
4. Open **Tools → Board → Boards Manager**
5. Search `esp32`, find **"esp32 by Espressif Systems"**, click **Install**

This downloads ~200MB. Wait for it to finish.

---

## 4. Configure TFT_eSPI for the IdeaSpark Board

### 4a. Install the library

In Arduino IDE → **Tools → Manage Libraries**:
- Search `TFT_eSPI`, install **TFT_eSPI by Bodmer**
- Search `ArduinoJson`, install **ArduinoJson by Benoit Blanchon** (v7+)

### 4b. Copy the pin config

The TFT_eSPI library needs a board-specific config file. Run this once from the project root:

```bash
cd ~/projects/CodexBar-ESP32
bash setup.sh
```

This copies `codexbar_esp32/User_Setup.h` to `~/Documents/Arduino/libraries/TFT_eSPI/User_Setup.h`.

**What it configures** (IdeaSpark ESP32 1.9" B0D6QXC813):

| Signal | GPIO |
|--------|------|
| MOSI   | 23   |
| SCLK   | 18   |
| CS     | 15   |
| DC     | 2    |
| RST    | 4    |
| BL (backlight) | 32 |

> **Important:** If you update or reinstall TFT_eSPI via the Library Manager, you'll need to run `bash setup.sh` again — updates overwrite `User_Setup.h`.

---

## 5. Configure WiFi and Proxy IP

Edit `codexbar_esp32/config.h`:

```cpp
#define WIFI_SSID  "YourNetworkName"
#define WIFI_PASS  "YourPassword"
#define PROXY_HOST "your-mac.local"   // ← prefer the Mac's stable mDNS hostname (survives DHCP
                                      //   reassignment); find it with `hostname`. A raw IP works too.
```

**If you prefer a raw IP instead of mDNS:**
```bash
ipconfig getifaddr en0      # WiFi
# or
ipconfig getifaddr en1      # Ethernet
```

Or: System Settings → Network → Wi-Fi → Details → IP Address.

---

## 6. Start the Companion Proxy

The proxy runs on your Mac, reads the Claude CLI OAuth token from Keychain, and serves usage data to the ESP32.

```bash
cd ~/projects/CodexBar-ESP32
python3 companion/server.py
```

Expected startup output:
```
CodexBar companion proxy starting on port 7842
ESP32 PROXY_HOST = "192.168.1.42"     ← copy this IP into config.h
Endpoint: http://192.168.1.42:7842/usage

Fetching initial usage data…
  5h       20%  resets in 37640s
  7d       17%  resets in 419014s

Serving on http://0.0.0.0:7842 — Ctrl-C to stop
```

**Test it manually:**
```bash
curl http://localhost:7842/usage
```

**Prerequisite:** You must be logged in to Claude CLI. If you've never run `claude` on this machine, authenticate first:
```bash
claude   # follow the login prompt
```

### Auto-start the proxy on login (optional)

Create `~/Library/LaunchAgents/com.codexbar.companion.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.codexbar.companion</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>/Users/jameslopez/projects/CodexBar-ESP32/companion/server.py</string>
    </array>
    <key>WorkingDirectory</key>
        <string>/Users/jameslopez/projects/CodexBar-ESP32</string>
    <key>RunAtLoad</key>   <true/>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key> <false/>
    </dict>
    <key>StandardOutPath</key>  <string>/tmp/codexbar-companion.log</string>
    <key>StandardErrorPath</key><string>/tmp/codexbar-companion.log</string>
</dict>
</plist>
```

Then load it:
```bash
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.codexbar.companion.plist 2>/dev/null || true
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.codexbar.companion.plist
launchctl kickstart -k gui/$(id -u)/com.codexbar.companion

# To stop:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.codexbar.companion.plist

# To inspect:
launchctl print gui/$(id -u)/com.codexbar.companion

# To view logs: tail -f /tmp/codexbar-companion.log
```

If you installed an older plist with plain `<key>KeepAlive</key><true/>`, replace it with the version above. Plain `KeepAlive=true` can repeatedly restart the job after a clean "port already in use" exit when another companion process is already running.

---

## 7. Flash the ESP32

1. Open `codexbar_esp32/codexbar_esp32.ino` in Arduino IDE
2. Select board settings:
   - **Tools → Board → esp32 → ESP32 Dev Module**
   - **Tools → Upload Speed → 921600**
   - **Tools → Flash Size → 16MB (128Mb)**
   - **Tools → Port → /dev/cu.wchusbserial…** (the CH340 port)
3. Click **Upload** (→)

First upload takes ~30s. Watch the progress bar at the bottom. When it says "Hard resetting via RTS pin…" it's done.

### What to expect on first boot

1. Screen turns on, shows "CodexBar / LLM Usage Monitor"
2. "Connecting WiFi…" status
3. Data fetches from the proxy
4. Each provider section renders a large 5h gauge and a smaller 7d/Opus gauge
5. A thin red header line appears only when data is unavailable

Open **Tools → Serial Monitor** (115200 baud) to watch the boot log:
```
[CodexBar] Boot
[CodexBar] Display 170x320
[WiFi] Connecting to YourSSID..........
[WiFi] Connected — IP: 192.168.1.105
[HTTP] GET http://192.168.1.42:7842/usage
[HTTP] Parsed 2 windows
[Loop] Redrew — dataOk=1, numWindows=2
```

---

## 8. Ongoing Usage

| Device behavior | What it means |
|----------------|---------------|
| Green dot, colored arcs | Live data from Anthropic API |
| Red dot, gray "--" arcs | Proxy unreachable or API error |
| "WiFi Failed" screen | Wrong SSID/password or WiFi not in range |
| "Proxy Error" screen | Companion not running or wrong PROXY_HOST |
| Arc is fully red (100%) | Usage limit hit or exceeded |

The device auto-refreshes every **5 minutes** (configurable via `REFRESH_SECS` in `config.h`).

---

## Troubleshooting

### Screen is black / backlight only
- Verify `User_Setup.h` was copied (run `bash setup.sh` again)
- In rare cases the backlight pin is GPIO21 — edit `TFT_BL_PIN` in `config.h` and try

### "Proxy Error" but server is running
- Confirm `PROXY_HOST` in `config.h` is the Mac's **local network** IP (not `127.0.0.1`)
- Both Mac and ESP32 must be on the same WiFi network
- macOS Firewall may be blocking port 7842: System Settings → Network → Firewall → Options → allow incoming connections for Python

### Proxy shows wrong percentages
The Anthropic API returns `utilization` as a **percentage** (0–100+), not a ratio (0–1). The companion already handles this correctly. Values can exceed 100 if you're over the limit — they're clamped to 100 for display.

### Claude token expired / 401 errors
The companion automatically tries to refresh the token using the stored refresh token. If that fails, re-authenticate:
```bash
claude   # re-login
```
Then restart `server.py`.

### Port /dev/cu.wchusbserial doesn't appear
- Try a different USB-C cable (some are power-only, not data)
- Reboot after installing the CH340 driver
- Check System Settings → Privacy & Security for a blocked kernel extension

---

## API Reference (what the companion fetches)

**Endpoint:** `GET https://api.anthropic.com/api/oauth/usage`

**Headers:**
```
Authorization: Bearer <oauth_access_token>
anthropic-beta: oauth-2025-04-20
User-Agent: claude-code/2.1.0
```

**Response shape:**
```json
{
  "five_hour":  { "utilization": 20.0, "resets_at": "2026-05-26T03:30:00Z" },
  "seven_day":  { "utilization": 17.0, "resets_at": "2026-05-30T21:00:00Z" },
  "seven_day_opus": null,
  "extra_usage": {
    "is_enabled": true,
    "monthly_limit": 2000,
    "used_credits": 483.0,
    "utilization": 24.15,
    "currency": "USD"
  }
}
```

**Key discovery:** `utilization` is already a **percent value** (0–100+), not a 0–1 ratio. This was confirmed by comparing `extra_usage.utilization` (24.15) against `used_credits/monthly_limit` (483/2000 = 24.15%).

**What the companion exposes to the ESP32** (`GET http://<mac>:7842/usage`):
```json
{
  "ok": true,
  "updated_at": 1748210000,
  "windows": [
    { "label": "5h",  "pct": 20, "resets_in_sec": 10414 },
    { "label": "7d",  "pct": 17, "resets_in_sec": 419014 }
  ]
}
```

---

## Display Arc Angle Math

TFT_eSPI `drawArc` convention: **0° = 12 o'clock, increases clockwise**.

The outer arc is a pace gauge, not a raw usage gauge. Green means under
sustainable pace, the white tick at 12 o'clock means exactly on pace, and red
past the tick means the current burn rate will hit the limit before reset. See
`README.md` and `docs/display-design.md` for the full graph-reading model.

---

## Project Files

```
CodexBar-ESP32/
├── README.md                     Overview and quick start
├── SETUP.md                      This file — full setup guide
├── SPEC.md                       Feature spec, design decisions, phases
├── setup.sh                      Copies User_Setup.h to TFT_eSPI library
│
├── codexbar_esp32/
│   ├── codexbar_esp32.ino        Main Arduino sketch (WiFi + display)
│   ├── config.h                  All configurable constants
│   └── User_Setup.h              TFT_eSPI pin config (copy via setup.sh)
│
└── companion/
    ├── server.py                 Mac proxy — Keychain → Anthropic API → ESP32
    ├── requirements.txt          (stdlib only, no pip install needed)
    └── README.md                 Companion-specific docs and launchd setup
```
