From 57fbcef102b8f43507472d9c3f87460649727ee1 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Mon, 30 Sep 2024 16:09:26 -0700 Subject: [PATCH 01/27] Reorganize backend --- backend_py/build.sh | 4 - backend_py/src/__init__.py | 15 +- backend_py/src/logging/log_handler.py | 2 +- backend_py/src/services/__init__.py | 3 + backend_py/src/services/cameras/__init__.py | 12 + .../services/cameras/camera_helper/build.sh | 4 + .../cameras/camera_helper}/camera_helper.c | 0 .../camera_helper}/camera_helper_loader.py | 4 +- .../{ => services/cameras}/camera_types.py | 3 + .../src/{ => services/cameras}/device.py | 2 +- .../{ => services/cameras}/device_manager.py | 9 +- .../{ => services/cameras}/device_utils.py | 0 .../src/{devices => services/cameras}/ehd.py | 8 +- .../{ => services/cameras}/ehd_controls.py | 0 .../src/{ => services/cameras}/enumeration.py | 0 .../src/{ => services/cameras}/saved_types.py | 0 .../src/{ => services/cameras}/schemas.py | 0 .../src/{ => services/cameras}/settings.py | 2 +- .../src/{devices => services/cameras}/shd.py | 6 +- .../src/{ => services/cameras}/stream.py | 0 .../{ => services/cameras}/stream_utils.py | 0 backend_py/src/{ => services/cameras}/v4l2.py | 0 backend_py/src/services/lights/__init__.py | 8 + .../src/{ => services}/lights/fake_pwm.py | 0 backend_py/src/{ => services}/lights/light.py | 0 .../{ => services}/lights/light_manager.py | 0 .../src/{ => services}/lights/light_types.py | 0 .../{ => services}/lights/pwm_controller.py | 0 .../{ => services}/lights/rpi_pwm_hardware.py | 0 .../src/{ => services}/lights/schemas.py | 0 backend_py/src/{ => services}/lights/utils.py | 0 .../src/services/preferences/__init__.py | 3 + .../preferences/preference_types.py | 2 +- .../preferences/preferences_manager.py | 2 +- .../preferences/schemas.py} | 2 +- .../src/{ => websockets}/broadcast_server.py | 0 install_requirements.sh | 3 - system_api/.gitignore | 26 -- system_api/README.md | 13 - system_api/build_system_api.sh | 11 - system_api/cpu_handler.go | 48 --- system_api/errors.go | 55 --- system_api/go.mod | 22 -- system_api/go.sum | 44 --- system_api/logger.go | 53 --- system_api/main.go | 350 ------------------ system_api/network_types.go | 75 ---- system_api/request_types.go | 27 -- system_api/system_handler.go | 33 -- system_api/temperature_handler.go | 98 ----- system_api/wifi_handler.go | 224 ----------- 51 files changed, 56 insertions(+), 1117 deletions(-) delete mode 100755 backend_py/build.sh create mode 100644 backend_py/src/services/__init__.py create mode 100644 backend_py/src/services/cameras/__init__.py create mode 100644 backend_py/src/services/cameras/camera_helper/build.sh rename backend_py/src/{ => services/cameras/camera_helper}/camera_helper.c (100%) rename backend_py/src/{ => services/cameras/camera_helper}/camera_helper_loader.py (61%) rename backend_py/src/{ => services/cameras}/camera_types.py (97%) rename backend_py/src/{ => services/cameras}/device.py (99%) rename backend_py/src/{ => services/cameras}/device_manager.py (98%) rename backend_py/src/{ => services/cameras}/device_utils.py (100%) rename backend_py/src/{devices => services/cameras}/ehd.py (91%) rename backend_py/src/{ => services/cameras}/ehd_controls.py (100%) rename backend_py/src/{ => services/cameras}/enumeration.py (100%) rename backend_py/src/{ => services/cameras}/saved_types.py (100%) rename backend_py/src/{ => services/cameras}/schemas.py (100%) rename backend_py/src/{ => services/cameras}/settings.py (99%) rename backend_py/src/{devices => services/cameras}/shd.py (96%) rename backend_py/src/{ => services/cameras}/stream.py (100%) rename backend_py/src/{ => services/cameras}/stream_utils.py (100%) rename backend_py/src/{ => services/cameras}/v4l2.py (100%) create mode 100644 backend_py/src/services/lights/__init__.py rename backend_py/src/{ => services}/lights/fake_pwm.py (100%) rename backend_py/src/{ => services}/lights/light.py (100%) rename backend_py/src/{ => services}/lights/light_manager.py (100%) rename backend_py/src/{ => services}/lights/light_types.py (100%) rename backend_py/src/{ => services}/lights/pwm_controller.py (100%) rename backend_py/src/{ => services}/lights/rpi_pwm_hardware.py (100%) rename backend_py/src/{ => services}/lights/schemas.py (100%) rename backend_py/src/{ => services}/lights/utils.py (100%) create mode 100644 backend_py/src/services/preferences/__init__.py rename backend_py/src/{ => services}/preferences/preference_types.py (71%) rename backend_py/src/{ => services}/preferences/preferences_manager.py (96%) rename backend_py/src/{preferences/preference_schemas.py => services/preferences/schemas.py} (85%) rename backend_py/src/{ => websockets}/broadcast_server.py (100%) delete mode 100644 system_api/.gitignore delete mode 100644 system_api/README.md delete mode 100644 system_api/build_system_api.sh delete mode 100644 system_api/cpu_handler.go delete mode 100644 system_api/errors.go delete mode 100644 system_api/go.mod delete mode 100644 system_api/go.sum delete mode 100644 system_api/logger.go delete mode 100644 system_api/main.go delete mode 100644 system_api/network_types.go delete mode 100644 system_api/request_types.go delete mode 100644 system_api/system_handler.go delete mode 100644 system_api/temperature_handler.go delete mode 100644 system_api/wifi_handler.go diff --git a/backend_py/build.sh b/backend_py/build.sh deleted file mode 100755 index b399d2bc..00000000 --- a/backend_py/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -mkdir build -p -cc -fPIC -shared -o ${PWD}/build/camera_helper.so ${PWD}/src/camera_helper.c diff --git a/backend_py/src/__init__.py b/backend_py/src/__init__.py index 510f8be4..cbc1f440 100644 --- a/backend_py/src/__init__.py +++ b/backend_py/src/__init__.py @@ -6,18 +6,9 @@ from flask_cors import CORS from gevent.pywsgi import WSGIServer -from .enumeration import * -from .camera_helper_loader import * -from .schemas import * -from .device import * -from .stream import * -from .settings import SettingsManager -from .broadcast_server import BroadcastServer -from .device_manager import DeviceManager -from .lights.light_manager import LightManager -from .lights.utils import create_pwm_controllers -from .preferences.preferences_manager import PreferencesManager -from .preferences.preference_schemas import SavedPrefrencesSchema, SavedPrefrences +from .websockets.broadcast_server import BroadcastServer + +from .services import * import logging diff --git a/backend_py/src/logging/log_handler.py b/backend_py/src/logging/log_handler.py index 656906c7..b98456b9 100644 --- a/backend_py/src/logging/log_handler.py +++ b/backend_py/src/logging/log_handler.py @@ -1,5 +1,5 @@ import logging -from ..broadcast_server import BroadcastServer, Message +from ..websockets.broadcast_server import BroadcastServer, Message class LogHandler(logging.Handler): def __init__(self, server: BroadcastServer, level: int | str = 0) -> None: diff --git a/backend_py/src/services/__init__.py b/backend_py/src/services/__init__.py new file mode 100644 index 00000000..25ddc59e --- /dev/null +++ b/backend_py/src/services/__init__.py @@ -0,0 +1,3 @@ +from .cameras import * +from .lights import * +from .preferences import * \ No newline at end of file diff --git a/backend_py/src/services/cameras/__init__.py b/backend_py/src/services/cameras/__init__.py new file mode 100644 index 00000000..6f834f90 --- /dev/null +++ b/backend_py/src/services/cameras/__init__.py @@ -0,0 +1,12 @@ +from .camera_types import * +from .device_manager import * +from .device_utils import * +from .device import * +from .ehd_controls import * +from .ehd import * +from .enumeration import * +from .saved_types import * +from .schemas import * +from .settings import * +from .shd import * +from .stream import * diff --git a/backend_py/src/services/cameras/camera_helper/build.sh b/backend_py/src/services/cameras/camera_helper/build.sh new file mode 100644 index 00000000..6388bd03 --- /dev/null +++ b/backend_py/src/services/cameras/camera_helper/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +mkdir build -p +cc -fPIC -shared -o ${PWD}/build/camera_helper.so ${PWD}/camera_helper.c diff --git a/backend_py/src/camera_helper.c b/backend_py/src/services/cameras/camera_helper/camera_helper.c similarity index 100% rename from backend_py/src/camera_helper.c rename to backend_py/src/services/cameras/camera_helper/camera_helper.c diff --git a/backend_py/src/camera_helper_loader.py b/backend_py/src/services/cameras/camera_helper/camera_helper_loader.py similarity index 61% rename from backend_py/src/camera_helper_loader.py rename to backend_py/src/services/cameras/camera_helper/camera_helper_loader.py index c9b766fb..cfc6c089 100644 --- a/backend_py/src/camera_helper_loader.py +++ b/backend_py/src/services/cameras/camera_helper/camera_helper_loader.py @@ -3,9 +3,9 @@ import subprocess dir_path = os.path.dirname(os.path.realpath(__file__)) -CAMERA_HELPER_SO_FILE = f'{dir_path}/../build/camera_helper.so' +CAMERA_HELPER_SO_FILE = f'{dir_path}/build/camera_helper.so' if not os.path.exists(CAMERA_HELPER_SO_FILE): - subprocess.call(['sh', 'build.sh'], cwd=f'{dir_path}/../') + subprocess.call(['sh', 'build.sh'], cwd=f'{dir_path}') camera_helper = CDLL(CAMERA_HELPER_SO_FILE) diff --git a/backend_py/src/camera_types.py b/backend_py/src/services/cameras/camera_types.py similarity index 97% rename from backend_py/src/camera_types.py rename to backend_py/src/services/cameras/camera_types.py index 7c206067..c1bb19dc 100644 --- a/backend_py/src/camera_types.py +++ b/backend_py/src/services/cameras/camera_types.py @@ -75,6 +75,9 @@ class H264Mode(Enum): MODE_VARIABLE_BITRATE = 2 class DeviceType(Enum): + ''' + Device type Enum + ''' EXPLOREHD = 0 STELLARHD_LEADER= 1 STELLARHD_FOLLOWER = 2 diff --git a/backend_py/src/device.py b/backend_py/src/services/cameras/device.py similarity index 99% rename from backend_py/src/device.py rename to backend_py/src/services/cameras/device.py index 3e4c62a2..9a05901b 100644 --- a/backend_py/src/device.py +++ b/backend_py/src/services/cameras/device.py @@ -13,7 +13,7 @@ from .stream_utils import fourcc2s from .enumeration import * -from .camera_helper_loader import * +from .camera_helper.camera_helper_loader import * from .camera_types import * from .stream import * from .saved_types import * diff --git a/backend_py/src/device_manager.py b/backend_py/src/services/cameras/device_manager.py similarity index 98% rename from backend_py/src/device_manager.py rename to backend_py/src/services/cameras/device_manager.py index bb3b9029..f2c19d5f 100644 --- a/backend_py/src/device_manager.py +++ b/backend_py/src/services/cameras/device_manager.py @@ -8,13 +8,14 @@ from .schemas import * from .device import Device, lookup_pid_vid, DeviceInfo, DeviceType from .settings import SettingsManager -from .broadcast_server import BroadcastServer, Message +from ...websockets.broadcast_server import BroadcastServer, Message from .enumeration import list_devices from .device_utils import list_diff, find_device_with_bus_info -from .devices.ehd import EHDDevice -from .devices.shd import SHDDevice -from .logging.log_handler import LogHandler +from .ehd import EHDDevice +from .shd import SHDDevice + +from ...logging.log_handler import LogHandler class DeviceManager(events.EventEmitter): diff --git a/backend_py/src/device_utils.py b/backend_py/src/services/cameras/device_utils.py similarity index 100% rename from backend_py/src/device_utils.py rename to backend_py/src/services/cameras/device_utils.py diff --git a/backend_py/src/devices/ehd.py b/backend_py/src/services/cameras/ehd.py similarity index 91% rename from backend_py/src/devices/ehd.py rename to backend_py/src/services/cameras/ehd.py index b80b4b9e..a7bbdf9b 100644 --- a/backend_py/src/devices/ehd.py +++ b/backend_py/src/services/cameras/ehd.py @@ -1,8 +1,8 @@ from typing import Dict -from ..enumeration import DeviceInfo -from ..device import Device, Option, ControlTypeEnum -from ..camera_types import H264Mode -from .. import ehd_controls as xu +from .enumeration import DeviceInfo +from .device import Device, Option, ControlTypeEnum +from .camera_types import H264Mode +from . import ehd_controls as xu class EHDDevice(Device): ''' diff --git a/backend_py/src/ehd_controls.py b/backend_py/src/services/cameras/ehd_controls.py similarity index 100% rename from backend_py/src/ehd_controls.py rename to backend_py/src/services/cameras/ehd_controls.py diff --git a/backend_py/src/enumeration.py b/backend_py/src/services/cameras/enumeration.py similarity index 100% rename from backend_py/src/enumeration.py rename to backend_py/src/services/cameras/enumeration.py diff --git a/backend_py/src/saved_types.py b/backend_py/src/services/cameras/saved_types.py similarity index 100% rename from backend_py/src/saved_types.py rename to backend_py/src/services/cameras/saved_types.py diff --git a/backend_py/src/schemas.py b/backend_py/src/services/cameras/schemas.py similarity index 100% rename from backend_py/src/schemas.py rename to backend_py/src/services/cameras/schemas.py diff --git a/backend_py/src/settings.py b/backend_py/src/services/cameras/settings.py similarity index 99% rename from backend_py/src/settings.py rename to backend_py/src/services/cameras/settings.py index adb1f871..7eb33f8a 100644 --- a/backend_py/src/settings.py +++ b/backend_py/src/services/cameras/settings.py @@ -7,7 +7,7 @@ from .saved_types import * from .schemas import SavedDeviceSchema from .device import Device -from .devices.shd import SHDDevice +from .shd import SHDDevice from .camera_types import DeviceType from .device_utils import find_device_with_bus_info diff --git a/backend_py/src/devices/shd.py b/backend_py/src/services/cameras/shd.py similarity index 96% rename from backend_py/src/devices/shd.py rename to backend_py/src/services/cameras/shd.py index 6db44abb..1d7ec493 100644 --- a/backend_py/src/devices/shd.py +++ b/backend_py/src/services/cameras/shd.py @@ -1,9 +1,9 @@ import logging -from ..saved_types import SavedDevice +from .saved_types import SavedDevice -from ..enumeration import DeviceInfo -from ..device import Device +from .enumeration import DeviceInfo +from .device import Device class SHDDevice(Device): ''' diff --git a/backend_py/src/stream.py b/backend_py/src/services/cameras/stream.py similarity index 100% rename from backend_py/src/stream.py rename to backend_py/src/services/cameras/stream.py diff --git a/backend_py/src/stream_utils.py b/backend_py/src/services/cameras/stream_utils.py similarity index 100% rename from backend_py/src/stream_utils.py rename to backend_py/src/services/cameras/stream_utils.py diff --git a/backend_py/src/v4l2.py b/backend_py/src/services/cameras/v4l2.py similarity index 100% rename from backend_py/src/v4l2.py rename to backend_py/src/services/cameras/v4l2.py diff --git a/backend_py/src/services/lights/__init__.py b/backend_py/src/services/lights/__init__.py new file mode 100644 index 00000000..f1643e1d --- /dev/null +++ b/backend_py/src/services/lights/__init__.py @@ -0,0 +1,8 @@ +from .fake_pwm import * +from .light_manager import * +from .light_types import * +from .light import * +from .pwm_controller import * +from .rpi_pwm_hardware import * +from .schemas import * +from .utils import * \ No newline at end of file diff --git a/backend_py/src/lights/fake_pwm.py b/backend_py/src/services/lights/fake_pwm.py similarity index 100% rename from backend_py/src/lights/fake_pwm.py rename to backend_py/src/services/lights/fake_pwm.py diff --git a/backend_py/src/lights/light.py b/backend_py/src/services/lights/light.py similarity index 100% rename from backend_py/src/lights/light.py rename to backend_py/src/services/lights/light.py diff --git a/backend_py/src/lights/light_manager.py b/backend_py/src/services/lights/light_manager.py similarity index 100% rename from backend_py/src/lights/light_manager.py rename to backend_py/src/services/lights/light_manager.py diff --git a/backend_py/src/lights/light_types.py b/backend_py/src/services/lights/light_types.py similarity index 100% rename from backend_py/src/lights/light_types.py rename to backend_py/src/services/lights/light_types.py diff --git a/backend_py/src/lights/pwm_controller.py b/backend_py/src/services/lights/pwm_controller.py similarity index 100% rename from backend_py/src/lights/pwm_controller.py rename to backend_py/src/services/lights/pwm_controller.py diff --git a/backend_py/src/lights/rpi_pwm_hardware.py b/backend_py/src/services/lights/rpi_pwm_hardware.py similarity index 100% rename from backend_py/src/lights/rpi_pwm_hardware.py rename to backend_py/src/services/lights/rpi_pwm_hardware.py diff --git a/backend_py/src/lights/schemas.py b/backend_py/src/services/lights/schemas.py similarity index 100% rename from backend_py/src/lights/schemas.py rename to backend_py/src/services/lights/schemas.py diff --git a/backend_py/src/lights/utils.py b/backend_py/src/services/lights/utils.py similarity index 100% rename from backend_py/src/lights/utils.py rename to backend_py/src/services/lights/utils.py diff --git a/backend_py/src/services/preferences/__init__.py b/backend_py/src/services/preferences/__init__.py new file mode 100644 index 00000000..57cb1cc0 --- /dev/null +++ b/backend_py/src/services/preferences/__init__.py @@ -0,0 +1,3 @@ +from .preference_types import * +from .preferences_manager import * +from .schemas import * \ No newline at end of file diff --git a/backend_py/src/preferences/preference_types.py b/backend_py/src/services/preferences/preference_types.py similarity index 71% rename from backend_py/src/preferences/preference_types.py rename to backend_py/src/services/preferences/preference_types.py index ea6610fa..89f8aba1 100644 --- a/backend_py/src/preferences/preference_types.py +++ b/backend_py/src/services/preferences/preference_types.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from ..camera_types import StreamEndpoint +from .. import StreamEndpoint @dataclass class SavedPrefrences: diff --git a/backend_py/src/preferences/preferences_manager.py b/backend_py/src/services/preferences/preferences_manager.py similarity index 96% rename from backend_py/src/preferences/preferences_manager.py rename to backend_py/src/services/preferences/preferences_manager.py index 78c06041..a861cc66 100644 --- a/backend_py/src/preferences/preferences_manager.py +++ b/backend_py/src/services/preferences/preferences_manager.py @@ -1,7 +1,7 @@ import json from typing import Dict from .preference_types import SavedPrefrences, StreamEndpoint -from .preference_schemas import SavedPrefrencesSchema +from .schemas import SavedPrefrencesSchema class DefaultPreferences(SavedPrefrences): def __init__(self): diff --git a/backend_py/src/preferences/preference_schemas.py b/backend_py/src/services/preferences/schemas.py similarity index 85% rename from backend_py/src/preferences/preference_schemas.py rename to backend_py/src/services/preferences/schemas.py index 0b71ced5..e343fadb 100644 --- a/backend_py/src/preferences/preference_schemas.py +++ b/backend_py/src/services/preferences/schemas.py @@ -1,6 +1,6 @@ from marshmallow import Schema, fields, post_load from .preference_types import SavedPrefrences -from ..schemas import StreamEndpointSchema +from ..cameras.schemas import StreamEndpointSchema class SavedPrefrencesSchema(Schema): default_stream = fields.Nested(StreamEndpointSchema) diff --git a/backend_py/src/broadcast_server.py b/backend_py/src/websockets/broadcast_server.py similarity index 100% rename from backend_py/src/broadcast_server.py rename to backend_py/src/websockets/broadcast_server.py diff --git a/install_requirements.sh b/install_requirements.sh index d319eae1..7659f5af 100755 --- a/install_requirements.sh +++ b/install_requirements.sh @@ -3,8 +3,5 @@ # install python sudo apt-get install python3 python3-venv -y -# install golang -sudo apt-get install golang -y - # install gstreamer sudo apt-get install -y libglib2.0-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav diff --git a/system_api/.gitignore b/system_api/.gitignore deleted file mode 100644 index 7eaa66b9..00000000 --- a/system_api/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work - -# Server stuff: -*.log -*.env -system_api diff --git a/system_api/README.md b/system_api/README.md deleted file mode 100644 index c9076505..00000000 --- a/system_api/README.md +++ /dev/null @@ -1,13 +0,0 @@ -go build && clear && ./system_api - -## Usage - -#### Available Commands - -Here is a list of all the available commands: - -| Script | Description | -| ----------- | --------------------------------------------------------------------------------------------- | -| go mod tidy | Installs all the dependencies listed in `go.mod`. | -| go run . | Runs the application without building it. | -| go build . | Builds the application into an executable file for different platforms using the Go compiler. | diff --git a/system_api/build_system_api.sh b/system_api/build_system_api.sh deleted file mode 100644 index b86305af..00000000 --- a/system_api/build_system_api.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -build_system_api() { - # Remove build file if it exists - rm -f system_api - # Install dependencies - go mod tidy - # Build for target - go build . -} -build_system_api diff --git a/system_api/cpu_handler.go b/system_api/cpu_handler.go deleted file mode 100644 index 6156d791..00000000 --- a/system_api/cpu_handler.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "time" - - "github.com/shirou/gopsutil/cpu" -) - -func GetCPUInfo() (map[string]interface{}, error) { - cpuInfo := make(map[string]interface{}) - - // Get CPU Info - cpuInfoStat, err := cpu.Info() - if err != nil { - return nil, err - } - cpuInfo["processor_name"] = cpuInfoStat[0].ModelName - - // Get Physical Cores - physicalCores, err := cpu.Counts(true) - if err != nil { - return nil, err - } - cpuInfo["physical_cores"] = physicalCores - - // Get Total Cores - totalCores, err := cpu.Counts(false) - if err != nil { - return nil, err - } - cpuInfo["total_cores"] = totalCores - - // Get CPU Usage Per Core - coreUsage, err := cpu.Percent(time.Duration(0), true) - if err != nil { - return nil, err - } - cpuInfo["core_usage"] = coreUsage - - // Get Total CPU Usage (all cores) - totalUsage, err := cpu.Percent(time.Duration(0), false) - if err != nil { - return nil, err - } - cpuInfo["total_usage"] = totalUsage - - return cpuInfo, nil -} diff --git a/system_api/errors.go b/system_api/errors.go deleted file mode 100644 index 67ec5c40..00000000 --- a/system_api/errors.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -// Raised for errors regarding failed parsing of string data to proper structs. -type ParseError struct { - Msg string -} - -func (e *ParseError) Error() string { - return e.Msg -} - -// Raised for errors regarding failed data fetching. -type FetchError struct { - Msg string -} - -func (e *FetchError) Error() string { - return e.Msg -} - -// Raised for errors regarding an excessive number of requests in a given time. -type BusyError struct { - Msg string -} - -func (e *BusyError) Error() string { - return e.Msg -} - -// Raised for errors regarding WPA socket communication. -type SockCommError struct { - Msg string -} - -func (e *SockCommError) Error() string { - return e.Msg -} - -// Raised when a WPA operation fails. -type WPAOperationFail struct { - Msg string -} - -func (e *WPAOperationFail) Error() string { - return e.Msg -} - -// Raised when a WPA add_network operation fails to return an int. -type NetworkAddFail struct { - Msg string -} - -func (e *NetworkAddFail) Error() string { - return e.Msg -} diff --git a/system_api/go.mod b/system_api/go.mod deleted file mode 100644 index 5e52b39b..00000000 --- a/system_api/go.mod +++ /dev/null @@ -1,22 +0,0 @@ -module system_api - -go 1.18 - -require ( - github.com/gorilla/handlers v1.5.1 - github.com/gorilla/mux v1.8.0 - github.com/shirou/gopsutil v3.21.11+incompatible - github.com/sirupsen/logrus v1.9.3 - github.com/theojulienne/go-wireless v1.2.0 - gopkg.in/natefinch/lumberjack.v2 v2.2.1 -) - -require ( - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/sys v0.11.0 // indirect -) diff --git a/system_api/go.sum b/system_api/go.sum deleted file mode 100644 index a2027c4e..00000000 --- a/system_api/go.sum +++ /dev/null @@ -1,44 +0,0 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= -github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/theojulienne/go-wireless v1.2.0 h1:9ElC6Tv17jqiIXwskVH4GuScK5qAzvLAuLvT8q1paiw= -github.com/theojulienne/go-wireless v1.2.0/go.mod h1:VgY9MuPShbRUwTVQArIgfwYJwAs9Q1zObxJiQFiVK8E= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/system_api/logger.go b/system_api/logger.go deleted file mode 100644 index fab75363..00000000 --- a/system_api/logger.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - - "github.com/sirupsen/logrus" - "gopkg.in/natefinch/lumberjack.v2" -) - -// Log is the central logger instance that can be used throughout the application. -var Log *logrus.Logger - -func initalizeLogger() { - // Read the value of the APP_ENV environment variable - env := os.Getenv("APP_ENV") - - // Create a new logger instance - log := logrus.New() - - // configure logrus to output to a file called server.log along with stdout - log.SetOutput(io.MultiWriter(os.Stdout, &lumberjack.Logger{ - Filename: "server.log", - MaxSize: 10, // megabytes - MaxBackups: 3, - MaxAge: 28, //days - Compress: true, // disabled by default - })) - - // Configure the log level based on the environment - if env == "development" { - // Development log level - log.SetLevel(logrus.DebugLevel) - } else { - // Default log level (e.g., production). - log.SetLevel(logrus.InfoLevel) - } - - // Set the logger instance to the package-level variable - Log = log - - Log.Info(fmt.Sprintf("Running in %s environment", func() string { - if env == "development" { - return "development" - } else { - return "production" - } - }())) - - // Log an info message - Log.Info("Logger initialized") -} diff --git a/system_api/main.go b/system_api/main.go deleted file mode 100644 index ee80681d..00000000 --- a/system_api/main.go +++ /dev/null @@ -1,350 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" -) - -func handleWifiStatus(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkStatus() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error getting wifi status: %v", err)) - return - } else { - // Marshal the response content into JSON - jsonData, err := json.Marshal(responseContent) - if err != nil { - Log.Error(fmt.Sprintf("Error marshaling JSON: %v", err)) - return - } - - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Write the JSON response to the client - w.Write(jsonData) - } -} - -func handleWifiScan(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkScan() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error getting the connected wifi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func handleWifiSaved(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkSaved() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error getting the connected wifi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func handleWifiConnected(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkConnected() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error getting the connected wifi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func toggleWifiStatus(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Parse the request body into a WifiToggleRequest struct - var request WifiToggleRequest - err := json.NewDecoder(r.Body).Decode(&request) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to parse request body: %v", err), http.StatusBadRequest) - return - } - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkToggle(request.WifiState) - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error toggling the WiFi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func connectToWifi(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Parse the request body into a WifiToggleRequest struct - var request WifiConnectRequest - err := json.NewDecoder(r.Body).Decode(&request) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to parse request body: %v", err), http.StatusBadRequest) - return - } - Log.Info(fmt.Sprintf("Attempting to connect to %s with password: %s", request.WifiSSID, request.WifiPassword)); - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkConnect(request.WifiSSID, request.WifiPassword) - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error connecting to the WiFi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func disconnectWifi(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Parse the request body into a WifiToggleRequest struct - var request WifiDisconnectRequest - err := json.NewDecoder(r.Body).Decode(&request) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to parse request body: %v", err), http.StatusBadRequest) - return - } - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkDisconnect(request.WifiSSID) - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error disconnecting from the WiFi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func forgetWifi(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Parse the request body into a WifiToggleRequest struct - var request WifiForgetRequest - err := json.NewDecoder(r.Body).Decode(&request) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to parse request body: %v", err), http.StatusBadRequest) - return - } - - // Build the response content as a JSON struct - responseContent, err := wifiHandler.NetworkForget(request.WifiSSID) - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error forgetting the WiFi network: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func shutdownMachine(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - err := ShutDown() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error forgetting the WiFi network: %v", err)) - return - } -} - -func restartMachine(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - err := Restart() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error forgetting the WiFi network: %v", err)) - return - } -} - -func handleGetTemperature(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - responseContent, err := GetTemperature() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error getting the temperature: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -func handleCPU(w http.ResponseWriter, r *http.Request) { - // Set the response status code - w.WriteHeader(http.StatusOK) - - // Set the response headers to indicate the content type - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - // Build the response content as a JSON struct - responseContent, err := GetCPUInfo() - - // Check if there was an error - if err != nil { - // Log the error - Log.Error(fmt.Sprintf("Error getting the CPU info: %v", err)) - return - } else { - // Send the response content convert encoded in utf-8 - json.NewEncoder(w).Encode(responseContent) - } -} - -var wifiHandler *WifiHandler -var err error -var errs []error - -func init() { - // Initialize the logger - initalizeLogger() - - // Initialize the Wi-Fi handler - wifiHandler, err = NewWifiHandler() - if err != nil { - Log.Error(fmt.Sprintf("Error initializing wifi handler: %v", err)) - return - } -} - -func main() { - if wifiHandler != nil { - // Initialize the router - r := mux.NewRouter() - r.HandleFunc("/wifiStatus", handleWifiStatus).Methods("GET") - r.HandleFunc("/wifiScan", handleWifiScan).Methods("GET") - r.HandleFunc("/wifiSaved", handleWifiSaved).Methods("GET") - r.HandleFunc("/wifiConnected", handleWifiConnected).Methods("GET") - r.HandleFunc("/wifiToggle", toggleWifiStatus).Methods("POST") - r.HandleFunc("/wifiConnect", connectToWifi).Methods("POST") - r.HandleFunc("/wifiDisconnect", disconnectWifi).Methods("POST") - r.HandleFunc("/wifiForget", forgetWifi).Methods("POST") - r.HandleFunc("/shutDownMachine", shutdownMachine).Methods("POST") - r.HandleFunc("/restartMachine", restartMachine).Methods("POST") - r.HandleFunc("/getTemperature", handleGetTemperature).Methods("GET") - r.HandleFunc("/getCPU", handleCPU).Methods("GET") - - // Create a new HTTP server with the CORS (Cross-Origin Resource Sharing) middleware enabled - corsOptions := handlers.CORS( - handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Accept"}), // Allow only headers from the list - handlers.AllowedOrigins([]string{"Access-Control-Allow-Origin", "*"}), // Allow requests from any origin - handlers.AllowedMethods([]string{"GET", "POST", "OPTIONS"}), // Allow only GET and POST methods - ) - - // Set up the server address and port - const port = 5050 - const host = "0.0.0.0" - - // Start the server - Log.Info(fmt.Sprintf("Server started at http://%s:%d.", host, port)) - http.Handle("/", corsOptions(r)) - http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), nil) - } -} diff --git a/system_api/network_types.go b/system_api/network_types.go deleted file mode 100644 index 344801d4..00000000 --- a/system_api/network_types.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -// Represents the security type of a Wifi network -type SecurityType string - -const ( - WPA SecurityType = "WPA" // WPA security - WEP SecurityType = "WEP" // WEP security - WSN SecurityType = "WSN" // WSN security -) - -type NetworkStatus struct { - WPAState string `json:"wpa_state"` // WPA state of the network interface - KeyMgmt string `json:"key_mgmt"` // Key management protocol - IPAddr string `json:"ip_address"` // IP address of the network interface - SSID string `json:"ssid"` // SSID of the network interface - Address string `json:"address"` // MAC address of the network interface - BSSID string `json:"bssid"` // BSSID of the network interface - Freq string `json:"freq"` // Frequency of the network interface -} - -// Represents a Wifi network scanned from the network interface -type ScannedWifiNetwork struct { - SSID string `json:"ssid,omitempty"` // Network SSID (can be hidden) - Frequency int `json:"frequency"` // Network frequency - MacAddress string `json:"mac_address"` // Network BSSID (always available) - Secure bool `json:"secure"` // Whether the network is secure - SecurityType []SecurityType `json:"security_type"` // Network security type - SignalStrength int `json:"signal_strength"` // Network signal strength -} - -// Ra Wifi network saved in the network interface -type SavedWifiNetwork struct { - NetworkID string `json:"network_id"` // Network ID (unique identifier) - SSID string `json:"ssid"` // Network SSID - MacAddress string `json:"mac_address"` // Network BSSID - Connected bool `json:"connected"` // Whether the network is connected -} - -// ConnectedWifiNetwork represents a Wifi network connected to the network interface -type ConnectedWifiNetwork struct { - SSID string `json:"ssid"` // Network SSID (can be hidden) - Frequency int `json:"frequency"` // Network frequency - MacAddress string `json:"mac_address"` // Network BSSID (always available) - Secure bool `json:"secure"` // Whether the network is secure - SecurityType []SecurityType `json:"security_type"` // Network security type - SignalStrength int `json:"signal_strength"` // Network signal strength - NetworkID int `json:"network_id"` // Network ID (unique identifier) -} - -// Represents a Wifi credential for connecting to a Wifi Network -type WifiCredentials struct { - SSID string `json:"ssid"` // Network SSID - Password string `json:"password"` // Network password -} - -// Represents the connection status of a Wifi network -type ConnectionStatus string - -const ( - // Disconnecting from a network - DISCONNECTING ConnectionStatus = "DISCONNECTING" - // Just disconnected from a network - JUST_DISCONNECTED ConnectionStatus = "JUST_DISCONNECTED" - // Still disconnected from a network - STILL_DISCONNECTED ConnectionStatus = "STILL_DISCONNECTED" - // Connecting to a network - CONNECTING ConnectionStatus = "CONNECTING" - // Just connected to a network - JUST_CONNECTED ConnectionStatus = "JUST_CONNECTED" - // Still connected to a network - STILL_CONNECTED ConnectionStatus = "STILL_CONNECTED" - // Unknown connection status - UNKNOWN ConnectionStatus = "UNKNOWN" -) diff --git a/system_api/request_types.go b/system_api/request_types.go deleted file mode 100644 index ea221df6..00000000 --- a/system_api/request_types.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -// Represets the request type of toggleWifiStatus() -type WifiToggleRequest struct { - WifiState bool `json:"wifi_state"` // The state the wifi should be set to (true = on, false = off) -} - -// Represets the request type of connectToWifi() -type WifiConnectRequest struct { - WifiSSID string `json:"wifi_ssid"` // The SSID of the wifi network to connect to - WifiPassword string `json:"wifi_password"` // The password of the wifi network to connect to -} - -// Represets the request type of disconnectFromWifi() -type WifiDisconnectRequest struct { - WifiSSID string `json:"wifi_ssid"` // The SSID of the wifi network to disconnect from -} - -// Represets the request type of forgetWifi() -type WifiForgetRequest struct { - WifiSSID string `json:"wifi_ssid"` // The SSID of the wifi network to forget -} - -// // Raised for errors regarding failed parsing of string data to proper structs. -// type ParseError struct { -// Msg string -// } diff --git a/system_api/system_handler.go b/system_api/system_handler.go deleted file mode 100644 index d7729a04..00000000 --- a/system_api/system_handler.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "fmt" - "os/exec" - "runtime" -) - -func ShutDown() error { - platform := runtime.GOOS - switch platform { - case "windows": - _ = exec.Command("shutdown", "/s", "/t", "1") - case "linux", "darwin": - _ = exec.Command("shutdown", "-h", "now") - default: - return fmt.Errorf("Unsupported platform") - } - return nil -} - -func Restart() error { - platform := runtime.GOOS - switch platform { - case "windows": - _ = exec.Command("shutdown", "/r", "/t", "0") - case "linux", "darwin": - _ = exec.Command("sudo", "reboot") - default: - return fmt.Errorf("Unsupported platform") - } - return nil -} diff --git a/system_api/temperature_handler.go b/system_api/temperature_handler.go deleted file mode 100644 index f3ad51d7..00000000 --- a/system_api/temperature_handler.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "fmt" - "os/exec" - "runtime" - "strconv" - "strings" -) - -// GetWindowsTemperature retrieves the CPU temperature on Windows. -func GetWindowsTemperature() (float64, error) { - cmd := exec.Command("powershell", "Get-CimInstance", "-ClassName", "Win32_TemperatureProbe") - output, err := cmd.Output() - if err != nil { - return 0.0, err - } - - lines := strings.Split(string(output), "\r\n") - if len(lines) >= 2 { - fields := strings.Fields(lines[1]) - if len(fields) >= 6 { - temperature, parseErr := strconv.ParseFloat(strings.TrimSpace(fields[5]), 64) - if parseErr != nil { - return 0.0, parseErr - } - return temperature / 10.0, nil - } - } - - return 0.0, fmt.Errorf("Unable to get CPU temperature") -} - -// GetLinuxTemperature retrieves the CPU temperature on Linux. -func GetLinuxTemperature() (float64, error) { - cmd := exec.Command("sensors") - output, err := cmd.Output() - if err != nil { - return 0.0, err - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if strings.Contains(line, "Core 0") { - fields := strings.Fields(line) - if len(fields) >= 3 { - temperature, parseErr := strconv.ParseFloat(strings.Trim(fields[2], "+°C"), 64) - if parseErr != nil { - return 0.0, parseErr - } - return temperature, nil - } - } - } - - return 0.0, fmt.Errorf("Unable to get CPU temperature") -} - -// GetMacTemperature retrieves the CPU temperature on macOS. -func GetMacTemperature() (float64, error) { - output, err := exec.Command("osx-cpu-temp").Output() - if err != nil { - return 0.0, err - } - - temperature, parseErr := strconv.ParseFloat(strings.TrimSpace(string(output)), 64) - if parseErr != nil { - return 0.0, parseErr - } - - return temperature, nil -} - -// GetTemperature retrieves the CPU temperature based on the current OS. -func GetTemperature() (map[string]float64, error) { - platform := runtime.GOOS - - cpuTemperature := 0.0 - var err error - - switch platform { - case "windows": - cpuTemperature, err = GetWindowsTemperature() - case "linux": - cpuTemperature, err = GetLinuxTemperature() - case "darwin": - cpuTemperature, err = GetMacTemperature() - default: - return nil, fmt.Errorf("Unsupported platform") - } - - if err != nil { - return nil, err - } - - tempInfo := map[string]float64{"processor_temp": cpuTemperature} - return tempInfo, nil -} diff --git a/system_api/wifi_handler.go b/system_api/wifi_handler.go deleted file mode 100644 index 026279e6..00000000 --- a/system_api/wifi_handler.go +++ /dev/null @@ -1,224 +0,0 @@ -package main - -import ( - "fmt" - "os/exec" - "strconv" - "strings" - "time" - - "github.com/theojulienne/go-wireless" -) - -type WifiHandler struct { - WPASupplicant *wireless.Client - TimeoutDuration time.Duration - InterfaceName string - ScanResults []ScannedWifiNetwork - SavedResults []SavedWifiNetwork -} - -func NewWifiHandler() (*WifiHandler, error) { - wifiHandler := &WifiHandler{} - err := wifiHandler.init() - if err != nil { - return nil, err - } - return wifiHandler, nil -} - -func (wh *WifiHandler) init() error { - // Establish a socket connection with the wifi manager - interfaces := wireless.Interfaces() - - if len(interfaces) == 0 { - return fmt.Errorf("No valid wifi interfaces found") - } - - Log.Printf("Connecting to wifi manager on interface %s", interfaces[0]) - - if err != nil { - return fmt.Errorf("Error connecting to wifi manager: %v", err) - } - - // Set the interface name - wh.InterfaceName = interfaces[0] - - wh.ScanResults = []ScannedWifiNetwork{} - wh.SavedResults = []SavedWifiNetwork{} - wh.WPASupplicant, err = wireless.NewClient(wh.InterfaceName) - - // Set the timeout duration - wh.TimeoutDuration = time.Second * 1 - wh.WPASupplicant.ScanTimeout = wh.TimeoutDuration - return nil -} - -func (wh *WifiHandler) NetworkScan() ([]ScannedWifiNetwork, error) { - scanResults, err := wh.WPASupplicant.Scan() - if err != nil { - if err.Error() == "FAIL-BUSY\n" { - return wh.ScanResults, nil - } else { - return nil, fmt.Errorf("Error getting scan results: %v", errs) - } - } - var scannedNetworks []ScannedWifiNetwork - - for _, network := range scanResults { - // check if the network is secure - joinedFlags := strings.Join(network.Flags, "") - securityTypes := []SecurityType{} - if strings.Contains(joinedFlags, "WPA") { - securityTypes = append(securityTypes, WPA) - } - if strings.Contains(joinedFlags, "WEP") { - securityTypes = append(securityTypes, WEP) - } - if strings.Contains(joinedFlags, "WSN") { - securityTypes = append(securityTypes, WSN) - } - - scannedNetworks = append(scannedNetworks, ScannedWifiNetwork{ - SSID: network.SSID, - Frequency: network.Frequency, - MacAddress: network.BSSID, - Secure: len(securityTypes) > 0, - SecurityType: securityTypes, - SignalStrength: network.Signal, - }) - } - - wh.ScanResults = scannedNetworks - - return scannedNetworks, nil -} - -func (wh *WifiHandler) NetworkStatus() (*NetworkStatus, error) { - result, err := wh.WPASupplicant.Status() - if err != nil { - return nil, fmt.Errorf("Error getting status: %v", err) - } - - return &NetworkStatus{ - WPAState: result.WpaState, - KeyMgmt: result.KeyManagement, - IPAddr: result.IPAddress, - SSID: result.SSID, - Address: result.Address, - BSSID: result.BSSID, - // Freq: result., - }, nil -} - -func (wh *WifiHandler) NetworkSaved() ([]SavedWifiNetwork, error) { - savedResults, err := wh.WPASupplicant.Networks() - if err != nil { - return nil, fmt.Errorf("Error listing connected networks: %v", err) - } - - var savedNetworks []SavedWifiNetwork - - for _, network := range savedResults { - savedNetworks = append(savedNetworks, SavedWifiNetwork{ - SSID: network.SSID, - MacAddress: network.BSSID, - NetworkID: network.IDStr, - Connected: strings.Contains(strings.Join(network.Flags, ""), "CURRENT"), - }) - } - - wh.SavedResults = savedNetworks - - return savedNetworks, nil -} - -func (wh *WifiHandler) NetworkConnected() ([]SavedWifiNetwork, error) { - savedResults, err := wh.WPASupplicant.Networks() - if err != nil { - return nil, fmt.Errorf("Error listing connected networks: %v", err) - } - - var connectedNetworks []SavedWifiNetwork - - for _, network := range savedResults { - if strings.Contains(strings.Join(network.Flags, ""), "CURRENT") { - connectedNetworks = append(connectedNetworks, SavedWifiNetwork{ - SSID: network.SSID, - MacAddress: network.BSSID, - NetworkID: network.IDStr, - Connected: true, - }) - } - } - return connectedNetworks, nil -} - -func (wh *WifiHandler) NetworkToggle(wifiOn bool) (bool, error) { - var cmdArgs []string - // InterfaceName - if !wifiOn { - cmdArgs = []string{"sudo", "ifconfig", wh.InterfaceName, "up"} - } else { - cmdArgs = []string{"sudo", "ifconfig", wh.InterfaceName, "down"} - } - output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output() - - if err != nil || strings.Contains(string(output), "Error") { - return wifiOn, err - } - - // Sleep for 3 seconds (3000 milliseconds) - time.Sleep(3 * time.Second) - - return !wifiOn, nil -} - -func (wh *WifiHandler) NetworkConnect(WifiSSID string, WifiPassword string) (bool, error) { - // net := wireless.NewNetwork(WifiSSID, WifiPassword) - // net, err := wh.WPASupplicant.Connect(net) - exec.Command("nmcli", "con", "delete", WifiSSID) - cmd := exec.Command("nmcli", "device", "wifi", "connect", WifiSSID, "password", WifiPassword) - out, err := cmd.Output() - if err != nil { - // if there was any error, print it here - fmt.Println("could not run command: ", err) - return false, err - } - // otherwise, print the output from running the command - fmt.Println("Output: ", string(out)) - return true, nil -} - -func (wh *WifiHandler) NetworkDisconnect(WifiSSID string) (bool, error) { - cmd := exec.Command("nmcli", "con", "down", WifiSSID) - out, err := cmd.Output() - if err != nil { - // if there was any error, print it here - fmt.Println("could not run command: ", err) - return false, err - } - // otherwise, print the output from running the command - fmt.Println("Output: ", string(out)) - return true, nil -} - -func (wh *WifiHandler) NetworkForget(WifiSSID string) (bool, error) { - var filtered []int - for _, item := range wh.SavedResults { - if item.SSID == WifiSSID { - num, err := strconv.Atoi(item.SSID) - if err != nil { - return false, err - } - filtered = append(filtered, num) - } - } - for _, network := range filtered { - err := wh.WPASupplicant.RemoveNetwork(network) - if err != nil { - return false, err - } - } - return true, nil -} From 026f81b8d0132427eabfae99e2b29cedd47902d3 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Mon, 30 Sep 2024 17:36:18 -0700 Subject: [PATCH 02/27] Add initial WifiManager --- backend_py/src/services/wifi/__init__.py | 57 ++++++++++++++++++++++ backend_py/src/services/wifi/wifi_types.py | 0 2 files changed, 57 insertions(+) create mode 100644 backend_py/src/services/wifi/__init__.py create mode 100644 backend_py/src/services/wifi/wifi_types.py diff --git a/backend_py/src/services/wifi/__init__.py b/backend_py/src/services/wifi/__init__.py new file mode 100644 index 00000000..59678536 --- /dev/null +++ b/backend_py/src/services/wifi/__init__.py @@ -0,0 +1,57 @@ +from typing import List +from dataclasses import dataclass +import nmcli + +@dataclass +class ScanResult: + name: str + bssid: str + signal: int + in_use: bool + password_required: bool + +class WiFiManager: + def __init__(self) -> None: + self.active_connection = self._get_connection() + self.connections = self._get_connections() + self.scan_results = self._scan() + + print(f'Active Connection: {self.active_connection}') + print(f'Connections: {self.connections}') + print(f'Scan Results: {self.scan_results}') + + def connect(self, ssid: str, password: str = ''): + nmcli.device.wifi_connect(ssid, password) + + def disconnect(self): + nmcli.device.disconnect() + + def _scan(self) -> List[ScanResult]: + results = [] + for device_wifi in nmcli.device.wifi(rescan=True): + results.append( + ScanResult(device_wifi.ssid, device_wifi.bssid, device_wifi.signal, device_wifi.in_use, 'WPA2' in device_wifi.security) + ) + return results + + def _get_connections(self) -> List[str]: + wifi_connections = [] + for connection in nmcli.connection(): + if connection.conn_type == 'wifi': + wifi_connections.append(connection.name) + return wifi_connections + + def _get_connection(self) -> None | str: + devices = nmcli.device.status() + wifi_device = None + for device in devices: + if device.device_type == 'wifi': + wifi_device = device + + if wifi_device is not None: + return wifi_device.connection + + return None + +wifi_manager = WiFiManager() +wifi_manager.connect('Brandon_5G-2') diff --git a/backend_py/src/services/wifi/wifi_types.py b/backend_py/src/services/wifi/wifi_types.py new file mode 100644 index 00000000..e69de29b From ce79e87e85349360a4b6f48fd72ae3e6259910a9 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 13:11:05 -0700 Subject: [PATCH 03/27] Rework WiFi --- backend_py/src/services/wifi/__init__.py | 57 ------- .../src/services/wifi/network_manager.py | 145 ++++++++++++++++++ backend_py/src/services/wifi/schemas.py | 10 ++ backend_py/src/services/wifi/wifi_manager.py | 35 +++++ backend_py/src/services/wifi/wifi_types.py | 12 ++ 5 files changed, 202 insertions(+), 57 deletions(-) create mode 100644 backend_py/src/services/wifi/network_manager.py create mode 100644 backend_py/src/services/wifi/schemas.py create mode 100644 backend_py/src/services/wifi/wifi_manager.py diff --git a/backend_py/src/services/wifi/__init__.py b/backend_py/src/services/wifi/__init__.py index 59678536..e69de29b 100644 --- a/backend_py/src/services/wifi/__init__.py +++ b/backend_py/src/services/wifi/__init__.py @@ -1,57 +0,0 @@ -from typing import List -from dataclasses import dataclass -import nmcli - -@dataclass -class ScanResult: - name: str - bssid: str - signal: int - in_use: bool - password_required: bool - -class WiFiManager: - def __init__(self) -> None: - self.active_connection = self._get_connection() - self.connections = self._get_connections() - self.scan_results = self._scan() - - print(f'Active Connection: {self.active_connection}') - print(f'Connections: {self.connections}') - print(f'Scan Results: {self.scan_results}') - - def connect(self, ssid: str, password: str = ''): - nmcli.device.wifi_connect(ssid, password) - - def disconnect(self): - nmcli.device.disconnect() - - def _scan(self) -> List[ScanResult]: - results = [] - for device_wifi in nmcli.device.wifi(rescan=True): - results.append( - ScanResult(device_wifi.ssid, device_wifi.bssid, device_wifi.signal, device_wifi.in_use, 'WPA2' in device_wifi.security) - ) - return results - - def _get_connections(self) -> List[str]: - wifi_connections = [] - for connection in nmcli.connection(): - if connection.conn_type == 'wifi': - wifi_connections.append(connection.name) - return wifi_connections - - def _get_connection(self) -> None | str: - devices = nmcli.device.status() - wifi_device = None - for device in devices: - if device.device_type == 'wifi': - wifi_device = device - - if wifi_device is not None: - return wifi_device.connection - - return None - -wifi_manager = WiFiManager() -wifi_manager.connect('Brandon_5G-2') diff --git a/backend_py/src/services/wifi/network_manager.py b/backend_py/src/services/wifi/network_manager.py new file mode 100644 index 00000000..08026bf6 --- /dev/null +++ b/backend_py/src/services/wifi/network_manager.py @@ -0,0 +1,145 @@ +import dbus +from typing import List +import time +from wifi_types import Connection, AccessPoint + + +class NetworkManager: + ''' + Class for interfacing with NetworkManager over dbus + ''' + + def __init__(self) -> None: + self.bus = dbus.SystemBus() + self.proxy = self.bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') + self.interface = dbus.Interface(self.proxy, 'org.freedesktop.NetworkManager') + self.props = dbus.Interface(self.proxy, 'org.freedesktop.DBus.Properties') + + def list_wireless_connections(self) -> List[Connection]: + ''' + Get a list of the active wireless connections + ''' + return list(filter(lambda conn: 'wireless' in conn.type, self.list_connections())) + + def get_active_wireless_connection(self) -> Connection | None: + ''' + Get the first active wireless connection + ''' + active_wireless_conections = list(filter(lambda conn: 'wireless' in conn.type, self.get_active_connections())) + return None if len(active_wireless_conections) == 0 else active_wireless_conections[0] + + def list_connections(self) -> List[Connection]: + ''' + Get a list of all the connections saved + ''' + connections = [] + settings_proxy = self.bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Settings') + settings = dbus.Interface(settings_proxy, 'org.freedesktop.NetworkManager.Settings') + + for connection_path in settings.ListConnections(): + proxy = self.bus.get_object('org.freedesktop.NetworkManager', connection_path) + connection = dbus.Interface(proxy, 'org.freedesktop.NetworkManager.Settings.Connection') + config = connection.GetSettings() + connections.append(Connection(config['connection']['id'], config['connection']['type'])) + return connections + + def get_active_connections(self) -> List[Connection]: + ''' + Get a list of active connections, including wired + ''' + active_connections = self.props.Get('org.freedesktop.NetworkManager', 'ActiveConnections') + connections = [] + for connection_path in active_connections: + active_conn_proxy = self.bus.get_object('org.freedesktop.NetworkManager', connection_path) + active_conn = dbus.Interface(active_conn_proxy, 'org.freedesktop.DBus.Properties') + + settings_path = active_conn.Get('org.freedesktop.NetworkManager.Connection.Active', 'Connection') + conn_proxy = self.bus.get_object('org.freedesktop.NetworkManager', settings_path) + connection = dbus.Interface(conn_proxy, 'org.freedesktop.NetworkManager.Settings.Connection') + config = connection.GetSettings() + + connections.append(Connection(config['connection']['id'], config['connection']['type'])) + + return connections + + def scan_wifi(self, timeout=30) -> List[AccessPoint]: + ''' + Scan wifi networks + ''' + devices = self.interface.GetDevices() + for dev_path in devices: + dev_proxy = self.bus.get_object('org.freedesktop.NetworkManager', dev_path) + dev_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties') + dev_type = dev_props.Get('org.freedesktop.NetworkManager.Device', 'DeviceType') + + # is wifi device + if dev_type == 2: + wifi_dev = dbus.Interface(dev_proxy, 'org.freedesktop.NetworkManager.Device.Wireless') + wifi_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties') + + # get the timestamp of the last scan + last_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') + + # request a scan + wifi_dev.RequestScan({}) + + # wait for scan to finish + start_time = time.time() + while time.time() - start_time < timeout: + current_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') + + if current_scan != last_scan: + # scan 'd, return the access points + return self._get_access_points(wifi_dev) + + # wait before checking + time.sleep(0.1) + + raise TimeoutError('Request timed out') + + return [] + + def _get_access_points(self, wifi_dev) -> List[AccessPoint]: + ''' + Get a list of access points. Should only be called after scanning for networks + ''' + access_points: List[AccessPoint] = [] + for ap_path in wifi_dev.GetAccessPoints(): + ap_proxy = self.bus.get_object('org.freedesktop.NetworkManager', ap_path) + ap_props = dbus.Interface(ap_proxy, 'org.freedesktop.DBus.Properties') + + ssid = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'Ssid') + strength = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'Strength') + flags = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'Flags') + wpa_flags = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'WpaFlags') + rsn_flags = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'RsnFlags') + + requires_password = self._ap_requires_password(flags, wpa_flags, rsn_flags) + + access_points.append(AccessPoint(''.join([chr(byte) for byte in ssid]), int(strength), requires_password)) + + return sorted(access_points, key=lambda ap: ap.strength, reverse=True) + + def _ap_requires_password(self, flags: int, wpa_flags: int, rsn_flags: int): + ''' + Check if a given access point requires password + ''' + NM_802_11_AP_FLAGS_PRIVACY = 0x1 + + # check the overall flags and additionally check if there are any security flags which would indicate a password is needed + return flags & NM_802_11_AP_FLAGS_PRIVACY or wpa_flags != 0 or rsn_flags != 0 + +nm = NetworkManager() +print ('All Conections') +for conn in nm.list_wireless_connections(): + print(f'{conn.id}: {conn.type}') + +print() + +active_wireless = nm.get_active_wireless_connection() +print (f'Active Connection: {active_wireless.id} ({active_wireless.type})') + +print() + +for ap in nm.scan_wifi(timeout=30): + print(f'{ap.ssid}: {ap.strength} - {"Password Auth" if ap.requires_password else ""}') diff --git a/backend_py/src/services/wifi/schemas.py b/backend_py/src/services/wifi/schemas.py new file mode 100644 index 00000000..26fadda4 --- /dev/null +++ b/backend_py/src/services/wifi/schemas.py @@ -0,0 +1,10 @@ +from marshmallow import Schema, fields + +class ConnectionSchema(Schema): + id = fields.Str(required=True) + type = fields.Str(required=True) + +class AccessPointSchema(Schema): + ssid = fields.Str(required=True) + strength = fields.Int(required=True) + requires_password = fields.Bool(required=True) diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py new file mode 100644 index 00000000..e3a992e7 --- /dev/null +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -0,0 +1,35 @@ +from typing import List +from wifi_types import AccessPoint +from schemas import AccessPointSchema, ConnectionSchema +import threading +import time +import logging +from network_manager import NetworkManager + +class WiFiManager: + + def __init__(self) -> None: + self.nm = NetworkManager() + self._thread = threading.Thread(target=self._scan) + self._is_scanning = False + self.access_points: List[AccessPoint] + + def start_scanning(self): + self._is_scanning = True + self._thread.start() + + def get_access_points(self): + return AccessPointSchema().dump(self.access_points, many=True) + + def list_connections(self): + return ConnectionSchema().dump(self.nm.list_wireless_connections(), many=True) + + def _scan(self): + while self._is_scanning: + try: + self.access_points = self.nm.scan_wifi() + except TimeoutError as e: + logging.warning(e) + + # sleep to allow for rescanning later + time.sleep(10) diff --git a/backend_py/src/services/wifi/wifi_types.py b/backend_py/src/services/wifi/wifi_types.py index e69de29b..ee886800 100644 --- a/backend_py/src/services/wifi/wifi_types.py +++ b/backend_py/src/services/wifi/wifi_types.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + +@dataclass +class Connection: + id: str + type: str + +@dataclass +class AccessPoint: + ssid: str + strength: int + requires_password: bool From 6ee83b6b60338944ae07af1d1b8df25c17cee165 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 13:33:06 -0700 Subject: [PATCH 04/27] Rework API by switching to blueprints --- backend_py/src/__init__.py | 146 +++--------------- backend_py/src/blueprints/__init__.py | 4 + backend_py/src/blueprints/cameras.py | 109 +++++++++++++ backend_py/src/blueprints/lights.py | 32 ++++ backend_py/src/blueprints/logs.py | 10 ++ backend_py/src/blueprints/preferences.py | 18 +++ backend_py/src/logging/__init__.py | 1 + .../src/services/cameras/device_manager.py | 12 -- 8 files changed, 192 insertions(+), 140 deletions(-) create mode 100644 backend_py/src/blueprints/__init__.py create mode 100644 backend_py/src/blueprints/cameras.py create mode 100644 backend_py/src/blueprints/lights.py create mode 100644 backend_py/src/blueprints/logs.py create mode 100644 backend_py/src/blueprints/preferences.py create mode 100644 backend_py/src/logging/__init__.py diff --git a/backend_py/src/__init__.py b/backend_py/src/__init__.py index cbc1f440..e87f9ba0 100644 --- a/backend_py/src/__init__.py +++ b/backend_py/src/__init__.py @@ -9,6 +9,8 @@ from .websockets.broadcast_server import BroadcastServer from .services import * +from .blueprints import cameras_bp, lights_bp, logs_bp, preferences_bp +from .logging import LogHandler import logging @@ -28,134 +30,22 @@ def main(): light_manager = LightManager(create_pwm_controllers()) preferences_manager = PreferencesManager() - ''' - Logs API - ''' - @app.route('/logs', methods=['GET']) - def get_logs(): - return jsonify(device_manager.get_logs()) - - ''' - Device API - ''' - @app.route('/devices', methods=['GET']) - def get_devices(): - return jsonify(device_manager.get_devices()) - - @app.route('/devices/set_option', methods=['POST']) - def set_option(): - option_value = OptionValueSchema().load(request.get_json()) - - device_manager.set_device_option( - option_value['bus_info'], option_value['option'], option_value['value']) - - return jsonify({}) - - @app.route('/devices/configure_stream', methods=['POST']) - def configure_stream(): - stream_info = StreamInfoSchema().load(request.get_json()) - - device_manager.configure_device_stream( - stream_info['bus_info'], stream_info) - - return jsonify({}) - - @app.route('/devices/unconfigure_stream', methods=['POST']) - def unconfigure_stream(): - bus_info = StreamInfoSchema(only=['bus_info']).load( - request.get_json())['bus_info'] - - device_manager.uncofigure_device_stream(bus_info) - - return jsonify({}) - - @app.route('/devices/set_nickname', methods=['POST']) - def set_nickname(): - device_nickname = DeviceNicknameSchema().load(request.get_json()) - - device_manager.set_device_nickname( - device_nickname['bus_info'], device_nickname['nickname']) - - return jsonify({}) - - @app.route('/devices/set_uvc_control', methods=['POST']) - def set_uvc_control(): - uvc_control = UVCControlSchema().load(request.get_json()) - - device_manager.set_device_uvc_control( - uvc_control['bus_info'], uvc_control['control_id'], uvc_control['value']) - - return jsonify({}) - - @app.route('/devices/leader_bus_infos') - def get_leader_bus_infos(): - bus_infos = [] - for device in device_manager.devices: - if device.device_type == DeviceType.STELLARHD_LEADER: - bus_infos.append({ - 'nickname': device.nickname, - 'bus_info': device.bus_info - }) - - return jsonify(bus_infos) - - @app.route('/devices/set_leader', methods=['POST']) - def set_leader(): - leader_schema = DeviceLeaderSchema().load(request.get_json()) - device_manager.set_leader(leader_schema['leader'], leader_schema['follower']) - return jsonify({}) - - @app.route('/devices/remove_leader', methods=['POST']) - def remove_leader(): - leader_schema = DeviceLeaderSchema().load(request.get_json()) - device_manager.remove_leader(leader_schema['follower']) - return jsonify({}) - - @app.route('/devices/restart_stream', methods=['POST']) - def restart_stream(): - bus_info = StreamInfoSchema(only=['bus_info']).load(request.get_json())['bus_info'] - dev = device_manager._find_device_with_bus_info(bus_info) - if not dev: - logging.warning(f'Unable to find device {bus_info}') - return jsonify({}) - dev.start_stream() - return jsonify({}) - - ''' - Lights API - ''' - @app.route('/lights') - def get_lights(): - return jsonify(light_manager.get_lights()) - - @app.route('/lights/controllers') - def get_pwm_controllers(): - return jsonify(light_manager.get_pwm_controllers()) - - @app.route('/lights/set_intensity', methods=['POST']) - def set_intensity(): - req = request.get_json() - light_manager.set_intensity(req['index'], req['intensity']) - return jsonify({}) - - @app.route('/lights/disable_pin', methods=['POST']) - def disable_light(): - req = request.get_json() - light_manager.disable_light(req['controller_index'], req['pin']) - return jsonify({}) - - ''' - Preferences API - ''' - @app.route('/preferences') - def get_preferences(): - return jsonify(preferences_manager.serialize_preferences()) - - @app.route('/preferences/save_preferences', methods=['POST']) - def set_preferences(): - req: SavedPrefrences = SavedPrefrencesSchema().load(request.get_json()) - preferences_manager.save(req) - return jsonify({}) + # Create the logging handler + log_handler = LogHandler(broadcast_server) + logging.getLogger().addHandler(log_handler) + logging.info('Log handler started...') + + # Set the app configs + app.config['device_manager'] = device_manager + app.config['light_manager'] = light_manager + app.config['preferences_manager'] = preferences_manager + app.config['log_handler'] = log_handler + + # Register the blueprints + app.register_blueprint(cameras_bp) + app.register_blueprint(lights_bp) + app.register_blueprint(logs_bp) + app.register_blueprint(preferences_bp) # create the server and run everything http_server = WSGIServer(('0.0.0.0', 8080), app, log=None) diff --git a/backend_py/src/blueprints/__init__.py b/backend_py/src/blueprints/__init__.py new file mode 100644 index 00000000..3de1cef9 --- /dev/null +++ b/backend_py/src/blueprints/__init__.py @@ -0,0 +1,4 @@ +from .cameras import * +from .lights import * +from .logs import * +from .preferences import * diff --git a/backend_py/src/blueprints/cameras.py b/backend_py/src/blueprints/cameras.py new file mode 100644 index 00000000..4cf4a6d2 --- /dev/null +++ b/backend_py/src/blueprints/cameras.py @@ -0,0 +1,109 @@ +from flask import Blueprint, request, jsonify, current_app +from ..services import DeviceManager, OptionValueSchema, StreamInfoSchema, DeviceNicknameSchema, UVCControlSchema, DeviceType, DeviceLeaderSchema +import logging + +cameras_bp = Blueprint('cameras', __name__) + +@cameras_bp.route('/devices', methods=['GET']) +def get_devices(): + device_manager: DeviceManager = current_app.config['device_manager'] + + return jsonify(device_manager.get_devices()) + +@cameras_bp.route('/devices/set_option', methods=['POST']) +def set_option(): + device_manager: DeviceManager = current_app.config['device_manager'] + + option_value = OptionValueSchema().load(request.get_json()) + + device_manager.set_device_option( + option_value['bus_info'], option_value['option'], option_value['value']) + + return jsonify({}) + +@cameras_bp.route('/devices/configure_stream', methods=['POST']) +def configure_stream(): + device_manager: DeviceManager = current_app.config['device_manager'] + + stream_info = StreamInfoSchema().load(request.get_json()) + + device_manager.configure_device_stream( + stream_info['bus_info'], stream_info) + + return jsonify({}) + +@cameras_bp.route('/devices/unconfigure_stream', methods=['POST']) +def unconfigure_stream(): + device_manager: DeviceManager = current_app.config['device_manager'] + + bus_info = StreamInfoSchema(only=['bus_info']).load( + request.get_json())['bus_info'] + + device_manager.uncofigure_device_stream(bus_info) + + return jsonify({}) + +@cameras_bp.route('/devices/set_nickname', methods=['POST']) +def set_nickname(): + device_manager: DeviceManager = current_app.config['device_manager'] + + device_nickname = DeviceNicknameSchema().load(request.get_json()) + + device_manager.set_device_nickname( + device_nickname['bus_info'], device_nickname['nickname']) + + return jsonify({}) + +@cameras_bp.route('/devices/set_uvc_control', methods=['POST']) +def set_uvc_control(): + device_manager: DeviceManager = current_app.config['device_manager'] + + uvc_control = UVCControlSchema().load(request.get_json()) + + device_manager.set_device_uvc_control( + uvc_control['bus_info'], uvc_control['control_id'], uvc_control['value']) + + return jsonify({}) + +@cameras_bp.route('/devices/leader_bus_infos') +def get_leader_bus_infos(): + device_manager: DeviceManager = current_app.config['device_manager'] + + bus_infos = [] + for device in device_manager.devices: + if device.device_type == DeviceType.STELLARHD_LEADER: + bus_infos.cameras_bpend({ + 'nickname': device.nickname, + 'bus_info': device.bus_info + }) + + return jsonify(bus_infos) + +@cameras_bp.route('/devices/set_leader', methods=['POST']) +def set_leader(): + device_manager: DeviceManager = current_app.config['device_manager'] + + leader_schema = DeviceLeaderSchema().load(request.get_json()) + device_manager.set_leader(leader_schema['leader'], leader_schema['follower']) + return jsonify({}) + +@cameras_bp.route('/devices/remove_leader', methods=['POST']) +def remove_leader(): + device_manager: DeviceManager = current_app.config['device_manager'] + + leader_schema = DeviceLeaderSchema().load(request.get_json()) + device_manager.remove_leader(leader_schema['follower']) + return jsonify({}) + +@cameras_bp.route('/devices/restart_stream', methods=['POST']) +def restart_stream(): + device_manager: DeviceManager = current_app.config['device_manager'] + + bus_info = StreamInfoSchema(only=['bus_info']).load(request.get_json())['bus_info'] + dev = device_manager._find_device_with_bus_info(bus_info) + if not dev: + logging.warning(f'Unable to find device {bus_info}') + return jsonify({}) + + dev.start_stream() + return jsonify({}) diff --git a/backend_py/src/blueprints/lights.py b/backend_py/src/blueprints/lights.py new file mode 100644 index 00000000..65405490 --- /dev/null +++ b/backend_py/src/blueprints/lights.py @@ -0,0 +1,32 @@ +from flask import Blueprint, request, jsonify, current_app +from ..services import LightManager + +lights_bp = Blueprint('lights', __name__) + +@lights_bp.route('/lights') +def get_lights(): + light_manager: LightManager = current_app.config['light_manager'] + + return jsonify(light_manager.get_lights()) + +@lights_bp.route('/lights/controllers') +def get_pwm_controllers(): + light_manager: LightManager = current_app.config['light_manager'] + + return jsonify(light_manager.get_pwm_controllers()) + +@lights_bp.route('/lights/set_intensity', methods=['POST']) +def set_intensity(): + light_manager: LightManager = current_app.config['light_manager'] + + req = request.get_json() + light_manager.set_intensity(req['index'], req['intensity']) + return jsonify({}) + +@lights_bp.route('/lights/disable_pin', methods=['POST']) +def disable_light(): + light_manager: LightManager = current_app.config['light_manager'] + + req = request.get_json() + light_manager.disable_light(req['controller_index'], req['pin']) + return jsonify({}) diff --git a/backend_py/src/blueprints/logs.py b/backend_py/src/blueprints/logs.py new file mode 100644 index 00000000..f6c72175 --- /dev/null +++ b/backend_py/src/blueprints/logs.py @@ -0,0 +1,10 @@ +from flask import Blueprint, jsonify, current_app +from ..logging import LogHandler + +logs_bp = Blueprint('logs', __name__) + +@logs_bp.route('/logs', methods=['GET']) +def get_logs(): + log_handler: LogHandler = current_app.config['log_handler'] + + return jsonify(log_handler.logs) \ No newline at end of file diff --git a/backend_py/src/blueprints/preferences.py b/backend_py/src/blueprints/preferences.py new file mode 100644 index 00000000..ff3536f9 --- /dev/null +++ b/backend_py/src/blueprints/preferences.py @@ -0,0 +1,18 @@ +from flask import Blueprint, request, jsonify, current_app +from ..services import PreferencesManager, SavedPrefrences, SavedPrefrencesSchema + +preferences_bp = Blueprint('preferences', __name__) + +@preferences_bp.route('/preferences') +def get_preferences(): + preferences_manager: PreferencesManager = current_app.config['preferences_manager'] + + return jsonify(preferences_manager.serialize_preferences()) + +@preferences_bp.route('/preferences/save_preferences', methods=['POST']) +def set_preferences(): + preferences_manager: PreferencesManager = current_app.config['preferences_manager'] + + req: SavedPrefrences = SavedPrefrencesSchema().load(request.get_json()) + preferences_manager.save(req) + return jsonify({}) diff --git a/backend_py/src/logging/__init__.py b/backend_py/src/logging/__init__.py new file mode 100644 index 00000000..0b25443e --- /dev/null +++ b/backend_py/src/logging/__init__.py @@ -0,0 +1 @@ +from .log_handler import * \ No newline at end of file diff --git a/backend_py/src/services/cameras/device_manager.py b/backend_py/src/services/cameras/device_manager.py index f2c19d5f..e0d5dfa8 100644 --- a/backend_py/src/services/cameras/device_manager.py +++ b/backend_py/src/services/cameras/device_manager.py @@ -15,8 +15,6 @@ from .ehd import EHDDevice from .shd import SHDDevice -from ...logging.log_handler import LogHandler - class DeviceManager(events.EventEmitter): ''' @@ -31,10 +29,6 @@ def __init__(self, broadcast_server=BroadcastServer(), settings_manager=Settings self._thread = threading.Thread(target=self._monitor) self._is_monitoring = False - self.log_handler = LogHandler(self.broadcast_server) - logging.getLogger().addHandler(self.log_handler) - logging.info('Log handler started...') - def start_monitoring(self): ''' Begin monitoring for devices in the background @@ -52,12 +46,6 @@ def stop_monitoring(self): for device in self.devices: device.stream.stop() - def get_logs(self): - ''' - Get the list of logs - ''' - return self.log_handler.logs - def create_device(self, device_info: DeviceInfo) -> Device | None: ''' Create a new device based on enumerated device info From 96a970b5b4db1c63cbf7c88a1875a3786ee2ea73 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 14:36:30 -0700 Subject: [PATCH 05/27] Initial WiFi API blueprint --- backend_py/src/__init__.py | 7 +- backend_py/src/blueprints/__init__.py | 1 + backend_py/src/blueprints/wifi.py | 20 +++ backend_py/src/services/__init__.py | 3 +- backend_py/src/services/wifi/__init__.py | 1 + .../src/services/wifi/network_manager.py | 161 +++++++++++++----- backend_py/src/services/wifi/wifi_manager.py | 47 +++-- 7 files changed, 188 insertions(+), 52 deletions(-) create mode 100644 backend_py/src/blueprints/wifi.py diff --git a/backend_py/src/__init__.py b/backend_py/src/__init__.py index e87f9ba0..3ef6086e 100644 --- a/backend_py/src/__init__.py +++ b/backend_py/src/__init__.py @@ -9,7 +9,7 @@ from .websockets.broadcast_server import BroadcastServer from .services import * -from .blueprints import cameras_bp, lights_bp, logs_bp, preferences_bp +from .blueprints import cameras_bp, lights_bp, logs_bp, preferences_bp, wifi_bp from .logging import LogHandler import logging @@ -29,6 +29,7 @@ def main(): settings_manager=settings_manager, broadcast_server=broadcast_server) light_manager = LightManager(create_pwm_controllers()) preferences_manager = PreferencesManager() + wifi_manager = WiFiManager() # Create the logging handler log_handler = LogHandler(broadcast_server) @@ -40,16 +41,19 @@ def main(): app.config['light_manager'] = light_manager app.config['preferences_manager'] = preferences_manager app.config['log_handler'] = log_handler + app.config['wifi_manager'] = wifi_manager # Register the blueprints app.register_blueprint(cameras_bp) app.register_blueprint(lights_bp) app.register_blueprint(logs_bp) app.register_blueprint(preferences_bp) + app.register_blueprint(wifi_bp) # create the server and run everything http_server = WSGIServer(('0.0.0.0', 8080), app, log=None) device_manager.start_monitoring() + wifi_manager.start_scanning() def exit_clean(sig, frame): logging.info('Shutting down') @@ -59,6 +63,7 @@ def exit_clean(sig, frame): http_server.stop() device_manager.stop_monitoring() broadcast_server.kill() + wifi_manager.stop_scanning() sys.exit(0) diff --git a/backend_py/src/blueprints/__init__.py b/backend_py/src/blueprints/__init__.py index 3de1cef9..ebfce82b 100644 --- a/backend_py/src/blueprints/__init__.py +++ b/backend_py/src/blueprints/__init__.py @@ -2,3 +2,4 @@ from .lights import * from .logs import * from .preferences import * +from .wifi import * diff --git a/backend_py/src/blueprints/wifi.py b/backend_py/src/blueprints/wifi.py new file mode 100644 index 00000000..b25d25f3 --- /dev/null +++ b/backend_py/src/blueprints/wifi.py @@ -0,0 +1,20 @@ +from flask import Blueprint, request, jsonify, current_app +from ..services import WiFiManager + +wifi_bp = Blueprint('wifi', __name__) + +@wifi_bp.route('/wifi/status') +def wifi_status(): + wifi_manager: WiFiManager = current_app.config['wifi_manager'] + return jsonify(wifi_manager.get_active_connection()) + +@wifi_bp.route('/wifi/access_points') +def access_points(): + wifi_manager: WiFiManager = current_app.config['wifi_manager'] + return jsonify(wifi_manager.get_access_points()) + +@wifi_bp.route('/wifi/connections') +def list_wifi_connections(): + wifi_manager: WiFiManager = current_app.config['wifi_manager'] + + return jsonify(wifi_manager.list_connections()) diff --git a/backend_py/src/services/__init__.py b/backend_py/src/services/__init__.py index 25ddc59e..fec71ebe 100644 --- a/backend_py/src/services/__init__.py +++ b/backend_py/src/services/__init__.py @@ -1,3 +1,4 @@ from .cameras import * from .lights import * -from .preferences import * \ No newline at end of file +from .preferences import * +from .wifi import * diff --git a/backend_py/src/services/wifi/__init__.py b/backend_py/src/services/wifi/__init__.py index e69de29b..2ea4ffff 100644 --- a/backend_py/src/services/wifi/__init__.py +++ b/backend_py/src/services/wifi/__init__.py @@ -0,0 +1 @@ +from .wifi_manager import * \ No newline at end of file diff --git a/backend_py/src/services/wifi/network_manager.py b/backend_py/src/services/wifi/network_manager.py index 08026bf6..bbb47286 100644 --- a/backend_py/src/services/wifi/network_manager.py +++ b/backend_py/src/services/wifi/network_manager.py @@ -1,8 +1,11 @@ import dbus from typing import List import time -from wifi_types import Connection, AccessPoint +from .wifi_types import Connection, AccessPoint +class NMNotSupportedError(Exception): + '''Exception raised when NetworkManager is not supported''' + pass class NetworkManager: ''' @@ -12,9 +15,90 @@ class NetworkManager: def __init__(self) -> None: self.bus = dbus.SystemBus() self.proxy = self.bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') + + if not self.proxy: + raise NMNotSupportedError('NetworkManager is not installed on this system.') + self.interface = dbus.Interface(self.proxy, 'org.freedesktop.NetworkManager') self.props = dbus.Interface(self.proxy, 'org.freedesktop.DBus.Properties') + def connect(self, ssid: str, password='') -> bool: + (wifi_dev, dev_proxy) = self._get_wifi_device() + if wifi_dev is None: + raise Exception('No WiFi device found') + + wifi_interface = dbus.Interface(dev_proxy, 'org.freedesktop.NetworkManager.Device.Wireless') + # Do not need to request a scan since the scan must have happened for the user to know this network exists + access_points = wifi_interface.GetAccessPoints() + + ap_path = None + ap_requires_password = False + for ap in access_points: + ap_proxy = self.bus.get_object('org.freedesktop.NetworkManager', ap) + ap_props = dbus.Interface(ap_proxy, 'org.freedesktop.DBus.Properties') + ap_ssid = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'Ssid') + ssid_str = ''.join([chr(byte) for byte in ap_ssid]) + + if ssid_str == ssid: + ap_path = ap + + # Check the security of the AP + flags = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'Flags') + wpa_flags = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'WpaFlags') + rsn_flags = ap_props.Get('org.freedesktop.NetworkManager.AccessPoint', 'RsnFlags') + ap_requires_password = self._ap_requires_password(flags, wpa_flags, rsn_flags) + break + + if ap_path is None: + raise Exception(f'Access point with SSID {ssid} not found') + + # Create the settings object assuming no password is needed + connection_settings = { + '802-11-wireless': { + 'ssid': dbus.ByteArray(ssid.encode('utf-8')), + 'mode': 'infrastructure' + }, + 'connection': { + 'id': ssid, + 'type': '802-11-wireless' + }, + 'ipv4': { + 'method': 'auto' + }, + 'ipv6': { + 'method': 'ignore' + } + } + + # Add security settings if the network requires a password + if ap_requires_password: + connection_settings['802-11-wireless-security'] = { + 'key-mgmt': 'wpa-psk', + 'psk': password + } + + settings_proxy = self.bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Settings') + settings_interface = dbus.Interface(settings_proxy, 'org.freedesktop.NetworkManager.Settings') + + connection_path = settings_interface.AddConnection(connection_settings) + self.interface.ActivateConnection(connection_path, dev_proxy, ap_path) + + def disconnect(self): + (wifi_dev, dev_proxy) = self._get_wifi_device() + + if not wifi_dev: + raise Exception('No WiFi device found') + + # get the device properties (yes this is gotten already in the get_wifi_device function) + dev_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties') + active_connection = dev_props.Get('org.freedesktop.NetworkManager.Device', 'ActiveConnection') + + # Deactivate the current active connection + self.interface.DeactivateConnection(active_connection) + + return True + + def list_wireless_connections(self) -> List[Connection]: ''' Get a list of the active wireless connections @@ -62,10 +146,45 @@ def get_active_connections(self) -> List[Connection]: return connections + def get_access_points(self) -> List[AccessPoint]: + ''' + Get wifi networks without a scan + ''' + (wifi_dev,_) = self._get_wifi_device() + return self._get_access_points(wifi_dev) + + def scan_wifi(self, timeout=30) -> List[AccessPoint]: ''' Scan wifi networks ''' + (wifi_dev, dev_proxy) = self._get_wifi_device() + + wifi_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties') + + # get the timestamp of the last scan + last_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') + + # request a scan + wifi_dev.RequestScan({}) + + # wait for scan to finish + start_time = time.time() + while time.time() - start_time < timeout: + current_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') + + if current_scan != last_scan: + # scan 'd, return the access points + return self._get_access_points(wifi_dev) + + # wait before checking + time.sleep(0.1) + + raise TimeoutError('Request timed out') + + return [] + + def _get_wifi_device(self): devices = self.interface.GetDevices() for dev_path in devices: dev_proxy = self.bus.get_object('org.freedesktop.NetworkManager', dev_path) @@ -75,29 +194,8 @@ def scan_wifi(self, timeout=30) -> List[AccessPoint]: # is wifi device if dev_type == 2: wifi_dev = dbus.Interface(dev_proxy, 'org.freedesktop.NetworkManager.Device.Wireless') - wifi_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties') - - # get the timestamp of the last scan - last_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') - - # request a scan - wifi_dev.RequestScan({}) - - # wait for scan to finish - start_time = time.time() - while time.time() - start_time < timeout: - current_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') - - if current_scan != last_scan: - # scan 'd, return the access points - return self._get_access_points(wifi_dev) - - # wait before checking - time.sleep(0.1) - - raise TimeoutError('Request timed out') - - return [] + return (wifi_dev, dev_proxy) + return (None, None) def _get_access_points(self, wifi_dev) -> List[AccessPoint]: ''' @@ -128,18 +226,3 @@ def _ap_requires_password(self, flags: int, wpa_flags: int, rsn_flags: int): # check the overall flags and additionally check if there are any security flags which would indicate a password is needed return flags & NM_802_11_AP_FLAGS_PRIVACY or wpa_flags != 0 or rsn_flags != 0 - -nm = NetworkManager() -print ('All Conections') -for conn in nm.list_wireless_connections(): - print(f'{conn.id}: {conn.type}') - -print() - -active_wireless = nm.get_active_wireless_connection() -print (f'Active Connection: {active_wireless.id} ({active_wireless.type})') - -print() - -for ap in nm.scan_wifi(timeout=30): - print(f'{ap.ssid}: {ap.strength} - {"Password Auth" if ap.requires_password else ""}') diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index e3a992e7..edf5e2cc 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -1,35 +1,60 @@ from typing import List -from wifi_types import AccessPoint -from schemas import AccessPointSchema, ConnectionSchema +from .wifi_types import AccessPoint +from .schemas import AccessPointSchema, ConnectionSchema import threading import time import logging -from network_manager import NetworkManager +from .network_manager import NetworkManager class WiFiManager: - def __init__(self) -> None: + def __init__(self, scan_interval=10) -> None: self.nm = NetworkManager() self._thread = threading.Thread(target=self._scan) self._is_scanning = False - self.access_points: List[AccessPoint] + self.scan_interval = scan_interval + + # get initial access points before scan + self.access_points = self.nm.get_access_points() + + def connect(self, ssid: str, password = '') -> bool: + return self.nm.connect(ssid, password) + + def disconnect(self): + self.nm.disconnect() def start_scanning(self): self._is_scanning = True self._thread.start() + def stop_scanning(self): + self._is_scanning = False + self._thread.join() + def get_access_points(self): return AccessPointSchema().dump(self.access_points, many=True) + def get_active_connection(self): + return ConnectionSchema().dump(self.nm.get_active_wireless_connection()) + def list_connections(self): return ConnectionSchema().dump(self.nm.list_wireless_connections(), many=True) def _scan(self): + start_time = time.time() while self._is_scanning: - try: - self.access_points = self.nm.scan_wifi() - except TimeoutError as e: - logging.warning(e) + current_time = time.time() + elapsed_time = current_time - start_time + + # this is used so the server does not hang on close + if elapsed_time >= self.scan_interval: + try: + self.access_points = self.nm.scan_wifi() + except TimeoutError as e: + logging.warning(e) + + # reset the counter + start_time = current_time - # sleep to allow for rescanning later - time.sleep(10) + # No reason to check too often + time.sleep(0.1) From edd776c7fc299bb749100f362092035d0ebbec11 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 15:51:45 -0700 Subject: [PATCH 06/27] Add wifi connection endpoints and update requirements --- backend_py/requirements.txt | 3 ++- backend_py/src/blueprints/wifi.py | 18 +++++++++++++++- backend_py/src/services/wifi/__init__.py | 3 ++- .../src/services/wifi/network_manager.py | 21 +++++++++++-------- backend_py/src/services/wifi/schemas.py | 11 +++++++++- backend_py/src/services/wifi/wifi_manager.py | 6 +++++- backend_py/src/services/wifi/wifi_types.py | 5 +++++ 7 files changed, 53 insertions(+), 14 deletions(-) diff --git a/backend_py/requirements.txt b/backend_py/requirements.txt index 1be2de07..370f4c11 100644 --- a/backend_py/requirements.txt +++ b/backend_py/requirements.txt @@ -7,4 +7,5 @@ marshmallow==3.22.0 natsort==8.4.0 PyEventEmitter==1.0.5 rpi_hardware_pwm==0.2.2 -websockets==12.0 \ No newline at end of file +websockets==12.0 +dbus-python==1.2.18 \ No newline at end of file diff --git a/backend_py/src/blueprints/wifi.py b/backend_py/src/blueprints/wifi.py index b25d25f3..aefac7f7 100644 --- a/backend_py/src/blueprints/wifi.py +++ b/backend_py/src/blueprints/wifi.py @@ -1,5 +1,6 @@ from flask import Blueprint, request, jsonify, current_app -from ..services import WiFiManager +from ..services import WiFiManager, NetworkConfigSchema, NetworkConfig +from marshmallow import ValidationError wifi_bp = Blueprint('wifi', __name__) @@ -18,3 +19,18 @@ def list_wifi_connections(): wifi_manager: WiFiManager = current_app.config['wifi_manager'] return jsonify(wifi_manager.list_connections()) + +@wifi_bp.route('/wifi/connect', methods=['POST']) +def connect(): + wifi_manager: WiFiManager = current_app.config['wifi_manager'] + try: + network_config: NetworkConfig = NetworkConfigSchema().load(request.get_json()) + return jsonify({'status': wifi_manager.connect(network_config.ssid, network_config.password)}) + except ValidationError as e: + return jsonify({'Error': e.messages}) + +@wifi_bp.route('/wifi/disconnect', methods=['POST']) +def disconnect(): + wifi_manager: WiFiManager = current_app.config['wifi_manager'] + wifi_manager.disconnect() + return jsonify({}) diff --git a/backend_py/src/services/wifi/__init__.py b/backend_py/src/services/wifi/__init__.py index 2ea4ffff..693bad2c 100644 --- a/backend_py/src/services/wifi/__init__.py +++ b/backend_py/src/services/wifi/__init__.py @@ -1 +1,2 @@ -from .wifi_manager import * \ No newline at end of file +from .wifi_manager import * +from .schemas import * \ No newline at end of file diff --git a/backend_py/src/services/wifi/network_manager.py b/backend_py/src/services/wifi/network_manager.py index bbb47286..392f5d74 100644 --- a/backend_py/src/services/wifi/network_manager.py +++ b/backend_py/src/services/wifi/network_manager.py @@ -88,17 +88,11 @@ def disconnect(self): if not wifi_dev: raise Exception('No WiFi device found') - - # get the device properties (yes this is gotten already in the get_wifi_device function) + dev_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties') active_connection = dev_props.Get('org.freedesktop.NetworkManager.Device', 'ActiveConnection') - - # Deactivate the current active connection self.interface.DeactivateConnection(active_connection) - return True - - def list_wireless_connections(self) -> List[Connection]: ''' Get a list of the active wireless connections @@ -181,8 +175,6 @@ def scan_wifi(self, timeout=30) -> List[AccessPoint]: time.sleep(0.1) raise TimeoutError('Request timed out') - - return [] def _get_wifi_device(self): devices = self.interface.GetDevices() @@ -226,3 +218,14 @@ def _ap_requires_password(self, flags: int, wpa_flags: int, rsn_flags: int): # check the overall flags and additionally check if there are any security flags which would indicate a password is needed return flags & NM_802_11_AP_FLAGS_PRIVACY or wpa_flags != 0 or rsn_flags != 0 + + def _get_connection_proxy(self, active_connection): + # Get the connection details from the active connection + connection_proxy = dbus.Interface(self.bus.get_object('org.freedesktop.NetworkManager', active_connection), + 'org.freedesktop.DBus.Properties') + + # Get the connection path (this will give us the object path for the connection) + connection_path = connection_proxy.Get('org.freedesktop.NetworkManager.Connection.Active', 'Connection') + + # Return the connection proxy object based on the connection path + return self.bus.get_object('org.freedesktop.NetworkManager', connection_path) diff --git a/backend_py/src/services/wifi/schemas.py b/backend_py/src/services/wifi/schemas.py index 26fadda4..62d70cc2 100644 --- a/backend_py/src/services/wifi/schemas.py +++ b/backend_py/src/services/wifi/schemas.py @@ -1,4 +1,5 @@ -from marshmallow import Schema, fields +from marshmallow import Schema, fields, post_load +from .wifi_types import NetworkConfig class ConnectionSchema(Schema): id = fields.Str(required=True) @@ -8,3 +9,11 @@ class AccessPointSchema(Schema): ssid = fields.Str(required=True) strength = fields.Int(required=True) requires_password = fields.Bool(required=True) + +class NetworkConfigSchema(Schema): + ssid = fields.Str(required=True) + password = fields.Str(required=False) + + @post_load + def make_network_config(self, data, **kwargs): + return NetworkConfig(**data) diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index edf5e2cc..03220797 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -18,7 +18,11 @@ def __init__(self, scan_interval=10) -> None: self.access_points = self.nm.get_access_points() def connect(self, ssid: str, password = '') -> bool: - return self.nm.connect(ssid, password) + try: + self.nm.connect(ssid, password) + except: + return False + return True def disconnect(self): self.nm.disconnect() diff --git a/backend_py/src/services/wifi/wifi_types.py b/backend_py/src/services/wifi/wifi_types.py index ee886800..f9a18b29 100644 --- a/backend_py/src/services/wifi/wifi_types.py +++ b/backend_py/src/services/wifi/wifi_types.py @@ -1,5 +1,10 @@ from dataclasses import dataclass +@dataclass +class NetworkConfig: + ssid: str + password: str = '' + @dataclass class Connection: id: str From 73931740b72e9d5032ea7fed0c771a86caefede4 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 16:45:44 -0700 Subject: [PATCH 07/27] Fix crash when disconnecting with no network connected --- backend_py/src/services/wifi/wifi_manager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index 03220797..d553a412 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -5,6 +5,7 @@ import time import logging from .network_manager import NetworkManager +from dbus import DBusException class WiFiManager: @@ -25,7 +26,11 @@ def connect(self, ssid: str, password = '') -> bool: return True def disconnect(self): - self.nm.disconnect() + try: + self.nm.disconnect() + except DBusException as e: + # ignore exception + pass def start_scanning(self): self._is_scanning = True From 94e79c24f5dc6bd8569d46aae7f8182074775f61 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 19:20:23 -0700 Subject: [PATCH 08/27] Generalize clean.sh --- backend_py/clean.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend_py/clean.sh b/backend_py/clean.sh index bbf58bf0..311ebeac 100755 --- a/backend_py/clean.sh +++ b/backend_py/clean.sh @@ -1,6 +1,10 @@ #!/bin/bash + +# Remove the build directory if it exists rm -rf build || true -rm -rf src/__pycache__ || true -rm -rf src/devices/__pycache__ || true -rm -rf src/lights/__pycache__ || true + +# Find and remove all __pycache__ directories +find . -type d -name "__pycache__" -exec rm -rf {} + + +# Remove the device_settings.json file if it exists rm -f device_settings.json || true From 05c3fb5c31b3008bde7ff5df6c66c9d9335d2493 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 19:23:28 -0700 Subject: [PATCH 09/27] Remove test.py --- backend_py/test.py | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 backend_py/test.py diff --git a/backend_py/test.py b/backend_py/test.py deleted file mode 100644 index 697ec7aa..00000000 --- a/backend_py/test.py +++ /dev/null @@ -1,23 +0,0 @@ -from src.devices.ehd import EHDDevice -from src.devices.shd import SHDDevice -from src.device import DeviceType, lookup_pid_vid, DeviceInfo, Device -from src.enumeration import list_devices - -def create_device(device_info: DeviceInfo) -> Device | None: - (_, device_type) = lookup_pid_vid(device_info.vid, device_info.pid) - - match device_type: - case DeviceType.EXPLOREHD: - return EHDDevice(device_info) - case DeviceType.STELLARHD: - return SHDDevice(device_info) - case _: - # Not a DWE device - return None - -if __name__ == '__main__': - devices = list_devices() - for device_info in devices: - dev = create_device(device_info) - print(dev.name) - From 2fe9c030c9b5c54729d284d4a266d2b44206cec9 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 19:23:48 -0700 Subject: [PATCH 10/27] Switch to using a class to run the server --- backend_py/run.py | 5 ++- backend_py/src/__init__.py | 79 +------------------------------------ backend_py/src/server.py | 81 ++++++++++++++++++++++++++++++++++++++ run_release.py | 5 ++- 4 files changed, 88 insertions(+), 82 deletions(-) create mode 100644 backend_py/src/server.py diff --git a/backend_py/run.py b/backend_py/run.py index c455ba25..95d42d0c 100644 --- a/backend_py/run.py +++ b/backend_py/run.py @@ -1,5 +1,6 @@ -from src import main, logging +from src import Server, logging logging.getLogger().setLevel(logging.INFO) -main() +server = Server() +server.serve() diff --git a/backend_py/src/__init__.py b/backend_py/src/__init__.py index 3ef6086e..fe0338a0 100644 --- a/backend_py/src/__init__.py +++ b/backend_py/src/__init__.py @@ -1,78 +1 @@ -from ctypes import * -import sys -import signal - -from flask import Flask, jsonify, request -from flask_cors import CORS -from gevent.pywsgi import WSGIServer - -from .websockets.broadcast_server import BroadcastServer - -from .services import * -from .blueprints import cameras_bp, lights_bp, logs_bp, preferences_bp, wifi_bp -from .logging import LogHandler - -import logging - - -def main(): - # Create the flask application - app = Flask(__name__) - CORS(app) - # avoid sorting the keys to keep the way we sort it in the backend - app.json.sort_keys = False - - # Create the managers - settings_manager = SettingsManager() - broadcast_server = BroadcastServer() - device_manager = DeviceManager( - settings_manager=settings_manager, broadcast_server=broadcast_server) - light_manager = LightManager(create_pwm_controllers()) - preferences_manager = PreferencesManager() - wifi_manager = WiFiManager() - - # Create the logging handler - log_handler = LogHandler(broadcast_server) - logging.getLogger().addHandler(log_handler) - logging.info('Log handler started...') - - # Set the app configs - app.config['device_manager'] = device_manager - app.config['light_manager'] = light_manager - app.config['preferences_manager'] = preferences_manager - app.config['log_handler'] = log_handler - app.config['wifi_manager'] = wifi_manager - - # Register the blueprints - app.register_blueprint(cameras_bp) - app.register_blueprint(lights_bp) - app.register_blueprint(logs_bp) - app.register_blueprint(preferences_bp) - app.register_blueprint(wifi_bp) - - # create the server and run everything - http_server = WSGIServer(('0.0.0.0', 8080), app, log=None) - device_manager.start_monitoring() - wifi_manager.start_scanning() - - def exit_clean(sig, frame): - logging.info('Shutting down') - - light_manager.cleanup() - - http_server.stop() - device_manager.stop_monitoring() - broadcast_server.kill() - wifi_manager.stop_scanning() - - sys.exit(0) - - broadcast_server.run_in_background() - signal.signal(signal.SIGINT, exit_clean) - - logging.info('Starting backend server on http://0.0.0.0:8080') - http_server.serve_forever() - - -if __name__ == '__main__': - main() +from .server import * diff --git a/backend_py/src/server.py b/backend_py/src/server.py new file mode 100644 index 00000000..65555ade --- /dev/null +++ b/backend_py/src/server.py @@ -0,0 +1,81 @@ +from ctypes import * +import sys +import signal + +from flask import Flask, jsonify, request +from flask_cors import CORS +from gevent.pywsgi import WSGIServer + +from .websockets.broadcast_server import BroadcastServer + +from .services import * +from .blueprints import cameras_bp, lights_bp, logs_bp, preferences_bp, wifi_bp +from .logging import LogHandler + +import logging + + +class Server: + + def __init__(self, port=8080) -> None: + # Create the flask application + self.app = Flask(__name__) + CORS(self.app) + + # avoid sorting the keys to keep the way we sort it in the backend + self.app.json.sort_keys = False + + # Create the managers + self.settings_manager = SettingsManager() + self.broadcast_server = BroadcastServer() + self.device_manager = DeviceManager( + settings_manager=self.settings_manager, broadcast_server=self.broadcast_server) + self.light_manager = LightManager(create_pwm_controllers()) + self.preferences_manager = PreferencesManager() + self.wifi_manager = WiFiManager() + + # Create the logging handler + self.log_handler = LogHandler(self.broadcast_server) + logging.getLogger().addHandler(self.log_handler) + + # Set the app configs + self.app.config['device_manager'] = self.device_manager + self.app.config['light_manager'] = self.light_manager + self.app.config['preferences_manager'] = self.preferences_manager + self.app.config['log_handler'] = self.log_handler + self.app.config['wifi_manager'] = self.wifi_manager + + # Register the blueprints + self.app.register_blueprint(cameras_bp) + self.app.register_blueprint(lights_bp) + self.app.register_blueprint(logs_bp) + self.app.register_blueprint(preferences_bp) + self.app.register_blueprint(wifi_bp) + + # create the server and run everything + self.http_server = WSGIServer(('0.0.0.0', port), self.app, log=None) + + def exit_clean(sig, frame): + self._exit_clean() + + signal.signal(signal.SIGINT, exit_clean) + + logging.info('Starting backend server on http://0.0.0.0:8080') + + def serve(self): + self.device_manager.start_monitoring() + self.wifi_manager.start_scanning() + self.broadcast_server.run_in_background() + self.http_server.serve_forever() + + def _exit_clean(self): + logging.info('Shutting down') + + light_manager.cleanup() + + self.http_server.stop() + device_manager.stop_monitoring() + self.broadcast_server.kill() + self.wifi_manager.stop_scanning() + + sys.exit(0) diff --git a/run_release.py b/run_release.py index 3d23e282..af3a02a0 100755 --- a/run_release.py +++ b/run_release.py @@ -3,7 +3,7 @@ import sys import signal import os -from backend_py.src import main +from backend_py.src import Server import multiprocessing import logging @@ -39,4 +39,5 @@ def exit_clean(sig, frame): signal.signal(signal.SIGINT, exit_clean) - main() + server = Server() + server.serve() From 7f1a3771370f2fd00c44f92793cc866a061f910b Mon Sep 17 00:00:00 2001 From: brandonhs Date: Tue, 1 Oct 2024 20:42:58 -0700 Subject: [PATCH 11/27] WiFi working from frontend --- backend_py/src/server.py | 23 +- frontend/src/components/NavigationBar.tsx | 6 +- frontend/src/layouts/cameras/index.tsx | 10 +- frontend/src/layouts/task_monitor/CPUCard.tsx | 145 ----------- .../src/layouts/task_monitor/DiskCard.tsx | 145 ----------- .../layouts/task_monitor/TemperatureCard.tsx | 173 ------------ .../src/layouts/task_monitor/api/index.tsx | 56 ---- frontend/src/layouts/task_monitor/index.tsx | 109 -------- .../src/layouts/task_monitor/types/index.tsx | 33 --- frontend/src/layouts/updater/api.tsx | 0 frontend/src/layouts/updater/index.tsx | 149 ----------- .../src/layouts/wifi/NetworkDetailsCard.tsx | 40 --- frontend/src/layouts/wifi/NetworkSettings.tsx | 246 ++++++------------ frontend/src/layouts/wifi/api.tsx | 32 +++ frontend/src/layouts/wifi/api/index.tsx | 113 -------- frontend/src/layouts/wifi/index.tsx | 34 +-- frontend/src/layouts/wifi/types.tsx | 15 ++ frontend/src/layouts/wifi/types/index.tsx | 30 --- frontend/src/routes.tsx | 22 +- frontend/src/utils/api.tsx | 97 ++----- frontend/src/utils/utils.tsx | 38 +++ 21 files changed, 226 insertions(+), 1290 deletions(-) delete mode 100644 frontend/src/layouts/task_monitor/CPUCard.tsx delete mode 100644 frontend/src/layouts/task_monitor/DiskCard.tsx delete mode 100644 frontend/src/layouts/task_monitor/TemperatureCard.tsx delete mode 100644 frontend/src/layouts/task_monitor/api/index.tsx delete mode 100644 frontend/src/layouts/task_monitor/index.tsx delete mode 100644 frontend/src/layouts/task_monitor/types/index.tsx delete mode 100644 frontend/src/layouts/updater/api.tsx delete mode 100644 frontend/src/layouts/updater/index.tsx delete mode 100644 frontend/src/layouts/wifi/NetworkDetailsCard.tsx create mode 100644 frontend/src/layouts/wifi/api.tsx delete mode 100644 frontend/src/layouts/wifi/api/index.tsx create mode 100644 frontend/src/layouts/wifi/types.tsx delete mode 100644 frontend/src/layouts/wifi/types/index.tsx diff --git a/backend_py/src/server.py b/backend_py/src/server.py index 65555ade..6e530271 100644 --- a/backend_py/src/server.py +++ b/backend_py/src/server.py @@ -56,7 +56,16 @@ def __init__(self, port=8080) -> None: self.http_server = WSGIServer(('0.0.0.0', port), self.app, log=None) def exit_clean(sig, frame): - self._exit_clean() + logging.info('Shutting down') + + self.light_manager.cleanup() + self.http_server.stop() + self.device_manager.stop_monitoring() + self.broadcast_server.kill() + self.wifi_manager.stop_scanning() + + sys.exit(0) + signal.signal(signal.SIGINT, exit_clean) @@ -67,15 +76,3 @@ def serve(self): self.wifi_manager.start_scanning() self.broadcast_server.run_in_background() self.http_server.serve_forever() - - def _exit_clean(self): - logging.info('Shutting down') - - light_manager.cleanup() - - self.http_server.stop() - device_manager.stop_monitoring() - self.broadcast_server.kill() - self.wifi_manager.stop_scanning() - - sys.exit(0) diff --git a/frontend/src/components/NavigationBar.tsx b/frontend/src/components/NavigationBar.tsx index 26e9578f..c1e83582 100644 --- a/frontend/src/components/NavigationBar.tsx +++ b/frontend/src/components/NavigationBar.tsx @@ -28,7 +28,7 @@ import { BrowserRouter as Router } from "react-router-dom"; import { routes } from "../routes"; import DWELogo_white from "../svg/DWELogo_white.svg"; -import { restartMachine, shutDownMachine } from "../utils/api"; +// import { restartMachine, shutDownMachine } from "../utils/api"; import NavigationItems from "../utils/getNavigationItems"; import NavigationRoutes from "../utils/getRoutes"; import dweTheme from "../utils/themes"; @@ -203,7 +203,7 @@ const NavigationBar = () => { { handleClose(); - shutDownMachine(); + // TODO: Shut down machine }} sx={{ color: "white", @@ -214,7 +214,7 @@ const NavigationBar = () => { { handleClose(); - restartMachine(); + // TODO: Restart machine }} sx={{ color: "white", diff --git a/frontend/src/layouts/cameras/index.tsx b/frontend/src/layouts/cameras/index.tsx index 3bdc0358..c14b3df1 100644 --- a/frontend/src/layouts/cameras/index.tsx +++ b/frontend/src/layouts/cameras/index.tsx @@ -3,8 +3,12 @@ import React, { useContext, useEffect, useState } from "react"; import DeviceCard from "../../components/DeviceCard"; import { Device, SavedPreferences } from "../../types/types"; -import { getDevices, DEVICE_API_WS, getSettings } from "../../utils/api"; -import { deserializeMessage, findDeviceWithBusInfo } from "../../utils/utils"; +import { getDevices, getSettings } from "../../utils/api"; +import { + BACKEND_API_WS, + deserializeMessage, + findDeviceWithBusInfo, +} from "../../utils/utils"; import DevicesContext from "../../contexts/DevicesContext"; import DeviceContext from "../../contexts/DeviceContext"; @@ -33,7 +37,7 @@ interface GstErrorMessage { bus_info: string; } -export const websocket = new WebSocket(DEVICE_API_WS); +export const websocket = new WebSocket(BACKEND_API_WS); const DevicesLayout = () => { const { enqueueSnackbar } = useSnackbar(); diff --git a/frontend/src/layouts/task_monitor/CPUCard.tsx b/frontend/src/layouts/task_monitor/CPUCard.tsx deleted file mode 100644 index b185ee85..00000000 --- a/frontend/src/layouts/task_monitor/CPUCard.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import MemoryIcon from "@mui/icons-material/Memory"; -import { - Avatar, - Card, - Divider, - Grid, - IconButton, - List, - ListItem, - ListItemAvatar, - ListItemText, - Typography, -} from "@mui/material"; -import React from "react"; - -interface CPUCardProps { - totalUsagePercent: number; - deviceName: string; - deviceStats: number[]; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function chunkArray(arr: T, size: number): T[] { - return arr.reduce( - (acc, _, i) => (i % size ? acc : [...acc, arr.slice(i, i + size)]), - [] - ); -} - -const CPUCard: React.FC = (props) => { - const rowLimit = 4; - return ( - - - console.log("delete", `sasa`)} - > - } - > - - - - - - - - - - {props.deviceName} - - - - - {props.deviceStats && - chunkArray(props.deviceStats, rowLimit).map( - (row, rowIndex) => ( - - {row.map((stat, index) => ( - - {/* Render your item component here */} - - - {`Core ${ - rowLimit * - rowIndex + - index - }`} - - - {stat} - - - - ))} - - ) - )} - - - - - - ); -}; - -export default CPUCard; diff --git a/frontend/src/layouts/task_monitor/DiskCard.tsx b/frontend/src/layouts/task_monitor/DiskCard.tsx deleted file mode 100644 index 7a74fea2..00000000 --- a/frontend/src/layouts/task_monitor/DiskCard.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import MemoryIcon from "@mui/icons-material/Memory"; -import { - Avatar, - Card, - Divider, - Grid, - IconButton, - List, - ListItem, - ListItemAvatar, - ListItemText, - Typography, -} from "@mui/material"; -import React from "react"; - -interface DiskCardProps { - currentDiskUsagePercent: number; - deviceName: string; - deviceStats: number[]; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function chunkArray(arr: T, size: number): T[] { - return arr.reduce( - (acc, _, i) => (i % size ? acc : [...acc, arr.slice(i, i + size)]), - [] - ); -} - -const DiskCard: React.FC = (props) => { - const rowLimit = 4; - return ( - - - console.log("delete", `sasa`)} - > - } - > - - - - - - - - - - {props.deviceName} - - - - - {props.deviceStats && - chunkArray(props.deviceStats, rowLimit).map( - (row, rowIndex) => ( - - {row.map((stat, index) => ( - - {/* Render your item component here */} - - - {`Core ${ - rowLimit * - rowIndex + - index - }`} - - - {stat} - - - - ))} - - ) - )} - - - - - - ); -}; - -export default DiskCard; diff --git a/frontend/src/layouts/task_monitor/TemperatureCard.tsx b/frontend/src/layouts/task_monitor/TemperatureCard.tsx deleted file mode 100644 index 0d734f9e..00000000 --- a/frontend/src/layouts/task_monitor/TemperatureCard.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import DeviceThermostatIcon from "@mui/icons-material/DeviceThermostat"; -import { - Avatar, - Card, - Divider, - Grid, - IconButton, - List, - ListItem, - ListItemAvatar, - ListItemText, - Typography, -} from "@mui/material"; -import React, { useState } from "react"; - -interface TemperatureCardProps { - cpuTemp: number; - minTemp: number; - maxTemp: number; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function chunkArray(arr: T, size: number): T[] { - return arr.reduce( - (acc, _, i) => (i % size ? acc : [...acc, arr.slice(i, i + size)]), - [] - ); -} - -const TemperatureCard: React.FC = (props) => { - console.log("maxTemp in TemperatureCard:", props.maxTemp); - return ( - - - console.log("delete", `sasa`)} - > - } - > - - - - - - - - - - Temperature - - - - - - - - - CPU - - - {props.cpuTemp}°C - - - - - - - Minimum - - - {props.minTemp}°C - - - - - - - Maximum - - - {props.maxTemp}°C - - - - - - - Critical - - - {props.cpuTemp}°C - - - - - - - - ); -}; - -export default TemperatureCard; diff --git a/frontend/src/layouts/task_monitor/api/index.tsx b/frontend/src/layouts/task_monitor/api/index.tsx deleted file mode 100644 index 27f451fb..00000000 --- a/frontend/src/layouts/task_monitor/api/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { SYSTEM_API_URL } from "../../../utils/api"; -import { CPUInfo, TemperatureInfo } from "../types"; - -/** - * Retrieves information about the CPU and its usage from the system. - * @returns {Promise} - A promise that resolves to an array of Device objects. - * @throws {Error} - If the request to retrieve the device list fails. - */ -export async function getCPUInfo(): Promise { - const url = `${SYSTEM_API_URL}/getCPU`; - const config: RequestInit = { - mode: "cors", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }; - return await fetch(url, config) - // Process the response data - .then((response: Response) => response.json()) - .then((data: CPUInfo) => { - return data; - }) - .catch((error: Error) => { - console.log("Failed to retrieve device processor information"); - console.error(error); - return {} as CPUInfo; - }); -} - -/** - * Retrieves information about the temperature from the system. - * @returns {Promise} - A promise that resolves to a TemperatureInfo object. - * @throws {Error} - If the request to retrieve the device list fails. - */ -export async function getTemperatureInfo(): Promise { - const url = `${SYSTEM_API_URL}/getTemperature`; - const config: RequestInit = { - mode: "cors", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }; - return await fetch(url, config) - // Process the response data - .then((response: Response) => response.json()) - .then((data: TemperatureInfo) => { - return data; - }) - .catch((error: Error) => { - console.log("Failed to retrieve device temperature"); - console.error(error); - return {} as TemperatureInfo; - }); -} diff --git a/frontend/src/layouts/task_monitor/index.tsx b/frontend/src/layouts/task_monitor/index.tsx deleted file mode 100644 index b5dad05d..00000000 --- a/frontend/src/layouts/task_monitor/index.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Grid } from "@mui/material"; -import React, { useEffect, useState } from "react"; - -import { getCPUInfo, getTemperatureInfo } from "./api"; -import CPUCard from "./CPUCard"; -import DiskCard from "./DiskCard"; -import { DiskInfo, MemoryInfo } from "./types"; -// import MemoryCard from "./MemoryCard"; -import TemperatureCard from "./TemperatureCard"; -import { CPUInfo, TemperatureInfo } from "./types"; - -const TaskMonitor: React.FC = () => { - const [cpuInfo, setCPUInfo] = useState(null); - const [diskInfo, setDiskInfo] = useState(null); - const [memoryInfo, setMemoryInfo] = useState(null); - const [temperatureInfo, setTemperatureInfo] = - useState(null); - const [minTemp, setMinTemp] = useState(null); - const [maxTemp, setMaxTemp] = useState(null); - - useEffect(() => { - // Track the maximum temperature using a local variable - let localMaxTemp = maxTemp; - let localMinTemp = minTemp; - // Fetch CPU info immediately when the component mounts - const fetchMachineInfo = async () => { - const cpuInfo = await getCPUInfo(); - setCPUInfo(cpuInfo); - // const diskInfo = await get - // setDiskInfo(diskInfo); - // const memoryInfo = await getCPUInfo(); - // setMemoryInfo(memoryInfo); - const temperatureInfo = await getTemperatureInfo(); - setTemperatureInfo(temperatureInfo); - // Update the localMaxTemp if the fetched temperature is greater than the current localMaxTemp - if ( - localMinTemp === null || - temperatureInfo.processor_temp < localMinTemp - ) { - localMinTemp = temperatureInfo.processor_temp; - console.log(localMinTemp); - } - if ( - localMaxTemp === null || - temperatureInfo.processor_temp > localMaxTemp - ) { - localMaxTemp = temperatureInfo.processor_temp; - console.log(localMaxTemp); - } - - // Update the state with the new temperatureInfo and the final localMaxTemp - setTemperatureInfo(temperatureInfo); - setMinTemp(localMinTemp); - setMaxTemp(localMaxTemp); - }; - fetchMachineInfo(); - - // Fetch CPU info every 30 seconds using setInterval - const interval = setInterval(async () => { - fetchMachineInfo(); - }, 1000); - - // Clean up the interval on component unmount to avoid memory leaks - return () => { - clearInterval(interval); - }; - }, []); - - return ( - - {cpuInfo !== null && - temperatureInfo !== null && - maxTemp !== null && ( - <> - - {/* */} - {/* - - */} - - - )} - - ); -}; - -export default TaskMonitor; diff --git a/frontend/src/layouts/task_monitor/types/index.tsx b/frontend/src/layouts/task_monitor/types/index.tsx deleted file mode 100644 index c854db2f..00000000 --- a/frontend/src/layouts/task_monitor/types/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Represents the types of information about the CPU - */ -export interface CPUInfo { - processor_name: string; - physical_cores: number; - total_cores: number; - core_usage: number[]; - total_usage: number; -} - -/** - * Represents the types of information about the disk - */ - -export interface DiskInfo { - disk_usage: number; -} - -/** - * Represents the types of information about the memory - */ -export interface MemoryInfo { - memory_usage: number; -} - -/** - * Represents the information available about the temperature - */ - -export interface TemperatureInfo { - processor_temp: number; -} diff --git a/frontend/src/layouts/updater/api.tsx b/frontend/src/layouts/updater/api.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/layouts/updater/index.tsx b/frontend/src/layouts/updater/index.tsx deleted file mode 100644 index a95dfa2b..00000000 --- a/frontend/src/layouts/updater/index.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import BrowserUpdatedSharpIcon from "@mui/icons-material/BrowserUpdatedSharp"; -import { - Avatar, - Button, - Card, - CardContent, - CardHeader, - Grid, - List, - ListItem, - ListItemAvatar, - ListItemText, - Paper, - Typography, -} from "@mui/material"; -import React, { useEffect, useState } from "react"; -import { Release } from "../../types/types"; -import { getReleases, installUpdate } from "../../utils/api"; - -export interface VersionItemProps { - isInstallable: boolean; - isMostRecent: boolean; - dateReleased: Date; - version: string; -} - -const VersionItem: React.FC = (props) => { - const currentDate = new Date(); - const daysSince = Math.floor( - (currentDate.getTime() - props.dateReleased.getTime()) / - (1000 * 3600 * 24) - ); - - return ( - { - installUpdate(props.version); - }} - > - Install - - ) : undefined - } - > - - - - - - - - ); -}; - -const Updater: React.FC = () => { - const [releases, setReleases] = useState([]); - const [currentRelease, setCurrentRelease] = useState(); - - useEffect(() => { - getReleases().then((r) => { - setReleases(r.releases.filter((release) => !release.current)); - setCurrentRelease(r.releases.find((release) => release.current)); - }); - }, []); - - return ( - - - - - - Current Version - - {currentRelease ? ( - - ) : undefined} - - Available Versions - - - - {releases?.map((release: Release) => { - return ( - - ); - })} - - - - - - ); -}; - -export default Updater; diff --git a/frontend/src/layouts/wifi/NetworkDetailsCard.tsx b/frontend/src/layouts/wifi/NetworkDetailsCard.tsx deleted file mode 100644 index 6c557953..00000000 --- a/frontend/src/layouts/wifi/NetworkDetailsCard.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Card, CardHeader, Typography } from "@mui/material"; -import { WifiStatus } from "./types"; -import React from "react"; - -export interface NetworkDetailsCardProps { - ip_address: string; -} - -const NetworkDetailsCard: React.FC = (props) => { - return ( - - - - - IP Address - {props.ip_address} - - - ); -}; - -export default NetworkDetailsCard; diff --git a/frontend/src/layouts/wifi/NetworkSettings.tsx b/frontend/src/layouts/wifi/NetworkSettings.tsx index 29522992..6353aaa1 100644 --- a/frontend/src/layouts/wifi/NetworkSettings.tsx +++ b/frontend/src/layouts/wifi/NetworkSettings.tsx @@ -26,13 +26,13 @@ import { SignalWifi3Bar, SignalWifi4Bar, } from "@mui/icons-material"; -import { WifiStatus, ScannedWifiNetwork } from "./types"; +import { AccessPoint, Connection } from "./types"; import { useEffect, useState } from "react"; import { - connectToWifi, - disconnectFromWifi, - getAvailableWifi, - getWifiStatus, + connectToNetwork, + disconnectFromNetwork, + getAccessPoints, + getWiFiStatus, } from "./api"; import { useSnackbar } from "notistack"; import React from "react"; @@ -41,21 +41,7 @@ export interface SignalIconProps { signal_strength: number; } -const thresholds = [-60, -70, -80, -90]; - -const getSignalStrength = (strength: number) => { - if (strength >= thresholds[0]) { - return 4; - } else if (strength >= thresholds[1]) { - return 3; - } else if (strength >= thresholds[2]) { - return 2; - } else if (strength >= thresholds[3]) { - return 1; - } else { - return 0; - } -}; +const thresholds = [100, 70, 50, 20]; const SignalIcon: React.FC = (props) => { // Define the signal strength thresholds for each bar @@ -113,29 +99,15 @@ const WifiListItem: React.FC = (props) => { style={{ width: "200px" }} /> {props.connected ? ( - <> - {/* */} - - + ) : ( - ) : ( - - )} - - ); -}; +import { styles } from "../../style"; +import { hash } from "../../utils/utils"; +import WifiListItem, { WifiListItemType } from "./WifiListItem"; export interface WifiConnectDialogProps { ssid: string; @@ -208,42 +112,68 @@ const WifiConnectDialog: React.FC = (props) => { ); }; -export interface NetworkSettingsCardProps { - currentNetwork: Connection; - setCurrentNetwork: React.Dispatch>; - accessPoints: AccessPoint[]; -} +export interface NetworkSettingsCardProps {} + +const NetworkSettingsCard: React.FC = ({}) => { + const [currentNetwork, setCurrentNetwork] = useState( + undefined as Connection | undefined + ); + const [accessPoints, setAccessPoints] = useState([] as AccessPoint[]); + const { enqueueSnackbar } = useSnackbar(); + + const refreshNetworks = async () => { + let newNetwork = await getWiFiStatus(); + let newAccessPoints = await getAccessPoints(); + + if (newNetwork && Object.keys(newNetwork).length === 0) + newNetwork = undefined; // no new network + + setCurrentNetwork(newNetwork as Connection | undefined); + setAccessPoints(newAccessPoints); + + return { + newNetwork: newNetwork as Connection | undefined, + newAccessPoints, + }; + }; + + // Initial request + useEffect(() => { + refreshNetworks(); + + const interval = setInterval(() => refreshNetworks(), 500); + return () => { + clearInterval(interval); + console.log("clearing interval"); + }; + }, []); -const NetworkSettingsCard: React.FC = ({ - currentNetwork, - accessPoints, - setCurrentNetwork, -}) => { const [connectDialog, setConnectDialog] = useState(false); const [connectNetwork, setConnectNetwork] = useState( undefined as AccessPoint | undefined ); - const onConnectToNewNetwork = (ssid: string, password?: string) => { - connectToNetwork(ssid, password).then(() => { - // TODO: check if the connection was successful - setCurrentNetwork({ id: ssid, type: "" }); - }); + const onConnectToNewNetwork = async (ssid: string, password?: string) => { + await connectToNetwork(ssid, password); + setTimeout(async () => { + const { newNetwork } = await refreshNetworks(); + if (newNetwork && newNetwork.id === ssid) + enqueueSnackbar("Connection Successful!", { + variant: "success", + }); + else enqueueSnackbar("Connection failed", { variant: "error" }); + }, 250); // FIXME: the api requires a slight delay after connecting. This should be fixed on the backend side with a timeout }; - const onDisconnectFromNetwork = () => { - setCurrentNetwork(undefined); - disconnectFromNetwork(); + const onDisconnectFromNetwork = async () => { + await disconnectFromNetwork(); }; return ( {connectNetwork && ( @@ -259,70 +189,76 @@ const NetworkSettingsCard: React.FC = ({ }} /> )} - - - - - - - - - {currentNetwork && ( - { - onDisconnectFromNetwork(); - }} - /> - )} - {accessPoints - .sort((a, b) => b.strength - a.strength) - .filter( - (network, index) => - accessPoints.findIndex( - (findNetwork) => - network.ssid === findNetwork.ssid - ) === index - ) // filter out duplicates - .filter((network) => - currentNetwork - ? network.ssid !== currentNetwork.id - : true - ) // filter out current network - .map((scanned_network) => { - if (!scanned_network.ssid) return; - else { - return ( - { - setConnectNetwork(scanned_network); - setConnectDialog(true); - }} - /> - ); - } - })} - - + + + + + {currentNetwork && ( + { + onDisconnectFromNetwork(); + }} + /> + )} + {accessPoints + .sort( + (a, b) => + b.strength - a.strength || + hash(a.ssid) - hash(b.ssid) + ) + .filter( + (network, index) => + accessPoints.findIndex( + (findNetwork) => + network.ssid === findNetwork.ssid + ) === index + ) // filter out duplicates + .filter((network) => + currentNetwork + ? network.ssid !== currentNetwork.id + : true + ) // filter out current network + .map((scanned_network) => { + if (!scanned_network.ssid) return; + else { + return ( + { + setConnectNetwork( + scanned_network + ); + setConnectDialog(true); + }} + /> + ); + } + })} + + + ); }; diff --git a/frontend/src/layouts/wifi/WifiListItem.tsx b/frontend/src/layouts/wifi/WifiListItem.tsx new file mode 100644 index 00000000..5d354b3c --- /dev/null +++ b/frontend/src/layouts/wifi/WifiListItem.tsx @@ -0,0 +1,136 @@ +import { + Avatar, + Button, + ListItem, + ListItemAvatar, + ListItemText, +} from "@mui/material"; + +import { + SignalWifi0Bar, + SignalWifi1Bar, + SignalWifi2Bar, + SignalWifi3Bar, + SignalWifi4Bar, +} from "@mui/icons-material"; +import React from "react"; + +export interface SignalIconProps { + signal_strength: number; +} + +export enum WifiListItemType { + CONNECTED = 0, + DISCONNECTED, + KNOWN, +} + +const thresholds = [100, 70, 50, 20]; + +const SignalIcon: React.FC = (props) => { + // Define the signal strength thresholds for each bar + + // Determine the number of bars based on the signal strength + if (props.signal_strength >= thresholds[0]) { + return ; + } else if (props.signal_strength >= thresholds[1]) { + return ; + } else if (props.signal_strength >= thresholds[2]) { + return ; + } else if (props.signal_strength >= thresholds[3]) { + return ; + } else { + return ; + } +}; + +export interface WifiSignalAvatarProps { + signal_strength: number; +} + +const WifiSignalAvatar: React.FC = (props) => { + return ( + + + + + + ); +}; + +export interface WifiListItemProps { + ssid: string; + signal_strength: number; + on_connect?: () => void; + on_disconnect?: () => void; + secure?: boolean; + type: WifiListItemType; +} + +const WifiListItem: React.FC = (props) => { + return ( + + + + {(() => { + switch (props.type) { + case WifiListItemType.CONNECTED: + return ( + + ); + case WifiListItemType.DISCONNECTED: + return ( + + ); + case WifiListItemType.KNOWN: + return ( + + ); + } + return <>; + })()} + + ); +}; + +export default WifiListItem; diff --git a/frontend/src/layouts/wifi/api.tsx b/frontend/src/layouts/wifi/api.tsx index 8379481d..8974a6bd 100644 --- a/frontend/src/layouts/wifi/api.tsx +++ b/frontend/src/layouts/wifi/api.tsx @@ -4,7 +4,7 @@ import { AccessPoint, Connection } from "./types"; export async function getWiFiStatus() { const url = `${BACKEND_API_URL}/wifi/status`; const response = await getRequest(url); - return (await response.json()) as Connection; + return (await response.json()) as Connection | {}; } export async function getAccessPoints() { diff --git a/frontend/src/layouts/wifi/index.tsx b/frontend/src/layouts/wifi/index.tsx index b1589991..ceabd58f 100644 --- a/frontend/src/layouts/wifi/index.tsx +++ b/frontend/src/layouts/wifi/index.tsx @@ -1,22 +1,9 @@ import Grid from "@mui/material/Grid"; -import React, { useEffect, useState } from "react"; - -import { getAccessPoints, getWiFiStatus } from "./api"; +import React from "react"; import NetworkSettingsCard from "./NetworkSettings"; -import { Connection, AccessPoint } from "./types"; +import KnownNetworksCard from "./KnownNetworksCard"; const Wifi: React.FC = () => { - const [currentNetwork, setCurrentNetwork] = useState( - {} as Connection | undefined - ); - const [accessPoints, setAccessPoints] = useState([] as AccessPoint[]); - - // Initial request - useEffect(() => { - getWiFiStatus().then(setCurrentNetwork); - getAccessPoints().then(setAccessPoints); - }, []); - return ( { padding: "0 3em", }} > - + + ); }; diff --git a/frontend/src/utils/utils.tsx b/frontend/src/utils/utils.tsx index 2833a35d..e3fb6bda 100644 --- a/frontend/src/utils/utils.tsx +++ b/frontend/src/utils/utils.tsx @@ -1,6 +1,7 @@ import { useRef, useEffect } from "react"; -import { Device, Message } from "../types/types"; +import { Message } from "../types/types"; +import { Device } from "../layouts/cameras/types"; export const deserializeMessage = (message_str: string) => { let parts = message_str.split(": "); @@ -64,6 +65,19 @@ export async function postRequest( return await fetch(url, config); } +export const hash = function (str: string) { + let hash = 0, + i, + chr; + if (str.length === 0) return hash; + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; + } + return hash; +}; + export const hostAddress: string = window.location.hostname; export const BACKEND_API_URL = `http://${hostAddress}:8080`; export const BACKEND_API_WS = `ws://${hostAddress}:9002`; From de083a4ef9829af2addd4cf3b77ddc8c8ccc1295 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Wed, 2 Oct 2024 16:06:12 -0700 Subject: [PATCH 19/27] Make styles correct in dark mode --- frontend/src/layouts/wifi/NetworkSettings.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/src/layouts/wifi/NetworkSettings.tsx b/frontend/src/layouts/wifi/NetworkSettings.tsx index 9e47d2be..6ce571ed 100644 --- a/frontend/src/layouts/wifi/NetworkSettings.tsx +++ b/frontend/src/layouts/wifi/NetworkSettings.tsx @@ -195,11 +195,7 @@ const NetworkSettingsCard: React.FC = ({}) => { /> - + Date: Wed, 2 Oct 2024 16:40:54 -0700 Subject: [PATCH 20/27] Add ability to forget networks --- backend_py/src/blueprints/wifi.py | 13 ++++++- .../src/services/wifi/network_manager.py | 35 +++++++++++++----- backend_py/src/services/wifi/wifi_manager.py | 3 ++ .../src/layouts/wifi/KnownNetworksCard.tsx | 36 +++++++++++++------ frontend/src/layouts/wifi/WifiListItem.tsx | 20 ++++++----- frontend/src/layouts/wifi/api.tsx | 6 ++++ 6 files changed, 85 insertions(+), 28 deletions(-) diff --git a/backend_py/src/blueprints/wifi.py b/backend_py/src/blueprints/wifi.py index 533a8a05..2df9d4b2 100644 --- a/backend_py/src/blueprints/wifi.py +++ b/backend_py/src/blueprints/wifi.py @@ -38,10 +38,21 @@ def connect(): network_config: NetworkConfig = NetworkConfigSchema().load(request.get_json()) return jsonify({'status': wifi_manager.connect(network_config.ssid, network_config.password)}) except ValidationError as e: - return jsonify({'Error': e.messages}) + return jsonify({'Error': e}) @wifi_bp.route('/wifi/disconnect', methods=['POST']) def disconnect(): wifi_manager: WiFiManager = current_app.config['wifi_manager'] wifi_manager.disconnect() return jsonify({}) + +@wifi_bp.route('/wifi/forget', methods=['POST']) +def forget(): + try: + wifi_manager: WiFiManager = current_app.config['wifi_manager'] + network_config: NetworkConfig = NetworkConfigSchema(only=['ssid']).load(request.get_json()) + wifi_manager.forget(network_config.ssid) + except: + # TODO: Add real error handler :) + return jsonify({}) + return jsonify({}) diff --git a/backend_py/src/services/wifi/network_manager.py b/backend_py/src/services/wifi/network_manager.py index 72573817..1fc9f362 100644 --- a/backend_py/src/services/wifi/network_manager.py +++ b/backend_py/src/services/wifi/network_manager.py @@ -2,6 +2,7 @@ from typing import List import time from .wifi_types import Connection, AccessPoint +import logging class NMNotSupportedError(Exception): '''Exception raised when NetworkManager is not supported''' @@ -119,14 +120,7 @@ def list_connections(self) -> List[Connection]: Get a list of all the connections saved ''' connections = [] - settings_proxy = self.bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Settings') - settings = dbus.Interface(settings_proxy, 'org.freedesktop.NetworkManager.Settings') - - # List all the connections saved - # This will have repeats for some reason, so this needs to be filtered - for connection_path in settings.ListConnections(): - proxy = self.bus.get_object('org.freedesktop.NetworkManager', connection_path) - connection = dbus.Interface(proxy, 'org.freedesktop.NetworkManager.Settings.Connection') + for connection in self._list_connections(): config = connection.GetSettings() new_connection = Connection(config['connection']['id'], config['connection']['type']) # Filter @@ -189,6 +183,16 @@ def scan_wifi(self, timeout=30) -> List[AccessPoint]: raise TimeoutError('Request timed out') + def forget(self, ssid: str): + ''' + Forget a network + ''' + for connection in self._list_connections(): + config = connection.GetSettings() + if config['connection']['id'] == ssid: + connection.Delete() + + def _get_wifi_device(self): devices = self.interface.GetDevices() for dev_path in devices: @@ -242,3 +246,18 @@ def _get_connection_proxy(self, active_connection): # Return the connection proxy object based on the connection path return self.bus.get_object('org.freedesktop.NetworkManager', connection_path) + + def _list_connections(self) -> dbus.Interface: + connections = [] + + settings_proxy = self.bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Settings') + settings = dbus.Interface(settings_proxy, 'org.freedesktop.NetworkManager.Settings') + + # List all the connections saved + # This will have repeats for some reason, so this needs to be filtered + for connection_path in settings.ListConnections(): + proxy = self.bus.get_object('org.freedesktop.NetworkManager', connection_path) + connection = dbus.Interface(proxy, 'org.freedesktop.NetworkManager.Settings.Connection') + connections.append(connection) + + return connections diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index d553a412..c09f288d 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -49,6 +49,9 @@ def get_active_connection(self): def list_connections(self): return ConnectionSchema().dump(self.nm.list_wireless_connections(), many=True) + def forget(self, ssid: str): + self.nm.forget(ssid) + def _scan(self): start_time = time.time() while self._is_scanning: diff --git a/frontend/src/layouts/wifi/KnownNetworksCard.tsx b/frontend/src/layouts/wifi/KnownNetworksCard.tsx index e3183b4d..e2e5b0da 100644 --- a/frontend/src/layouts/wifi/KnownNetworksCard.tsx +++ b/frontend/src/layouts/wifi/KnownNetworksCard.tsx @@ -5,18 +5,27 @@ import { CardHeader, Divider, List, + Typography, } from "@mui/material"; import React, { useEffect, useState } from "react"; import { styles } from "../../style"; import { Connection } from "./types"; -import { getConnections } from "./api"; +import { forgetNetwork, getConnections } from "./api"; import WifiListItem, { WifiListItemType } from "./WifiListItem"; const KnownNetworksCard = ({}) => { const [knownNetworks, setKnownNetworks] = useState([] as Connection[]); - useEffect(() => { + const refreshNetworks = () => { getConnections().then(setKnownNetworks); + }; + + useEffect(() => { + const interval = setInterval(() => refreshNetworks(), 500); + return () => { + clearInterval(interval); + console.log("clearing interval"); + }; }, []); return ( @@ -32,14 +41,21 @@ const KnownNetworksCard = ({}) => { dense={true} style={{ maxHeight: 300, overflow: "auto" }} > - {knownNetworks.map((connection, index) => ( - - ))} + {knownNetworks.length > 0 ? ( + knownNetworks.map((connection, index) => ( + + forgetNetwork(connection.id) + } + /> + )) + ) : ( + No known networks. + )} diff --git a/frontend/src/layouts/wifi/WifiListItem.tsx b/frontend/src/layouts/wifi/WifiListItem.tsx index 5d354b3c..796cf825 100644 --- a/frontend/src/layouts/wifi/WifiListItem.tsx +++ b/frontend/src/layouts/wifi/WifiListItem.tsx @@ -63,23 +63,25 @@ export interface WifiListItemProps { signal_strength: number; on_connect?: () => void; on_disconnect?: () => void; + on_forget?: () => void; secure?: boolean; type: WifiListItemType; } const WifiListItem: React.FC = (props) => { + let header = WifiListItemType.CONNECTED + ? "Connected" + : props.secure + ? "Secured" + : "Unsecured"; + if (props.type === WifiListItemType.KNOWN) header = "Saved"; + return ( {(() => { @@ -118,8 +120,8 @@ const WifiListItem: React.FC = (props) => { variant='contained' style={{ color: "white", fontWeight: "bold" }} onClick={() => { - props.on_connect - ? props.on_connect() + props.on_forget + ? props.on_forget() : undefined; }} > diff --git a/frontend/src/layouts/wifi/api.tsx b/frontend/src/layouts/wifi/api.tsx index 8974a6bd..7d8b8581 100644 --- a/frontend/src/layouts/wifi/api.tsx +++ b/frontend/src/layouts/wifi/api.tsx @@ -30,3 +30,9 @@ export async function disconnectFromNetwork() { const response = await postRequest(url); return await response.json(); } + +export async function forgetNetwork(ssid: string) { + const url = `${BACKEND_API_URL}/wifi/forget`; + const response = await postRequest(url, { ssid }); + return await response.json(); +} From 1711e9e43475339a9e92c8f8b605e7ccc2578146 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Wed, 2 Oct 2024 16:54:51 -0700 Subject: [PATCH 21/27] Add TODO --- backend_py/src/services/wifi/wifi_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index c09f288d..2b5fa4e3 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -61,6 +61,7 @@ def _scan(self): # this is used so the server does not hang on close if elapsed_time >= self.scan_interval: try: + # TODO: move this to a new thread self.access_points = self.nm.scan_wifi() except TimeoutError as e: logging.warning(e) From 911bc92bfb91017af1eae330e1a6643257bfb03a Mon Sep 17 00:00:00 2001 From: brandonhs Date: Thu, 3 Oct 2024 09:28:22 -0700 Subject: [PATCH 22/27] Create the log handler before the other managers to ensure the logs get captured --- backend_py/src/server.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend_py/src/server.py b/backend_py/src/server.py index 6e530271..5d4d7860 100644 --- a/backend_py/src/server.py +++ b/backend_py/src/server.py @@ -26,18 +26,17 @@ def __init__(self, port=8080) -> None: self.app.json.sort_keys = False # Create the managers - self.settings_manager = SettingsManager() self.broadcast_server = BroadcastServer() + # Create the logging handler + self.log_handler = LogHandler(self.broadcast_server) + logging.getLogger().addHandler(self.log_handler) + self.settings_manager = SettingsManager() self.device_manager = DeviceManager( settings_manager=self.settings_manager, broadcast_server=self.broadcast_server) self.light_manager = LightManager(create_pwm_controllers()) self.preferences_manager = PreferencesManager() self.wifi_manager = WiFiManager() - # Create the logging handler - self.log_handler = LogHandler(self.broadcast_server) - logging.getLogger().addHandler(self.log_handler) - # Set the app configs self.app.config['device_manager'] = self.device_manager self.app.config['light_manager'] = self.light_manager From 62700db9f4acbc5ea8643106e3c3c3ffac28082d Mon Sep 17 00:00:00 2001 From: brandonhs Date: Thu, 3 Oct 2024 09:28:43 -0700 Subject: [PATCH 23/27] Add check to ensure the scan stops when the program quits --- backend_py/src/services/wifi/network_manager.py | 6 +++--- backend_py/src/services/wifi/wifi_manager.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend_py/src/services/wifi/network_manager.py b/backend_py/src/services/wifi/network_manager.py index 1fc9f362..2828ed0f 100644 --- a/backend_py/src/services/wifi/network_manager.py +++ b/backend_py/src/services/wifi/network_manager.py @@ -1,5 +1,5 @@ import dbus -from typing import List +from typing import List, Callable import time from .wifi_types import Connection, AccessPoint import logging @@ -155,7 +155,7 @@ def get_access_points(self) -> List[AccessPoint]: return self._get_access_points(wifi_dev) - def scan_wifi(self, timeout=30) -> List[AccessPoint]: + def scan_wifi(self, is_scanning_func: Callable[[], bool], timeout=30) -> List[AccessPoint]: ''' Scan wifi networks ''' @@ -171,7 +171,7 @@ def scan_wifi(self, timeout=30) -> List[AccessPoint]: # wait for scan to finish start_time = time.time() - while time.time() - start_time < timeout: + while is_scanning_func() and time.time() - start_time < timeout: current_scan = wifi_props.Get('org.freedesktop.NetworkManager.Device.Wireless', 'LastScan') if current_scan != last_scan: diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index 2b5fa4e3..34a57668 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -61,8 +61,8 @@ def _scan(self): # this is used so the server does not hang on close if elapsed_time >= self.scan_interval: try: - # TODO: move this to a new thread - self.access_points = self.nm.scan_wifi() + # Make sure the scan stops when we are no longer scanning + self.access_points = self.nm.scan_wifi(lambda: self._is_scanning) except TimeoutError as e: logging.warning(e) From 142ddfd626a185cb86ca16e4780375d66319ff26 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Thu, 3 Oct 2024 10:08:45 -0700 Subject: [PATCH 24/27] Fix issue where connected network would not appear as connected --- frontend/src/layouts/wifi/WifiListItem.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/layouts/wifi/WifiListItem.tsx b/frontend/src/layouts/wifi/WifiListItem.tsx index 796cf825..f48e4840 100644 --- a/frontend/src/layouts/wifi/WifiListItem.tsx +++ b/frontend/src/layouts/wifi/WifiListItem.tsx @@ -69,11 +69,12 @@ export interface WifiListItemProps { } const WifiListItem: React.FC = (props) => { - let header = WifiListItemType.CONNECTED - ? "Connected" - : props.secure - ? "Secured" - : "Unsecured"; + let header = + props.type === WifiListItemType.CONNECTED + ? "Connected" + : props.secure + ? "Secured" + : "Unsecured"; if (props.type === WifiListItemType.KNOWN) header = "Saved"; return ( From c29b30900c42b2a31bdfaf059bf3d96da4a11ce2 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Thu, 3 Oct 2024 10:34:50 -0700 Subject: [PATCH 25/27] Improve stability of WiFi --- backend_py/src/services/wifi/wifi_manager.py | 74 ++++++++++++++----- .../src/layouts/wifi/KnownNetworksCard.tsx | 1 + frontend/src/layouts/wifi/NetworkSettings.tsx | 2 +- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index 34a57668..0f292073 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -1,5 +1,4 @@ -from typing import List -from .wifi_types import AccessPoint +from .wifi_types import NetworkConfig from .schemas import AccessPointSchema, ConnectionSchema import threading import time @@ -11,46 +10,66 @@ class WiFiManager: def __init__(self, scan_interval=10) -> None: self.nm = NetworkManager() - self._thread = threading.Thread(target=self._scan) + self._update_thread = threading.Thread(target=self._update) + self._scan_thread = threading.Thread(target=self._scan) # Secondary thread is needed to conduct scans separately self._is_scanning = False self.scan_interval = scan_interval + self.connections = [] + self.active_connection = {} + + self.to_forget: str = None + self.to_disconnect = False + self.to_connect: NetworkConfig = None # get initial access points before scan self.access_points = self.nm.get_access_points() def connect(self, ssid: str, password = '') -> bool: - try: - self.nm.connect(ssid, password) - except: - return False - return True + self.to_connect = NetworkConfig(ssid, password) def disconnect(self): - try: - self.nm.disconnect() - except DBusException as e: - # ignore exception - pass + self.to_disconnect = True def start_scanning(self): self._is_scanning = True - self._thread.start() + self._update_thread.start() + self._scan_thread.start() def stop_scanning(self): self._is_scanning = False - self._thread.join() + self._update_thread.join() + self._scan_thread.join() def get_access_points(self): return AccessPointSchema().dump(self.access_points, many=True) def get_active_connection(self): - return ConnectionSchema().dump(self.nm.get_active_wireless_connection()) + return ConnectionSchema().dump(self.active_connection) def list_connections(self): - return ConnectionSchema().dump(self.nm.list_wireless_connections(), many=True) + return ConnectionSchema().dump(self.connections, many=True) def forget(self, ssid: str): - self.nm.forget(ssid) + self.to_forget = ssid + + def _forget(self): + self.nm.forget(self.to_forget) + self.to_forget = None + + def _connect(self): + try: + self.nm.connect(self.to_connect.ssid, self.to_connect.password) + except Exception: + pass + self.to_connect = None + + def _disconnect(self): + try: + self.nm.disconnect() + except DBusException as e: + # ignore exception + pass + self.to_disconnect = False def _scan(self): start_time = time.time() @@ -71,3 +90,22 @@ def _scan(self): # No reason to check too often time.sleep(0.1) + + def _update(self): + while self._is_scanning: + # Queue requests to the network manager to avoid issues + + self.connections = self.nm.list_wireless_connections() + self.active_connection = self.nm.get_active_wireless_connection() + + if not self.to_forget is None: + self._forget() + + if not self.to_connect is None: + self._connect() + + if self.to_disconnect: + self._disconnect() + + # No reason to check too often + time.sleep(0.1) diff --git a/frontend/src/layouts/wifi/KnownNetworksCard.tsx b/frontend/src/layouts/wifi/KnownNetworksCard.tsx index e2e5b0da..3f1027ed 100644 --- a/frontend/src/layouts/wifi/KnownNetworksCard.tsx +++ b/frontend/src/layouts/wifi/KnownNetworksCard.tsx @@ -21,6 +21,7 @@ const KnownNetworksCard = ({}) => { }; useEffect(() => { + refreshNetworks(); const interval = setInterval(() => refreshNetworks(), 500); return () => { clearInterval(interval); diff --git a/frontend/src/layouts/wifi/NetworkSettings.tsx b/frontend/src/layouts/wifi/NetworkSettings.tsx index 6ce571ed..2d0c88f1 100644 --- a/frontend/src/layouts/wifi/NetworkSettings.tsx +++ b/frontend/src/layouts/wifi/NetworkSettings.tsx @@ -162,7 +162,7 @@ const NetworkSettingsCard: React.FC = ({}) => { variant: "success", }); else enqueueSnackbar("Connection failed", { variant: "error" }); - }, 250); // FIXME: the api requires a slight delay after connecting. This should be fixed on the backend side with a timeout + }, 500); }; const onDisconnectFromNetwork = async () => { From 4ae8c890ed2c6910e8d07448aa50e40a95308a7c Mon Sep 17 00:00:00 2001 From: brandonhs Date: Thu, 3 Oct 2024 10:46:01 -0700 Subject: [PATCH 26/27] Add information about forgetting network --- .../src/layouts/wifi/KnownNetworksCard.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/src/layouts/wifi/KnownNetworksCard.tsx b/frontend/src/layouts/wifi/KnownNetworksCard.tsx index 3f1027ed..e03238b0 100644 --- a/frontend/src/layouts/wifi/KnownNetworksCard.tsx +++ b/frontend/src/layouts/wifi/KnownNetworksCard.tsx @@ -12,9 +12,11 @@ import { styles } from "../../style"; import { Connection } from "./types"; import { forgetNetwork, getConnections } from "./api"; import WifiListItem, { WifiListItemType } from "./WifiListItem"; +import { useSnackbar } from "notistack"; const KnownNetworksCard = ({}) => { const [knownNetworks, setKnownNetworks] = useState([] as Connection[]); + const { enqueueSnackbar } = useSnackbar(); const refreshNetworks = () => { getConnections().then(setKnownNetworks); @@ -29,6 +31,21 @@ const KnownNetworksCard = ({}) => { }; }, []); + const onForgetNetwork = async (ssid: string) => { + await forgetNetwork(ssid); + setTimeout(async () => { + let newNetworks = await getConnections(); + if (newNetworks.find((connection) => connection.id === ssid)) + enqueueSnackbar("Failed to forget network", { + variant: "error", + }); + else + enqueueSnackbar("Successfully forgot network!", { + variant: "success", + }); + }, 250); + }; + return ( { signal_strength={100} type={WifiListItemType.KNOWN} on_forget={() => - forgetNetwork(connection.id) + onForgetNetwork(connection.id) } /> )) From 97400bcaadb92dca93192d48cff3bd6ea412e040 Mon Sep 17 00:00:00 2001 From: brandonhs Date: Thu, 3 Oct 2024 10:50:51 -0700 Subject: [PATCH 27/27] Add logging to wifi manager --- backend_py/src/services/wifi/wifi_manager.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backend_py/src/services/wifi/wifi_manager.py b/backend_py/src/services/wifi/wifi_manager.py index 0f292073..6730426d 100644 --- a/backend_py/src/services/wifi/wifi_manager.py +++ b/backend_py/src/services/wifi/wifi_manager.py @@ -31,6 +31,7 @@ def disconnect(self): self.to_disconnect = True def start_scanning(self): + logging.info('Starting WiFi Manager...') self._is_scanning = True self._update_thread.start() self._scan_thread.start() @@ -57,19 +58,21 @@ def _forget(self): self.to_forget = None def _connect(self): + logging.info(f'Connecting to network: {self.to_connect.ssid}') try: self.nm.connect(self.to_connect.ssid, self.to_connect.password) except Exception: - pass + logging.error(f'Failed to connect to network: {self.to_connect.ssid}') self.to_connect = None def _disconnect(self): + logging.info('Disconnecting from network') try: self.nm.disconnect() except DBusException as e: # ignore exception - pass - self.to_disconnect = False + logging.error('Failed to disconnect from network') + self.to_disconnect = False def _scan(self): start_time = time.time()