Merge dev into master: сглаживание температуры и бэклог
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
96df0465d5
4 files changed
+155
-4
No files matched your search
+34
-3
@@ -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
@@ -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` не знает, откуда
|
||||
взялись данные, а логика состояний живёт в скетче.
|
||||
|
||||
@@ -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 градус в простое
|
||||
|
||||
@@ -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, так что выигрыш только для будущих клонов.
|
||||
|
||||
Решено оставить как есть.
|
||||
Reference in new issue
Block a user