Jump to content
ModemJunki

I fried a computer and made a temperature watchdog for the new one

Recommended Posts

ModemJunki

Edit: I found out from looking into the post from coffeeturtle that not all motherboards support WMI temperature reading. You have to explore the WMI namespaces for your system!

I (stupidly) fried a computer. It was an old Zotac small form factor device that was junked at work, so I turned it into a home theater PC.

But I had put it in my "audio rack", which has glass doors and not very good ventilation. Normally I would open the doors while it was running and I use a media remote which would hibernate the system. At some point, it woke up by itself (probably Windows Update) and did not go to sleep again. Unfortunately this happened during the summer months while I was away on a small holiday and of course the glass doors were closed.

When I finally noticed the system was on, it was super hot and the fan was at full blast. Dead. No POST, no green light on the mainboard even with a new power supply, old mechanical laptop hard drive gives errors when connected to another device. My next home project will be to make some appropriate ventilation in the audio cabinet.

For the replacement I bought a used HP ultra small form factor machine and decided to start working on an app to monitor the ambient temperature sensor on the motherboard and shutdown the system if needed. I used some code for smooth label updates from here (needed updating to work with latest AutoIT) and temperature conversion from here (not really needed, only if you want temperatures in something other than C).

Maybe I will work on this some more and make it configurable with an .INI file or even storing the settings in registry, but since I probably won't I thought I'd put it up as an example of what a non-expert can do with an afternoon of coding and ideas from the community here.

This uses an ambient temperature sensor populated on HP machines in HP-specific WMI extensions but the WMI query can easily be changed to any available sensor including CPU or GPU.

The watchdog monitors temperature, warns with S.O.S beeping if the set point is exceeded, and either shuts down if a timeout is reached while the temperatures are high or goes back to monitoring if the temperature goes lower then the set point, and logs events to the Windows application event logs.

The GUI it puts up is very small at the top center of the screen (very small on a 4k display anyway).

#requireadmin because of the WMI query.

A timer for the WMI query because of prior experience using similar functions in WinPE to get model infos from HP WMI bios extensions.

If you play around with this, please post your fixes/improvements/changes back to this thread.

Have fun!

Edit: Updated to show "Unable to query temperature" if the WMI query returns a null

#AutoIt3Wrapper_Icon=temperature-2-multi-size.ico
#RequireAdmin
#NoTrayIcon
#include <Temperature.au3>
#include <FFLabels.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>

Global $winLogger = "C:\WINDOWS\system32\eventcreate.exe" ; application to use for event logging
Global $MessageSource = "HP TempMon"
Global $width = 185
Global $height = 15
Global $FontSize = 8
Global $FontFamily = 'Microsoft Sans Serif'
Global $normClr = 0xFF000000
Global $warnClr = 0xFF0000
Global $WMInameSpace = "HP_BIOSNumericSensor"
;~ Global $WMIReadSensor = "Chassis Thermal Index" ; HP 8300
Global $WMIReadSensor = "System Ambient Temperature" ; HP Z400
Global $warnTmp = 46.1111111111 ; degrees C
;~ Global $warnTmp = 24 ; degrees C for testing
Global $ovrtmpTime = 5 ; in minutes
Global $hGUI = GUICreate("Board Temperature", $width, $height, -1, 0, BitOR($WS_SYSMENU, $WS_POPUP), BitOR($WS_EX_TOPMOST, $WS_EX_WINDOWEDGE, $WS_EX_TOOLWINDOW))
Global $lb1 = _GUICtrlFFLabel_Create($hGUI, "", -1, -1, $width, $height, 8, -1, 0, 1, $normClr)
GUISetState(@SW_SHOW)

_TempSenseLoop()

Func _TempSenseLoop()
    While 1
        Sleep(250)
        $nMsg = GUIGetMsg()
        Switch $nMsg
            Case $GUI_EVENT_CLOSE
                GUIDelete($hGUI)
                Return
            Case Else
                $s_ambTempC = _doQueryHPNumSens(5, $WMInameSpace, $WMIReadSensor, "100") ; + 100
                Select
                    Case $s_ambTempC = ""
                        _GUICtrlFFLabel_SetTextColor($lb1, $normClr)
                        _GUICtrlFFLabel_SetData($lb1, "Unable to query temperature")
                    Case $s_ambTempC < $warnTmp
                        _GUICtrlFFLabel_SetTextColor($lb1, $normClr)
                        _GUICtrlFFLabel_SetData($lb1, "Board temperature: " & $s_ambTempC & "C" & "/" & Round(_CelsiusToFahrenheit($s_ambTempC), 0) & "F")
                    Case Else
                        _GUICtrlFFLabel_SetTextColor($lb1, $warnClr)
                        _GUICtrlFFLabel_SetData($lb1, "Board temperature: " & $s_ambTempC & "C" & "/" & Round(_CelsiusToFahrenheit($s_ambTempC), 0) & "F")
                        _doLog("System ambient temperature has exceeded " & $warnTmp & "C.", "WARN", $MessageSource)
                        _AlarmMonitor($ovrtmpTime, $warnTmp)
                EndSelect
        EndSwitch
    WEnd
EndFunc   ;==>_TempSenseLoop

Func _AlarmMonitor($min, $tmp)
    Local $Shutdown = 1
    Local $s_ambTemp
    Local $freq = 3500
    Local $shrt = 200
    Local $long = 500
    Local $timer = TimerInit()
    Local $wait = 1000 * ($min * 60)
    While TimerDiff($timer) < $wait
        $s_ambTemp = _doQueryHPNumSens(5, $WMInameSpace, $WMIReadSensor, "100") ; + 100
        If $s_ambTemp >= $tmp Then
            _GUICtrlFFLabel_SetData($lb1, "WARNING! OVERTEMP!")
            Beep($freq, $shrt)
            Beep($freq, $shrt)
            Beep($freq, $shrt)
            Sleep(250)
            Beep($freq, $long)
            Beep($freq, $long)
            Beep($freq, $long)
            Sleep(250)
            Beep($freq, $shrt)
            Beep($freq, $shrt)
            Beep($freq, $shrt)
            _GUICtrlFFLabel_SetData($lb1, "Board temperature: " & $s_ambTemp & "C" & "/" & _CelsiusToFahrenheit($s_ambTemp) & "F")
            Sleep(2000)
        ElseIf $s_ambTemp < $tmp Then
            $Shutdown = 0
            ExitLoop
        EndIf
    WEnd
    If $Shutdown = 1 Then
        _doLog("The system is shutting down due to overtemperature conditions. The temperature at the time of shutdown was " & $s_ambTemp & "C" & "/" & _CelsiusToFahrenheit($s_ambTemp) & "F", "ERRR", $MessageSource)
        Shutdown(24)
    ElseIf $Shutdown = 0 Then
        _doLog("System ambient temperature has been reduced below " & $warnTmp & "C.", "INFO", $MessageSource)
    EndIf
EndFunc   ;==>_AlarmMonitor

Func _doQueryHPNumSens($min, $class, $name, $sleep) ; $sleep = milliseconds
    Local $s_tempReading = ""
    Local $s_HPBiosWMIService = "winmgmts:\\" & @ComputerName & "\Root\HP\InstrumentedBIOS"
    Local $objHPBiosWMIService = ObjGet($s_HPBiosWMIService)
    Local $s_biosQuery = $objHPBiosWMIService.ExecQuery("select * from " & $class & " where Name='" & $name & "'")

    $timer = TimerInit() ; we may need to wait for the HP WMI extensions to enumerate in WMI, in WinPE this takes some minutes.
    $wait = 1000 * ($min * 60)

    If Not IsObj($s_biosQuery) Then ; we do need to wait, put up a splash screen
        $s_WMISplash = 1
        SplashTextOn("WMI", "Probing WMI (up to 10 minutes)...", 300, 50, -1, -1, 1)
        Sleep(1000)
        While TimerDiff($timer) < $wait
            Sleep(500)
            $objHPBiosWMIService = ObjGet($s_HPBiosWMIService)
            If IsObj($objHPBiosWMIService) Then
                Sleep(500)
                $colProdName = $s_biosQuery
                If IsObj($colProdName) Then
                    For $oItem In $colProdName
                        $s_tempReading = $oItem.CurrentReading
                    Next
                EndIf
            EndIf
        WEnd
    ElseIf IsObj($s_biosQuery) Then
        ConsoleWrite($s_biosQuery & @CRLF)
        For $oItem In $s_biosQuery
            $s_tempReading = $oItem.CurrentReading
        Next
    Else
        $s_tempReading = 0
    EndIf
    Sleep($sleep)
    Return $s_tempReading
EndFunc   ;==>_doQueryHPNumSens

Func _doLog($message, $type, $msgsrc)
    Select
        Case $type = "INFO"
            $logTyp = "INFORMATION"
        Case $type = "WARN"
            $logTyp = "WARNING"
        Case $type = "ERRR"
            $logTyp = "ERROR"
    EndSelect

    If FileExists($winLogger) Then
        RunWait(@ComSpec & " /c " & $winLogger & " /L Application /T " & $logTyp & " /SO " & Chr(34) & $msgsrc & Chr(34) & " /ID 1000 /D " & Chr(34) & $message & Chr(34), "", @SW_HIDE)
    EndIf
EndFunc   ;==>_doLog

 

 

 

Temperature.au3

FFLabels.au3

HP_Temp.au3

Edited by ModemJunki
New information

Share this post


Link to post
Share on other sites
coffeeturtle

Very nice work!

 

I have an HP Pavilion - is the output in the screenshot correct?

 

Thanks!

temp.png

Share this post


Link to post
Share on other sites
ModemJunki

 

3 hours ago, coffeeturtle said:

Very nice work!

 

I have an HP Pavilion - is the output in the screenshot correct?

 

Thanks!

temp.png

No, it means the system is not detecting temperature, something for which my simple code does not (yet) have the ability to report.

Did you modify the code to query a different sensor? At my house I have two HP systems, for example, and each uses a different sensor. One of them has a "Chassis Thermal Index" where the other one has a "System Ambient Temperature". At the moment I don't have access to any systems other than HP business class machines (my workplace junks the some of nicest old stuff, the Zotac was a weird find).

Scriptomatic is your friend when looking for WMI namespaces to query! At home I also use WMI Explorer http://www.ks-soft.net/hostmon.eng/downpage.htm#utils (Free for non-commercial use).

What version of Windows are you running?

Edit: Try WMI explorer and see if you can query Root\WMI -> MSAcpi_ThermalZoneTemperature

Maybe it's supported by your mainboard manufacturer.

To connect to the namespace in WMI explorer, select Action->Connect to host/namespace, then click the book icon next to Name space. After a few momente the namespaces load and you can browse for WMI (or, HP!)

Edited by ModemJunki

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Similar Content

    • FrancescoDiMuro
      By FrancescoDiMuro
      Good evening everyone
      I'm working on a little project of mines, and I was trying to use WMI Object.
      The question which I don't find an answer is: 
      Once I do the query with WMI Object, something like "SELECT * FROM Win32_LogonSession", instead of specify the field of the collection returned, ( i.e. $colItems.Caption ), can I loop though each property and each value of the property, writing so one row of code only?
      Hope my question was clear enough.
      Thanks in advance.

      Best Regards.
    • ahmet
      By ahmet
      Hello,
      I am trying to make a program that will uninstall some software, provided by some form of a list. I have this
      ; Generated by AutoIt Scriptomatic June 08, 2010 ;#RequireAdmin $sPartialName="java" $wbemFlagReturnImmediately = 0x10 $wbemFlagForwardOnly = 0x20 $colItems = "" $strComputer = "localhost" ;$objWMIService = ObjGet("winmgmts:\\" & $strComputer & "\root\CIMV2") ;$objWMIService=ObjGet("winmgmts:{impersonationLevel=impersonate}!\\" & @ComputerName & "\root\cimv2") $objWMIService=ObjGet("winmgmts:{impersonationLevel=impersonate}!\\" & @ComputerName & "\root\cimv2") $colItems = $objWMIService.ExecQuery("SELECT * FROM Win32_Product", "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly) If IsObj($colItems) then For $objItem In $colItems If StringInStr($objItem.Name,$sPartialName)=1 Then ConsoleWrite("Full name:" & $objItem.Name & @CRLF) RunAs("USERNAME",@ComputerName,"PASSWORD",0,@ComSpec & " /c" & ' wmic product where name="Java 9.0.4 (64-bit)" call uninstall /nointeractive',"C:\WINDOWS\system32\wbem",@SW_MAXIMIZE) ;Run('wmic product where name="Java 9.0.4 (64-bit)" call uninstall /nointeractive',"",@SW_MAXIMIZE) ExitLoop EndIf Next Else Msgbox(0,"WMI Output","No WMI Objects Found for class: " & "Win32_Product" ) Endif The script above fails uninstalling software despite providing username and password for admin account. If I run script with admin rights then the software gets uninstalled.
      At the following link there is a script by JLogan3o13 but it does not either uninstall software, unless run as admin..
      Is there some way to uninstall software using wim or wimc by providing user name and password?
       
    • Bilgus
      By Bilgus
      #include <Array.Au3> ;WMI EXAMPLE Local $strComputer = "localhost" Local $sClass = "" Local $objClass Local $objWMIService = ObjGet("winmgmts:\\" & $strComputer & "\root\cimv2") ; Display all available Classes in this object _ArrayDisplay(Get_Classes($objWMIService), "AVAILABLE WMI CLASSES", "", 0, Default, "NAME") ;Split string at \n(@LF) place each into an array $sClass = "Win32_BIOS" ;<-The Class I've choosen ;Lets Try out one of the classes Local $objClass = ObjGet("winmgmts:\\" & $strComputer & "\root\cimv2:" & $sClass) ConsoleWrite($sClass & @CRLF & "------------------------------------------" & @CRLF) _ArrayDisplay(Split_Properties(Get_Properties($objClass)), $sClass & " Properties", "", 0, Default, "NAME|VALUE") ;No methods available for this one but... ConsoleWrite("Methods:" & @CRLF & @CRLF) Get_Methods($objClass) ;Lets Do it again for the network adapter Class $sProperties = "" $sClass = "Win32_NetworkAdapterConfiguration" ;<-The Class I've choosen ;Lets Try out one of the classes $objClass = ObjGet("winmgmts:\\" & $strComputer & "\root\cimv2:" & $sClass) ConsoleWrite($sClass & @CRLF & "------------------------------------------" & @CRLF) _ArrayDisplay(Split_Properties(Get_Properties($objClass)), $sClass & " Properties", "", 0, Default, "NAME|VALUE") ConsoleWrite("Methods:" & @CRLF & @CRLF) Get_Methods($objClass) Func Get_Classes($obj) ; Display all available Classes in this object If IsObj($obj) Then Local $sClasses = "" For $objClass In $obj.SubclassesOf() ;<--WMI Method $sClasses &= ($objClass.Path_.Class) & @LF ;Build a string seperated by \n(@LF) Next EndIf Return StringSplit($sClasses, @LF) EndFunc ;==>Get_Classes Func Get_Methods($objClass) Local $sResults = "" If IsObj($objClass) Then For $objMethods In $objClass.Methods_ ConsoleWrite(@TAB & $objMethods.Name & @CRLF) $sResults &= $objMethods.Name & @LF Next EndIf Return $sResults EndFunc ;==>Get_Methods Func Get_Properties($objClass) Local $sProperties = "" If IsObj($objClass) Then For $objClassProp In $objClass.Properties_() ;<-Another WMI Method For $obj In $objWMIService.ExecQuery("Select * from " & $sClass) ;<-Another WMI Method $sProperties &= $objClassProp.Name & @CR $sProperties &= Parse_Value($obj.Properties_($objClassProp.Name).Value) ;Use the Properties_ Method to call our desired property Next Next EndIf Return $sProperties EndFunc ;==>Get_Properties Func Parse_Value($vValue) Local $sRet = "" Switch StringLower(VarGetType($vValue)) Case "keyword" ; Not really sure what this one is probably NULL $sRet = Number($vValue) = 0 ? "" : Number($vValue) Case "array" $sRet = _ArrayToString($vValue, ", ") Case "string", "bool", "int32", "int64", "double", "binary" $sRet = $vValue Case "pointer" $sRet = "[PTR]:" & $vValue Case Else $sRet = "[" & VarGetType($vValue) & "]: " & $vValue EndSwitch $sRet &= @LF Return $sRet EndFunc ;==>Parse_Value Func Split_Properties($sProperties) Local $aDisp = StringSplit($sProperties, @LF) ;Split string at \n(@LF) place each into an array Local $aTmp If IsArray($aDisp) Then _ArrayColInsert($aDisp, 1) For $i = 1 To $aDisp[0][0] ; String Split stores Count in the first element by default $aTmp = StringSplit($aDisp[$i][0], @CR) ;Split string at \r(@CR) place each into an array If IsArray($aTmp) Then $aDisp[$i][0] = $aTmp[0] > 0 ? $aTmp[1] : "?" ;Check if this element exists if not make it "?" $aDisp[$i][1] = $aTmp[0] > 1 ? $aTmp[2] : " " ;Check if this element exists if not make it " " EndIf Next EndIf Return $aDisp EndFunc ;==>Split_Properties  
    • nikink
      By nikink
      Hi all,
      I have a bit of code that works on my old Win10 PC, that fails on my new Win10 PC, and I think the only significant difference is the version of Autoit - old PC has 3.3.12, new has 3.3.14.
      I couldn't find anything mentioned in the change logs though, so perhaps I'm wrong.
      Anyway, the code to replicate my issue is:
      Test('username', 'DOMAIN') ; THIS ERRORS: ;Test('localun', 'DOMAIN') ; THIS ERRORS: ;Test(' ', ' ') ; THIS ERRORS: ;Test('', '') ; THIS ERRORS: ;Test('localun', '') ; THIS ERRORS: ;Test('', 'DOMAIN') Func Test($un, $dom) $compName = 'PCNAME' $FullName = '.' $Description = '.' ; get the WIM object $objWMIService = ObjGet("winmgmts:\\" & $compName & "\root\cimv2") ; get default user full name and description $objAccount = $objWMIService.Get("Win32_UserAccount.Name='" & $un & "',Domain='" & $dom & "'") If IsObj($objAccount) Then $FullName = $objAccount.FullName $Description = $objAccount.Description EndIf ConsoleWrite($FullName & @CRLF) ConsoleWrite($Description & @CRLF) Return EndFunc  
      On my old PC this code will output just . and . for each of those line currently commented out. Which is fine.
      On my new PC any of those commented out lines of code cause an error, and the script won't even compile.
      $objAccount = $objWMIService.Get("Win32_UserAccount.Name='" & $un & "',Domain='" & $dom & "'") $objAccount = $objWMIService^ ERROR I'm very much a newb with the WMI stuff and objects, but it looks like the .Get property is failing when either $un or $dom aren't valid in v3.3.14, whereas in 3.3.12 the .Get would fail to return an object, which is then caught by the If statement.
      Am I on track with this? Is there some new/better way to code the example so that 3.3.14 will compile it?
    • ModemJunki
      By ModemJunki
      Hello,
      In Windows 10 PowerShell, one can do this to change the metric for a NIC in Windows 10:
      Get-NetAdapter | Where-Object -FilterScript {$_.InterfaceAlias -Eq "Ethernet 2"} | Set-NetIPInterface -InterfaceMetric 2 I know I can script the above PowerShell line (and it works!), but I wanted to try something I hadn't done before after looking into jguinch's most excellent Network configuration UDF. I wanted to make use of the SetIPConnectionMetric method in the WMI classes. There is an example VBscript here but this is not for Windows 10. Using AutoIT would also give better control over capturing error return codes than with PowerShell.
      But I cannot get my script to work! The return from SetIPConnectionMetric() is 0, which would indicate success. Yet the change does not happen. I also tried WMI methods using .put_ but this fails.
      Anyone more experienced than I have ideas to make this work?
      #RequireAdmin _SetNicInterfaceMetric2("Ethernet 2", "2") Func _SetNicInterfaceMetric2($NIC_NAME, $METRIC) Local $s_setIndx = 0 $objWMIService = ObjGet("winmgmts:{impersonationLevel = impersonate}!\\" & "." & "\root\cimv2") $colNICItems = $objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID = '" & $NIC_NAME & "'", "WQL") If IsObj($colNICItems) Then For $objItem In $colNICItems $s_nicIndex = $objItem.Index Next ConsoleWrite("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = '" & $s_nicIndex & "'" & @CRLF) $colNAC = $objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = '" & $s_nicIndex & "'", "WQL") If IsObj($colNAC) Then For $objNetCard In $colNAC If $METRIC <> $objNetCard.IPConnectionMetric Then ConsoleWrite("Metric was set to " & $objNetCard.IPConnectionMetric & ". Setting to " & $METRIC & "." & @CRLF) $s_isSet = $objNetCard.SetIPConnectionMetric($METRIC) ConsoleWrite("SetIPConnectionMetric Result = " & $s_isSet & @CRLF) Else ConsoleWrite("Metric is already set to " & $METRIC & @CRLF) EndIf Next EndIf EndIf EndFunc ;==>_SetNicInterfaceMetric2  
×