Compare commits
1 Commits
d0b49bf30c
...
ai_branch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e656d76a1 |
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(python3:*)"
|
|
||||||
],
|
|
||||||
"deny": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,6 +14,4 @@ NPM/data/*.txt
|
|||||||
NPM/data/*.json
|
NPM/data/*.json
|
||||||
*.lock
|
*.lock
|
||||||
sqlite/*.db
|
sqlite/*.db
|
||||||
sqlite/*.sql
|
|
||||||
|
|
||||||
tests/
|
tests/
|
||||||
24
CLAUDE.md
Normal file
24
CLAUDE.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# NebuleAir Pro 4G Development Guidelines
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `sudo systemctl restart master_nebuleair.service` - Restart main service
|
||||||
|
- `sudo systemctl status master_nebuleair.service` - Check service status
|
||||||
|
- Manual testing: Run individual Python scripts (e.g., `sudo python3 NPM/get_data_modbus_v3.py`)
|
||||||
|
- Installation: `sudo ./installation_part1.sh` followed by `sudo ./installation_part2.sh`
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
- **Language:** Python 3 with HTML/JS/CSS for web interface
|
||||||
|
- **Structure:** Organized by component (BME280, NPM, RTC, SARA, etc.)
|
||||||
|
- **Naming:** snake_case for variables/functions, version suffix for iterations (e.g., `_v2.py`)
|
||||||
|
- **Documentation:** Include docstrings with script purpose and usage instructions
|
||||||
|
- **Error Handling:** Use try/except blocks for I/O operations, print errors to logs
|
||||||
|
- **Configuration:** All settings in `config.json`, avoid hardcoding values
|
||||||
|
- **Web Components:** Follow Bootstrap patterns, use fetch() for AJAX
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- Check if features are enabled in config before execution
|
||||||
|
- Close database connections after use
|
||||||
|
- Round sensor readings to appropriate precision
|
||||||
|
- Keep web interface mobile-responsive
|
||||||
|
- Include error handling for network operations
|
||||||
|
- Follow existing patterns when adding new functionality
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
'''
|
|
||||||
____ ____ ___ ___
|
|
||||||
/ ___| _ \_ _/ _ \
|
|
||||||
| | _| |_) | | | | |
|
|
||||||
| |_| | __/| | |_| |
|
|
||||||
\____|_| |___\___/
|
|
||||||
|
|
||||||
script to control GPIO output
|
|
||||||
|
|
||||||
GPIO 16 -> SARA 5V
|
|
||||||
GPIO 20 -> SARA PWR ON
|
|
||||||
|
|
||||||
option 1:
|
|
||||||
CLI tool like pinctrl
|
|
||||||
pinctrl set 16 op
|
|
||||||
pinctrl set 16 dh
|
|
||||||
pinctrl set 16 dl
|
|
||||||
|
|
||||||
option 2:
|
|
||||||
python library RPI.GPIO
|
|
||||||
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/GPIO/control.py
|
|
||||||
'''
|
|
||||||
|
|
||||||
import RPi.GPIO as GPIO
|
|
||||||
import time
|
|
||||||
|
|
||||||
selected_GPIO = 16
|
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BCM) # Use BCM numbering
|
|
||||||
GPIO.setup(selected_GPIO, GPIO.OUT) # Set GPIO17 as an output
|
|
||||||
|
|
||||||
while True:
|
|
||||||
GPIO.output(selected_GPIO, GPIO.HIGH) # Turn ON
|
|
||||||
time.sleep(1) # Wait 1 sec
|
|
||||||
GPIO.output(selected_GPIO, GPIO.LOW) # Turn OFF
|
|
||||||
time.sleep(1) # Wait 1 sec
|
|
||||||
|
|
||||||
|
|
||||||
282
MPPT/read.py
282
MPPT/read.py
@@ -1,282 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
__ __ ____ ____ _____
|
|
||||||
| \/ | _ \| _ \_ _|
|
|
||||||
| |\/| | |_) | |_) || |
|
|
||||||
| | | | __/| __/ | |
|
|
||||||
|_| |_|_| |_| |_|
|
|
||||||
|
|
||||||
MPPT Chargeur solaire Victron interface UART
|
|
||||||
|
|
||||||
MPPT connections
|
|
||||||
5V / Rx / TX / GND
|
|
||||||
RPI connection
|
|
||||||
-- / GPIO9 / GPIO8 / GND
|
|
||||||
* pas besoin de connecter le 5V (le GND uniquement)
|
|
||||||
|
|
||||||
Fixed version - properly handles continuous data stream
|
|
||||||
"""
|
|
||||||
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
# ===== LOGGING CONFIGURATION =====
|
|
||||||
# Set to True to enable all print statements, False to run silently
|
|
||||||
DEBUG_MODE = False
|
|
||||||
|
|
||||||
# Alternative: Use environment variable (can be set in systemd service)
|
|
||||||
# DEBUG_MODE = os.environ.get('MPPT_DEBUG', 'false').lower() == 'true'
|
|
||||||
|
|
||||||
# Alternative: Check if running under systemd
|
|
||||||
# DEBUG_MODE = os.isatty(1) # True if running in terminal, False if systemd/cron
|
|
||||||
|
|
||||||
# Alternative: Use different log levels
|
|
||||||
# LOG_LEVEL = "ERROR" # Options: "DEBUG", "INFO", "ERROR", "NONE"
|
|
||||||
# =================================
|
|
||||||
|
|
||||||
# Connect to the SQLite database
|
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Logging function
|
|
||||||
def log(message, level="INFO"):
|
|
||||||
"""Print message only if DEBUG_MODE is True"""
|
|
||||||
if DEBUG_MODE:
|
|
||||||
print(message)
|
|
||||||
# Alternative: could write to a log file instead
|
|
||||||
# with open('/var/log/mppt.log', 'a') as f:
|
|
||||||
# f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} [{level}] {message}\n")
|
|
||||||
|
|
||||||
|
|
||||||
def read_vedirect(port='/dev/ttyAMA4', baudrate=19200, timeout=10):
|
|
||||||
"""
|
|
||||||
Read and parse data from Victron MPPT controller
|
|
||||||
Returns parsed data as a dictionary
|
|
||||||
"""
|
|
||||||
required_keys = ['V', 'I', 'VPV', 'PPV', 'CS'] # Essential keys we need
|
|
||||||
|
|
||||||
try:
|
|
||||||
log(f"Opening serial port {port} at {baudrate} baud...")
|
|
||||||
ser = serial.Serial(port, baudrate, timeout=1)
|
|
||||||
|
|
||||||
# Clear any buffered data
|
|
||||||
ser.reset_input_buffer()
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# Initialize data dictionary
|
|
||||||
data = {}
|
|
||||||
start_time = time.time()
|
|
||||||
lines_read = 0
|
|
||||||
blocks_seen = 0
|
|
||||||
|
|
||||||
while time.time() - start_time < timeout:
|
|
||||||
try:
|
|
||||||
line = ser.readline().decode('utf-8', errors='ignore').strip()
|
|
||||||
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
lines_read += 1
|
|
||||||
|
|
||||||
# Check if this line contains tab-separated key-value pair
|
|
||||||
if '\t' in line:
|
|
||||||
parts = line.split('\t', 1)
|
|
||||||
if len(parts) == 2:
|
|
||||||
key, value = parts
|
|
||||||
data[key] = value
|
|
||||||
log(f"{key}: {value}")
|
|
||||||
|
|
||||||
# Check for checksum line (end of block)
|
|
||||||
elif line.startswith('Checksum'):
|
|
||||||
blocks_seen += 1
|
|
||||||
log(f"--- End of block {blocks_seen} ---")
|
|
||||||
|
|
||||||
# Check if we have all required keys
|
|
||||||
missing_keys = [key for key in required_keys if key not in data]
|
|
||||||
|
|
||||||
if not missing_keys:
|
|
||||||
log(f"✓ Complete data block received after {lines_read} lines!")
|
|
||||||
ser.close()
|
|
||||||
return data
|
|
||||||
else:
|
|
||||||
log(f"Block {blocks_seen} incomplete, missing: {', '.join(missing_keys)}")
|
|
||||||
# Don't clear data, maybe we missed the beginning of first block
|
|
||||||
if blocks_seen > 1:
|
|
||||||
# If we've seen multiple blocks and still missing data,
|
|
||||||
# something is wrong
|
|
||||||
log("Multiple incomplete blocks, clearing data...")
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
except UnicodeDecodeError as e:
|
|
||||||
log(f"Decode error: {e}", "ERROR")
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
|
||||||
log(f"Error reading line: {e}", "ERROR")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Timeout reached
|
|
||||||
log(f"Timeout after {timeout}s, read {lines_read} lines, saw {blocks_seen} blocks")
|
|
||||||
ser.close()
|
|
||||||
|
|
||||||
# If we have some data but not all required keys, return what we have
|
|
||||||
if data and len(data) >= len(required_keys) - 1:
|
|
||||||
log("Returning partial data...")
|
|
||||||
return data
|
|
||||||
|
|
||||||
except serial.SerialException as e:
|
|
||||||
log(f"Serial port error: {e}", "ERROR")
|
|
||||||
except Exception as e:
|
|
||||||
log(f"Unexpected error: {e}", "ERROR")
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def parse_values(data):
|
|
||||||
"""Convert string values to appropriate types"""
|
|
||||||
if not data:
|
|
||||||
return None
|
|
||||||
|
|
||||||
parsed = {}
|
|
||||||
|
|
||||||
# Define conversions for each key
|
|
||||||
conversions = {
|
|
||||||
'PID': str,
|
|
||||||
'FW': int,
|
|
||||||
'SER#': str,
|
|
||||||
'V': lambda x: float(x)/1000, # Convert mV to V
|
|
||||||
'I': lambda x: float(x)/1000, # Convert mA to A
|
|
||||||
'VPV': lambda x: float(x)/1000 if x != '---' else 0, # Convert mV to V
|
|
||||||
'PPV': int,
|
|
||||||
'CS': int,
|
|
||||||
'MPPT': int,
|
|
||||||
'OR': str,
|
|
||||||
'ERR': int,
|
|
||||||
'LOAD': str,
|
|
||||||
'IL': lambda x: float(x)/1000, # Convert mA to A
|
|
||||||
'H19': float, # Total energy absorbed in kWh (already in kWh)
|
|
||||||
'H20': float, # Total energy discharged in kWh
|
|
||||||
'H21': int, # Maximum power today (W)
|
|
||||||
'H22': float, # Energy generated today (kWh)
|
|
||||||
'H23': int, # Maximum power yesterday (W)
|
|
||||||
'HSDS': int # Day sequence number
|
|
||||||
}
|
|
||||||
|
|
||||||
# Convert values according to their type
|
|
||||||
for key, value in data.items():
|
|
||||||
if key in conversions:
|
|
||||||
try:
|
|
||||||
parsed[key] = conversions[key](value)
|
|
||||||
except (ValueError, TypeError) as e:
|
|
||||||
log(f"Conversion error for {key}={value}: {e}", "ERROR")
|
|
||||||
parsed[key] = value # Keep as string if conversion fails
|
|
||||||
else:
|
|
||||||
parsed[key] = value
|
|
||||||
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def get_charger_status(cs_value):
|
|
||||||
"""Convert CS numeric value to human-readable status"""
|
|
||||||
status_map = {
|
|
||||||
0: "Off",
|
|
||||||
2: "Fault",
|
|
||||||
3: "Bulk",
|
|
||||||
4: "Absorption",
|
|
||||||
5: "Float",
|
|
||||||
6: "Storage",
|
|
||||||
7: "Equalize",
|
|
||||||
9: "Inverting",
|
|
||||||
11: "Power supply",
|
|
||||||
245: "Starting-up",
|
|
||||||
247: "Repeated absorption",
|
|
||||||
252: "External control"
|
|
||||||
}
|
|
||||||
return status_map.get(cs_value, f"Unknown ({cs_value})")
|
|
||||||
|
|
||||||
|
|
||||||
def get_mppt_status(mppt_value):
|
|
||||||
"""Convert MPPT value to human-readable status"""
|
|
||||||
mppt_map = {
|
|
||||||
0: "Off",
|
|
||||||
1: "Voltage or current limited",
|
|
||||||
2: "MPP Tracker active"
|
|
||||||
}
|
|
||||||
return mppt_map.get(mppt_value, f"Unknown ({mppt_value})")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
log("=== Victron MPPT Reader ===")
|
|
||||||
log(f"Started at: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
||||||
|
|
||||||
# Read data
|
|
||||||
raw_data = read_vedirect()
|
|
||||||
|
|
||||||
if raw_data:
|
|
||||||
# Parse data
|
|
||||||
parsed_data = parse_values(raw_data)
|
|
||||||
|
|
||||||
if parsed_data:
|
|
||||||
# Display summary
|
|
||||||
log("\n===== MPPT Status Summary =====")
|
|
||||||
log(f"Product: {parsed_data.get('PID', 'Unknown')} (FW: {parsed_data.get('FW', '?')})")
|
|
||||||
log(f"Serial: {parsed_data.get('SER#', 'Unknown')}")
|
|
||||||
log(f"\nBattery: {parsed_data.get('V', 0):.2f}V, {parsed_data.get('I', 0):.2f}A")
|
|
||||||
log(f"Solar Panel: {parsed_data.get('VPV', 0):.2f}V, {parsed_data.get('PPV', 0)}W")
|
|
||||||
log(f"Charger Status: {get_charger_status(parsed_data.get('CS', 0))}")
|
|
||||||
log(f"MPPT Status: {get_mppt_status(parsed_data.get('MPPT', 0))}")
|
|
||||||
log(f"Load Output: {parsed_data.get('LOAD', 'Unknown')}, {parsed_data.get('IL', 0):.2f}A")
|
|
||||||
log(f"\nToday's Energy: {parsed_data.get('H22', 0)}kWh (Max: {parsed_data.get('H21', 0)}W)")
|
|
||||||
log(f"Total Energy: {parsed_data.get('H19', 0)}kWh")
|
|
||||||
|
|
||||||
# Validate critical values
|
|
||||||
battery_voltage = parsed_data.get('V', 0)
|
|
||||||
|
|
||||||
if battery_voltage > 0:
|
|
||||||
# Get timestamp
|
|
||||||
try:
|
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
|
||||||
row = cursor.fetchone()
|
|
||||||
rtc_time_str = row[1] if row else time.strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
except:
|
|
||||||
rtc_time_str = time.strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
# Extract values for database
|
|
||||||
battery_current = parsed_data.get('I', 0)
|
|
||||||
solar_voltage = parsed_data.get('VPV', 0)
|
|
||||||
solar_power = parsed_data.get('PPV', 0)
|
|
||||||
charger_status = parsed_data.get('CS', 0)
|
|
||||||
|
|
||||||
# Save to database
|
|
||||||
try:
|
|
||||||
cursor.execute('''
|
|
||||||
INSERT INTO data_MPPT (timestamp, battery_voltage, battery_current, solar_voltage, solar_power, charger_status)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)''',
|
|
||||||
(rtc_time_str, battery_voltage, battery_current, solar_voltage, solar_power, charger_status))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
log(f"\n✓ Data saved to database at {rtc_time_str}")
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
# Always log database errors regardless of DEBUG_MODE
|
|
||||||
if not DEBUG_MODE:
|
|
||||||
print(f"Database error: {e}")
|
|
||||||
else:
|
|
||||||
log(f"\n✗ Database error: {e}", "ERROR")
|
|
||||||
conn.rollback()
|
|
||||||
else:
|
|
||||||
log("\n✗ Invalid data: Battery voltage is zero or missing", "ERROR")
|
|
||||||
else:
|
|
||||||
log("\n✗ Failed to parse data", "ERROR")
|
|
||||||
else:
|
|
||||||
log("\n✗ No valid data received from MPPT controller", "ERROR")
|
|
||||||
log("\nPossible issues:")
|
|
||||||
log("- Check serial connection (TX/RX/GND)")
|
|
||||||
log("- Verify port is /dev/ttyAMA4")
|
|
||||||
log("- Ensure MPPT is powered on")
|
|
||||||
log("- Check baudrate (should be 19200)")
|
|
||||||
|
|
||||||
# Always close the connection
|
|
||||||
conn.close()
|
|
||||||
log("\nDone.")
|
|
||||||
@@ -52,7 +52,9 @@ def load_config(config_file):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Load the configuration data
|
# Load the configuration data
|
||||||
npm_solo_port = "/dev/ttyAMA5" #port du NPM solo
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
config = load_config(config_file)
|
||||||
|
npm_solo_port = config.get('NPM_solo_port', '') #port du NPM solo
|
||||||
|
|
||||||
#GET RTC TIME from SQlite
|
#GET RTC TIME from SQlite
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -28,19 +28,17 @@ Line by line installation.
|
|||||||
|
|
||||||
```
|
```
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install git gh apache2 sqlite3 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus -y
|
sudo apt install git gh apache2 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus -y
|
||||||
sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil ntplib pytz gpiozero adafruit-circuitpython-ads1x15 numpy nsrt-mk3-dev --break-system-packages
|
sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil ntplib pytz --break-system-packages
|
||||||
sudo mkdir -p /var/www/.ssh
|
sudo mkdir -p /var/www/.ssh
|
||||||
sudo ssh-keygen -t rsa -b 4096 -f /var/www/.ssh/id_rsa -N ""
|
sudo ssh-keygen -t rsa -b 4096 -f /var/www/.ssh/id_rsa -N ""
|
||||||
sudo ssh-copy-id -i /var/www/.ssh/id_rsa.pub -p 50221 airlab_server1@aircarto.fr
|
sudo ssh-copy-id -i /var/www/.ssh/id_rsa.pub -p 50221 airlab_server1@aircarto.fr
|
||||||
sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git /var/www/nebuleair_pro_4g
|
sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git /var/www/nebuleair_pro_4g
|
||||||
sudo mkdir /var/www/nebuleair_pro_4g/logs
|
sudo mkdir /var/www/nebuleair_pro_4g/logs
|
||||||
sudo touch /var/www/nebuleair_pro_4g/logs/app.log /var/www/nebuleair_pro_4g/logs/loop.log /var/www/nebuleair_pro_4g/wifi_list.csv
|
sudo touch /var/www/nebuleair_pro_4g/logs/app.log /var/www/nebuleair_pro_4g/logs/loop.log /var/www/nebuleair_pro_4g/wifi_list.csv
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
|
sudo cp /var/www/nebuleair_pro_4g/config.json.dist /var/www/nebuleair_pro_4g/config.json
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
|
|
||||||
sudo chmod -R 777 /var/www/nebuleair_pro_4g/
|
sudo chmod -R 777 /var/www/nebuleair_pro_4g/
|
||||||
git config --global core.fileMode false
|
git config --global core.fileMode false
|
||||||
git -C /var/www/nebuleair_pro_4g config core.fileMode false
|
|
||||||
git config --global --add safe.directory /var/www/nebuleair_pro_4g
|
git config --global --add safe.directory /var/www/nebuleair_pro_4g
|
||||||
sudo crontab /var/www/nebuleair_pro_4g/cron_jobs
|
sudo crontab /var/www/nebuleair_pro_4g/cron_jobs
|
||||||
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
|
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
|
||||||
@@ -59,8 +57,6 @@ ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot
|
|||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
|
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
|
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
|
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
|
||||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl *
|
|
||||||
www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*
|
|
||||||
```
|
```
|
||||||
## Serial
|
## Serial
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
"""
|
"""
|
||||||
____ _____ ____
|
Script to set the RTC using an NTP server.
|
||||||
| _ \_ _/ ___|
|
|
||||||
| |_) || || |
|
|
||||||
| _ < | || |___
|
|
||||||
|_| \_\|_| \____|
|
|
||||||
|
|
||||||
Script to set the RTC using an NTP server (script used by web UI)
|
|
||||||
RPI needs to be connected to the internet (WIFI).
|
RPI needs to be connected to the internet (WIFI).
|
||||||
Requires ntplib and pytz:
|
Requires ntplib and pytz:
|
||||||
sudo pip3 install ntplib pytz --break-system-packages
|
sudo pip3 install ntplib pytz --break-system-packages
|
||||||
|
|
||||||
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import smbus2
|
import smbus2
|
||||||
import time
|
import time
|
||||||
@@ -53,131 +49,49 @@ def set_time(bus, year, month, day, hour, minute, second):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def read_time(bus):
|
def read_time(bus):
|
||||||
"""Read the RTC time and validate the values."""
|
"""Read the RTC time."""
|
||||||
try:
|
data = bus.read_i2c_block_data(DS3231_ADDR, REG_TIME, 7)
|
||||||
data = bus.read_i2c_block_data(DS3231_ADDR, REG_TIME, 7)
|
second = bcd_to_dec(data[0] & 0x7F)
|
||||||
|
minute = bcd_to_dec(data[1])
|
||||||
# Convert from BCD
|
hour = bcd_to_dec(data[2] & 0x3F)
|
||||||
second = bcd_to_dec(data[0] & 0x7F)
|
day = bcd_to_dec(data[4])
|
||||||
minute = bcd_to_dec(data[1])
|
month = bcd_to_dec(data[5])
|
||||||
hour = bcd_to_dec(data[2] & 0x3F)
|
year = bcd_to_dec(data[6]) + 2000
|
||||||
day = bcd_to_dec(data[4])
|
return (year, month, day, hour, minute, second)
|
||||||
month = bcd_to_dec(data[5])
|
|
||||||
year = bcd_to_dec(data[6]) + 2000
|
|
||||||
|
|
||||||
# Print raw values for debugging
|
|
||||||
print(f"Raw RTC values: {data}")
|
|
||||||
print(f"Decoded values: Y:{year} M:{month} D:{day} H:{hour} M:{minute} S:{second}")
|
|
||||||
|
|
||||||
# Validate date values
|
|
||||||
if not (1 <= month <= 12):
|
|
||||||
print(f"Invalid month value: {month}, using default")
|
|
||||||
month = 1
|
|
||||||
|
|
||||||
# Check days in month (simplified)
|
|
||||||
days_in_month = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
||||||
if not (1 <= day <= days_in_month[month]):
|
|
||||||
print(f"Invalid day value: {day} for month {month}, using default")
|
|
||||||
day = 1
|
|
||||||
|
|
||||||
# Validate time values
|
|
||||||
if not (0 <= hour <= 23):
|
|
||||||
print(f"Invalid hour value: {hour}, using default")
|
|
||||||
hour = 0
|
|
||||||
|
|
||||||
if not (0 <= minute <= 59):
|
|
||||||
print(f"Invalid minute value: {minute}, using default")
|
|
||||||
minute = 0
|
|
||||||
|
|
||||||
if not (0 <= second <= 59):
|
|
||||||
print(f"Invalid second value: {second}, using default")
|
|
||||||
second = 0
|
|
||||||
|
|
||||||
return (year, month, day, hour, minute, second)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading RTC: {e}")
|
|
||||||
# Return a safe default date (2023-01-01 00:00:00)
|
|
||||||
return (2023, 1, 1, 0, 0, 0)
|
|
||||||
|
|
||||||
def get_internet_time():
|
def get_internet_time():
|
||||||
"""Get the current time from an NTP server."""
|
"""Get the current time from an NTP server."""
|
||||||
ntp_client = ntplib.NTPClient()
|
ntp_client = ntplib.NTPClient()
|
||||||
# Try multiple NTP servers in case one fails
|
response = ntp_client.request('pool.ntp.org')
|
||||||
servers = ['pool.ntp.org', 'time.google.com', 'time.windows.com', 'time.apple.com']
|
utc_time = datetime.utcfromtimestamp(response.tx_time)
|
||||||
|
return utc_time
|
||||||
for server in servers:
|
|
||||||
try:
|
|
||||||
print(f"Trying NTP server: {server}")
|
|
||||||
response = ntp_client.request(server, timeout=2)
|
|
||||||
utc_time = datetime.utcfromtimestamp(response.tx_time)
|
|
||||||
print(f"Successfully got time from {server}")
|
|
||||||
return utc_time
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to get time from {server}: {e}")
|
|
||||||
|
|
||||||
# If all servers fail, raise exception
|
|
||||||
raise Exception("All NTP servers failed")
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
bus = smbus2.SMBus(1)
|
||||||
|
|
||||||
|
# Get the current time from the RTC
|
||||||
|
year, month, day, hours, minutes, seconds = read_time(bus)
|
||||||
|
rtc_time = datetime(year, month, day, hours, minutes, seconds)
|
||||||
|
|
||||||
|
# Get current UTC time from an NTP server
|
||||||
try:
|
try:
|
||||||
bus = smbus2.SMBus(1)
|
internet_utc_time = get_internet_time()
|
||||||
|
print(f"Time from Internet (UTC) : {internet_utc_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
# Test if RTC is accessible
|
|
||||||
try:
|
|
||||||
bus.read_byte(DS3231_ADDR)
|
|
||||||
print("RTC module is accessible")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error accessing RTC module: {e}")
|
|
||||||
print("Please check connections and I2C configuration")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the current time from the RTC
|
|
||||||
try:
|
|
||||||
year, month, day, hours, minutes, seconds = read_time(bus)
|
|
||||||
# Create datetime object with validation to handle invalid dates
|
|
||||||
rtc_time = datetime(year, month, day, hours, minutes, seconds)
|
|
||||||
print(f"Actual RTC Time : {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
||||||
except ValueError as e:
|
|
||||||
print(f"Invalid date/time read from RTC: {e}")
|
|
||||||
print("Will proceed with setting RTC from internet time")
|
|
||||||
rtc_time = None
|
|
||||||
|
|
||||||
# Get current UTC time from an NTP server
|
|
||||||
try:
|
|
||||||
internet_utc_time = get_internet_time()
|
|
||||||
print(f"Time from Internet (UTC) : {internet_utc_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error retrieving time from the internet: {e}")
|
|
||||||
if rtc_time is None:
|
|
||||||
print("Cannot proceed without either valid RTC time or internet time")
|
|
||||||
return
|
|
||||||
print("Will keep current RTC time")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Set the RTC to UTC time
|
|
||||||
print("Setting RTC to internet time...")
|
|
||||||
set_time(bus, internet_utc_time.year, internet_utc_time.month, internet_utc_time.day,
|
|
||||||
internet_utc_time.hour, internet_utc_time.minute, internet_utc_time.second)
|
|
||||||
|
|
||||||
# Read and print the new time from RTC
|
|
||||||
print("Reading back new RTC time...")
|
|
||||||
year, month, day, hour, minute, second = read_time(bus)
|
|
||||||
rtc_time_new = datetime(year, month, day, hour, minute, second)
|
|
||||||
print(f"New RTC Time (UTC) : {rtc_time_new.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
||||||
|
|
||||||
# Calculate difference to verify accuracy
|
|
||||||
time_diff = abs((rtc_time_new - internet_utc_time).total_seconds())
|
|
||||||
print(f"Time difference : {time_diff:.2f} seconds")
|
|
||||||
|
|
||||||
if time_diff > 5:
|
|
||||||
print("Warning: RTC time differs significantly from internet time")
|
|
||||||
print("You may need to retry or check RTC module")
|
|
||||||
else:
|
|
||||||
print("RTC successfully synchronized with internet time")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Unexpected error: {e}")
|
print(f"Error retrieving time from the internet: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Print current RTC time
|
||||||
|
print(f"Actual RTC Time : {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
|
||||||
|
# Set the RTC to UTC time
|
||||||
|
set_time(bus, internet_utc_time.year, internet_utc_time.month, internet_utc_time.day,
|
||||||
|
internet_utc_time.hour, internet_utc_time.minute, internet_utc_time.second)
|
||||||
|
|
||||||
|
# Read and print the new time from RTC
|
||||||
|
year, month, day, hour, minute, second = read_time(bus)
|
||||||
|
rtc_time_new = datetime(year, month, day, hour, minute, second)
|
||||||
|
print(f"New RTC Time (UTC) : {rtc_time_new.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
____ _____ ____
|
Script to set the RTC using the browser time.
|
||||||
| _ \_ _/ ___|
|
|
||||||
| |_) || || |
|
|
||||||
| _ < | || |___
|
|
||||||
|_| \_\|_| \____|
|
|
||||||
|
|
||||||
Script to set the RTC using the browser time (script used by the web UI).
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39'
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39'
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# PPP activation
|
|
||||||
|
|
||||||
Une fois la connexion PPP activée on peut retrouver la connexion pp0 avec `ifconfig`.
|
|
||||||
|
|
||||||
### Test avec curl
|
|
||||||
|
|
||||||
On peut forcer l'utilisation du réseau pp0 avec curl:
|
|
||||||
|
|
||||||
`curl --interface ppp0 https://ifconfig.me`
|
|
||||||
|
|
||||||
ou avec ping:
|
|
||||||
|
|
||||||
`ping -I ppp0 google.com`
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
sudo pppd /dev/ttyAMA2 115200 \
|
|
||||||
connect '/usr/sbin/chat -v -s "" "AT" OK "ATD*99#" CONNECT' \
|
|
||||||
noauth debug dump nodetach nocrtscts
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
'''
|
|
||||||
____ _ ____ _
|
|
||||||
/ ___| / \ | _ \ / \
|
|
||||||
\___ \ / _ \ | |_) | / _ \
|
|
||||||
___) / ___ \| _ < / ___ \
|
|
||||||
|____/_/ \_\_| \_\/_/ \_\
|
|
||||||
|
|
||||||
Script to set the PDP context for the SARA R5
|
|
||||||
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/R5/setPDP.py
|
|
||||||
|
|
||||||
'''
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
|
|
||||||
#get data from config
|
|
||||||
def load_config(config_file):
|
|
||||||
try:
|
|
||||||
with open(config_file, 'r') as file:
|
|
||||||
config_data = json.load(file)
|
|
||||||
return config_data
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error loading config file: {e}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
#Fonction pour mettre à jour le JSON de configuration
|
|
||||||
def update_json_key(file_path, key, value):
|
|
||||||
"""
|
|
||||||
Updates a specific key in a JSON file with a new value.
|
|
||||||
|
|
||||||
:param file_path: Path to the JSON file.
|
|
||||||
:param key: The key to update in the JSON file.
|
|
||||||
:param value: The new value to assign to the key.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Load the existing data
|
|
||||||
with open(file_path, "r") as file:
|
|
||||||
data = json.load(file)
|
|
||||||
|
|
||||||
# Check if the key exists in the JSON file
|
|
||||||
if key in data:
|
|
||||||
data[key] = value # Update the key with the new value
|
|
||||||
else:
|
|
||||||
print(f"Key '{key}' not found in the JSON file.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Write the updated data back to the file
|
|
||||||
with open(file_path, "w") as file:
|
|
||||||
json.dump(data, file, indent=2) # Use indent for pretty printing
|
|
||||||
|
|
||||||
print(f"💾 updating '{key}' to '{value}'.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error updating the JSON file: {e}")
|
|
||||||
|
|
||||||
# Define the config file path
|
|
||||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
|
||||||
# Load the configuration data
|
|
||||||
config = load_config(config_file)
|
|
||||||
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
|
|
||||||
device_id = config.get('deviceID', '').upper() #device ID en maj
|
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
|
||||||
port='/dev/ttyAMA2',
|
|
||||||
baudrate=baudrate, #115200 ou 9600
|
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
|
||||||
stopbits=serial.STOPBITS_ONE,
|
|
||||||
bytesize=serial.EIGHTBITS,
|
|
||||||
timeout = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
|
|
||||||
'''
|
|
||||||
Fonction très importante !!!
|
|
||||||
Reads the complete response from a serial connection and waits for specific lines.
|
|
||||||
'''
|
|
||||||
if wait_for_lines is None:
|
|
||||||
wait_for_lines = [] # Default to an empty list if not provided
|
|
||||||
|
|
||||||
response = bytearray()
|
|
||||||
serial_connection.timeout = timeout
|
|
||||||
end_time = time.time() + end_of_response_timeout
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
elapsed_time = time.time() - start_time # Time since function start
|
|
||||||
if serial_connection.in_waiting > 0:
|
|
||||||
data = serial_connection.read(serial_connection.in_waiting)
|
|
||||||
response.extend(data)
|
|
||||||
end_time = time.time() + end_of_response_timeout # Reset timeout on new data
|
|
||||||
|
|
||||||
# Decode and check for any target line
|
|
||||||
decoded_response = response.decode('utf-8', errors='replace')
|
|
||||||
for target_line in wait_for_lines:
|
|
||||||
if target_line in decoded_response:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] 🔎 Found target line: {target_line} (in {elapsed_time:.2f}s)")
|
|
||||||
return decoded_response # Return response immediately if a target line is found
|
|
||||||
elif time.time() > end_time:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] Timeout reached. No more data received.")
|
|
||||||
break
|
|
||||||
time.sleep(0.1) # Short sleep to prevent busy waiting
|
|
||||||
|
|
||||||
# Final response and debug output
|
|
||||||
total_elapsed_time = time.time() - start_time
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] ⏱️ elapsed time: {total_elapsed_time:.2f}s. ⏱️")
|
|
||||||
# Check if the elapsed time exceeded 10 seconds
|
|
||||||
if total_elapsed_time > 10 and debug:
|
|
||||||
print(f"[ALERT] 🚨 The operation took too long 🚨")
|
|
||||||
print(f'<span style="color: red;font-weight: bold;">[ALERT] ⚠️{total_elapsed_time:.2f}s⚠️</span>')
|
|
||||||
|
|
||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
|
||||||
|
|
||||||
try:
|
|
||||||
print('Start script')
|
|
||||||
|
|
||||||
# 1. Check connection
|
|
||||||
print('➡️Check SARA R5 connexion')
|
|
||||||
command = f'ATI0\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_1, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Activate PDP context 1
|
|
||||||
print('➡️Activate PDP context 1')
|
|
||||||
command = f'AT+CGACT=1,1\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_2, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Set the PDP type
|
|
||||||
print('➡️Set the PDP type to IPv4 referring to the outputof the +CGDCONT read command')
|
|
||||||
command = f'AT+UPSD=0,0,0\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_3, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Profile #0 is mapped on CID=1.
|
|
||||||
print('➡️Profile #0 is mapped on CID=1.')
|
|
||||||
command = f'AT+UPSD=0,100,1\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_3, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Set the PDP type
|
|
||||||
print('➡️Activate the PSD profile #0: the IPv4 address is already assigned by the network.')
|
|
||||||
command = f'AT+UPSDA=0,3\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, wait_for_lines=["OK","+UUPSDA"])
|
|
||||||
print(response_SARA_3, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("An error occurred:", e)
|
|
||||||
traceback.print_exc() # This prints the full traceback
|
|
||||||
@@ -25,8 +25,23 @@ url = parameter[1] # ex: data.mobileair.fr
|
|||||||
|
|
||||||
profile_id = 3
|
profile_id = 3
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
send_uSpot = False
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
send_uSpot = config.get('send_uSpot', False)
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_line=None):
|
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_line=None):
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
|
|||||||
@@ -26,8 +26,23 @@ url = parameter[1] # ex: data.mobileair.fr
|
|||||||
|
|
||||||
profile_id = 3
|
profile_id = 3
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
send_uSpot = False
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
send_uSpot = config.get('send_uSpot', False)
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
|
|||||||
@@ -28,8 +28,23 @@ url = parameter[1] # ex: data.mobileair.fr
|
|||||||
endpoint = parameter[2]
|
endpoint = parameter[2]
|
||||||
profile_id = 2
|
profile_id = 2
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
send_uSpot = False
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
send_uSpot = config.get('send_uSpot', False)
|
||||||
|
|
||||||
def color_text(text, color):
|
def color_text(text, color):
|
||||||
colors = {
|
colors = {
|
||||||
|
|||||||
@@ -31,8 +31,23 @@ endpoint = parameter[2]
|
|||||||
|
|
||||||
profile_id = 3
|
profile_id = 3
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
send_uSpot = False
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
send_uSpot = config.get('send_uSpot', False)
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
|
|||||||
@@ -21,8 +21,23 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
url = parameter[1] # ex: data.mobileair.fr
|
url = parameter[1] # ex: data.mobileair.fr
|
||||||
|
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
send_uSpot = False
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
send_uSpot = config.get('send_uSpot', False)
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
|
|||||||
@@ -23,8 +23,24 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||||
url = parameter[1] # ex: data.mobileair.fr
|
url = parameter[1] # ex: data.mobileair.fr
|
||||||
|
|
||||||
baudrate = 115200
|
|
||||||
send_uSpot = False
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
send_uSpot = config.get('send_uSpot', False)
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
|
|||||||
@@ -14,7 +14,19 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port = '/dev/' + parameter[0] # e.g., ttyAMA2
|
port = '/dev/' + parameter[0] # e.g., ttyAMA2
|
||||||
timeout = float(parameter[1]) # e.g., 2 seconds
|
timeout = float(parameter[1]) # e.g., 2 seconds
|
||||||
|
|
||||||
baudrate = 115200
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
config = load_config(config_file)
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||||
response = bytearray()
|
response = bytearray()
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
'''
|
|
||||||
____ _ ____ _
|
|
||||||
/ ___| / \ | _ \ / \
|
|
||||||
\___ \ / _ \ | |_) | / _ \
|
|
||||||
___) / ___ \| _ < / ___ \
|
|
||||||
|____/_/ \_\_| \_\/_/ \_\
|
|
||||||
|
|
||||||
Script to read UDP message
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/UDP/receiveUDP_downlink.py
|
|
||||||
|
|
||||||
'''
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
'''
|
|
||||||
____ _ ____ _
|
|
||||||
/ ___| / \ | _ \ / \
|
|
||||||
\___ \ / _ \ | |_) | / _ \
|
|
||||||
___) / ___ \| _ < / ___ \
|
|
||||||
|____/_/ \_\_| \_\/_/ \_\
|
|
||||||
|
|
||||||
Script to send UDP message
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/UDP/sendUDP_message.py
|
|
||||||
|
|
||||||
'''
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
|
||||||
port='/dev/ttyAMA2',
|
|
||||||
baudrate=115200, #115200 ou 9600
|
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
|
||||||
stopbits=serial.STOPBITS_ONE,
|
|
||||||
bytesize=serial.EIGHTBITS,
|
|
||||||
timeout = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
|
|
||||||
'''
|
|
||||||
Fonction très importante !!!
|
|
||||||
Reads the complete response from a serial connection and waits for specific lines.
|
|
||||||
'''
|
|
||||||
if wait_for_lines is None:
|
|
||||||
wait_for_lines = [] # Default to an empty list if not provided
|
|
||||||
|
|
||||||
response = bytearray()
|
|
||||||
serial_connection.timeout = timeout
|
|
||||||
end_time = time.time() + end_of_response_timeout
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
elapsed_time = time.time() - start_time # Time since function start
|
|
||||||
if serial_connection.in_waiting > 0:
|
|
||||||
data = serial_connection.read(serial_connection.in_waiting)
|
|
||||||
response.extend(data)
|
|
||||||
end_time = time.time() + end_of_response_timeout # Reset timeout on new data
|
|
||||||
|
|
||||||
# Decode and check for any target line
|
|
||||||
decoded_response = response.decode('utf-8', errors='replace')
|
|
||||||
for target_line in wait_for_lines:
|
|
||||||
if target_line in decoded_response:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] 🔎 Found target line: {target_line} (in {elapsed_time:.2f}s)")
|
|
||||||
return decoded_response # Return response immediately if a target line is found
|
|
||||||
elif time.time() > end_time:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] Timeout reached. No more data received.")
|
|
||||||
break
|
|
||||||
time.sleep(0.1) # Short sleep to prevent busy waiting
|
|
||||||
|
|
||||||
# Final response and debug output
|
|
||||||
total_elapsed_time = time.time() - start_time
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] ⏱️ elapsed time: {total_elapsed_time:.2f}s. ⏱️")
|
|
||||||
# Check if the elapsed time exceeded 10 seconds
|
|
||||||
if total_elapsed_time > 10 and debug:
|
|
||||||
print(f"[ALERT] 🚨 The operation took too long 🚨")
|
|
||||||
print(f'<span style="color: red;font-weight: bold;">[ALERT] ⚠️{total_elapsed_time:.2f}s⚠️</span>')
|
|
||||||
|
|
||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
|
||||||
|
|
||||||
try:
|
|
||||||
print('Start script')
|
|
||||||
|
|
||||||
# Increase verbosity
|
|
||||||
command = f'AT+CMEE=2\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR"])
|
|
||||||
print(response_SARA_1, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 1. Create SOCKET
|
|
||||||
print('➡️Create SOCKET')
|
|
||||||
command = f'AT+USOCR=17\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR"])
|
|
||||||
print(response_SARA_1, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Retreive Socket ID
|
|
||||||
match = re.search(r'\+USOCR:\s*(\d+)', response_SARA_1)
|
|
||||||
if match:
|
|
||||||
socket_id = match.group(1)
|
|
||||||
print(f"Socket ID: {socket_id}")
|
|
||||||
else:
|
|
||||||
print("Failed to extract socket ID")
|
|
||||||
|
|
||||||
|
|
||||||
#3. Connect to UDP server
|
|
||||||
print("Connect to server:")
|
|
||||||
command = f'AT+USOCO={socket_id},"192.168.0.20",4242\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
|
||||||
print(response_SARA_2)
|
|
||||||
|
|
||||||
# 4. Write data and send
|
|
||||||
print("Write data:")
|
|
||||||
command = f'AT+USOWR={socket_id},10\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False)
|
|
||||||
print(response_SARA_2)
|
|
||||||
|
|
||||||
ser_sara.write("1234567890".encode())
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["@","OK", "+CME ERROR", "ERROR"], debug=False)
|
|
||||||
print(response_SARA_2)
|
|
||||||
|
|
||||||
#Close socket
|
|
||||||
print("Close socket:")
|
|
||||||
command = f'AT+USOCL={socket_id}\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=False)
|
|
||||||
print(response_SARA_2)
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("An error occurred:", e)
|
|
||||||
traceback.print_exc() # This prints the full traceback
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
'''
|
|
||||||
____ _ ____ _
|
|
||||||
/ ___| / \ | _ \ / \
|
|
||||||
\___ \ / _ \ | |_) | / _ \
|
|
||||||
___) / ___ \| _ < / ___ \
|
|
||||||
|____/_/ \_\_| \_\/_/ \_\
|
|
||||||
|
|
||||||
Script to Configures the network connection to a Multi GNSS Assistance (MGA) server used also per CellLocate
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/cellLocate/server_conf.py ttyAMA2 1
|
|
||||||
|
|
||||||
AT+UGSRV="cell-live1.services.u-blox.com","cell-live2.services.u-blox.com","XkEKfGqVSbmNE1eZfBZm4Q"
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
|
|
||||||
parameter = sys.argv[1:] # Exclude the script name
|
|
||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
|
||||||
timeout = float(parameter[1]) # ex:2
|
|
||||||
|
|
||||||
|
|
||||||
#get baudrate
|
|
||||||
def load_config(config_file):
|
|
||||||
try:
|
|
||||||
with open(config_file, 'r') as file:
|
|
||||||
config_data = json.load(file)
|
|
||||||
return config_data
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error loading config file: {e}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Define the config file path
|
|
||||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
|
||||||
# Load the configuration data
|
|
||||||
config = load_config(config_file)
|
|
||||||
# Access the shared variables
|
|
||||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
|
||||||
port=port, #USB0 or ttyS0
|
|
||||||
baudrate=baudrate, #115200 ou 9600
|
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
|
||||||
stopbits=serial.STOPBITS_ONE,
|
|
||||||
bytesize=serial.EIGHTBITS,
|
|
||||||
timeout = timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
|
|
||||||
'''
|
|
||||||
Fonction très importante !!!
|
|
||||||
Reads the complete response from a serial connection and waits for specific lines.
|
|
||||||
'''
|
|
||||||
if wait_for_lines is None:
|
|
||||||
wait_for_lines = [] # Default to an empty list if not provided
|
|
||||||
|
|
||||||
response = bytearray()
|
|
||||||
serial_connection.timeout = timeout
|
|
||||||
end_time = time.time() + end_of_response_timeout
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
elapsed_time = time.time() - start_time # Time since function start
|
|
||||||
if serial_connection.in_waiting > 0:
|
|
||||||
data = serial_connection.read(serial_connection.in_waiting)
|
|
||||||
response.extend(data)
|
|
||||||
end_time = time.time() + end_of_response_timeout # Reset timeout on new data
|
|
||||||
|
|
||||||
# Decode and check for any target line
|
|
||||||
decoded_response = response.decode('utf-8', errors='replace')
|
|
||||||
for target_line in wait_for_lines:
|
|
||||||
if target_line in decoded_response:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] 🔎 Found target line: {target_line} (in {elapsed_time:.2f}s)")
|
|
||||||
return decoded_response # Return response immediately if a target line is found
|
|
||||||
elif time.time() > end_time:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] Timeout reached. No more data received.")
|
|
||||||
break
|
|
||||||
time.sleep(0.1) # Short sleep to prevent busy waiting
|
|
||||||
|
|
||||||
# Final response and debug output
|
|
||||||
total_elapsed_time = time.time() - start_time
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] ⏱️ elapsed time: {total_elapsed_time:.2f}s. ⏱️")
|
|
||||||
# Check if the elapsed time exceeded 10 seconds
|
|
||||||
if total_elapsed_time > 10 and debug:
|
|
||||||
print(f"[ALERT] 🚨 The operation took too long 🚨")
|
|
||||||
print(f'<span style="color: red;font-weight: bold;">[ALERT] ⚠️{total_elapsed_time:.2f}s⚠️</span>')
|
|
||||||
|
|
||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
|
||||||
|
|
||||||
|
|
||||||
#command = f'ATI\r'
|
|
||||||
command = f'AT+UGSRV="cell-live1.services.u-blox.com","cell-live2.services.u-blox.com","XkEKfGqVSbmNE1eZfBZm4Q"\r'
|
|
||||||
ser.write((command + '\r').encode('utf-8'))
|
|
||||||
|
|
||||||
response = read_complete_response(ser, wait_for_lines=["+UULOC"])
|
|
||||||
print(response)
|
|
||||||
@@ -13,110 +13,57 @@ Script that starts at the boot of the RPI (with cron)
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
import serial
|
import serial
|
||||||
import RPi.GPIO as GPIO
|
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
#GPIO
|
#get data from config
|
||||||
SARA_power_GPIO = 16
|
def load_config(config_file):
|
||||||
SARA_ON_GPIO = 20
|
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BCM) # Use BCM numbering
|
|
||||||
GPIO.setup(SARA_power_GPIO, GPIO.OUT) # Set GPIO17 as an output
|
|
||||||
|
|
||||||
# database connection
|
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
#get config data from SQLite table
|
|
||||||
def load_config_sqlite():
|
|
||||||
"""
|
|
||||||
Load configuration data from SQLite config table
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Configuration data with proper type conversion
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
# Query the config table
|
config_data = json.load(file)
|
||||||
cursor.execute("SELECT key, value, type FROM config_table")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
|
|
||||||
# Create config dictionary
|
|
||||||
config_data = {}
|
|
||||||
for key, value, type_name in rows:
|
|
||||||
# Convert value based on its type
|
|
||||||
if type_name == 'bool':
|
|
||||||
config_data[key] = value == '1' or value == 'true'
|
|
||||||
elif type_name == 'int':
|
|
||||||
config_data[key] = int(value)
|
|
||||||
elif type_name == 'float':
|
|
||||||
config_data[key] = float(value)
|
|
||||||
else:
|
|
||||||
config_data[key] = value
|
|
||||||
|
|
||||||
return config_data
|
return config_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading config from SQLite: {e}")
|
print(f"Error loading config file: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def update_sqlite_config(key, value):
|
#Fonction pour mettre à jour le JSON de configuration
|
||||||
|
def update_json_key(file_path, key, value):
|
||||||
"""
|
"""
|
||||||
Updates a specific key in the SQLite config_table with a new value.
|
Updates a specific key in a JSON file with a new value.
|
||||||
|
|
||||||
:param key: The key to update in the config_table.
|
:param file_path: Path to the JSON file.
|
||||||
|
:param key: The key to update in the JSON file.
|
||||||
:param value: The new value to assign to the key.
|
:param value: The new value to assign to the key.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# Load the existing data
|
||||||
# Check if the key exists and get its type
|
with open(file_path, "r") as file:
|
||||||
cursor.execute("SELECT type FROM config_table WHERE key = ?", (key,))
|
data = json.load(file)
|
||||||
result = cursor.fetchone()
|
|
||||||
|
|
||||||
if result is None:
|
# Check if the key exists in the JSON file
|
||||||
print(f"Key '{key}' not found in the config_table.")
|
if key in data:
|
||||||
conn.close()
|
data[key] = value # Update the key with the new value
|
||||||
|
else:
|
||||||
|
print(f"Key '{key}' not found in the JSON file.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the type of the value from the database
|
# Write the updated data back to the file
|
||||||
value_type = result[0]
|
with open(file_path, "w") as file:
|
||||||
|
json.dump(data, file, indent=2) # Use indent for pretty printing
|
||||||
|
|
||||||
# Convert the value to the appropriate string representation based on its type
|
print(f"💾 updating '{key}' to '{value}'.")
|
||||||
if value_type == 'bool':
|
|
||||||
# Convert Python boolean or string 'true'/'false' to '1'/'0'
|
|
||||||
if isinstance(value, bool):
|
|
||||||
str_value = '1' if value else '0'
|
|
||||||
else:
|
|
||||||
str_value = '1' if str(value).lower() in ('true', '1', 'yes', 'y') else '0'
|
|
||||||
elif value_type == 'int':
|
|
||||||
str_value = str(int(value))
|
|
||||||
elif value_type == 'float':
|
|
||||||
str_value = str(float(value))
|
|
||||||
else:
|
|
||||||
str_value = str(value)
|
|
||||||
|
|
||||||
# Update the value in the database
|
|
||||||
cursor.execute("UPDATE config_table SET value = ? WHERE key = ?", (str_value, key))
|
|
||||||
|
|
||||||
# Commit the changes and close the connection
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print(f"💾 Updated '{key}' to '{value}' in database.")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error updating the SQLite database: {e}")
|
print(f"Error updating the JSON file: {e}")
|
||||||
|
|
||||||
#Load config
|
# Define the config file path
|
||||||
config = load_config_sqlite()
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
#config
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
|
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
|
||||||
device_id = config.get('deviceID', '').upper() #device ID en maj
|
device_id = config.get('deviceID', '').upper() #device ID en maj
|
||||||
|
|
||||||
sara_r5_DPD_setup = False
|
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
ser_sara = serial.Serial(
|
||||||
port='/dev/ttyAMA2',
|
port='/dev/ttyAMA2',
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=baudrate, #115200 ou 9600
|
||||||
@@ -173,46 +120,20 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
try:
|
try:
|
||||||
print('<h3>Start reboot python script</h3>')
|
print('<h3>Start reboot python script</h3>')
|
||||||
|
|
||||||
#First we need to power on the module (if connected to mosfet via gpio16)
|
|
||||||
GPIO.output(SARA_power_GPIO, GPIO.HIGH)
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
#check modem status
|
#check modem status
|
||||||
#Attention:
|
|
||||||
# SARA R4 response: Manufacturer: u-blox Model: SARA-R410M-02B
|
|
||||||
# SArA R5 response: SARA-R500S-01B-00
|
|
||||||
print("⚙️Check SARA Status")
|
print("⚙️Check SARA Status")
|
||||||
command = f'ATI\r'
|
command = f'ATI\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
response_SARA_ATI = read_complete_response(ser_sara, wait_for_lines=["IMEI"])
|
response_SARA_ATI = read_complete_response(ser_sara, wait_for_lines=["IMEI"])
|
||||||
print(response_SARA_ATI)
|
print(response_SARA_ATI)
|
||||||
|
match = re.search(r"Model:\s*(.+)", response_SARA_ATI)
|
||||||
# Check for SARA model with more robust regex
|
model = match.group(1).strip() if match else "Unknown" # Strip unwanted characters
|
||||||
model = "Unknown"
|
print(f" Model: {model}")
|
||||||
if "SARA-R410M" in response_SARA_ATI:
|
update_json_key(config_file, "modem_version", model)
|
||||||
model = "SARA-R410M"
|
|
||||||
print("📱 Detected SARA R4 modem")
|
|
||||||
elif "SARA-R500" in response_SARA_ATI:
|
|
||||||
model = "SARA-R500"
|
|
||||||
print("📱 Detected SARA R5 modem")
|
|
||||||
sara_r5_DPD_setup = True
|
|
||||||
else:
|
|
||||||
# Fallback to regex match if direct string match fails
|
|
||||||
match = re.search(r"Model:\s*([A-Za-z0-9\-]+)", response_SARA_ATI)
|
|
||||||
if match:
|
|
||||||
model = match.group(1).strip()
|
|
||||||
else:
|
|
||||||
model = "Unknown"
|
|
||||||
print("⚠️ Could not identify modem model")
|
|
||||||
|
|
||||||
print(f"🔍 Model: {model}")
|
|
||||||
update_sqlite_config("modem_version", model)
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
'''
|
|
||||||
AIRCARTO
|
# 1. Set AIRCARTO URL
|
||||||
'''
|
|
||||||
# 1. Set AIRCARTO URL (profile id = 0)
|
|
||||||
print('➡️Set aircarto URL')
|
print('➡️Set aircarto URL')
|
||||||
aircarto_profile_id = 0
|
aircarto_profile_id = 0
|
||||||
aircarto_url="data.nebuleair.fr"
|
aircarto_url="data.nebuleair.fr"
|
||||||
@@ -222,155 +143,26 @@ try:
|
|||||||
print(response_SARA_1)
|
print(response_SARA_1)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
'''
|
#2. Set uSpot URL
|
||||||
uSpot
|
print('➡️Set uSpot URL')
|
||||||
'''
|
|
||||||
print("➡️➡️Set uSpot URL with SSL")
|
|
||||||
|
|
||||||
security_profile_id = 1
|
|
||||||
uSpot_profile_id = 1
|
uSpot_profile_id = 1
|
||||||
uSpot_url="api-prod.uspot.probesys.net"
|
uSpot_url="api-prod.uspot.probesys.net"
|
||||||
|
|
||||||
|
|
||||||
#step 1: import the certificate
|
|
||||||
print("➡️ import certificate")
|
|
||||||
certificate_name = "e6"
|
|
||||||
with open("/var/www/nebuleair_pro_4g/SARA/SSL/certificate/e6.pem", "rb") as cert_file:
|
|
||||||
certificate = cert_file.read()
|
|
||||||
size_of_string = len(certificate)
|
|
||||||
|
|
||||||
# AT+USECMNG=0,<type>,<internal_name>,<data_size>
|
|
||||||
# type-> 0 -> trusted root CA
|
|
||||||
command = f'AT+USECMNG=0,0,"{certificate_name}",{size_of_string}\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
|
||||||
response_SARA_1 = read_complete_response(ser_sara)
|
|
||||||
print(response_SARA_1)
|
|
||||||
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
print("➡️ add certificate")
|
|
||||||
ser_sara.write(certificate)
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara)
|
|
||||||
print(response_SARA_2)
|
|
||||||
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# op_code: 0 -> certificate validation level
|
|
||||||
# param_val : 0 -> Level 0 No validation; 1-> Level 1 Root certificate validation
|
|
||||||
print("➡️Set the security profile (params)")
|
|
||||||
certification_level=0
|
|
||||||
command = f'AT+USECPRF={security_profile_id},0,{certification_level}\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
|
||||||
response_SARA_5b = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_5b)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# op_code: 1 -> minimum SSL/TLS version
|
|
||||||
# param_val : 0 -> any; server can use any version for the connection; 1-> LSv1.0; 2->TLSv1.1; 3->TLSv1.2;
|
|
||||||
print("➡️Set the security profile (params)")
|
|
||||||
minimum_SSL_version = 0
|
|
||||||
command = f'AT+USECPRF={security_profile_id},1,{minimum_SSL_version}\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
|
||||||
response_SARA_5bb = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_5bb)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
#op_code: 2 -> legacy cipher suite selection
|
|
||||||
# 0 (factory-programmed value): a list of default cipher suites is proposed at the beginning of handshake process, and a cipher suite will be negotiated among the cipher suites proposed in the list.
|
|
||||||
print("➡️Set cipher")
|
|
||||||
cipher_suite = 0
|
|
||||||
command = f'AT+USECPRF={security_profile_id},2,{cipher_suite}\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
|
||||||
response_SARA_5cc = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_5cc)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# op_code: 3 -> trusted root certificate internal name
|
|
||||||
print("➡️Set the security profile (choose cert)")
|
|
||||||
command = f'AT+USECPRF={security_profile_id},3,"{certificate_name}"\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
|
||||||
response_SARA_5c = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_5c)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# op_code: 10 -> SNI (server name indication)
|
|
||||||
print("➡️Set the SNI")
|
|
||||||
command = f'AT+USECPRF={security_profile_id},10,"{uSpot_url}"\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
|
||||||
response_SARA_5cf = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_5cf)
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
#step 4: set url (op_code = 1)
|
|
||||||
print("➡️SET URL")
|
|
||||||
command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r'
|
command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r'
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||||
print(response_SARA_5)
|
print(response_SARA_2)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
#step 4: set PORT (op_code = 5)
|
print("set port 81")
|
||||||
print("➡️SET PORT")
|
command = f'AT+UHTTP={uSpot_profile_id},5,81\r'
|
||||||
port = 443
|
|
||||||
command = f'AT+UHTTP={uSpot_profile_id},5,{port}\r'
|
|
||||||
ser_sara.write((command + '\r').encode('utf-8'))
|
ser_sara.write((command + '\r').encode('utf-8'))
|
||||||
response_SARA_55 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
response_SARA_55 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||||
print(response_SARA_55)
|
print(response_SARA_55)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
#step 4: set url to SSL (op_code = 6) (http_secure = 1 for HTTPS)(USECMNG_PROFILE = 2)
|
|
||||||
print("➡️SET SSL")
|
|
||||||
http_secure = 1
|
|
||||||
command = f'AT+UHTTP={uSpot_profile_id},6,{http_secure},{security_profile_id}\r'
|
|
||||||
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_5fg = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_5fg)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
SARA R5
|
|
||||||
'''
|
|
||||||
|
|
||||||
if sara_r5_DPD_setup:
|
|
||||||
print("➡️➡️SARA R5 PDP SETUP")
|
|
||||||
# 2. Activate PDP context 1
|
|
||||||
print('➡️Activate PDP context 1')
|
|
||||||
command = f'AT+CGACT=1,1\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_2, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Set the PDP type
|
|
||||||
print('➡️Set the PDP type to IPv4 referring to the outputof the +CGDCONT read command')
|
|
||||||
command = f'AT+UPSD=0,0,0\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_3, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Profile #0 is mapped on CID=1.
|
|
||||||
print('➡️Profile #0 is mapped on CID=1.')
|
|
||||||
command = f'AT+UPSD=0,100,1\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_SARA_3, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 2. Set the PDP type
|
|
||||||
print('➡️Activate the PSD profile #0: the IPv4 address is already assigned by the network.')
|
|
||||||
command = f'AT+UPSDA=0,3\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, wait_for_lines=["OK","+UUPSDA"])
|
|
||||||
print(response_SARA_3, end="")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
|
|
||||||
#3. Get localisation (CellLocate)
|
#3. Get localisation (CellLocate)
|
||||||
mode = 2 #single shot position
|
mode = 2
|
||||||
sensor = 2 #use cellular CellLocate® location information
|
sensor = 2
|
||||||
response_type = 0
|
response_type = 0
|
||||||
timeout_s = 2
|
timeout_s = 2
|
||||||
accuracy_m = 1
|
accuracy_m = 1
|
||||||
@@ -387,9 +179,9 @@ try:
|
|||||||
else:
|
else:
|
||||||
print("❌ Failed to extract coordinates.")
|
print("❌ Failed to extract coordinates.")
|
||||||
|
|
||||||
#update sqlite table
|
#update config.json
|
||||||
update_sqlite_config("latitude_raw", float(latitude))
|
update_json_key(config_file, "latitude_raw", float(latitude))
|
||||||
update_sqlite_config("longitude_raw", float(longitude))
|
update_json_key(config_file, "longitude_raw", float(longitude))
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|||||||
104
SARA/sara.py
104
SARA/sara.py
@@ -6,9 +6,7 @@
|
|||||||
|____/_/ \_\_| \_\/_/ \_\
|
|____/_/ \_\_| \_\/_/ \_\
|
||||||
|
|
||||||
Script to see if the SARA-R410 is running
|
Script to see if the SARA-R410 is running
|
||||||
ex:
|
ex:
|
||||||
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 ATI 2
|
|
||||||
ex 1 (get SIM infos)
|
|
||||||
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+CCID? 2
|
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+CCID? 2
|
||||||
ex 2 (turn on blue light):
|
ex 2 (turn on blue light):
|
||||||
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UGPIOC=16,2 2
|
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UGPIOC=16,2 2
|
||||||
@@ -16,8 +14,6 @@ ex 3 (reconnect network)
|
|||||||
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+COPS=1,2,20801 20
|
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+COPS=1,2,20801 20
|
||||||
ex 4 (get HTTP Profiles)
|
ex 4 (get HTTP Profiles)
|
||||||
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UHTTP? 2
|
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UHTTP? 2
|
||||||
ex 5 (get IP addr)
|
|
||||||
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+CGPADDR=1 2
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -32,64 +28,68 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
command = parameter[1] # ex: AT+CCID?
|
command = parameter[1] # ex: AT+CCID?
|
||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
# Access the shared variables
|
# Access the shared variables
|
||||||
baudrate = 115200
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
|
ser = serial.Serial(
|
||||||
|
port=port, #USB0 or ttyS0
|
||||||
|
baudrate=baudrate, #115200 ou 9600
|
||||||
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
|
stopbits=serial.STOPBITS_ONE,
|
||||||
|
bytesize=serial.EIGHTBITS,
|
||||||
|
timeout = timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
ser.write((command + '\r').encode('utf-8'))
|
||||||
|
|
||||||
|
#ser.write(b'ATI\r') #General Information
|
||||||
|
#ser.write(b'AT+CCID?\r') #SIM card number
|
||||||
|
#ser.write(b'AT+CPIN?\r') #Check the status of the SIM card
|
||||||
|
#ser.write(b'AT+CIND?\r') #Indication state (last number is SIM detection: 0 no SIM detection, 1 SIM detected, 2 not available)
|
||||||
|
#ser.write(b'AT+UGPIOR=?\r') #Reads the current value of the specified GPIO pin
|
||||||
|
#ser.write(b'AT+UGPIOC?\r') #GPIO select configuration
|
||||||
|
#ser.write(b'AT+COPS=?\r') #Check the network and cellular technology the modem is currently using
|
||||||
|
#ser.write(b'AT+COPS=1,2,20801') #connext to orange
|
||||||
|
#ser.write(b'AT+CFUN=?\r') #Selects/read the level of functionality
|
||||||
|
#ser.write(b'AT+URAT=?\r') #Radio Access Technology
|
||||||
|
#ser.write(b'AT+USIMSTAT?')
|
||||||
|
#ser.write(b'AT+IPR=115200') #Check/Define baud rate
|
||||||
|
#ser.write(b'AT+CMUX=?')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
ser = serial.Serial(
|
|
||||||
port=port, #USB0 or ttyS0
|
|
||||||
baudrate=baudrate, #115200 ou 9600
|
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
|
||||||
stopbits=serial.STOPBITS_ONE,
|
|
||||||
bytesize=serial.EIGHTBITS,
|
|
||||||
timeout = timeout
|
|
||||||
)
|
|
||||||
|
|
||||||
ser.write((command + '\r').encode('utf-8'))
|
|
||||||
|
|
||||||
#ser.write(b'ATI\r') #General Information
|
|
||||||
#ser.write(b'AT+CCID?\r') #SIM card number
|
|
||||||
#ser.write(b'AT+CPIN?\r') #Check the status of the SIM card
|
|
||||||
#ser.write(b'AT+CIND?\r') #Indication state (last number is SIM detection: 0 no SIM detection, 1 SIM detected, 2 not available)
|
|
||||||
#ser.write(b'AT+UGPIOR=?\r') #Reads the current value of the specified GPIO pin
|
|
||||||
#ser.write(b'AT+UGPIOC?\r') #GPIO select configuration
|
|
||||||
#ser.write(b'AT+COPS=?\r') #Check the network and cellular technology the modem is currently using
|
|
||||||
#ser.write(b'AT+COPS=1,2,20801') #connext to orange
|
|
||||||
#ser.write(b'AT+CFUN=?\r') #Selects/read the level of functionality
|
|
||||||
#ser.write(b'AT+URAT=?\r') #Radio Access Technology
|
|
||||||
#ser.write(b'AT+USIMSTAT?')
|
|
||||||
#ser.write(b'AT+IPR=115200') #Check/Define baud rate
|
|
||||||
#ser.write(b'AT+CMUX=?')
|
|
||||||
|
|
||||||
|
|
||||||
# Read lines until a timeout occurs
|
# Read lines until a timeout occurs
|
||||||
response_lines = []
|
response_lines = []
|
||||||
start_time = time.time()
|
while True:
|
||||||
|
line = ser.readline().decode('utf-8').strip()
|
||||||
while (time.time() - start_time) < timeout:
|
if not line:
|
||||||
line = ser.readline().decode('utf-8', errors='ignore').strip()
|
break # Break the loop if an empty line is encountered
|
||||||
if line:
|
response_lines.append(line)
|
||||||
response_lines.append(line)
|
|
||||||
|
|
||||||
# Check if we received any data
|
|
||||||
if not response_lines:
|
|
||||||
print(f"ERROR: No response received from {port} after sending command: {command}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Print the response
|
# Print the response
|
||||||
for line in response_lines:
|
for line in response_lines:
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
print(f"ERROR: Serial communication error: {e}")
|
print(f"Error: {e}")
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: Unexpected error: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
finally:
|
finally:
|
||||||
# Close the serial port if it's open
|
if ser.is_open:
|
||||||
if 'ser' in locals() and ser.is_open:
|
|
||||||
ser.close()
|
ser.close()
|
||||||
|
#print("Serial closed")
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
'''
|
|
||||||
____ _ ____ _
|
|
||||||
/ ___| / \ | _ \ / \
|
|
||||||
\___ \ / _ \ | |_) | / _ \
|
|
||||||
___) / ___ \| _ < / ___ \
|
|
||||||
|____/_/ \_\_| \_\/_/ \_\
|
|
||||||
|
|
||||||
Script to resolve DNS (get IP from domain name) with AT+UDNSRN command
|
|
||||||
Ex:
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_checkDNS.py ttyAMA2 data.nebuleair.fr
|
|
||||||
To do: need to add profile id as parameter
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
|
|
||||||
parameter = sys.argv[1:] # Exclude the script name
|
|
||||||
#print("Parameters received:")
|
|
||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
|
||||||
url = parameter[1] # ex: data.mobileair.fr
|
|
||||||
|
|
||||||
baudrate = 115200
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
|
||||||
port=port, #USB0 or ttyS0
|
|
||||||
baudrate=baudrate, #115200 ou 9600
|
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
|
||||||
stopbits=serial.STOPBITS_ONE,
|
|
||||||
bytesize=serial.EIGHTBITS,
|
|
||||||
timeout = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
command = f'AT+UDNSRN=0,"{url}"\r'
|
|
||||||
ser.write((command + '\r').encode('utf-8'))
|
|
||||||
|
|
||||||
print("****")
|
|
||||||
print("DNS check")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Read lines until a timeout occurs
|
|
||||||
response_lines = []
|
|
||||||
while True:
|
|
||||||
line = ser.readline().decode('utf-8').strip()
|
|
||||||
if not line:
|
|
||||||
break # Break the loop if an empty line is encountered
|
|
||||||
response_lines.append(line)
|
|
||||||
|
|
||||||
# Print the response
|
|
||||||
for line in response_lines:
|
|
||||||
print(line)
|
|
||||||
|
|
||||||
except serial.SerialException as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if ser.is_open:
|
|
||||||
ser.close()
|
|
||||||
print("****")
|
|
||||||
#print("Serial closed")
|
|
||||||
|
|
||||||
@@ -26,54 +26,22 @@ networkID = parameter[1] # ex: 20801
|
|||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
|
|
||||||
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
'''
|
# Load the configuration data
|
||||||
Fonction très importante !!!
|
config = load_config(config_file)
|
||||||
Reads the complete response from a serial connection and waits for specific lines.
|
# Access the shared variables
|
||||||
'''
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
if wait_for_lines is None:
|
|
||||||
wait_for_lines = [] # Default to an empty list if not provided
|
|
||||||
|
|
||||||
response = bytearray()
|
|
||||||
serial_connection.timeout = timeout
|
|
||||||
end_time = time.time() + end_of_response_timeout
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
elapsed_time = time.time() - start_time # Time since function start
|
|
||||||
if serial_connection.in_waiting > 0:
|
|
||||||
data = serial_connection.read(serial_connection.in_waiting)
|
|
||||||
response.extend(data)
|
|
||||||
end_time = time.time() + end_of_response_timeout # Reset timeout on new data
|
|
||||||
|
|
||||||
# Decode and check for any target line
|
|
||||||
decoded_response = response.decode('utf-8', errors='replace')
|
|
||||||
for target_line in wait_for_lines:
|
|
||||||
if target_line in decoded_response:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] 🔎 Found target line: {target_line} (in {elapsed_time:.2f}s)")
|
|
||||||
return decoded_response # Return response immediately if a target line is found
|
|
||||||
elif time.time() > end_time:
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] Timeout reached. No more data received.")
|
|
||||||
break
|
|
||||||
time.sleep(0.1) # Short sleep to prevent busy waiting
|
|
||||||
|
|
||||||
# Final response and debug output
|
|
||||||
total_elapsed_time = time.time() - start_time
|
|
||||||
if debug:
|
|
||||||
print(f"[DEBUG] ⏱️ elapsed time: {total_elapsed_time:.2f}s. ⏱️")
|
|
||||||
# Check if the elapsed time exceeded 10 seconds
|
|
||||||
if total_elapsed_time > 10 and debug:
|
|
||||||
print(f"[ALERT] 🚨 The operation took too long 🚨")
|
|
||||||
print(f'<span style="color: red;font-weight: bold;">[ALERT] ⚠️{total_elapsed_time:.2f}s⚠️</span>')
|
|
||||||
|
|
||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
|
||||||
|
|
||||||
|
|
||||||
baudrate = 115200
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
@@ -89,11 +57,17 @@ ser.write((command + '\r').encode('utf-8'))
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = read_complete_response(ser, wait_for_lines=["OK", "ERROR"],timeout=5, end_of_response_timeout=120, debug=True)
|
# Read lines until a timeout occurs
|
||||||
|
response_lines = []
|
||||||
print('<p class="text-danger-emphasis">')
|
while True:
|
||||||
print(response)
|
line = ser.readline().decode('utf-8').strip()
|
||||||
print("</p>", end="")
|
if not line:
|
||||||
|
break # Break the loop if an empty line is encountered
|
||||||
|
response_lines.append(line)
|
||||||
|
|
||||||
|
# Print the response
|
||||||
|
for line in response_lines:
|
||||||
|
print(line)
|
||||||
|
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
print(f"Error: {e}")
|
print(f"Error: {e}")
|
||||||
|
|||||||
@@ -11,7 +11,22 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||||
message = parameter[1] # ex: Hello
|
message = parameter[1] # ex: Hello
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
|
|||||||
@@ -18,7 +18,24 @@ import sys
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
baudrate = 115200
|
|
||||||
|
|
||||||
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port='/dev/ttyAMA2',
|
port='/dev/ttyAMA2',
|
||||||
|
|||||||
@@ -17,7 +17,23 @@ import json
|
|||||||
# SARA R4 UHTTPC profile IDs
|
# SARA R4 UHTTPC profile IDs
|
||||||
aircarto_profile_id = 0
|
aircarto_profile_id = 0
|
||||||
|
|
||||||
baudrate = 115200
|
|
||||||
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
ser_sara = serial.Serial(
|
||||||
port='/dev/ttyAMA2',
|
port='/dev/ttyAMA2',
|
||||||
@@ -73,24 +89,6 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
||||||
|
|
||||||
|
|
||||||
def extract_error_code(response):
|
|
||||||
"""
|
|
||||||
Extract just the error code from AT+UHTTPER response
|
|
||||||
"""
|
|
||||||
for line in response.split('\n'):
|
|
||||||
if '+UHTTPER' in line:
|
|
||||||
try:
|
|
||||||
# Split the line and get the third value (error code)
|
|
||||||
parts = line.split(':')[1].strip().split(',')
|
|
||||||
if len(parts) >= 3:
|
|
||||||
error_code = int(parts[2])
|
|
||||||
return error_code
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Return None if we couldn't find the error code
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#3. Send to endpoint (with device ID)
|
#3. Send to endpoint (with device ID)
|
||||||
print("Send data (GET REQUEST):")
|
print("Send data (GET REQUEST):")
|
||||||
@@ -113,36 +111,7 @@ try:
|
|||||||
parts = http_response.split(',')
|
parts = http_response.split(',')
|
||||||
# 2.1 code 0 (HTTP failed) ⛔⛔⛔
|
# 2.1 code 0 (HTTP failed) ⛔⛔⛔
|
||||||
if len(parts) == 3 and parts[-1] == '0': # The third value indicates success
|
if len(parts) == 3 and parts[-1] == '0': # The third value indicates success
|
||||||
print("⛔⛔ATTENTION: HTTP operation failed")
|
print("⛔ATTENTION: HTTP operation failed")
|
||||||
#get error code
|
|
||||||
print("Getting error code (11->Server connection error, 73->Secure socket connect error)")
|
|
||||||
command = f'AT+UHTTPER={aircarto_profile_id}\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_SARA_9 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
|
||||||
print('<p class="text-danger-emphasis">')
|
|
||||||
print(response_SARA_9)
|
|
||||||
print("</p>", end="")
|
|
||||||
# Extract just the error code
|
|
||||||
error_code = extract_error_code(response_SARA_9)
|
|
||||||
if error_code is not None:
|
|
||||||
# Display interpretation based on error code
|
|
||||||
if error_code == 0:
|
|
||||||
print('<p class="text-success">No error detected</p>')
|
|
||||||
elif error_code == 4:
|
|
||||||
print('<p class="text-danger">Error 4: Invalid server Hostname</p>')
|
|
||||||
elif error_code == 11:
|
|
||||||
print('<p class="text-danger">Error 11: Server connection error</p>')
|
|
||||||
elif error_code == 22:
|
|
||||||
print('<p class="text-danger">Error 22: PSD or CSD connection not established</p>')
|
|
||||||
elif error_code == 73:
|
|
||||||
print('<p class="text-danger">Error 73: Secure socket connect error</p>')
|
|
||||||
else:
|
|
||||||
print(f'<p class="text-danger">Unknown error code: {error_code}</p>')
|
|
||||||
else:
|
|
||||||
print('<p class="text-danger">Could not extract error code from response</p>')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 2.2 code 1 (HHTP succeded)
|
# 2.2 code 1 (HHTP succeded)
|
||||||
else:
|
else:
|
||||||
# Si la commande HTTP a réussi
|
# Si la commande HTTP a réussi
|
||||||
|
|||||||
@@ -11,7 +11,22 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||||
message = parameter[1] # ex: Hello
|
message = parameter[1] # ex: Hello
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
|
|||||||
@@ -12,7 +12,22 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
endpoint = parameter[1] # ex: /pro_4G/notif_message.php
|
endpoint = parameter[1] # ex: /pro_4G/notif_message.php
|
||||||
profile_id = parameter[2]
|
profile_id = parameter[2]
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
|
|||||||
@@ -21,7 +21,23 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
apn_address = parameter[1] # ex: data.mono
|
apn_address = parameter[1] # ex: data.mono
|
||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
baudrate = 115200
|
|
||||||
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
@@ -33,8 +49,6 @@ ser = serial.Serial(
|
|||||||
)
|
)
|
||||||
|
|
||||||
command = f'AT+CGDCONT=1,"IP","{apn_address}"\r'
|
command = f'AT+CGDCONT=1,"IP","{apn_address}"\r'
|
||||||
#command = f'AT+CGDCONT=1,"IPV4V6","{apn_address}"\r'
|
|
||||||
#command = f'AT+CGDCONT=1,"IP","{apn_address}",0,0\r'
|
|
||||||
ser.write((command + '\r').encode('utf-8'))
|
ser.write((command + '\r').encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
Script to set the URL for a HTTP request
|
Script to set the URL for a HTTP request
|
||||||
Ex:
|
Ex:
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setURL.py ttyAMA2 data.nebuleair.fr 0
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setURL.py ttyAMA2 data.nebuleair.fr 0
|
||||||
|
To do: need to add profile id as parameter
|
||||||
|
|
||||||
First profile id:
|
First profile id:
|
||||||
AT+UHTTP=0,1,"data.nebuleair.fr"
|
AT+UHTTP=0,1,"data.nebuleair.fr"
|
||||||
@@ -27,7 +28,22 @@ url = parameter[1] # ex: data.mobileair.fr
|
|||||||
profile_id = parameter[2] #ex: 0
|
profile_id = parameter[2] #ex: 0
|
||||||
|
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
|
|||||||
@@ -40,7 +40,22 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
|
|
||||||
return response.decode('utf-8', errors='replace')
|
return response.decode('utf-8', errors='replace')
|
||||||
|
|
||||||
baudrate = 115200
|
#get baudrate
|
||||||
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
ser_sara = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
|
|||||||
@@ -12,7 +12,21 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
message = parameter[1] # ex: Hello
|
message = parameter[1] # ex: Hello
|
||||||
|
|
||||||
#get baudrate
|
#get baudrate
|
||||||
baudrate = 115200
|
def load_config(config_file):
|
||||||
|
try:
|
||||||
|
with open(config_file, 'r') as file:
|
||||||
|
config_data = json.load(file)
|
||||||
|
return config_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
# Access the shared variables
|
||||||
|
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
# Script to check if wifi is connected and start hotspot if not
|
# Script to check if wifi is connected and start hotspot if not
|
||||||
# will also retreive unique RPi ID and store it to deviceID.txt
|
# will also retreive unique RPi ID and store it to deviceID.txt
|
||||||
# script that starts at boot:
|
|
||||||
# @reboot /var/www/nebuleair_pro_4g/boot_hotspot.sh >> /var/www/nebuleair_pro_4g/logs/app.log 2>&1
|
|
||||||
|
|
||||||
OUTPUT_FILE="/var/www/nebuleair_pro_4g/wifi_list.csv"
|
OUTPUT_FILE="/var/www/nebuleair_pro_4g/wifi_list.csv"
|
||||||
|
JSON_FILE="/var/www/nebuleair_pro_4g/config.json"
|
||||||
|
|
||||||
|
|
||||||
echo "-------------------"
|
echo "-------------------"
|
||||||
@@ -13,8 +12,6 @@ echo "-------------------"
|
|||||||
|
|
||||||
echo "NebuleAir pro started at $(date)"
|
echo "NebuleAir pro started at $(date)"
|
||||||
|
|
||||||
chmod -R 777 /var/www/nebuleair_pro_4g/
|
|
||||||
|
|
||||||
# Blink GPIO 23 and 24 five times
|
# Blink GPIO 23 and 24 five times
|
||||||
for i in {1..5}; do
|
for i in {1..5}; do
|
||||||
# Turn GPIO 23 and 24 ON
|
# Turn GPIO 23 and 24 ON
|
||||||
@@ -28,19 +25,15 @@ for i in {1..5}; do
|
|||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "getting RPI serial number"
|
echo "getting SARA R4 serial number"
|
||||||
# Get the last 8 characters of the serial number and write to text file
|
# Get the last 8 characters of the serial number and write to text file
|
||||||
serial_number=$(cat /proc/cpuinfo | grep Serial | awk '{print substr($3, length($3) - 7)}')
|
serial_number=$(cat /proc/cpuinfo | grep Serial | awk '{print substr($3, length($3) - 7)}')
|
||||||
|
# Use jq to update the "deviceID" in the JSON file
|
||||||
# update Sqlite database
|
jq --arg serial_number "$serial_number" '.deviceID = $serial_number' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE"
|
||||||
echo "Updating SQLite database with device ID: $serial_number"
|
|
||||||
sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='$serial_number' WHERE key='deviceID';"
|
|
||||||
|
|
||||||
echo "id: $serial_number"
|
echo "id: $serial_number"
|
||||||
|
|
||||||
|
#get the SSH port for tunneling
|
||||||
# Get SSH tunnel port from SQLite config_table
|
SSH_TUNNEL_PORT=$(jq -r '.sshTunnel_port' "$JSON_FILE")
|
||||||
SSH_TUNNEL_PORT=$(sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "SELECT value FROM config_table WHERE key='sshTunnel_port'")
|
|
||||||
|
|
||||||
#need to wait for the network manager to be ready
|
#need to wait for the network manager to be ready
|
||||||
sleep 20
|
sleep 20
|
||||||
@@ -58,16 +51,19 @@ if [ "$STATE" == "30 (disconnected)" ]; then
|
|||||||
echo "Starting hotspot..."
|
echo "Starting hotspot..."
|
||||||
sudo nmcli device wifi hotspot ifname wlan0 ssid nebuleair_pro password nebuleaircfg
|
sudo nmcli device wifi hotspot ifname wlan0 ssid nebuleair_pro password nebuleaircfg
|
||||||
|
|
||||||
# Update SQLite to reflect hotspot mode
|
# Update JSON to reflect hotspot mode
|
||||||
sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='hotspot' WHERE key='WIFI_status'"
|
jq --arg status "hotspot" '.WIFI_status = $status' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE"
|
||||||
|
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "🛜Success: wlan0 is connected!🛜"
|
echo "🛜Success: wlan0 is connected!🛜"
|
||||||
CONN_SSID=$(nmcli -g GENERAL.CONNECTION device show wlan0)
|
CONN_SSID=$(nmcli -g GENERAL.CONNECTION device show wlan0)
|
||||||
echo "Connection: $CONN_SSID"
|
echo "Connection: $CONN_SSID"
|
||||||
|
|
||||||
# Update SQLite to reflect hotspot mode
|
#update config JSON file
|
||||||
sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='connected' WHERE key='WIFI_status'"
|
jq --arg status "connected" '.WIFI_status = $status' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE"
|
||||||
|
|
||||||
|
sudo chmod 777 "$JSON_FILE"
|
||||||
|
|
||||||
# Lancer le tunnel SSH
|
# Lancer le tunnel SSH
|
||||||
#echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..."
|
#echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..."
|
||||||
|
|||||||
5
old/config.json.dist → config.json.dist
Normal file → Executable file
5
old/config.json.dist → config.json.dist
Normal file → Executable file
@@ -5,11 +5,8 @@
|
|||||||
"RTC/save_to_db.py": true,
|
"RTC/save_to_db.py": true,
|
||||||
"BME280/get_data_v2.py": true,
|
"BME280/get_data_v2.py": true,
|
||||||
"envea/read_value_v2.py": false,
|
"envea/read_value_v2.py": false,
|
||||||
"MPPT/read.py": false,
|
|
||||||
"windMeter/read.py": false,
|
|
||||||
"sqlite/flush_old_data.py": true,
|
"sqlite/flush_old_data.py": true,
|
||||||
"deviceID": "XXXX",
|
"deviceID": "XXXX",
|
||||||
"npm_5channel": false,
|
|
||||||
"latitude_raw": 0,
|
"latitude_raw": 0,
|
||||||
"longitude_raw":0,
|
"longitude_raw":0,
|
||||||
"latitude_precision": 0,
|
"latitude_precision": 0,
|
||||||
@@ -28,7 +25,7 @@
|
|||||||
"SARA_R4_general_status": "connected",
|
"SARA_R4_general_status": "connected",
|
||||||
"SARA_R4_SIM_status": "connected",
|
"SARA_R4_SIM_status": "connected",
|
||||||
"SARA_R4_network_status": "connected",
|
"SARA_R4_network_status": "connected",
|
||||||
"SARA_R4_neworkID": 20810,
|
"SARA_R4_neworkID": 0,
|
||||||
"WIFI_status": "connected",
|
"WIFI_status": "connected",
|
||||||
"MQTT_GUI": false,
|
"MQTT_GUI": false,
|
||||||
"send_aircarto": true,
|
"send_aircarto": true,
|
||||||
@@ -4,10 +4,4 @@
|
|||||||
|
|
||||||
@reboot sleep 30 && /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/reboot/start.py >> /var/www/nebuleair_pro_4g/logs/app.log 2>&1
|
@reboot sleep 30 && /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/reboot/start.py >> /var/www/nebuleair_pro_4g/logs/app.log 2>&1
|
||||||
|
|
||||||
#0 0 * * * > /var/www/nebuleair_pro_4g/logs/master.log
|
0 0 * * * > /var/www/nebuleair_pro_4g/logs/master.log
|
||||||
#0 0 * * * > /var/www/nebuleair_pro_4g/logs/master_errors.log
|
|
||||||
#0 0 * * * > /var/www/nebuleair_pro_4g/logs/app.log
|
|
||||||
|
|
||||||
0 0 * * * find /var/www/nebuleair_pro_4g/logs -name "*.log" -type f -exec truncate -s 0 {} \;
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import serial
|
import serial
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import re
|
|
||||||
|
|
||||||
parameter = sys.argv[1:] # Exclude the script name
|
parameter = sys.argv[1:] # Exclude the script name
|
||||||
#print("Parameters received:")
|
#print("Parameters received:")
|
||||||
@@ -62,46 +61,8 @@ def read_cairsens(port, baudrate=9600, parity=serial.PARITY_NONE, stopbits=seria
|
|||||||
|
|
||||||
# ASCII characters
|
# ASCII characters
|
||||||
ascii_data = ''.join(chr(b) if 0x20 <= b <= 0x7E else '.' for b in raw_bytes)
|
ascii_data = ''.join(chr(b) if 0x20 <= b <= 0x7E else '.' for b in raw_bytes)
|
||||||
sensor_type = "Unknown" # ou None, selon ton besoin
|
print(f"Valeurs converties en ASCII : {ascii_data}")
|
||||||
sensor_measurement = "Unknown"
|
|
||||||
sensor_range = "Unknown"
|
|
||||||
|
|
||||||
letters = re.findall(r'[A-Za-z]', ascii_data)
|
|
||||||
if len(letters) >= 1:
|
|
||||||
#print(f"First letter found: {letters[0]}")
|
|
||||||
if letters[0] == "C":
|
|
||||||
sensor_type = "Cairclip"
|
|
||||||
if len(letters) >= 2:
|
|
||||||
#print(f"Second letter found: {letters[1]}")
|
|
||||||
if letters[1] == "A":
|
|
||||||
sensor_measurement = "Ammonia(NH3)"
|
|
||||||
if letters[1] == "C":
|
|
||||||
sensor_measurement = "O3 and NO2"
|
|
||||||
if letters[1] == "G":
|
|
||||||
sensor_measurement = "CH4"
|
|
||||||
if letters[1] == "H":
|
|
||||||
sensor_measurement = "H2S"
|
|
||||||
if letters[1] == "N":
|
|
||||||
sensor_measurement = "NO2"
|
|
||||||
if len(letters) >= 3:
|
|
||||||
#print(f"Thrisd letter found: {letters[2]}")
|
|
||||||
if letters[2] == "B":
|
|
||||||
sensor_range = "0-250 ppb"
|
|
||||||
if letters[2] == "M":
|
|
||||||
sensor_range = "0-1ppm"
|
|
||||||
if letters[2] == "V":
|
|
||||||
sensor_range = "0-20 ppm"
|
|
||||||
if letters[2] == "P":
|
|
||||||
sensor_range = "PACKET data block ?"
|
|
||||||
|
|
||||||
if len(letters) < 1:
|
|
||||||
print("No letter found in the ASCII data.")
|
|
||||||
|
|
||||||
print(f"Valeurs converties en ASCII : {sensor_type} {sensor_measurement} {sensor_range}")
|
|
||||||
|
|
||||||
#print(f"Sensor type: {sensor_type}")
|
|
||||||
#print(f"Sensor measurment: {sensor_measurement}")
|
|
||||||
#print(f"Sensor range: {sensor_range}")
|
|
||||||
# Numeric values
|
# Numeric values
|
||||||
numeric_values = [b for b in raw_bytes]
|
numeric_values = [b for b in raw_bytes]
|
||||||
print(f"Valeurs numériques : {numeric_values}")
|
print(f"Valeurs numériques : {numeric_values}")
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
"""
|
|
||||||
_____ _ ___ _______ _
|
|
||||||
| ____| \ | \ \ / / ____| / \
|
|
||||||
| _| | \| |\ \ / /| _| / _ \
|
|
||||||
| |___| |\ | \ V / | |___ / ___ \
|
|
||||||
|_____|_| \_| \_/ |_____/_/ \_\
|
|
||||||
|
|
||||||
Gather data from envea Sensors and store them to the SQlite table
|
|
||||||
Use the RTC time for the timestamp
|
|
||||||
|
|
||||||
This script is run by a service nebuleair-envea-data.service
|
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_ref_v2.py ttyAMA4
|
|
||||||
|
|
||||||
ATTENTION --> read_ref.py fonctionne mieux
|
|
||||||
"""
|
|
||||||
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
parameter = sys.argv[1:] # Exclude the script name
|
|
||||||
port='/dev/'+parameter[0]
|
|
||||||
|
|
||||||
# Mapping dictionaries
|
|
||||||
COMPOUND_MAP = {
|
|
||||||
'A': 'Ammonia',
|
|
||||||
'B': 'Benzene',
|
|
||||||
'C': 'Carbon Monoxide',
|
|
||||||
'D': 'Hydrogen Sulfide',
|
|
||||||
'E': 'Ethylene',
|
|
||||||
'F': 'Formaldehyde',
|
|
||||||
'G': 'Gasoline',
|
|
||||||
'H': 'Hydrogen',
|
|
||||||
'I': 'Isobutylene',
|
|
||||||
'J': 'Jet Fuel',
|
|
||||||
'K': 'Kerosene',
|
|
||||||
'L': 'Liquified Petroleum Gas',
|
|
||||||
'M': 'Methane',
|
|
||||||
'N': 'Nitrogen Dioxide',
|
|
||||||
'O': 'Ozone',
|
|
||||||
'P': 'Propane',
|
|
||||||
'Q': 'Quinoline',
|
|
||||||
'R': 'Refrigerant',
|
|
||||||
'S': 'Sulfur Dioxide',
|
|
||||||
'T': 'Toluene',
|
|
||||||
'U': 'Uranium Hexafluoride',
|
|
||||||
'V': 'Vinyl Chloride',
|
|
||||||
'W': 'Water Vapor',
|
|
||||||
'X': 'Xylene',
|
|
||||||
'Y': 'Yttrium',
|
|
||||||
'Z': 'Zinc'
|
|
||||||
}
|
|
||||||
|
|
||||||
RANGE_MAP = {
|
|
||||||
'A': '0-10 ppm',
|
|
||||||
'B': '0-250 ppb',
|
|
||||||
'C': '0-1000 ppm',
|
|
||||||
'D': '0-50 ppm',
|
|
||||||
'E': '0-100 ppm',
|
|
||||||
'F': '0-5 ppm',
|
|
||||||
'G': '0-500 ppm',
|
|
||||||
'H': '0-2000 ppm',
|
|
||||||
'I': '0-200 ppm',
|
|
||||||
'J': '0-300 ppm',
|
|
||||||
'K': '0-400 ppm',
|
|
||||||
'L': '0-600 ppm',
|
|
||||||
'M': '0-800 ppm',
|
|
||||||
'N': '0-20 ppm',
|
|
||||||
'O': '0-1 ppm',
|
|
||||||
'P': '0-5000 ppm',
|
|
||||||
'Q': '0-150 ppm',
|
|
||||||
'R': '0-750 ppm',
|
|
||||||
'S': '0-25 ppm',
|
|
||||||
'T': '0-350 ppm',
|
|
||||||
'U': '0-450 ppm',
|
|
||||||
'V': '0-550 ppm',
|
|
||||||
'W': '0-650 ppm',
|
|
||||||
'X': '0-850 ppm',
|
|
||||||
'Y': '0-950 ppm',
|
|
||||||
'Z': '0-1500 ppm'
|
|
||||||
}
|
|
||||||
|
|
||||||
INTERFACE_MAP = {
|
|
||||||
0x01: 'USB',
|
|
||||||
0x02: 'UART',
|
|
||||||
0x03: 'I2C',
|
|
||||||
0x04: 'SPI'
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse_cairsens_data(hex_data):
|
|
||||||
"""
|
|
||||||
Parse the extracted hex data from CAIRSENS sensor.
|
|
||||||
|
|
||||||
:param hex_data: Hexadecimal string of extracted data (indices 11-28)
|
|
||||||
:return: Dictionary with parsed information
|
|
||||||
"""
|
|
||||||
# Convert hex to bytes for easier processing
|
|
||||||
raw_bytes = bytes.fromhex(hex_data)
|
|
||||||
|
|
||||||
# Initialize result dictionary
|
|
||||||
result = {
|
|
||||||
'device_type': 'Unknown',
|
|
||||||
'compound': 'Unknown',
|
|
||||||
'range': 'Unknown',
|
|
||||||
'interface': 'Unknown',
|
|
||||||
'raw_data': hex_data
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(raw_bytes) >= 4: # Ensure we have at least 4 bytes
|
|
||||||
# First byte: Device type check
|
|
||||||
first_char = chr(raw_bytes[0]) if 0x20 <= raw_bytes[0] <= 0x7E else '?'
|
|
||||||
if first_char == 'C':
|
|
||||||
result['device_type'] = 'CAIRCLIP'
|
|
||||||
else:
|
|
||||||
result['device_type'] = f'Unknown ({first_char})'
|
|
||||||
|
|
||||||
# Second byte: Compound mapping
|
|
||||||
second_char = chr(raw_bytes[1]) if 0x20 <= raw_bytes[1] <= 0x7E else '?'
|
|
||||||
result['compound'] = COMPOUND_MAP.get(second_char, f'Unknown ({second_char})')
|
|
||||||
|
|
||||||
# Third byte: Range mapping
|
|
||||||
third_char = chr(raw_bytes[2]) if 0x20 <= raw_bytes[2] <= 0x7E else '?'
|
|
||||||
result['range'] = RANGE_MAP.get(third_char, f'Unknown ({third_char})')
|
|
||||||
|
|
||||||
# Fourth byte: Interface (raw byte value)
|
|
||||||
interface_byte = raw_bytes[3]
|
|
||||||
result['interface'] = INTERFACE_MAP.get(interface_byte, f'Unknown (0x{interface_byte:02X})')
|
|
||||||
result['interface_raw'] = f'0x{interface_byte:02X}'
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def read_cairsens(port, baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, databits=serial.EIGHTBITS, timeout=1):
|
|
||||||
"""
|
|
||||||
Lit les données de la sonde CAIRSENS via UART.
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_ref_v2.py ttyAMA4
|
|
||||||
|
|
||||||
|
|
||||||
:param port: Le port série utilisé (ex: 'COM1' ou '/dev/ttyAMA0').
|
|
||||||
:param baudrate: Le débit en bauds (ex: 9600).
|
|
||||||
:param parity: Le bit de parité (serial.PARITY_NONE, serial.PARITY_EVEN, serial.PARITY_ODD).
|
|
||||||
:param stopbits: Le nombre de bits de stop (serial.STOPBITS_ONE, serial.STOPBITS_TWO).
|
|
||||||
:param databits: Le nombre de bits de données (serial.FIVEBITS, serial.SIXBITS, serial.SEVENBITS, serial.EIGHTBITS).
|
|
||||||
:param timeout: Temps d'attente maximal pour la lecture (en secondes).
|
|
||||||
:return: Les données reçues sous forme de chaîne de caractères.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Ouvrir la connexion série
|
|
||||||
ser = serial.Serial(
|
|
||||||
port=port,
|
|
||||||
baudrate=baudrate,
|
|
||||||
parity=parity,
|
|
||||||
stopbits=stopbits,
|
|
||||||
bytesize=databits,
|
|
||||||
timeout=timeout
|
|
||||||
)
|
|
||||||
print(f"Connexion ouverte sur {port} à {baudrate} bauds.")
|
|
||||||
|
|
||||||
# Attendre un instant pour stabiliser la connexion
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Envoyer une commande à la sonde (si nécessaire)
|
|
||||||
# Adapter cette ligne selon la documentation de la sonde
|
|
||||||
#ser.write(b'\r\n')
|
|
||||||
ser.write(b'\xFF\x02\x13\x30\x01\x02\x03\x04\x05\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x1C\xD1\x61\x03')
|
|
||||||
|
|
||||||
# Lire les données reçues
|
|
||||||
data = ser.readline()
|
|
||||||
print(f"Données reçues brutes : {data}")
|
|
||||||
|
|
||||||
# Convertir les données en hexadécimal
|
|
||||||
hex_data = data.hex() # Convertit en chaîne hexadécimale
|
|
||||||
formatted_hex = ' '.join(hex_data[i:i+2] for i in range(0, len(hex_data), 2)) # Formate avec des espaces
|
|
||||||
print(f"Données reçues en hexadécimal : {formatted_hex}")
|
|
||||||
|
|
||||||
# Extraire les valeurs de l'index 11 à 28 (indices 22 à 56 en hex string)
|
|
||||||
extracted_hex = hex_data[22:56] # Each byte is 2 hex chars, so 11*2=22 to 28*2=56
|
|
||||||
print(f"Valeurs hexadécimales extraites (11 à 28) : {extracted_hex}")
|
|
||||||
|
|
||||||
# Parse the extracted data
|
|
||||||
parsed_data = parse_cairsens_data(extracted_hex)
|
|
||||||
|
|
||||||
# Display parsed information
|
|
||||||
print("\n=== CAIRSENS SENSOR INFORMATION ===")
|
|
||||||
print(f"Device Type: {parsed_data['device_type']}")
|
|
||||||
print(f"Compound: {parsed_data['compound']}")
|
|
||||||
print(f"Range: {parsed_data['range']}")
|
|
||||||
print(f"Interface: {parsed_data['interface']} ({parsed_data.get('interface_raw', 'N/A')})")
|
|
||||||
print(f"Raw Data: {parsed_data['raw_data']}")
|
|
||||||
print("=====================================")
|
|
||||||
|
|
||||||
# Convertir en ASCII et en valeurs numériques (pour debug)
|
|
||||||
if extracted_hex:
|
|
||||||
raw_bytes = bytes.fromhex(extracted_hex)
|
|
||||||
ascii_data = ''.join(chr(b) if 0x20 <= b <= 0x7E else '.' for b in raw_bytes)
|
|
||||||
print(f"Valeurs converties en ASCII : {ascii_data}")
|
|
||||||
|
|
||||||
numeric_values = [b for b in raw_bytes]
|
|
||||||
print(f"Valeurs numériques : {numeric_values}")
|
|
||||||
|
|
||||||
# Fermer la connexion
|
|
||||||
ser.close()
|
|
||||||
print("Connexion fermée.")
|
|
||||||
|
|
||||||
return parsed_data
|
|
||||||
|
|
||||||
except serial.SerialException as e:
|
|
||||||
print(f"Erreur de connexion série : {e}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Erreur générale : {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Exemple d'utilisation
|
|
||||||
if __name__ == "__main__":
|
|
||||||
port = port # Remplacez par votre port série (ex: /dev/ttyAMA0 sur Raspberry Pi)
|
|
||||||
baudrate = 9600 # Débit en bauds (à vérifier dans la documentation)
|
|
||||||
parity = serial.PARITY_NONE # Parité (NONE, EVEN, ODD)
|
|
||||||
stopbits = serial.STOPBITS_ONE # Bits de stop (ONE, TWO)
|
|
||||||
databits = serial.EIGHTBITS # Bits de données (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
|
|
||||||
|
|
||||||
data = read_cairsens(port, baudrate, parity, stopbits, databits)
|
|
||||||
if data:
|
|
||||||
print(f"\nRésultat final : {data}")
|
|
||||||
@@ -44,9 +44,9 @@ def read_cairsens(port, baudrate=9600, parity=serial.PARITY_NONE, stopbits=seria
|
|||||||
|
|
||||||
|
|
||||||
# Lire les données reçues
|
# Lire les données reçues
|
||||||
data = ser.read_until(b'\n') # Lire jusqu'à la fin de ligne ou un autre délimiteur
|
#data = ser.read_until(b'\n') # Lire jusqu'à la fin de ligne ou un autre délimiteur
|
||||||
data = ser.readline()
|
data = ser.readline()
|
||||||
print(f"Données reçues brutes : {data}")
|
#print(f"Données reçues brutes : {data}")
|
||||||
#print(f"Données reçues (utf-8) : {data.decode('utf-8').strip()}")
|
#print(f"Données reçues (utf-8) : {data.decode('utf-8').strip()}")
|
||||||
|
|
||||||
# Extraire le 20ème octet
|
# Extraire le 20ème octet
|
||||||
|
|||||||
0
envea/old/read_value_loop.py → envea/read_value_loop.py
Normal file → Executable file
0
envea/old/read_value_loop.py → envea/read_value_loop.py
Normal file → Executable file
0
envea/old/read_value_loop_json.py → envea/read_value_loop_json.py
Normal file → Executable file
0
envea/old/read_value_loop_json.py → envea/read_value_loop_json.py
Normal file → Executable file
@@ -8,9 +8,7 @@
|
|||||||
Gather data from envea Sensors and store them to the SQlite table
|
Gather data from envea Sensors and store them to the SQlite table
|
||||||
Use the RTC time for the timestamp
|
Use the RTC time for the timestamp
|
||||||
|
|
||||||
This script is run by a service nebuleair-envea-data.service
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py
|
||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py -d
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -20,59 +18,41 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import sys
|
|
||||||
|
|
||||||
# Set DEBUG to True to enable debug prints, False to disable
|
|
||||||
DEBUG = False # Change this to False to disable debug output
|
|
||||||
|
|
||||||
# You can also control debug via command line argument
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] in ['--debug', '-d']:
|
|
||||||
DEBUG = True
|
|
||||||
elif len(sys.argv) > 1 and sys.argv[1] in ['--quiet', '-q']:
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
def debug_print(message):
|
|
||||||
"""Print debug messages only if DEBUG is True"""
|
|
||||||
if DEBUG:
|
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
print(f"[{timestamp}] {message}")
|
|
||||||
|
|
||||||
debug_print("=== ENVEA Sensor Reader Started ===")
|
|
||||||
|
|
||||||
# Connect to the SQLite database
|
# Connect to the SQLite database
|
||||||
try:
|
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
cursor = conn.cursor()
|
||||||
cursor = conn.cursor()
|
|
||||||
except Exception as e:
|
|
||||||
debug_print(f"✗ Failed to connect to database: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# GET RTC TIME from SQlite
|
#GET RTC TIME from SQlite
|
||||||
try:
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
row = cursor.fetchone() # Get the first (and only) row
|
||||||
row = cursor.fetchone() # Get the first (and only) row
|
rtc_time_str = row[1] # '2025-02-07 12:30:45'
|
||||||
rtc_time_str = row[1] # '2025-02-07 12:30:45'
|
|
||||||
except Exception as e:
|
|
||||||
debug_print(f"✗ Failed to get RTC time: {e}")
|
|
||||||
rtc_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
debug_print(f" Using system time instead: {rtc_time_str}")
|
|
||||||
|
|
||||||
# Fetch connected ENVEA sondes from SQLite config table
|
# Function to load config data
|
||||||
try:
|
def load_config(config_file):
|
||||||
cursor.execute("SELECT port, name, coefficient FROM envea_sondes_table WHERE connected = 1")
|
try:
|
||||||
connected_envea_sondes = cursor.fetchall() # List of tuples (port, name, coefficient)
|
with open(config_file, 'r') as file:
|
||||||
debug_print(f"✓ Found {len(connected_envea_sondes)} connected ENVEA sensors")
|
config_data = json.load(file)
|
||||||
for port, name, coefficient in connected_envea_sondes:
|
return config_data
|
||||||
debug_print(f" - {name}: port={port}, coefficient={coefficient}")
|
except Exception as e:
|
||||||
except Exception as e:
|
print(f"Error loading config file: {e}")
|
||||||
debug_print(f"✗ Failed to fetch connected sensors: {e}")
|
return {}
|
||||||
connected_envea_sondes = []
|
|
||||||
|
|
||||||
|
# Define the config file path
|
||||||
|
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||||
|
|
||||||
|
# Load the configuration data
|
||||||
|
config = load_config(config_file)
|
||||||
|
|
||||||
|
# Initialize sensors and serial connections
|
||||||
|
envea_sondes = config.get('envea_sondes', [])
|
||||||
|
connected_envea_sondes = [sonde for sonde in envea_sondes if sonde.get('connected', False)]
|
||||||
serial_connections = {}
|
serial_connections = {}
|
||||||
|
|
||||||
if connected_envea_sondes:
|
if connected_envea_sondes:
|
||||||
debug_print("\n--- Opening Serial Connections ---")
|
for device in connected_envea_sondes:
|
||||||
for port, name, coefficient in connected_envea_sondes:
|
port = device.get('port', 'Unknown')
|
||||||
|
name = device.get('name', 'Unknown')
|
||||||
try:
|
try:
|
||||||
serial_connections[name] = serial.Serial(
|
serial_connections[name] = serial.Serial(
|
||||||
port=f'/dev/{port}',
|
port=f'/dev/{port}',
|
||||||
@@ -82,101 +62,60 @@ if connected_envea_sondes:
|
|||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
timeout=1
|
timeout=1
|
||||||
)
|
)
|
||||||
debug_print(f"✓ Opened serial port for {name} on /dev/{port}")
|
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
debug_print(f"✗ Error opening serial port for {name}: {e}")
|
print(f"Error opening serial port for {name}: {e}")
|
||||||
else:
|
|
||||||
debug_print("! No connected ENVEA sensors found in configuration")
|
|
||||||
|
|
||||||
# Initialize sensor data variables
|
global data_h2s, data_no2, data_o3
|
||||||
global data_h2s, data_no2, data_o3, data_co, data_nh3, data_so2
|
|
||||||
data_h2s = 0
|
data_h2s = 0
|
||||||
data_no2 = 0
|
data_no2 = 0
|
||||||
data_o3 = 0
|
data_o3 = 0
|
||||||
data_co = 0
|
data_co = 0
|
||||||
data_nh3 = 0
|
data_nh3 = 0
|
||||||
data_so2 = 0
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if connected_envea_sondes:
|
if connected_envea_sondes:
|
||||||
debug_print("\n--- Reading Sensor Data ---")
|
for device in connected_envea_sondes:
|
||||||
for port, name, coefficient in connected_envea_sondes:
|
name = device.get('name', 'Unknown')
|
||||||
|
coefficient = device.get('coefficient', 1)
|
||||||
if name in serial_connections:
|
if name in serial_connections:
|
||||||
serial_connection = serial_connections[name]
|
serial_connection = serial_connections[name]
|
||||||
try:
|
try:
|
||||||
debug_print(f"Reading from {name}...")
|
serial_connection.write(
|
||||||
|
b"\xFF\x02\x13\x30\x01\x02\x03\x04\x05\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x12\xAF\x88\x03"
|
||||||
# Send command to sensor
|
)
|
||||||
command = b"\xFF\x02\x13\x30\x01\x02\x03\x04\x05\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x12\xAF\x88\x03"
|
|
||||||
serial_connection.write(command)
|
|
||||||
debug_print(f" → Sent command: {command.hex()}")
|
|
||||||
|
|
||||||
# Read response
|
|
||||||
data_envea = serial_connection.readline()
|
data_envea = serial_connection.readline()
|
||||||
debug_print(f" ← Received {len(data_envea)} bytes: {data_envea.hex()}")
|
|
||||||
|
|
||||||
if len(data_envea) >= 20:
|
if len(data_envea) >= 20:
|
||||||
byte_20 = data_envea[19]
|
byte_20 = data_envea[19] * coefficient
|
||||||
raw_value = byte_20
|
|
||||||
calculated_value = byte_20 * coefficient
|
|
||||||
debug_print(f" → Byte 20 value: {raw_value} (0x{raw_value:02X})")
|
|
||||||
debug_print(f" → Calculated value: {raw_value} × {coefficient} = {calculated_value}")
|
|
||||||
|
|
||||||
if name == "h2s":
|
if name == "h2s":
|
||||||
data_h2s = calculated_value
|
data_h2s = byte_20
|
||||||
elif name == "no2":
|
elif name == "no2":
|
||||||
data_no2 = calculated_value
|
data_no2 = byte_20
|
||||||
elif name == "o3":
|
elif name == "o3":
|
||||||
data_o3 = calculated_value
|
data_o3 = byte_20
|
||||||
elif name == "co":
|
|
||||||
data_co = calculated_value
|
|
||||||
elif name == "nh3":
|
|
||||||
data_nh3 = calculated_value
|
|
||||||
elif name == "so2":
|
|
||||||
data_so2 = calculated_value
|
|
||||||
|
|
||||||
debug_print(f" ✓ {name.upper()} = {calculated_value}")
|
|
||||||
else:
|
|
||||||
debug_print(f" ✗ Response too short (expected ≥20 bytes)")
|
|
||||||
|
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
debug_print(f"✗ Error communicating with {name}: {e}")
|
print(f"Error communicating with {name}: {e}")
|
||||||
else:
|
|
||||||
debug_print(f"! No serial connection available for {name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_print(f"\n✗ An error occurred while gathering data: {e}")
|
print("An error occurred while gathering data:", e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
# Display all collected data
|
|
||||||
debug_print(f"\n--- Collected Sensor Data ---")
|
|
||||||
debug_print(f"H2S: {data_h2s} ppb")
|
|
||||||
debug_print(f"NO2: {data_no2} ppb")
|
|
||||||
debug_print(f"O3: {data_o3} ppb")
|
|
||||||
debug_print(f"CO: {data_co} ppb")
|
|
||||||
debug_print(f"NH3: {data_nh3} ppb")
|
|
||||||
debug_print(f"SO2: {data_so2} ppb")
|
|
||||||
|
|
||||||
# Save to sqlite database
|
#print(f" H2S: {data_h2s}, NO2: {data_no2}, O3: {data_o3}")
|
||||||
|
|
||||||
|
#save to sqlite database
|
||||||
try:
|
try:
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO data_envea (timestamp, h2s, no2, o3, co, nh3, so2) VALUES (?,?,?,?,?,?,?)'''
|
INSERT INTO data_envea (timestamp,h2s, no2, o3, co, nh3) VALUES (?,?,?,?,?,?)'''
|
||||||
, (rtc_time_str, data_h2s, data_no2, data_o3, data_co, data_nh3, data_so2))
|
, (rtc_time_str,data_h2s,data_no2,data_o3,data_co,data_nh3 ))
|
||||||
|
|
||||||
# Commit and close the connection
|
# Commit and close the connection
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
debug_print(f"✗ Database error: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
# Close serial connections
|
#print("Sensor data saved successfully!")
|
||||||
if serial_connections:
|
|
||||||
for name, connection in serial_connections.items():
|
except Exception as e:
|
||||||
try:
|
print(f"Database error: {e}")
|
||||||
connection.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
debug_print("\n=== ENVEA Sensor Reader Finished ===\n")
|
|
||||||
|
|
||||||
|
|||||||
1381
html/admin.html
1381
html/admin.html
File diff suppressed because it is too large
Load Diff
344
html/config.html
Normal file
344
html/config.html
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>NebuleAir - Config Editor</title>
|
||||||
|
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
#sidebar a.nav-link {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#sidebar a.nav-link:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
#sidebar a.nav-link svg {
|
||||||
|
margin-right: 8px; /* Add spacing between icons and text */
|
||||||
|
}
|
||||||
|
#sidebar {
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.offcanvas-backdrop {
|
||||||
|
z-index: 1040;
|
||||||
|
}
|
||||||
|
#jsonEditor {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.password-popup {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 2000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.password-container {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Topbar -->
|
||||||
|
<span id="topbar"></span>
|
||||||
|
|
||||||
|
<!-- Sidebar Offcanvas for Mobile -->
|
||||||
|
<div class="offcanvas offcanvas-start text-white bg-dark" tabindex="-1" id="sidebarOffcanvas" aria-labelledby="sidebarOffcanvasLabel">
|
||||||
|
<div class="offcanvas-header">
|
||||||
|
<h5 class="offcanvas-title" id="sidebarOffcanvasLabel">NebuleAir</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body" id="sidebar_mobile">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-5">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Side bar -->
|
||||||
|
<aside class="col-md-2 col-lg-1 d-none d-md-block vh-100 position-fixed bg-dark text-white" id="sidebar">
|
||||||
|
</aside>
|
||||||
|
<!-- Main content -->
|
||||||
|
<main class="col-md-10 ms-sm-auto col-lg-11 offset-md-2 offset-lg-1 px-md-4">
|
||||||
|
<h1 class="mt-4">Configuration Editor</h1>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Warning:</strong> Editing the configuration file directly can affect system functionality.
|
||||||
|
Make changes carefully and ensure valid JSON format.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">config.json</h5>
|
||||||
|
<div>
|
||||||
|
<button id="editBtn" class="btn btn-primary me-2">Edit</button>
|
||||||
|
<button id="saveBtn" class="btn btn-success me-2" disabled>Save</button>
|
||||||
|
<button id="cancelBtn" class="btn btn-secondary" disabled>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="jsonEditor" class="mb-3" readonly></div>
|
||||||
|
<div id="errorMsg" class="alert alert-danger" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password Modal -->
|
||||||
|
<div class="password-popup" id="passwordModal">
|
||||||
|
<div class="password-container">
|
||||||
|
<h5>Authentication Required</h5>
|
||||||
|
<p>Please enter the admin password to edit configuration:</p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="password" class="form-control" id="adminPassword" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 d-flex justify-content-between">
|
||||||
|
<button class="btn btn-secondary" id="cancelPasswordBtn">Cancel</button>
|
||||||
|
<button class="btn btn-primary" id="submitPasswordBtn">Submit</button>
|
||||||
|
</div>
|
||||||
|
<div id="passwordError" class="text-danger mt-2" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JAVASCRIPT -->
|
||||||
|
|
||||||
|
<!-- Link Ajax locally -->
|
||||||
|
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
|
||||||
|
<!-- Link Bootstrap JS and Popper.js locally -->
|
||||||
|
<script src="assets/js/bootstrap.bundle.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const elementsToLoad = [
|
||||||
|
{ id: 'topbar', file: 'topbar.html' },
|
||||||
|
{ id: 'sidebar', file: 'sidebar.html' },
|
||||||
|
{ id: 'sidebar_mobile', file: 'sidebar.html' }
|
||||||
|
];
|
||||||
|
|
||||||
|
elementsToLoad.forEach(({ id, file }) => {
|
||||||
|
fetch(file)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.innerHTML = data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error(`Error loading ${file}:`, error));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add admin password (should be changed to something more secure)
|
||||||
|
const ADMIN_PASSWORD = "nebuleair123";
|
||||||
|
|
||||||
|
// Global variables for editor
|
||||||
|
let originalConfig = '';
|
||||||
|
let jsonEditor;
|
||||||
|
let editBtn;
|
||||||
|
let saveBtn;
|
||||||
|
let cancelBtn;
|
||||||
|
let passwordModal;
|
||||||
|
let adminPassword;
|
||||||
|
let submitPasswordBtn;
|
||||||
|
let cancelPasswordBtn;
|
||||||
|
let passwordError;
|
||||||
|
let errorMsg;
|
||||||
|
|
||||||
|
// Initialize DOM references after document is loaded
|
||||||
|
function initializeElements() {
|
||||||
|
jsonEditor = document.getElementById('jsonEditor');
|
||||||
|
editBtn = document.getElementById('editBtn');
|
||||||
|
saveBtn = document.getElementById('saveBtn');
|
||||||
|
cancelBtn = document.getElementById('cancelBtn');
|
||||||
|
passwordModal = document.getElementById('passwordModal');
|
||||||
|
adminPassword = document.getElementById('adminPassword');
|
||||||
|
submitPasswordBtn = document.getElementById('submitPasswordBtn');
|
||||||
|
cancelPasswordBtn = document.getElementById('cancelPasswordBtn');
|
||||||
|
passwordError = document.getElementById('passwordError');
|
||||||
|
errorMsg = document.getElementById('errorMsg');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load config file
|
||||||
|
function loadConfigFile() {
|
||||||
|
fetch('../config.json')
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
originalConfig = data;
|
||||||
|
// Format JSON for display with proper indentation
|
||||||
|
try {
|
||||||
|
const jsonObj = JSON.parse(data);
|
||||||
|
const formattedJSON = JSON.stringify(jsonObj, null, 2);
|
||||||
|
jsonEditor.textContent = formattedJSON;
|
||||||
|
} catch (e) {
|
||||||
|
jsonEditor.textContent = data;
|
||||||
|
console.error("Error parsing JSON:", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading config.json:', error);
|
||||||
|
jsonEditor.textContent = "Error loading configuration file.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize DOM elements
|
||||||
|
initializeElements();
|
||||||
|
|
||||||
|
// Load config file
|
||||||
|
loadConfigFile();
|
||||||
|
|
||||||
|
// Edit button
|
||||||
|
editBtn.addEventListener('click', function() {
|
||||||
|
passwordModal.style.display = 'flex';
|
||||||
|
adminPassword.value = ''; // Clear password field
|
||||||
|
passwordError.style.display = 'none';
|
||||||
|
adminPassword.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Password submit button
|
||||||
|
submitPasswordBtn.addEventListener('click', function() {
|
||||||
|
if (adminPassword.value === ADMIN_PASSWORD) {
|
||||||
|
passwordModal.style.display = 'none';
|
||||||
|
enableEditing();
|
||||||
|
} else {
|
||||||
|
passwordError.textContent = 'Invalid password';
|
||||||
|
passwordError.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enter key for password
|
||||||
|
adminPassword.addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
submitPasswordBtn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel password button
|
||||||
|
cancelPasswordBtn.addEventListener('click', function() {
|
||||||
|
passwordModal.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save button
|
||||||
|
saveBtn.addEventListener('click', function() {
|
||||||
|
saveConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel button
|
||||||
|
cancelBtn.addEventListener('click', function() {
|
||||||
|
cancelEditing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable editing mode
|
||||||
|
function enableEditing() {
|
||||||
|
jsonEditor.setAttribute('contenteditable', 'true');
|
||||||
|
jsonEditor.focus();
|
||||||
|
jsonEditor.classList.add('border-primary');
|
||||||
|
editBtn.disabled = true;
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
cancelBtn.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel editing
|
||||||
|
function cancelEditing() {
|
||||||
|
jsonEditor.setAttribute('contenteditable', 'false');
|
||||||
|
jsonEditor.classList.remove('border-primary');
|
||||||
|
jsonEditor.textContent = originalConfig;
|
||||||
|
// Reformat JSON
|
||||||
|
try {
|
||||||
|
const jsonObj = JSON.parse(originalConfig);
|
||||||
|
const formattedJSON = JSON.stringify(jsonObj, null, 2);
|
||||||
|
jsonEditor.textContent = formattedJSON;
|
||||||
|
} catch (e) {
|
||||||
|
jsonEditor.textContent = originalConfig;
|
||||||
|
}
|
||||||
|
editBtn.disabled = false;
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
cancelBtn.disabled = true;
|
||||||
|
errorMsg.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save config
|
||||||
|
function saveConfig() {
|
||||||
|
const newConfig = jsonEditor.textContent;
|
||||||
|
|
||||||
|
// Validate JSON
|
||||||
|
try {
|
||||||
|
JSON.parse(newConfig);
|
||||||
|
|
||||||
|
// Send to server
|
||||||
|
$.ajax({
|
||||||
|
url: 'launcher.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
type: 'save_config',
|
||||||
|
config: newConfig
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
originalConfig = newConfig;
|
||||||
|
jsonEditor.setAttribute('contenteditable', 'false');
|
||||||
|
jsonEditor.classList.remove('border-primary');
|
||||||
|
editBtn.disabled = false;
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
cancelBtn.disabled = true;
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
errorMsg.textContent = 'Configuration saved successfully!';
|
||||||
|
errorMsg.classList.remove('alert-danger');
|
||||||
|
errorMsg.classList.add('alert-success');
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
|
||||||
|
// Hide success message after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMsg.style.display = 'none';
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
errorMsg.textContent = 'Error saving configuration: ' + response.message;
|
||||||
|
errorMsg.classList.remove('alert-success');
|
||||||
|
errorMsg.classList.add('alert-danger');
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
errorMsg.textContent = 'Error saving configuration: ' + error;
|
||||||
|
errorMsg.classList.remove('alert-success');
|
||||||
|
errorMsg.classList.add('alert-danger');
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
errorMsg.textContent = 'Invalid JSON format: ' + e.message;
|
||||||
|
errorMsg.classList.remove('alert-success');
|
||||||
|
errorMsg.classList.add('alert-danger');
|
||||||
|
errorMsg.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -71,11 +71,6 @@
|
|||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button>
|
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button>
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button>
|
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button>
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button>
|
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button>
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NOISE',getSelectedLimit(),false)">Sonde bruit</button>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)">Sonde Vent</button>
|
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_MPPT',getSelectedLimit(),false)">Batterie</button>
|
|
||||||
|
|
||||||
<button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button>
|
<button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -99,9 +94,6 @@
|
|||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,true, getStartDate(), getEndDate())">Mesures Temp/Hum</button>
|
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,true, getStartDate(), getEndDate())">Mesures Temp/Hum</button>
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',10,true, getStartDate(), getEndDate())">Mesures PM (5 canaux)</button>
|
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',10,true, getStartDate(), getEndDate())">Mesures PM (5 canaux)</button>
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())">Sonde Cairsens</button>
|
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())">Sonde Cairsens</button>
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NOISE',10,true, getStartDate(), getEndDate())">Sonde Bruit</button>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_mppt',10,true, getStartDate(), getEndDate())">Batterie</button>
|
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,55 +147,42 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
|
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
||||||
|
.then(response => response.json()) // Parse response as JSON
|
||||||
|
.then(data => {
|
||||||
|
console.log("Getting config file (onload)");
|
||||||
|
//get device ID
|
||||||
|
const deviceID = data.deviceID.trim().toUpperCase();
|
||||||
|
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
|
||||||
|
|
||||||
|
//get device Name
|
||||||
|
const deviceName = data.deviceName;
|
||||||
|
|
||||||
|
const elements = document.querySelectorAll('.sideBar_sensorName');
|
||||||
|
elements.forEach((element) => {
|
||||||
|
element.innerText = deviceName;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
//NEW way to get data from SQLITE
|
//get local RTC
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
url: 'launcher.php?type=RTC_time',
|
||||||
dataType:'json',
|
dataType: 'text', // Specify that you expect a JSON response
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
success: function(response) {
|
||||||
success: function(response) {
|
console.log("Local RTC: " + response);
|
||||||
console.log("Getting SQLite config table:");
|
const RTC_Element = document.getElementById("RTC_time");
|
||||||
console.log(response);
|
RTC_Element.textContent = response;
|
||||||
|
},
|
||||||
//get device Name (for the side bar)
|
error: function(xhr, status, error) {
|
||||||
const deviceName = response.deviceName;
|
console.error('AJAX request failed:', status, error);
|
||||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
}
|
||||||
elements.forEach((element) => {
|
|
||||||
element.innerText = deviceName;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//device name html page title
|
})
|
||||||
if (response.deviceName) {
|
.catch(error => console.error('Error loading config.json:', error));
|
||||||
document.title = response.deviceName;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
}); //end ajax
|
|
||||||
|
|
||||||
|
|
||||||
//get local RTC
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=RTC_time',
|
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Local RTC: " + response);
|
|
||||||
const RTC_Element = document.getElementById("RTC_time");
|
|
||||||
RTC_Element.textContent = response;
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
}); //end AJAX
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -220,6 +199,7 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
|
|
||||||
console.log(url);
|
console.log(url);
|
||||||
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
dataType: 'text', // Specify that you expect a JSON response
|
||||||
@@ -280,32 +260,8 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
tableHTML += `
|
tableHTML += `
|
||||||
<th>Timestamp</th>
|
<th>Timestamp</th>
|
||||||
`;
|
`;
|
||||||
}else if (table === "data_WIND") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>speed (km/h)</th>
|
|
||||||
<th>Direction (V)</th>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_MPPT") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Battery Voltage</th>
|
|
||||||
<th>Battery Current</th>
|
|
||||||
<th> solar_voltage</th>
|
|
||||||
<th> solar_power</th>
|
|
||||||
<th> charger_status</th>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "data_NOISE") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Curent LEQ</th>
|
|
||||||
<th>DB_A_value</th>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tableHTML += `</tr></thead><tbody>`;
|
tableHTML += `</tr></thead><tbody>`;
|
||||||
|
|
||||||
// Loop through rows and create table rows
|
// Loop through rows and create table rows
|
||||||
@@ -354,28 +310,6 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
tableHTML += `
|
tableHTML += `
|
||||||
<td>${columns[1]}</td>
|
<td>${columns[1]}</td>
|
||||||
`;
|
`;
|
||||||
}else if (table === "data_WIND") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_MPPT") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${columns[3]}</td>
|
|
||||||
<td>${columns[4]}</td>
|
|
||||||
<td>${columns[5]}</td>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_NOISE") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tableHTML += "</tr>";
|
tableHTML += "</tr>";
|
||||||
|
|||||||
@@ -135,35 +135,6 @@
|
|||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
|
|
||||||
//NEW way to get data from SQLITE
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
|
||||||
dataType:'json',
|
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Getting SQLite config table:");
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
//get device Name (for the side bar)
|
|
||||||
const deviceName = response.deviceName;
|
|
||||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
|
||||||
elements.forEach((element) => {
|
|
||||||
element.innerText = deviceName;
|
|
||||||
});
|
|
||||||
|
|
||||||
//device name html page title
|
|
||||||
if (response.deviceName) {
|
|
||||||
document.title = response.deviceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
}); //end ajax
|
|
||||||
|
|
||||||
/* OLD way of getting config data
|
|
||||||
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
||||||
.then(response => response.json()) // Parse response as JSON
|
.then(response => response.json()) // Parse response as JSON
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@@ -180,12 +151,7 @@ window.onload = function() {
|
|||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
element.innerText = deviceName;
|
element.innerText = deviceName;
|
||||||
});
|
});
|
||||||
|
|
||||||
//end fetch config
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error loading config.json:', error));
|
|
||||||
//end windows on load
|
|
||||||
*/
|
|
||||||
//get local RTC
|
//get local RTC
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=RTC_time',
|
url: 'launcher.php?type=RTC_time',
|
||||||
@@ -455,6 +421,10 @@ window.onload = function() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//end fetch config
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading config.json:', error));
|
||||||
|
//end windows on load
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
//Prevents caching → Adds headers to ensure fresh response.
|
// ✅ Prevents caching → Adds headers to ensure fresh response.
|
||||||
// to test this page http://192.168.1.127/html/launcher.php?type=get_config_scripts_sqlite
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
|
||||||
header("Pragma: no-cache");
|
header("Pragma: no-cache");
|
||||||
|
|
||||||
$database_path = "/var/www/nebuleair_pro_4g/sqlite/sensors.db";
|
// Get request type from GET or POST parameters
|
||||||
|
$type = isset($_GET['type']) ? $_GET['type'] : (isset($_POST['type']) ? $_POST['type'] : '');
|
||||||
|
|
||||||
$type=$_GET['type'];
|
|
||||||
|
|
||||||
if ($type == "get_npm_sqlite_data") {
|
if ($type == "get_npm_sqlite_data") {
|
||||||
|
$database_path = "/var/www/nebuleair_pro_4g/sqlite/sensors.db";
|
||||||
//echo "Getting data from sqlite database";
|
//echo "Getting data from sqlite database";
|
||||||
try {
|
try {
|
||||||
$db = new PDO("sqlite:$database_path");
|
$db = new PDO("sqlite:$database_path");
|
||||||
@@ -28,281 +26,8 @@ if ($type == "get_npm_sqlite_data") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
|
||||||
//GETING data from config_table (SQLite DB)
|
|
||||||
if ($type == "get_config_sqlite") {
|
|
||||||
try {
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// Get all main configuration entries
|
|
||||||
$config_query = $db->query("SELECT key, value, type FROM config_table");
|
|
||||||
$config_data = $config_query->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Convert data types according to their 'type' field
|
|
||||||
$result = [];
|
|
||||||
foreach ($config_data as $item) {
|
|
||||||
$key = $item['key'];
|
|
||||||
$value = $item['value'];
|
|
||||||
$type = $item['type'];
|
|
||||||
|
|
||||||
// Convert value based on its type
|
|
||||||
switch ($type) {
|
|
||||||
case 'bool':
|
|
||||||
$parsed_value = ($value == '1' || $value == 'true') ? true : false;
|
|
||||||
break;
|
|
||||||
case 'int':
|
|
||||||
$parsed_value = intval($value);
|
|
||||||
break;
|
|
||||||
case 'float':
|
|
||||||
$parsed_value = floatval($value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$parsed_value = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result[$key] = $parsed_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return JSON response
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode($result, JSON_PRETTY_PRINT);
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
// Return error as JSON
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
|
||||||
//GETING data from config_scrips_table (SQLite DB)
|
|
||||||
if ($type == "get_config_scripts_sqlite") {
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// Get all main configuration entries
|
|
||||||
$config_query = $db->query("SELECT * FROM config_scripts_table");
|
|
||||||
$config_data = $config_query->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Convert data types according to their 'type' field
|
|
||||||
$result = [];
|
|
||||||
foreach ($config_data as $item) {
|
|
||||||
$script_path = $item['script_path'];
|
|
||||||
$enabled = $item['enabled'];
|
|
||||||
|
|
||||||
// Convert the enabled field to a proper boolean
|
|
||||||
$result[$script_path] = ($enabled == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return JSON response
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
// Return error as JSON
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
|
||||||
//GETING data from envea_sondes_table (SQLite DB)
|
|
||||||
if ($type == "get_envea_sondes_table_sqlite") {
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// Get all entries from envea_sondes_table
|
|
||||||
$query = $db->query("SELECT id, connected, port, name, coefficient FROM envea_sondes_table");
|
|
||||||
$data = $query->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Convert data types appropriately
|
|
||||||
$result = [];
|
|
||||||
foreach ($data as $item) {
|
|
||||||
// Create object for each sonde with proper data types
|
|
||||||
$sonde = [
|
|
||||||
'id' => (int)$item['id'],
|
|
||||||
'connected' => $item['connected'] == 1, // Convert to boolean
|
|
||||||
'port' => $item['port'],
|
|
||||||
'name' => $item['name'],
|
|
||||||
'coefficient' => (float)$item['coefficient'] // Convert to float
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add to results array
|
|
||||||
$result[] = $sonde;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Return JSON response
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
// Return error as JSON
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//UPDATING the config_table from SQLite DB
|
|
||||||
if ($type == "update_config_sqlite") {
|
|
||||||
$param = $_GET['param'] ?? null;
|
|
||||||
$value = $_GET['value'] ?? null;
|
|
||||||
|
|
||||||
if ($param === null || $value === null) {
|
|
||||||
echo json_encode(["error" => "Missing parameter or value"]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// First, check if parameter exists and get its type
|
|
||||||
$checkStmt = $db->prepare("SELECT type FROM config_table WHERE key = :param");
|
|
||||||
$checkStmt->bindParam(':param', $param);
|
|
||||||
$checkStmt->execute();
|
|
||||||
$result = $checkStmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
// Parameter exists, determine type and update
|
|
||||||
$type = $result['type'];
|
|
||||||
|
|
||||||
// Convert value according to type if needed
|
|
||||||
$convertedValue = $value;
|
|
||||||
if ($type == "bool") {
|
|
||||||
// Convert various boolean representations to 0/1
|
|
||||||
$convertedValue = (filter_var($value, FILTER_VALIDATE_BOOLEAN)) ? "1" : "0";
|
|
||||||
} elseif ($type == "int") {
|
|
||||||
$convertedValue = (string)intval($value);
|
|
||||||
} elseif ($type == "float") {
|
|
||||||
$convertedValue = (string)floatval($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the value
|
|
||||||
$updateStmt = $db->prepare("UPDATE config_table SET value = :value WHERE key = :param");
|
|
||||||
$updateStmt->bindParam(':value', $convertedValue);
|
|
||||||
$updateStmt->bindParam(':param', $param);
|
|
||||||
$updateStmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"message" => "Configuration updated successfully",
|
|
||||||
"param" => $param,
|
|
||||||
"value" => $convertedValue,
|
|
||||||
"type" => $type
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
echo json_encode([
|
|
||||||
"error" => "Parameter not found in configuration",
|
|
||||||
"param" => $param
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(["error" => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//UPDATING the envea_sondes_table table from SQLite DB
|
|
||||||
if ($type == "update_sonde") {
|
|
||||||
$id = $_GET['id'] ?? null;
|
|
||||||
$field = $_GET['field'] ?? null;
|
|
||||||
$value = $_GET['value'] ?? null;
|
|
||||||
|
|
||||||
// Validate parameters
|
|
||||||
if ($id === null || $field === null || $value === null) {
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"error" => "Missing required parameters (id, field, or value)"
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate field name (whitelist approach for security)
|
|
||||||
$allowed_fields = ['connected', 'port', 'name', 'coefficient'];
|
|
||||||
if (!in_array($field, $allowed_fields)) {
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"error" => "Invalid field name: " . $field
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Connect to the database
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// Check if the sonde exists
|
|
||||||
$checkStmt = $db->prepare("SELECT id FROM envea_sondes_table WHERE id = :id");
|
|
||||||
$checkStmt->bindParam(':id', $id, PDO::PARAM_INT);
|
|
||||||
$checkStmt->execute();
|
|
||||||
|
|
||||||
if (!$checkStmt->fetch()) {
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"error" => "Sonde with ID $id not found"
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process value based on field type
|
|
||||||
if ($field == 'connected') {
|
|
||||||
// Convert to integer (0 or 1)
|
|
||||||
$processedValue = filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
|
|
||||||
$paramType = PDO::PARAM_INT;
|
|
||||||
} else if ($field == 'coefficient') {
|
|
||||||
// Convert to float
|
|
||||||
$processedValue = floatval($value);
|
|
||||||
$paramType = PDO::PARAM_STR; // SQLite doesn't have PARAM_FLOAT
|
|
||||||
} else {
|
|
||||||
// For text fields (port, name)
|
|
||||||
$processedValue = $value;
|
|
||||||
$paramType = PDO::PARAM_STR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the sonde record
|
|
||||||
$updateStmt = $db->prepare("UPDATE envea_sondes_table SET $field = :value WHERE id = :id");
|
|
||||||
$updateStmt->bindParam(':value', $processedValue, $paramType);
|
|
||||||
$updateStmt->bindParam(':id', $id, PDO::PARAM_INT);
|
|
||||||
$updateStmt->execute();
|
|
||||||
|
|
||||||
// Return success response
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"message" => "Sonde $id updated successfully",
|
|
||||||
"field" => $field,
|
|
||||||
"value" => $processedValue
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
// Return error as JSON
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"error" => "Database error: " . $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//update the config (old JSON updating)
|
|
||||||
if ($type == "update_config") {
|
if ($type == "update_config") {
|
||||||
echo "updating.... ";
|
echo "updating....";
|
||||||
$param=$_GET['param'];
|
$param=$_GET['param'];
|
||||||
$value=$_GET['value'];
|
$value=$_GET['value'];
|
||||||
$configFile = '../config.json';
|
$configFile = '../config.json';
|
||||||
@@ -349,20 +74,6 @@ if ($type == "git_pull") {
|
|||||||
echo $output;
|
echo $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type == "update_firmware") {
|
|
||||||
// Execute the comprehensive update script
|
|
||||||
$command = 'sudo /var/www/nebuleair_pro_4g/update_firmware.sh 2>&1';
|
|
||||||
$output = shell_exec($command);
|
|
||||||
|
|
||||||
// Return the output as JSON for better web display
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'output' => $output,
|
|
||||||
'timestamp' => date('Y-m-d H:i:s')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($type == "set_RTC_withNTP") {
|
if ($type == "set_RTC_withNTP") {
|
||||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
||||||
$output = shell_exec($command);
|
$output = shell_exec($command);
|
||||||
@@ -391,50 +102,9 @@ if ($type == "set_RTC_withBrowser") {
|
|||||||
|
|
||||||
|
|
||||||
if ($type == "clear_loopLogs") {
|
if ($type == "clear_loopLogs") {
|
||||||
$response = array();
|
$command = 'truncate -s 0 /var/www/nebuleair_pro_4g/logs/loop.log';
|
||||||
|
$output = shell_exec($command);
|
||||||
try {
|
echo $output;
|
||||||
$logPath = '/var/www/nebuleair_pro_4g/logs/master.log';
|
|
||||||
|
|
||||||
// Check if file exists and is writable
|
|
||||||
if (!file_exists($logPath)) {
|
|
||||||
throw new Exception("Log file does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_writable($logPath)) {
|
|
||||||
throw new Exception("Log file is not writable");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the command
|
|
||||||
$command = 'truncate -s 0 ' . escapeshellarg($logPath);
|
|
||||||
$output = shell_exec($command . ' 2>&1');
|
|
||||||
|
|
||||||
// Check if there was any error output
|
|
||||||
if (!empty($output)) {
|
|
||||||
throw new Exception("Command error: " . $output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success response
|
|
||||||
$response = array(
|
|
||||||
'status' => 'success',
|
|
||||||
'message' => 'Logs cleared successfully',
|
|
||||||
'timestamp' => date('Y-m-d H:i:s')
|
|
||||||
);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// Error response
|
|
||||||
$response = array(
|
|
||||||
'status' => 'error',
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
'timestamp' => date('Y-m-d H:i:s')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set content type to JSON
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
// Return the JSON response
|
|
||||||
echo json_encode($response);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type == "database_size") {
|
if ($type == "database_size") {
|
||||||
@@ -599,58 +269,38 @@ if ($type == "sara_connectNetwork") {
|
|||||||
$port=$_GET['port'];
|
$port=$_GET['port'];
|
||||||
$timeout=$_GET['timeout'];
|
$timeout=$_GET['timeout'];
|
||||||
$networkID=$_GET['networkID'];
|
$networkID=$_GET['networkID'];
|
||||||
$param="SARA_R4_neworkID";
|
|
||||||
|
|
||||||
//echo "updating SARA_R4_networkID in config file";
|
|
||||||
|
|
||||||
//OLD way to store data (JSON file)
|
|
||||||
|
|
||||||
|
echo "updating SARA_R4_networkID in config file";
|
||||||
// Convert `networkID` to an integer (or float if needed)
|
// Convert `networkID` to an integer (or float if needed)
|
||||||
//$networkID = is_numeric($networkID) ? (strpos($networkID, '.') !== false ? (float)$networkID : (int)$networkID) : 0;
|
$networkID = is_numeric($networkID) ? (strpos($networkID, '.') !== false ? (float)$networkID : (int)$networkID) : 0;
|
||||||
#save to config.json
|
#save to config.json
|
||||||
//$configFile = '/var/www/nebuleair_pro_4g/config.json';
|
$configFile = '/var/www/nebuleair_pro_4g/config.json';
|
||||||
// Read the JSON file
|
// Read the JSON file
|
||||||
//$jsonData = file_get_contents($configFile);
|
$jsonData = file_get_contents($configFile);
|
||||||
// Decode JSON data into an associative array
|
// Decode JSON data into an associative array
|
||||||
//$config = json_decode($jsonData, true);
|
$config = json_decode($jsonData, true);
|
||||||
// Check if decoding was successful
|
// Check if decoding was successful
|
||||||
//if ($config === null) {
|
if ($config === null) {
|
||||||
// die("Error: Could not decode JSON file.");
|
die("Error: Could not decode JSON file.");
|
||||||
//}
|
}
|
||||||
// Update the value of SARA_R4_networkID
|
// Update the value of SARA_R4_networkID
|
||||||
//$config['SARA_R4_neworkID'] = $networkID; // Replace 42 with the desired value
|
$config['SARA_R4_neworkID'] = $networkID; // Replace 42 with the desired value
|
||||||
// Encode the array back to JSON with pretty printing
|
// Encode the array back to JSON with pretty printing
|
||||||
//$newJsonData = json_encode($config, JSON_PRETTY_PRINT);
|
$newJsonData = json_encode($config, JSON_PRETTY_PRINT);
|
||||||
// Check if encoding was successful
|
// Check if encoding was successful
|
||||||
//if ($newJsonData === false) {
|
if ($newJsonData === false) {
|
||||||
// die("Error: Could not encode JSON data.");
|
die("Error: Could not encode JSON data.");
|
||||||
//}
|
|
||||||
|
|
||||||
// Write the updated JSON back to the file
|
|
||||||
//if (file_put_contents($configFile, $newJsonData) === false) {
|
|
||||||
// die("Error: Could not write to JSON file.");
|
|
||||||
//}
|
|
||||||
|
|
||||||
//echo "SARA_R4_networkID updated successfully.";
|
|
||||||
|
|
||||||
//NEW way to store data -> use SQLITE
|
|
||||||
try {
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
$updateStmt = $db->prepare("UPDATE config_table SET value = :value WHERE key = :param");
|
|
||||||
$updateStmt->bindParam(':value', $networkID);
|
|
||||||
$updateStmt->bindParam(':param', $param);
|
|
||||||
$updateStmt->execute();
|
|
||||||
echo "SARA_R4_networkID updated successfully.";
|
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
// Return error as JSON
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//echo "connecting to network... please wait...";
|
// Write the updated JSON back to the file
|
||||||
|
if (file_put_contents($configFile, $newJsonData) === false) {
|
||||||
|
die("Error: Could not write to JSON file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "SARA_R4_networkID updated successfully.";
|
||||||
|
|
||||||
|
|
||||||
|
echo "connecting to network... please wait...";
|
||||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout;
|
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout;
|
||||||
$output = shell_exec($command);
|
$output = shell_exec($command);
|
||||||
echo $output;
|
echo $output;
|
||||||
@@ -836,14 +486,57 @@ if ($type == "wifi_scan_old") {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Save config.json with password protection
|
||||||
_____ _ _
|
if ($type == "save_config") {
|
||||||
|_ _|__ _ __ _ __ ___ (_)_ __ __ _| |
|
// Verify that the request is using POST method
|
||||||
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | |
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
| | __/ | | | | | | | | | | | (_| | |
|
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
|
||||||
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_|
|
exit;
|
||||||
|
}
|
||||||
*/
|
|
||||||
|
// Get the config content from POST data
|
||||||
|
$config = isset($_POST['config']) ? $_POST['config'] : '';
|
||||||
|
|
||||||
|
if (empty($config)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No configuration data provided']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the content is valid JSON
|
||||||
|
$decodedConfig = json_decode($config);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid JSON format: ' . json_last_error_msg()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path to the configuration file
|
||||||
|
$configFile = '/var/www/nebuleair_pro_4g/config.json';
|
||||||
|
|
||||||
|
// Create a backup of the current config
|
||||||
|
$backupFile = '/var/www/nebuleair_pro_4g/config.json.backup-' . date('Y-m-d-H-i-s');
|
||||||
|
if (file_exists($configFile)) {
|
||||||
|
copy($configFile, $backupFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the updated configuration to the file
|
||||||
|
$result = file_put_contents($configFile, $config);
|
||||||
|
|
||||||
|
if ($result === false) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to write configuration file. Check permissions.'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Configuration saved successfully',
|
||||||
|
'bytes_written' => $result
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Execute shell command with security restrictions
|
// Execute shell command with security restrictions
|
||||||
if ($type == "execute_command") {
|
if ($type == "execute_command") {
|
||||||
@@ -865,7 +558,7 @@ if ($type == "execute_command") {
|
|||||||
$allowedCommands = [
|
$allowedCommands = [
|
||||||
'ls', 'cat', 'cd', 'pwd', 'df', 'free', 'ifconfig', 'ip', 'ps', 'date', 'uptime',
|
'ls', 'cat', 'cd', 'pwd', 'df', 'free', 'ifconfig', 'ip', 'ps', 'date', 'uptime',
|
||||||
'systemctl status', 'whoami', 'hostname', 'uname', 'grep', 'tail', 'head', 'find',
|
'systemctl status', 'whoami', 'hostname', 'uname', 'grep', 'tail', 'head', 'find',
|
||||||
'less', 'more', 'du', 'echo', 'git'
|
'less', 'more', 'du', 'echo'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check if command is allowed
|
// Check if command is allowed
|
||||||
@@ -904,7 +597,7 @@ if ($type == "execute_command") {
|
|||||||
$prohibitedPatterns = [
|
$prohibitedPatterns = [
|
||||||
'sudo rm', ';', '&&', '||', '|', '>', '>>', '&',
|
'sudo rm', ';', '&&', '||', '|', '>', '>>', '&',
|
||||||
'wget', 'curl', 'nc', 'ssh', 'scp', 'ftp', 'telnet',
|
'wget', 'curl', 'nc', 'ssh', 'scp', 'ftp', 'telnet',
|
||||||
'iptables', 'passwd', 'chown', 'chmod', 'mkfs', ' dd ',
|
'iptables', 'passwd', 'chown', 'chmod', 'mkfs', 'dd',
|
||||||
'mount', 'umount', 'kill', 'killall'
|
'mount', 'umount', 'kill', 'killall'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -982,316 +675,3 @@ if ($type == "execute_command") {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
____ _ ____ _ __ __ _
|
|
||||||
/ ___| _ _ ___| |_ ___ _ __ ___ | _ \ / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
|
|
||||||
\___ \| | | / __| __/ _ \ '_ ` _ \| | | | \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
|
|
||||||
___) | |_| \__ \ || __/ | | | | | |_| | ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
|
|
||||||
|____/ \__, |___/\__\___|_| |_| |_|____/ |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
|
|
||||||
|___/ |___/
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get systemd services status
|
|
||||||
if ($type == "get_systemd_services") {
|
|
||||||
try {
|
|
||||||
// List of NebuleAir services to monitor with descriptions and frequencies
|
|
||||||
$services = [
|
|
||||||
'nebuleair-npm-data.timer' => [
|
|
||||||
'description' => 'Collects particulate matter data from NextPM sensor',
|
|
||||||
'frequency' => 'Every 10 seconds'
|
|
||||||
],
|
|
||||||
'nebuleair-envea-data.timer' => [
|
|
||||||
'description' => 'Reads environmental data from Envea sensors',
|
|
||||||
'frequency' => 'Every 10 seconds'
|
|
||||||
],
|
|
||||||
'nebuleair-sara-data.timer' => [
|
|
||||||
'description' => 'Transmits collected data via 4G cellular modem',
|
|
||||||
'frequency' => 'Every 60 seconds'
|
|
||||||
],
|
|
||||||
'nebuleair-bme280-data.timer' => [
|
|
||||||
'description' => 'Monitors temperature and humidity (BME280)',
|
|
||||||
'frequency' => 'Every 2 minutes'
|
|
||||||
],
|
|
||||||
'nebuleair-mppt-data.timer' => [
|
|
||||||
'description' => 'Tracks solar panel and battery status',
|
|
||||||
'frequency' => 'Every 2 minutes'
|
|
||||||
],
|
|
||||||
'nebuleair-noise-data.timer' => [
|
|
||||||
'description' => 'Get Data from noise sensor',
|
|
||||||
'frequency' => 'Every minute'
|
|
||||||
],
|
|
||||||
'nebuleair-db-cleanup-data.timer' => [
|
|
||||||
'description' => 'Cleans up old data from database',
|
|
||||||
'frequency' => 'Daily'
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
$serviceStatus = [];
|
|
||||||
|
|
||||||
foreach ($services as $service => $serviceInfo) {
|
|
||||||
// Get service active status
|
|
||||||
$activeCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null";
|
|
||||||
$activeStatus = trim(shell_exec($activeCmd));
|
|
||||||
$isActive = ($activeStatus === 'active');
|
|
||||||
|
|
||||||
// Get service enabled status
|
|
||||||
$enabledCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null";
|
|
||||||
$enabledStatus = trim(shell_exec($enabledCmd));
|
|
||||||
$isEnabled = ($enabledStatus === 'enabled');
|
|
||||||
|
|
||||||
// Clean up service name for display
|
|
||||||
$displayName = str_replace(['.timer', 'nebuleair-', '-data'], '', $service);
|
|
||||||
$displayName = ucfirst(str_replace('-', ' ', $displayName));
|
|
||||||
|
|
||||||
$serviceStatus[] = [
|
|
||||||
'name' => $service,
|
|
||||||
'display_name' => $displayName,
|
|
||||||
'description' => $serviceInfo['description'],
|
|
||||||
'frequency' => $serviceInfo['frequency'],
|
|
||||||
'active' => $isActive,
|
|
||||||
'enabled' => $isEnabled
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'services' => $serviceStatus
|
|
||||||
], JSON_PRETTY_PRINT);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart a systemd service
|
|
||||||
if ($type == "restart_systemd_service") {
|
|
||||||
$service = $_GET['service'] ?? null;
|
|
||||||
|
|
||||||
if (empty($service)) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'No service specified'
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate service name (security check)
|
|
||||||
$allowedServices = [
|
|
||||||
'nebuleair-npm-data.timer',
|
|
||||||
'nebuleair-envea-data.timer',
|
|
||||||
'nebuleair-sara-data.timer',
|
|
||||||
'nebuleair-bme280-data.timer',
|
|
||||||
'nebuleair-mppt-data.timer',
|
|
||||||
'nebuleair-db-cleanup-data.timer'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!in_array($service, $allowedServices)) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Invalid service name'
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Restart the service
|
|
||||||
$command = "sudo systemctl restart " . escapeshellarg($service) . " 2>&1";
|
|
||||||
$output = shell_exec($command);
|
|
||||||
|
|
||||||
// Check if restart was successful
|
|
||||||
$statusCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null";
|
|
||||||
$status = trim(shell_exec($statusCmd));
|
|
||||||
|
|
||||||
if ($status === 'active' || empty($output)) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'message' => "Service $service restarted successfully"
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => "Failed to restart service: $output"
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable/disable a systemd service
|
|
||||||
if ($type == "toggle_systemd_service") {
|
|
||||||
$service = $_GET['service'] ?? null;
|
|
||||||
$enable = $_GET['enable'] ?? null;
|
|
||||||
|
|
||||||
if (empty($service) || $enable === null) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Missing service name or enable parameter'
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate service name (security check)
|
|
||||||
$allowedServices = [
|
|
||||||
'nebuleair-npm-data.timer',
|
|
||||||
'nebuleair-envea-data.timer',
|
|
||||||
'nebuleair-sara-data.timer',
|
|
||||||
'nebuleair-bme280-data.timer',
|
|
||||||
'nebuleair-mppt-data.timer',
|
|
||||||
'nebuleair-db-cleanup-data.timer'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!in_array($service, $allowedServices)) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Invalid service name'
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$enable = filter_var($enable, FILTER_VALIDATE_BOOLEAN);
|
|
||||||
$action = $enable ? 'enable' : 'disable';
|
|
||||||
|
|
||||||
// Enable/disable the service
|
|
||||||
$command = "sudo systemctl $action " . escapeshellarg($service) . " 2>&1";
|
|
||||||
$output = shell_exec($command);
|
|
||||||
|
|
||||||
// If disabling, also stop the service
|
|
||||||
if (!$enable) {
|
|
||||||
$stopCommand = "sudo systemctl stop " . escapeshellarg($service) . " 2>&1";
|
|
||||||
$stopOutput = shell_exec($stopCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If enabling, also start the service
|
|
||||||
if ($enable) {
|
|
||||||
$startCommand = "sudo systemctl start " . escapeshellarg($service) . " 2>&1";
|
|
||||||
$startOutput = shell_exec($startCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the operation was successful
|
|
||||||
$statusCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null";
|
|
||||||
$status = trim(shell_exec($statusCmd));
|
|
||||||
|
|
||||||
$expectedStatus = $enable ? 'enabled' : 'disabled';
|
|
||||||
|
|
||||||
if ($status === $expectedStatus) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'message' => "Service $service " . ($enable ? 'enabled and started' : 'disabled and stopped') . " successfully"
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => "Failed to $action service: $output"
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
_____ ____ _ _ _
|
|
||||||
| ____|_ ____ _____ __ _ | _ \ ___| |_ ___ ___| |_(_) ___ _ __
|
|
||||||
| _| | '_ \ \ / / _ \/ _` | | | | |/ _ \ __/ _ \/ __| __| |/ _ \| '_ \
|
|
||||||
| |___| | | \ V / __/ (_| | | |_| | __/ || __/ (__| |_| | (_) | | | |
|
|
||||||
|_____|_| |_|\_/ \___|\__,_| |____/ \___|\__\___|\___|\__|_|\___/|_| |_|
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Detect Envea devices on specified port
|
|
||||||
if ($type == "detect_envea_device") {
|
|
||||||
$port = $_GET['port'] ?? null;
|
|
||||||
|
|
||||||
if (empty($port)) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'No port specified'
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate port name (security check)
|
|
||||||
$allowedPorts = ['ttyAMA2', 'ttyAMA3', 'ttyAMA4', 'ttyAMA5'];
|
|
||||||
|
|
||||||
if (!in_array($port, $allowedPorts)) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Invalid port name'
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Execute the envea detection script
|
|
||||||
$command = "sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_ref.py " . escapeshellarg($port) . " 2>&1";
|
|
||||||
$output = shell_exec($command);
|
|
||||||
|
|
||||||
// Check if we got any meaningful output
|
|
||||||
$detected = false;
|
|
||||||
$device_info = '';
|
|
||||||
$raw_data = $output;
|
|
||||||
|
|
||||||
if (!empty($output)) {
|
|
||||||
// Look for indicators that a device is connected
|
|
||||||
if (strpos($output, 'Connexion ouverte') !== false) {
|
|
||||||
// Connection was successful
|
|
||||||
if (strpos($output, 'Données reçues brutes') !== false &&
|
|
||||||
strpos($output, 'b\'\'') === false) {
|
|
||||||
// We received actual data (not empty)
|
|
||||||
$detected = true;
|
|
||||||
$device_info = 'Envea CAIRSENS Device';
|
|
||||||
|
|
||||||
// Try to extract device type from ASCII data if available
|
|
||||||
if (preg_match('/Valeurs converties en ASCII : (.+)/', $output, $matches)) {
|
|
||||||
$ascii_data = trim($matches[1]);
|
|
||||||
if (!empty($ascii_data) && $ascii_data !== '........') {
|
|
||||||
$device_info = "Envea Device: " . $ascii_data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Connection successful but no data
|
|
||||||
$device_info = 'Port accessible but no Envea device detected';
|
|
||||||
}
|
|
||||||
} else if (strpos($output, 'Erreur de connexion série') !== false) {
|
|
||||||
// Serial connection error
|
|
||||||
$device_info = 'Serial connection error - port may be busy or not available';
|
|
||||||
} else {
|
|
||||||
// Other output
|
|
||||||
$device_info = 'Unexpected response from port';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No output at all
|
|
||||||
$device_info = 'No response from port';
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'port' => $port,
|
|
||||||
'detected' => $detected,
|
|
||||||
'device_info' => $device_info,
|
|
||||||
'data' => $raw_data,
|
|
||||||
'timestamp' => date('Y-m-d H:i:s')
|
|
||||||
], JSON_PRETTY_PRINT);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode([
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'Script execution failed: ' . $e->getMessage(),
|
|
||||||
'port' => $port
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
216
html/logs.html
216
html/logs.html
@@ -56,10 +56,7 @@
|
|||||||
<div class="col-lg-6 col-12">
|
<div class="col-lg-6 col-12">
|
||||||
<div class="card" style="height: 80vh;">
|
<div class="card" style="height: 80vh;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Sara logs
|
Master logs <button type="submit" class="btn btn-secondary btn-sm" onclick="clear_loopLogs()">Clear</button>
|
||||||
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-master-log">Refresh</button>
|
|
||||||
<button type="submit" class="btn btn-secondary btn-sm" onclick="clear_loopLogs()">Clear</button>
|
|
||||||
|
|
||||||
<span id="script_running"></span>
|
<span id="script_running"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body overflow-auto" id="card_loop_content">
|
<div class="card-body overflow-auto" id="card_loop_content">
|
||||||
@@ -72,7 +69,6 @@
|
|||||||
<div class="card" style="height: 80vh;">
|
<div class="card" style="height: 80vh;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Boot logs
|
Boot logs
|
||||||
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-boot-log">Refresh</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body overflow-auto" id="card_boot_content">
|
<div class="card-body overflow-auto" id="card_boot_content">
|
||||||
|
|
||||||
@@ -115,17 +111,65 @@
|
|||||||
const boot_card_content = document.getElementById('card_boot_content');
|
const boot_card_content = document.getElementById('card_boot_content');
|
||||||
|
|
||||||
//Getting Master logs
|
//Getting Master logs
|
||||||
console.log("Getting SARA logs");
|
console.log("Getting master logs");
|
||||||
displayLogFile('../logs/sara_service.log', loop_card_content, true, 1000);
|
|
||||||
|
|
||||||
console.log("Getting app/boot logs");
|
|
||||||
displayLogFile('../logs/app.log', boot_card_content, true, 1000);
|
|
||||||
|
|
||||||
// Setup master log with refresh button
|
fetch('../logs/master.log')
|
||||||
setupLogRefreshButton('refresh-master-log', '../logs/sara_service.log', 'card_loop_content', 3000);
|
.then((response) => {
|
||||||
|
console.log("OK");
|
||||||
// Setup boot log with refresh button
|
|
||||||
setupLogRefreshButton('refresh-boot-log', '../logs/app.log', 'card_boot_content', 300);
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch the log file.');
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
const lines = data.split('\n');
|
||||||
|
|
||||||
|
// Format log content
|
||||||
|
const formattedLog = lines
|
||||||
|
.map((line) => line.trim()) // Remove extra whitespace
|
||||||
|
.filter((line) => line) // Remove empty lines
|
||||||
|
.join('<br>'); // Join formatted lines with line breaks
|
||||||
|
|
||||||
|
loop_card_content.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${formattedLog}</pre>`;
|
||||||
|
loop_card_content.scrollTop = loop_card_content.scrollHeight; // Scroll to the bottom
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
loop_card_content.textContent = 'Error loading log file.';
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Getting app/boot logs");
|
||||||
|
|
||||||
|
//Getting App logs
|
||||||
|
fetch('../logs/app.log')
|
||||||
|
.then((response) => {
|
||||||
|
console.log("OK");
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch the log file.');
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
const lines = data.split('\n');
|
||||||
|
|
||||||
|
// Format log content
|
||||||
|
const formattedLog = lines
|
||||||
|
.map((line) => line.trim()) // Remove extra whitespace
|
||||||
|
.filter((line) => line) // Remove empty lines
|
||||||
|
.join('<br>'); // Join formatted lines with line breaks
|
||||||
|
|
||||||
|
boot_card_content.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${formattedLog}</pre>`;
|
||||||
|
boot_card_content.scrollTop = loop_card_content.scrollHeight; // Scroll to the bottom
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
boot_card_content.textContent = 'Error loading log file.';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -135,121 +179,41 @@ window.onload = function() {
|
|||||||
getModem_busy_status();
|
getModem_busy_status();
|
||||||
setInterval(getModem_busy_status, 2000);
|
setInterval(getModem_busy_status, 2000);
|
||||||
|
|
||||||
//NEW way to get config (SQLite)
|
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
||||||
$.ajax({
|
.then(response => response.json()) // Parse response as JSON
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
.then(data => {
|
||||||
dataType:'json',
|
console.log("Getting config file (onload)");
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
//get device ID
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
const deviceID = data.deviceID.trim().toUpperCase();
|
||||||
success: function(response) {
|
// document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
|
||||||
console.log("Getting SQLite config table:");
|
//get device Name
|
||||||
console.log(response);
|
const deviceName = data.deviceName;
|
||||||
|
|
||||||
//device name_side bar
|
const elements = document.querySelectorAll('.sideBar_sensorName');
|
||||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
elements.forEach((element) => {
|
||||||
elements.forEach((element) => {
|
element.innerText = deviceName;
|
||||||
element.innerText = response.deviceName;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
//device name html page title
|
|
||||||
if (response.deviceName) {
|
|
||||||
document.title = response.deviceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end AJAX
|
|
||||||
|
|
||||||
|
|
||||||
//get local RTC
|
//get local RTC
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=RTC_time',
|
url: 'launcher.php?type=RTC_time',
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
dataType: 'text', // Specify that you expect a JSON response
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
console.log("Local RTC: " + response);
|
console.log("Local RTC: " + response);
|
||||||
const RTC_Element = document.getElementById("RTC_time");
|
const RTC_Element = document.getElementById("RTC_time");
|
||||||
RTC_Element.textContent = response;
|
RTC_Element.textContent = response;
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
console.error('AJAX request failed:', status, error);
|
console.error('AJAX request failed:', status, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading config.json:', error));
|
||||||
|
}
|
||||||
|
|
||||||
}//end onload
|
|
||||||
|
|
||||||
function displayLogFile(logFilePath, containerElement, scrollToBottom = true, maxLines = 0) {
|
|
||||||
// Show loading indicator
|
|
||||||
containerElement.innerHTML = '<div class="text-center"><i>Loading log file...</i></div>';
|
|
||||||
|
|
||||||
return fetch(logFilePath)
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch the log file: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
return response.text();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
// Split the log into lines
|
|
||||||
let lines = data.split('\n');
|
|
||||||
|
|
||||||
// Apply max lines limit if specified
|
|
||||||
if (maxLines > 0 && lines.length > maxLines) {
|
|
||||||
lines = lines.slice(-maxLines); // Get only the last N lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format log content
|
|
||||||
const formattedLog = lines
|
|
||||||
.map((line) => line.trim()) // Remove extra whitespace
|
|
||||||
.filter((line) => line) // Remove empty lines
|
|
||||||
.join('<br>'); // Join formatted lines with line breaks
|
|
||||||
|
|
||||||
// Display the formatted log
|
|
||||||
containerElement.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${formattedLog}</pre>`;
|
|
||||||
|
|
||||||
// Scroll to bottom if requested
|
|
||||||
if (scrollToBottom) {
|
|
||||||
containerElement.scrollTop = containerElement.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedLog; // Return the formatted log in case the caller needs it
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(`Error loading log file ${logFilePath}:`, error);
|
|
||||||
containerElement.innerHTML = `<div class="text-danger">Error loading log file: ${error.message}</div>`;
|
|
||||||
throw error; // Re-throw the error for the caller to handle if needed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up a refresh button for a log file
|
|
||||||
* @param {string} buttonId - ID of the button element
|
|
||||||
* @param {string} logFilePath - Path to the log file
|
|
||||||
* @param {string} containerId - ID of the container to display the log in
|
|
||||||
* @param {number} maxLines - Maximum number of lines to display (0 for all)
|
|
||||||
*/
|
|
||||||
function setupLogRefreshButton(buttonId, logFilePath, containerId, maxLines = 0) {
|
|
||||||
console.log("Refreshing logs");
|
|
||||||
|
|
||||||
const button = document.getElementById(buttonId);
|
|
||||||
const container = document.getElementById(containerId);
|
|
||||||
|
|
||||||
if (!button || !container) {
|
|
||||||
console.error('Button or container element not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial load
|
|
||||||
displayLogFile(logFilePath, container, true, maxLines);
|
|
||||||
|
|
||||||
// Set up button click handler
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
displayLogFile(logFilePath, container, true, maxLines);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear_loopLogs(){
|
function clear_loopLogs(){
|
||||||
console.log("Clearing loop logs");
|
console.log("Clearing loop logs");
|
||||||
|
|||||||
281
html/saraR4.html
281
html/saraR4.html
@@ -59,12 +59,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span id="modem_status_message"></span>
|
<span id="modem_status_message"></span>
|
||||||
<!--
|
|
||||||
<h3>
|
<h3>
|
||||||
Status
|
Status
|
||||||
<span id="modem-status" class="badge">Loading...</span>
|
<span id="modem-status" class="badge">Loading...</span>
|
||||||
</h3>
|
</h3>
|
||||||
-->
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
|
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
@@ -72,7 +71,7 @@
|
|||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">General information. </p>
|
<p class="card-text">General information. </p>
|
||||||
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'ATI', 1)">Get Data</button>
|
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'ATI', 2)">Get Data</button>
|
||||||
<div id="loading_ttyAMA2_ATI" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
<div id="loading_ttyAMA2_ATI" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
<div id="response_ttyAMA2_ATI"></div>
|
<div id="response_ttyAMA2_ATI"></div>
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@
|
|||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">SIM card information.</p>
|
<p class="card-text">SIM card information.</p>
|
||||||
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CCID?', 1)">Get Data</button>
|
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CCID?', 2)">Get Data</button>
|
||||||
<div id="loading_ttyAMA2_AT_CCID_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
<div id="loading_ttyAMA2_AT_CCID_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
<div id="response_ttyAMA2_AT_CCID_"></div>
|
<div id="response_ttyAMA2_AT_CCID_"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +109,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">Signal strength </p>
|
<p class="card-text">Signal strength </p>
|
||||||
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CSQ', 1)">Get Data</button>
|
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CSQ', 2)">Get Data</button>
|
||||||
<div id="loading_ttyAMA2_AT_CSQ" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
<div id="loading_ttyAMA2_AT_CSQ" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
<div id="response_ttyAMA2_AT_CSQ"></div>
|
<div id="response_ttyAMA2_AT_CSQ"></div>
|
||||||
</table>
|
</table>
|
||||||
@@ -122,7 +121,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">Modem Reset </p>
|
<p class="card-text">Modem Reset </p>
|
||||||
<button class="btn btn-danger" onclick="getData_saraR4('ttyAMA2', 'AT+CFUN=15', 1)">Reset</button>
|
<button class="btn btn-danger" onclick="getData_saraR4('ttyAMA2', 'AT+CFUN=15', 2)">Reset</button>
|
||||||
<div id="loading_ttyAMA2_AT_CFUN_15" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
<div id="loading_ttyAMA2_AT_CFUN_15" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
<div id="response_ttyAMA2_AT_CFUN_15"></div>
|
<div id="response_ttyAMA2_AT_CFUN_15"></div>
|
||||||
</table>
|
</table>
|
||||||
@@ -305,20 +304,7 @@
|
|||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- toast -->
|
|
||||||
|
|
||||||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
|
||||||
<div id="liveToast" class="toast align-items-center text-bg-primary border-1" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="toast-body">
|
|
||||||
Hello, world! This is a toast message.
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -331,118 +317,38 @@
|
|||||||
<script src="assets/js/bootstrap.bundle.js"></script>
|
<script src="assets/js/bootstrap.bundle.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const elementsToLoad = [
|
const elementsToLoad = [
|
||||||
{ id: 'topbar', file: 'topbar.html' },
|
{ id: 'topbar', file: 'topbar.html' },
|
||||||
{ id: 'sidebar', file: 'sidebar.html' },
|
{ id: 'sidebar', file: 'sidebar.html' },
|
||||||
{ id: 'sidebar_mobile', file: 'sidebar.html' }
|
{ id: 'sidebar_mobile', file: 'sidebar.html' }
|
||||||
];
|
];
|
||||||
|
|
||||||
elementsToLoad.forEach(({ id, file }) => {
|
elementsToLoad.forEach(({ id, file }) => {
|
||||||
fetch(file)
|
fetch(file)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.innerHTML = data;
|
element.innerHTML = data;
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => console.error(`Error loading ${file}:`, error));
|
|
||||||
});
|
|
||||||
|
|
||||||
//OLD way to retreive data from JSON
|
|
||||||
/*
|
|
||||||
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
|
||||||
.then(response => response.json()) // Parse response as JSON
|
|
||||||
.then(data => {
|
|
||||||
console.log("Getting config file (onload)");
|
|
||||||
//modem config mode
|
|
||||||
const check_modem_configMode = document.getElementById("check_modem_configMode");
|
|
||||||
check_modem_configMode.checked = data.modem_config_mode;
|
|
||||||
console.log("Modem configuration: " + data.modem_config_mode);
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
//NEW way to get data from SQLITE
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
|
||||||
dataType:'json',
|
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Getting SQLite config table:");
|
|
||||||
console.log(response);
|
|
||||||
//modem_version
|
|
||||||
const modem_version_html = document.getElementById("modem_version");
|
|
||||||
modem_version_html.innerText = response.modem_version;
|
|
||||||
|
|
||||||
// Set checkbox state based on the response data
|
|
||||||
const check_modem_configMode = document.getElementById("check_modem_configMode");
|
|
||||||
if (check_modem_configMode) {
|
|
||||||
check_modem_configMode.checked = response.modem_config_mode;
|
|
||||||
console.log("Modem configuration: " + response.modem_config_mode);
|
|
||||||
} else {
|
|
||||||
console.error("Checkbox element not found");
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error(`Error loading ${file}:`, error));
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
||||||
|
.then(response => response.json()) // Parse response as JSON
|
||||||
|
.then(data => {
|
||||||
|
console.log("Getting config file (onload)");
|
||||||
|
//modem config mode
|
||||||
|
const check_modem_configMode = document.getElementById("check_modem_configMode");
|
||||||
|
check_modem_configMode.checked = data.modem_config_mode;
|
||||||
|
console.log("Modem configuration: " + data.modem_config_mode);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onload = function() {
|
|
||||||
getModem_busy_status();
|
|
||||||
setInterval(getModem_busy_status, 1000);
|
|
||||||
|
|
||||||
//NEW way to get config (SQLite)
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
|
||||||
dataType:'json',
|
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Getting SQLite config table:");
|
|
||||||
console.log(response);
|
|
||||||
//device name_side bar
|
|
||||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
|
||||||
elements.forEach((element) => {
|
|
||||||
element.innerText = response.deviceName;
|
|
||||||
});
|
|
||||||
//device name html page title
|
|
||||||
if (response.deviceName) {
|
|
||||||
document.title = response.deviceName;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end AJAX
|
|
||||||
|
|
||||||
|
|
||||||
//get local RTC
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=RTC_time',
|
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Local RTC: " + response);
|
|
||||||
const RTC_Element = document.getElementById("RTC_time");
|
|
||||||
RTC_Element.textContent = response;
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getData_saraR4(port, command, timeout){
|
function getData_saraR4(port, command, timeout){
|
||||||
console.log("Data from SaraR4");
|
console.log("Data from SaraR4");
|
||||||
console.log("Port: " + port );
|
console.log("Port: " + port );
|
||||||
@@ -524,10 +430,8 @@ function getData_saraR4(port, command, timeout){
|
|||||||
} else{
|
} else{
|
||||||
// si c'est une commande AT normale
|
// si c'est une commande AT normale
|
||||||
// Replace newline characters with <br> tags
|
// Replace newline characters with <br> tags
|
||||||
const formattedResponse = response.replace(/\n/g, "<br>")
|
const formattedResponse = response.replace(/\n/g, "<br>");
|
||||||
.replace(/\b(OK)\b/g, '<span style="color: green; font-weight: bold;">$1</span>');;
|
|
||||||
$("#response_"+port+"_"+safeCommand).html(formattedResponse);
|
$("#response_"+port+"_"+safeCommand).html(formattedResponse);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
@@ -796,75 +700,84 @@ function getModem_busy_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function update_modem_configMode(param, checked){
|
function update_modem_configMode(param, checked){
|
||||||
//change ('modem_config_mode', '0', 'bool') inside SQLITE db
|
|
||||||
// response type: {"success":true,"message":"Configuration updated successfully","param":"modem_config_mode","value":"0","type":"bool"}
|
|
||||||
const toastLiveExample = document.getElementById('liveToast')
|
|
||||||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
|
||||||
|
|
||||||
console.log("updating modem config mode to :" + checked);
|
console.log("updating modem config mode to :" + checked);
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=update_config_sqlite¶m='+param+'&value='+checked,
|
url: 'launcher.php?type=update_config¶m='+param+'&value='+checked,
|
||||||
dataType: 'json', // Specify that you expect a JSON response
|
dataType: 'text', // Specify that you expect a JSON response
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
cache: false, // Prevent AJAX from caching
|
cache: false, // Prevent AJAX from caching
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
|
console.log(response);
|
||||||
console.log("AJAX success:");
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
// Format the response nicely
|
|
||||||
let formattedMessage = '';
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
// Success message
|
|
||||||
toastLiveExample.classList.remove('text-bg-danger');
|
|
||||||
toastLiveExample.classList.add('text-bg-success');
|
|
||||||
|
|
||||||
formattedMessage = `
|
|
||||||
<strong>Success!</strong><br>
|
|
||||||
Parameter: ${response.param || param}<br>
|
|
||||||
Value: ${response.value || checked}<br>
|
|
||||||
${response.message || ''}
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
// Error message
|
|
||||||
toastLiveExample.classList.remove('text-bg-success');
|
|
||||||
toastLiveExample.classList.add('text-bg-danger');
|
|
||||||
|
|
||||||
formattedMessage = `
|
|
||||||
<strong>Error!</strong><br>
|
|
||||||
${response.error || 'Unknown error'}<br>
|
|
||||||
Parameter: ${response.param || param}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the toast body with formatted content
|
|
||||||
toastBody.innerHTML = formattedMessage;
|
|
||||||
// Show the toast
|
|
||||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
|
|
||||||
toastBootstrap.show()
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
console.error('AJAX request failed:', status, error);
|
console.error('AJAX request failed:', status, error);
|
||||||
// Update toast with error message
|
|
||||||
toastBody.textContent = 'Error: ' + error;
|
|
||||||
|
|
||||||
// Set toast to danger color
|
|
||||||
toastLiveExample.classList.remove('text-bg-success');
|
|
||||||
toastLiveExample.classList.add('text-bg-danger');
|
|
||||||
|
|
||||||
// Show the toast for errors too
|
|
||||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
|
||||||
toastBootstrap.show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
getModem_busy_status();
|
||||||
|
setInterval(getModem_busy_status, 1000);
|
||||||
|
|
||||||
|
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
||||||
|
.then(response => response.json()) // Parse response as JSON
|
||||||
|
.then(data => {
|
||||||
|
//get device ID
|
||||||
|
const deviceID = data.deviceID.trim().toUpperCase();
|
||||||
|
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
|
||||||
|
//get device Name
|
||||||
|
const deviceName = data.deviceName;
|
||||||
|
|
||||||
|
//get modem version
|
||||||
|
const modem_version = data.modem_version;
|
||||||
|
const modem_version_html = document.getElementById("modem_version");
|
||||||
|
modem_version_html.textContent = modem_version;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const elements = document.querySelectorAll('.sideBar_sensorName');
|
||||||
|
elements.forEach((element) => {
|
||||||
|
element.innerText = deviceName;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//get SARA_R4 connection status
|
||||||
|
const SARA_statusElement = document.getElementById("modem-status");
|
||||||
|
console.log("SARA R4 is: " + data.SARA_R4_network_status);
|
||||||
|
|
||||||
|
if (data.SARA_R4_network_status === "connected") {
|
||||||
|
SARA_statusElement.textContent = "Connected";
|
||||||
|
SARA_statusElement.className = "badge text-bg-success";
|
||||||
|
} else if (data.SARA_R4_network_status === "disconnected") {
|
||||||
|
SARA_statusElement.textContent = "Disconnected";
|
||||||
|
SARA_statusElement.className = "badge text-bg-danger";
|
||||||
|
} else {
|
||||||
|
SARA_statusElement.textContent = "Unknown";
|
||||||
|
SARA_statusElement.className = "badge text-bg-secondary";
|
||||||
|
}
|
||||||
|
|
||||||
|
//get local RTC
|
||||||
|
$.ajax({
|
||||||
|
url: 'launcher.php?type=RTC_time',
|
||||||
|
dataType: 'text', // Specify that you expect a JSON response
|
||||||
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
|
success: function(response) {
|
||||||
|
console.log("Local RTC: " + response);
|
||||||
|
const RTC_Element = document.getElementById("RTC_time");
|
||||||
|
RTC_Element.textContent = response;
|
||||||
|
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('AJAX request failed:', status, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading config.json:', error));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -144,50 +144,40 @@ function getNPM_values(port){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getENVEA_values(port, name){
|
function getENVEA_values(port, name){
|
||||||
console.log("Data from Envea " + name + " (port " + port + "):");
|
console.log("Data from Envea "+ name+" (port "+port+"):");
|
||||||
$("#loading_envea" + name).show();
|
$("#loading_envea"+name).show();
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=envea&port=' + port + '&name=' + name,
|
url: 'launcher.php?type=envea&port='+port+'&name='+name,
|
||||||
dataType: 'json',
|
dataType: 'json', // Specify that you expect a JSON response
|
||||||
method: 'GET',
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
const tableBody = document.getElementById("data-table-body_envea" + name);
|
const tableBody = document.getElementById("data-table-body_envea"+name);
|
||||||
tableBody.innerHTML = "";
|
tableBody.innerHTML = "";
|
||||||
|
|
||||||
$("#loading_envea" + name).hide();
|
$("#loading_envea"+name).hide();
|
||||||
|
// Create an array of the desired keys
|
||||||
const keysToShow = [name];
|
// Create an array of the desired keys
|
||||||
keysToShow.forEach(key => {
|
const keysToShow = [name];
|
||||||
if (response !== undefined) {
|
// Add only the specified elements to the table
|
||||||
const value = response;
|
keysToShow.forEach(key => {
|
||||||
$("#data-table-body_envea" + name).append(`
|
if (response !== undefined) { // Check if the key exists in the response
|
||||||
<tr>
|
const value = response;
|
||||||
<td>${key}</td>
|
$("#data-table-body_envea"+name).append(`
|
||||||
<td>${value} ppb</td>
|
<tr>
|
||||||
</tr>
|
<td>${key}</td>
|
||||||
`);
|
<td>${value} ppb</td>
|
||||||
}
|
</tr>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('AJAX request failed:', status, error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
const tableBody = document.getElementById("data-table-body_envea" + name);
|
|
||||||
$("#loading_envea" + name).hide();
|
|
||||||
|
|
||||||
tableBody.innerHTML = `
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" class="text-danger">
|
|
||||||
❌ Error: unable to get data from sensor.<br>
|
|
||||||
<small>${status}: ${error}</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getNoise_values(){
|
function getNoise_values(){
|
||||||
console.log("Data from I2C Noise Sensor:");
|
console.log("Data from I2C Noise Sensor:");
|
||||||
@@ -271,190 +261,143 @@ function getBME280_values(){
|
|||||||
|
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
|
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
|
||||||
//NEW way to get config (SQLite)
|
.then(response => response.json()) // Parse response as JSON
|
||||||
$.ajax({
|
.then(data => {
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
//get device ID
|
||||||
dataType:'json',
|
const deviceID = data.deviceID.trim().toUpperCase();
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
//get device Name
|
||||||
success: function(response) {
|
const deviceName = data.deviceName;
|
||||||
console.log("Getting SQLite config table:");
|
|
||||||
console.log(response);
|
const elements = document.querySelectorAll('.sideBar_sensorName');
|
||||||
|
elements.forEach((element) => {
|
||||||
|
element.innerText = deviceName;
|
||||||
//device name_side bar
|
});
|
||||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
|
||||||
elements.forEach((element) => {
|
|
||||||
element.innerText = response.deviceName;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end AJAX
|
|
||||||
|
|
||||||
//getting config_scripts table
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=get_config_scripts_sqlite',
|
|
||||||
dataType:'json',
|
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Getting SQLite config scripts table:");
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
const container = document.getElementById('card-container'); // Conteneur des cartes
|
|
||||||
|
|
||||||
//creates NPM card
|
|
||||||
if (response["NPM/get_data_modbus_v3.py"]) {
|
|
||||||
const cardHTML = `
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
Port UART
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">NextPM</h5>
|
|
||||||
<p class="card-text">Capteur particules fines.</p>
|
|
||||||
<button class="btn btn-primary" onclick="getNPM_values('ttyAMA5')">Get Data</button>
|
|
||||||
<br>
|
|
||||||
<div id="loading_ttyAMA5" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
|
||||||
<table class="table table-striped-columns">
|
|
||||||
<tbody id="data-table-body_ttyAMA5"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
container.innerHTML += cardHTML; // Add the I2C card if condition is met
|
|
||||||
}
|
|
||||||
|
|
||||||
//creates i2c BME280 card
|
|
||||||
if (response["BME280/get_data_v2.py"]) {
|
|
||||||
const i2C_BME_HTML = `
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
Port I2C
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">BME280 Temp/Hum sensor</h5>
|
|
||||||
<p class="card-text">Capteur température et humidité sur le port I2C.</p>
|
|
||||||
<button class="btn btn-primary mb-1" onclick="getBME280_values()">Get Data</button>
|
|
||||||
<br>
|
|
||||||
<div id="loading_BME280" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
|
||||||
<table class="table table-striped-columns">
|
|
||||||
<tbody id="data-table-body_BME280"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
container.innerHTML += i2C_BME_HTML; // Add the I2C card if condition is met
|
|
||||||
}
|
|
||||||
|
|
||||||
//creates i2c sound card
|
|
||||||
if (response.i2C_sound) {
|
|
||||||
const i2C_HTML = `
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
Port I2C
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Decibel Meter</h5>
|
|
||||||
<p class="card-text">Capteur bruit sur le port I2C.</p>
|
|
||||||
<button class="btn btn-primary mb-1" onclick="getNoise_values()">Get Data</button>
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-success" onclick="startNoise()">Start recording</button>
|
|
||||||
<button class="btn btn-danger" onclick="stopNoise()">Stop recording</button>
|
|
||||||
<div id="loading_noise" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
|
||||||
<table class="table table-striped-columns">
|
|
||||||
<tbody id="data-table-body_noise"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
|
|
||||||
}
|
|
||||||
|
|
||||||
//Si on a des SONDES ENVEA connectée il faut faire un deuxième call dans la table envea_sondes_table
|
|
||||||
//creates ENVEA cards
|
|
||||||
if (response["envea/read_value_v2.py"]) {
|
|
||||||
console.log("Need to display ENVEA sondes");
|
|
||||||
//getting config_scripts table
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=get_envea_sondes_table_sqlite',
|
|
||||||
dataType:'json',
|
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(sondes) {
|
|
||||||
console.log("Getting SQLite envea sondes table:");
|
|
||||||
console.log(sondes);
|
|
||||||
const ENVEA_sensors = sondes.filter(sonde => sonde.connected); // Filter only connected sondes
|
|
||||||
|
|
||||||
ENVEA_sensors.forEach((sensor, index) => {
|
|
||||||
const port = sensor.port; // Port from the sensor object
|
|
||||||
const name = sensor.name; // Port from the sensor object
|
|
||||||
const coefficient = sensor.coefficient;
|
|
||||||
const cardHTML = `
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
Port UART ${port.replace('ttyAMA', '')}
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Sonde Envea ${name}</h5>
|
|
||||||
<p class="card-text">Capteur gas.</p>
|
|
||||||
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}')">Get Data</button>
|
|
||||||
<div id="loading_envea${name}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
|
||||||
<table class="table table-striped-columns">
|
|
||||||
<tbody id="data-table-body_envea${name}"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end AJAX envea Sondes
|
|
||||||
|
|
||||||
|
//get local RTC
|
||||||
|
$.ajax({
|
||||||
|
url: 'launcher.php?type=RTC_time',
|
||||||
|
dataType: 'text', // Specify that you expect a JSON response
|
||||||
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
|
success: function(response) {
|
||||||
|
console.log("Local RTC: " + response);
|
||||||
|
const RTC_Element = document.getElementById("RTC_time");
|
||||||
|
RTC_Element.textContent = response;
|
||||||
|
|
||||||
}//end if
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('AJAX request failed:', status, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
},
|
const container = document.getElementById('card-container'); // Conteneur des cartes
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
//creates NPM cards
|
||||||
}
|
const NPM_ports = data.NextPM_ports; // Récupère les ports
|
||||||
});//end AJAX (config_scripts)
|
NPM_ports.forEach((port, index) => {
|
||||||
|
const cardHTML = `
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Port UART ${port.replace('ttyAMA', '')}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">NextPM ${String.fromCharCode(65 + index)}</h5>
|
||||||
|
<p class="card-text">Capteur particules fines.</p>
|
||||||
|
<button class="btn btn-primary" onclick="getNPM_values('${port}')">Get Data</button>
|
||||||
|
<div id="loading_${port}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
|
<table class="table table-striped-columns">
|
||||||
|
<tbody id="data-table-body_${port}"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
|
||||||
|
});
|
||||||
|
|
||||||
//get local RTC
|
//creates ENVEA cards
|
||||||
$.ajax({
|
const ENVEA_sensors = data.envea_sondes.filter(sonde => sonde.connected); // Filter only connected sondes
|
||||||
url: 'launcher.php?type=RTC_time',
|
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Local RTC: " + response);
|
|
||||||
const RTC_Element = document.getElementById("RTC_time");
|
|
||||||
RTC_Element.textContent = response;
|
|
||||||
|
|
||||||
},
|
ENVEA_sensors.forEach((sensor, index) => {
|
||||||
error: function(xhr, status, error) {
|
const port = sensor.port; // Port from the sensor object
|
||||||
console.error('AJAX request failed:', status, error);
|
const name = sensor.name; // Port from the sensor object
|
||||||
}
|
const coefficient = sensor.coefficient;
|
||||||
});
|
const cardHTML = `
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Port UART ${port.replace('ttyAMA', '')}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Sonde Envea ${name}</h5>
|
||||||
|
<p class="card-text">Capteur gas.</p>
|
||||||
|
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}','${coefficient}')">Get Data</button>
|
||||||
|
<div id="loading_envea${name}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
|
<table class="table table-striped-columns">
|
||||||
|
<tbody id="data-table-body_envea${name}"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
|
||||||
|
});
|
||||||
|
|
||||||
} //end windows onload
|
//creates i2c BME280 card
|
||||||
|
if (data["BME280/get_data_v2.py"]) {
|
||||||
|
const i2C_BME_HTML = `
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Port I2C
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">BME280 Temp/Hum sensor</h5>
|
||||||
|
<p class="card-text">Capteur température et humidité sur le port I2C.</p>
|
||||||
|
<button class="btn btn-primary mb-1" onclick="getBME280_values()">Get Data</button>
|
||||||
|
<br>
|
||||||
|
<div id="loading_BME280" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
|
<table class="table table-striped-columns">
|
||||||
|
<tbody id="data-table-body_BME280"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
container.innerHTML += i2C_BME_HTML; // Add the I2C card if condition is met
|
||||||
|
}
|
||||||
|
|
||||||
|
//creates i2c sound card
|
||||||
|
if (data.i2C_sound) {
|
||||||
|
const i2C_HTML = `
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Port I2C
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Decibel Meter</h5>
|
||||||
|
<p class="card-text">Capteur bruit sur le port I2C.</p>
|
||||||
|
<button class="btn btn-primary mb-1" onclick="getNoise_values()">Get Data</button>
|
||||||
|
<br>
|
||||||
|
<button class="btn btn-success" onclick="startNoise()">Start recording</button>
|
||||||
|
<button class="btn btn-danger" onclick="stopNoise()">Stop recording</button>
|
||||||
|
<div id="loading_noise" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
|
<table class="table table-striped-columns">
|
||||||
|
<tbody id="data-table-body_noise"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading config.json:', error));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -47,6 +47,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Carte
|
Carte
|
||||||
</a>
|
</a>
|
||||||
|
<a class="nav-link text-white" href="config.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16">
|
||||||
|
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
||||||
|
</svg>
|
||||||
|
Config
|
||||||
|
</a>
|
||||||
<a class="nav-link text-white" href="terminal.html">
|
<a class="nav-link text-white" href="terminal.html">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-terminal-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-terminal-fill" viewBox="0 0 16 16">
|
||||||
<path d="M0 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm9.5 5.5h-3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm-6.354-.354a.5.5 0 1 0 .708.708l2-2a.5.5 0 0 0 0-.708l-2-2a.5.5 0 1 0-.708.708L4.793 6.5 3.146 8.146z"/>
|
<path d="M0 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm9.5 5.5h-3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm-6.354-.354a.5.5 0 1 0 .708.708l2-2a.5.5 0 0 0 0-.708l-2-2a.5.5 0 1 0-.708.708L4.793 6.5 3.146 8.146z"/>
|
||||||
|
|||||||
@@ -141,8 +141,8 @@
|
|||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="command-container" id="commandContainer">
|
<div class="command-container" id="commandContainer">
|
||||||
<div id="terminal">Welcome to NebuleAir Terminal Console
|
<div id="terminal">Welcome to NebuleAir Terminal Console
|
||||||
Type your commands below. Type 'help' for a list of commands.
|
Type your commands below. Type 'help' for a list of commands.
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="cmdLine" placeholder="Enter command..." disabled>
|
<input type="text" id="cmdLine" placeholder="Enter command..." disabled>
|
||||||
</div>
|
</div>
|
||||||
<div id="errorMsg" class="alert alert-danger m-3" style="display:none;"></div>
|
<div id="errorMsg" class="alert alert-danger m-3" style="display:none;"></div>
|
||||||
@@ -200,34 +200,8 @@
|
|||||||
initializeElements();
|
initializeElements();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onload = function() {
|
|
||||||
|
|
||||||
//NEW way to get config (SQLite)
|
|
||||||
$.ajax({
|
|
||||||
url: 'launcher.php?type=get_config_sqlite',
|
|
||||||
dataType:'json',
|
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Getting SQLite config table:");
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
|
|
||||||
//device name_side bar
|
|
||||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
|
||||||
elements.forEach((element) => {
|
|
||||||
element.innerText = response.deviceName;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end AJAX
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add admin password (should be changed to something more secure)
|
// Add admin password (should be changed to something more secure)
|
||||||
const ADMIN_PASSWORD = "123plouf";
|
const ADMIN_PASSWORD = "nebuleair123";
|
||||||
|
|
||||||
// Global variables
|
// Global variables
|
||||||
let terminal;
|
let terminal;
|
||||||
@@ -364,6 +338,23 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command === 'help') {
|
||||||
|
terminal.innerHTML += `
|
||||||
|
Available commands:
|
||||||
|
help - Show this help message
|
||||||
|
clear - Clear the terminal
|
||||||
|
ls [options] - List directory contents
|
||||||
|
df -h - Show disk usage
|
||||||
|
free -h - Show memory usage
|
||||||
|
cat [file] - Display file contents
|
||||||
|
systemctl - Control system services
|
||||||
|
ifconfig - Show network configuration
|
||||||
|
reboot - Reboot the system (use with caution)
|
||||||
|
|
||||||
|
[Any other Linux command]\n`;
|
||||||
|
terminal.scrollTop = terminal.scrollHeight;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Filter dangerous commands
|
// Filter dangerous commands
|
||||||
const dangerousCommands = [
|
const dangerousCommands = [
|
||||||
@@ -384,24 +375,21 @@
|
|||||||
|
|
||||||
// Execute the command via AJAX
|
// Execute the command via AJAX
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=execute_command',
|
url: 'launcher.php',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
dataType:'json',
|
|
||||||
data: {
|
data: {
|
||||||
type: 'execute_command',
|
type: 'execute_command',
|
||||||
command: command
|
command: command
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
console.log(response);
|
if (response.success) {
|
||||||
|
// Add command output to terminal
|
||||||
if (response.success) {
|
terminal.innerHTML += `<span style="color: #00ff00;">${response.output}</span>\n`;
|
||||||
// Add command output to terminal
|
} else {
|
||||||
terminal.innerHTML += `<span style="color: #00ff00;">${response.output}</span>\n`;
|
terminal.innerHTML += `<span style="color: red;">Error: ${response.message}</span>\n`;
|
||||||
} else {
|
}
|
||||||
terminal.innerHTML += `<span style="color: red;">Error: ${response.message}</span>\n`;
|
terminal.scrollTop = terminal.scrollHeight;
|
||||||
}
|
},
|
||||||
terminal.scrollTop = terminal.scrollHeight;
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
terminal.innerHTML += `<span style="color: red;">Error executing command: ${error}</span>\n`;
|
terminal.innerHTML += `<span style="color: red;">Error executing command: ${error}</span>\n`;
|
||||||
terminal.scrollTop = terminal.scrollHeight;
|
terminal.scrollTop = terminal.scrollHeight;
|
||||||
|
|||||||
@@ -302,11 +302,6 @@ function get_internet(){
|
|||||||
element.innerText = deviceName;
|
element.innerText = deviceName;
|
||||||
});
|
});
|
||||||
|
|
||||||
//device name html page title
|
|
||||||
if (response.deviceName) {
|
|
||||||
document.title = response.deviceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//get wifi connection status
|
//get wifi connection status
|
||||||
const WIFI_statusElement = document.getElementById("wifi-status");
|
const WIFI_statusElement = document.getElementById("wifi-status");
|
||||||
|
|||||||
0
old/install_software.yaml → install_software.yaml
Normal file → Executable file
0
old/install_software.yaml → install_software.yaml
Normal file → Executable file
@@ -23,27 +23,40 @@ fi
|
|||||||
|
|
||||||
# Update and install necessary packages
|
# Update and install necessary packages
|
||||||
info "Updating package list and installing necessary packages..."
|
info "Updating package list and installing necessary packages..."
|
||||||
sudo apt update && sudo apt install -y git gh apache2 sqlite3 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus python3-rpi.gpio || error "Failed to install required packages."
|
sudo apt update && sudo apt install -y git gh apache2 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus || error "Failed to install required packages."
|
||||||
|
|
||||||
# Install Python libraries
|
# Install Python libraries
|
||||||
info "Installing Python libraries..."
|
info "Installing Python libraries..."
|
||||||
sudo pip3 install pyserial requests adafruit-circuitpython-bme280 crcmod psutil gpiozero ntplib adafruit-circuitpython-ads1x15 nsrt-mk3-dev pytz --break-system-packages || error "Failed to install Python libraries."
|
sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil ntplib pytz --break-system-packages || error "Failed to install Python libraries."
|
||||||
|
|
||||||
|
# Ask user if they want to set up SSH keys
|
||||||
|
read -p "Do you want to set up an SSH key for /var/www? (y/n): " answer
|
||||||
|
answer=${answer,,} # Convert to lowercase
|
||||||
|
|
||||||
|
if [[ "$answer" == "y" ]]; then
|
||||||
|
info "Setting up SSH keys..."
|
||||||
|
|
||||||
|
sudo mkdir -p /var/www/.ssh
|
||||||
|
sudo chmod 700 /var/www/.ssh
|
||||||
|
|
||||||
|
if [[ ! -f /var/www/.ssh/id_rsa ]]; then
|
||||||
|
sudo ssh-keygen -t rsa -b 4096 -f /var/www/.ssh/id_rsa -N ""
|
||||||
|
success "SSH key generated successfully."
|
||||||
|
else
|
||||||
|
warning "SSH key already exists. Skipping key generation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo ssh-copy-id -i /var/www/.ssh/id_rsa.pub -p 50221 airlab_server1@aircarto.fr || warning "Failed to copy SSH key. Please check the server connection."
|
||||||
|
|
||||||
|
success "SSH setup complete!"
|
||||||
|
else
|
||||||
|
warning "Skipping SSH key setup."
|
||||||
|
fi
|
||||||
|
|
||||||
# Clone the repository (check if it exists first)
|
# Clone the repository (check if it exists first)
|
||||||
REPO_DIR="/var/www/nebuleair_pro_4g"
|
REPO_DIR="/var/www/nebuleair_pro_4g"
|
||||||
if [[ -d "$REPO_DIR" ]]; then
|
if [[ -d "$REPO_DIR" ]]; then
|
||||||
warning "Repository already exists. Will update instead of clone."
|
warning "Repository already exists. Skipping clone."
|
||||||
# Save current directory
|
|
||||||
local current_dir=$(pwd)
|
|
||||||
# Navigate to repository directory
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
# Stash any local changes
|
|
||||||
sudo git stash || warning "Failed to stash local changes"
|
|
||||||
# Pull latest changes
|
|
||||||
sudo git pull || error "Failed to pull latest changes"
|
|
||||||
# Return to original directory
|
|
||||||
cd "$current_dir"
|
|
||||||
success "Repository updated successfully!"
|
|
||||||
else
|
else
|
||||||
info "Cloning the NebuleAir Pro 4G repository..."
|
info "Cloning the NebuleAir Pro 4G repository..."
|
||||||
sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git "$REPO_DIR" || error "Failed to clone repository."
|
sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git "$REPO_DIR" || error "Failed to clone repository."
|
||||||
@@ -53,6 +66,7 @@ fi
|
|||||||
info "Setting up repository files and permissions..."
|
info "Setting up repository files and permissions..."
|
||||||
sudo mkdir -p "$REPO_DIR/logs"
|
sudo mkdir -p "$REPO_DIR/logs"
|
||||||
sudo touch "$REPO_DIR/logs/app.log" "$REPO_DIR/logs/master.log" "$REPO_DIR/logs/master_errors.log" "$REPO_DIR/wifi_list.csv"
|
sudo touch "$REPO_DIR/logs/app.log" "$REPO_DIR/logs/master.log" "$REPO_DIR/logs/master_errors.log" "$REPO_DIR/wifi_list.csv"
|
||||||
|
sudo cp "$REPO_DIR/config.json.dist" "$REPO_DIR/config.json"
|
||||||
sudo chmod -R 755 "$REPO_DIR/"
|
sudo chmod -R 755 "$REPO_DIR/"
|
||||||
sudo chown -R www-data:www-data "$REPO_DIR/"
|
sudo chown -R www-data:www-data "$REPO_DIR/"
|
||||||
sudo git config --global core.fileMode false
|
sudo git config --global core.fileMode false
|
||||||
@@ -77,15 +91,6 @@ else
|
|||||||
warning "Database creation script not found."
|
warning "Database creation script not found."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set config
|
|
||||||
info "Set config..."
|
|
||||||
if [[ -f "$REPO_DIR/sqlite/set_config.py" ]]; then
|
|
||||||
sudo /usr/bin/python3 "$REPO_DIR/sqlite/set_config.py" || error "Failed to set config."
|
|
||||||
success "Databases created successfully."
|
|
||||||
else
|
|
||||||
warning "Database creation script not found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configure Apache
|
# Configure Apache
|
||||||
info "Configuring Apache..."
|
info "Configuring Apache..."
|
||||||
APACHE_CONF="/etc/apache2/sites-available/000-default.conf"
|
APACHE_CONF="/etc/apache2/sites-available/000-default.conf"
|
||||||
@@ -99,48 +104,13 @@ fi
|
|||||||
|
|
||||||
# Add sudo authorization (prevent duplicate entries)
|
# Add sudo authorization (prevent duplicate entries)
|
||||||
info "Setting up sudo authorization..."
|
info "Setting up sudo authorization..."
|
||||||
SUDOERS_FILE="/etc/sudoers"
|
if ! sudo grep -q "/usr/bin/nmcli" /etc/sudoers; then
|
||||||
|
echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *" | sudo tee -a /etc/sudoers > /dev/null
|
||||||
# First, fix any existing syntax errors
|
success "Sudo authorization added."
|
||||||
if sudo visudo -c 2>&1 | grep -q "syntax error"; then
|
|
||||||
warning "Syntax error detected in sudoers file. Attempting to fix..."
|
|
||||||
# Remove the problematic line if it exists
|
|
||||||
sudo sed -i '/www-data ALL=(ALL) NOPASSWD: \/usr\/bin\/python3 \* www-data/d' "$SUDOERS_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add proper sudo rules (each on a separate line)
|
|
||||||
if ! sudo grep -q "/usr/bin/nmcli" "$SUDOERS_FILE"; then
|
|
||||||
# Create a temporary file with the new rules
|
|
||||||
cat <<EOF | sudo tee /tmp/sudoers_additions > /dev/null
|
|
||||||
# NebuleAir Pro 4G sudo rules
|
|
||||||
ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot
|
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
|
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
|
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
|
|
||||||
www-data ALL=(ALL) NOPASSWD: /bin/systemctl *
|
|
||||||
www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Validate the temporary file
|
|
||||||
if sudo visudo -c -f /tmp/sudoers_additions; then
|
|
||||||
# Append to sudoers if valid
|
|
||||||
sudo cat /tmp/sudoers_additions >> "$SUDOERS_FILE"
|
|
||||||
success "Sudo authorization added."
|
|
||||||
else
|
|
||||||
error "Failed to add sudo rules - syntax validation failed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
sudo rm -f /tmp/sudoers_additions
|
|
||||||
else
|
else
|
||||||
warning "Sudo authorization already set. Skipping."
|
warning "Sudo authorization already set. Skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate sudoers file after changes
|
|
||||||
if ! sudo visudo -c; then
|
|
||||||
error "Sudoers file has syntax errors! Please fix manually with 'sudo visudo'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Open all UART serial ports (avoid duplication)
|
# Open all UART serial ports (avoid duplication)
|
||||||
info "Configuring UART serial ports..."
|
info "Configuring UART serial ports..."
|
||||||
if ! grep -q "enable_uart=1" /boot/firmware/config.txt; then
|
if ! grep -q "enable_uart=1" /boot/firmware/config.txt; then
|
||||||
@@ -163,12 +133,6 @@ success "I2C ports enabled."
|
|||||||
info "Creates sqlites databases..."
|
info "Creates sqlites databases..."
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
|
||||||
|
|
||||||
# Final sudoers check
|
|
||||||
if sudo visudo -c; then
|
|
||||||
success "Sudoers file is valid."
|
|
||||||
else
|
|
||||||
error "Sudoers file has errors! System may not function correctly."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Completion message
|
# Completion message
|
||||||
success "Setup completed successfully!"
|
success "Setup completed successfully!"
|
||||||
|
|||||||
@@ -22,21 +22,11 @@ error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
|||||||
if [[ "$EUID" -ne 0 ]]; then
|
if [[ "$EUID" -ne 0 ]]; then
|
||||||
error "This script must be run as root. Use 'sudo ./installation.sh'"
|
error "This script must be run as root. Use 'sudo ./installation.sh'"
|
||||||
fi
|
fi
|
||||||
REPO_DIR="/var/www/nebuleair_pro_4g"
|
|
||||||
#set up the RTC
|
#set up the RTC
|
||||||
info "Set up the RTC"
|
info "Set up the RTC"
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
|
||||||
|
|
||||||
#Wake up SARA
|
|
||||||
info "Wake Up SARA"
|
|
||||||
pinctrl set 16 op
|
|
||||||
pinctrl set 16 dh
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
#Check SARA connection
|
|
||||||
info "Check SARA connection"
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 ATI 2
|
|
||||||
|
|
||||||
#set up SARA R4 APN
|
#set up SARA R4 APN
|
||||||
info "Set up Monogoto APN"
|
info "Set up Monogoto APN"
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setAPN.py ttyAMA2 data.mono 2
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setAPN.py ttyAMA2 data.mono 2
|
||||||
@@ -46,48 +36,32 @@ info "Activate blue LED"
|
|||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UGPIOC=16,2 2
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UGPIOC=16,2 2
|
||||||
|
|
||||||
#Connect to network
|
#Connect to network
|
||||||
#info "Connect SARA R4 to network"
|
info "Connect SARA R4 to network"
|
||||||
#python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 20810 60
|
python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 20810 60
|
||||||
|
|
||||||
#Need to create the two service
|
#Add master_nebuleair.service
|
||||||
# 1. start the scripts to set-up the services
|
SERVICE_FILE="/etc/systemd/system/master_nebuleair.service"
|
||||||
# 2. rtc_save_to_db
|
info "Setting up systemd service for master_nebuleair..."
|
||||||
|
|
||||||
#1. set-up the services (SARA, NPM, BME280, etc)
|
|
||||||
info "Setting up systemd services..."
|
|
||||||
|
|
||||||
if [[ -f "$REPO_DIR/services/setup_services.sh" ]]; then
|
|
||||||
sudo chmod +x "$REPO_DIR/services/setup_services.sh"
|
|
||||||
sudo "$REPO_DIR/services/setup_services.sh" || warning "Failed to set up systemd services"
|
|
||||||
success "Systemd services set up successfully."
|
|
||||||
else
|
|
||||||
warning "Systemd services setup script not found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
#2. Add rtc_save_to_db.service
|
|
||||||
SERVICE_FILE_2="/etc/systemd/system/rtc_save_to_db.service"
|
|
||||||
info "Setting up systemd service for rtc_save_to_db..."
|
|
||||||
|
|
||||||
# Create the systemd service file (overwrite if necessary)
|
# Create the systemd service file (overwrite if necessary)
|
||||||
sudo bash -c "cat > $SERVICE_FILE_2" <<EOF
|
sudo bash -c "cat > $SERVICE_FILE" <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=RTC Save to DB Script
|
Description=Master manager for the Python loop scripts
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/save_to_db.py
|
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/master.py
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=1
|
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
WorkingDirectory=/var/www/nebuleair_pro_4g
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/rtc_save_to_db.log
|
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/master.log
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/rtc_save_to_db_errors.log
|
StandardError=append:/var/www/nebuleair_pro_4g/logs/master_errors.log
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
success "Systemd service file created: $SERVICE_FILE_2"
|
success "Systemd service file created: $SERVICE_FILE"
|
||||||
|
|
||||||
# Reload systemd to recognize the new service
|
# Reload systemd to recognize the new service
|
||||||
info "Reloading systemd daemon..."
|
info "Reloading systemd daemon..."
|
||||||
@@ -95,8 +69,8 @@ sudo systemctl daemon-reload
|
|||||||
|
|
||||||
# Enable the service to start on boot
|
# Enable the service to start on boot
|
||||||
info "Enabling the service to start on boot..."
|
info "Enabling the service to start on boot..."
|
||||||
sudo systemctl enable rtc_save_to_db.service
|
sudo systemctl enable master_nebuleair.service
|
||||||
|
|
||||||
# Start the service immediately
|
# Start the service immediately
|
||||||
info "Starting the service..."
|
info "Starting the service..."
|
||||||
sudo systemctl start rtc_save_to_db.service
|
sudo systemctl start master_nebuleair.service
|
||||||
File diff suppressed because it is too large
Load Diff
100
master.py
Executable file
100
master.py
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
'''
|
||||||
|
__ __ _
|
||||||
|
| \/ | __ _ ___| |_ ___ _ __
|
||||||
|
| |\/| |/ _` / __| __/ _ \ '__|
|
||||||
|
| | | | (_| \__ \ || __/ |
|
||||||
|
|_| |_|\__,_|___/\__\___|_|
|
||||||
|
|
||||||
|
Master Python script that will trigger other scripts at every chosen time pace
|
||||||
|
This script is triggered as a systemd service used as an alternative to cronjobs
|
||||||
|
|
||||||
|
Attention:
|
||||||
|
to do -> prevent SARA R4 Script to run again if it's taking more than 60 secs to finish (using a lock file ??)
|
||||||
|
|
||||||
|
First time: need to create the service file
|
||||||
|
|
||||||
|
--> sudo nano /etc/systemd/system/master_nebuleair.service
|
||||||
|
|
||||||
|
⬇️
|
||||||
|
[Unit]
|
||||||
|
Description=Master manager for the Python loop scripts
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/master.py
|
||||||
|
Restart=always
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/var/www/nebuleair_pro_4g
|
||||||
|
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/master.log
|
||||||
|
StandardError=append:/var/www/nebuleair_pro_4g/logs/master_errors.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
⬆️
|
||||||
|
|
||||||
|
Reload systemd (first time after creating the service):
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
Enable (once), start (once and after stopping) and restart (after modification)systemd:
|
||||||
|
sudo systemctl enable master_nebuleair.service
|
||||||
|
sudo systemctl start master_nebuleair.service
|
||||||
|
sudo systemctl restart master_nebuleair.service
|
||||||
|
|
||||||
|
Check the service status:
|
||||||
|
sudo systemctl status master_nebuleair.service
|
||||||
|
|
||||||
|
|
||||||
|
Specific scripts can be disabled with config.json
|
||||||
|
Exemple: stop gathering data from NPM
|
||||||
|
Exemple: stop sending data with SARA R4
|
||||||
|
|
||||||
|
'''
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Base directory where scripts are stored
|
||||||
|
SCRIPT_DIR = "/var/www/nebuleair_pro_4g/"
|
||||||
|
CONFIG_FILE = "/var/www/nebuleair_pro_4g/config.json"
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
"""Load the configuration file to determine which scripts to run."""
|
||||||
|
with open(CONFIG_FILE, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def run_script(script_name, interval, delay=0):
|
||||||
|
"""Run a script in a synchronized loop with an optional start delay."""
|
||||||
|
script_path = os.path.join(SCRIPT_DIR, script_name) # Build full path
|
||||||
|
next_run = time.monotonic() + delay # Apply the initial delay
|
||||||
|
|
||||||
|
while True:
|
||||||
|
config = load_config()
|
||||||
|
if config.get(script_name, True): # Default to True if not found
|
||||||
|
subprocess.run(["python3", script_path])
|
||||||
|
|
||||||
|
# Wait until the next exact interval
|
||||||
|
next_run += interval
|
||||||
|
sleep_time = max(0, next_run - time.monotonic()) # Prevent negative sleep times
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
# Define scripts and their execution intervals (seconds)
|
||||||
|
SCRIPTS = [
|
||||||
|
#("RTC/save_to_db.py", 1, 0), # SAVE RTC time every 1 second, no delay
|
||||||
|
("NPM/get_data_modbus_v3.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s, with 2s delay
|
||||||
|
("envea/read_value_v2.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s, with 2s delay
|
||||||
|
("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 2s delay
|
||||||
|
("BME280/get_data_v2.py", 120, 0), # Get BME280 data every 120 seconds, no delay
|
||||||
|
("sqlite/flush_old_data.py", 86400, 0) # flush old data inside db every day ()
|
||||||
|
]
|
||||||
|
|
||||||
|
# Start threads for enabled scripts
|
||||||
|
for script_name, interval, delay in SCRIPTS:
|
||||||
|
thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# Keep the main script running
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
166
old/master.py
166
old/master.py
@@ -1,166 +0,0 @@
|
|||||||
'''
|
|
||||||
__ __ _
|
|
||||||
| \/ | __ _ ___| |_ ___ _ __
|
|
||||||
| |\/| |/ _` / __| __/ _ \ '__|
|
|
||||||
| | | | (_| \__ \ || __/ |
|
|
||||||
|_| |_|\__,_|___/\__\___|_|
|
|
||||||
|
|
||||||
Master Python script that will trigger other scripts at every chosen time pace
|
|
||||||
This script is triggered as a systemd service used as an alternative to cronjobs
|
|
||||||
|
|
||||||
Attention:
|
|
||||||
to do -> prevent SARA R4 Script to run again if it's taking more than 60 secs to finish (using a lock file ??)
|
|
||||||
|
|
||||||
First time: need to create the service file
|
|
||||||
|
|
||||||
--> sudo nano /etc/systemd/system/master_nebuleair.service
|
|
||||||
|
|
||||||
⬇️
|
|
||||||
[Unit]
|
|
||||||
Description=Master manager for the Python loop scripts
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/master.py
|
|
||||||
Restart=always
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/master.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/master_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
⬆️
|
|
||||||
|
|
||||||
Reload systemd (first time after creating the service):
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
|
|
||||||
Enable (once), start (once and after stopping) and restart (after modification)systemd:
|
|
||||||
sudo systemctl enable master_nebuleair.service
|
|
||||||
sudo systemctl start master_nebuleair.service
|
|
||||||
sudo systemctl restart master_nebuleair.service
|
|
||||||
|
|
||||||
Check the service status:
|
|
||||||
sudo systemctl status master_nebuleair.service
|
|
||||||
|
|
||||||
|
|
||||||
Specific scripts can be disabled with config.json
|
|
||||||
Exemple: stop gathering data from NPM
|
|
||||||
Exemple: stop sending data with SARA R4
|
|
||||||
|
|
||||||
'''
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
|
|
||||||
# Base directory where scripts are stored
|
|
||||||
SCRIPT_DIR = "/var/www/nebuleair_pro_4g/"
|
|
||||||
DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db"
|
|
||||||
|
|
||||||
# Lock file path for SARA script
|
|
||||||
SARA_LOCK_FILE = "/var/www/nebuleair_pro_4g/sara_script.lock"
|
|
||||||
|
|
||||||
|
|
||||||
def is_script_enabled(script_name):
|
|
||||||
"""Check if a script is enabled in the database."""
|
|
||||||
try:
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
cursor.execute(
|
|
||||||
"SELECT enabled FROM config_scripts_table WHERE script_path = ?",
|
|
||||||
(script_name,)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if result is None:
|
|
||||||
return True # Default to enabled if not found in database
|
|
||||||
|
|
||||||
return bool(result[0])
|
|
||||||
except Exception:
|
|
||||||
# If any database error occurs, default to enabled
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def create_lock_file():
|
|
||||||
"""Create a lock file for the SARA script."""
|
|
||||||
with open(SARA_LOCK_FILE, 'w') as f:
|
|
||||||
f.write(str(int(time.time())))
|
|
||||||
|
|
||||||
|
|
||||||
def remove_lock_file():
|
|
||||||
"""Remove the SARA script lock file."""
|
|
||||||
if os.path.exists(SARA_LOCK_FILE):
|
|
||||||
os.remove(SARA_LOCK_FILE)
|
|
||||||
|
|
||||||
|
|
||||||
def is_script_locked():
|
|
||||||
"""Check if the SARA script is currently locked."""
|
|
||||||
if not os.path.exists(SARA_LOCK_FILE):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if lock is older than 60 seconds (stale)
|
|
||||||
with open(SARA_LOCK_FILE, 'r') as f:
|
|
||||||
try:
|
|
||||||
lock_time = int(f.read().strip())
|
|
||||||
if time.time() - lock_time > 60:
|
|
||||||
# Lock is stale, remove it
|
|
||||||
remove_lock_file()
|
|
||||||
return False
|
|
||||||
except ValueError:
|
|
||||||
# Invalid lock file content
|
|
||||||
remove_lock_file()
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def run_script(script_name, interval, delay=0):
|
|
||||||
"""Run a script in a synchronized loop with an optional start delay."""
|
|
||||||
script_path = os.path.join(SCRIPT_DIR, script_name) # Build full path
|
|
||||||
next_run = time.monotonic() + delay # Apply the initial delay
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if is_script_enabled(script_name):
|
|
||||||
# Special handling for SARA script to prevent concurrent runs
|
|
||||||
if script_name == "loop/SARA_send_data_v2.py":
|
|
||||||
if not is_script_locked():
|
|
||||||
create_lock_file()
|
|
||||||
try:
|
|
||||||
subprocess.run(["python3", script_path], timeout=200)
|
|
||||||
finally:
|
|
||||||
remove_lock_file()
|
|
||||||
else:
|
|
||||||
# Run other scripts normally
|
|
||||||
subprocess.run(["python3", script_path])
|
|
||||||
|
|
||||||
# Wait until the next exact interval
|
|
||||||
next_run += interval
|
|
||||||
sleep_time = max(0, next_run - time.monotonic()) # Prevent negative sleep times
|
|
||||||
time.sleep(sleep_time)
|
|
||||||
|
|
||||||
|
|
||||||
# Define scripts and their execution intervals (seconds)
|
|
||||||
SCRIPTS = [
|
|
||||||
# Format: (script_name, interval_in_seconds, start_delay_in_seconds)
|
|
||||||
("NPM/get_data_modbus_v3.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s
|
|
||||||
("envea/read_value_v2.py", 10, 0), # Get Envea data every 10s
|
|
||||||
("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 1s delay
|
|
||||||
("BME280/get_data_v2.py", 120, 0), # Get BME280 data every 120 seconds
|
|
||||||
("MPPT/read.py", 120, 0), # Get MPPT data every 120 seconds
|
|
||||||
("sqlite/flush_old_data.py", 86400, 0) # Flush old data inside db every day
|
|
||||||
]
|
|
||||||
|
|
||||||
# Start threads for scripts
|
|
||||||
for script_name, interval, delay in SCRIPTS:
|
|
||||||
thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
# Keep the main script running
|
|
||||||
while True:
|
|
||||||
time.sleep(1)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# NebuleAir Pro Services
|
|
||||||
|
|
||||||
Les scripts importants tournent à l'aide d'un service et d'un timer associé.
|
|
||||||
|
|
||||||
Pour les installer:
|
|
||||||
|
|
||||||
sudo chmod +x /var/www/nebuleair_pro_4g/services/setup_services.sh
|
|
||||||
sudo /var/www/nebuleair_pro_4g/services/setup_services.sh
|
|
||||||
|
|
||||||
Supprimer l'ancien master:
|
|
||||||
sudo systemctl stop master_nebuleair.service
|
|
||||||
sudo systemctl disable master_nebuleair.service
|
|
||||||
|
|
||||||
# Check les services
|
|
||||||
|
|
||||||
SARA:
|
|
||||||
sudo systemctl status nebuleair-sara-data.service
|
|
||||||
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# File: /var/www/nebuleair_pro_4g/services/check_services.sh
|
|
||||||
# Purpose: Check status of all NebuleAir services and logs
|
|
||||||
# Version with fixed color handling for proper table display
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
BOLD='\033[1m'
|
|
||||||
DIM='\033[2m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Service list
|
|
||||||
SERVICES=("npm" "envea" "sara" "bme280" "mppt" "db-cleanup" "noise")
|
|
||||||
|
|
||||||
# Function to print header
|
|
||||||
print_header() {
|
|
||||||
local text="$1"
|
|
||||||
echo ""
|
|
||||||
echo -e "${BLUE}${BOLD}=== $text ===${NC}"
|
|
||||||
echo -e "${BLUE}$(printf '%.0s=' {1..70})${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to print section
|
|
||||||
print_section() {
|
|
||||||
local text="$1"
|
|
||||||
echo ""
|
|
||||||
echo -e "${CYAN}${BOLD}--- $text ---${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to print a separator line
|
|
||||||
print_separator() {
|
|
||||||
echo "+--------------------------+-----------+-----------+-------------+-------------+-------------------------+"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear screen for clean output
|
|
||||||
clear
|
|
||||||
|
|
||||||
# Main header
|
|
||||||
print_header "NebuleAir Services Status Report"
|
|
||||||
echo -e "Generated on: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
|
|
||||||
# Timer Schedule
|
|
||||||
print_section "Active Timers Schedule"
|
|
||||||
echo ""
|
|
||||||
systemctl list-timers --no-pager | head -n 1
|
|
||||||
systemctl list-timers --no-pager | grep nebuleair || echo "No active nebuleair timers found"
|
|
||||||
|
|
||||||
# Service Status Overview with fixed color handling
|
|
||||||
print_section "Service Status Overview"
|
|
||||||
echo ""
|
|
||||||
print_separator
|
|
||||||
printf "| %-24s | %-9s | %-9s | %-11s | %-11s | %-23s |\n" "Service" "Svc State" "Svc Boot" "Timer State" "Timer Boot" "Health Status"
|
|
||||||
print_separator
|
|
||||||
|
|
||||||
for service in "${SERVICES[@]}"; do
|
|
||||||
# Check the actual service and timer names (with -data suffix)
|
|
||||||
full_service_name="nebuleair-${service}-data"
|
|
||||||
|
|
||||||
# Get raw status values
|
|
||||||
service_status=$(systemctl is-active ${full_service_name}.service 2>/dev/null | tr -d '\n' || echo "not-found")
|
|
||||||
service_enabled=$(systemctl is-enabled ${full_service_name}.service 2>/dev/null | tr -d '\n' || echo "not-found")
|
|
||||||
timer_status=$(systemctl is-active ${full_service_name}.timer 2>/dev/null | tr -d '\n' || echo "not-found")
|
|
||||||
timer_enabled=$(systemctl is-enabled ${full_service_name}.timer 2>/dev/null | tr -d '\n' || echo "not-found")
|
|
||||||
|
|
||||||
# Check if files exist and override if not found
|
|
||||||
if ! systemctl list-unit-files | grep -q "^${full_service_name}.service" &>/dev/null; then
|
|
||||||
service_status="not-found"
|
|
||||||
service_enabled="not-found"
|
|
||||||
fi
|
|
||||||
if ! systemctl list-unit-files | grep -q "^${full_service_name}.timer" &>/dev/null; then
|
|
||||||
timer_status="not-found"
|
|
||||||
timer_enabled="not-found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create display strings without embedded colors for table cells
|
|
||||||
case $service_status in
|
|
||||||
"active") svc_st_display="active"; svc_st_color="${GREEN}" ;;
|
|
||||||
"inactive") svc_st_display="inactive"; svc_st_color="${DIM}" ;;
|
|
||||||
"activating") svc_st_display="starting"; svc_st_color="${YELLOW}" ;;
|
|
||||||
"not-found") svc_st_display="missing"; svc_st_color="${RED}" ;;
|
|
||||||
*) svc_st_display="$service_status"; svc_st_color="${RED}" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $service_enabled in
|
|
||||||
"enabled"|"static") svc_en_display="enabled"; svc_en_color="${GREEN}" ;;
|
|
||||||
"disabled") svc_en_display="disabled"; svc_en_color="${YELLOW}" ;;
|
|
||||||
"not-found") svc_en_display="missing"; svc_en_color="${RED}" ;;
|
|
||||||
*) svc_en_display="$service_enabled"; svc_en_color="${YELLOW}" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $timer_status in
|
|
||||||
"active") tim_st_display="active"; tim_st_color="${GREEN}" ;;
|
|
||||||
"inactive") tim_st_display="inactive"; tim_st_color="${RED}" ;;
|
|
||||||
"not-found") tim_st_display="missing"; tim_st_color="${RED}" ;;
|
|
||||||
*) tim_st_display="$timer_status"; tim_st_color="${RED}" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $timer_enabled in
|
|
||||||
"enabled"|"static") tim_en_display="enabled"; tim_en_color="${GREEN}" ;;
|
|
||||||
"disabled") tim_en_display="disabled"; tim_en_color="${YELLOW}" ;;
|
|
||||||
"not-found") tim_en_display="missing"; tim_en_color="${RED}" ;;
|
|
||||||
*) tim_en_display="$timer_enabled"; tim_en_color="${YELLOW}" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Determine health status
|
|
||||||
if [[ "$timer_status" == "active" ]]; then
|
|
||||||
if [[ "$timer_enabled" == "enabled" || "$timer_enabled" == "static" ]]; then
|
|
||||||
health_display="✓ OK"
|
|
||||||
health_color="${GREEN}"
|
|
||||||
else
|
|
||||||
health_display="⚠ Boot disabled"
|
|
||||||
health_color="${YELLOW}"
|
|
||||||
fi
|
|
||||||
elif [[ "$timer_status" == "inactive" ]]; then
|
|
||||||
health_display="✗ Timer stopped"
|
|
||||||
health_color="${RED}"
|
|
||||||
else
|
|
||||||
health_display="✗ Timer missing"
|
|
||||||
health_color="${RED}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Print row with colors applied outside of printf formatting
|
|
||||||
printf "| %-24s | " "$full_service_name"
|
|
||||||
printf "${svc_st_color}%-9s${NC} | " "$svc_st_display"
|
|
||||||
printf "${svc_en_color}%-9s${NC} | " "$svc_en_display"
|
|
||||||
printf "${tim_st_color}%-11s${NC} | " "$tim_st_display"
|
|
||||||
printf "${tim_en_color}%-11s${NC} | " "$tim_en_display"
|
|
||||||
printf "${health_color}%-23s${NC} |\n" "$health_display"
|
|
||||||
done
|
|
||||||
print_separator
|
|
||||||
|
|
||||||
# Understanding the table
|
|
||||||
echo ""
|
|
||||||
echo -e "${DIM}Note: For timer-based services, it's normal for the service to be 'inactive' and 'disabled'.${NC}"
|
|
||||||
echo -e "${DIM} What matters is that the timer is 'active' and 'enabled'.${NC}"
|
|
||||||
|
|
||||||
# Configuration Issues
|
|
||||||
print_section "Configuration Issues"
|
|
||||||
echo ""
|
|
||||||
issues_found=false
|
|
||||||
|
|
||||||
for service in "${SERVICES[@]}"; do
|
|
||||||
full_service_name="nebuleair-${service}-data"
|
|
||||||
|
|
||||||
timer_status=$(systemctl is-active ${full_service_name}.timer 2>/dev/null | tr -d '\n' || echo "not-found")
|
|
||||||
timer_enabled=$(systemctl is-enabled ${full_service_name}.timer 2>/dev/null | tr -d '\n' || echo "not-found")
|
|
||||||
|
|
||||||
# Check if timer exists
|
|
||||||
if ! systemctl list-unit-files | grep -q "^${full_service_name}.timer" &>/dev/null; then
|
|
||||||
timer_status="not-found"
|
|
||||||
timer_enabled="not-found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$timer_status" != "active" || ("$timer_enabled" != "enabled" && "$timer_enabled" != "static") ]]; then
|
|
||||||
issues_found=true
|
|
||||||
echo -e " ${RED}•${NC} ${BOLD}$full_service_name${NC}"
|
|
||||||
if [[ "$timer_status" == "not-found" ]]; then
|
|
||||||
echo -e " ${RED}→${NC} Timer unit file is missing"
|
|
||||||
elif [[ "$timer_status" != "active" ]]; then
|
|
||||||
echo -e " ${RED}→${NC} Timer is not running (status: $timer_status)"
|
|
||||||
fi
|
|
||||||
if [[ "$timer_enabled" == "not-found" ]]; then
|
|
||||||
echo -e " ${RED}→${NC} Timer unit file is missing"
|
|
||||||
elif [[ "$timer_enabled" != "enabled" && "$timer_enabled" != "static" ]]; then
|
|
||||||
echo -e " ${YELLOW}→${NC} Timer won't start on boot (status: $timer_enabled)"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$issues_found" == "false" ]]; then
|
|
||||||
echo -e " ${GREEN}✓${NC} All timers are properly configured!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Recent Executions - Simplified
|
|
||||||
print_section "Last Execution Status"
|
|
||||||
echo ""
|
|
||||||
printf " %-12s %-20s %s\n" "Service" "Last Run" "Status"
|
|
||||||
printf " %-12s %-20s %s\n" "-------" "--------" "------"
|
|
||||||
|
|
||||||
for service in "${SERVICES[@]}"; do
|
|
||||||
full_service_name="nebuleair-${service}-data"
|
|
||||||
|
|
||||||
# Get last execution time and status
|
|
||||||
last_log=$(journalctl -u ${full_service_name}.service -n 3 --no-pager 2>/dev/null | grep -E "(Started|Finished|Failed)" | tail -1)
|
|
||||||
|
|
||||||
if [[ -n "$last_log" ]]; then
|
|
||||||
timestamp=$(echo "$last_log" | awk '{print $1, $2, $3}')
|
|
||||||
if echo "$last_log" | grep -q "Finished"; then
|
|
||||||
status="${GREEN}✓ Success${NC}"
|
|
||||||
elif echo "$last_log" | grep -q "Failed"; then
|
|
||||||
status="${RED}✗ Failed${NC}"
|
|
||||||
elif echo "$last_log" | grep -q "Started"; then
|
|
||||||
status="${YELLOW}⟳ Running${NC}"
|
|
||||||
else
|
|
||||||
status="${DIM}- Unknown${NC}"
|
|
||||||
fi
|
|
||||||
printf " %-12s %-20s %b\n" "$service" "$timestamp" "$status"
|
|
||||||
else
|
|
||||||
printf " %-12s %-20s %b\n" "$service" "-" "${DIM}- No data${NC}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print_section "Summary"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
working=0
|
|
||||||
needs_attention=0
|
|
||||||
|
|
||||||
for service in "${SERVICES[@]}"; do
|
|
||||||
full_service_name="nebuleair-${service}-data"
|
|
||||||
timer_status=$(systemctl is-active ${full_service_name}.timer 2>/dev/null | tr -d '\n')
|
|
||||||
timer_enabled=$(systemctl is-enabled ${full_service_name}.timer 2>/dev/null | tr -d '\n')
|
|
||||||
|
|
||||||
if [[ "$timer_status" == "active" ]] && [[ "$timer_enabled" == "enabled" || "$timer_enabled" == "static" ]]; then
|
|
||||||
((working++))
|
|
||||||
else
|
|
||||||
((needs_attention++))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
total=${#SERVICES[@]}
|
|
||||||
|
|
||||||
# Visual progress bar
|
|
||||||
echo -n " Overall Health: ["
|
|
||||||
for ((i=1; i<=10; i++)); do
|
|
||||||
if ((i <= working * 10 / total)); then
|
|
||||||
echo -n -e "${GREEN}▰${NC}"
|
|
||||||
else
|
|
||||||
echo -n -e "${RED}▱${NC}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo -e "] ${working}/${total}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e " ${GREEN}✓${NC} Working properly: ${BOLD}$working${NC} services"
|
|
||||||
echo -e " ${RED}✗${NC} Need attention: ${BOLD}$needs_attention${NC} services"
|
|
||||||
|
|
||||||
# Quick Commands
|
|
||||||
print_section "Quick Commands"
|
|
||||||
echo ""
|
|
||||||
echo -e " ${BOLD}Fix a timer that needs attention:${NC}"
|
|
||||||
echo " $ sudo systemctl enable --now nebuleair-[service]-data.timer"
|
|
||||||
echo ""
|
|
||||||
echo -e " ${BOLD}View live logs:${NC}"
|
|
||||||
echo " $ sudo journalctl -u nebuleair-[service]-data.service -f"
|
|
||||||
echo ""
|
|
||||||
echo -e " ${BOLD}Check timer details:${NC}"
|
|
||||||
echo " $ systemctl status nebuleair-[service]-data.timer"
|
|
||||||
echo ""
|
|
||||||
echo -e " ${BOLD}Run service manually:${NC}"
|
|
||||||
echo " $ sudo systemctl start nebuleair-[service]-data.service"
|
|
||||||
|
|
||||||
# Specific fixes needed
|
|
||||||
if [[ $needs_attention -gt 0 ]]; then
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}${BOLD}Recommended Actions:${NC}"
|
|
||||||
for service in "${SERVICES[@]}"; do
|
|
||||||
full_service_name="nebuleair-${service}-data"
|
|
||||||
timer_status=$(systemctl is-active ${full_service_name}.timer 2>/dev/null | tr -d '\n')
|
|
||||||
timer_enabled=$(systemctl is-enabled ${full_service_name}.timer 2>/dev/null | tr -d '\n')
|
|
||||||
|
|
||||||
if [[ "$timer_status" != "active" ]] && [[ "$timer_status" != "not-found" ]]; then
|
|
||||||
echo -e " ${RED}→${NC} sudo systemctl start ${full_service_name}.timer"
|
|
||||||
fi
|
|
||||||
if [[ "$timer_enabled" != "enabled" ]] && [[ "$timer_enabled" != "static" ]] && [[ "$timer_enabled" != "not-found" ]]; then
|
|
||||||
echo -e " ${YELLOW}→${NC} sudo systemctl enable ${full_service_name}.timer"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# File: /var/www/nebuleair_pro_4g/services/setup_services.sh
|
|
||||||
# Purpose: Set up all systemd services for NebuleAir data collection
|
|
||||||
# to install:
|
|
||||||
# sudo chmod +x /var/www/nebuleair_pro_4g/services/setup_services.sh
|
|
||||||
# sudo /var/www/nebuleair_pro_4g/services/setup_services.sh
|
|
||||||
|
|
||||||
echo "Setting up NebuleAir systemd services and timers..."
|
|
||||||
|
|
||||||
# Create directory for logs if it doesn't exist
|
|
||||||
mkdir -p /var/www/nebuleair_pro_4g/logs
|
|
||||||
|
|
||||||
# Create service and timer files for NPM Data
|
|
||||||
cat > /etc/systemd/system/nebuleair-npm-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir NPM Data Collection Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_v3.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/npm_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/npm_service_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-npm-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir NPM Data Collection every 10 seconds
|
|
||||||
Requires=nebuleair-npm-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=10s
|
|
||||||
OnUnitActiveSec=10s
|
|
||||||
AccuracySec=1s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create service and timer files for Envea Data
|
|
||||||
cat > /etc/systemd/system/nebuleair-envea-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir Envea Data Collection Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/envea_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/envea_service_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-envea-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir Envea Data Collection every 10 seconds
|
|
||||||
Requires=nebuleair-envea-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=10s
|
|
||||||
OnUnitActiveSec=10s
|
|
||||||
AccuracySec=1s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create service and timer files for SARA Data (No Lock File Needed)
|
|
||||||
cat > /etc/systemd/system/nebuleair-sara-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir SARA Data Transmission Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/loop/SARA_send_data_v2.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/sara_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/sara_service_errors.log
|
|
||||||
RuntimeMaxSec=200s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-sara-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir SARA Data Transmission every 60 seconds
|
|
||||||
Requires=nebuleair-sara-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=60s
|
|
||||||
OnUnitActiveSec=60s
|
|
||||||
AccuracySec=1s
|
|
||||||
# This is the key setting that prevents overlap
|
|
||||||
Persistent=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create service and timer files for BME280 Data
|
|
||||||
cat > /etc/systemd/system/nebuleair-bme280-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir BME280 Data Collection Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/BME280/get_data_v2.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/bme280_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/bme280_service_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-bme280-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir BME280 Data Collection every 120 seconds
|
|
||||||
Requires=nebuleair-bme280-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=120s
|
|
||||||
OnUnitActiveSec=120s
|
|
||||||
AccuracySec=1s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create service and timer files for MPPT Data
|
|
||||||
cat > /etc/systemd/system/nebuleair-mppt-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir MPPT Data Collection Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/MPPT/read.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/mppt_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/mppt_service_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-mppt-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir MPPT Data Collection every 120 seconds
|
|
||||||
Requires=nebuleair-mppt-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=120s
|
|
||||||
OnUnitActiveSec=120s
|
|
||||||
AccuracySec=1s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create service and timer files for noise Data (every minutes)
|
|
||||||
cat > /etc/systemd/system/nebuleair-noise-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir noise Data Collection Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/sound_meter/NSRT_mk4_get_data.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/noise_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/noise_service_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-noise-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir MPPT Data Collection every 120 seconds
|
|
||||||
Requires=nebuleair-noise-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=60s
|
|
||||||
OnUnitActiveSec=60s
|
|
||||||
AccuracySec=1s
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Create service and timer files for Database Cleanup
|
|
||||||
cat > /etc/systemd/system/nebuleair-db-cleanup-data.service << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=NebuleAir Database Cleanup Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/flush_old_data.py
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/db_cleanup_service.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/db_cleanup_service_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
cat > /etc/systemd/system/nebuleair-db-cleanup-data.timer << 'EOL'
|
|
||||||
[Unit]
|
|
||||||
Description=Run NebuleAir Database Cleanup daily
|
|
||||||
Requires=nebuleair-db-cleanup-data.service
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=1h
|
|
||||||
OnUnitActiveSec=24h
|
|
||||||
AccuracySec=1h
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOL
|
|
||||||
|
|
||||||
# Reload systemd to recognize new services
|
|
||||||
systemctl daemon-reload
|
|
||||||
|
|
||||||
# Enable and start all timers
|
|
||||||
echo "Enabling and starting all services..."
|
|
||||||
for service in npm envea sara bme280 mppt db-cleanup noise; do
|
|
||||||
systemctl enable nebuleair-$service-data.timer
|
|
||||||
systemctl start nebuleair-$service-data.timer
|
|
||||||
echo "Started nebuleair-$service-data timer"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Checking status of all timers..."
|
|
||||||
systemctl list-timers | grep nebuleair
|
|
||||||
|
|
||||||
echo "Setup complete. All NebuleAir services are now running."
|
|
||||||
echo "To check the status of a specific service:"
|
|
||||||
echo " sudo systemctl status nebuleair-npm-data.service"
|
|
||||||
echo "To view logs for a specific service:"
|
|
||||||
echo " sudo journalctl -u nebuleair-npm-data.service"
|
|
||||||
echo "To restart a specific timer:"
|
|
||||||
echo " sudo systemctl restart nebuleair-npm-data.timer"
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
'''
|
|
||||||
____ ___ _ _ _ _ ____
|
|
||||||
/ ___| / _ \| | | | \ | | _ \
|
|
||||||
\___ \| | | | | | | \| | | | |
|
|
||||||
___) | |_| | |_| | |\ | |_| |
|
|
||||||
|____/ \___/ \___/|_| \_|____/
|
|
||||||
|
|
||||||
python3 /var/www/nebuleair_pro_4g/sound_meter/NSRT_MK4_change_config.py
|
|
||||||
|
|
||||||
1.Intervalle d'enregistrement
|
|
||||||
L'intervalle d'enregistrement définit le temps entre deux points successifs enregistrés.
|
|
||||||
Cela définit également la période d'intégration pour le LEQ, et la période d'observation pour L-min et L-max et Lpeak.
|
|
||||||
L'intervalle d'enregistrement peut être réglé de 125 ms (1/8ème) à 2 H par incréments de 125 ms.
|
|
||||||
|
|
||||||
some parameters can be changed:
|
|
||||||
write_tau(tau: float) -> time constant
|
|
||||||
write_fs(frequency: int) -> sampling freq
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import nsrt_mk3_dev
|
|
||||||
#from nsrt_mk3_dev import Weighting
|
|
||||||
#from nsrt_mk3_dev.nsrt_mk3_dev import NsrtMk3Dev, Weighting
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
class Weighting(Enum):
|
|
||||||
DB_A = 1
|
|
||||||
DB_C = 2
|
|
||||||
DB_Z = 3
|
|
||||||
|
|
||||||
nsrt = nsrt_mk3_dev.NsrtMk3Dev('/dev/ttyACM0')
|
|
||||||
|
|
||||||
#####################
|
|
||||||
#change time constant
|
|
||||||
nsrt.write_tau(1)
|
|
||||||
#####################
|
|
||||||
|
|
||||||
#####################
|
|
||||||
#change Weighting curve
|
|
||||||
# - Weighting.DB_A (A-weighting - most common for environmental noise)
|
|
||||||
# - Weighting.DB_C (C-weighting - for peak measurements)
|
|
||||||
# - Weighting.DB_Z (Z-weighting - linear/flat response)
|
|
||||||
nsrt.write_weighting(Weighting.DB_A)
|
|
||||||
#####################
|
|
||||||
|
|
||||||
freq_level = nsrt.read_fs() #current sampling frequency
|
|
||||||
time_constant = nsrt.read_tau() #reads the current time constant
|
|
||||||
leq_level = nsrt.read_leq() #current running LEQ and starts the integration of a new LEQ.
|
|
||||||
weighting = nsrt.read_weighting() #weighting curve that is currently selected
|
|
||||||
weighted_level = nsrt.read_level() #current running level in dB.
|
|
||||||
|
|
||||||
print(f'current sampling freq : {freq_level} Hz')
|
|
||||||
print(f'current time constant : {time_constant} s')
|
|
||||||
print(f'current LEQ level: {leq_level:0.2f} dB')
|
|
||||||
print(f'{weighting} value: {weighted_level:0.2f} dBA')
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
'''
|
|
||||||
____ ___ _ _ _ _ ____
|
|
||||||
/ ___| / _ \| | | | \ | | _ \
|
|
||||||
\___ \| | | | | | | \| | | | |
|
|
||||||
___) | |_| | |_| | |\ | |_| |
|
|
||||||
|____/ \___/ \___/|_| \_|____/
|
|
||||||
|
|
||||||
python3 /var/www/nebuleair_pro_4g/sound_meter/NSRT_mk4_get_data.py
|
|
||||||
|
|
||||||
Script to get data from the NSRT_MK4 Sound Level Meter
|
|
||||||
|
|
||||||
triggered by a systemd service
|
|
||||||
sudo systemctl status nebuleair-noise-data.service
|
|
||||||
|
|
||||||
Need to install "nsrt_mk3_dev"
|
|
||||||
|
|
||||||
1.Intervalle d'enregistrement
|
|
||||||
L'intervalle d'enregistrement définit le temps entre deux points successifs enregistrés.
|
|
||||||
Cela définit également la période d'intégration pour le LEQ, et la période d'observation pour L-min et L-max et Lpeak.
|
|
||||||
L'intervalle d'enregistrement peut être réglé de 125 ms (1/8ème) à 2 H par incréments de 125 ms.
|
|
||||||
|
|
||||||
some parameters can be changed:
|
|
||||||
write_tau(tau: float) -> time constant
|
|
||||||
write_fs(frequency: int) -> sampling freq
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import nsrt_mk3_dev
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
nsrt = nsrt_mk3_dev.NsrtMk3Dev('/dev/ttyACM0')
|
|
||||||
|
|
||||||
# Connect to the SQLite database
|
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
#GET RTC TIME from SQlite
|
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
|
||||||
row = cursor.fetchone() # Get the first (and only) row
|
|
||||||
rtc_time_str = row[1] # '2025-02-07 12:30:45'
|
|
||||||
|
|
||||||
freq_level = nsrt.read_fs() #current sampling frequency
|
|
||||||
time_constant = nsrt.read_tau() #reads the current time constant
|
|
||||||
leq_level = nsrt.read_leq() #current running LEQ and starts the integration of a new LEQ.
|
|
||||||
weighting = nsrt.read_weighting() #weighting curve that is currently selected ( A ou C)
|
|
||||||
weighted_level = nsrt.read_level() #current running level in dB.
|
|
||||||
|
|
||||||
#print(f'current sampling freq : {freq_level} Hz')
|
|
||||||
#print(f'current time constant : {time_constant} s')
|
|
||||||
#print(f'current LEQ level: {leq_level:0.2f} dB')
|
|
||||||
#print(f'{weighting} value: {weighted_level:0.2f} dBA')
|
|
||||||
# Round values to 2 decimal places before saving
|
|
||||||
leq_level_rounded = round(leq_level, 2)
|
|
||||||
weighted_level_rounded = round(weighted_level, 2)
|
|
||||||
|
|
||||||
#save to db
|
|
||||||
#save to sqlite database
|
|
||||||
try:
|
|
||||||
cursor.execute('''
|
|
||||||
INSERT INTO data_NOISE (timestamp,current_LEQ, DB_A_value) VALUES (?,?,?)'''
|
|
||||||
, (rtc_time_str,leq_level_rounded,weighted_level_rounded))
|
|
||||||
|
|
||||||
# Commit and close the connection
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
#print("Sensor data saved successfully!")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Database error: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
conn.close()
|
|
||||||
0
sound_meter/old/sound_meter → sound_meter/sound_meter
Normal file → Executable file
0
sound_meter/old/sound_meter → sound_meter/sound_meter
Normal file → Executable file
0
sound_meter/old/sound_meter.c → sound_meter/sound_meter.c
Normal file → Executable file
0
sound_meter/old/sound_meter.c → sound_meter/sound_meter.c
Normal file → Executable file
0
sound_meter/old/sound_meter_moving_avg → sound_meter/sound_meter_moving_avg
Normal file → Executable file
0
sound_meter/old/sound_meter_moving_avg → sound_meter/sound_meter_moving_avg
Normal file → Executable file
0
sound_meter/old/sound_meter_moving_avg.c → sound_meter/sound_meter_moving_avg.c
Normal file → Executable file
0
sound_meter/old/sound_meter_moving_avg.c → sound_meter/sound_meter_moving_avg.c
Normal file → Executable file
0
sound_meter/old/sound_meter_nonStop → sound_meter/sound_meter_nonStop
Normal file → Executable file
0
sound_meter/old/sound_meter_nonStop → sound_meter/sound_meter_nonStop
Normal file → Executable file
0
sound_meter/old/sound_meter_nonStop.c → sound_meter/sound_meter_nonStop.c
Normal file → Executable file
0
sound_meter/old/sound_meter_nonStop.c → sound_meter/sound_meter_nonStop.c
Normal file → Executable file
@@ -18,26 +18,6 @@ import sqlite3
|
|||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
#create a config table
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS config_table (
|
|
||||||
key TEXT PRIMARY KEY,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
type TEXT NOT NULL
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
#creates a config table for envea sondes
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS envea_sondes_table (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
connected INTEGER NOT NULL,
|
|
||||||
port TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
coefficient REAL NOT NULL
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Create a table timer
|
# Create a table timer
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS timestamp_table (
|
CREATE TABLE IF NOT EXISTS timestamp_table (
|
||||||
@@ -50,14 +30,7 @@ cursor.execute("""
|
|||||||
VALUES (1, CURRENT_TIMESTAMP);
|
VALUES (1, CURRENT_TIMESTAMP);
|
||||||
""")
|
""")
|
||||||
|
|
||||||
#create a modem status table
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS modem_status (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
timestamp TEXT,
|
|
||||||
status TEXT
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Create a table NPM
|
# Create a table NPM
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
@@ -89,8 +62,7 @@ CREATE TABLE IF NOT EXISTS data_envea (
|
|||||||
h2s REAL,
|
h2s REAL,
|
||||||
nh3 REAL,
|
nh3 REAL,
|
||||||
co REAL,
|
co REAL,
|
||||||
o3 REAL,
|
o3 REAL
|
||||||
so2 REAL
|
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
@@ -106,35 +78,7 @@ CREATE TABLE IF NOT EXISTS data_NPM_5channels (
|
|||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
# Create a table WIND
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS data_WIND (
|
|
||||||
timestamp TEXT,
|
|
||||||
wind_speed REAL,
|
|
||||||
wind_direction REAL
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Create a table MPPT
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS data_MPPT (
|
|
||||||
timestamp TEXT,
|
|
||||||
battery_voltage REAL,
|
|
||||||
battery_current REAL,
|
|
||||||
solar_voltage REAL,
|
|
||||||
solar_power REAL,
|
|
||||||
charger_status INTEGER
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Create a table noise capture (NSRT mk4)
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS data_NOISE (
|
|
||||||
timestamp TEXT,
|
|
||||||
current_LEQ REAL,
|
|
||||||
DB_A_value REAL
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
|
|
||||||
# Commit and close the connection
|
# Commit and close the connection
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
232
sqlite/delete.py
232
sqlite/delete.py
@@ -1,232 +0,0 @@
|
|||||||
'''
|
|
||||||
____ ___ _ _ _
|
|
||||||
/ ___| / _ \| | (_) |_ ___
|
|
||||||
\___ \| | | | | | | __/ _ \
|
|
||||||
___) | |_| | |___| | || __/
|
|
||||||
|____/ \__\_\_____|_|\__\___|
|
|
||||||
|
|
||||||
Script to delete a table from sqlite database
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/delete.py table_name [--confirm]
|
|
||||||
|
|
||||||
Available tables are:
|
|
||||||
data_NPM
|
|
||||||
data_NPM_5channels
|
|
||||||
data_BME280
|
|
||||||
data_envea
|
|
||||||
timestamp_table
|
|
||||||
data_MPPT
|
|
||||||
data_WIND
|
|
||||||
modem_status
|
|
||||||
config_table
|
|
||||||
envea_sondes_table
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
# Will ask for confirmation
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/delete.py data_NPM
|
|
||||||
|
|
||||||
# Skip confirmation prompt
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/delete.py data_NPM --confirm
|
|
||||||
|
|
||||||
# List all tables
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/delete.py --list
|
|
||||||
'''
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
def list_tables(cursor):
|
|
||||||
"""List all tables in the database"""
|
|
||||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
||||||
tables = cursor.fetchall()
|
|
||||||
|
|
||||||
print("\n📋 Available tables:")
|
|
||||||
print("-" * 40)
|
|
||||||
for table in tables:
|
|
||||||
# Get row count for each table
|
|
||||||
cursor.execute(f"SELECT COUNT(*) FROM {table[0]}")
|
|
||||||
count = cursor.fetchone()[0]
|
|
||||||
print(f" {table[0]} ({count} rows)")
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
def get_table_info(cursor, table_name):
|
|
||||||
"""Get information about a table"""
|
|
||||||
try:
|
|
||||||
# Check if table exists
|
|
||||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
|
||||||
if not cursor.fetchone():
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Get row count
|
|
||||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
|
|
||||||
row_count = cursor.fetchone()[0]
|
|
||||||
|
|
||||||
# Get table schema
|
|
||||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
||||||
columns = cursor.fetchall()
|
|
||||||
|
|
||||||
return {
|
|
||||||
'row_count': row_count,
|
|
||||||
'columns': columns
|
|
||||||
}
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"Error getting table info: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def backup_table(cursor, table_name, db_path):
|
|
||||||
"""Create a backup of the table before deletion"""
|
|
||||||
try:
|
|
||||||
backup_dir = os.path.dirname(db_path)
|
|
||||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
||||||
backup_file = os.path.join(backup_dir, f"{table_name}_backup_{timestamp}.sql")
|
|
||||||
|
|
||||||
# Get table schema
|
|
||||||
cursor.execute(f"SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
|
||||||
create_sql = cursor.fetchone()
|
|
||||||
|
|
||||||
if create_sql:
|
|
||||||
with open(backup_file, 'w') as f:
|
|
||||||
# Write table creation SQL
|
|
||||||
f.write(f"-- Backup of table {table_name} created on {datetime.now()}\n")
|
|
||||||
f.write(f"{create_sql[0]};\n\n")
|
|
||||||
|
|
||||||
# Write data
|
|
||||||
cursor.execute(f"SELECT * FROM {table_name}")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
|
|
||||||
if rows:
|
|
||||||
# Get column names
|
|
||||||
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
||||||
columns = [col[1] for col in cursor.fetchall()]
|
|
||||||
|
|
||||||
f.write(f"-- Data for table {table_name}\n")
|
|
||||||
for row in rows:
|
|
||||||
values = []
|
|
||||||
for value in row:
|
|
||||||
if value is None:
|
|
||||||
values.append('NULL')
|
|
||||||
elif isinstance(value, str):
|
|
||||||
escaped_value = value.replace("'", "''")
|
|
||||||
values.append(f"'{escaped_value}'")
|
|
||||||
else:
|
|
||||||
values.append(str(value))
|
|
||||||
|
|
||||||
f.write(f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES ({', '.join(values)});\n")
|
|
||||||
|
|
||||||
print(f"✓ Table backed up to: {backup_file}")
|
|
||||||
return backup_file
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Backup failed: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def delete_table(cursor, table_name, create_backup=True, db_path=None):
|
|
||||||
"""Delete a table from the database"""
|
|
||||||
|
|
||||||
# Get table info first
|
|
||||||
table_info = get_table_info(cursor, table_name)
|
|
||||||
if not table_info:
|
|
||||||
print(f"❌ Table '{table_name}' does not exist!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
print(f"\n📊 Table Information:")
|
|
||||||
print(f" Name: {table_name}")
|
|
||||||
print(f" Rows: {table_info['row_count']}")
|
|
||||||
print(f" Columns: {len(table_info['columns'])}")
|
|
||||||
|
|
||||||
# Create backup if requested
|
|
||||||
backup_file = None
|
|
||||||
if create_backup and db_path:
|
|
||||||
print(f"\n💾 Creating backup...")
|
|
||||||
backup_file = backup_table(cursor, table_name, db_path)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Delete the table
|
|
||||||
cursor.execute(f"DROP TABLE {table_name}")
|
|
||||||
print(f"\n✅ Table '{table_name}' deleted successfully!")
|
|
||||||
|
|
||||||
if backup_file:
|
|
||||||
print(f" Backup saved: {backup_file}")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"❌ Error deleting table: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: python3 delete_table.py <table_name> [--confirm] [--no-backup]")
|
|
||||||
print(" python3 delete_table.py --list")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
db_path = "/var/www/nebuleair_pro_4g/sqlite/sensors.db"
|
|
||||||
|
|
||||||
# Check if database exists
|
|
||||||
if not os.path.exists(db_path):
|
|
||||||
print(f"❌ Database not found: {db_path}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
args = sys.argv[1:]
|
|
||||||
|
|
||||||
if '--list' in args:
|
|
||||||
# List all tables
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
list_tables(cursor)
|
|
||||||
conn.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
table_name = args[0]
|
|
||||||
skip_confirmation = '--confirm' in args
|
|
||||||
create_backup = '--no-backup' not in args
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Connect to database
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# List available tables first
|
|
||||||
list_tables(cursor)
|
|
||||||
|
|
||||||
# Check if table exists
|
|
||||||
table_info = get_table_info(cursor, table_name)
|
|
||||||
if not table_info:
|
|
||||||
print(f"\n❌ Table '{table_name}' does not exist!")
|
|
||||||
conn.close()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Confirmation prompt
|
|
||||||
if not skip_confirmation:
|
|
||||||
print(f"\n⚠️ WARNING: You are about to delete table '{table_name}'")
|
|
||||||
print(f" This table contains {table_info['row_count']} rows")
|
|
||||||
if create_backup:
|
|
||||||
print(f" A backup will be created before deletion")
|
|
||||||
else:
|
|
||||||
print(f" NO BACKUP will be created (--no-backup flag used)")
|
|
||||||
|
|
||||||
response = input(f"\nAre you sure you want to delete '{table_name}'? (yes/no): ").lower().strip()
|
|
||||||
|
|
||||||
if response not in ['yes', 'y']:
|
|
||||||
print("❌ Operation cancelled")
|
|
||||||
conn.close()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Perform deletion
|
|
||||||
success = delete_table(cursor, table_name, create_backup, db_path)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
conn.commit()
|
|
||||||
print(f"\n🎉 Operation completed successfully!")
|
|
||||||
else:
|
|
||||||
print(f"\n❌ Operation failed!")
|
|
||||||
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -9,9 +9,6 @@ Script to flush (delete) data from a sqlite database
|
|||||||
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/flush_old_data.py
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/flush_old_data.py
|
||||||
|
|
||||||
Script that is triggered by a systemd
|
|
||||||
sudo systemctl status nebuleair-db-cleanup-data.service
|
|
||||||
|
|
||||||
Available table are
|
Available table are
|
||||||
|
|
||||||
data_NPM
|
data_NPM
|
||||||
@@ -19,184 +16,56 @@ data_NPM_5channels
|
|||||||
data_BME280
|
data_BME280
|
||||||
data_envea
|
data_envea
|
||||||
timestamp_table
|
timestamp_table
|
||||||
data_MPPT
|
|
||||||
data_NOISE
|
|
||||||
data_WIND
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def table_exists(cursor, table_name):
|
# Connect to the SQLite database
|
||||||
"""Check if a table exists in the database"""
|
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||||
try:
|
cursor = conn.cursor()
|
||||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
|
||||||
return cursor.fetchone() is not None
|
#GET RTC TIME from SQlite
|
||||||
except sqlite3.Error as e:
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
print(f"[ERROR] Failed to check if table '{table_name}' exists: {e}")
|
row = cursor.fetchone() # Get the first (and only) row
|
||||||
return False
|
|
||||||
|
if row:
|
||||||
|
rtc_time_str = row[1] # Assuming timestamp is stored as TEXT (YYYY-MM-DD HH:MM:SS)
|
||||||
|
print(f"[INFO] Last recorded timestamp: {rtc_time_str}")
|
||||||
|
|
||||||
|
# Convert last_updated to a datetime object
|
||||||
|
last_updated = datetime.datetime.strptime(rtc_time_str, "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
# Calculate the cutoff date (3 months before last_updated)
|
||||||
|
cutoff_date = last_updated - datetime.timedelta(days=60)
|
||||||
|
cutoff_date_str = cutoff_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
print(f"[INFO] Deleting records older than: {cutoff_date_str}")
|
||||||
|
|
||||||
|
# List of tables to delete old data from
|
||||||
|
tables_to_clean = ["data_NPM", "data_NPM_5channels", "data_BME280", "data_envea"]
|
||||||
|
|
||||||
|
# Loop through each table and delete old data
|
||||||
|
for table in tables_to_clean:
|
||||||
|
delete_query = f"DELETE FROM {table} WHERE timestamp < ?"
|
||||||
|
cursor.execute(delete_query, (cutoff_date_str,))
|
||||||
|
print(f"[INFO] Deleted old records from {table}")
|
||||||
|
|
||||||
|
# **Commit changes before running VACUUM**
|
||||||
|
conn.commit()
|
||||||
|
print("[INFO] Changes committed successfully!")
|
||||||
|
|
||||||
|
# Now it's safe to run VACUUM
|
||||||
|
print("[INFO] Running VACUUM to optimize database space...")
|
||||||
|
cursor.execute("VACUUM")
|
||||||
|
|
||||||
|
print("[SUCCESS] Old data flushed successfully!")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("[ERROR] No timestamp found in timestamp_table.")
|
||||||
|
|
||||||
|
|
||||||
def get_table_count(cursor, table_name):
|
# Close the database connection
|
||||||
"""Get the number of records in a table"""
|
conn.close()
|
||||||
try:
|
|
||||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
|
|
||||||
return cursor.fetchone()[0]
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"[WARNING] Could not get count for table '{table_name}': {e}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def delete_old_records(cursor, table_name, cutoff_date_str):
|
|
||||||
"""Delete old records from a specific table"""
|
|
||||||
try:
|
|
||||||
# First check how many records will be deleted
|
|
||||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name} WHERE timestamp < ?", (cutoff_date_str,))
|
|
||||||
records_to_delete = cursor.fetchone()[0]
|
|
||||||
|
|
||||||
if records_to_delete == 0:
|
|
||||||
print(f"[INFO] No old records to delete from '{table_name}'")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Delete the records
|
|
||||||
cursor.execute(f"DELETE FROM {table_name} WHERE timestamp < ?", (cutoff_date_str,))
|
|
||||||
deleted_count = cursor.rowcount
|
|
||||||
|
|
||||||
print(f"[SUCCESS] Deleted {deleted_count} old records from '{table_name}'")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"[ERROR] Failed to delete records from '{table_name}': {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
# Connect to the SQLite database
|
|
||||||
print("[INFO] Connecting to database...")
|
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Check database connection
|
|
||||||
cursor.execute("SELECT sqlite_version()")
|
|
||||||
version = cursor.fetchone()[0]
|
|
||||||
print(f"[INFO] Connected to SQLite version: {version}")
|
|
||||||
|
|
||||||
# GET RTC TIME from SQLite
|
|
||||||
print("[INFO] Getting timestamp from database...")
|
|
||||||
|
|
||||||
# First check if timestamp_table exists
|
|
||||||
if not table_exists(cursor, "timestamp_table"):
|
|
||||||
print("[ERROR] timestamp_table does not exist!")
|
|
||||||
return False
|
|
||||||
|
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
|
||||||
row = cursor.fetchone()
|
|
||||||
|
|
||||||
if not row:
|
|
||||||
print("[ERROR] No timestamp found in timestamp_table.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
rtc_time_str = row[1] # Assuming timestamp is stored as TEXT (YYYY-MM-DD HH:MM:SS)
|
|
||||||
print(f"[INFO] Last recorded timestamp: {rtc_time_str}")
|
|
||||||
|
|
||||||
# Convert last_updated to a datetime object
|
|
||||||
try:
|
|
||||||
last_updated = datetime.datetime.strptime(rtc_time_str, "%Y-%m-%d %H:%M:%S")
|
|
||||||
except ValueError as e:
|
|
||||||
print(f"[ERROR] Invalid timestamp format: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Calculate the cutoff date (60 days before last_updated)
|
|
||||||
cutoff_date = last_updated - datetime.timedelta(days=60)
|
|
||||||
cutoff_date_str = cutoff_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
print(f"[INFO] Deleting records older than: {cutoff_date_str}")
|
|
||||||
|
|
||||||
# List of tables to delete old data from
|
|
||||||
tables_to_clean = [
|
|
||||||
"data_NPM",
|
|
||||||
"data_NPM_5channels",
|
|
||||||
"data_BME280",
|
|
||||||
"data_envea",
|
|
||||||
"data_WIND",
|
|
||||||
"data_MPPT",
|
|
||||||
"data_NOISE"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Check which tables actually exist
|
|
||||||
existing_tables = []
|
|
||||||
missing_tables = []
|
|
||||||
|
|
||||||
for table in tables_to_clean:
|
|
||||||
if table_exists(cursor, table):
|
|
||||||
existing_tables.append(table)
|
|
||||||
record_count = get_table_count(cursor, table)
|
|
||||||
print(f"[INFO] Table '{table}' exists with {record_count} records")
|
|
||||||
else:
|
|
||||||
missing_tables.append(table)
|
|
||||||
print(f"[WARNING] Table '{table}' does not exist - skipping")
|
|
||||||
|
|
||||||
if missing_tables:
|
|
||||||
print(f"[INFO] Missing tables: {', '.join(missing_tables)}")
|
|
||||||
|
|
||||||
if not existing_tables:
|
|
||||||
print("[WARNING] No tables found to clean!")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Loop through existing tables and delete old data
|
|
||||||
successful_deletions = 0
|
|
||||||
failed_deletions = 0
|
|
||||||
|
|
||||||
for table in existing_tables:
|
|
||||||
if delete_old_records(cursor, table, cutoff_date_str):
|
|
||||||
successful_deletions += 1
|
|
||||||
else:
|
|
||||||
failed_deletions += 1
|
|
||||||
|
|
||||||
# Commit changes before running VACUUM
|
|
||||||
print("[INFO] Committing changes...")
|
|
||||||
conn.commit()
|
|
||||||
print("[SUCCESS] Changes committed successfully!")
|
|
||||||
|
|
||||||
# Only run VACUUM if at least some deletions were successful
|
|
||||||
if successful_deletions > 0:
|
|
||||||
print("[INFO] Running VACUUM to optimize database space...")
|
|
||||||
try:
|
|
||||||
cursor.execute("VACUUM")
|
|
||||||
print("[SUCCESS] Database optimized successfully!")
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"[WARNING] VACUUM failed: {e}")
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print(f"\n[SUMMARY]")
|
|
||||||
print(f"Tables processed successfully: {successful_deletions}")
|
|
||||||
print(f"Tables with errors: {failed_deletions}")
|
|
||||||
print(f"Tables skipped (missing): {len(missing_tables)}")
|
|
||||||
|
|
||||||
if failed_deletions == 0:
|
|
||||||
print("[SUCCESS] Old data flushed successfully!")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print("[WARNING] Some operations failed - check logs above")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
print(f"[ERROR] Database error: {e}")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[ERROR] Unexpected error: {e}")
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
# Always close the database connection
|
|
||||||
if 'conn' in locals():
|
|
||||||
conn.close()
|
|
||||||
print("[INFO] Database connection closed")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
success = main()
|
|
||||||
sys.exit(0 if success else 1)
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ data_NPM_5channels
|
|||||||
data_BME280
|
data_BME280
|
||||||
data_envea
|
data_envea
|
||||||
timestamp_table
|
timestamp_table
|
||||||
data_MPPT
|
|
||||||
data_WIND
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -36,6 +34,7 @@ cursor = conn.cursor()
|
|||||||
#cursor.execute("SELECT * FROM timestamp_table")
|
#cursor.execute("SELECT * FROM timestamp_table")
|
||||||
if table_name == "timestamp_table":
|
if table_name == "timestamp_table":
|
||||||
cursor.execute("SELECT * FROM timestamp_table")
|
cursor.execute("SELECT * FROM timestamp_table")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
query = f"SELECT * FROM {table_name} ORDER BY timestamp DESC LIMIT ?"
|
query = f"SELECT * FROM {table_name} ORDER BY timestamp DESC LIMIT ?"
|
||||||
cursor.execute(query, (limit_num,))
|
cursor.execute(query, (limit_num,))
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
'''
|
|
||||||
____ ___ _ _ _
|
|
||||||
/ ___| / _ \| | (_) |_ ___
|
|
||||||
\___ \| | | | | | | __/ _ \
|
|
||||||
___) | |_| | |___| | || __/
|
|
||||||
|____/ \__\_\_____|_|\__\___|
|
|
||||||
|
|
||||||
Script to read data from a sqlite database
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read_config.py config_table
|
|
||||||
|
|
||||||
Available table are
|
|
||||||
config_table
|
|
||||||
config_scripts_table
|
|
||||||
envea_sondes_table
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
import sys
|
|
||||||
parameter = sys.argv[1:] # Exclude the script name
|
|
||||||
#print("Parameters received:")
|
|
||||||
table_name=parameter[0]
|
|
||||||
|
|
||||||
|
|
||||||
# Connect to the SQLite database
|
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Retrieve the data
|
|
||||||
query = f"SELECT * FROM {table_name}"
|
|
||||||
cursor.execute(query)
|
|
||||||
|
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
rows.reverse() # Reverse the order in Python (to get ascending order)
|
|
||||||
|
|
||||||
|
|
||||||
# Display the results
|
|
||||||
for row in rows:
|
|
||||||
print(row)
|
|
||||||
|
|
||||||
# Close the database connection
|
|
||||||
conn.close()
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
'''
|
|
||||||
____ ___ _ _ _
|
|
||||||
/ ___| / _ \| | (_) |_ ___
|
|
||||||
\___ \| | | | | | | __/ _ \
|
|
||||||
___) | |_| | |___| | || __/
|
|
||||||
|____/ \__\_\_____|_|\__\___|
|
|
||||||
|
|
||||||
Script to set the config
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
|
|
||||||
|
|
||||||
in case of readonly error:
|
|
||||||
sudo chmod 777 /var/www/nebuleair_pro_4g/sqlite/sensors.db
|
|
||||||
'''
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
# Connect to (or create if not existent) the database
|
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
print(f"Connected to database")
|
|
||||||
|
|
||||||
# Note: Using INSERT OR IGNORE to add only new configurations without overwriting existing ones
|
|
||||||
print("Adding new configurations (existing ones will be preserved)")
|
|
||||||
|
|
||||||
|
|
||||||
# Insert general configurations
|
|
||||||
config_entries = [
|
|
||||||
("modem_config_mode", "0", "bool"),
|
|
||||||
("deviceID", "XXXX", "str"),
|
|
||||||
("latitude_raw", "0", "int"),
|
|
||||||
("longitude_raw", "0", "int"),
|
|
||||||
("latitude_precision", "0", "int"),
|
|
||||||
("longitude_precision", "0", "int"),
|
|
||||||
("deviceName", "NebuleAir-proXXX", "str"),
|
|
||||||
("SaraR4_baudrate", "115200", "int"),
|
|
||||||
("NPM_solo_port", "/dev/ttyAMA5", "str"),
|
|
||||||
("sshTunnel_port", "59228", "int"),
|
|
||||||
("SARA_R4_general_status", "connected", "str"),
|
|
||||||
("SARA_R4_SIM_status", "connected", "str"),
|
|
||||||
("SARA_R4_network_status", "connected", "str"),
|
|
||||||
("SARA_R4_neworkID", "20810", "int"),
|
|
||||||
("WIFI_status", "connected", "str"),
|
|
||||||
("send_aircarto", "1", "bool"),
|
|
||||||
("send_uSpot", "0", "bool"),
|
|
||||||
("send_miotiq", "0", "bool"),
|
|
||||||
("npm_5channel", "0", "bool"),
|
|
||||||
("envea", "0", "bool"),
|
|
||||||
("windMeter", "0", "bool"),
|
|
||||||
("BME280", "0", "bool"),
|
|
||||||
("MPPT", "0", "bool"),
|
|
||||||
("NOISE", "0", "bool"),
|
|
||||||
("modem_version", "XXX", "str")
|
|
||||||
]
|
|
||||||
|
|
||||||
for key, value, value_type in config_entries:
|
|
||||||
cursor.execute(
|
|
||||||
"INSERT OR IGNORE INTO config_table (key, value, type) VALUES (?, ?, ?)",
|
|
||||||
(key, value, value_type)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Clean up duplicate envea sondes first (keep only first occurrence of each name)
|
|
||||||
print("Cleaning up duplicate envea sondes...")
|
|
||||||
cursor.execute("""
|
|
||||||
DELETE FROM envea_sondes_table
|
|
||||||
WHERE id NOT IN (
|
|
||||||
SELECT MIN(id)
|
|
||||||
FROM envea_sondes_table
|
|
||||||
GROUP BY name
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
deleted_count = cursor.rowcount
|
|
||||||
if deleted_count > 0:
|
|
||||||
print(f"Deleted {deleted_count} duplicate envea sonde entries")
|
|
||||||
|
|
||||||
# Insert envea sondes (only if they don't already exist)
|
|
||||||
# Attention pour le H2S il y a plusieurs sondes
|
|
||||||
# H2S 1ppm -> coef 4
|
|
||||||
# H2S 20ppm -> coef 1
|
|
||||||
# H2S 200ppm -> coef 10
|
|
||||||
|
|
||||||
envea_sondes = [
|
|
||||||
(False, "ttyAMA4", "h2s", 4), #H2S
|
|
||||||
(False, "ttyAMA3", "no2", 1),
|
|
||||||
(False, "ttyAMA3", "nh3", 100),
|
|
||||||
(False, "ttyAMA3", "so2", 4),
|
|
||||||
(False, "ttyAMA2", "o3", 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
for connected, port, name, coefficient in envea_sondes:
|
|
||||||
# Check if sensor with this name already exists
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM envea_sondes_table WHERE name = ?", (name,))
|
|
||||||
exists = cursor.fetchone()[0] > 0
|
|
||||||
|
|
||||||
if not exists:
|
|
||||||
cursor.execute(
|
|
||||||
"INSERT INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
|
|
||||||
(1 if connected else 0, port, name, coefficient)
|
|
||||||
)
|
|
||||||
print(f"Added envea sonde: {name}")
|
|
||||||
else:
|
|
||||||
print(f"Envea sonde '{name}' already exists, skipping")
|
|
||||||
|
|
||||||
|
|
||||||
# Commit and close the connection
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
print("Database updated successfully!")
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# NebuleAir Pro 4G - Comprehensive Update Script
|
|
||||||
# This script performs a complete system update including git pull,
|
|
||||||
# config initialization, and service management
|
|
||||||
# Non-interactive version for WebUI
|
|
||||||
|
|
||||||
echo "======================================"
|
|
||||||
echo "NebuleAir Pro 4G - Firmware Update"
|
|
||||||
echo "======================================"
|
|
||||||
echo "Started at: $(date)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
cd /var/www/nebuleair_pro_4g
|
|
||||||
|
|
||||||
# Ensure this script is executable
|
|
||||||
chmod +x /var/www/nebuleair_pro_4g/update_firmware.sh
|
|
||||||
|
|
||||||
# Function to print status messages
|
|
||||||
print_status() {
|
|
||||||
echo "[$(date '+%H:%M:%S')] $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check command success
|
|
||||||
check_status() {
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
print_status "✓ $1 completed successfully"
|
|
||||||
else
|
|
||||||
print_status "✗ $1 failed"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Step 1: Git operations
|
|
||||||
print_status "Step 1: Updating firmware from repository..."
|
|
||||||
|
|
||||||
# Disable filemode to prevent permission issues
|
|
||||||
git -C /var/www/nebuleair_pro_4g config core.fileMode false
|
|
||||||
check_status "Git fileMode disabled"
|
|
||||||
|
|
||||||
# Fetch latest changes
|
|
||||||
git fetch origin
|
|
||||||
check_status "Git fetch"
|
|
||||||
|
|
||||||
# Show current branch
|
|
||||||
print_status "Current branch: $(git branch --show-current)"
|
|
||||||
|
|
||||||
# Check for local changes
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
|
||||||
print_status "Warning: Local changes detected, stashing..."
|
|
||||||
git stash push -m "Auto-stash before update $(date)"
|
|
||||||
check_status "Git stash"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Pull latest changes
|
|
||||||
git pull origin $(git branch --show-current)
|
|
||||||
check_status "Git pull"
|
|
||||||
|
|
||||||
# Step 2: Update database configuration
|
|
||||||
print_status ""
|
|
||||||
print_status "Step 2: Updating database configuration..."
|
|
||||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
|
|
||||||
check_status "Database configuration update"
|
|
||||||
|
|
||||||
# Step 3: Check and fix file permissions
|
|
||||||
print_status ""
|
|
||||||
print_status "Step 3: Checking file permissions..."
|
|
||||||
sudo chmod +x /var/www/nebuleair_pro_4g/update_firmware.sh
|
|
||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/sqlite/*.py
|
|
||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/NPM/*.py
|
|
||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/BME280/*.py
|
|
||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/SARA/*.py
|
|
||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/envea/*.py
|
|
||||||
sudo chmod 755 /var/www/nebuleair_pro_4g/MPPT/*.py 2>/dev/null
|
|
||||||
check_status "File permissions update"
|
|
||||||
|
|
||||||
# Step 4: Restart critical services if they exist
|
|
||||||
print_status ""
|
|
||||||
print_status "Step 4: Managing system services..."
|
|
||||||
|
|
||||||
# List of services to check and restart
|
|
||||||
services=(
|
|
||||||
"nebuleair-npm-data.timer"
|
|
||||||
"nebuleair-envea-data.timer"
|
|
||||||
"nebuleair-sara-data.timer"
|
|
||||||
"nebuleair-bme280-data.timer"
|
|
||||||
"nebuleair-mppt-data.timer"
|
|
||||||
"nebuleair-noise-data.timer"
|
|
||||||
)
|
|
||||||
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
if systemctl list-unit-files | grep -q "$service"; then
|
|
||||||
# Check if service is enabled before restarting
|
|
||||||
if systemctl is-enabled --quiet "$service" 2>/dev/null; then
|
|
||||||
print_status "Restarting enabled service: $service"
|
|
||||||
sudo systemctl restart "$service"
|
|
||||||
if systemctl is-active --quiet "$service"; then
|
|
||||||
print_status "✓ $service is running"
|
|
||||||
else
|
|
||||||
print_status "⚠ $service failed to start"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_status "ℹ Service $service is disabled, skipping restart"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_status "ℹ Service $service not found (may not be installed)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Step 5: System health check
|
|
||||||
print_status ""
|
|
||||||
print_status "Step 5: System health check..."
|
|
||||||
|
|
||||||
# Check disk space
|
|
||||||
disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
|
|
||||||
if [ "$disk_usage" -gt 90 ]; then
|
|
||||||
print_status "⚠ Warning: Disk usage is high ($disk_usage%)"
|
|
||||||
else
|
|
||||||
print_status "✓ Disk usage is acceptable ($disk_usage%)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if database is accessible
|
|
||||||
if [ -f "/var/www/nebuleair_pro_4g/sqlite/sensors.db" ]; then
|
|
||||||
print_status "✓ Database file exists"
|
|
||||||
else
|
|
||||||
print_status "⚠ Warning: Database file not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 6: Final cleanup
|
|
||||||
print_status ""
|
|
||||||
print_status "Step 6: Cleaning up..."
|
|
||||||
sudo find /var/www/nebuleair_pro_4g/logs -name "*.log" -size +10M -exec truncate -s 0 {} \;
|
|
||||||
check_status "Log cleanup"
|
|
||||||
|
|
||||||
print_status ""
|
|
||||||
print_status "======================================"
|
|
||||||
print_status "Update completed successfully!"
|
|
||||||
print_status "======================================"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
'''
|
|
||||||
Script to test the abs115 an analog-to-digital converter
|
|
||||||
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/ads115.py
|
|
||||||
|
|
||||||
'''
|
|
||||||
import time
|
|
||||||
import board
|
|
||||||
import busio
|
|
||||||
import adafruit_ads1x15.ads1115 as ADS
|
|
||||||
from adafruit_ads1x15.analog_in import AnalogIn
|
|
||||||
|
|
||||||
i2c = busio.I2C(board.SCL, board.SDA)
|
|
||||||
ads = ADS.ADS1115(i2c)
|
|
||||||
channel = AnalogIn(ads, ADS.P0)
|
|
||||||
|
|
||||||
print("Testing ADS1115 readings...")
|
|
||||||
readings = []
|
|
||||||
|
|
||||||
for i in range(5):
|
|
||||||
voltage = channel.voltage
|
|
||||||
readings.append(voltage)
|
|
||||||
print(f"Voltage: {voltage:.6f}V")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# Calculate and display the mean
|
|
||||||
mean_voltage = sum(readings) / len(readings)
|
|
||||||
print(f"\nMean voltage: {mean_voltage:.6f}V")
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
'''
|
|
||||||
__ _____ _ _ ____
|
|
||||||
\ \ / /_ _| \ | | _ \
|
|
||||||
\ \ /\ / / | || \| | | | |
|
|
||||||
\ V V / | || |\ | |_| |
|
|
||||||
\_/\_/ |___|_| \_|____/
|
|
||||||
|
|
||||||
Script to read wind speed from a Davis Anémomètre-girouette Vantage Pro (6410)
|
|
||||||
https://www.shapemaker.io/blog/wind-speed-measurements-with-anemometer-and-a-raspberry-pi
|
|
||||||
|
|
||||||
Connexion:
|
|
||||||
black (wind speed ) -> gpio21
|
|
||||||
green (wind direction) -> ADS1115 (module I2C)
|
|
||||||
Yellow -> 5v
|
|
||||||
RED -> GND
|
|
||||||
|
|
||||||
Attention: The Raspberry Pi doesn't have analog inputs, so we need an analog-to-digital converter (ADC) to read the wind direction.
|
|
||||||
|
|
||||||
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read.py
|
|
||||||
|
|
||||||
this need to run as a service
|
|
||||||
|
|
||||||
--> sudo nano /etc/systemd/system/windMeter.service
|
|
||||||
|
|
||||||
⬇️
|
|
||||||
[Unit]
|
|
||||||
Description=Master manager for the Python wind meter scripts
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read.py
|
|
||||||
Restart=always
|
|
||||||
User=root
|
|
||||||
WorkingDirectory=/var/www/nebuleair_pro_4g
|
|
||||||
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/wind.log
|
|
||||||
StandardError=append:/var/www/nebuleair_pro_4g/logs/wind_errors.log
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
⬆️
|
|
||||||
|
|
||||||
Reload systemd (first time after creating the service):
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
|
|
||||||
Enable (once), start (once and after stopping) and restart (after modification)systemd:
|
|
||||||
sudo systemctl enable windMeter.service
|
|
||||||
sudo systemctl start windMeter.service
|
|
||||||
sudo systemctl restart windMeter.service
|
|
||||||
|
|
||||||
Check the service status:
|
|
||||||
sudo systemctl status windMeter.service
|
|
||||||
|
|
||||||
'''
|
|
||||||
#!/usr/bin/python3
|
|
||||||
import time
|
|
||||||
import sqlite3
|
|
||||||
import board
|
|
||||||
import busio
|
|
||||||
import numpy as np
|
|
||||||
import threading
|
|
||||||
import adafruit_ads1x15.ads1115 as ADS
|
|
||||||
from adafruit_ads1x15.analog_in import AnalogIn
|
|
||||||
from gpiozero import Button
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db"
|
|
||||||
|
|
||||||
# Initialize I2C & ADS1115
|
|
||||||
i2c = busio.I2C(board.SCL, board.SDA)
|
|
||||||
ads = ADS.ADS1115(i2c)
|
|
||||||
channel = AnalogIn(ads, ADS.P0) # Connect to A0 on the ADS1115
|
|
||||||
|
|
||||||
# Wind speed sensor setup
|
|
||||||
wind_speed_sensor = Button(21)
|
|
||||||
wind_count = 0
|
|
||||||
wind_lock = threading.Lock()
|
|
||||||
|
|
||||||
def spin():
|
|
||||||
global wind_count
|
|
||||||
with wind_lock:
|
|
||||||
wind_count += 1
|
|
||||||
|
|
||||||
def reset_wind():
|
|
||||||
global wind_count
|
|
||||||
with wind_lock:
|
|
||||||
wind_count = 0
|
|
||||||
|
|
||||||
wind_speed_sensor.when_activated = spin # More reliable
|
|
||||||
|
|
||||||
def calc_speed(spins, interval):
|
|
||||||
return spins * (2.25 / interval) * 1.60934 # Convert MPH to km/h
|
|
||||||
|
|
||||||
def get_wind_direction():
|
|
||||||
voltage = channel.voltage
|
|
||||||
return voltage
|
|
||||||
|
|
||||||
def save_to_database(wind_speed, wind_direction, spin_count):
|
|
||||||
"""Save wind data to SQLite database."""
|
|
||||||
try:
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
|
||||||
row = cursor.fetchone()
|
|
||||||
rtc_time_str = row[1] if row else datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
cursor.execute('''
|
|
||||||
INSERT INTO data_wind (timestamp, wind_speed, wind_direction)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
''', (rtc_time_str, round(wind_speed, 2), round(wind_direction, 2)))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
print(f"Saved: {rtc_time_str}, {wind_speed:.2f} km/h, {wind_direction:.2f}V, Spins: {spin_count}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Database error: {e}")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Wind monitoring started...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
reset_wind()
|
|
||||||
print("Measuring for 60 seconds...")
|
|
||||||
time.sleep(60)
|
|
||||||
|
|
||||||
wind_speed_kmh = calc_speed(wind_count, 60)
|
|
||||||
wind_direction = get_wind_direction()
|
|
||||||
|
|
||||||
save_to_database(wind_speed_kmh, wind_direction, wind_count)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nMonitoring stopped.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
'''
|
|
||||||
__ _____ _ _ ____
|
|
||||||
\ \ / /_ _| \ | | _ \
|
|
||||||
\ \ /\ / / | || \| | | | |
|
|
||||||
\ V V / | || |\ | |_| |
|
|
||||||
\_/\_/ |___|_| \_|____/
|
|
||||||
|
|
||||||
|
|
||||||
Script to read wind speed from a Davis Anémomètre-girouette Vantage Pro (6410)
|
|
||||||
https://www.shapemaker.io/blog/wind-speed-measurements-with-anemometer-and-a-raspberry-pi
|
|
||||||
|
|
||||||
Connexion:
|
|
||||||
black (wind speed ) -> gpio21
|
|
||||||
green (wind direction) -> ADS1115 (module I2C)
|
|
||||||
Yellow -> 5v
|
|
||||||
RED -> GND
|
|
||||||
|
|
||||||
Attention: The Raspberry Pi doesn't have analog inputs, so we need an analog-to-digital converter (ADC) to read the wind direction.
|
|
||||||
|
|
||||||
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read_wind_direction.py
|
|
||||||
|
|
||||||
'''
|
|
||||||
import time
|
|
||||||
import board
|
|
||||||
import busio
|
|
||||||
import adafruit_ads1x15.ads1115 as ADS
|
|
||||||
from adafruit_ads1x15.analog_in import AnalogIn
|
|
||||||
|
|
||||||
# Create the I2C bus and ADC object
|
|
||||||
i2c = busio.I2C(board.SCL, board.SDA)
|
|
||||||
ads = ADS.ADS1115(i2c)
|
|
||||||
|
|
||||||
# Connect to the channel with your Davis wind vane
|
|
||||||
wind_dir_sensor = AnalogIn(ads, ADS.P0)
|
|
||||||
|
|
||||||
# Check the current voltage range
|
|
||||||
min_voltage = 9999
|
|
||||||
max_voltage = -9999
|
|
||||||
|
|
||||||
def get_wind_direction():
|
|
||||||
"""Get wind direction angle from Davis Vantage Pro2 wind vane"""
|
|
||||||
global min_voltage, max_voltage
|
|
||||||
|
|
||||||
# Read voltage from ADS1115
|
|
||||||
voltage = wind_dir_sensor.voltage
|
|
||||||
|
|
||||||
# Update min/max for calibration
|
|
||||||
if voltage < min_voltage:
|
|
||||||
min_voltage = voltage
|
|
||||||
if voltage > max_voltage:
|
|
||||||
max_voltage = voltage
|
|
||||||
|
|
||||||
# We'll use a safer mapping approach
|
|
||||||
# Assuming the Davis sensor is linear from 0° to 360°
|
|
||||||
estimated_max = 3.859 # Initial estimate, will refine
|
|
||||||
|
|
||||||
# Calculate angle with bounds checking
|
|
||||||
angle = (voltage / estimated_max) * 360.0
|
|
||||||
|
|
||||||
# Ensure angle is in 0-360 range
|
|
||||||
angle = angle % 360
|
|
||||||
|
|
||||||
return voltage, angle
|
|
||||||
|
|
||||||
# Main loop
|
|
||||||
try:
|
|
||||||
print("Reading wind direction. Press Ctrl+C to exit.")
|
|
||||||
print("Voltage, Angle, Min Voltage, Max Voltage")
|
|
||||||
while True:
|
|
||||||
voltage, angle = get_wind_direction()
|
|
||||||
print(f"{voltage:.3f}V, {angle:.1f}°, {min_voltage:.3f}V, {max_voltage:.3f}V")
|
|
||||||
time.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nProgram stopped")
|
|
||||||
print(f"Observed voltage range: {min_voltage:.3f}V to {max_voltage:.3f}V")
|
|
||||||
|
|
||||||
# Suggest calibration if we have enough data
|
|
||||||
if max_voltage > min_voltage:
|
|
||||||
print("\nSuggested calibration for your setup:")
|
|
||||||
print(f"max_voltage = {max_voltage:.3f}")
|
|
||||||
print(f"def get_wind_direction():")
|
|
||||||
print(f" voltage = wind_dir_sensor.voltage")
|
|
||||||
print(f" angle = (voltage / {max_voltage:.3f}) * 360.0")
|
|
||||||
print(f" return angle % 360")
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
'''
|
|
||||||
__ _____ _ _ ____
|
|
||||||
\ \ / /_ _| \ | | _ \
|
|
||||||
\ \ /\ / / | || \| | | | |
|
|
||||||
\ V V / | || |\ | |_| |
|
|
||||||
\_/\_/ |___|_| \_|____/
|
|
||||||
|
|
||||||
|
|
||||||
Script to read wind speed from a Davis Anémomètre-girouette Vantage Pro (6410)
|
|
||||||
https://www.shapemaker.io/blog/wind-speed-measurements-with-anemometer-and-a-raspberry-pi
|
|
||||||
|
|
||||||
Connexion:
|
|
||||||
black (wind speed ) -> gpio21
|
|
||||||
green (wind direction) -> ADS1115 (module I2C)
|
|
||||||
Yellow -> 5v
|
|
||||||
RED -> GND
|
|
||||||
|
|
||||||
Attention: The Raspberry Pi doesn't have analog inputs, so we need an analog-to-digital converter (ADC) to read the wind direction.
|
|
||||||
|
|
||||||
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read_wind_speed.py
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
import time
|
|
||||||
from gpiozero import Button
|
|
||||||
from signal import pause
|
|
||||||
|
|
||||||
# Setup wind speed sensor on GPIO pin 21 (instead of 5)
|
|
||||||
wind_speed_sensor = Button(21)
|
|
||||||
wind_count = 0
|
|
||||||
|
|
||||||
def spin():
|
|
||||||
global wind_count
|
|
||||||
wind_count = wind_count + 1
|
|
||||||
|
|
||||||
def calc_speed(spins, interval):
|
|
||||||
# Davis anemometer formula: V = P*(2.25/T) in MPH
|
|
||||||
# P = pulses per sample period, T = sample period in seconds
|
|
||||||
wind_speed_mph = spins * (2.25 / interval)
|
|
||||||
return wind_speed_mph
|
|
||||||
|
|
||||||
def reset_wind():
|
|
||||||
global wind_count
|
|
||||||
wind_count = 0
|
|
||||||
|
|
||||||
# Register the event handler for the sensor
|
|
||||||
wind_speed_sensor.when_pressed = spin
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Wind speed measurement started. Press Ctrl+C to exit.")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# Reset the counter
|
|
||||||
reset_wind()
|
|
||||||
|
|
||||||
# Wait for 3 seconds and count rotations
|
|
||||||
print("Measuring for 3 seconds...")
|
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
# Calculate and display wind speed
|
|
||||||
wind_speed = calc_speed(wind_count, 3)
|
|
||||||
print(f"Wind count: {wind_count} spins")
|
|
||||||
print(f"Wind speed: {wind_speed:.2f} mph ({wind_speed * 1.60934:.2f} km/h)")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nMeasurement stopped by user")
|
|
||||||
Reference in New Issue
Block a user