From 730ba5ea155474ae276371a8660dab6df85467e9 Mon Sep 17 00:00:00 2001 From: Thaddeus Hughes Date: Wed, 4 Mar 2026 12:34:08 -0600 Subject: [PATCH] init claude --- CLAUDE.md | 268 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ TODO.md | 11 +++ 2 files changed, 279 insertions(+) create mode 100644 CLAUDE.md create mode 100644 TODO.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e91ea90 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,268 @@ +# SC-F001 Firmware — CLAUDE.md + +## Overview + +The SC-F001 is a **solar-powered automated crop harvesting robot** built on the ESP32. It drives a carriage horizontally via a drive motor and lifts/lowers a cutting head via a jack motor, with an auxiliary "fluffer" motor always running during operation. The firmware handles motor sequencing, safety interlocks, remote control, data logging, and a WiFi web interface. + +**Primary operational cycle:** Idle → Move Start Delay → Jack Up → Drive → Jack Down → Idle + +--- + +## Hardware Platform + +**MCU:** ESP32 (Xtensa dual-core), IDF framework + +**GPIO Map:** +| GPIO | Function | +|------|----------| +| 13 | Button interrupt (active low, pull-up) — also EXT0 wakeup | +| 14 | Jack position sensor / encoder | +| 16 | Drive encoder | +| 19 | Aux sensor 2 (reserved) | +| 21/22 | I2C SDA/SCL (400kHz) → TCA9555 I/O expander | +| 25 | 433MHz RF receiver (RMT input) | +| 26 | Solar charger bulk enable (RTC GPIO, holds across deep sleep) | +| 27 | Safety sensor (active low) | +| 32/33 | External 32kHz RTC crystal | +| 36 (VP) | ADC: drive current sense | +| 39 (VN) | ADC: battery voltage | +| 34 | ADC: jack current sense | +| 35 | ADC: aux current sense | + +**TCA9555 (I2C at 0x20):** +- Port 0 (input): 2 physical buttons + 2 additional inputs +- Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX) + LEDs + +**Motor / Bridge Specs:** +- `BRIDGE_DRIVE` — 100A max, ACS37220 sense chip (13.2 mV/A, inverted polarity) +- `BRIDGE_JACK` — 30A max, ACS37042 sense chip (44 mV/A) +- `BRIDGE_AUX` — 30A max, ACS37042 sense chip (44 mV/A) + +--- + +## Software Architecture + +``` +app_main() +├── rtc_xtal_init() RTC crystal + EXT0 wakeup + sleep wakeup check +├── i2c_init() TCA9555 init (relays off, LEDs off) +├── adc_init() ADC1 calibration (12dB attenuation, line-fit) +├── storage_init() Flash params + circular log buffer +├── solar_run_fsm() (called in main loop too) +├── uart_init() Serial JSON API task +├── rf_433_init() 433MHz RMT receiver task +├── bt_hid_init() BLE HID host scanner task +├── fsm_init() Control FSM task (priority 10, 20ms tick) +└── webserver_init() WiFi softAP + HTTP + mDNS + DNS + +Main loop (50ms): + i2c_poll_buttons() + fsm_request() based on button events + solar_run_fsm() + driveLEDs() status animation + rtc_check_shutdown_timer() → deep sleep on inactivity (180s) +``` + +**Task Priorities:** +- FSM control task: priority 10 (real-time) +- All others: default priority + +--- + +## Key Files + +| File | Purpose | +|------|---------| +| `main.c` | Entry point, 50ms main loop, factory reset, LED animation | +| `control_fsm.c/h` | State machine, relay control, current monitoring, calibration | +| `power_mgmt.c/h` | ADC reading, e-fuse thermal algorithm, battery voltage | +| `sensors.c/h` | GPIO ISR-based sensor debouncing, encoder counters | +| `i2c.c/h` | TCA9555 relay/LED/button control | +| `storage.c/h` | 47-param NVM table + circular binary log buffer | +| `comms.c/h` | Unified GET/POST JSON API (shared by HTTP and UART) | +| `webserver.c/h` | WiFi softAP, HTTP server, embedded gzip webpage | +| `uart_comms.c/h` | Serial JSON interface (115200 8N1) | +| `rf_433.c/h` | 433MHz OOK receiver, keycode learn/match | +| `bt_hid.c/h` | BLE HID host, media remote button mapping | +| `rtc.c/h` | Unix time, harvest alarms, deep sleep scheduling | +| `solar.c/h` | Simple FLOAT/BULK solar charge state machine | +| `sc_err.h` | Error code definitions | +| `log_test.c/h` | Flash log unit tests | +| `hard_ui.c` | Legacy LCD code (unused/obsolete) | + +--- + +## Control FSM States + +``` +STATE_IDLE +STATE_MOVE_START_DELAY (1s) +STATE_JACK_UP_START (detect current spike → jack engaged) +STATE_JACK_UP (continue until timer/e-fuse) +STATE_DRIVE_START_DELAY (1s) +STATE_DRIVE (encoder-based distance control) +STATE_DRIVE_END_DELAY (1s) +STATE_JACK_DOWN (reverse until e-fuse/sensor) +→ back to STATE_IDLE + +STATE_UNDO_JACK_START (emergency: reverse jack immediately) +STATE_UNDO_JACK (run until e-fuse/sensor) +→ back to STATE_IDLE + +CAL_JACK_DELAY / CAL_JACK_MOVE (jack calibration sequence) +CAL_DRIVE_DELAY / CAL_DRIVE_MOVE (drive calibration sequence) +``` + +**Guards before START:** +- Remaining distance > 0 (leash protection) +- Battery V ≥ `LOW_PROTECTION_V` (default 10V) +- Safety sensor active (debounced stable) +- All e-fuses not tripped + +**FSM Loop (20ms tick in `control_task()`):** +1. `process_bridge_current()` — ADC → EMA → auto-zero → e-fuse +2. `process_battery_voltage()` — ADC → EMA +3. `sensors_check()` — drain ISR queue, update counters/debounce +4. State machine transitions (timer + sensor + efuse checks) +5. `driveRelays()` — write relay output from current state +6. `send_fsm_log()` — 39-byte timestamped entry to flash + +--- + +## E-Fuse Algorithm (`power_mgmt.c`) + +Per bridge, each 20ms tick: +1. Raw ADC → EMA filter (α = `ADC_ALPHA_ISENS`) +2. Auto-zero: learn zero offset when motor is off + grace period expired +3. Grace period: 250ms after relay closes (ignores startup inrush) +4. **Instant trip:** I ≥ `EFUSE_KINST` × I_nom (default 2×) +5. **Thermal trip:** heat accumulates as I²·Δt; dissipates at τ_cool rate +6. **Auto-reset:** after `EFUSE_TCOOL` seconds of cooling (default 5s) + +--- + +## Safety Sensor Debouncing (Asymmetric) + +``` +LOW (safe): 1000ms make time → slow to declare safe (SAFETY_MAKE_US) +HIGH (break): 300ms break time → fast to kill operation (SAFETY_BREAK_US) +``` + +Safety break → immediate `STATE_UNDO_JACK_START`. + +--- + +## Communication Interfaces + +### WiFi (softAP) +- SSID/password/channel configurable via params (`WIFI_SSID`, `WIFI_PASS`, `WIFI_CHANNEL`) +- mDNS hostname: `sc.local` +- Captive portal DNS: all queries → 192.168.4.1 +- HTTP port 80 + +### HTTP API (port 80) +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Embedded gzip HTML webpage | +| `/get` | GET | JSON system status | +| `/set` | POST | JSON commands + parameter updates | +| `/log` | GET | Binary log download (4B JSON len + JSON + 8B offsets + log data) | + +### UART (115200 8N1) +- `GET` → same as HTTP GET /get +- `POST: {json}` → same as HTTP POST /set +- `HELP` → command reference +- Shares `comms_handle_get()` / `comms_handle_post()` with HTTP + +### 433MHz RF (GPIO25, RMT) +- 24-bit OOK codes (P_HIGH≈1040µs, P_LOW≈340µs, margin 70µs) +- 8 stored keycodes → FSM_OVERRIDE_* commands +- Learn mode: capture next RX → temp buffer → user commits via web + +### Bluetooth HID Host +- Scans for BLE HID devices (service UUID 0x1812) +- Tries saved BDA first, then scans for best RSSI +- Button mapping: + - VOL_UP → Jack Up (override pulse) + - VOL_DOWN → Jack Down + - PREV → Drive Reverse + - NEXT → Drive Forward + +--- + +## Storage Layout + +**Flash partition "storage":** +``` +0x0000 – 0x0FFF Parameters (4 sectors, CRC32-protected, 47 params) +0x1000 – end Circular log buffer (head/tail tracked) +``` + +**Log entry format (39 bytes typical):** +``` +[0:8] Timestamp ms (u64 BE) +[8:12] Battery voltage (f32) +[12:16] Drive current (f32) +[16:20] Jack current (f32) +[20:24] Aux current (f32) +[24:26] Drive encoder count (i16) +[26] Sensor states (packed) +[27:31] Drive heat (f32) +[31:35] Jack heat (f32) +[35:39] Aux heat (f32) +``` + +**Key Parameters:** +- Motion: `DRIVE_DIST`, `JACK_DIST`, `DRIVE_KT`, `JACK_KT`, `DRIVE_KE` +- E-fuse: `EFUSE_INOM_1/2/3`, `EFUSE_HEAT_THRESH`, `EFUSE_KINST`, `EFUSE_TCOOL` +- Safety: `SAFETY_BREAK_US`, `SAFETY_MAKE_US`, `LOW_PROTECTION_V` +- RF: `KEYCODE_0` … `KEYCODE_7` +- WiFi: `WIFI_SSID`, `WIFI_PASS`, `WIFI_CHANNEL` +- Schedule: `NUM_MOVES`, `MOVE_START`, `MOVE_END` (seconds-since-midnight) + +--- + +## Power Management + +- **Battery voltage:** GPIO39, divider → `V = raw × 0.00767 + 0.4` +- **Solar charger:** GPIO26 (RTC hold) — FLOAT/BULK FSM, bulk for 20s when V < 5V for 5s +- **Inactivity shutdown:** 180s → deep sleep +- **Deep sleep wakeup:** RTC timer (120s), RTC alarm (next harvest), EXT0 GPIO13 (button) +- **RTC_DATA_ATTR:** FSM state, errors, alarm times, charge state — survive deep sleep + +--- + +## Error Codes (`sc_err.h`) + +```c +SC_ERR_EFUSE_TRIP_1 = 0x201 // Drive overcurrent/overheat +SC_ERR_EFUSE_TRIP_2 = 0x202 // Jack +SC_ERR_EFUSE_TRIP_3 = 0x203 // Aux +SC_ERR_SAFETY_TRIP = 0x210 // Safety sensor break +SC_ERR_LEASH_HIT = 0x211 // Distance limit reached +SC_ERR_RTC_NOT_SET = 0x220 // Clock not synchronized +SC_ERR_LOW_BATTERY = 0x230 // Voltage below threshold +``` + +--- + +## Build System + +- **Framework:** ESP-IDF (>=4.1.0) +- **Component deps** (`idf_component.yml`): `espressif/mdns`, `joltwallet/littlefs`, `esp-idf-lib/tca95x5` +- **IDF requires:** `driver`, `esp_http_server`, `esp_netif`, `lwip`, `json`, `esp_timer`, `esp_adc`, `app_update`, `esp_wifi`, `nvs_flash`, `mdns`, `bt`, `esp_hid` +- **Webpage:** `webpage.html` → `webpage_compile.py` → `webpage_gzip.h` (embedded gzip binary) +- **Version:** `version.h.in` filled by CMake from git tags → `FIRMWARE_VERSION`, `BUILD_DATE` +- **Factory reset:** Hold GPIO13 button on cold boot → full parameter + log erase + +--- + +## Conventions + +- **Naming:** `snake_case` functions with module prefix (`fsm_init`, `i2c_poll_buttons`); `UPPER_SNAKE_CASE` constants/enums +- **Module pattern:** `.c` / `.h` pairs; headers expose only public API +- **Concurrency:** FSM commands via `xQueueSend`; log writes via async queue; GPIO ISR → minimal work → sensor queue +- **State machine pattern:** transitions in one `switch`, relay outputs in a second `switch` (separated) +- **Watchdog:** `esp_task_wdt_add/reset` in each task, 10s timeout +- **Logging:** `ESP_LOGI(TAG, ...)` per module; flash circular log for telemetry +- **No dynamic allocation** in ISR or high-priority paths diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..9952c33 --- /dev/null +++ b/TODO.md @@ -0,0 +1,11 @@ +# TODO +- [ ] Seamless crashing + - crashes need to not cause RTC to lose time + - the remaining_distance needs to be unaffected + - the equivalent of a try-catch block on the whole program + - this should also make a log +- [ ] Logtool: python tool that shows logs + - needs to support both opening a log.bin and streaming from http://ip-address-or-hostname/log + - needs to have a CLI table output + - needs to have a GUI output (matplotlib) +- [ ] Refactor; make sure everything adheres to naming conventions