Add optimized fan curves: Balanced, Silent, Performance + curve API

This commit is contained in:
devmatrix 2026-02-20 16:41:51 +00:00
parent bd99b80aab
commit 1d258875a8
5 changed files with 103 additions and 202 deletions

View File

@ -20,70 +20,54 @@
"fans": {},
"fan_groups": {},
"fan_curves": {
"Default": {
"Balanced": {
"points": [
{
"temp": 30,
"speed": 15
},
{
"temp": 40,
"speed": 25
},
{
"temp": 50,
"speed": 40
},
{
"temp": 60,
"speed": 60
},
{
"temp": 70,
"speed": 80
},
{
"temp": 80,
"speed": 100
}
{"temp": 30, "speed": 10},
{"temp": 35, "speed": 12},
{"temp": 40, "speed": 15},
{"temp": 45, "speed": 20},
{"temp": 50, "speed": 30},
{"temp": 55, "speed": 40},
{"temp": 60, "speed": 55},
{"temp": 65, "speed": 70},
{"temp": 70, "speed": 85},
{"temp": 75, "speed": 95},
{"temp": 80, "speed": 100}
],
"sensor_source": "cpu",
"applies_to": "all"
},
"Silent": {
"points": [
{"temp": 30, "speed": 5},
{"temp": 40, "speed": 10},
{"temp": 50, "speed": 15},
{"temp": 55, "speed": 25},
{"temp": 60, "speed": 35},
{"temp": 65, "speed": 50},
{"temp": 70, "speed": 70},
{"temp": 75, "speed": 85},
{"temp": 80, "speed": 100}
],
"sensor_source": "cpu",
"applies_to": "all"
},
"Performance": {
"points": [
{"temp": 30, "speed": 20},
{"temp": 35, "speed": 25},
{"temp": 40, "speed": 35},
{"temp": 45, "speed": 45},
{"temp": 50, "speed": 55},
{"temp": 55, "speed": 70},
{"temp": 60, "speed": 85},
{"temp": 65, "speed": 95},
{"temp": 70, "speed": 100}
],
"sensor_source": "cpu",
"applies_to": "all"
}
},
"theme": "dark",
"ssh_enabled": false,
"ssh_host": null,
"ssh_username": null,
"ssh_password": null,
"ssh_use_key": false,
"ssh_key_file": null,
"ssh_port": 22,
"interval": 10,
"fan_curve": [
{
"temp": 30,
"speed": 15
},
{
"temp": 40,
"speed": 25
},
{
"temp": 50,
"speed": 40
},
{
"temp": 60,
"speed": 60
},
{
"temp": 70,
"speed": 80
},
{
"temp": 80,
"speed": 100
}
]
"active_curve": "Balanced",
"theme": "dark"
}

View File

@ -563,7 +563,8 @@ class IPMIControllerService:
# Get fan curves
curves = self.config.get('fan_curves', {})
default_curve = curves.get('Default', {'points': [{'temp': 30, 'speed': 15}, {'temp': 80, 'speed': 100}]})
active_curve_name = self.config.get('active_curve', 'Balanced')
default_curve = curves.get(active_curve_name, curves.get('Balanced', {'points': [{'temp': 30, 'speed': 15}, {'temp': 80, 'speed': 100}]}))
# Apply curves to fans
fans = self.config.get('fans', {})

View File

@ -1,147 +1,14 @@
INFO: Started server process [18785]
INFO: Started server process [20120]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 192.168.5.30:50547 - "GET /api/status HTTP/1.1" 401 Unauthorized
INFO: 192.168.5.30:50547 - "GET /login HTTP/1.1" 200 OK
INFO: 192.168.5.30:60395 - "GET /api/status HTTP/1.1" 401 Unauthorized
INFO: 192.168.5.30:60395 - "GET /login HTTP/1.1" 200 OK
INFO: 127.0.0.1:40728 - "GET /api/curves HTTP/1.1" 401 Unauthorized
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
self._sessions[token] = (username, datetime.utcnow() + timedelta(days=7))
INFO: 192.168.5.30:64565 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:40744 - "POST /api/auth/login HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
2026-02-20 16:36:09,107 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /favicon.ico HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 16:36:15,352 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 16:36:15,642 - fan_controller - INFO - Connected to IPMI at 192.168.5.191
INFO: 192.168.5.30:64565 - "POST /api/config/ipmi HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /favicon.ico HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "POST /api/test HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 16:36:59,135 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 16:36:59,301 - fan_controller - INFO - Fan 0xff speed set to 100%
INFO: 192.168.5.30:64565 - "POST /api/control/manual HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:42260 - "GET /api/public/temperatures HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 16:37:10,019 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 16:37:10,171 - fan_controller - INFO - Fan 0xff speed set to 0%
INFO: 192.168.5.30:64565 - "POST /api/control/manual HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 16:37:16,181 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 16:37:16,392 - fan_controller - INFO - Fan 0xff speed set to 13%
INFO: 192.168.5.30:64565 - "POST /api/control/manual HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
self._sessions[token] = (username, datetime.utcnow() + timedelta(days=7))
INFO: 127.0.0.1:53160 - "POST /api/auth/login HTTP/1.1" 200 OK
2026-02-20 16:37:20,069 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 16:37:20,231 - fan_controller - INFO - Manual fan control enabled
2026-02-20 16:37:20,400 - fan_controller - INFO - Connected to IPMI at 192.168.5.191
2026-02-20 16:37:20,400 - fan_controller - INFO - HTTP sensor client initialized for http://192.168.5.200:8888
2026-02-20 16:37:20,401 - fan_controller - INFO - IPMI Controller service started
INFO: 127.0.0.1:53164 - "POST /api/control/auto HTTP/1.1" 200 OK
2026-02-20 16:37:20,568 - fan_controller - INFO - Manual fan control enabled
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:54102 - "GET /api/public/temperatures HTTP/1.1" 200 OK
2026-02-20 16:37:26,052 - fan_controller - INFO - Fan 0xff speed set to 15%
2026-02-20 16:37:26,052 - fan_controller - INFO - All fans set to 15% (Temp 27.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
self._sessions[token] = (username, datetime.utcnow() + timedelta(days=7))
INFO: 127.0.0.1:46386 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 127.0.0.1:46402 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:49878 - "POST /api/auth/login HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
2026-02-20 16:37:51,890 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 127.0.0.1:49884 - "POST /api/config/settings HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:51568 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
self._sessions[token] = (username, datetime.utcnow() + timedelta(days=7))
INFO: 127.0.0.1:60806 - "POST /api/auth/login HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
2026-02-20 16:38:11,328 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 127.0.0.1:60810 - "POST /api/config/settings HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 16:38:12,401 - fan_controller - INFO - Fan 0xff speed set to 44%
2026-02-20 16:38:12,401 - fan_controller - INFO - All fans set to 44% (Temp 52.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:40062 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64565 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:53448 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
if datetime.utcnow() > expiry:
INFO: 192.168.5.30:53448 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:53448 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
self._sessions[token] = (username, datetime.utcnow() + timedelta(days=7))
INFO: 127.0.0.1:34550 - "POST /api/auth/login HTTP/1.1" 200 OK
2026-02-20 16:38:28,624 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 127.0.0.1:34556 - "POST /api/config/settings HTTP/1.1" 200 OK
INFO: 192.168.5.30:53448 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:53448 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 16:41:42,900 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
INFO: 127.0.0.1:40756 - "GET /api/curves HTTP/1.1" 200 OK

View File

@ -1393,6 +1393,55 @@ async def api_config_settings(data: FanSettings, username: str = Depends(get_cur
service.update_config(**updates)
return {"success": True}
# Fan Curves API
@app.get("/api/curves")
async def api_list_curves(username: str = Depends(get_current_user)):
"""List all fan curves."""
service = get_service(str(CONFIG_FILE))
return {
"curves": service.config.get('fan_curves', {}),
"active": service.config.get('active_curve', 'Balanced')
}
@app.post("/api/curves")
async def api_create_curve(data: dict, username: str = Depends(get_current_user)):
"""Create or update a fan curve."""
service = get_service(str(CONFIG_FILE))
name = data.get('name')
if not name:
return {"success": False, "error": "Name required"}
curves = service.config.get('fan_curves', {})
curves[name] = {
"points": data.get('points', []),
"sensor_source": data.get('sensor_source', 'cpu'),
"applies_to": data.get('applies_to', 'all')
}
service.update_config(fan_curves=curves)
return {"success": True}
@app.post("/api/curves/active")
async def api_set_active_curve(data: dict, username: str = Depends(get_current_user)):
"""Set active fan curve."""
service = get_service(str(CONFIG_FILE))
name = data.get('name')
curves = service.config.get('fan_curves', {})
if name not in curves:
return {"success": False, "error": "Curve not found"}
service.update_config(active_curve=name)
return {"success": True}
@app.delete("/api/curves/{name}")
async def api_delete_curve(name: str, username: str = Depends(get_current_user)):
"""Delete a fan curve."""
service = get_service(str(CONFIG_FILE))
curves = service.config.get('fan_curves', {})
if name in curves:
del curves[name]
service.update_config(fan_curves=curves)
return {"success": True}
# Fan API
@app.post("/api/fans/identify")
async def api_identify_fan(req: IdentifyRequest, username: str = Depends(get_current_user)):