Compare commits

..
3 Commits
Author SHA1 Message Date
oskarvitaliiandClaude Opus 5 96df0465d5 Merge dev into master: сглаживание температуры и бэклог
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:42:59 +07:00
oskarvitaliiandClaude Opus 5 fcc96f5090 feat(ui): сглаживать температуру скользящим средним за 5 замеров
Мгновенное значение плавает на +-1 градус, и цифра мельтешила каждую
секунду при полном покое. На экран теперь идёт среднее за
TEMP_AVG_SAMPLES последних замеров; агент шлёт раз в секунду, так что
это ровно 5 секунд.

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

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 00:41:20 +07:00
oskarvitaliiandClaude Opus 5 1047ae1452 docs: завести бэклог проекта
Первый пункт - другие показатели (GPU, память, диски) и листалка экранов
кнопкой BOOT. Протокол к этому уже готов: незнакомые ключи прошивка
игнорирует, так что агент может начать слать новые поля, не ломая уже
прошитые платы.

Там же записаны открытые хвосты: обрезание строки при 100 градусах и
100% загрузки, непроверенный перезагрузкой автозапуск, ручной цикл
заливки и вес обзорного изображения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:33:02 +07:00
4 changed files with 155 additions and 4 deletions

No files matched your search

+34 -3
View File
@@ -13,6 +13,30 @@ static uint32_t lastSample = 0;
static uint32_t lastDraw = 0;
static bool everGot = 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() {
pinMode(PIN_LED, OUTPUT);
@@ -32,14 +56,21 @@ void loop() {
CpuStats fresh;
if (linkPoll(fresh)) {
stats = fresh;
tempShown = tempAvgPush(fresh.tempC);
lastData = millis();
everGot = true;
}
const bool isFresh = everGot && (millis() - lastData < STALE_MS);
// После паузы связи среднее нельзя считать по значениям до обрыва.
if (wasFresh && !isFresh) tempAvgReset();
wasFresh = isFresh;
if (isFresh) {
updateAlarm(stats.tempC);
// Тревога по усреднённому значению: иначе экран показывал бы 84, а мигание
// уже шло бы. Побочно это защищает от срабатывания на одиночный выброс.
updateAlarm(tempShown);
} else {
alarmActive = false; // без данных о перегреве судить не по чему
}
@@ -57,11 +88,11 @@ void loop() {
// Историю дописываем только при живой связи: пауза не должна рисоваться провалом.
if (isFresh && millis() - lastSample >= SAMPLE_MS) {
lastSample = millis();
uiPushSample(stats.tempC);
uiPushSample(stats.tempC); // график по сырым: 5 градусов на пиксель, шум там не виден
}
if (millis() - lastDraw >= DRAW_MS) {
lastDraw = millis();
uiRender(isFresh, stats.tempC, stats.loadPct, alarmActive && blinkOn);
uiRender(isFresh, tempShown, stats.loadPct, alarmActive && blinkOn);
}
}
+22 -1
View File
@@ -241,6 +241,7 @@ Start-ScheduledTask -TaskName 'CPU Temp Agent'
| `ALARM_BLINK_MS` | 500 | полупериод мигания |
| `PIN_LED` | 8 | светодиод |
| `LED_ACTIVE_LEVEL` | `LOW` | уровень, при котором светодиод горит |
| `TEMP_AVG_SAMPLES` | 5 | окно сглаживания температуры, замеров |
| `SCALE_MIN`, `SCALE_MAX` | 30.0, 90.0 | шкала графика, °C |
| `SERIAL_BAUD` | 115200 | скорость обмена; менять вместе с агентом |
| `STALE_MS` | 5000 | молчание, после которого показывается `NO DATA` |
@@ -253,6 +254,26 @@ Start-ScheduledTask -TaskName 'CPU Temp Agent'
пока температура топчется на границе. Поэтому включение и отпускание разведены:
загорается на 85, гаснет только при спуске до 82.
### Почему цифра температуры усредняется
Мгновенное значение плавает на ±1 °C вокруг стабильного, и цифра мельтешит
каждую секунду, хотя ничего не происходит — это отвлекает. На экран выводится
среднее за `TEMP_AVG_SAMPLES` последних замеров, то есть за 5 секунд: агент
шлёт раз в секунду.
Плата за это — запаздывание реакции на реальный нагрев примерно на 2–3 секунды.
Для температуры процессора незаметно: она физически инертна.
Что осталось несглаженным и почему:
- **загрузка CPU** — её скачки настоящие, ради них на неё и смотрят;
- **график** — 12 пикселей на диапазон 60 °C дают 5 °C на пиксель, шум там и так
не виден, зато история остаётся сырой.
Тревога считается по усреднённому значению, а не по сырому: иначе экран
показывал бы 84, когда мигание уже началось. Побочно это защищает от
срабатывания на одиночный выброс — перегрев должен продержаться пару секунд.
### Почему шкала графика фиксированная
Автомасштаб отвергнут сознательно: в простое колебания ±1 °C рисовались бы во
@@ -335,7 +356,7 @@ $sp.Close()
| `tools/i2c_scan/` | одноразовый скетч поиска пинов дисплея |
| `agent/cpu_temp_agent.ps1` | агент для ПК |
| `agent/README.md` | подробности по агенту |
| `docs/` | спецификация и план реализации |
| `docs/` | спецификация, план реализации и [бэклог](docs/BACKLOG.md) |
Разделение простое: `link` ничего не знает об экране, `ui` не знает, откуда
взялись данные, а логика состояний живёт в скетче.
+9
View File
@@ -37,6 +37,15 @@ static const uint8_t PIN_LED = 8;
// высокий уровень и нужен.
static const uint8_t LED_ACTIVE_LEVEL = LOW;
// --- Сглаживание показаний -------------------------------------------------
// Скользящее среднее температуры: цифра не должна мельтешить на +-1 градус,
// пока ничего не происходит. Единица - один принятый замер, а агент шлёт раз
// в секунду, поэтому 5 означает "среднее за последние 5 секунд".
// Загрузка сознательно не сглаживается: её скачки настоящие, ради них на неё
// и смотрят.
static const uint8_t TEMP_AVG_SAMPLES = 5;
// --- График ----------------------------------------------------------------
// Шкала фиксированная: при автомасштабе шум +-1 градус в простое
+90
View File
@@ -0,0 +1,90 @@
# Бэклог
Идеи и незакрытые хвосты по проекту. Не план работ: сюда складывается то, что
решено не делать сейчас, чтобы не потерялось.
---
## 1. Другие показатели и листалка по кнопке BOOT
**Что хочется.** Показывать не только процессор: видеокарту, оперативную память,
диски. Переключать экраны кнопкой **BOOT** на плате — нажал, увидел следующий
набор данных.
**Почему это дёшево со стороны протокола.** Формат `T=67.0;L=34` изначально
сделан расширяемым: прошивка молча игнорирует незнакомые ключи. Агент может
начать слать `G=61;GL=88;M=73` (видеокарта, её загрузка, память), и уже прошитые
платы от этого не сломаются — просто не покажут новое.
**Что придётся решить при реализации:**
- **Кнопка BOOT сидит на GPIO9.** Это пин начальной загрузки: при сбросе он
должен быть в высоком уровне, иначе плата уйдёт в режим прошивки. Читать его
как `INPUT_PULLUP` в работе безопасно, но проверить фактически.
- **Дребезг контактов.** Нужна программная защита, иначе одно нажатие пролистает
три экрана.
- **Свой макет под каждый экран.** На 72×40 не получится «то же самое, только
другие цифры»: у памяти уместнее проценты и гигабайты, у диска — температура
без графика. Каждый экран проектировать отдельно.
- **Отдельная история на каждый показатель.** Сейчас кольцевой буфер один и
хранит температуру CPU. На каждый график нужен свой буфер: 72 байта на штуку,
памяти достаточно.
- **Что показывать, если данных нет.** Например, в системе нет дискретной
видеокарты — экран GPU надо либо пропускать при листании, либо честно писать,
что источника нет.
- **Запоминать ли выбранный экран** между перезагрузками. Если да, понадобится
энергонезависимое хранилище, которого у прошивки сейчас нет вовсе.
- **Тревога при листании.** Сейчас перегрев виден всегда, потому что экран один.
С листалкой можно стоять на экране памяти и не увидеть, что горит процессор.
Нужно решить: принудительно перекидывать на экран CPU, показывать тревогу
поверх любого экрана или мигать только светодиодом.
Последний пункт — самый важный: он затрагивает смысл устройства, а не удобство.
---
## 2. Обрезание строки при 100° и 100%
При трёхзначной температуре и трёхзначной загрузке верхняя строка занимает 79
пикселей из 72: `100` — 49 px, зазор 2, `100%` — 28 px. Лишние 7 пикселей
уходят за правый край, и загрузка обрезается.
Дефект существовал с самого начала, проявляется только под предельной нагрузкой.
Для i5-11400H это не гипотеза: предельная температура около 100 °C.
Решение на выбор: при трёхзначной температуре переходить на более мелкий шрифт
загрузки либо убирать знак процента. Оценка — около получаса вместе с проверкой.
Решено оставить как есть: в такой момент экран всё равно мигает тревогой.
---
## 3. Автозапуск не проверен перезагрузкой
Задание Планировщика `CPU Temp Agent` зарегистрировано с триггером «при входе в
систему», запускается вручную и работает. Живой перезагрузкой триггер не
испытан.
Проверится само при следующей перезагрузке: экран должен ожить без участия
пользователя в течение примерно полуминуты.
---
## 4. Скрипт заливки
Цикл «остановить агента → залить прошивку → запустить агента» делается тремя
командами вручную. Пока правок мало, это терпимо; если работа по экрану
продолжится, стоит свести в `flash.ps1`.
Причина, по которой шаг вообще нужен: плата прошивается через тот же COM-порт,
по которому идут данные, и занятый порт валит заливку с `PermissionError`.
---
## 5. Вес обзорного изображения
`docs/promo.png` весит 2,1 МБ при исходниках примерно на 30 КБ. Сжатие до
300–500 КБ не ухудшило бы читаемость, но старый файл всё равно останется в
истории git, так что выигрыш только для будущих клонов.
Решено оставить как есть.