This file provides guidance to agents such as Claude Code (claude.ai/code) when working with code in this repository.
The Bindicator is an ESP32-based IoT device that displays which bins (rubbish/recycling) need to be put out based on Google Calendar events. It features an LED matrix display, button interface, OAuth authentication with Google Calendar, and a web-based setup interface.
- Target: ESP32 (specifically ESP32-S3)
- Board FQBN:
esp32:esp32:esp32s3:CDCOnBoot=cdc - Default Port:
/dev/cu.usbmodem2101 - Build System: Arduino CLI via Makefile
make # Build and upload firmware to device
make compile # Build only (no upload)
make nocache # Clean build and upload
make just-upload # Upload existing build without recompiling
make monitor # Open serial monitor (auto-reconnects)
make update # Update Arduino platform packages
make simulator # Build native desktop simulator (ncurses UI)
make sim-run # Build + run simulator
make sim-clean # Clean simulator build artifactscd tests
./run_tests.sh # Build and run all unit tests (requires GoogleTest)
make test # Alternative: run tests directlyTests require GoogleTest installed via Homebrew: brew install googletest
The cloud-run/ directory contains a Google Cloud Function (Node.js) used as the OAuth redirect handler for the device setup process.
- Lives in
simulator/and runs the real firmware with mocked ESP32/Arduino APIs in a ncurses UI - Build/run via
make simulatorormake sim-run(requires g++, pthreads, ncurses, curl) - Serial console is integrated: type commands then Enter;
b= short press,l= long press,q= quit - Preferences persist to
simulator/simulator-state.json(SIMULATOR_EPHEMERAL=1keeps them in-memory) - Mock Google responses are in
simulator/mock_responses/; setSIMULATOR_MOCK=0when real HTTP support lands - Simulator-only serial helpers:
mock_setup,mock_bin none|recycling|rubbish,check
The Bindicator class manages the device's primary state machine with these states:
NO_COLLECTION: No bins dueRECYCLING_DUE: Recycling bin needs to go outRUBBISH_DUE: Rubbish bin needs to go outCOMPLETED: Bin marked as taken out (resets after 3am)LOADING: Initial/transition state- Error states (WiFi, API, other)
- Setup mode
State transitions are triggered by:
- Calendar checks (periodic polling)
- Button presses (mark bin as taken out)
- Time-based resets (3am daily)
- Error conditions
The firmware uses FreeRTOS tasks pinned to specific cores:
- animationTask (Core 1, Priority 2): Handles LED matrix animations and display updates
- calendarTask (Core 0, Priority 1): Polls Google Calendar API periodically
Tasks communicate via a FreeRTOS queue (commandQueue) that carries Command enums to trigger display changes.
- OAuthHandler: Manages Google OAuth 2.0 flow, token refresh, and storage in ESP32 Preferences
- CalendarHandler: Queries Google Calendar API for bin collection events
- ConfigManager: Persistent storage interface (WiFi credentials, calendar ID, device state) using ESP32 Preferences
- SetupServer: Web server for initial device configuration (WiFi, OAuth, calendar selection)
- DisplayHandler: LED matrix control (hardware interface)
- ButtonHandler: Physical button with long-press detection
Normal Mode: Device connects to WiFi, polls calendar, displays bin status
Setup Mode: Device creates AP "Bindicator Setup", serves web UI at bindicator.local for configuration
Transitions into setup mode when:
- No WiFi credentials configured
- No OAuth refresh token available
- Forced setup flag set (via serial command)
The system scans Google Calendar for events with titles containing:
- "(recycling)" → Sets
RECYCLING_DUE - "(rubbish)" → Sets
RUBBISH_DUE
Events are checked for "today" to determine if bins need to go out.
Device state persists across reboots via ESP32 Preferences:
- OAuth refresh token
- WiFi credentials
- Current device state
- Completion timestamp
- Bin taken out timestamp
- Configured calendar ID
The device accepts serial commands for debugging/control (see serial_commands.cpp):
clear,clear_oauth,prefs,setup,undo_bin,help- Simulator-only:
mock_setup,mock_bin none|recycling|rubbish,check
Unit tests use GoogleTest with mocks for Arduino/FreeRTOS APIs. The test suite covers:
- State machine logic in
Bindicator - Time calculations in
time_manager - Configuration persistence in
ConfigManager - Utility functions
Tests compile production C++ code with -DTESTING flag and mock out hardware dependencies.
bindicator.ino: Arduino sketch entry point (setup/loop)bindicator.cpp/h: Core state machine logicsecrets.h: OAuth credentials and WiFi defaults (not committed)tasks.cpp/h: FreeRTOS task definitions and command queueconfig_manager.cpp/h: Persistent storage abstractionsimulator/: Desktop simulator, ESP32/Arduino mocks, and mock HTTP responses
- Header files use
#pragma once(modern) or include guards (older files) - All source files should start with
ABOUTME:comments explaining purpose - State changes must go through
Bindicator::transitionTo()to ensure proper persistence and command queue updates - OAuth tokens are automatically refreshed when expired (handled in
OAuthHandler)