From ea3ec372d5d9ceee7de0f89d4067e4257a6ad57b Mon Sep 17 00:00:00 2001 From: nhson0110-coder Date: Sun, 11 Jan 2026 21:48:46 +0700 Subject: [PATCH 1/6] feat: add System Monitor sensor plugin Implemented the SystemMonitorSensor in `src/inputs/plugins/system_monitor.py` to track hardware health. Key changes: - Inherits correctly from `Sensor[dict]` (inputs/base). - Implements required abstract methods: `_listen_loop`, `formatted_latest_buffer`, and `raw_to_text`. - Monitors CPU usage, Memory usage, and Temperature using `psutil`. - Includes robust exception handling and Mock mode support for non-hardware environments. - Complies with Ruff/Black formatting standards (sorted imports, relative imports). --- src/inputs/plugins/system_monitor.py | 97 ++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/inputs/plugins/system_monitor.py diff --git a/src/inputs/plugins/system_monitor.py b/src/inputs/plugins/system_monitor.py new file mode 100644 index 000000000..557c1bde2 --- /dev/null +++ b/src/inputs/plugins/system_monitor.py @@ -0,0 +1,97 @@ +import asyncio +import json +import logging +import time +import typing as T + +import psutil + +# Relative import to access the base Sensor class from parent directory +from ..base import Sensor, SensorConfig + + +# Setup logger for the plugin +logger = logging.getLogger(__name__) + + +class SystemMonitorSensor(Sensor[dict]): + """ + SystemMonitorSensor: Monitors hardware health (CPU, RAM, Temperature). + Inherits from Sensor[R] where R is a dictionary of metrics. + """ + + def __init__(self, config: SensorConfig): + super().__init__(config) + self.name = "system_monitor" + # Extract mock_mode from config (loaded via kwargs in SensorConfig) + self.mock_mode = getattr(config, "mock_mode", False) + self.latest_data: dict = {} + logger.info(f"SystemMonitorSensor initialized (Mock: {self.mock_mode})") + + async def _listen_loop(self) -> T.AsyncIterator[dict]: + """ + Main loop: continuously yields system metrics at set intervals. + This is the required implementation for the Sensor base class listen() method. + """ + while True: + try: + data = self._collect_metrics() + self.latest_data = data # Cache data for formatted_latest_buffer + yield data + + # Sleep for 2 seconds to avoid overloading the CPU + await asyncio.sleep(2.0) + + except Exception as e: + logger.error(f"Error in system monitor loop: {e}") + await asyncio.sleep(5.0) # Sleep longer on error before retrying + + def formatted_latest_buffer(self) -> str | None: + """ + Returns a formatted string description of the current system status + for the Fuser (AI Agent) to read. + """ + if not self.latest_data: + return None + + # Format as a readable string for the LLM + return ( + f"System Status -> " + f"CPU: {self.latest_data.get('cpu_usage_percent', 0)}% | " + f"RAM: {self.latest_data.get('memory_usage_percent', 0)}% | " + f"Temp: {self.latest_data.get('temperature_c', 'N/A')}C" + ) + + async def raw_to_text(self, raw_input: dict) -> str: + """ + Converts raw input data (dict) into text format (JSON) for logging or processing. + """ + return json.dumps(raw_input) + + def _collect_metrics(self) -> dict: + """ + Helper method: Gathers actual hardware metrics using psutil. + """ + return { + "cpu_usage_percent": psutil.cpu_percent(interval=None), + "memory_usage_percent": psutil.virtual_memory().percent, + "temperature_c": self._get_temp(), + "timestamp": time.time(), + } + + def _get_temp(self): + """ + Safely retrieves CPU temperature. + """ + if self.mock_mode: + return 45.0 + + try: + temps = psutil.sensors_temperatures() + # Check for common thermal sensor names across different platforms + for name in ["cpu_thermal", "coretemp", "soc_thermal", "k10temp"]: + if name in temps: + return temps[name][0].current + return "N/A" + except (AttributeError, KeyError, PermissionError): + return "N/A" From b14a14609956e323c1b22bef058aef56f9ff7e0b Mon Sep 17 00:00:00 2001 From: nhson0110-coder Date: Sun, 11 Jan 2026 22:00:10 +0700 Subject: [PATCH 2/6] Update system_monitor.py --- src/inputs/plugins/system_monitor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/inputs/plugins/system_monitor.py b/src/inputs/plugins/system_monitor.py index 557c1bde2..0c58da4f1 100644 --- a/src/inputs/plugins/system_monitor.py +++ b/src/inputs/plugins/system_monitor.py @@ -5,8 +5,6 @@ import typing as T import psutil - -# Relative import to access the base Sensor class from parent directory from ..base import Sensor, SensorConfig From 671720ccce16b3fdbf4163986080f28f996f4acb Mon Sep 17 00:00:00 2001 From: nhson0110-coder Date: Sun, 11 Jan 2026 22:06:33 +0700 Subject: [PATCH 3/6] Update system_monitor.py --- src/inputs/plugins/system_monitor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/inputs/plugins/system_monitor.py b/src/inputs/plugins/system_monitor.py index 0c58da4f1..b490c002a 100644 --- a/src/inputs/plugins/system_monitor.py +++ b/src/inputs/plugins/system_monitor.py @@ -5,6 +5,7 @@ import typing as T import psutil + from ..base import Sensor, SensorConfig From 014864232ee0cfdacf17763ef055b6523bd79abe Mon Sep 17 00:00:00 2001 From: nhson0110-coder Date: Sun, 11 Jan 2026 22:09:23 +0700 Subject: [PATCH 4/6] Update system_monitor.py From 2c6dadd8a203245b027f409c39c215bc74dd8252 Mon Sep 17 00:00:00 2001 From: nhson0110-coder Date: Sun, 11 Jan 2026 23:20:36 +0700 Subject: [PATCH 5/6] Update system_monitor.py From 4b503627a9d34dcbdf67f3c287c90ea66ba17bf9 Mon Sep 17 00:00:00 2001 From: SonNguyen Date: Mon, 12 Jan 2026 00:10:34 +0700 Subject: [PATCH 6/6] fix: auto-format imports by ruff --- src/inputs/plugins/system_monitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/inputs/plugins/system_monitor.py b/src/inputs/plugins/system_monitor.py index b490c002a..c49ca7012 100644 --- a/src/inputs/plugins/system_monitor.py +++ b/src/inputs/plugins/system_monitor.py @@ -8,7 +8,6 @@ from ..base import Sensor, SensorConfig - # Setup logger for the plugin logger = logging.getLogger(__name__)