Прошивка её пока не показывает - на 72x40 нет места под третий показатель. Это намеренно: неизвестный ключ прошивка игнорирует, поэтому агента можно обновлять независимо от плат. Память берётся у железа с именем Total Memory - это физическая память. У соседнего Virtual Memory датчик называется точно так же, но считает вместе с файлом подкачки и показывает заниженную цифру: 87% против фактических 97%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
6.5 KiB
PowerShell
146 lines
6.5 KiB
PowerShell
<#
|
||
.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.IsMemoryEnabled = $true
|
||
$computer.Open()
|
||
|
||
function Get-CpuStats {
|
||
foreach ($hw in $computer.Hardware) { $hw.Update() }
|
||
|
||
$temp = $null
|
||
$load = $null
|
||
$mem = $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 }
|
||
# Имя железа проверяем обязательно: у "Virtual Memory" датчик зовётся
|
||
# точно так же, но считает физическую память вместе с файлом подкачки
|
||
# и показывает заниженную цифру.
|
||
if ($hw.Name -eq 'Total Memory' -and $s.SensorType -eq 'Load' -and $s.Name -eq 'Memory') { $mem = $s.Value }
|
||
}
|
||
}
|
||
|
||
# Возвращаем обе величины даже неполными: вызывающий код должен уметь
|
||
# сказать, какая именно отсутствует, а не валить всё в одно "нет данных".
|
||
return @{
|
||
Temp = $temp
|
||
Load = $load
|
||
Mem = $mem
|
||
}
|
||
}
|
||
|
||
# Температура читается только через драйвер ядра. У 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) {
|
||
$line = [string]::Format([cultureinfo]::InvariantCulture, 'T={0:F1};L={1}', [double]$stats.Temp, [int][math]::Round([double]$stats.Load))
|
||
# Память необязательна: если датчика нет, строка просто идёт без ключа M,
|
||
# и прошивка этого не заметит.
|
||
if ($null -ne $stats.Mem) {
|
||
$line += [string]::Format([cultureinfo]::InvariantCulture, ';M={0}', [int][math]::Round([double]$stats.Mem))
|
||
}
|
||
return $line
|
||
}
|
||
|
||
# Номер порта не хардкодим: он меняется при перетыкании в другой разъём.
|
||
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
|
||
}
|
||
}
|