From 79f3ede17f82070017227dcd4714bb6720d271eb Mon Sep 17 00:00:00 2001 From: AirLab Date: Tue, 7 Oct 2025 14:59:38 +0200 Subject: [PATCH] Add password protection for critical transmission settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add unlock/lock button for protected settings in admin panel - Protect AirCarto, uSpot, and miotiq transmission checkboxes - Require password '123plouf' to enable editing protected checkboxes - Visual feedback with lock/unlock icons and toast notifications - Add CLAUDE.md documentation file for development guidance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 270 ++++++++++++++++++++++++++++++++++++++++++++++++ html/admin.html | 105 ++++++++++++++++--- 2 files changed, 360 insertions(+), 15 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0c82c71 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,270 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +NebuleAir Pro 4G is an environmental monitoring system running on Raspberry Pi 4/CM4. It collects air quality and environmental data from multiple sensors and transmits it via 4G cellular modem. The system includes a self-hosted web interface for configuration and monitoring. + +## Architecture + +### Data Flow +1. **Data Collection**: Sensors are polled by individual Python scripts triggered by systemd timers +2. **Local Storage**: All sensor data is stored in SQLite database (`sqlite/sensors.db`) +3. **Data Transmission**: Main loop script reads aggregated data from SQLite and transmits via SARA R4/R5 4G modem +4. **Web Interface**: Apache serves PHP pages that interact with SQLite and execute Python scripts + +### Key Components + +**Sensors & Hardware:** +- NextPM (NPM): Particulate matter sensor via Modbus (ttyAMA0) +- Envea CAIRSENS: Gas sensors (NO2, H2S, NH3, CO, O3) via serial (ttyAMA2-5) +- BME280: Temperature, humidity, pressure via I2C (0x76) +- NSRT MK4: Noise sensor via I2C (0x48) +- SARA R4/R5: 4G cellular modem (ttyAMA2) +- Wind meter: via ADS1115 ADC +- MPPT: Solar charger monitoring + +**Software Stack:** +- OS: Raspberry Pi OS (Linux) +- Web server: Apache2 +- Database: SQLite3 +- Languages: Python 3, PHP, Bash +- Network: NetworkManager (nmcli) + +### Directory Structure +- `NPM/`: NextPM sensor scripts +- `envea/`: Envea sensor scripts +- `BME280/`: BME280 sensor scripts +- `sound_meter/`: Noise sensor code (C program) +- `SARA/`: 4G modem communication (AT commands) +- `windMeter/`: Wind sensor scripts +- `MPPT/`: Solar charger scripts +- `RTC/`: DS3231 real-time clock module scripts +- `sqlite/`: Database scripts (create, read, write, config) +- `loop/`: Main data transmission script +- `html/`: Web interface files +- `services/`: Systemd service/timer configuration +- `logs/`: Application logs + +## Common Development Commands + +### Database Operations +```bash +# Initialize database +/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py + +# Set configuration +/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py + +# Read data from specific table +/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py +``` + +### Systemd Services +The system uses systemd timers for scheduling sensor data collection: + +```bash +# View all NebuleAir timers +systemctl list-timers | grep nebuleair + +# Check specific service status +systemctl status nebuleair-npm-data.service +systemctl status nebuleair-sara-data.service + +# View service logs +journalctl -u nebuleair-npm-data.service +journalctl -u nebuleair-sara-data.service -f # follow + +# Restart services +systemctl restart nebuleair-npm-data.timer +systemctl restart nebuleair-sara-data.timer + +# Setup all services +sudo /var/www/nebuleair_pro_4g/services/setup_services.sh +``` + +**Service Schedule:** +- `nebuleair-npm-data.timer`: Every 10 seconds (NextPM sensor) +- `nebuleair-envea-data.timer`: Every 10 seconds (Envea sensors) +- `nebuleair-sara-data.timer`: Every 60 seconds (4G data transmission) +- `nebuleair-bme280-data.timer`: Every 120 seconds (BME280 sensor) +- `nebuleair-mppt-data.timer`: Every 120 seconds (MPPT charger) +- `nebuleair-noise-data.timer`: Every 60 seconds (Noise sensor) +- `nebuleair-db-cleanup-data.timer`: Daily (database cleanup) + +### Sensor Testing +```bash +# Test NextPM sensor +/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_v3.py + +# Test Envea sensors (read reference/ID) +/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_ref_v2.py + +# Test Envea sensor values +/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py + +# Test BME280 +/usr/bin/python3 /var/www/nebuleair_pro_4g/BME280/get_data_v2.py + +# Test noise sensor +/usr/bin/python3 /var/www/nebuleair_pro_4g/sound_meter/NSRT_mk4_get_data.py + +# Test RTC module +/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/read.py + +# Test MPPT charger +/usr/bin/python3 /var/www/nebuleair_pro_4g/MPPT/read.py +``` + +### 4G Modem (SARA R4/R5) +```bash +# Send AT command +/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 "AT+CSQ" 5 + +# Check network connection +/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 120 + +# Set server URL (HTTP profile 0 for AirCarto) +/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setURL.py ttyAMA2 data.nebuleair.fr 0 + +# Check modem status +/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/check_running.py +``` + +### Network Configuration +```bash +# Scan WiFi networks (saved to wifi_list.csv) +nmcli device wifi list ifname wlan0 + +# Connect to WiFi +sudo nmcli device wifi connect "SSID" password "PASSWORD" + +# Create hotspot (done automatically at boot if no connection) +sudo nmcli device wifi hotspot ifname wlan0 ssid nebuleair_pro password nebuleaircfg + +# Check connection status +nmcli device show wlan0 +nmcli device show eth0 +``` + +### Permissions & Serial Ports +```bash +# Grant serial port access (run at boot via cron) +chmod 777 /dev/ttyAMA* /dev/i2c-1 + +# Check I2C devices +sudo i2cdetect -y 1 +``` + +### Logs +```bash +# View main application log +tail -f /var/www/nebuleair_pro_4g/logs/app.log + +# View SARA transmission log +tail -f /var/www/nebuleair_pro_4g/logs/sara_service.log + +# View service-specific logs +tail -f /var/www/nebuleair_pro_4g/logs/npm_service.log +tail -f /var/www/nebuleair_pro_4g/logs/envea_service.log + +# Clear logs (done daily via cron) +find /var/www/nebuleair_pro_4g/logs -name "*.log" -type f -exec truncate -s 0 {} \; +``` + +## Configuration System + +Configuration is stored in SQLite database (`sqlite/sensors.db`) in the `config_table`: + +**Key Configuration Parameters:** +- `deviceID`: Unique device identifier (string) +- `modem_config_mode`: When true, disables data transmission loop (bool) +- `modem_version`: SARA R4 or R5 (string) +- `SaraR4_baudrate`: Modem baudrate, usually 115200 or 9600 (int) +- `SARA_R4_neworkID`: Cellular network ID for connection (int) +- `send_miotiq`: Enable UDP transmission to Miotiq server (bool) +- `send_aircarto`: Enable HTTP transmission to AirCarto server (bool) +- `send_uSpot`: Enable HTTPS transmission to uSpot server (bool) +- `npm_5channel`: Enable 5-channel particle size distribution (bool) +- `envea`: Enable Envea gas sensors (bool) +- `windMeter`: Enable wind meter (bool) +- `BME280`: Enable BME280 sensor (bool) +- `MPPT`: Enable MPPT charger monitoring (bool) +- `NOISE`: Enable noise sensor (bool) +- `latitude_raw`, `longitude_raw`: GPS coordinates (float) + +Configuration can be updated via web interface (launcher.php) or Python scripts in `sqlite/`. + +## Data Transmission Protocols + +### 1. UDP to Miotiq (Binary Protocol) +- 100-byte fixed binary payload via UDP socket +- Server: 192.168.0.20:4242 +- Format: Device ID (8 bytes) + signal quality + sensor data (all packed as binary) + +### 2. HTTP POST to AirCarto +- CSV payload sent as file attachment +- URL: `data.nebuleair.fr/pro_4G/data.php?sensor_id={id}&datetime={timestamp}` +- Uses SARA HTTP client (AT+UHTTPC profile 0) + +### 3. HTTPS POST to uSpot +- JSON payload with SSL/TLS +- URL: `api-prod.uspot.probesys.net/nebuleair?token=2AFF6dQk68daFZ` (port 443) +- Uses SARA HTTPS client with certificate validation (AT+UHTTPC profile 1) + +## Important Implementation Notes + +### SARA R4/R5 Modem Communication +- The main transmission script (`loop/SARA_send_data_v2.py`) handles complex error recovery +- Error codes from AT+UHTTPER command trigger specific recovery actions: + - Error 4: Invalid hostname → Reset HTTP profile URL + - Error 11: Server connection error → Hardware modem reboot + - Error 22: PDP connection issue → Reset PSD/CSD connection (R5 only) + - Error 26/44: Timeout/connection lost → Send WiFi notification + - Error 73: SSL error → Re-import certificate and reset HTTPS profile +- Modem hardware reboot uses GPIO 16 (transistor control to cut power) +- Script waits 2 minutes after system boot before executing + +### Serial Communication +- All UART ports must be enabled in `/boot/firmware/config.txt` +- Permissions reset after reboot, must be re-granted via cron @reboot +- Read functions use custom timeout logic to handle slow modem responses +- Special handling for multi-line AT command responses using `wait_for_lines` parameter + +### Time Synchronization +- DS3231 RTC module maintains time when no network available +- RTC timestamp stored in SQLite (`timestamp_table`) +- Script compares server datetime (from HTTP headers) with RTC +- Auto-updates RTC if difference > 60 seconds +- Handles RTC reset condition (year 2000) and disconnection + +### LED Indicators +- GPIO 23 (blue LED): Successful data transmission +- GPIO 24 (red LED): Transmission errors +- Uses thread locking to prevent GPIO conflicts + +### Web Interface +- Apache DocumentRoot set to `/var/www/nebuleair_pro_4g` +- `html/launcher.php` provides REST-like API for all operations +- Uses shell_exec() to run Python scripts with proper sudo permissions +- Configuration updates modify SQLite database, not JSON files + +### Security Considerations +- www-data user has sudo access to specific commands (defined in /etc/sudoers) +- Terminal command execution in launcher.php has whitelist of allowed commands +- No sensitive credentials should be committed (all in database/config files) + +## Boot Sequence + +1. Grant serial/I2C permissions (cron @reboot) +2. Check WiFi connection, start hotspot if needed (`boot_hotspot.sh`) +3. Start SARA modem initialization (`SARA/reboot/start.py`) +4. Systemd timers begin sensor data collection +5. SARA transmission loop begins after 2-minute delay + +## Cron Jobs + +Located in `/var/www/nebuleair_pro_4g/cron_jobs`: +- @reboot: Permissions setup, hotspot check, SARA initialization +- Daily 00:00: Truncate log files diff --git a/html/admin.html b/html/admin.html index 20a2443..3489160 100755 --- a/html/admin.html +++ b/html/admin.html @@ -118,23 +118,33 @@ +
+ Protected Settings + +
+
- +
- +
- +
- + @@ -1396,23 +1406,23 @@ function startEnveaDetection() { function displayDetectionResults(results) { console.log("Displaying detection results:", results); - + // Hide progress spinner document.getElementById('detectionProgress').style.display = 'none'; - + let htmlContent = '
Detection Results:
'; - + // Create cards for each port result results.forEach(function(result, index) { - const statusBadge = result.detected ? - 'Device Detected' : - result.success ? - 'No Device' : + const statusBadge = result.detected ? + 'Device Detected' : + result.success ? + 'No Device' : 'Error'; - + const deviceInfo = result.device_info || (result.detected ? 'Envea Device' : 'None'); const rawData = result.data || 'No data'; - + htmlContent += `
@@ -1442,18 +1452,83 @@ function displayDetectionResults(results) {
`; }); - + // Add summary const detectedCount = results.filter(r => r.detected).length; htmlContent += `
Summary: ${detectedCount} device(s) detected out of ${results.length} ports tested.
`; - + document.getElementById('detectionResults').innerHTML = htmlContent; document.getElementById('startDetectionBtn').style.display = 'inline-block'; document.getElementById('startDetectionBtn').textContent = 'Scan Again'; } +/* + ____ _ _ _ ____ _ _ _ + | _ \ _ __ ___ | |_ ___ ___| |_ ___ __| | / ___| ___| |_| |_(_)_ __ __ _ ___ + | |_) | '__/ _ \| __/ _ \/ __| __/ _ \/ _` | \___ \ / _ \ __| __| | '_ \ / _` / __| + | __/| | | (_) | || __/ (__| || __/ (_| | ___) | __/ |_| |_| | | | | (_| \__ \ + |_| |_| \___/ \__\___|\___|\__\___|\__,_| |____/ \___|\__|\__|_|_| |_|\__, |___/ + |___/ +*/ + +// Track if protected settings are unlocked +let protectedSettingsUnlocked = false; + +function toggleProtectedSettings() { + const unlockBtn = document.getElementById('unlockBtn'); + const protectedCheckboxes = document.querySelectorAll('.protected-checkbox'); + + if (protectedSettingsUnlocked) { + // Lock the settings + protectedSettingsUnlocked = false; + protectedCheckboxes.forEach(checkbox => { + checkbox.disabled = true; + }); + + // Update button appearance + unlockBtn.classList.remove('btn-success'); + unlockBtn.classList.add('btn-outline-primary'); + unlockBtn.innerHTML = ` + + + + Unlock + `; + + // Show toast notification + showToast('Protected settings locked', 'info'); + } else { + // Prompt for password + const password = prompt('Enter admin password to unlock protected settings:'); + + if (password === '123plouf') { + // Correct password - unlock the settings + protectedSettingsUnlocked = true; + protectedCheckboxes.forEach(checkbox => { + checkbox.disabled = false; + }); + + // Update button appearance + unlockBtn.classList.remove('btn-outline-primary'); + unlockBtn.classList.add('btn-success'); + unlockBtn.innerHTML = ` + + + + Lock + `; + + // Show success toast + showToast('Protected settings unlocked! You can now edit the checkboxes.', 'success'); + } else if (password !== null) { + // Wrong password (null means user cancelled) + showToast('Incorrect password!', 'error'); + } + } +} +