Merge pull request #14 from JeffCurless/argoneonSupport
Argoneon support
This commit is contained in:
388
argononeup.sh
388
argononeup.sh
@@ -1,388 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "*************"
|
|
||||||
echo " Argon Setup "
|
|
||||||
echo "*************"
|
|
||||||
|
|
||||||
|
|
||||||
# Check time if need to 'fix'
|
|
||||||
NEEDSTIMESYNC=0
|
|
||||||
LOCALTIME=$(date -u +%s%N | cut -b1-10)
|
|
||||||
GLOBALTIME=$(curl -s 'http://worldtimeapi.org/api/ip.txt' | grep unixtime | cut -b11-20)
|
|
||||||
TIMEDIFF=$((GLOBALTIME-LOCALTIME))
|
|
||||||
|
|
||||||
# about 26hrs, max timezone difference
|
|
||||||
if [ $TIMEDIFF -gt 100000 ]
|
|
||||||
then
|
|
||||||
NEEDSTIMESYNC=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
argon_time_error() {
|
|
||||||
echo "**********************************************"
|
|
||||||
echo "* WARNING: Device time seems to be incorrect *"
|
|
||||||
echo "* This may cause problems during setup. *"
|
|
||||||
echo "**********************************************"
|
|
||||||
echo "Possible Network Time Protocol Server issue"
|
|
||||||
echo "Try running the following to correct:"
|
|
||||||
echo " curl -k http://files.iamnet.com.ph/argon/setup/tools/setntpserver.sh | bash"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ $NEEDSTIMESYNC -eq 1 ]
|
|
||||||
then
|
|
||||||
argon_time_error
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Helper variables
|
|
||||||
ARGONDOWNLOADSERVER=http://files.iamnet.com.ph/argon/setup
|
|
||||||
|
|
||||||
INSTALLATIONFOLDER=/etc/argon
|
|
||||||
pythonbin="sudo /usr/bin/python3"
|
|
||||||
|
|
||||||
versioninfoscript=$INSTALLATIONFOLDER/argon-versioninfo.sh
|
|
||||||
|
|
||||||
uninstallscript=$INSTALLATIONFOLDER/argon-uninstall.sh
|
|
||||||
configscript=$INSTALLATIONFOLDER/argon-config
|
|
||||||
unitconfigscript=$INSTALLATIONFOLDER/argon-unitconfig.sh
|
|
||||||
argondashboardscript=$INSTALLATIONFOLDER/argondashboard.py
|
|
||||||
|
|
||||||
|
|
||||||
setupmode="Setup"
|
|
||||||
|
|
||||||
if [ -f $configscript ]
|
|
||||||
then
|
|
||||||
setupmode="Update"
|
|
||||||
echo "Updating files"
|
|
||||||
else
|
|
||||||
sudo mkdir $INSTALLATIONFOLDER
|
|
||||||
sudo chmod 755 $INSTALLATIONFOLDER
|
|
||||||
fi
|
|
||||||
|
|
||||||
##########
|
|
||||||
# Start code lifted from raspi-config
|
|
||||||
# set_config_var based on raspi-config
|
|
||||||
|
|
||||||
if [ -e /boot/firmware/config.txt ] ; then
|
|
||||||
FIRMWARE=/firmware
|
|
||||||
else
|
|
||||||
FIRMWARE=
|
|
||||||
fi
|
|
||||||
CONFIG=/boot${FIRMWARE}/config.txt
|
|
||||||
|
|
||||||
set_config_var() {
|
|
||||||
if ! grep -q -E "$1=$2" $3 ; then
|
|
||||||
echo "$1=$2" | sudo tee -a $3 > /dev/null
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# End code lifted from raspi-config
|
|
||||||
##########
|
|
||||||
|
|
||||||
# Reuse set_config_var
|
|
||||||
set_nvme_default() {
|
|
||||||
set_config_var dtparam nvme $CONFIG
|
|
||||||
set_config_var dtparam=pciex1_gen 3 $CONFIG
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
argon_check_pkg() {
|
|
||||||
RESULT=$(dpkg-query -W -f='${Status}\n' "$1" 2> /dev/null | grep "installed")
|
|
||||||
|
|
||||||
if [ "" == "$RESULT" ]; then
|
|
||||||
echo "NG"
|
|
||||||
else
|
|
||||||
echo "OK"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CHECKDEVICE="oneup" # Hardcoded for argononeup
|
|
||||||
|
|
||||||
CHECKGPIOMODE="libgpiod" # libgpiod or rpigpio
|
|
||||||
|
|
||||||
# Check if Raspbian, Ubuntu, others
|
|
||||||
CHECKPLATFORM="Others"
|
|
||||||
CHECKPLATFORMVERSION=""
|
|
||||||
CHECKPLATFORMVERSIONNUM=""
|
|
||||||
if [ -f "/etc/os-release" ]
|
|
||||||
then
|
|
||||||
source /etc/os-release
|
|
||||||
if [ "$ID" = "raspbian" ]
|
|
||||||
then
|
|
||||||
CHECKPLATFORM="Raspbian"
|
|
||||||
CHECKPLATFORMVERSION=$VERSION_ID
|
|
||||||
elif [ "$ID" = "debian" ]
|
|
||||||
then
|
|
||||||
# For backwards compatibility, continue using raspbian
|
|
||||||
CHECKPLATFORM="Raspbian"
|
|
||||||
CHECKPLATFORMVERSION=$VERSION_ID
|
|
||||||
elif [ "$ID" = "ubuntu" ]
|
|
||||||
then
|
|
||||||
CHECKPLATFORM="Ubuntu"
|
|
||||||
CHECKPLATFORMVERSION=$VERSION_ID
|
|
||||||
fi
|
|
||||||
echo ${CHECKPLATFORMVERSION} | grep -e "\." > /dev/null
|
|
||||||
if [ $? -eq 0 ]
|
|
||||||
then
|
|
||||||
CHECKPLATFORMVERSIONNUM=`cut -d "." -f2 <<< $CHECKPLATFORMVERSION `
|
|
||||||
CHECKPLATFORMVERSION=`cut -d "." -f1 <<< $CHECKPLATFORMVERSION `
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
gpiopkg="python3-libgpiod"
|
|
||||||
if [ "$CHECKGPIOMODE" = "rpigpio" ]
|
|
||||||
then
|
|
||||||
if [ "$CHECKPLATFORM" = "Raspbian" ]
|
|
||||||
then
|
|
||||||
gpiopkg="raspi-gpio python3-rpi.gpio"
|
|
||||||
else
|
|
||||||
gpiopkg="python3-rpi.gpio"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
pkglist=($gpiopkg python3-smbus i2c-tools python3-evdev ddcutil)
|
|
||||||
|
|
||||||
|
|
||||||
for curpkg in ${pkglist[@]}; do
|
|
||||||
sudo apt-get install -y $curpkg
|
|
||||||
RESULT=$(argon_check_pkg "$curpkg")
|
|
||||||
if [ "NG" == "$RESULT" ]
|
|
||||||
then
|
|
||||||
echo "********************************************************************"
|
|
||||||
echo "Please also connect device to the internet and restart installation."
|
|
||||||
echo "********************************************************************"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Ubuntu Mate for RPi has raspi-config too
|
|
||||||
command -v raspi-config &> /dev/null
|
|
||||||
if [ $? -eq 0 ]
|
|
||||||
then
|
|
||||||
# Enable i2c
|
|
||||||
sudo raspi-config nonint do_i2c 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Added to enabled NVMe for pi5
|
|
||||||
set_nvme_default
|
|
||||||
|
|
||||||
# Fan Setup
|
|
||||||
basename="argononeup"
|
|
||||||
daemonname=$basename"d"
|
|
||||||
eepromrpiscript="/usr/bin/rpi-eeprom-config"
|
|
||||||
eepromconfigscript=$INSTALLATIONFOLDER/${basename}-eepromconfig.py
|
|
||||||
daemonscript=$INSTALLATIONFOLDER/$daemonname.py
|
|
||||||
daemonservice=/lib/systemd/system/$daemonname.service
|
|
||||||
|
|
||||||
if [ -f "$eepromrpiscript" ]
|
|
||||||
then
|
|
||||||
# EEPROM Config Script
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/argon-rpi-eeprom-config-psu.py -O $eepromconfigscript --quiet
|
|
||||||
sudo chmod 755 $eepromconfigscript
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Daemon/Service Files
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.py -O $daemonscript --quiet
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/${daemonname}.service -O $daemonservice --quiet
|
|
||||||
sudo chmod 644 $daemonservice
|
|
||||||
|
|
||||||
# Battery Images
|
|
||||||
if [ ! -d "$INSTALLATIONFOLDER/ups" ]
|
|
||||||
then
|
|
||||||
sudo mkdir $INSTALLATIONFOLDER/ups
|
|
||||||
fi
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/ups/upsimg.tar.gz -O $INSTALLATIONFOLDER/ups/upsimg.tar.gz --quiet
|
|
||||||
sudo tar xfz $INSTALLATIONFOLDER/ups/upsimg.tar.gz -C $INSTALLATIONFOLDER/ups/
|
|
||||||
sudo rm -Rf $INSTALLATIONFOLDER/ups/upsimg.tar.gz
|
|
||||||
|
|
||||||
|
|
||||||
# Other utility scripts
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/argondashboard.py -O $INSTALLATIONFOLDER/argondashboard.py --quiet
|
|
||||||
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/argon-versioninfo.sh -O $versioninfoscript --quiet
|
|
||||||
sudo chmod 755 $versioninfoscript
|
|
||||||
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/argonsysinfo.py -O $INSTALLATIONFOLDER/argonsysinfo.py --quiet
|
|
||||||
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/argonregister-v1.py -O $INSTALLATIONFOLDER/argonregister.py --quiet
|
|
||||||
|
|
||||||
|
|
||||||
# Argon Uninstall Script
|
|
||||||
sudo wget $ARGONDOWNLOADSERVER/scripts/argon-uninstall.sh -O $uninstallscript --quiet
|
|
||||||
sudo chmod 755 $uninstallscript
|
|
||||||
|
|
||||||
# Argon Config Script
|
|
||||||
if [ -f $configscript ]; then
|
|
||||||
sudo rm $configscript
|
|
||||||
fi
|
|
||||||
sudo touch $configscript
|
|
||||||
|
|
||||||
# To ensure we can write the following lines
|
|
||||||
sudo chmod 666 $configscript
|
|
||||||
|
|
||||||
echo '#!/bin/bash' >> $configscript
|
|
||||||
|
|
||||||
echo 'echo "--------------------------"' >> $configscript
|
|
||||||
echo 'echo "Argon Configuration Tool"' >> $configscript
|
|
||||||
echo "$versioninfoscript simple" >> $configscript
|
|
||||||
echo 'echo "--------------------------"' >> $configscript
|
|
||||||
|
|
||||||
echo 'get_number () {' >> $configscript
|
|
||||||
echo ' read curnumber' >> $configscript
|
|
||||||
echo ' if [ -z "$curnumber" ]' >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo ' echo "-2"' >> $configscript
|
|
||||||
echo ' return' >> $configscript
|
|
||||||
echo ' elif [[ $curnumber =~ ^[+-]?[0-9]+$ ]]' >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo ' if [ $curnumber -lt 0 ]' >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo ' echo "-1"' >> $configscript
|
|
||||||
echo ' return' >> $configscript
|
|
||||||
echo ' elif [ $curnumber -gt 100 ]' >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo ' echo "-1"' >> $configscript
|
|
||||||
echo ' return' >> $configscript
|
|
||||||
echo ' fi ' >> $configscript
|
|
||||||
echo ' echo $curnumber' >> $configscript
|
|
||||||
echo ' return' >> $configscript
|
|
||||||
echo ' fi' >> $configscript
|
|
||||||
echo ' echo "-1"' >> $configscript
|
|
||||||
echo ' return' >> $configscript
|
|
||||||
echo '}' >> $configscript
|
|
||||||
echo '' >> $configscript
|
|
||||||
|
|
||||||
echo 'mainloopflag=1' >> $configscript
|
|
||||||
echo 'while [ $mainloopflag -eq 1 ]' >> $configscript
|
|
||||||
echo 'do' >> $configscript
|
|
||||||
echo ' echo' >> $configscript
|
|
||||||
echo ' echo "Choose Option:"' >> $configscript
|
|
||||||
|
|
||||||
|
|
||||||
echo ' echo " 1. Get Battery Status"' >> $configscript
|
|
||||||
uninstalloption="3"
|
|
||||||
|
|
||||||
statusoption=$(($uninstalloption-1))
|
|
||||||
echo " echo \" $statusoption. Dashboard\"" >> $configscript
|
|
||||||
|
|
||||||
echo " echo \" $uninstalloption. Uninstall\"" >> $configscript
|
|
||||||
echo ' echo ""' >> $configscript
|
|
||||||
echo ' echo " 0. Exit"' >> $configscript
|
|
||||||
echo " echo -n \"Enter Number (0-$uninstalloption):\"" >> $configscript
|
|
||||||
echo ' newmode=$( get_number )' >> $configscript
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo ' if [ $newmode -eq 0 ]' >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo ' echo "Thank you."' >> $configscript
|
|
||||||
echo ' mainloopflag=0' >> $configscript
|
|
||||||
echo ' elif [ $newmode -eq 1 ]' >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
|
|
||||||
# Option 1
|
|
||||||
echo " $pythonbin $daemonscript GETBATTERY" >> $configscript
|
|
||||||
|
|
||||||
# Standard options
|
|
||||||
echo " elif [ \$newmode -eq $statusoption ]" >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo " $pythonbin $argondashboardscript" >> $configscript
|
|
||||||
|
|
||||||
echo " elif [ \$newmode -eq $uninstalloption ]" >> $configscript
|
|
||||||
echo ' then' >> $configscript
|
|
||||||
echo " $uninstallscript" >> $configscript
|
|
||||||
echo ' mainloopflag=0' >> $configscript
|
|
||||||
echo ' fi' >> $configscript
|
|
||||||
echo 'done' >> $configscript
|
|
||||||
|
|
||||||
sudo chmod 755 $configscript
|
|
||||||
|
|
||||||
# Desktop Icon
|
|
||||||
destfoldername=$USERNAME
|
|
||||||
if [ -z "$destfoldername" ]
|
|
||||||
then
|
|
||||||
destfoldername=$USER
|
|
||||||
fi
|
|
||||||
if [ -z "$destfoldername" ]
|
|
||||||
then
|
|
||||||
destfoldername="pi"
|
|
||||||
fi
|
|
||||||
|
|
||||||
shortcutfile="/home/$destfoldername/Desktop/argononeup.desktop"
|
|
||||||
if [ -d "/home/$destfoldername/Desktop" ]
|
|
||||||
then
|
|
||||||
terminalcmd="lxterminal --working-directory=/home/$destfoldername/ -t"
|
|
||||||
if [ -f "/home/$destfoldername/.twisteros.twid" ]
|
|
||||||
then
|
|
||||||
terminalcmd="xfce4-terminal --default-working-directory=/home/$destfoldername/ -T"
|
|
||||||
fi
|
|
||||||
imagefile=argon40.png
|
|
||||||
sudo wget http://files.iamnet.com.ph/argon/setup/$imagefile -O /etc/argon/$imagefile --quiet
|
|
||||||
if [ -f $shortcutfile ]; then
|
|
||||||
sudo rm $shortcutfile
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create Shortcuts
|
|
||||||
echo "[Desktop Entry]" > $shortcutfile
|
|
||||||
echo "Name=Argon Configuration" >> $shortcutfile
|
|
||||||
echo "Comment=Argon Configuration" >> $shortcutfile
|
|
||||||
echo "Icon=/etc/argon/$imagefile" >> $shortcutfile
|
|
||||||
echo 'Exec='$terminalcmd' "Argon Configuration" -e '$configscript >> $shortcutfile
|
|
||||||
echo "Type=Application" >> $shortcutfile
|
|
||||||
echo "Encoding=UTF-8" >> $shortcutfile
|
|
||||||
echo "Terminal=false" >> $shortcutfile
|
|
||||||
echo "Categories=None;" >> $shortcutfile
|
|
||||||
chmod 755 $shortcutfile
|
|
||||||
fi
|
|
||||||
|
|
||||||
configcmd="$(basename -- $configscript)"
|
|
||||||
|
|
||||||
if [ "$setupmode" = "Setup" ]
|
|
||||||
then
|
|
||||||
if [ -f "/usr/bin/$configcmd" ]
|
|
||||||
then
|
|
||||||
sudo rm /usr/bin/$configcmd
|
|
||||||
fi
|
|
||||||
sudo ln -s $configscript /usr/bin/$configcmd
|
|
||||||
|
|
||||||
# Enable and Start Service(s)
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable argononeupd.service
|
|
||||||
sudo systemctl start argononeupd.service
|
|
||||||
else
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl restart argononeupd.service
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$CHECKPLATFORM" = "Raspbian" ]
|
|
||||||
then
|
|
||||||
if [ -f "$eepromrpiscript" ]
|
|
||||||
then
|
|
||||||
sudo apt-get update && sudo apt-get upgrade -y
|
|
||||||
sudo rpi-eeprom-update
|
|
||||||
# EEPROM Config Script
|
|
||||||
sudo $eepromconfigscript
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "WARNING: EEPROM not updated. Please run this under Raspberry Pi OS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
echo "*********************"
|
|
||||||
echo " $setupmode Completed "
|
|
||||||
echo "*********************"
|
|
||||||
$versioninfoscript
|
|
||||||
echo
|
|
||||||
echo "Use '$configcmd' to configure device"
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if [ $NEEDSTIMESYNC -eq 1 ]
|
|
||||||
then
|
|
||||||
argon_time_error
|
|
||||||
fi
|
|
||||||
|
|
||||||
@@ -22,10 +22,10 @@ class ConfigClass:
|
|||||||
occasionally.
|
occasionally.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if not self.readFile:
|
try:
|
||||||
_result = self.config.read( self.filename )
|
_result = self.config.read( self.filename )
|
||||||
if len(_result) > 0:
|
except Exception as error:
|
||||||
self.readFile = True
|
print( f"{error}" )
|
||||||
|
|
||||||
def getValue( self, section : str, key : str, default="" ) -> str:
|
def getValue( self, section : str, key : str, default="" ) -> str:
|
||||||
'''
|
'''
|
||||||
@@ -41,7 +41,6 @@ class ConfigClass:
|
|||||||
The value of the key from the section read.
|
The value of the key from the section read.
|
||||||
'''
|
'''
|
||||||
value = default
|
value = default
|
||||||
self._openConfig()
|
|
||||||
try:
|
try:
|
||||||
value = self.config[section][key].replace('"','').strip()
|
value = self.config[section][key].replace('"','').strip()
|
||||||
except:
|
except:
|
||||||
@@ -62,7 +61,6 @@ class ConfigClass:
|
|||||||
a List of items
|
a List of items
|
||||||
'''
|
'''
|
||||||
value = default
|
value = default
|
||||||
self._openConfig()
|
|
||||||
try:
|
try:
|
||||||
temp = self.config[section][name]
|
temp = self.config[section][name]
|
||||||
value = [ n.replace('"','').strip() for n in temp.split(",")]
|
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.getValueAsList( 'temperature', 'ignore' )}" )
|
||||||
print( f"Value = {cfg.getValue( 'performance', 'ignore' )}" )
|
print( f"Value = {cfg.getValue( 'performance', 'ignore' )}" )
|
||||||
print( f"Value = {cfg.getValueAsList( 'performance', 'ignore' )}" )
|
print( f"Value = {cfg.getValueAsList( 'performance', 'ignore' )}" )
|
||||||
|
|
||||||
|
drive = cfg.getValue( 'smartctl', 'sda' )
|
||||||
|
print( f"drive = {drive}" )
|
||||||
|
|
||||||
cfg = ConfigClass( "missingfile.ini" )
|
cfg = ConfigClass( "missingfile.ini" )
|
||||||
|
|
||||||
|
|||||||
46
monitor/fanspeed.py
Normal file
46
monitor/fanspeed.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
|
||||||
|
WAIT_TIME = 1
|
||||||
|
|
||||||
|
class GetCaseFanSpeed:
|
||||||
|
TACH = 18
|
||||||
|
PULSE = 2
|
||||||
|
def __init__( self, tachPin = TACH):
|
||||||
|
self._tachPin = tachPin
|
||||||
|
self._rpm = 0
|
||||||
|
self._t = time.time()
|
||||||
|
self.GPIO = GPIO
|
||||||
|
self.GPIO.setmode( self.GPIO.BCM )
|
||||||
|
self.GPIO.setwarnings( False )
|
||||||
|
self.GPIO.setup( self._tachPin, self.GPIO.IN, pull_up_down=self.GPIO.PUD_UP )
|
||||||
|
self.GPIO.add_event_detect( self._tachPin, self.GPIO.FALLING, self._calcRPM )
|
||||||
|
|
||||||
|
def __del__( self ):
|
||||||
|
self.GPIO.cleanup()
|
||||||
|
|
||||||
|
def _calcRPM( self, n ):
|
||||||
|
dt = time.time() - self._t
|
||||||
|
if dt < 0.005: return # Reject spuriously short pulses
|
||||||
|
|
||||||
|
freq = 1 / dt
|
||||||
|
self._rpm = (freq / GetCaseFanSpeed.PULSE) * 60
|
||||||
|
self._t = time.time()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def RPM( self ):
|
||||||
|
return self._rpm
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
fanSpeed = GetCaseFanSpeed()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i in range( 10 ):
|
||||||
|
print( f"{fanSpeed.RPM:.0f} RPM" )
|
||||||
|
time.sleep( 2 )
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
|
||||||
@@ -3,27 +3,34 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Application that monitors current CPU and Drive temp, along with fan speed and IO utilization
|
Application that monitors current CPU and Drive temp, along with fan speed and IO utilization
|
||||||
Requires: PyQt5 (including QtCharts)
|
Requires: PyQt6 (including QtCharts)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from systemsupport import CPUInfo, CPULoad, multiDriveStat
|
from systemsupport import CPUInfo, CPULoad, multiDriveStat, NetworkLoad
|
||||||
|
import gc
|
||||||
from configfile import ConfigClass
|
from configfile import ConfigClass
|
||||||
|
from fanspeed import GetCaseFanSpeed
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
# Globals
|
# Globals
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|
||||||
|
MIN_WIDTH = 1000
|
||||||
|
MIN_HEIGHT = 800
|
||||||
|
|
||||||
|
DATA_WINDOW = 60
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
# UI
|
# UI
|
||||||
# --------------------------
|
# --------------------------
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt, QTimer
|
||||||
from PyQt5.QtGui import QPainter
|
from PyQt6.QtGui import QPainter
|
||||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
|
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
|
||||||
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QValueAxis
|
from PyQt6.QtCharts import QChart, QChartView, QLineSeries, QValueAxis
|
||||||
from PyQt5 import QtGui
|
from PyQt6 import QtGui
|
||||||
|
|
||||||
class RollingChart(QWidget):
|
class RollingChart(QWidget):
|
||||||
'''
|
'''
|
||||||
@@ -35,16 +42,16 @@ class RollingChart(QWidget):
|
|||||||
y_min,y_max - Fixed Y axis range.
|
y_min,y_max - Fixed Y axis range.
|
||||||
window - Number of points to keep (points are 1 per tick by default).
|
window - Number of points to keep (points are 1 per tick by default).
|
||||||
'''
|
'''
|
||||||
def __init__(self, title: str, series_defs: list[tuple], y_min: float, y_max: float, window: int = 120, parent=None):
|
def __init__(self, title: str, series_defs: list[tuple], y_min: float, y_max: float, window: int = DATA_WINDOW, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self.title = title
|
||||||
self.pointWindow = window
|
self.pointWindow = window
|
||||||
self.xpos = window - 1
|
self.xpos = window - 1
|
||||||
self.chart = QChart()
|
self.chart = QChart()
|
||||||
|
|
||||||
self.chart.setTitle(title)
|
self.chart.setTitle(title)
|
||||||
self.chart.legend().setVisible(len(series_defs) > 1)
|
self.chart.legend().setVisible(len(series_defs) > 1)
|
||||||
self.chart.legend().setAlignment(Qt.AlignBottom)
|
self.chart.legend().setAlignment(Qt.AlignmentFlag.AlignBottom)
|
||||||
|
|
||||||
self.series:list[QLineSeries] = []
|
self.series:list[QLineSeries] = []
|
||||||
for name, color in series_defs:
|
for name, color in series_defs:
|
||||||
@@ -70,8 +77,8 @@ class RollingChart(QWidget):
|
|||||||
self.axis_y.setRange(y_min, y_max)
|
self.axis_y.setRange(y_min, y_max)
|
||||||
self.axis_y.setLabelFormat( "%d" )
|
self.axis_y.setLabelFormat( "%d" )
|
||||||
|
|
||||||
self.chart.addAxis(self.axis_x, Qt.AlignBottom)
|
self.chart.addAxis(self.axis_x, Qt.AlignmentFlag.AlignBottom)
|
||||||
self.chart.addAxis(self.axis_y, Qt.AlignLeft)
|
self.chart.addAxis(self.axis_y, Qt.AlignmentFlag.AlignLeft)
|
||||||
|
|
||||||
for s in self.series:
|
for s in self.series:
|
||||||
s.attachAxis(self.axis_x)
|
s.attachAxis(self.axis_x)
|
||||||
@@ -113,7 +120,7 @@ class RollingChart(QWidget):
|
|||||||
for s in self.series:
|
for s in self.series:
|
||||||
# Efficient trim: remove points with x < min_x_to_keep
|
# Efficient trim: remove points with x < min_x_to_keep
|
||||||
# QLineSeries doesn't provide O(1) pop from front, so we rebuild if large
|
# QLineSeries doesn't provide O(1) pop from front, so we rebuild if large
|
||||||
points = s.pointsVector()
|
points = s.points()
|
||||||
if points and points[0].x() < min_x_to_keep:
|
if points and points[0].x() < min_x_to_keep:
|
||||||
# binary search for first index >= min_x_to_keep
|
# binary search for first index >= min_x_to_keep
|
||||||
lo, hi = 0, len(points)
|
lo, hi = 0, len(points)
|
||||||
@@ -169,7 +176,7 @@ class scaleValues:
|
|||||||
|
|
||||||
|
|
||||||
class RollingChartDynamic(RollingChart):
|
class RollingChartDynamic(RollingChart):
|
||||||
def __init__(self, title : str, series_defs: list[tuple], range_y : list[tuple], window=120,parent=None):
|
def __init__(self, title : str, series_defs: list[tuple], range_y : list[tuple], window=DATA_WINDOW,parent=None):
|
||||||
self.maxY = 512
|
self.maxY = 512
|
||||||
super().__init__(title,series_defs,0,self.maxY,window,parent)
|
super().__init__(title,series_defs,0,self.maxY,window,parent)
|
||||||
self.title = title
|
self.title = title
|
||||||
@@ -183,6 +190,7 @@ class RollingChartDynamic(RollingChart):
|
|||||||
if value < i:
|
if value < i:
|
||||||
return i
|
return i
|
||||||
return 4
|
return 4
|
||||||
|
|
||||||
def append(self, values: list[float]):
|
def append(self, values: list[float]):
|
||||||
'''
|
'''
|
||||||
Append one sample (for each series) at the next x value. Handles rolling window.
|
Append one sample (for each series) at the next x value. Handles rolling window.
|
||||||
@@ -222,7 +230,7 @@ class RollingChartDynamic(RollingChart):
|
|||||||
maxV = 0
|
maxV = 0
|
||||||
for s in self.series:
|
for s in self.series:
|
||||||
drop = 0
|
drop = 0
|
||||||
points = s.pointsVector()
|
points = s.points()
|
||||||
for index, point in enumerate(points):
|
for index, point in enumerate(points):
|
||||||
if point.x() < min_x_to_keep:
|
if point.x() < min_x_to_keep:
|
||||||
drop = index
|
drop = index
|
||||||
@@ -240,7 +248,7 @@ class RollingChartDynamic(RollingChart):
|
|||||||
self.scale.prevScale()
|
self.scale.prevScale()
|
||||||
self.chart.setTitle( self.title + f" ({self.scale.name})")
|
self.chart.setTitle( self.title + f" ({self.scale.name})")
|
||||||
for s in self.series:
|
for s in self.series:
|
||||||
points = s.pointsVector()
|
points = s.points()
|
||||||
for point in points:
|
for point in points:
|
||||||
point.setY( self.scale.scaleUp(point.y()))
|
point.setY( self.scale.scaleUp(point.y()))
|
||||||
s.replace(points)
|
s.replace(points)
|
||||||
@@ -256,21 +264,26 @@ class MonitorWindow(QMainWindow):
|
|||||||
is a data refresh period.
|
is a data refresh period.
|
||||||
Parent - Owning parent of this window... default is None.
|
Parent - Owning parent of this window... default is None.
|
||||||
'''
|
'''
|
||||||
def __init__(self, refresh_ms: int = 1000, window = 120, parent=None):
|
def __init__(self, refresh_ms: int = 1000, keepWindow = DATA_WINDOW, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
# Get all the filters loaded
|
# Get all the filters loaded
|
||||||
self.config = ConfigClass("/etc/sysmon.ini")
|
self.config = ConfigClass("/etc/sysmon.ini")
|
||||||
self.driveTempFilter = self.config.getValueAsList( 'temperature', 'ignore' )
|
self.driveTempFilter = self.config.getValueAsList( 'drive', 'temp_ignore' )
|
||||||
self.drivePerfFilter = self.config.getValueAsList( 'performance', 'ignore' )
|
self.drivePerfFilter = self.config.getValueAsList( 'drive', 'perf_ignore' )
|
||||||
|
|
||||||
# Get supporting objects
|
# Get supporting objects
|
||||||
self.cpuinfo = CPUInfo()
|
self.cpuinfo = CPUInfo()
|
||||||
self.cpuload = CPULoad()
|
self.cpuload = CPULoad()
|
||||||
|
self.caseFanPin = self.config.getValue( 'cooling', 'casefan',None )
|
||||||
|
if self.caseFanPin is None :
|
||||||
|
self.caseFan = None
|
||||||
|
else:
|
||||||
|
self.caseFan = GetCaseFanSpeed( int(self.caseFanPin) )
|
||||||
self.multiDrive = multiDriveStat()
|
self.multiDrive = multiDriveStat()
|
||||||
|
|
||||||
self.setWindowTitle("System Monitor")
|
self.setWindowTitle("System Monitor")
|
||||||
self.setMinimumSize(900, 900)
|
self.setMinimumSize(MIN_WIDTH, MIN_HEIGHT)
|
||||||
|
|
||||||
central = QWidget(self)
|
central = QWidget(self)
|
||||||
grid = QGridLayout(central)
|
grid = QGridLayout(central)
|
||||||
@@ -284,7 +297,7 @@ class MonitorWindow(QMainWindow):
|
|||||||
title="CPU Utilization",
|
title="CPU Utilization",
|
||||||
series_defs=[ (name, None) for name in self.cpuload.cpuNames ],
|
series_defs=[ (name, None) for name in self.cpuload.cpuNames ],
|
||||||
y_min=0, y_max=100,
|
y_min=0, y_max=100,
|
||||||
window=120
|
window=keepWindow
|
||||||
)
|
)
|
||||||
|
|
||||||
series = [("CPU", None)]
|
series = [("CPU", None)]
|
||||||
@@ -296,15 +309,24 @@ class MonitorWindow(QMainWindow):
|
|||||||
title="Temperature (°C)",
|
title="Temperature (°C)",
|
||||||
series_defs= series,
|
series_defs= series,
|
||||||
y_min=20, y_max=80,
|
y_min=20, y_max=80,
|
||||||
window=window
|
window=keepWindow
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.cpuinfo.model == 5:
|
||||||
|
self.caseFanPin = self.config.getValue( "cooling", "casefan", None )
|
||||||
|
if self.caseFanPin is None:
|
||||||
|
series = [("CPU",None)]
|
||||||
|
else:
|
||||||
|
series = [("CPU",None),("CaseFan",None)]
|
||||||
|
|
||||||
self.fan_chart = RollingChart(
|
self.fan_chart = RollingChart(
|
||||||
title="Fan Speed",
|
title="Fan Speed",
|
||||||
series_defs=[("RPM",None)],
|
series_defs=[("RPM",None)],
|
||||||
y_min=0,y_max=6000,
|
y_min=0,y_max=6000,
|
||||||
window=window
|
window=keepWindow
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.fan_chart = None
|
||||||
|
|
||||||
series = []
|
series = []
|
||||||
for name in self.multiDrive.drives:
|
for name in self.multiDrive.drives:
|
||||||
@@ -315,15 +337,47 @@ class MonitorWindow(QMainWindow):
|
|||||||
self.io_chart = RollingChartDynamic(
|
self.io_chart = RollingChartDynamic(
|
||||||
title="Disk I/O",
|
title="Disk I/O",
|
||||||
series_defs=series,
|
series_defs=series,
|
||||||
range_y=[("Bytes/s", 1),("KiB/s",1024),("MiB/s", 1024*1024),("GiB/s",1024*1024*1024)],
|
range_y=[("Bytes/s", 1),("KiB/s", 1024),("MiB/s", 1024*1024),("GiB/s", 1024*1024*1024)],
|
||||||
window=window,
|
window=keepWindow,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.networkFilter = self.config.getValueAsList( 'network', 'device_ignore')
|
||||||
|
self.network = NetworkLoad(self.networkFilter)
|
||||||
|
series = []
|
||||||
|
for name in self.network.names:
|
||||||
|
series.append( (f"{name} Read", None) )
|
||||||
|
series.append( (f"{name} Write", None) )
|
||||||
|
|
||||||
|
self.network_chart = RollingChartDynamic(
|
||||||
|
title="Network I/O",
|
||||||
|
series_defs=series,
|
||||||
|
range_y=[("Bytes/s", 1),("KiB/s", 1024),("MiB/s", 1024*1024),("GiB/s", 1024*1024*1024)],
|
||||||
|
window=keepWindow,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.networkFilter = self.config.getValueAsList( 'network', 'device_ignore')
|
||||||
|
self.network = NetworkLoad(self.networkFilter)
|
||||||
|
series = []
|
||||||
|
for name in self.network.names:
|
||||||
|
series.append( (f"{name} Read", None) )
|
||||||
|
series.append( (f"{name} Write", None) )
|
||||||
|
|
||||||
|
self.network_chart = RollingChartDynamic(
|
||||||
|
title="Network I/O",
|
||||||
|
series_defs=series,
|
||||||
|
range_y=[("Bytes/s", 1),("KiB/s", 1024),("MiB/s", 1024*1024),("GiB/s", 1024*1024*1024)],
|
||||||
|
window=keepWindow,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Layout: 2x2 grid (CPU, NVMe on top; IO full width bottom)
|
# Layout: 2x2 grid (CPU, NVMe on top; IO full width bottom)
|
||||||
grid.addWidget(self.use_chart, 0, 0, 1, 2 )
|
grid.addWidget(self.use_chart, 0, 0, 1, 2 )
|
||||||
grid.addWidget(self.io_chart, 1, 0, 1, 2 )
|
grid.addWidget(self.io_chart, 1, 0, 1, 2 )
|
||||||
grid.addWidget(self.cpu_chart, 2, 0, 1, 1 )
|
grid.addWidget(self.network_chart, 2, 0, 1, 2 )
|
||||||
grid.addWidget(self.fan_chart, 2, 1, 1, 1 )
|
if self.fan_chart:
|
||||||
|
grid.addWidget(self.cpu_chart, 3, 0, 1, 1 )
|
||||||
|
grid.addWidget(self.fan_chart, 3, 1, 1, 1 )
|
||||||
|
else:
|
||||||
|
grid.addWidget(self.cpu_chart, 3, 0, 1, 2 )
|
||||||
|
|
||||||
# Get the initial information from the syste
|
# Get the initial information from the syste
|
||||||
self.refresh_metrics()
|
self.refresh_metrics()
|
||||||
@@ -338,22 +392,30 @@ class MonitorWindow(QMainWindow):
|
|||||||
This routine is called periodically, as setup in the __init__ functon. Since this
|
This routine is called periodically, as setup in the __init__ functon. Since this
|
||||||
routine calls out to other things, we want to make sure that there is no possible
|
routine calls out to other things, we want to make sure that there is no possible
|
||||||
exception, so everything needs to be wrapped in a handler.
|
exception, so everything needs to be wrapped in a handler.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
# Obtain the current fan speed
|
# Obtain the current fan speed
|
||||||
try:
|
if self.cpuinfo.model == 5:
|
||||||
fan_speed = self.cpuinfo.CPUFanSpeed
|
try:
|
||||||
except Exception:
|
if self.caseFanPin:
|
||||||
fan_speed = None
|
fan_speed = [self.cpuinfo.CPUFanSpeed,self.caseFan.RPM]
|
||||||
|
else:
|
||||||
|
fan_speed = [self.cpuinfo.CPUFanSpeed]
|
||||||
|
except Exception as e:
|
||||||
|
print( f"error getting fan speed: {e}" )
|
||||||
|
fan_speed = [None,None]
|
||||||
|
else:
|
||||||
|
fan_speed = [None,None]
|
||||||
|
|
||||||
# Setup the temperature for the CPU and Drives
|
# Setup the temperature for the CPU and Drives
|
||||||
temperatures = []
|
temperatures = []
|
||||||
try:
|
try:
|
||||||
temperatures.append( float(self.cpuinfo.temperature) )
|
temperatures.append( float(self.cpuinfo.temperature) )
|
||||||
except Exception:
|
except Exception:
|
||||||
temperatures.append( 0.0 )
|
temperatures.append( 0.0 )
|
||||||
|
|
||||||
# Obtain the drive temperatures
|
# Obtain the drive temperatures
|
||||||
try:
|
try:
|
||||||
for _drive in self.multiDrive.drives:
|
for _drive in self.multiDrive.drives:
|
||||||
@@ -374,6 +436,16 @@ class MonitorWindow(QMainWindow):
|
|||||||
except Exception :
|
except Exception :
|
||||||
rwData = [ None, None ]
|
rwData = [ None, None ]
|
||||||
|
|
||||||
|
# obtain network device read and writes rates
|
||||||
|
try:
|
||||||
|
netData = []
|
||||||
|
networks = self.network.stats
|
||||||
|
for network in networks:
|
||||||
|
netData.append( float( networks[network][0]))
|
||||||
|
netData.append( float( networks[network][1]))
|
||||||
|
except Exception:
|
||||||
|
netData = [None,None]
|
||||||
|
|
||||||
# Get the CPU load precentages
|
# Get the CPU load precentages
|
||||||
try:
|
try:
|
||||||
p = self.cpuload.getPercentages()
|
p = self.cpuload.getPercentages()
|
||||||
@@ -383,16 +455,18 @@ class MonitorWindow(QMainWindow):
|
|||||||
|
|
||||||
# Append to charts
|
# Append to charts
|
||||||
self.cpu_chart.append( temperatures )
|
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.io_chart.append( rwData )
|
||||||
|
self.network_chart.append( netData )
|
||||||
self.use_chart.append( values )
|
self.use_chart.append( values )
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
gc.enable()
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
w = MonitorWindow(refresh_ms=1000)
|
w = MonitorWindow(refresh_ms=1000)
|
||||||
w.show()
|
w.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
33
monitor/sysmon.ini
Normal file
33
monitor/sysmon.ini
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#
|
||||||
|
# For drives, you can ignore a device from collecting the temperature,
|
||||||
|
# or the performance data. A good device to ignore for temperature
|
||||||
|
# collection is the mmcblk0 device, as no temp available. Also
|
||||||
|
# in a system using RAID, the temperature is not available.
|
||||||
|
#
|
||||||
|
[drive]
|
||||||
|
temp_ignore = mmcblk
|
||||||
|
perf_ignore = mmcblk
|
||||||
|
|
||||||
|
#
|
||||||
|
# When monitorin the network, you can ignore speciic devices. On
|
||||||
|
# the Argon40 OneUP there is an eth0 thats available but not connected
|
||||||
|
# to anything, so that an lo are good to ignore
|
||||||
|
#
|
||||||
|
[network]
|
||||||
|
device_ignore = "lo,eth0"
|
||||||
|
|
||||||
|
#
|
||||||
|
# if your system has a separate case fan, you can montor the speed if
|
||||||
|
# it has a tachometer attached to a GPIO pin
|
||||||
|
#
|
||||||
|
#[cooling]
|
||||||
|
# casefan = 18
|
||||||
|
|
||||||
|
#
|
||||||
|
# In the rare case there is an additional smartctl command option needed
|
||||||
|
# for a speciic drive, you can add that here.
|
||||||
|
#
|
||||||
|
#[smartctl]
|
||||||
|
# drive = extra_smartctl_command
|
||||||
|
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ class DriveStats:
|
|||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self._device
|
return self._device
|
||||||
|
|
||||||
|
@property
|
||||||
def readAllStats( self ) -> list[int]:
|
def readAllStats( self ) -> list[int]:
|
||||||
'''
|
'''
|
||||||
read all of the drive statisics from sysfs for the device.
|
read all of the drive statisics from sysfs for the device.
|
||||||
@@ -93,19 +94,24 @@ class DriveStats:
|
|||||||
'''
|
'''
|
||||||
return self._getStats()
|
return self._getStats()
|
||||||
|
|
||||||
|
@property
|
||||||
def readSectors( self )-> int:
|
def readSectors( self )-> int:
|
||||||
return self._getStats()[DriveStats.READ_SECTORS]
|
return self._getStats()[DriveStats.READ_SECTORS]
|
||||||
|
|
||||||
|
@property
|
||||||
def writeSectors( self ) -> int:
|
def writeSectors( self ) -> int:
|
||||||
return self._getStats()[DriveStats.WRITE_SECTORS]
|
return self._getStats()[DriveStats.WRITE_SECTORS]
|
||||||
|
|
||||||
|
@property
|
||||||
def discardSectors( self ) -> int:
|
def discardSectors( self ) -> int:
|
||||||
return self._getStats()[DriveStats.DISCARD_SECTORS]
|
return self._getStats()[DriveStats.DISCARD_SECTORS]
|
||||||
|
|
||||||
|
@property
|
||||||
def readWriteSectors( self ) -> tuple[int,int]:
|
def readWriteSectors( self ) -> tuple[int,int]:
|
||||||
curData = self._getStats()
|
curData = self._getStats()
|
||||||
return (curData[DriveStats.READ_SECTORS],curData[DriveStats.WRITE_SECTORS])
|
return (curData[DriveStats.READ_SECTORS],curData[DriveStats.WRITE_SECTORS])
|
||||||
|
|
||||||
|
@property
|
||||||
def readWriteBytes( self ) -> tuple[int,int]:
|
def readWriteBytes( self ) -> tuple[int,int]:
|
||||||
curData = self._getStats()
|
curData = self._getStats()
|
||||||
return (curData[DriveStats.READ_SECTORS]*512,curData[DriveStats.WRITE_SECTORS]*512)
|
return (curData[DriveStats.READ_SECTORS]*512,curData[DriveStats.WRITE_SECTORS]*512)
|
||||||
@@ -168,6 +174,19 @@ class multiDriveStat():
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def driveTemp(self,_drive:str, extracmd = None) -> float:
|
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 = ""
|
smartOutRaw = ""
|
||||||
if extracmd is None:
|
if extracmd is None:
|
||||||
cmd = f'sudo smartctl -A /dev/{_drive}'
|
cmd = f'sudo smartctl -A /dev/{_drive}'
|
||||||
@@ -208,7 +227,7 @@ class multiDriveStat():
|
|||||||
'''
|
'''
|
||||||
curData = {}
|
curData = {}
|
||||||
for _ in self._stats:
|
for _ in self._stats:
|
||||||
curData[_.name] = _.readWriteSectors()
|
curData[_.name] = _.readWriteSectors
|
||||||
return curData
|
return curData
|
||||||
|
|
||||||
def readWriteBytes( self ) -> dict[str,tuple[int,int]]:
|
def readWriteBytes( self ) -> dict[str,tuple[int,int]]:
|
||||||
@@ -218,7 +237,7 @@ class multiDriveStat():
|
|||||||
'''
|
'''
|
||||||
curData = {}
|
curData = {}
|
||||||
for _ in self._stats:
|
for _ in self._stats:
|
||||||
curData[_.name] = _.readWriteBytes()
|
curData[_.name] = _.readWriteBytes
|
||||||
return curData
|
return curData
|
||||||
|
|
||||||
class CPUInfo:
|
class CPUInfo:
|
||||||
@@ -229,6 +248,34 @@ class CPUInfo:
|
|||||||
def __init__( self ):
|
def __init__( self ):
|
||||||
self._cputemp = CPUTemperature()
|
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 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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model( self ) -> int:
|
||||||
|
return self._cpuModel()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature( self ) -> float:
|
def temperature( self ) -> float:
|
||||||
'''
|
'''
|
||||||
@@ -249,15 +296,17 @@ class CPUInfo:
|
|||||||
Return:
|
Return:
|
||||||
The fanspeed as a floating point number
|
The fanspeed as a floating point number
|
||||||
'''
|
'''
|
||||||
speed= 0
|
speed = 0
|
||||||
try:
|
if self._cpuModel() == 5:
|
||||||
command = os.popen( 'cat /sys/devices/platform/cooling_fan/hwmon/*/fan1_input' )
|
try:
|
||||||
speed = int( command.read().strip())
|
command = os.popen( 'cat /sys/devices/platform/cooling_fan/hwmon/*/fan1_input' )
|
||||||
except Exception as error:
|
speed = int( command.read().strip())
|
||||||
print( f"Could not determine fan speed, error {error}" )
|
except Exception as error:
|
||||||
finally:
|
print( f"Could not determine fan speed, error {error}" )
|
||||||
command.close()
|
finally:
|
||||||
|
command.close()
|
||||||
|
else:
|
||||||
|
speed = 0
|
||||||
return float(speed)
|
return float(speed)
|
||||||
|
|
||||||
class CPULoad:
|
class CPULoad:
|
||||||
@@ -363,8 +412,49 @@ class CPULoad:
|
|||||||
Number of CPU's
|
Number of CPU's
|
||||||
'''
|
'''
|
||||||
return len(self._previousData)
|
return len(self._previousData)
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkLoad:
|
||||||
|
def __init__(self, networkIgnoreList : list[str]=[]):
|
||||||
|
self._networks = []
|
||||||
|
with os.popen( 'ls -1 /sys/class/net') as command:
|
||||||
|
net_raw = command.read()
|
||||||
|
for l in net_raw.split('\n'):
|
||||||
|
if len(l) == 0:
|
||||||
|
continue
|
||||||
|
if not l in networkIgnoreList:
|
||||||
|
self._networks.append( l )
|
||||||
|
self.prevStats = {}
|
||||||
|
for net in self._networks:
|
||||||
|
self.prevStats[net] = self._getData(net)
|
||||||
|
@property
|
||||||
|
def names( self ):
|
||||||
|
return self._networks
|
||||||
|
|
||||||
|
def _getData( self, name : str ) -> tuple[int,int]:
|
||||||
|
readData = 0
|
||||||
|
writeData = 0
|
||||||
|
try:
|
||||||
|
with open( f"/sys/class/net/{name}/statistics/rx_bytes" ) as f:
|
||||||
|
readData = f.read().strip()
|
||||||
|
with open( f"/sys/class/net/{name}/statistics/tx_bytes" ) as f:
|
||||||
|
writeData = f.read().strip()
|
||||||
|
except Exception as e:
|
||||||
|
print( f"Error {e}" )
|
||||||
|
|
||||||
|
#print( f"{readData} {writeData}" )
|
||||||
|
return (int(readData), int(writeData))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stats(self) -> dict[tuple[int,int]]:
|
||||||
|
data = {}
|
||||||
|
curstats = {}
|
||||||
|
for net in self._networks:
|
||||||
|
curstats[net] = self._getData( net )
|
||||||
|
data[net] = ((curstats[net][0] - self.prevStats[net][0]),
|
||||||
|
(curstats[net][1] - self.prevStats[net][1]))
|
||||||
|
self.prevStats = curstats
|
||||||
|
return data
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
load = CPULoad()
|
load = CPULoad()
|
||||||
@@ -378,11 +468,17 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
cpuinfo = CPUInfo()
|
cpuinfo = CPUInfo()
|
||||||
print( f"CPU Temperature = {cpuinfo.temperature}" )
|
print( f"CPU Temperature = {cpuinfo.temperature}" )
|
||||||
print( f"CPU Fan Speed = {cpuinfo.CPUFanSpeed}" )
|
print( f"CPU Fan Speed = {cpuinfo.CPUFanSpeed}" )
|
||||||
|
print( f"CPU Model = {cpuinfo.model}" )
|
||||||
|
|
||||||
test = multiDriveStat()
|
test = multiDriveStat()
|
||||||
print( test.drives )
|
print( test.drives )
|
||||||
for drive in test.drives:
|
for drive in test.drives:
|
||||||
print( f"Drive {drive} size is {test.driveSize( drive )}" )
|
print( f"Drive {drive} size is {test.driveSize( drive )}" )
|
||||||
print( test.readWriteSectors() )
|
print( test.readWriteSectors() )
|
||||||
|
|
||||||
|
network = NetworkLoad( ['lo','eth0'])
|
||||||
|
print( f"Networks Available: {network.names}" )
|
||||||
|
while True:
|
||||||
|
print( f"Stats = {network.stats}" )
|
||||||
|
time.sleep(1)
|
||||||
|
|||||||
@@ -3,3 +3,8 @@
|
|||||||
|
|
||||||
[performance]
|
[performance]
|
||||||
ignore = "mmcblk0"
|
ignore = "mmcblk0"
|
||||||
|
|
||||||
|
[smartctl]
|
||||||
|
sda="foobar"
|
||||||
|
sda="duplicate"
|
||||||
|
sdb='hello'
|
||||||
|
|||||||
Reference in New Issue
Block a user