commit 9ba660f803cce9f8e4860159724f78d0ed6780f0 Author: Gleb O. Ivaniczkij Date: Fri Jul 26 02:45:25 2024 +0300 Разработаны модули извлечения и сохранения статистических данных, разработан набросок веб-приложения diff --git a/OpenHardwareMonitorLib.dll b/OpenHardwareMonitorLib.dll new file mode 100644 index 0000000..ea3c3bd Binary files /dev/null and b/OpenHardwareMonitorLib.dll differ diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..a1edb25 --- /dev/null +++ b/database/__init__.py @@ -0,0 +1 @@ +from .main import connect, get_sensors, get_sensor_values diff --git a/database/main.py b/database/main.py new file mode 100644 index 0000000..6aa0fe5 --- /dev/null +++ b/database/main.py @@ -0,0 +1,190 @@ +import os +import sqlite3 + +from models import HardwareType, Hardware, SensorType, Sensor, SensorValue + + +directory_path = os.path.join( + os.getenv('appdata'), + 'csasq', + 'Hardware Monitor', +) +file_path = os.path.join( + directory_path, + 'data.sql', +) + + +def connect(): + os.makedirs( + name=directory_path, + exist_ok=True, + ) + return sqlite3.connect( + database=file_path, + ) + + +with connect() as connection: + cursor = connection.cursor() + sql = ''' + create table if not exists settings ( + name, + value, + primary key (name) + ); + + create table if not exists hardware_types ( + id integer, + name, + primary key (id), + unique (name) + ); + + create table if not exists hardware ( + id integer, + identifier, + name, + hardware_type_id, + primary key (id), + foreign key (hardware_type_id) references hardware_types on delete cascade on update cascade, + unique (identifier) + ); + + create table if not exists sensor_types ( + id integer, + name, + primary key (id), + unique (name) + ); + + create table if not exists sensors ( + id integer, + hardware_id, + sensor_type_id, + identifier, + name, + 'index', + is_default_hidden, + enabled, + primary key (id), + foreign key (hardware_id) references hardware on delete cascade on update cascade, + foreign key (sensor_type_id) references sensor_types on delete cascade on update cascade, + unique (identifier, sensor_type_id) + ); + + create table if not exists sensor_values ( + sensor_id, + timestamp, + value, + foreign key (sensor_id) references sensors + ); + + pragma foreign_keys = on; + ''' + cursor.executescript(sql) + + +def get_sensors() -> list[Sensor]: + with connect() as connection: + cursor = connection.cursor() + sql = ''' + select + sensors.id, + hardware.id, + hardware.identifier, + hardware.name, + hardware_types.id, + hardware_types.name, + sensor_types.id, + sensor_types.name, + sensors.identifier, + sensors.name, + sensors.'index', + sensors.is_default_hidden, + sensors.enabled + from + sensors + left outer join hardware on + sensors.hardware_id = hardware.id + left outer join hardware_types on + hardware.hardware_type_id = hardware_types.id + left outer join sensor_types on + sensors.sensor_type_id = sensor_types.id; + ''' + cursor.execute(sql) + records = cursor.fetchall() + return list( + Sensor( + id=sensor_id, + hardware=Hardware( + id=sensor_hardware_id, + identifier=sensor_hardware_identifier, + name=sensor_hardware_name, + hardware_type=HardwareType( + id=sensor_hardware_type_id, + name=sensor_hardware_type_name, + ), + ), + sensor_type=SensorType( + id=sensor_type_id, + name=sensor_type_name, + ), + identifier=sensor_identifier, + name=sensor_name, + index=sensor_index, + is_default_hidden=sensor_is_default_hidden, + enabled=sensor_enabled, + ) + for + sensor_id, + sensor_hardware_id, + sensor_hardware_identifier, + sensor_hardware_name, + sensor_hardware_type_id, + sensor_hardware_type_name, + sensor_type_id, + sensor_type_name, + sensor_identifier, + sensor_name, + sensor_index, + sensor_is_default_hidden, + sensor_enabled, + in + records + ) + + +def get_sensor_values() -> list[SensorValue]: + with connect() as connection: + cursor = connection.cursor() + sql = ''' + select + sensor_values.sensor_id, + sensor_values.timestamp, + sensor_values.value + from + sensor_values + where + sensor_values.sensor_id in ?; + ''' + cursor.execute( + sql, + ( + [15, 16, 17], + ), + ) + records, = cursor.fetchall() + return list( + SensorValue( + sensor_id=sensor_id, + timestamp=timestamp, + value=value, + ) + for + sensor_id, + timestamp, + value, + in + records + ) diff --git a/main.py b/main.py new file mode 100644 index 0000000..1949cc4 --- /dev/null +++ b/main.py @@ -0,0 +1,15 @@ +from monitor import Monitor +from threading import Thread +import uvicorn +from web import app + + +Monitor().start() +web_thread = Thread( + target=uvicorn.run( + app=app, + host='localhost', + port=8000, + ), +) +web_thread.start() diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..86d5978 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +from .main import Setting, HardwareType, Hardware, SensorType, Sensor, SensorValue diff --git a/models/main.py b/models/main.py new file mode 100644 index 0000000..047ba11 --- /dev/null +++ b/models/main.py @@ -0,0 +1,53 @@ +from datetime import datetime +from pydantic import BaseModel + + +class Setting(BaseModel): + name: str + value: str + + +class HardwareType(BaseModel): + id: int + name: str + + def __hash__(self): + return self.id + + +class Hardware(BaseModel): + id: int + identifier: str + name: str + hardware_type: HardwareType + + def __hash__(self): + return self.id + + +class SensorType(BaseModel): + id: int + name: str + + def __hash__(self): + return self.id + + +class Sensor(BaseModel): + id: int + hardware: Hardware + sensor_type: SensorType + identifier: str + name: str + index: int + is_default_hidden: bool + enabled: bool + + def __hash__(self): + return self.id + + +class SensorValue(BaseModel): + sensor: Sensor + timestamp: datetime + value: str diff --git a/monitor/__init__.py b/monitor/__init__.py new file mode 100644 index 0000000..9eab169 --- /dev/null +++ b/monitor/__init__.py @@ -0,0 +1 @@ +from .main import Monitor diff --git a/monitor/main.py b/monitor/main.py new file mode 100644 index 0000000..250298d --- /dev/null +++ b/monitor/main.py @@ -0,0 +1,159 @@ +import clr +from datetime import datetime +import os +from threading import Thread +import time + +import database + + +class Monitor(Thread): + def __init__(self): + clr.AddReference(r'OpenHardwareMonitorLib') + from OpenHardwareMonitor import Hardware + self.handle = Hardware.Computer() + self.handle.MainboardEnabled = True + self.handle.CPUEnabled = True + self.handle.RAMEnabled = True + self.handle.GPUEnabled = True + self.handle.HDDEnabled = True + self.handle.Open() + super().__init__() + + def run(self): + def parse_sensor(sensor): + sql = ''' + insert into sensor_types ( + name + ) values ( + ? + ) + on conflict (name) do update set + name = sensor_types.name + returning + sensor_types.id; + ''' + cursor.execute( + sql, + ( + str(sensor.SensorType), + ), + ) + sensor_type_id, = cursor.fetchone() + sql = ''' + insert into sensors ( + hardware_id, + sensor_type_id, + identifier, + name, + 'index', + is_default_hidden, + enabled + ) values ( + ?, + ?, + ?, + ?, + ?, + ?, + false + ) + on conflict (identifier, sensor_type_id) do update set + hardware_id = sensors.hardware_id, + name = sensors.name, + 'index' = sensors.'index', + is_default_hidden = sensors.is_default_hidden, + enabled = sensors.enabled + returning + sensors.id, + sensors.enabled; + ''' + cursor.execute( + sql, + ( + str(hardware_id), + sensor_type_id, + str(sensor.Identifier), + str(sensor.Name), + int(sensor.Index), + bool(sensor.IsDefaultHidden), + ), + ) + sensor_id, sensor_enabled, = cursor.fetchone() + if not sensor_enabled: + return + sql = ''' + insert into sensor_values ( + sensor_id, + timestamp, + value + ) values ( + ?, + ?, + ? + ); + ''' + cursor.execute( + sql, + ( + sensor_id, + datetime.now(), + sensor.Value, + ), + ) + + while True: + for hardware in self.handle.Hardware: + with database.connect() as connection: + cursor = connection.cursor() + sql = ''' + insert into hardware_types ( + name + ) values ( + ? + ) + on conflict (name) do update set + name = hardware_types.name + returning + hardware_types.id; + ''' + cursor.execute( + sql, + ( + str(hardware.HardwareType), + ), + ) + hardware_type_id, = cursor.fetchone() + sql = ''' + insert into hardware ( + identifier, + name, + hardware_type_id + ) values ( + ?, + ?, + ? + ) + on conflict (identifier) do update set + name = hardware.name, + hardware_type_id = hardware.hardware_type_id + returning + hardware.id; + ''' + cursor.execute( + sql, + ( + str(hardware.Identifier), + str(hardware.Name), + hardware_type_id, + ), + ) + hardware_id, = cursor.fetchone() + hardware.Update() + for sensor in hardware.Sensors: + parse_sensor(sensor) + for subhardware in hardware.SubHardware: + subhardware.Update() + for subsensor in subhardware.Sensors: + parse_sensor(subsensor) + time.sleep(1) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2a304ef --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +fastapi +# gunicorn +jinja2 +pydantic +pythonnet +uvicorn diff --git a/templates/main.jinja2 b/templates/main.jinja2 new file mode 100644 index 0000000..f5e5ef4 --- /dev/null +++ b/templates/main.jinja2 @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ {% block main %} + {% endblock %} +
+
+
+ + diff --git a/templates/reports.jinja2 b/templates/reports.jinja2 new file mode 100644 index 0000000..c6c1780 --- /dev/null +++ b/templates/reports.jinja2 @@ -0,0 +1,99 @@ +{% extends 'main.jinja2' %} +{% block main %} +
+

Mainboard

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +{% endblock %} diff --git a/templates/settings.jinja2 b/templates/settings.jinja2 new file mode 100644 index 0000000..6de4a4c --- /dev/null +++ b/templates/settings.jinja2 @@ -0,0 +1,79 @@ +{% extends 'main.jinja2' %} +{% block main %} +
+

Настройки

+
+{#

Панель управления

#} +{#
#} +{# http://#} +{# #} +{# :#} +{# #} +{#
#} +
+

Датчики

+
+ {% for hardware_type in settings['hardware_types'] %} +
+ + +
+ {% for hardware in settings['hardware_types'][hardware_type] %} +
+
+ + +
+ {% for sensor_type in settings['hardware_types'][hardware_type][hardware] %} +
+
+ + +
+ {% for sensor in settings['hardware_types'][hardware_type][hardware][sensor_type] %} +
+
+ + +
+
+ {% endfor %} +
+ {% endfor %} +
+ {% endfor %} + {% endfor %} +
+
+ +{# #} +{% endblock %} diff --git a/web/__init__.py b/web/__init__.py new file mode 100644 index 0000000..e4c7aa5 --- /dev/null +++ b/web/__init__.py @@ -0,0 +1 @@ +from .main import app diff --git a/web/main.py b/web/main.py new file mode 100644 index 0000000..4bfc779 --- /dev/null +++ b/web/main.py @@ -0,0 +1,96 @@ +import asyncio +from fastapi import FastAPI, Request, Header, Body, Path +from fastapi.responses import Response, HTMLResponse, FileResponse, RedirectResponse, JSONResponse +from fastapi.exceptions import HTTPException +from fastapi.staticfiles import StaticFiles +from jinja2 import Environment, FileSystemLoader +import os + +import database +import models +# from models import MailSizeData + + +app = FastAPI() +app.mount( + path='/static', + app=StaticFiles( + directory=os.path.join( + os.getcwd(), + 'static', + ), + ), +) + +env = Environment( + loader=FileSystemLoader('./templates'), + enable_async=True, +) + + +@app.get('/') +async def function(): + return RedirectResponse( + url='/reports/mainboard', + status_code=307, + ) + + +@app.get('/reports/mainboard') +async def function(): + template = env.get_template('reports.jinja2') + return HTMLResponse( + content=await template.render_async(), + status_code=200, + ) + + +@app.get('/settings') +async def function(): + hardware_types = dict() + sensors = database.get_sensors() + # hardware_types = set( + # sensor.hardware.hardware_type + # for sensor in sensors + # ) + for hardware_type in set( + sensor.hardware.hardware_type + for sensor in sensors + ): + hardware_types[hardware_type] = dict() + for hardware in set( + sensor.hardware + for sensor in sensors + if sensor.hardware.hardware_type == hardware_type + ): + hardware_types[hardware_type][hardware] = dict() + for sensor_type in set( + sensor.sensor_type + for sensor in sensors + if sensor.hardware == hardware + ): + hardware_types[hardware_type][hardware][sensor_type] = list() + for sensor in sensors: + if sensor.hardware == hardware and sensor.sensor_type == sensor_type: + hardware_types[hardware_type][hardware][sensor_type].append(sensor) + template = env.get_template('settings.jinja2') + return HTMLResponse( + content=await template.render_async( + settings={ + 'hostname': 'localhost', + 'port': 8000, + 'hardware_types': hardware_types, + }, + ), + status_code=200, + ) + + +@app.get('/api/sensors') +async def function(): + return database.get_sensors() + + +@app.get('/api/sensor-values') +async def function(): + return database.get_sensor_values()