Files
Arduino/CPU_temp/agent/cpu_temp_agent.ps1
T
oskarvitaliiandClaude Opus 5 59848698ff feat(cpu_temp): индикатор температуры CPU на ESP32-C3 с OLED
Плата принимает по USB-CDC строки "T=67.0;L=34" от агента на ПК и рисует
крупную цифру, загрузку и график за последнюю минуту на экране 72x40.

Пины дисплея (SDA=5, SCL=6) определены сканером, а не по шелкографии:
подписи GPIO8/GPIO9 на плате - это дефолтные Wire-пины чипа.

Агент читает сенсоры через LibreHardwareMonitorLib. С версии 0.9.5 она
требует отдельно установленный драйвер PawnIO: без него загрузка CPU
читается, а температуры пусты даже под администратором.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 21:42:24 +07:00

133 lines
5.7 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
Читает температуру и загрузку CPU и раз в секунду отдаёт их плате ESP32-C3
по USB-COM в виде строки "T=67.0;L=34".
.NOTES
Требует прав администратора: без них драйвер LibreHardwareMonitor не
загружается, и температуры приходят пустыми (загрузка при этом читается —
отсюда ложное ощущение, что всё работает).
#>
#Requires -RunAsAdministrator
[CmdletBinding()]
param(
# Печатать каждую отправленную строку в консоль (для отладки).
[switch]$Echo
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$LhmPath = 'D:\Soft\LibreHardwareMonitor\LibreHardwareMonitorLib.dll'
if (-not (Test-Path $LhmPath)) {
throw "Не найден $LhmPath. Скачайте LibreHardwareMonitor (архив LibreHardwareMonitor.zip, не .NET.10) и распакуйте в D:\Soft\LibreHardwareMonitor"
}
Add-Type -Path $LhmPath
$computer = New-Object LibreHardwareMonitor.Hardware.Computer
$computer.IsCpuEnabled = $true
$computer.Open()
function Get-CpuStats {
foreach ($hw in $computer.Hardware) { $hw.Update() }
$temp = $null
$load = $null
foreach ($hw in $computer.Hardware) {
foreach ($s in $hw.Sensors) {
if ($s.SensorType -eq 'Temperature' -and $s.Name -eq 'CPU Package') { $temp = $s.Value }
if ($s.SensorType -eq 'Load' -and $s.Name -eq 'CPU Total') { $load = $s.Value }
}
}
# Возвращаем обе величины даже неполными: вызывающий код должен уметь
# сказать, какая именно отсутствует, а не валить всё в одно "нет данных".
return @{
Temp = $temp
Load = $load
}
}
# Температура читается только через драйвер ядра. У LibreHardwareMonitor 0.9.5+
# это PawnIO, который ставится отдельным установщиком и в архив с программой не
# входит. Без него загрузка читается (она из счётчиков Windows), а температура —
# нет. Права администратора этого не заменяют.
function Assert-SensorsAvailable {
$probe = Get-CpuStats
if ($null -eq $probe.Temp) {
$hasPawnIo = (Get-CimInstance Win32_SystemDriver | Where-Object { $_.Name -match 'PawnIO' }) -ne $null
Write-Host 'Температура CPU недоступна.' -ForegroundColor Red
if (-not $hasPawnIo) {
Write-Host 'Причина: не установлен драйвер PawnIO (https://pawnio.eu).' -ForegroundColor Red
} else {
Write-Host 'Драйвер PawnIO найден, но значение пустое — запущено без прав администратора?' -ForegroundColor Red
}
throw 'Нет источника температуры'
}
if ($null -eq $probe.Load) { throw 'Недоступна загрузка CPU (сенсор "CPU Total" не найден)' }
}
# Форматирование только через InvariantCulture: в русской локали вышло бы
# "T=67,0", и atof на плате прочитал бы 67, молча потеряв дробную часть.
function Format-StatsLine($stats) {
return [string]::Format([cultureinfo]::InvariantCulture, 'T={0:F1};L={1}', [double]$stats.Temp, [int][math]::Round([double]$stats.Load))
}
# Номер порта не хардкодим: он меняется при перетыкании в другой разъём.
function Find-BoardPort {
Get-CimInstance Win32_PnPEntity |
Where-Object { $_.DeviceID -like 'USB\VID_303A&PID_1001*' -and $_.Name -match '\(COM\d+\)' } |
ForEach-Object { if ($_.Name -match '\((COM\d+)\)') { $Matches[1] } } |
Select-Object -First 1
}
function Open-BoardPort {
$name = Find-BoardPort
if (-not $name) { return $null }
$sp = New-Object System.IO.Ports.SerialPort $name, 115200, 'None', 8, 'One'
# RTS явно выключен: одновременное дёрганье DTR и RTS переводит плату в загрузчик.
$sp.DtrEnable = $true
$sp.RtsEnable = $false
$sp.WriteTimeout = 1000
$sp.NewLine = "`n"
$sp.Open()
Write-Host "connected: $name"
return $sp
}
# Падаем сразу и с внятной причиной, а не крутим бесконечный цикл молча.
Assert-SensorsAvailable
$port = $null
while ($true) {
try {
if ($null -eq $port -or -not $port.IsOpen) {
if ($null -ne $port) { $port.Dispose(); $port = $null }
$port = Open-BoardPort
if ($null -eq $port) { Start-Sleep -Seconds 2; continue }
}
$stats = Get-CpuStats
if ($null -ne $stats.Temp -and $null -ne $stats.Load) {
$line = Format-StatsLine $stats
$port.WriteLine($line)
if ($Echo) { Write-Host $line }
}
elseif ($Echo) {
Write-Host "пропуск: Temp=$($stats.Temp) Load=$($stats.Load)"
}
Start-Sleep -Seconds 1
}
catch {
# Порт исчез: перезагрузка платы, перезаливка прошивки, выдёргивание кабеля.
Write-Host "port lost: $($_.Exception.Message)"
if ($null -ne $port) { try { $port.Dispose() } catch {} ; $port = $null }
Start-Sleep -Seconds 2
}
}