Files
SC-F001/CLAUDE.md
Thaddeus Hughes 730ba5ea15 init claude
2026-03-04 12:34:08 -06:00

269 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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