From 1d258875a8486f72c299a4a9c4c46791147f128b Mon Sep 17 00:00:00 2001 From: devmatrix Date: Fri, 20 Feb 2026 16:41:51 +0000 Subject: [PATCH] Add optimized fan curves: Balanced, Silent, Performance + curve API --- __pycache__/fan_controller.cpython-312.pyc | Bin 35456 -> 35644 bytes data/config.json | 106 +++++++-------- fan_controller.py | 3 +- server.log | 147 +-------------------- web_server.py | 49 +++++++ 5 files changed, 103 insertions(+), 202 deletions(-) diff --git a/__pycache__/fan_controller.cpython-312.pyc b/__pycache__/fan_controller.cpython-312.pyc index 20555c4605b2425097400dbabf81b604ff076be7..01117a436ba6c81df82901689c14f843320ad499 100644 GIT binary patch delta 1044 zcmah{U1$_n6uxJ6c3rbOna$27?rzp(ON&2kDncuwXyPA+ChjIxTCm1$*1co0o4Co& zWLFdJV2tq>5#_X@@u%xU3oVKy%!8#BEdI5$ARGEp^3bC6t>VwXCJQ2-G_ky_0?Q2n z?Dx6xQ|fUlRwb(x(v>uP73R9BFG85ny_)4?su5x;^Aw0t%{)vE;ncn$_UIm&=bn${ zV?MTcp-mMdm|PL4VJ=ohwGh`_Ncjge94^%3>UhAhl-6NZ-}qiVb5<>Sf1#XCQ-a=AHjn{%vG$gpdw? z9+tv~BAHq&hc%}nsOal@7v8WNyE-5^?Mf7dwn3bUcRHs0&AnOCi%_= zIo4$74pt(!+oah;>ls8PqQ5yxAfL=;mWNF;lE z6Z?}{)3C3bH^7?_yY_I18@-szSBIW+^+`2^hp*j(%C)=xl*21Eq$>spUQ( pf_INk`$hy;+?uSs2trBhd|;Htt+#aH)yX5Th!$Y$?Ah4x|{;Lo4>cs&;gY5y=X2~YmC5PmcT(^12E$P`9n@I@i?O1rcnP7&~PtsA3OgLOA|Xo2JUs8BH}W@5(WN~b7!=V8q${@yB+ ziX~ei&O$`lOGIK!aSYNi2~o@hBD#FKxL4gX2r_|If~*X=GQHmQ{8 zZ*h|4j%NK(;6@EZzK<8d8UCm^+URsha|R5)W8A`PvoopG)?YgV7c-zLxds9ATyj~NMh#fjBBUP z2eXN6ZA)%T>ng`qD`TKGp6dZf;pI$~Zk%dg;p5Cp7{m1WH`WyMvZp5(GmK++G6RVD V$*<;vdT{1B#+5OX`sKnN_zC4J?Pvf1 diff --git a/data/config.json b/data/config.json index b22f3b3..5814d53 100644 --- a/data/config.json +++ b/data/config.json @@ -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 - } - ] -} \ No newline at end of file + "active_curve": "Balanced", + "theme": "dark" +} diff --git a/fan_controller.py b/fan_controller.py index d09728e..f1e5eba 100644 --- a/fan_controller.py +++ b/fan_controller.py @@ -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', {}) diff --git a/server.log b/server.log index b12e2a4..24333aa 100644 --- a/server.log +++ b/server.log @@ -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 diff --git a/web_server.py b/web_server.py index 6355981..01444cf 100644 --- a/web_server.py +++ b/web_server.py @@ -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)):