Merge pull request 'feat: persistent config' (#14) from feature/persistent-config into main
Reviewed-on: #14
This commit is contained in:
95
ctl.sh
95
ctl.sh
@@ -4,6 +4,25 @@ set -e
|
||||
|
||||
source .env
|
||||
|
||||
## Spore Control Script
|
||||
## Usage: ./ctl.sh <command> [options]
|
||||
##
|
||||
## Commands:
|
||||
## build [target] - Build firmware for target (base, d1_mini, etc.)
|
||||
## flash [target] - Flash firmware to device
|
||||
## uploadfs [target] - Upload filesystem to device
|
||||
## ota update <ip> <target> - OTA update specific node
|
||||
## ota all <target> - OTA update all nodes in cluster
|
||||
## cluster members - List cluster members
|
||||
## node wifi <ssid> <password> [ip] - Configure WiFi on node
|
||||
## monitor - Monitor serial output
|
||||
##
|
||||
## Examples:
|
||||
## ./ctl.sh build base
|
||||
## ./ctl.sh flash d1_mini
|
||||
## ./ctl.sh node wifi "MyNetwork" "MyPassword"
|
||||
## ./ctl.sh node wifi "MyNetwork" "MyPassword" 192.168.1.100
|
||||
|
||||
function info {
|
||||
sed -n 's/^##//p' ctl.sh
|
||||
}
|
||||
@@ -69,6 +88,82 @@ function cluster {
|
||||
${@:-info}
|
||||
}
|
||||
|
||||
function node {
|
||||
function wifi {
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 node wifi <ssid> <password> [node_ip]"
|
||||
echo " ssid: WiFi network name"
|
||||
echo " password: WiFi password"
|
||||
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ssid="$1"
|
||||
local password="$2"
|
||||
local node_ip="${3:-$API_NODE}"
|
||||
|
||||
echo "Configuring WiFi on node $node_ip..."
|
||||
echo "SSID: $ssid"
|
||||
|
||||
# Configure WiFi using the API endpoint
|
||||
response=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "ssid=$ssid&password=$password" \
|
||||
"http://$node_ip/api/network/wifi/config" 2>/dev/null || echo -e "\n000")
|
||||
|
||||
# Extract HTTP status code and response body
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
response_body=$(echo "$response" | head -n -1)
|
||||
|
||||
# Check if curl succeeded
|
||||
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
|
||||
echo "Error: Failed to connect to node at $node_ip"
|
||||
echo "Please check:"
|
||||
echo " - Node is powered on and connected to network"
|
||||
echo " - IP address is correct"
|
||||
echo " - Node is running Spore firmware"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check HTTP status code
|
||||
if [ "$http_code" != "200" ]; then
|
||||
echo "Error: HTTP $http_code - Server error"
|
||||
echo "Response: $response_body"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Parse and display the response
|
||||
status=$(echo "$response_body" | jq -r '.status // "unknown"')
|
||||
message=$(echo "$response_body" | jq -r '.message // "No message"')
|
||||
config_saved=$(echo "$response_body" | jq -r '.config_saved // false')
|
||||
restarting=$(echo "$response_body" | jq -r '.restarting // false')
|
||||
connected=$(echo "$response_body" | jq -r '.connected // false')
|
||||
ip=$(echo "$response_body" | jq -r '.ip // "N/A"')
|
||||
|
||||
echo "Status: $status"
|
||||
echo "Message: $message"
|
||||
echo "Config saved: $config_saved"
|
||||
if [ "$restarting" = "true" ]; then
|
||||
echo "Restarting: true"
|
||||
echo "Note: Node will restart to apply new WiFi settings"
|
||||
fi
|
||||
echo "Connected: $connected"
|
||||
if [ "$connected" = "true" ]; then
|
||||
echo "IP Address: $ip"
|
||||
fi
|
||||
|
||||
# Return appropriate exit code
|
||||
if [ "$status" = "success" ]; then
|
||||
echo "WiFi configuration completed successfully!"
|
||||
return 0
|
||||
else
|
||||
echo "WiFi configuration failed!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
${@:-info}
|
||||
}
|
||||
|
||||
function monitor {
|
||||
pio run --target monitor
|
||||
}
|
||||
|
||||
@@ -291,6 +291,51 @@ The system includes automatic WiFi fallback for robust operation:
|
||||
|
||||
## Configuration Management
|
||||
|
||||
SPORE implements a persistent configuration system that manages device settings across reboots and provides runtime reconfiguration capabilities.
|
||||
|
||||
### Configuration Architecture
|
||||
|
||||
The configuration system consists of several key components:
|
||||
|
||||
- **`Config` Class**: Central configuration management with default constants
|
||||
- **LittleFS Storage**: Persistent file-based storage (`/config.json`)
|
||||
- **Runtime Updates**: Live configuration changes via HTTP API
|
||||
- **Automatic Persistence**: Configuration changes are automatically saved
|
||||
|
||||
### Configuration Categories
|
||||
|
||||
| Category | Description | Examples |
|
||||
|----------|-------------|----------|
|
||||
| **WiFi Configuration** | Network connection settings | SSID, password, timeouts |
|
||||
| **Network Configuration** | Network service settings | UDP port, API server port |
|
||||
| **Cluster Configuration** | Cluster management settings | Discovery intervals, heartbeat timing |
|
||||
| **Node Status Thresholds** | Health monitoring thresholds | Active/inactive/dead timeouts |
|
||||
| **System Configuration** | Core system settings | Restart delay, JSON document size |
|
||||
| **Memory Management** | Resource management settings | Memory thresholds, HTTP request limits |
|
||||
|
||||
### Configuration Lifecycle
|
||||
|
||||
1. **Boot Process**: Load configuration from `/config.json` or use defaults
|
||||
2. **Runtime Updates**: Configuration changes via HTTP API
|
||||
3. **Persistent Storage**: Changes automatically saved to LittleFS
|
||||
4. **Service Integration**: Configuration applied to all system services
|
||||
|
||||
### Default Value Management
|
||||
|
||||
All default values are defined as `constexpr` constants in the `Config` class:
|
||||
|
||||
```cpp
|
||||
static constexpr const char* DEFAULT_WIFI_SSID = "shroud";
|
||||
static constexpr uint16_t DEFAULT_UDP_PORT = 4210;
|
||||
static constexpr unsigned long DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- **Single Source of Truth**: All defaults defined once
|
||||
- **Type Safety**: Compile-time type checking
|
||||
- **Maintainability**: Easy to update default values
|
||||
- **Consistency**: Same defaults used in `setDefaults()` and `loadFromFile()`
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
@@ -380,6 +425,8 @@ pio device monitor
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[Configuration Management](./ConfigurationManagement.md)** - Persistent configuration system
|
||||
- **[WiFi Configuration](./WiFiConfiguration.md)** - WiFi setup and reconfiguration process
|
||||
- **[Task Management](./TaskManagement.md)** - Background task system
|
||||
- **[API Reference](./API.md)** - REST API documentation
|
||||
- **[TaskManager API](./TaskManager.md)** - TaskManager class reference
|
||||
|
||||
340
docs/ConfigurationManagement.md
Normal file
340
docs/ConfigurationManagement.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# SPORE Configuration Management
|
||||
|
||||
## Overview
|
||||
|
||||
SPORE implements a comprehensive persistent configuration system that manages device settings across reboots and provides runtime reconfiguration capabilities. The system uses LittleFS for persistent storage and provides both programmatic and HTTP API access to configuration parameters.
|
||||
|
||||
## Configuration Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **`Config` Class**: Central configuration management
|
||||
- **LittleFS Storage**: Persistent file-based storage (`/config.json`)
|
||||
- **Default Constants**: Single source of truth for all default values
|
||||
- **Runtime Updates**: Live configuration changes via HTTP API
|
||||
- **Automatic Persistence**: Configuration changes are automatically saved
|
||||
|
||||
### Configuration Categories
|
||||
|
||||
The configuration system manages several categories of settings:
|
||||
|
||||
| Category | Description | Examples |
|
||||
|----------|-------------|----------|
|
||||
| **WiFi Configuration** | Network connection settings | SSID, password, timeouts |
|
||||
| **Network Configuration** | Network service settings | UDP port, API server port |
|
||||
| **Cluster Configuration** | Cluster management settings | Discovery intervals, heartbeat timing |
|
||||
| **Node Status Thresholds** | Health monitoring thresholds | Active/inactive/dead timeouts |
|
||||
| **System Configuration** | Core system settings | Restart delay, JSON document size |
|
||||
| **Memory Management** | Resource management settings | Memory thresholds, HTTP request limits |
|
||||
|
||||
## Configuration Lifecycle
|
||||
|
||||
### 1. Boot Process
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[System Boot] --> B[Initialize LittleFS]
|
||||
B --> C{Config File Exists?}
|
||||
C -->|Yes| D[Load from File]
|
||||
C -->|No| E[Use Defaults]
|
||||
D --> F[Apply Configuration]
|
||||
E --> G[Save Defaults to File]
|
||||
G --> F
|
||||
F --> H[Start Services]
|
||||
```
|
||||
|
||||
**Boot Sequence:**
|
||||
|
||||
1. **LittleFS Initialization**: Mount the filesystem for persistent storage
|
||||
2. **Configuration Loading**: Attempt to load `/config.json`
|
||||
3. **Fallback to Defaults**: If no config file exists, use hardcoded defaults
|
||||
4. **Default Persistence**: Save default configuration to file for future boots
|
||||
5. **Service Initialization**: Apply configuration to all system services
|
||||
|
||||
### 2. Runtime Configuration
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[HTTP API Request] --> B[Validate Parameters]
|
||||
B --> C[Update Config Object]
|
||||
C --> D[Save to File]
|
||||
D --> E{Requires Restart?}
|
||||
E -->|Yes| F[Schedule Restart]
|
||||
E -->|No| G[Apply Changes]
|
||||
F --> H[Send Response]
|
||||
G --> H
|
||||
```
|
||||
|
||||
**Runtime Update Process:**
|
||||
|
||||
1. **API Request**: Configuration change via HTTP API
|
||||
2. **Parameter Validation**: Validate input parameters
|
||||
3. **Memory Update**: Update configuration object in memory
|
||||
4. **Persistent Save**: Save changes to `/config.json`
|
||||
5. **Service Notification**: Notify affected services of changes
|
||||
6. **Restart if Needed**: Restart system for certain configuration changes
|
||||
|
||||
## Configuration File Format
|
||||
|
||||
### JSON Structure
|
||||
|
||||
The configuration is stored as a JSON file with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"wifi": {
|
||||
"ssid": "MyNetwork",
|
||||
"password": "mypassword",
|
||||
"connect_timeout_ms": 15000,
|
||||
"retry_delay_ms": 500
|
||||
},
|
||||
"network": {
|
||||
"udp_port": 4210,
|
||||
"api_server_port": 80
|
||||
},
|
||||
"cluster": {
|
||||
"discovery_interval_ms": 1000,
|
||||
"heartbeat_interval_ms": 5000,
|
||||
"cluster_listen_interval_ms": 10,
|
||||
"status_update_interval_ms": 1000,
|
||||
"member_info_update_interval_ms": 10000,
|
||||
"print_interval_ms": 5000
|
||||
},
|
||||
"thresholds": {
|
||||
"node_active_threshold_ms": 10000,
|
||||
"node_inactive_threshold_ms": 60000,
|
||||
"node_dead_threshold_ms": 120000
|
||||
},
|
||||
"system": {
|
||||
"restart_delay_ms": 10,
|
||||
"json_doc_size": 1024
|
||||
},
|
||||
"memory": {
|
||||
"low_memory_threshold_bytes": 10000,
|
||||
"critical_memory_threshold_bytes": 5000,
|
||||
"max_concurrent_http_requests": 3
|
||||
},
|
||||
"_meta": {
|
||||
"version": "1.0",
|
||||
"saved_at": 1234567890
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Metadata Fields
|
||||
|
||||
- **`version`**: Configuration schema version for future compatibility
|
||||
- **`saved_at`**: Timestamp when configuration was saved (millis())
|
||||
|
||||
## Default Configuration Constants
|
||||
|
||||
All default values are defined as `constexpr` constants in the `Config` class header:
|
||||
|
||||
```cpp
|
||||
// Default Configuration Constants
|
||||
static constexpr const char* DEFAULT_WIFI_SSID = "shroud";
|
||||
static constexpr const char* DEFAULT_WIFI_PASSWORD = "th3r31sn0sp00n";
|
||||
static constexpr uint16_t DEFAULT_UDP_PORT = 4210;
|
||||
static constexpr uint16_t DEFAULT_API_SERVER_PORT = 80;
|
||||
// ... additional constants
|
||||
```
|
||||
|
||||
### Benefits of Constants
|
||||
|
||||
- **Single Source of Truth**: All defaults defined once
|
||||
- **Type Safety**: Compile-time type checking
|
||||
- **Maintainability**: Easy to update default values
|
||||
- **Consistency**: Same defaults used in `setDefaults()` and `loadFromFile()`
|
||||
|
||||
## Configuration Methods
|
||||
|
||||
### Core Methods
|
||||
|
||||
| Method | Purpose | Parameters |
|
||||
|--------|---------|------------|
|
||||
| `setDefaults()` | Initialize with default values | None |
|
||||
| `loadFromFile()` | Load configuration from persistent storage | `filename` (optional) |
|
||||
| `saveToFile()` | Save configuration to persistent storage | `filename` (optional) |
|
||||
|
||||
### Loading Process
|
||||
|
||||
```cpp
|
||||
bool Config::loadFromFile(const String& filename) {
|
||||
// 1. Initialize LittleFS
|
||||
if (!LittleFS.begin()) {
|
||||
LOG_ERROR("Config", "LittleFS not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Check file existence
|
||||
if (!LittleFS.exists(filename)) {
|
||||
LOG_DEBUG("Config", "Config file does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Parse JSON with fallback defaults
|
||||
wifi_ssid = doc["wifi"]["ssid"] | DEFAULT_WIFI_SSID;
|
||||
wifi_password = doc["wifi"]["password"] | DEFAULT_WIFI_PASSWORD;
|
||||
// ... additional fields
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Saving Process
|
||||
|
||||
```cpp
|
||||
bool Config::saveToFile(const String& filename) {
|
||||
// 1. Create JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// 2. Serialize all configuration fields
|
||||
doc["wifi"]["ssid"] = wifi_ssid;
|
||||
doc["wifi"]["password"] = wifi_password;
|
||||
// ... additional fields
|
||||
|
||||
// 3. Add metadata
|
||||
doc["_meta"]["version"] = "1.0";
|
||||
doc["_meta"]["saved_at"] = millis();
|
||||
|
||||
// 4. Write to file
|
||||
size_t bytesWritten = serializeJson(doc, file);
|
||||
return bytesWritten > 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Scenarios
|
||||
|
||||
| Scenario | Error Handling | Recovery |
|
||||
|----------|----------------|----------|
|
||||
| **LittleFS Init Failure** | Log warning, use defaults | Continue with default configuration |
|
||||
| **File Not Found** | Log debug message, return false | Caller handles fallback to defaults |
|
||||
| **JSON Parse Error** | Log error, return false | Caller handles fallback to defaults |
|
||||
| **Write Failure** | Log error, return false | Configuration not persisted |
|
||||
| **Memory Allocation Failure** | Log error, return false | Operation aborted |
|
||||
|
||||
### Logging Levels
|
||||
|
||||
- **ERROR**: Critical failures that prevent operation
|
||||
- **WARN**: Non-critical issues that affect functionality
|
||||
- **INFO**: Normal operation events
|
||||
- **DEBUG**: Detailed diagnostic information
|
||||
|
||||
## Configuration Validation
|
||||
|
||||
### Input Validation
|
||||
|
||||
- **Required Fields**: SSID and password are mandatory for WiFi configuration
|
||||
- **Range Validation**: Numeric values are validated against reasonable ranges
|
||||
- **Type Validation**: JSON parsing ensures correct data types
|
||||
- **Length Limits**: String fields have maximum length constraints
|
||||
|
||||
### Default Value Fallback
|
||||
|
||||
The system uses the `|` operator for safe fallback to defaults:
|
||||
|
||||
```cpp
|
||||
// Safe loading with fallback
|
||||
wifi_ssid = doc["wifi"]["ssid"] | DEFAULT_WIFI_SSID;
|
||||
udp_port = doc["network"]["udp_port"] | DEFAULT_UDP_PORT;
|
||||
```
|
||||
|
||||
This ensures that:
|
||||
- Missing fields use default values
|
||||
- Invalid values are replaced with defaults
|
||||
- System remains functional with partial configuration
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Memory Usage
|
||||
|
||||
- **Configuration Object**: ~200 bytes in RAM
|
||||
- **JSON Document**: ~1KB during parsing/saving
|
||||
- **LittleFS Overhead**: ~2-4KB for filesystem
|
||||
|
||||
### Storage Requirements
|
||||
|
||||
- **Config File**: ~500-800 bytes on disk
|
||||
- **LittleFS Minimum**: ~64KB partition size
|
||||
- **Available Space**: Depends on flash size (1MB+ recommended)
|
||||
|
||||
### Processing Overhead
|
||||
|
||||
- **Load Time**: ~10-50ms for JSON parsing
|
||||
- **Save Time**: ~20-100ms for JSON serialization
|
||||
- **File I/O**: Minimal impact on system performance
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Current Implementation
|
||||
|
||||
- **Local Storage Only**: Configuration stored on device filesystem
|
||||
- **No Encryption**: Plain text storage (LAN-only access assumed)
|
||||
- **Access Control**: No authentication for configuration changes
|
||||
|
||||
### Future Enhancements
|
||||
|
||||
- **Configuration Encryption**: Encrypt sensitive fields (passwords)
|
||||
- **Access Control**: Authentication for configuration changes
|
||||
- **Audit Logging**: Track configuration modifications
|
||||
- **Backup/Restore**: Configuration backup and restore capabilities
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Configuration Not Persisting**
|
||||
- Check LittleFS initialization
|
||||
- Verify file write permissions
|
||||
- Monitor available flash space
|
||||
|
||||
2. **Default Values Not Applied**
|
||||
- Verify constants are properly defined
|
||||
- Check JSON parsing errors
|
||||
- Ensure fallback logic is working
|
||||
|
||||
3. **Configuration Corruption**
|
||||
- Delete `/config.json` to reset to defaults
|
||||
- Check for JSON syntax errors
|
||||
- Verify file system integrity
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check configuration status
|
||||
curl -s http://192.168.1.100/api/network/status | jq '.'
|
||||
|
||||
# View current WiFi settings
|
||||
curl -s http://192.168.1.100/api/network/status | jq '.wifi'
|
||||
|
||||
# Test configuration save
|
||||
curl -X POST http://192.168.1.100/api/network/wifi/config \
|
||||
-d "ssid=TestNetwork&password=testpass"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Configuration Management
|
||||
|
||||
1. **Use Constants**: Always define defaults as constants
|
||||
2. **Validate Input**: Check all configuration parameters
|
||||
3. **Handle Errors**: Implement proper error handling
|
||||
4. **Log Changes**: Log configuration modifications
|
||||
5. **Test Fallbacks**: Ensure default fallbacks work correctly
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
1. **Single Source**: Define each default value only once
|
||||
2. **Type Safety**: Use appropriate data types
|
||||
3. **Documentation**: Document all configuration parameters
|
||||
4. **Versioning**: Include version metadata in config files
|
||||
5. **Backward Compatibility**: Handle old configuration formats
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[WiFi Configuration Process](./WiFiConfiguration.md)** - Detailed WiFi setup workflow
|
||||
- **[API Reference](./API.md)** - HTTP API for configuration management
|
||||
- **[Architecture Overview](./Architecture.md)** - System architecture and components
|
||||
- **[OpenAPI Specification](../api/)** - Machine-readable API specification
|
||||
478
docs/WiFiConfiguration.md
Normal file
478
docs/WiFiConfiguration.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# SPORE WiFi Configuration Process
|
||||
|
||||
## Overview
|
||||
|
||||
SPORE implements a WiFi configuration system that handles initial setup, runtime reconfiguration, and automatic fallback mechanisms. The system supports both Station (STA) and Access Point (AP) modes with seamless switching between them.
|
||||
|
||||
## WiFi Configuration Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **`NetworkManager`**: Handles WiFi operations and configuration
|
||||
- **`NetworkService`**: Provides HTTP API endpoints for WiFi management
|
||||
- **`Config`**: Stores WiFi credentials and connection parameters
|
||||
- **LittleFS**: Persistent storage for WiFi configuration
|
||||
- **ESP8266 WiFi Library**: Low-level WiFi operations
|
||||
|
||||
### Configuration Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `wifi_ssid` | String | "shroud" | Network SSID for connection |
|
||||
| `wifi_password` | String | "th3r31sn0sp00n" | Network password |
|
||||
| `wifi_connect_timeout_ms` | uint32_t | 15000 | Connection timeout (15 seconds) |
|
||||
| `wifi_retry_delay_ms` | uint32_t | 500 | Delay between connection attempts |
|
||||
|
||||
## WiFi Configuration Lifecycle
|
||||
|
||||
### 1. Boot Process
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[System Boot] --> B[Initialize LittleFS]
|
||||
B --> C[Load Configuration]
|
||||
C --> D[Initialize WiFi Mode]
|
||||
D --> E[Set Hostname from MAC]
|
||||
E --> F[Attempt STA Connection]
|
||||
F --> G{Connection Successful?}
|
||||
G -->|Yes| H[STA Mode Active]
|
||||
G -->|No| I[Switch to AP Mode]
|
||||
I --> J[Create Access Point]
|
||||
J --> K[AP Mode Active]
|
||||
H --> L[Start UDP Services]
|
||||
K --> L
|
||||
L --> M[Initialize Node Context]
|
||||
M --> N[Start Cluster Services]
|
||||
```
|
||||
|
||||
**Detailed Boot Sequence:**
|
||||
|
||||
1. **LittleFS Initialization**
|
||||
```cpp
|
||||
if (!LittleFS.begin()) {
|
||||
LOG_WARN("Config", "Failed to initialize LittleFS, using defaults");
|
||||
setDefaults();
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Configuration Loading**
|
||||
- Load WiFi credentials from `/config.json`
|
||||
- Fall back to defaults if file doesn't exist
|
||||
- Validate configuration parameters
|
||||
|
||||
3. **WiFi Mode Initialization**
|
||||
```cpp
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str());
|
||||
```
|
||||
|
||||
4. **Hostname Generation**
|
||||
```cpp
|
||||
void NetworkManager::setHostnameFromMac() {
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
char buf[32];
|
||||
sprintf(buf, "esp-%02X%02X%02X%02X%02X%02X",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
WiFi.hostname(buf);
|
||||
ctx.hostname = String(buf);
|
||||
}
|
||||
```
|
||||
|
||||
5. **Connection Attempt**
|
||||
```cpp
|
||||
unsigned long startAttemptTime = millis();
|
||||
while (WiFi.status() != WL_CONNECTED &&
|
||||
millis() - startAttemptTime < ctx.config.wifi_connect_timeout_ms) {
|
||||
delay(ctx.config.wifi_retry_delay_ms);
|
||||
}
|
||||
```
|
||||
|
||||
6. **Fallback to AP Mode**
|
||||
```cpp
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
LOG_WARN("WiFi", "Failed to connect to AP. Creating AP...");
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Runtime Reconfiguration
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[HTTP API Request] --> B[Validate Parameters]
|
||||
B --> C[Update Config Object]
|
||||
C --> D[Save to Persistent Storage]
|
||||
D --> E[Send Response to Client]
|
||||
E --> F[Schedule Node Restart]
|
||||
F --> G[Node Restarts]
|
||||
G --> H[Apply New Configuration]
|
||||
H --> I[Attempt New Connection]
|
||||
```
|
||||
|
||||
**Runtime Reconfiguration Process:**
|
||||
|
||||
1. **API Request Validation**
|
||||
```cpp
|
||||
if (!request->hasParam("ssid", true) || !request->hasParam("password", true)) {
|
||||
request->send(400, "application/json", "{\"error\": \"Missing required parameters\"}");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Configuration Update**
|
||||
```cpp
|
||||
void NetworkManager::setWiFiConfig(const String& ssid, const String& password,
|
||||
uint32_t connect_timeout_ms, uint32_t retry_delay_ms) {
|
||||
ctx.config.wifi_ssid = ssid;
|
||||
ctx.config.wifi_password = password;
|
||||
ctx.config.wifi_connect_timeout_ms = connect_timeout_ms;
|
||||
ctx.config.wifi_retry_delay_ms = retry_delay_ms;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Persistent Storage**
|
||||
```cpp
|
||||
bool configSaved = networkManager.saveConfig();
|
||||
if (!configSaved) {
|
||||
LOG_WARN("NetworkService", "Failed to save WiFi configuration to persistent storage");
|
||||
}
|
||||
```
|
||||
|
||||
4. **Restart Scheduling**
|
||||
```cpp
|
||||
request->onDisconnect([this]() {
|
||||
LOG_INFO("NetworkService", "Restarting node to apply WiFi configuration...");
|
||||
delay(100); // Give time for response to be sent
|
||||
networkManager.restartNode();
|
||||
});
|
||||
```
|
||||
|
||||
## WiFi Modes
|
||||
|
||||
### Station Mode (STA)
|
||||
|
||||
**Purpose**: Connect to existing WiFi network as a client
|
||||
|
||||
**Configuration**:
|
||||
- SSID and password required
|
||||
- Automatic IP assignment via DHCP
|
||||
- Hostname set from MAC address
|
||||
- UDP services on configured port
|
||||
|
||||
**Connection Process**:
|
||||
1. Set WiFi mode to STA
|
||||
2. Begin connection with credentials
|
||||
3. Wait for connection with timeout
|
||||
4. Set hostname and start services
|
||||
|
||||
### Access Point Mode (AP)
|
||||
|
||||
**Purpose**: Create WiFi hotspot when STA connection fails
|
||||
|
||||
**Configuration**:
|
||||
- Uses configured SSID/password for AP
|
||||
- Fixed IP address (usually 192.168.4.1)
|
||||
- Allows other devices to connect
|
||||
- Maintains cluster functionality
|
||||
|
||||
**AP Creation Process**:
|
||||
1. Switch WiFi mode to AP
|
||||
2. Create soft access point
|
||||
3. Set AP IP address
|
||||
4. Start services on AP IP
|
||||
|
||||
## HTTP API Endpoints
|
||||
|
||||
### Network Status
|
||||
|
||||
#### GET `/api/network/status`
|
||||
|
||||
Returns comprehensive WiFi and network status information.
|
||||
|
||||
**Response Fields**:
|
||||
```json
|
||||
{
|
||||
"wifi": {
|
||||
"connected": true,
|
||||
"mode": "STA",
|
||||
"ssid": "MyNetwork",
|
||||
"ip": "192.168.1.100",
|
||||
"mac": "AA:BB:CC:DD:EE:FF",
|
||||
"hostname": "esp-AABBCCDDEEFF",
|
||||
"rssi": -45,
|
||||
"ap_ip": "192.168.4.1",
|
||||
"ap_mac": "AA:BB:CC:DD:EE:FF",
|
||||
"stations_connected": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WiFi Scanning
|
||||
|
||||
#### GET `/api/network/wifi/scan`
|
||||
|
||||
Returns list of available WiFi networks from last scan.
|
||||
|
||||
**Response Fields**:
|
||||
```json
|
||||
{
|
||||
"access_points": [
|
||||
{
|
||||
"ssid": "MyNetwork",
|
||||
"rssi": -45,
|
||||
"channel": 6,
|
||||
"encryption_type": 4,
|
||||
"hidden": false,
|
||||
"bssid": "AA:BB:CC:DD:EE:FF"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### POST `/api/network/wifi/scan`
|
||||
|
||||
Initiates a new WiFi network scan.
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"status": "scanning",
|
||||
"message": "WiFi scan started"
|
||||
}
|
||||
```
|
||||
|
||||
### WiFi Configuration
|
||||
|
||||
#### POST `/api/network/wifi/config`
|
||||
|
||||
Configures WiFi connection with new credentials.
|
||||
|
||||
**Parameters**:
|
||||
- `ssid` (required): Network SSID
|
||||
- `password` (required): Network password
|
||||
- `connect_timeout_ms` (optional): Connection timeout (default: 10000)
|
||||
- `retry_delay_ms` (optional): Retry delay (default: 500)
|
||||
|
||||
**Request Example**:
|
||||
```bash
|
||||
curl -X POST http://192.168.1.100/api/network/wifi/config \
|
||||
-d "ssid=MyNewNetwork&password=newpassword&connect_timeout_ms=15000"
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "WiFi configuration updated and saved",
|
||||
"config_saved": true,
|
||||
"restarting": true
|
||||
}
|
||||
```
|
||||
|
||||
## WiFi Scanning Process
|
||||
|
||||
### Scanning Implementation
|
||||
|
||||
```cpp
|
||||
void NetworkManager::scanWifi() {
|
||||
if (!isScanning) {
|
||||
isScanning = true;
|
||||
LOG_INFO("WiFi", "Starting WiFi scan...");
|
||||
WiFi.scanNetworksAsync([this](int networksFound) {
|
||||
LOG_INFO("WiFi", "Scan completed, found " + String(networksFound) + " networks");
|
||||
this->processAccessPoints();
|
||||
this->isScanning = false;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Access Point Processing
|
||||
|
||||
```cpp
|
||||
void NetworkManager::processAccessPoints() {
|
||||
int numNetworks = WiFi.scanComplete();
|
||||
if (numNetworks <= 0) return;
|
||||
|
||||
accessPoints.clear();
|
||||
for (int i = 0; i < numNetworks; i++) {
|
||||
AccessPoint ap;
|
||||
ap.ssid = WiFi.SSID(i);
|
||||
ap.rssi = WiFi.RSSI(i);
|
||||
ap.encryptionType = WiFi.encryptionType(i);
|
||||
ap.channel = WiFi.channel(i);
|
||||
ap.isHidden = ap.ssid.length() == 0;
|
||||
|
||||
uint8_t* newBssid = new uint8_t[6];
|
||||
memcpy(newBssid, WiFi.BSSID(i), 6);
|
||||
ap.bssid = newBssid;
|
||||
|
||||
accessPoints.push_back(ap);
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Connection Failures
|
||||
|
||||
| Error Type | Handling | Recovery |
|
||||
|------------|----------|----------|
|
||||
| **Invalid Credentials** | Log error, switch to AP mode | Manual reconfiguration via API |
|
||||
| **Network Unavailable** | Log warning, switch to AP mode | Automatic retry on next boot |
|
||||
| **Timeout** | Log timeout, switch to AP mode | Increase timeout or check network |
|
||||
| **Hardware Failure** | Log error, continue in AP mode | Hardware replacement required |
|
||||
|
||||
### API Error Responses
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Missing required parameters",
|
||||
"status": "error"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Failed to save configuration",
|
||||
"status": "error",
|
||||
"config_saved": false
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Current Implementation
|
||||
|
||||
- **Plain Text Storage**: WiFi passwords stored unencrypted
|
||||
- **Local Network Only**: No internet exposure
|
||||
- **No Authentication**: API access without authentication
|
||||
- **MAC-based Hostnames**: Predictable hostname generation
|
||||
|
||||
### Security Best Practices
|
||||
|
||||
1. **Network Isolation**: Keep SPORE devices on isolated network
|
||||
2. **Strong Passwords**: Use complex WiFi passwords
|
||||
3. **Regular Updates**: Keep firmware updated
|
||||
4. **Access Control**: Implement network-level access control
|
||||
|
||||
### Future Security Enhancements
|
||||
|
||||
- **Password Encryption**: Encrypt stored WiFi credentials
|
||||
- **API Authentication**: Add authentication to configuration API
|
||||
- **Certificate-based Security**: Implement TLS/SSL
|
||||
- **Access Control Lists**: Role-based configuration access
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **WiFi Connection Fails**
|
||||
- Check SSID and password
|
||||
- Verify network availability
|
||||
- Check signal strength
|
||||
- Increase connection timeout
|
||||
|
||||
2. **Configuration Not Persisting**
|
||||
- Check LittleFS initialization
|
||||
- Verify file write permissions
|
||||
- Monitor available flash space
|
||||
|
||||
3. **AP Mode Not Working**
|
||||
- Check AP credentials
|
||||
- Verify IP address assignment
|
||||
- Check for IP conflicts
|
||||
|
||||
4. **Scan Not Finding Networks**
|
||||
- Wait for scan completion
|
||||
- Check WiFi hardware
|
||||
- Verify scan permissions
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check WiFi status
|
||||
curl -s http://192.168.1.100/api/network/status | jq '.wifi'
|
||||
|
||||
# Scan for networks
|
||||
curl -X POST http://192.168.1.100/api/network/wifi/scan
|
||||
|
||||
# View scan results
|
||||
curl -s http://192.168.1.100/api/network/wifi/scan | jq '.'
|
||||
|
||||
# Test configuration
|
||||
curl -X POST http://192.168.1.100/api/network/wifi/config \
|
||||
-d "ssid=TestNetwork&password=testpass"
|
||||
```
|
||||
|
||||
### Log Analysis
|
||||
|
||||
**Successful Connection**:
|
||||
```
|
||||
[INFO] WiFi: Connected to AP, IP: 192.168.1.100
|
||||
[INFO] WiFi: Hostname set to: esp-AABBCCDDEEFF
|
||||
[INFO] WiFi: UDP listening on port 4210
|
||||
```
|
||||
|
||||
**Connection Failure**:
|
||||
```
|
||||
[WARN] WiFi: Failed to connect to AP. Creating AP...
|
||||
[INFO] WiFi: AP created, IP: 192.168.4.1
|
||||
```
|
||||
|
||||
**Configuration Update**:
|
||||
```
|
||||
[INFO] NetworkService: Restarting node to apply WiFi configuration...
|
||||
[INFO] WiFi: Connecting to AP...
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Connection Time
|
||||
|
||||
- **STA Connection**: 5-15 seconds typical
|
||||
- **AP Creation**: 1-3 seconds
|
||||
- **Scan Duration**: 5-10 seconds
|
||||
- **Configuration Save**: 100-500ms
|
||||
|
||||
### Memory Usage
|
||||
|
||||
- **WiFi Stack**: ~20-30KB RAM
|
||||
- **Scan Results**: ~1KB per network
|
||||
- **Configuration**: ~200 bytes
|
||||
- **API Buffers**: ~2-4KB
|
||||
|
||||
### Network Overhead
|
||||
|
||||
- **Scan Packets**: Minimal impact
|
||||
- **Configuration API**: ~500 bytes per request
|
||||
- **Status Updates**: ~200 bytes per response
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Configuration Management
|
||||
|
||||
1. **Use Strong Passwords**: Implement password complexity requirements
|
||||
2. **Set Appropriate Timeouts**: Balance connection speed vs reliability
|
||||
3. **Monitor Connection Quality**: Track RSSI and connection stability
|
||||
4. **Implement Retry Logic**: Handle temporary network issues
|
||||
5. **Log Configuration Changes**: Audit trail for troubleshooting
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
1. **Handle All Error Cases**: Implement comprehensive error handling
|
||||
2. **Provide Clear Feedback**: Inform users of connection status
|
||||
3. **Optimize Scan Frequency**: Balance discovery vs performance
|
||||
4. **Test Fallback Scenarios**: Ensure AP mode works correctly
|
||||
5. **Document Configuration Options**: Clear parameter documentation
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **[Configuration Management](./ConfigurationManagement.md)** - Persistent configuration system
|
||||
- **[API Reference](./API.md)** - Complete HTTP API documentation
|
||||
- **[Architecture Overview](./Architecture.md)** - System architecture and components
|
||||
- **[OpenAPI Specification](../api/)** - Machine-readable API specification
|
||||
@@ -27,6 +27,8 @@ public:
|
||||
void setWiFiConfig(const String& ssid, const String& password,
|
||||
uint32_t connect_timeout_ms = 10000,
|
||||
uint32_t retry_delay_ms = 500);
|
||||
bool saveConfig();
|
||||
void restartNode();
|
||||
|
||||
// Network status methods
|
||||
bool isConnected() const { return WiFi.isConnected(); }
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
class Config {
|
||||
public:
|
||||
// Default Configuration Constants
|
||||
static constexpr const char* DEFAULT_WIFI_SSID = "shroud";
|
||||
static constexpr const char* DEFAULT_WIFI_PASSWORD = "th3r31sn0sp00n";
|
||||
static constexpr uint16_t DEFAULT_UDP_PORT = 4210;
|
||||
static constexpr uint16_t DEFAULT_API_SERVER_PORT = 80;
|
||||
static constexpr unsigned long DEFAULT_DISCOVERY_INTERVAL_MS = 1000;
|
||||
static constexpr unsigned long DEFAULT_CLUSTER_LISTEN_INTERVAL_MS = 10;
|
||||
static constexpr unsigned long DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
|
||||
static constexpr unsigned long DEFAULT_STATUS_UPDATE_INTERVAL_MS = 1000;
|
||||
static constexpr unsigned long DEFAULT_MEMBER_INFO_UPDATE_INTERVAL_MS = 10000;
|
||||
static constexpr unsigned long DEFAULT_PRINT_INTERVAL_MS = 5000;
|
||||
static constexpr unsigned long DEFAULT_NODE_ACTIVE_THRESHOLD_MS = 10000;
|
||||
static constexpr unsigned long DEFAULT_NODE_INACTIVE_THRESHOLD_MS = 60000;
|
||||
static constexpr unsigned long DEFAULT_NODE_DEAD_THRESHOLD_MS = 120000;
|
||||
static constexpr unsigned long DEFAULT_WIFI_CONNECT_TIMEOUT_MS = 15000;
|
||||
static constexpr unsigned long DEFAULT_WIFI_RETRY_DELAY_MS = 500;
|
||||
static constexpr unsigned long DEFAULT_RESTART_DELAY_MS = 10;
|
||||
static constexpr uint16_t DEFAULT_JSON_DOC_SIZE = 1024;
|
||||
static constexpr uint32_t DEFAULT_LOW_MEMORY_THRESHOLD_BYTES = 10000;
|
||||
static constexpr uint32_t DEFAULT_CRITICAL_MEMORY_THRESHOLD_BYTES = 5000;
|
||||
static constexpr size_t DEFAULT_MAX_CONCURRENT_HTTP_REQUESTS = 3;
|
||||
|
||||
// WiFi Configuration
|
||||
String wifi_ssid;
|
||||
String wifi_password;
|
||||
@@ -40,4 +64,12 @@ public:
|
||||
|
||||
// Constructor
|
||||
Config();
|
||||
|
||||
// Persistence methods
|
||||
bool saveToFile(const String& filename = "/config.json");
|
||||
bool loadFromFile(const String& filename = "/config.json");
|
||||
void setDefaults();
|
||||
|
||||
private:
|
||||
static const char* CONFIG_FILE_PATH;
|
||||
};
|
||||
@@ -65,6 +65,16 @@ void NetworkManager::setWiFiConfig(const String& ssid, const String& password,
|
||||
ctx.config.wifi_retry_delay_ms = retry_delay_ms;
|
||||
}
|
||||
|
||||
bool NetworkManager::saveConfig() {
|
||||
return ctx.config.saveToFile();
|
||||
}
|
||||
|
||||
void NetworkManager::restartNode() {
|
||||
LOG_INFO("NetworkManager", "Restarting node after WiFi configuration change...");
|
||||
delay(100); // Give time for response to be sent
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void NetworkManager::setHostnameFromMac() {
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "spore/services/NetworkService.h"
|
||||
#include "spore/util/Logging.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
NetworkService::NetworkService(NetworkManager& networkManager)
|
||||
@@ -116,21 +117,34 @@ void NetworkService::handleSetWifiConfig(AsyncWebServerRequest* request) {
|
||||
retry_delay_ms = request->getParam("retry_delay_ms", true)->value().toInt();
|
||||
}
|
||||
|
||||
// Update configuration
|
||||
// Update configuration in memory
|
||||
networkManager.setWiFiConfig(ssid, password, connect_timeout_ms, retry_delay_ms);
|
||||
|
||||
// Attempt to connect with new settings
|
||||
networkManager.setupWiFi();
|
||||
// Save configuration to persistent storage
|
||||
bool configSaved = networkManager.saveConfig();
|
||||
if (!configSaved) {
|
||||
LOG_WARN("NetworkService", "Failed to save WiFi configuration to persistent storage");
|
||||
}
|
||||
|
||||
// Prepare response
|
||||
JsonDocument doc;
|
||||
doc["status"] = "success";
|
||||
doc["message"] = "WiFi configuration updated";
|
||||
doc["connected"] = WiFi.isConnected();
|
||||
if (WiFi.isConnected()) {
|
||||
doc["ip"] = WiFi.localIP().toString();
|
||||
}
|
||||
doc["message"] = "WiFi configuration updated and saved";
|
||||
doc["config_saved"] = configSaved;
|
||||
doc["restarting"] = true;
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
|
||||
// Send response before restarting
|
||||
AsyncWebServerResponse* response = request->beginResponse(200, "application/json", json);
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
|
||||
// Restart the node to apply new WiFi settings
|
||||
request->onDisconnect([this]() {
|
||||
LOG_INFO("NetworkService", "Restarting node to apply WiFi configuration...");
|
||||
delay(100); // Give time for response to be sent
|
||||
networkManager.restartNode();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,37 +1,185 @@
|
||||
#include "spore/types/Config.h"
|
||||
#include "spore/util/Logging.h"
|
||||
|
||||
const char* Config::CONFIG_FILE_PATH = "/config.json";
|
||||
|
||||
Config::Config() {
|
||||
// Initialize LittleFS
|
||||
if (!LittleFS.begin()) {
|
||||
LOG_WARN("Config", "Failed to initialize LittleFS, using defaults");
|
||||
setDefaults();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load configuration from file
|
||||
if (!loadFromFile()) {
|
||||
LOG_INFO("Config", "No config file found, using defaults");
|
||||
setDefaults();
|
||||
// Save defaults to file for future use
|
||||
saveToFile();
|
||||
} else {
|
||||
LOG_INFO("Config", "Configuration loaded from file");
|
||||
}
|
||||
}
|
||||
|
||||
void Config::setDefaults() {
|
||||
// WiFi Configuration
|
||||
wifi_ssid = "shroud";
|
||||
wifi_password = "th3r31sn0sp00n";
|
||||
wifi_ssid = DEFAULT_WIFI_SSID;
|
||||
wifi_password = DEFAULT_WIFI_PASSWORD;
|
||||
|
||||
// Network Configuration
|
||||
udp_port = 4210;
|
||||
api_server_port = 80;
|
||||
udp_port = DEFAULT_UDP_PORT;
|
||||
api_server_port = DEFAULT_API_SERVER_PORT;
|
||||
|
||||
// Cluster Configuration
|
||||
discovery_interval_ms = 1000; // TODO retire this in favor of heartbeat_interval_ms
|
||||
cluster_listen_interval_ms = 10;
|
||||
heartbeat_interval_ms = 5000;
|
||||
status_update_interval_ms = 1000;
|
||||
member_info_update_interval_ms = 10000; // TODO retire this in favor of heartbeat_interval_ms
|
||||
print_interval_ms = 5000;
|
||||
discovery_interval_ms = DEFAULT_DISCOVERY_INTERVAL_MS; // TODO retire this in favor of heartbeat_interval_ms
|
||||
cluster_listen_interval_ms = DEFAULT_CLUSTER_LISTEN_INTERVAL_MS;
|
||||
heartbeat_interval_ms = DEFAULT_HEARTBEAT_INTERVAL_MS;
|
||||
status_update_interval_ms = DEFAULT_STATUS_UPDATE_INTERVAL_MS;
|
||||
member_info_update_interval_ms = DEFAULT_MEMBER_INFO_UPDATE_INTERVAL_MS; // TODO retire this in favor of heartbeat_interval_ms
|
||||
print_interval_ms = DEFAULT_PRINT_INTERVAL_MS;
|
||||
|
||||
// Node Status Thresholds
|
||||
node_active_threshold_ms = 10000;
|
||||
node_inactive_threshold_ms = 60000;
|
||||
node_dead_threshold_ms = 120000;
|
||||
node_active_threshold_ms = DEFAULT_NODE_ACTIVE_THRESHOLD_MS;
|
||||
node_inactive_threshold_ms = DEFAULT_NODE_INACTIVE_THRESHOLD_MS;
|
||||
node_dead_threshold_ms = DEFAULT_NODE_DEAD_THRESHOLD_MS;
|
||||
|
||||
// WiFi Connection
|
||||
wifi_connect_timeout_ms = 15000;
|
||||
wifi_retry_delay_ms = 500;
|
||||
wifi_connect_timeout_ms = DEFAULT_WIFI_CONNECT_TIMEOUT_MS;
|
||||
wifi_retry_delay_ms = DEFAULT_WIFI_RETRY_DELAY_MS;
|
||||
|
||||
// System Configuration
|
||||
restart_delay_ms = 10;
|
||||
json_doc_size = 1024;
|
||||
restart_delay_ms = DEFAULT_RESTART_DELAY_MS;
|
||||
json_doc_size = DEFAULT_JSON_DOC_SIZE;
|
||||
|
||||
// Memory Management
|
||||
low_memory_threshold_bytes = 10000; // 10KB
|
||||
critical_memory_threshold_bytes = 5000; // 5KB
|
||||
max_concurrent_http_requests = 3;
|
||||
low_memory_threshold_bytes = DEFAULT_LOW_MEMORY_THRESHOLD_BYTES; // 10KB
|
||||
critical_memory_threshold_bytes = DEFAULT_CRITICAL_MEMORY_THRESHOLD_BYTES; // 5KB
|
||||
max_concurrent_http_requests = DEFAULT_MAX_CONCURRENT_HTTP_REQUESTS;
|
||||
}
|
||||
|
||||
bool Config::saveToFile(const String& filename) {
|
||||
if (!LittleFS.begin()) {
|
||||
LOG_ERROR("Config", "LittleFS not initialized, cannot save config");
|
||||
return false;
|
||||
}
|
||||
|
||||
File file = LittleFS.open(filename, "w");
|
||||
if (!file) {
|
||||
LOG_ERROR("Config", "Failed to open config file for writing: " + filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
|
||||
// WiFi Configuration
|
||||
doc["wifi"]["ssid"] = wifi_ssid;
|
||||
doc["wifi"]["password"] = wifi_password;
|
||||
doc["wifi"]["connect_timeout_ms"] = wifi_connect_timeout_ms;
|
||||
doc["wifi"]["retry_delay_ms"] = wifi_retry_delay_ms;
|
||||
|
||||
// Network Configuration
|
||||
doc["network"]["udp_port"] = udp_port;
|
||||
doc["network"]["api_server_port"] = api_server_port;
|
||||
|
||||
// Cluster Configuration
|
||||
doc["cluster"]["discovery_interval_ms"] = discovery_interval_ms;
|
||||
doc["cluster"]["heartbeat_interval_ms"] = heartbeat_interval_ms;
|
||||
doc["cluster"]["cluster_listen_interval_ms"] = cluster_listen_interval_ms;
|
||||
doc["cluster"]["status_update_interval_ms"] = status_update_interval_ms;
|
||||
doc["cluster"]["member_info_update_interval_ms"] = member_info_update_interval_ms;
|
||||
doc["cluster"]["print_interval_ms"] = print_interval_ms;
|
||||
|
||||
// Node Status Thresholds
|
||||
doc["thresholds"]["node_active_threshold_ms"] = node_active_threshold_ms;
|
||||
doc["thresholds"]["node_inactive_threshold_ms"] = node_inactive_threshold_ms;
|
||||
doc["thresholds"]["node_dead_threshold_ms"] = node_dead_threshold_ms;
|
||||
|
||||
// System Configuration
|
||||
doc["system"]["restart_delay_ms"] = restart_delay_ms;
|
||||
doc["system"]["json_doc_size"] = json_doc_size;
|
||||
|
||||
// Memory Management
|
||||
doc["memory"]["low_memory_threshold_bytes"] = low_memory_threshold_bytes;
|
||||
doc["memory"]["critical_memory_threshold_bytes"] = critical_memory_threshold_bytes;
|
||||
doc["memory"]["max_concurrent_http_requests"] = max_concurrent_http_requests;
|
||||
|
||||
// Add metadata
|
||||
doc["_meta"]["version"] = "1.0";
|
||||
doc["_meta"]["saved_at"] = millis();
|
||||
|
||||
size_t bytesWritten = serializeJson(doc, file);
|
||||
file.close();
|
||||
|
||||
if (bytesWritten > 0) {
|
||||
LOG_INFO("Config", "Configuration saved to " + filename + " (" + String(bytesWritten) + " bytes)");
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR("Config", "Failed to write configuration to file");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Config::loadFromFile(const String& filename) {
|
||||
if (!LittleFS.begin()) {
|
||||
LOG_ERROR("Config", "LittleFS not initialized, cannot load config");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LittleFS.exists(filename)) {
|
||||
LOG_DEBUG("Config", "Config file does not exist: " + filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
File file = LittleFS.open(filename, "r");
|
||||
if (!file) {
|
||||
LOG_ERROR("Config", "Failed to open config file for reading: " + filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
file.close();
|
||||
|
||||
if (error) {
|
||||
LOG_ERROR("Config", "Failed to parse config file: " + String(error.c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load WiFi Configuration with defaults
|
||||
wifi_ssid = doc["wifi"]["ssid"] | DEFAULT_WIFI_SSID;
|
||||
wifi_password = doc["wifi"]["password"] | DEFAULT_WIFI_PASSWORD;
|
||||
wifi_connect_timeout_ms = doc["wifi"]["connect_timeout_ms"] | DEFAULT_WIFI_CONNECT_TIMEOUT_MS;
|
||||
wifi_retry_delay_ms = doc["wifi"]["retry_delay_ms"] | DEFAULT_WIFI_RETRY_DELAY_MS;
|
||||
|
||||
// Load Network Configuration with defaults
|
||||
udp_port = doc["network"]["udp_port"] | DEFAULT_UDP_PORT;
|
||||
api_server_port = doc["network"]["api_server_port"] | DEFAULT_API_SERVER_PORT;
|
||||
|
||||
// Load Cluster Configuration with defaults
|
||||
discovery_interval_ms = doc["cluster"]["discovery_interval_ms"] | DEFAULT_DISCOVERY_INTERVAL_MS;
|
||||
heartbeat_interval_ms = doc["cluster"]["heartbeat_interval_ms"] | DEFAULT_HEARTBEAT_INTERVAL_MS;
|
||||
cluster_listen_interval_ms = doc["cluster"]["cluster_listen_interval_ms"] | DEFAULT_CLUSTER_LISTEN_INTERVAL_MS;
|
||||
status_update_interval_ms = doc["cluster"]["status_update_interval_ms"] | DEFAULT_STATUS_UPDATE_INTERVAL_MS;
|
||||
member_info_update_interval_ms = doc["cluster"]["member_info_update_interval_ms"] | DEFAULT_MEMBER_INFO_UPDATE_INTERVAL_MS;
|
||||
print_interval_ms = doc["cluster"]["print_interval_ms"] | DEFAULT_PRINT_INTERVAL_MS;
|
||||
|
||||
// Load Node Status Thresholds with defaults
|
||||
node_active_threshold_ms = doc["thresholds"]["node_active_threshold_ms"] | DEFAULT_NODE_ACTIVE_THRESHOLD_MS;
|
||||
node_inactive_threshold_ms = doc["thresholds"]["node_inactive_threshold_ms"] | DEFAULT_NODE_INACTIVE_THRESHOLD_MS;
|
||||
node_dead_threshold_ms = doc["thresholds"]["node_dead_threshold_ms"] | DEFAULT_NODE_DEAD_THRESHOLD_MS;
|
||||
|
||||
// Load System Configuration with defaults
|
||||
restart_delay_ms = doc["system"]["restart_delay_ms"] | DEFAULT_RESTART_DELAY_MS;
|
||||
json_doc_size = doc["system"]["json_doc_size"] | DEFAULT_JSON_DOC_SIZE;
|
||||
|
||||
// Load Memory Management with defaults
|
||||
low_memory_threshold_bytes = doc["memory"]["low_memory_threshold_bytes"] | DEFAULT_LOW_MEMORY_THRESHOLD_BYTES;
|
||||
critical_memory_threshold_bytes = doc["memory"]["critical_memory_threshold_bytes"] | DEFAULT_CRITICAL_MEMORY_THRESHOLD_BYTES;
|
||||
max_concurrent_http_requests = doc["memory"]["max_concurrent_http_requests"] | DEFAULT_MAX_CONCURRENT_HTTP_REQUESTS;
|
||||
|
||||
LOG_DEBUG("Config", "Loaded WiFi SSID: " + wifi_ssid);
|
||||
LOG_DEBUG("Config", "Config file version: " + String(doc["_meta"]["version"] | "unknown"));
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user