From adab150746565cd9b7092b9f4da9ce26638e5db2 Mon Sep 17 00:00:00 2001 From: Jeff Curless Date: Wed, 19 Nov 2025 23:12:54 -0500 Subject: [PATCH] Add process to allow modified smartctl commands --- monitor/configfile.py | 11 ++++---- monitor/oneUpMon.py | 59 +++++++++++++++++++++++++--------------- monitor/systemsupport.py | 58 ++++++++++++++++++++++++++++++++++++--- monitor/test.ini | 5 ++++ 4 files changed, 102 insertions(+), 31 deletions(-) diff --git a/monitor/configfile.py b/monitor/configfile.py index 318eaef..e3ba858 100644 --- a/monitor/configfile.py +++ b/monitor/configfile.py @@ -22,10 +22,10 @@ class ConfigClass: occasionally. ''' - if not self.readFile: + try: _result = self.config.read( self.filename ) - if len(_result) > 0: - self.readFile = True + except Exception as error: + print( f"{error}" ) def getValue( self, section : str, key : str, default="" ) -> str: ''' @@ -41,7 +41,6 @@ class ConfigClass: The value of the key from the section read. ''' value = default - self._openConfig() try: value = self.config[section][key].replace('"','').strip() except: @@ -62,7 +61,6 @@ class ConfigClass: a List of items ''' value = default - self._openConfig() try: temp = self.config[section][name] value = [ n.replace('"','').strip() for n in temp.split(",")] @@ -76,6 +74,9 @@ if __name__ == "__main__": print( f"Value = {cfg.getValueAsList( 'temperature', 'ignore' )}" ) print( f"Value = {cfg.getValue( 'performance', 'ignore' )}" ) print( f"Value = {cfg.getValueAsList( 'performance', 'ignore' )}" ) + + drive = cfg.getValue( 'smartctl', 'sda' ) + print( f"drive = {drive}" ) cfg = ConfigClass( "missingfile.ini" ) diff --git a/monitor/oneUpMon.py b/monitor/oneUpMon.py index 4eb7686..544aeb0 100755 --- a/monitor/oneUpMon.py +++ b/monitor/oneUpMon.py @@ -8,13 +8,16 @@ Requires: PyQt5 (including QtCharts) """ import sys -from systemsupport import CPUInfo, CPULoad, multiDriveStat +from systemsupport import CPUInfo, CPULoad, multiDriveStat, CaseFan from configfile import ConfigClass # -------------------------- # Globals # -------------------------- +MIN_WIDTH = 1000 +MIN_HEIGHT = 800 + # -------------------------- # UI # -------------------------- @@ -265,12 +268,14 @@ class MonitorWindow(QMainWindow): self.drivePerfFilter = self.config.getValueAsList( 'performance', 'ignore' ) # Get supporting objects - self.cpuinfo = CPUInfo() - self.cpuload = CPULoad() + self.cpuinfo = CPUInfo() + self.cpuload = CPULoad() + self.casefan = CaseFan() + self.caseFanPin = None self.multiDrive = multiDriveStat() self.setWindowTitle("System Monitor") - self.setMinimumSize(900, 900) + self.setMinimumSize(MIN_WIDTH, MIN_HEIGHT) central = QWidget(self) grid = QGridLayout(central) @@ -299,18 +304,22 @@ class MonitorWindow(QMainWindow): window=window ) - casefan = self.config.getValue( "cooling", "casefan", None ) - if casefan is None: - series = [("RPM",None)] - else: - series = [("CPU", None),("CaseFan",None)] + if self.cpuinfo.model == 5: + self.caseFanPin = self.config.getValue( "cooling", "casefan", None ) + if self.caseFanPin is None: + series = [("CPU",None)] + else: + self.casefan.setTACHPin( self.caseFanPin) + series = [("CPU",None),("CaseFan",None)] - self.fan_chart = RollingChart( - title="Fan Speed", - series_defs=series, - y_min=0,y_max=6000, - window=window - ) + self.fan_chart = RollingChart( + title="Fan Speed", + series_defs=series, + y_min=0,y_max=6000, + window=window + ) + else: + self.fan_chart = None series = [] for name in self.multiDrive.drives: @@ -328,8 +337,11 @@ class MonitorWindow(QMainWindow): # Layout: 2x2 grid (CPU, NVMe on top; IO full width bottom) grid.addWidget(self.use_chart, 0, 0, 1, 2 ) grid.addWidget(self.io_chart, 1, 0, 1, 2 ) - grid.addWidget(self.cpu_chart, 2, 0, 1, 1 ) - grid.addWidget(self.fan_chart, 2, 1, 1, 1 ) + if self.fan_chart: + grid.addWidget(self.cpu_chart, 2, 0, 1, 1 ) + grid.addWidget(self.fan_chart, 2, 1, 1, 1 ) + else: + grid.addWidget(self.cpu_chart, 2, 0, 1, 2 ) # Get the initial information from the syste self.refresh_metrics() @@ -350,11 +362,14 @@ class MonitorWindow(QMainWindow): # Obtain the current fan speed if self.cpuinfo.model == 5: try: - fan_speed = self.cpuinfo.CPUFanSpeed + if self.caseFanPin: + fan_speed = [self.cpuinfo.CPUFanSpeed,self.casefan.speed] + else: + fan_speed = [self.cpuinfo.CPUFanSpeed] except Exception: - fan_speed = None + fan_speed = [None,None] else: - fan_speed = None + fan_speed = [None,None] # Setup the temperature for the CPU and Drives temperatures = [] @@ -392,7 +407,8 @@ class MonitorWindow(QMainWindow): # Append to charts self.cpu_chart.append( temperatures ) - self.fan_chart.append([fan_speed]) + if self.fan_chart: + self.fan_chart.append( fan_speed ) self.io_chart.append( rwData ) self.use_chart.append( values ) @@ -404,4 +420,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/monitor/systemsupport.py b/monitor/systemsupport.py index 8a92da1..5de71ea 100755 --- a/monitor/systemsupport.py +++ b/monitor/systemsupport.py @@ -84,6 +84,7 @@ class DriveStats: def name(self) -> str: return self._device + @property def readAllStats( self ) -> list[int]: ''' read all of the drive statisics from sysfs for the device. @@ -93,19 +94,24 @@ class DriveStats: ''' return self._getStats() + @property def readSectors( self )-> int: return self._getStats()[DriveStats.READ_SECTORS] + @property def writeSectors( self ) -> int: return self._getStats()[DriveStats.WRITE_SECTORS] + @property def discardSectors( self ) -> int: return self._getStats()[DriveStats.DISCARD_SECTORS] + @property def readWriteSectors( self ) -> tuple[int,int]: curData = self._getStats() return (curData[DriveStats.READ_SECTORS],curData[DriveStats.WRITE_SECTORS]) + @property def readWriteBytes( self ) -> tuple[int,int]: curData = self._getStats() return (curData[DriveStats.READ_SECTORS]*512,curData[DriveStats.WRITE_SECTORS]*512) @@ -168,6 +174,19 @@ class multiDriveStat(): return 0 def driveTemp(self,_drive:str, extracmd = None) -> float: + ''' + Get the drive temperature using smart data. There are three basic temperature + settings we can read, smart ID 194, 190 and the Temperature: value. These are + depenent on the drive, so look for all of them, and depending on the result, we + get the value. + + Parameters: + _drive : The device we wish to scan + extraCmd : An optional additional command to send to the device. + + Returns: + The temperature as a float, or zero if there is an error. + ''' smartOutRaw = "" if extracmd is None: cmd = f'sudo smartctl -A /dev/{_drive}' @@ -208,7 +227,7 @@ class multiDriveStat(): ''' curData = {} for _ in self._stats: - curData[_.name] = _.readWriteSectors() + curData[_.name] = _.readWriteSectors return curData def readWriteBytes( self ) -> dict[str,tuple[int,int]]: @@ -218,7 +237,7 @@ class multiDriveStat(): ''' curData = {} for _ in self._stats: - curData[_.name] = _.readWriteBytes() + curData[_.name] = _.readWriteBytes return curData class CPUInfo: @@ -230,14 +249,26 @@ class CPUInfo: self._cputemp = CPUTemperature() def _cpuModel( self ) -> int: + ''' + Check for the cpu model. Scan cpuinfo to see if we can locate a string that + matches something we are looking for. + + Return: + Model of the Raspberry PI. This treats the Comput Modules the same as + standard model B's + ''' with os.popen( "grep Model /proc/cpuinfo" ) as command: data = command.read() if "Compute Module 5" in data: return 5 - elif "Raspberry Pi 4" in data: - return 4 elif "Raspberry Pi 5" in data: return 5 + elif "Raspberry Pi 4" in data: + return 4 + elif "Compute Module 4" in data: + return 4 + elif "Raspberry Pi 3" in data: + return 3 else: return 0 @@ -382,6 +413,20 @@ class CPULoad: ''' return len(self._previousData) +class CaseFan: + ''' + Class used to monitor a TACH from a case fan. + ''' + def __init__( self, pin=None ): + self.tach = pin + self.rpm = 0 + + def setTACHPin( self, pin = int): + self.tach = pin + + @property + def speed( self ) -> float: + return self.rpm if __name__ == "__main__": @@ -399,6 +444,11 @@ if __name__ == "__main__": print( f"CPU Fan Speed = {cpuinfo.CPUFanSpeed}" ) print( f"CPU Model = {cpuinfo.model}" ) + caseFan = CaseFan( 18 ) + print( f"RPM = {caseFan.speed}" ) + time.sleep(1) + print( f"RPM = {caseFan.speed}" ) + test = multiDriveStat() print( test.drives ) for drive in test.drives: diff --git a/monitor/test.ini b/monitor/test.ini index 2803e19..cac8142 100644 --- a/monitor/test.ini +++ b/monitor/test.ini @@ -3,3 +3,8 @@ [performance] ignore = "mmcblk0" + +[smartctl] + sda="foobar" + sda="duplicate" + sdb='hello'