refactor: trim CLAUDE.md per best practices
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 <noreply@anthropic.com>
This commit is contained in:
536
CLAUDE.md
536
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 <hal/nrf_power.h>
|
||||
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 = <NRF_SAADC_AIN0>;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## 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/`
|
||||
|
||||
Reference in New Issue
Block a user