feat(ui): сглаживать температуру скользящим средним за 5 замеров

Мгновенное значение плавает на +-1 градус, и цифра мельтешила каждую
секунду при полном покое. На экран теперь идёт среднее за
TEMP_AVG_SAMPLES последних замеров; агент шлёт раз в секунду, так что
это ровно 5 секунд.

Сумма пересчитывается заново на каждом шаге, а не копится: на пяти
числах это бесплатно, зато нет накопления ошибки округления за сутки
непрерывной работы. Окно сбрасывается при обрыве связи, иначе после
паузы среднее считалось бы по значениям до обрыва.

Тревога переведена на усреднённое значение: иначе экран показывал бы 84,
когда мигание уже идёт. Загрузка и график намеренно остались сырыми.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
oskarvitaliiandClaude Opus 5 committed 2026-09-15 00:41:20 +07:00
1 parent 1047ae1452
commit fcc96f5090
3 files changed
+64 -3

No files matched your search

+34 -3
View File
@@ -13,6 +13,30 @@ static uint32_t lastSample = 0;
static uint32_t lastDraw = 0; static uint32_t lastDraw = 0;
static bool everGot = false; static bool everGot = false;
static bool alarmActive = false; static bool alarmActive = false;
static bool wasFresh = false;
static float tempShown = 0.0f; // усреднённое значение, именно оно на экране
// Скользящее среднее температуры. Сумма пересчитывается заново, а не копится:
// на пяти числах это бесплатно, зато нет накопления ошибки округления за сутки
// непрерывной работы.
static float tempWindow[TEMP_AVG_SAMPLES];
static uint8_t tempCount = 0;
static uint8_t tempIndex = 0;
static void tempAvgReset() {
tempCount = 0;
tempIndex = 0;
}
static float tempAvgPush(float v) {
tempWindow[tempIndex] = v;
tempIndex = (uint8_t)((tempIndex + 1) % TEMP_AVG_SAMPLES);
if (tempCount < TEMP_AVG_SAMPLES) tempCount++;
float sum = 0.0f;
for (uint8_t i = 0; i < tempCount; i++) sum += tempWindow[i];
return sum / tempCount;
}
void setup() { void setup() {
pinMode(PIN_LED, OUTPUT); pinMode(PIN_LED, OUTPUT);
@@ -32,14 +56,21 @@ void loop() {
CpuStats fresh; CpuStats fresh;
if (linkPoll(fresh)) { if (linkPoll(fresh)) {
stats = fresh; stats = fresh;
tempShown = tempAvgPush(fresh.tempC);
lastData = millis(); lastData = millis();
everGot = true; everGot = true;
} }
const bool isFresh = everGot && (millis() - lastData < STALE_MS); const bool isFresh = everGot && (millis() - lastData < STALE_MS);
// После паузы связи среднее нельзя считать по значениям до обрыва.
if (wasFresh && !isFresh) tempAvgReset();
wasFresh = isFresh;
if (isFresh) { if (isFresh) {
updateAlarm(stats.tempC); // Тревога по усреднённому значению: иначе экран показывал бы 84, а мигание
// уже шло бы. Побочно это защищает от срабатывания на одиночный выброс.
updateAlarm(tempShown);
} else { } else {
alarmActive = false; // без данных о перегреве судить не по чему alarmActive = false; // без данных о перегреве судить не по чему
} }
@@ -57,11 +88,11 @@ void loop() {
// Историю дописываем только при живой связи: пауза не должна рисоваться провалом. // Историю дописываем только при живой связи: пауза не должна рисоваться провалом.
if (isFresh && millis() - lastSample >= SAMPLE_MS) { if (isFresh && millis() - lastSample >= SAMPLE_MS) {
lastSample = millis(); lastSample = millis();
uiPushSample(stats.tempC); uiPushSample(stats.tempC); // график по сырым: 5 градусов на пиксель, шум там не виден
} }
if (millis() - lastDraw >= DRAW_MS) { if (millis() - lastDraw >= DRAW_MS) {
lastDraw = millis(); lastDraw = millis();
uiRender(isFresh, stats.tempC, stats.loadPct, alarmActive && blinkOn); uiRender(isFresh, tempShown, stats.loadPct, alarmActive && blinkOn);
} }
} }
+21
View File
@@ -241,6 +241,7 @@ Start-ScheduledTask -TaskName 'CPU Temp Agent'
| `ALARM_BLINK_MS` | 500 | полупериод мигания | | `ALARM_BLINK_MS` | 500 | полупериод мигания |
| `PIN_LED` | 8 | светодиод | | `PIN_LED` | 8 | светодиод |
| `LED_ACTIVE_LEVEL` | `LOW` | уровень, при котором светодиод горит | | `LED_ACTIVE_LEVEL` | `LOW` | уровень, при котором светодиод горит |
| `TEMP_AVG_SAMPLES` | 5 | окно сглаживания температуры, замеров |
| `SCALE_MIN`, `SCALE_MAX` | 30.0, 90.0 | шкала графика, °C | | `SCALE_MIN`, `SCALE_MAX` | 30.0, 90.0 | шкала графика, °C |
| `SERIAL_BAUD` | 115200 | скорость обмена; менять вместе с агентом | | `SERIAL_BAUD` | 115200 | скорость обмена; менять вместе с агентом |
| `STALE_MS` | 5000 | молчание, после которого показывается `NO DATA` | | `STALE_MS` | 5000 | молчание, после которого показывается `NO DATA` |
@@ -253,6 +254,26 @@ Start-ScheduledTask -TaskName 'CPU Temp Agent'
пока температура топчется на границе. Поэтому включение и отпускание разведены: пока температура топчется на границе. Поэтому включение и отпускание разведены:
загорается на 85, гаснет только при спуске до 82. загорается на 85, гаснет только при спуске до 82.
### Почему цифра температуры усредняется
Мгновенное значение плавает на ±1 °C вокруг стабильного, и цифра мельтешит
каждую секунду, хотя ничего не происходит — это отвлекает. На экран выводится
среднее за `TEMP_AVG_SAMPLES` последних замеров, то есть за 5 секунд: агент
шлёт раз в секунду.
Плата за это — запаздывание реакции на реальный нагрев примерно на 2–3 секунды.
Для температуры процессора незаметно: она физически инертна.
Что осталось несглаженным и почему:
- **загрузка CPU** — её скачки настоящие, ради них на неё и смотрят;
- **график** — 12 пикселей на диапазон 60 °C дают 5 °C на пиксель, шум там и так
не виден, зато история остаётся сырой.
Тревога считается по усреднённому значению, а не по сырому: иначе экран
показывал бы 84, когда мигание уже началось. Побочно это защищает от
срабатывания на одиночный выброс — перегрев должен продержаться пару секунд.
### Почему шкала графика фиксированная ### Почему шкала графика фиксированная
Автомасштаб отвергнут сознательно: в простое колебания ±1 °C рисовались бы во Автомасштаб отвергнут сознательно: в простое колебания ±1 °C рисовались бы во
+9
View File
@@ -37,6 +37,15 @@ static const uint8_t PIN_LED = 8;
// высокий уровень и нужен. // высокий уровень и нужен.
static const uint8_t LED_ACTIVE_LEVEL = LOW; static const uint8_t LED_ACTIVE_LEVEL = LOW;
// --- Сглаживание показаний -------------------------------------------------
// Скользящее среднее температуры: цифра не должна мельтешить на +-1 градус,
// пока ничего не происходит. Единица - один принятый замер, а агент шлёт раз
// в секунду, поэтому 5 означает "среднее за последние 5 секунд".
// Загрузка сознательно не сглаживается: её скачки настоящие, ради них на неё
// и смотрят.
static const uint8_t TEMP_AVG_SAMPLES = 5;
// --- График ---------------------------------------------------------------- // --- График ----------------------------------------------------------------
// Шкала фиксированная: при автомасштабе шум +-1 градус в простое // Шкала фиксированная: при автомасштабе шум +-1 градус в простое