From 93fe42f46c923ae565af7cbc2b382f33701ba5d9 Mon Sep 17 00:00:00 2001 From: devmatrix Date: Fri, 20 Feb 2026 20:03:54 +0000 Subject: [PATCH] Add auto-start on startup - resume last settings if enabled --- server.log | 195 ++++++++++++++++++++++++++++++++++- static/icons/thermometer.png | Bin 0 -> 26393 bytes static/icons/thermometer.svg | 4 +- web_server.py | 12 +++ 4 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 static/icons/thermometer.png diff --git a/server.log b/server.log index 4018fa0..271f345 100644 --- a/server.log +++ b/server.log @@ -1,6 +1,195 @@ -INFO: Started server process [68799] +INFO: Started server process [69621] 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: 127.0.0.1:48966 - "GET / HTTP/1.1" 200 OK -INFO: 127.0.0.1:56088 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:63726 - "GET /api/status HTTP/1.1" 401 Unauthorized +INFO: 192.168.5.30:63726 - "GET /login HTTP/1.1" 200 OK +INFO: 192.168.5.30:63726 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified +/home/devmatrix/projects/fan-controller-v2/web_server.py:153: 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:59632 - "POST /api/auth/login HTTP/1.1" 200 OK +INFO: 192.168.5.30:59632 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:59632 - "GET /icons/sun.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:57438 - "GET /icons/lock-closed.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:49371 - "GET /icons/arrow-right-on-rectangle.svg HTTP/1.1" 304 Not Modified +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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 19:59:12,404 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json +INFO: 192.168.5.30:57438 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:59632 - "GET /icons/thermometer.svg HTTP/1.1" 200 OK +INFO: 192.168.5.30:53397 - "GET /icons/list-bullet.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:60055 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:61231 - "GET /icons/server-stack.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:57438 - "GET /icons/document-text.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:49371 - "GET /icons/chart-bar.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:49371 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 127.0.0.1:57942 - "GET /icons/thermometer.svg HTTP/1.1" 200 OK +2026-02-20 19:59:42,166 - fan_controller - INFO - Connected to IPMI at 192.168.5.191 +INFO: 192.168.5.30:49371 - "POST /api/test HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:49371 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:58740 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:61627 - "GET /icons/adjustments-horizontal.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:62162 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:62162 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:64252 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:62162 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:64911 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:62162 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:50760 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:60142 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:50760 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "POST /api/test HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50760 - "GET /api/status HTTP/1.1" 200 OK +2026-02-20 20:01:35,820 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json +2026-02-20 20:01:35,970 - fan_controller - INFO - Manual fan control enabled +2026-02-20 20:01:36,121 - fan_controller - INFO - Connected to IPMI at 192.168.5.191 +2026-02-20 20:01:36,122 - fan_controller - INFO - HTTP sensor client initialized for http://192.168.5.200:8888 +2026-02-20 20:01:36,122 - fan_controller - INFO - IPMI Controller service started +INFO: 192.168.5.30:50760 - "POST /api/control/auto HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +2026-02-20 20:01:36,290 - fan_controller - INFO - Manual fan control enabled +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50760 - "GET /api/status HTTP/1.1" 200 OK +2026-02-20 20:01:41,821 - fan_controller - INFO - Fan 0xff speed set to 27% +2026-02-20 20:01:41,822 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C) +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:62312 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:54108 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:50760 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:50760 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:50760 - "GET /login HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:153: 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:55526 - "POST /api/auth/login HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET / HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55526 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55526 - "GET /api/status HTTP/1.1" 200 OK +2026-02-20 20:02:11,960 - fan_controller - INFO - Fan 0xff speed set to 29% +2026-02-20 20:02:11,961 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C) +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55526 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET / HTTP/1.1" 200 OK +INFO: 192.168.5.30:55175 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:55526 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:65016 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified +INFO: 192.168.5.30:65016 - "GET /favicon.ico HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +2026-02-20 20:02:28,030 - fan_controller - INFO - Fan 0xff speed set to 27% +2026-02-20 20:02:28,030 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C) +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +INFO: 192.168.5.30:65016 - "GET /api/status HTTP/1.1" 200 OK +/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:65016 - "GET /api/status HTTP/1.1" 200 OK diff --git a/static/icons/thermometer.png b/static/icons/thermometer.png new file mode 100644 index 0000000000000000000000000000000000000000..241f4f137afc1dd02779bc5068188cd21a11ad56 GIT binary patch literal 26393 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 literal 0 HcmV?d00001 diff --git a/static/icons/thermometer.svg b/static/icons/thermometer.svg index 8d48764..f511dcf 100644 --- a/static/icons/thermometer.svg +++ b/static/icons/thermometer.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/web_server.py b/web_server.py index d17c920..a7282cc 100644 --- a/web_server.py +++ b/web_server.py @@ -1157,6 +1157,18 @@ with open('/home/devmatrix/.openclaw/workspace/setup_html.txt', 'r') as f: @asynccontextmanager async def lifespan(app: FastAPI): DATA_DIR.mkdir(parents=True, exist_ok=True) + + # Auto-start fan control if enabled in config and setup is complete + if user_manager.is_setup_complete(): + try: + service = get_service(str(CONFIG_FILE)) + if service.config.get('enabled', False): + logger.info("Auto-starting fan control (enabled in config)") + if not service.running: + service.start() + except Exception as e: + logger.error(f"Auto-start failed: {e}") + yield app = FastAPI(