diff --git a/.gitignore b/.gitignore index fe5bdf2..429d7fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/ .env dev -node_modules \ No newline at end of file +node_modules +__pycache__ \ No newline at end of file diff --git a/client/main.py b/client/main.py index 9b9f670..d1f0e1f 100644 --- a/client/main.py +++ b/client/main.py @@ -1,7 +1,8 @@ -from flask import Flask, Response, jsonify +from flask import Flask, Response, jsonify, request from flask_cors import CORS import system_helpers import stream_helpers +import settings_helpers app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}}) @@ -36,6 +37,23 @@ def toggle_recording(): else: stream_helpers.start_recording() return jsonify({"message": "Recording started"}) + +@app.route('/settings', methods=['GET', 'POST']) +def settings(): + if request.method == 'GET': + return jsonify(settings_helpers.get_settings()) + elif request.method == 'POST': + new_settings = request.json + if "VideoSaveLocation" in new_settings: + success = settings_helpers.update_video_save_location(new_settings["VideoSaveLocation"]) + if success: + return jsonify({"message": "Settings updated"}) + else: + return jsonify({"message": "Invalid directory or insufficient permissions"}), 400 + else: + settings_helpers.update_settings(new_settings) + return jsonify({"message": "Settings updated"}) + if __name__ == "__main__": diff --git a/client/settings/settings.json b/client/settings/settings.json new file mode 100644 index 0000000..5781126 --- /dev/null +++ b/client/settings/settings.json @@ -0,0 +1,23 @@ +{ + "TARGET_BT_ADDRESSES": [ + { + "address": "XX:XX:XX:XX:XX:XX", + "name": "Device 1" + }, + { + "address": "XX:XX:XX:XX:XX:XX", + "name": "Device 2" + } + ], + "TARGET_AP_MAC_ADDRESSES": [ + { + "address": "XX:XX:XX:XX:XX:XX", + "name": "Device 1" + }, + { + "address": "XX:XX:XX:XX:XX:XX", + "name": "Device 2" + } + ], + "VideoSaveLocation": "./recordings" +} \ No newline at end of file diff --git a/client/settings_helpers.py b/client/settings_helpers.py new file mode 100644 index 0000000..14c7f10 --- /dev/null +++ b/client/settings_helpers.py @@ -0,0 +1,37 @@ +import json +import os + +SETTINGS_FILE = './settings/settings.json' + +def get_settings(): + with open(SETTINGS_FILE, 'r') as f: + settings = json.load(f) + return settings + +def update_settings(new_settings): + with open(SETTINGS_FILE, 'r+') as f: + settings = json.load(f) + settings.update(new_settings) + f.seek(0) + json.dump(settings, f, indent=4) + f.truncate() + +def is_valid_directory(path): + if not os.path.exists(path): + try: + os.makedirs(path) + return True + except Exception as e: + print(f"Error creating directory: {e}") + return False + elif os.path.isdir(path) and os.access(path, os.W_OK): + return True + else: + return False + +def update_video_save_location(new_location): + if is_valid_directory(new_location): + update_settings({"VideoSaveLocation": new_location}) + return True + else: + return False diff --git a/client/stream_helpers.py b/client/stream_helpers.py index 230f529..25a79b7 100644 --- a/client/stream_helpers.py +++ b/client/stream_helpers.py @@ -1,5 +1,6 @@ import os import cv2 +import json import threading from datetime import datetime @@ -8,8 +9,8 @@ out = None is_recording = False -# Ensure recordings directory exists -os.makedirs('./recordings', exist_ok=True) +# Load settings.json +SETTINGS_FILE = './settings/settings.json' def generate_frames(): @@ -51,10 +52,16 @@ def start_recording() -> None: Returns: None """ - global out, is_recording + + with open(SETTINGS_FILE, 'r') as f: + settings = json.load(f) + + video_save_location = settings.get('VideoSaveLocation', './recordings') + os.makedirs(video_save_location, exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f'./recordings/output_{timestamp}.avi' + filename = os.path.join(video_save_location, f'output_{timestamp}.avi') fourcc = cv2.VideoWriter_fourcc(*'XVID') with lock: diff --git a/client/system_helpers.py b/client/system_helpers.py index 4b8bfe8..e9abc35 100644 --- a/client/system_helpers.py +++ b/client/system_helpers.py @@ -1,54 +1,60 @@ import psutil -def get_cpu_temp() -> float | None: +def get_cpu_temp() -> int | None: """ Returns the CPU temperature of the Raspberry Pi. Returns: - float: The CPU temperature in Celsius. + int: The CPU temperature in Celsius rounded to the nearest integer. None: If the temperature file is not found. """ + try: with open("/sys/class/thermal/thermal_zone0/temp", "r") as file: temp_str = file.read().strip() cpu_temp = int(temp_str) / 1000.0 - return cpu_temp + return round(cpu_temp) except FileNotFoundError: return None -def get_cpu_load() -> float: +def get_cpu_load() -> int: """ - Returns the CPU load as a percentage. + Returns the CPU load as a percentage, considering the maximum load across all cores. Returns: - float: The CPU load as a percentage. + int: The CPU load as a percentage rounded to the nearest integer. """ - return psutil.cpu_percent(interval=1) + + per_core_loads = psutil.cpu_percent(interval=1, percpu=True) + max_load = max(per_core_loads) + return round(max_load) -def get_storage_info() -> dict[str, float]: +def get_storage_info() -> dict[str, int]: """ Returns the total size and used space of the disk where the root directory is mounted. Returns: - dict[str, float]: A dictionary containing the total and used space in GB. + dict[str, int]: A dictionary containing the total and used space in GB, each rounded to the nearest integer. """ + usage = psutil.disk_usage('/') total = usage.total / (1024 ** 3) # Convert bytes to GB used = usage.used / (1024 ** 3) # Convert bytes to GB - return {'total_gb': total, 'used_gb': used} + return {'total_gb': round(total), 'used_gb': round(used)} -def get_ram_usage() -> dict[str, float]: +def get_ram_usage() -> dict[str, int]: """ Returns the total and used RAM in MB. Returns: - dict[str, float]: A dictionary containing the total and used RAM in MB. + dict[str, int]: A dictionary containing the total and used RAM in MB, each rounded to the nearest integer. """ + mem = psutil.virtual_memory() total = mem.total / (1024 ** 2) # Convert bytes to MB used = mem.used / (1024 ** 2) # Convert bytes to MB - return {'total_mb': total, 'used_mb': used} + return {'total_mb': round(total), 'used_mb': round(used)} diff --git a/server/package-lock.json b/server/package-lock.json index cd30324..23b8418 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -4338,8 +4338,15 @@ } }, "node_modules/server": { - "resolved": "", - "link": true + "version": "0.1.0", + "resolved": "file:", + "dependencies": { + "next": "14.2.5", + "react": "^18", + "react-dom": "^18", + "react-player": "^2.16.0", + "server": "file:" + } }, "node_modules/set-function-length": { "version": "1.2.2", diff --git a/server/src/app/page.tsx b/server/src/app/page.tsx index 18ac987..0c2b432 100644 --- a/server/src/app/page.tsx +++ b/server/src/app/page.tsx @@ -1,5 +1,6 @@ import React from "react"; import ClientVideoPlayer from "@/components/ClientVideoPlayer"; +import SystemMonitor from "@/components/SystemMonitor"; const Home = () => { @@ -25,11 +26,7 @@ const Home = () => { -
-
temp: 20°C
-
cpu: 20%
-
storage: 2.69gb / 64gb
-
+ ); }; diff --git a/server/src/app/settings/page.tsx b/server/src/app/settings/page.tsx index a174718..f547e33 100644 --- a/server/src/app/settings/page.tsx +++ b/server/src/app/settings/page.tsx @@ -1,12 +1,76 @@ -const page = () => { +'use client'; +import React, { useState, useEffect } from "react"; + +const Page = () => { + const [settings, setSettings] = useState({ + VideoSaveLocation: "Loading...", + }); + const [newLocation, setNewLocation] = useState(""); + + useEffect(() => { + const settingsFeedUrl = `${window.location.protocol}//${window.location.hostname}:5005/settings`; + + const fetchSettingsInfo = async () => { + try { + const response = await fetch(settingsFeedUrl); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + console.log("Fetched Settings Info:", data); + setSettings(data); + } catch (error) { + console.error("Error fetching settings info:", error); + setSettings({ + VideoSaveLocation: "Error loading data", + }); + } + }; + + fetchSettingsInfo(); + }, []); + + const handleSaveLocationChange = async () => { + const settingsFeedUrl = `${window.location.protocol}//${window.location.hostname}:5005/settings`; + try { + const response = await fetch(settingsFeedUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ VideoSaveLocation: newLocation }), + }); + + const result = await response.json(); + if (!response.ok) { + alert(result.message || "Error updating settings"); + } else { + alert("Settings updated successfully"); + setSettings(prevSettings => ({ ...prevSettings, VideoSaveLocation: newLocation })); + } + } catch (error) { + console.error("Error updating settings:", error); + alert("Error updating settings"); + } + }; + return (
bt device mac
wifi device mac
-
modular trigger sensors
-
video save location
+
{`Video save location: ${settings.VideoSaveLocation}`}
+
+ setNewLocation(e.target.value)} + placeholder="Enter new video save location" + /> + +
+
modular trigger sensors: TBD
- ) -} + ); +}; -export default page \ No newline at end of file +export default Page; diff --git a/server/src/components/SystemMonitor.tsx b/server/src/components/SystemMonitor.tsx new file mode 100644 index 0000000..fd205a5 --- /dev/null +++ b/server/src/components/SystemMonitor.tsx @@ -0,0 +1,43 @@ +'use client'; +import React, { useState, useEffect } from "react"; + +const SystemMonitor = () => { + const [systemInfo, setSystemInfo] = useState({ + cpu_temp_celsius: "Loading...", + cpu_load_percent: "Loading...", + storage_info_gb: { total_gb: "Loading...", used_gb: "Loading..." }, + ram_usage_mb: { total_mb: "Loading...", used_mb: "Loading..." }, + }); + + const systemFeedUrl = `${window.location.protocol}//${window.location.hostname}:5005/system_info`; + + useEffect(() => { + const fetchSystemInfo = async () => { + try { + const response = await fetch(systemFeedUrl); + const data = await response.json(); + console.log("Fetched System Info:", data); // Log the data structure + setSystemInfo(data); + } catch (error) { + console.error("Error fetching system info:", error); + } + }; + + fetchSystemInfo(); + }, [systemFeedUrl]); + + return ( +
+
Temp: {systemInfo.cpu_temp_celsius}°C
+
CPU: {systemInfo.cpu_load_percent}%
+
+ Storage: {systemInfo.storage_info_gb.used_gb} GB / {systemInfo.storage_info_gb.total_gb} GB +
+
+ RAM: {systemInfo.ram_usage_mb.used_mb} MB / {systemInfo.ram_usage_mb.total_mb} MB +
+
+ ); +}; + +export default SystemMonitor;