From 4f8a43a582653f0c3fdf01d255987f574d06f0ce Mon Sep 17 00:00:00 2001 From: CI System Date: Wed, 10 Dec 2025 15:54:36 -0700 Subject: [PATCH] refactor: trim CLAUDE.md per best practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following Anthropic's Claude Code best practices: - CLAUDE.md: 504 โ†’ 121 lines Keep concise: commands, code style, critical rules. Move detailed specs to /opt/repos/docs/reference/ ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 536 ++++++++---------------------------------------------- 1 file changed, 76 insertions(+), 460 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3c95170..73bec37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,504 +1,120 @@ # ClearGrow Probe Firmware -nRF52840-based wireless sensor node with Thread networking, code-based auto-pairing, and comprehensive environmental monitoring. +nRF52840 wireless sensor node with Thread networking and code-based pairing. -## Quick Reference +## Commands ```bash -# Build environment setup +# Setup source ~/ncs/.venv/bin/activate source ~/ncs/zephyr/zephyr-env.sh -cd /root/cleargrow/probe +cd /opt/repos/probe -# Build +# Build and flash west build -b nrf52840dk_nrf52840 - -# Flash west flash # Clean rebuild rm -rf build && west build -b nrf52840dk_nrf52840 -# Monitor logs (RTT or serial) -minicom -D /dev/ttyACM0 -b 115200 - -# Check memory usage +# Memory reports west build -t rom_report west build -t ram_report + +# Monitor (RTT recommended) +JLinkRTTClient +# Or serial +minicom -D /dev/ttyACM0 -b 115200 ``` -## Architecture Overview +## Code Style -### Source Structure +- **Zephyr style**: 4-space indent, `snake_case` +- **Logging**: `LOG_MODULE_REGISTER(name, LOG_LEVEL_INF)`, then `LOG_INF()`, `LOG_WRN()`, `LOG_ERR()` +- **Timing**: `k_sleep(K_MSEC(100))`, never raw ticks + +## Critical Rules + +**DO NOT use Zephyr PM framework** - nRF52840 lacks `HAS_PM` support. Use Nordic HAL directly: +```c +#include +nrf_power_system_off(NRF_POWER); // <1ยตA, does NOT return +``` + +**Thread role is MTD-SED** (Sleepy End Device) - poll period 1 second + +**CoAP operations are blocking** - never call from time-critical contexts + +## Architecture ``` src/ -โ”œโ”€โ”€ main.c # Entry point, threads, state machine -โ”œโ”€โ”€ sensor_manager.c # I2C sensor drivers, auto-detection -โ”œโ”€โ”€ thread_node.c # OpenThread MTD-SED, CoAP client -โ”œโ”€โ”€ power_manager.c # Battery monitoring, sleep modes -โ””โ”€โ”€ pairing_code.c # PSKd generation and storage - -include/ -โ”œโ”€โ”€ probe_config.h # Hardware config, data structures -โ””โ”€โ”€ pairing_code.h # Pairing API declarations +โ”œโ”€โ”€ main.c # Entry point, state machine +โ”œโ”€โ”€ sensor_manager.c # I2C sensor drivers +โ”œโ”€โ”€ thread_node.c # Thread + CoAP client +โ”œโ”€โ”€ power_manager.c # Battery, sleep modes +โ””โ”€โ”€ pairing_code.c # PSKd generation ``` -### Task Architecture - -| Thread | Stack | Priority | Purpose | -|--------|-------|----------|---------| -| Main | 2048 | Default | Watchdog, power state management | -| Sensor | 2048 | 7 | Periodic sensor reading | -| Transmit | 3072 | 8 | CoAP data transmission | - -**Data Flow**: -1. Sensor thread reads I2C sensors every 5s (configurable) -2. Signals transmit thread via semaphore -3. Transmit thread encodes TLV and sends CoAP POST -4. Main thread feeds watchdog, manages power states - -### Power Management - -Target: 1+ year on 2x AA batteries - -| State | Current | When Used | Implementation | -|-------|---------|-----------|----------------| -| Active | 10-15mA | Not commissioned | Normal operation | -| Idle | 6-8mA | Normal operation, Thread child | Thread SED mode | -| Sleep | 3-5mA | Low battery | Adaptive polling | -| Deep Sleep | <3ยตA | Critical battery | Nordic `nrf_power_system_off()` | -| SHIPPING | <1ยตA | Storage/transit | System OFF, all peripherals disabled | - -**Strategy**: -- Thread SED mode with 1s poll period -- Adaptive sensor polling based on battery level -- Soil sensor power controlled via GPIO -- **System OFF via Nordic HAL** (not Zephyr PM - see note below) - -**Important**: nRF52840 does NOT support Zephyr's generic `CONFIG_PM` framework (requires `HAS_PM` which is only available on newer chips like nRF54). Deep sleep and SHIPPING mode use Nordic HAL `nrf_power_system_off(NRF_POWER)` directly. - -## Sensor Configuration - -### Supported Sensors (I2C) - -| Sensor | Address | Measurement | Driver Status | -|--------|---------|-------------|---------------| -| SHT4x/SHTC3 | 0x44/0x45 | Temp, humidity, VPD, dew point | Production | -| MLX90614 | 0x5A | Leaf temp (IR), leaf VPD | Production | -| ADS1115 | 0x48 | Soil moisture, EC, pH, temp | Production | -| SCD4x | 0x62 | CO2, temp, humidity | Production | -| VEML7700 | 0x10 | PAR, lux, DLI | Production | -| Module EEPROM | 0x50 | Module identification | Optional | - -### Auto-Detection - -On startup, `sensor_manager` scans I2C bus for: -1. Module EEPROM (0x50) - reads type, serial, calibration -2. Direct sensor detection - checks if device responds -3. Builds `modules_present` bitmap - -**Retry Logic**: 3 attempts with exponential backoff (10ms base) - -## Thread Network - -### Role: MTD-SED (Minimal Thread Device - Sleepy End Device) - -**Configuration** (prj.conf): -```ini -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_MTD_SED=y -CONFIG_OPENTHREAD_POLL_PERIOD=1000 # Poll parent every 1 second -CONFIG_OPENTHREAD_JOINER=y -CONFIG_OPENTHREAD_SRP_CLIENT=y -``` - -**Network Behavior**: -1. On boot, checks if commissioned (`otDatasetIsCommissioned`) -2. If not commissioned, auto-starts joiner with PSKd -3. Joiner retry: 5s initial, exponential backoff to 5min max -4. **Battery protection timeout**: 10 minutes total, then enters low-power wait -5. When joined, registers SRP service: `_cleargrow._udp` - -### CoAP Message Format - -**POST /sensors** (TLV encoding): - -``` -Header (12 bytes): - [0-7] EUI-64 (probe ID) - [8] Protocol version (1) - [9] Battery percent - [10-11] Sequence number (uint16, little-endian) - -Measurements (TLV): - [Type][ValueInfo][Value...] - -Types: - 0x01 = Temperature (float32) - 0x02 = Humidity (float32) - 0x03 = CO2 (uint16) - 0x04 = PPFD/PAR (float32) - 0x05 = VPD (float32) - 0x06 = Leaf temp (float32) - 0x07 = Soil moisture (float32) - 0x08 = Soil temp (float32) - 0x09 = EC (float32) - 0x0A = pH (float32) - 0x0B = DLI (float32) - 0x0C = Dew point (float32) -``` - -**ValueInfo nibbles**: -- Upper nibble: type (0=float32, 3=int16, 4=uint16) -- Lower nibble: length in bytes - -**Controller Address**: Set via `thread_node_set_controller_addr()` with IPv6 address. DNS-SD resolution not yet implemented. - -## Code-Based Pairing - -Uses a printed code on device label for pairing. - -### PSKd (Pre-Shared Key for Device) - -**Format**: -- 6 characters (configurable via `CONFIG_PSKD_LENGTH`) -- Character set: 0-9, A-Z excluding I, O, Q, Z (Thread spec compliant) -- Generated using cryptographic RNG (`sys_csrand_get`) -- Stored in NVS flash (`settings` subsystem) - -**Example**: `A3F2K7` - -**Generation** (`pairing_code.c`): -1. On first boot, generates random PSKd -2. Saves to flash: `pairing/pskd` -3. Persists across reboots -4. Can regenerate via `pairing_code_regenerate()` - -### Auto-Joiner Behavior - -**State Machine** (`thread_node.c:auto_start_joiner()`): - -```c -1. Check if commissioned โ†’ YES: enable Thread, done -2. Check battery timeout (10min) โ†’ YES: stop attempts, sleep -3. Get PSKd from pairing_code module -4. Start joiner: otJoinerStart(pskd, "ClearGrow", "Probe", "1.0") -5. On failure: schedule retry with exponential backoff -``` - -**Retry Parameters**: -- Initial delay: 5 seconds -- Max delay: 5 minutes -- Total timeout: 10 minutes (battery protection) - -**User Flow**: -1. User powers on probe -2. Probe auto-starts joiner (LED blinks pattern) -3. User enters PSKd on controller UI -4. Controller enables commissioner with wildcard EUI-64 -5. Probe completes DTLS handshake, joins network -6. First CoAP message confirms pairing (3 quick blinks) - ## Key APIs -### Sensor Manager (`sensor_manager.c`) - ```c -// Initialize (detects modules, starts SCD4x periodic mode) -int sensor_manager_init(void); +// Sensors +sensor_manager_init(); +sensor_manager_get_data(&data); -// Main loop - call periodically -void sensor_manager_loop(void); +// Thread +thread_node_init(); +thread_node_send_sensors(&data); -// Thread-safe data access -int sensor_manager_get_data(probe_sensor_data_t *data); +// Power +power_manager_get_battery_percent(); +power_manager_set_state(POWER_STATE_SLEEP); -// Check if specific module present -bool sensor_manager_has_module(probe_module_type_t type); - -// Reset DLI accumulator (daily) -void sensor_manager_reset_dli(void); +// Pairing +pairing_code_get_pskd(); // Returns 6-char code ``` -**VPD Calculation** (Magnus formula): -```c -float svp = 0.6108 * exp(17.27 * temp_c / (temp_c + 237.3)); -float vpd = svp * (1.0 - (rh / 100.0)); -``` +## Sensors (I2C) -**DLI Accumulation** (umol/m2/s โ†’ mol/m2/day): -```c -dli += (par_umol * dt_sec) / 1000000.0; // Resets at 24h -``` +| Sensor | Addr | Measures | +|--------|------|----------| +| SHT4x | 0x44 | Temp, humidity | +| MLX90614 | 0x5A | Leaf temp (IR) | +| ADS1115 | 0x48 | Soil moisture, EC, pH | +| SCD4x | 0x62 | CO2 | +| VEML7700 | 0x10 | PAR/PPFD, DLI | -### Thread Node (`thread_node.c`) +## Power States -```c -// Initialize Thread stack -int thread_node_init(void); +| State | Current | Use | +|-------|---------|-----| +| Active | 10-15mA | Not commissioned | +| Idle | 6-8mA | Normal operation (SED) | +| Sleep | 3-5mA | Low battery | +| System OFF | <1ยตA | Critical/shipping | -// Send sensor data (blocking CoAP POST) -int thread_node_send_sensors(const probe_sensor_data_t *data); +## Pairing -// Set controller IPv6 address -int thread_node_set_controller_addr(const struct in6_addr *addr, uint16_t port); +- **PSKd**: 6-character code printed on device label +- **Format**: `A3F2K7` (excludes I, O, Q, Z) +- **Auto-joiner**: Retries with backoff, 10-min timeout for battery protection -// Get EUI-64 identifier -int thread_node_get_eui64(uint8_t eui64[8]); +## Debugging -// Factory reset Thread credentials -int thread_node_factory_reset(void); - -// State callback -void thread_node_set_state_callback(void (*cb)(thread_state_t, void *), void *ctx); -``` - -**Important**: CoAP operations are blocking. Do NOT call from UI contexts. - -### Power Manager (`power_manager.c`) - -```c -// Initialize (configures ADC, starts battery sampling) -int power_manager_init(void); - -// Set power state -int power_manager_set_state(power_state_t state); - -// Get battery info -uint16_t power_manager_get_battery_mv(void); -uint8_t power_manager_get_battery_percent(void); -bool power_manager_is_battery_low(void); - -// Adaptive polling interval -uint32_t power_manager_get_recommended_poll_interval(void); - -// Soil sensor power control -int power_manager_set_soil_power(bool enabled); -``` - -**Battery Sampling**: -- ADC channel 0 (AIN0) with voltage divider -- Sampled every 5 minutes (configurable) -- Low battery callback available - -### Pairing Code (`pairing_code.c`) - -```c -// Initialize (loads or generates PSKd) -int pairing_code_init(void); - -// Get current PSKd -const char *pairing_code_get_pskd(void); - -// Regenerate PSKd (security) -int pairing_code_regenerate(void); - -// Validate PSKd format -bool pairing_code_validate(const char *pskd); -``` - -## Configuration - -### Key Build Options (prj.conf) - -```ini -# Thread networking -CONFIG_OPENTHREAD_MTD_SED=y -CONFIG_OPENTHREAD_POLL_PERIOD=1000 -CONFIG_OPENTHREAD_JOINER=y -CONFIG_OPENTHREAD_SRP_CLIENT=y - -# Code-based pairing -CONFIG_CODE_PAIRING=y -CONFIG_PSKD_LENGTH=6 - -# Power management (nRF52840-specific) -# NOTE: CONFIG_PM/CONFIG_PM_DEVICE are NOT supported on nRF52840 -# (Zephyr PM framework requires HAS_PM, which nRF52840 lacks) -# Instead, use Nordic HAL: nrf_power_system_off() for System OFF state -# See PROBE-SL-002 remediation (2025-12-09) for details - -# Persistent storage -CONFIG_NVS=y -CONFIG_SETTINGS=y -CONFIG_SETTINGS_NVS=y - -# Watchdog -CONFIG_WATCHDOG=y -CONFIG_WDT_DISABLE_AT_BOOT=n - -# MCUboot for OTA -CONFIG_BOOTLOADER_MCUBOOT=y -CONFIG_STREAM_FLASH=y -CONFIG_IMG_MANAGER=y - -# Memory -CONFIG_MAIN_STACK_SIZE=2048 -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 -CONFIG_HEAP_MEM_POOL_SIZE=16384 -``` - -### Runtime Configuration - -**Adaptive Polling** (battery-based): -- Battery >50%: 5 seconds (default) -- Battery 20-50%: 10 seconds -- Battery low: 30 seconds -- Battery critical: 60 seconds - -**Thread Poll Period**: 1000ms (1 second) -- Probe wakes to poll parent, check for downlink data -- CPU sleeps between polls - -## Debugging Tips - -### Common Issues - -1. **Probe won't join network** - - Check PSKd matches device label - - Verify controller is in pairing mode (commissioner active) - - Check Thread network is active (border router running) - - Battery level >30% (joiner requires radio power) - - Look for `Joiner failed` logs with error code - -2. **High power consumption** - - Verify SED mode: `grep "MTD-SED" build/zephyr/zephyr.dts` - - Check poll period: should be 1000ms - - Ensure soil sensor power disabled between reads - - Look for I2C bus lockups (retry loops) - -3. **Sensor read failures** - - Check I2C pull-ups (4.7k to 3.3V) - - Verify I2C address in `probe_config.h` - - Try `sensor_manager_rescan()` to re-detect - - Check module EEPROM if present - -4. **Auto-joiner timeout** - - 10-minute timeout is safety feature - - Power cycle to retry - - Check battery level (critical = no join attempts) - - Verify Thread network credentials correct - -5. **CoAP transmission failures** - - Controller address must be set (IPv6) - - Check `s_controller_resolved` flag - - Verify controller CoAP server listening on port 5683 - - Thread parent must be reachable (RSSI check) - -### Log Analysis - -**Look for these messages**: -``` -โœ“ "Pairing code initialized (PSKd: XXXXXX)" -โœ“ "Thread state: Child" -โœ“ "Sent TLV sensor data (seq X, Y bytes)" -โœ— "Joiner failed: X" -โœ— "Failed to transmit sensor data: X" -โœ— "Battery: XmV (critical)" -``` - -**Memory usage**: ```bash -west build -t rom_report # Flash usage by section -west build -t ram_report # RAM usage by symbol +# Check Thread SED mode +grep "MTD-SED" build/zephyr/zephyr.dts + +# Log levels +LOG_MODULE_REGISTER(thread_node, LOG_LEVEL_DBG); ``` -**Expected**: -- Flash: 400-600KB (< 70% of 1MB) -- RAM: 64-100KB (< 50% of 256KB) +**Common issues**: +- Won't join: Check PSKd, verify controller in pairing mode +- High power: Verify SED mode, check poll period +- Sensor fails: Check I2C pull-ups (4.7k), verify address -### Serial Logging +## Documentation -**RTT (recommended)**: -```bash -JLinkRTTClient -# Fast, no UART overhead -``` - -**UART**: -```bash -minicom -D /dev/ttyACM0 -b 115200 -# Standard serial console -``` - -**Log Levels** (per module): -```c -LOG_MODULE_REGISTER(module_name, LOG_LEVEL_INF); -// Change to LOG_LEVEL_DBG for verbose output -``` - - -## OTA Updates - -**MCUboot dual-bank scheme**: -- Slot 0: Active firmware -- Slot 1: Download area -- On reboot, MCUboot validates and swaps if needed - -**Build artifacts**: -``` -build/zephyr/zephyr.hex # Main firmware (for direct flash) -build/zephyr/merged.hex # Firmware + MCUboot bootloader -build/zephyr/zephyr.signed.bin # Signed image for OTA -``` - -**OTA not yet implemented** - placeholder for future work via CoAP block transfer. - -## Memory Map - -| Region | Address | Size | Usage | -|--------|---------|------|-------| -| Flash | 0x00000000 | 48KB | MCUboot bootloader | -| Flash | 0x0000C000 | 476KB | Slot 0 (active firmware) | -| Flash | 0x00083000 | 476KB | Slot 1 (OTA download) | -| Flash | 0x000FA000 | 24KB | NVS settings storage | -| RAM | 0x20000000 | 256KB | Application + stack | - -## Hardware Variants - -**Current target**: nRF52840 DK (`nrf52840dk_nrf52840`) - -**Custom board**: Create devicetree overlay in board-specific directory. - -**Example** (`boards/nrf52840_probe.overlay`): -```dts -&i2c0 { - status = "okay"; - sda-pin = <26>; - scl-pin = <27>; -}; - -&adc { - status = "okay"; - #address-cells = <1>; - #size-cells = <0>; - - channel@0 { - reg = <0>; - zephyr,gain = "ADC_GAIN_1_6"; - zephyr,reference = "ADC_REF_INTERNAL"; - zephyr,acquisition-time = <10>; - zephyr,input-positive = ; - }; -}; -``` - -## Appendix: Constants - -**Battery thresholds** (3.0V LiPo): -```c -BATTERY_FULL_MV 4200 -BATTERY_NOMINAL_MV 3700 -BATTERY_LOW_MV 3400 -BATTERY_CRITICAL_MV 3200 -BATTERY_CUTOFF_MV 3000 -``` - -**PSKd character set**: -``` -0123456789ABCDEFGHJKLMNPRSTUVWXY -(32 chars: excludes I, O, Q, Z) -``` - -**Module EEPROM magic**: `0x434C4752` ("CLGR") +Full docs: `/opt/repos/docs/reference/firmware/probe/`