feat(ui): экран оперативной памяти и переключение кнопкой BOOT
Второй экран показывает загрузку ОЗУ крупной цифрой и её график со шкалой 0-100 - у памяти она естественная, подбирать нечего. В углу свой значок, планка памяти вместо микросхемы: он же и говорит, какой экран открыт. Тревога в приоритете: при перегреве плата принудительно возвращается на экран процессора и кнопка не работает, пока температура не упадёт. Смысл тревоги в том, что её нельзя прощёлкать. Экран памяти выпадает из листания, если агент не шлёт ключ M - со старым агентом кнопка просто ничего не делает. То же решение подойдёт машинам без дискретной видеокарты. Выбранный экран не запоминается при перезагрузке: хранить негде. Тревоги по памяти нет намеренно - загрузка ОЗУ под 100% здесь обычное рабочее состояние. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4344fa8873
commit
f18090b5b7
8 files changed
+201
-59
No files matched your search
+48
-5
@@ -1,13 +1,14 @@
|
||||
// Индикатор температуры CPU: плата принимает строки "T=67.0;L=34" по USB-COM
|
||||
// Индикатор состояния ПК: плата принимает строки "T=67.0;L=34;M=97" по USB-COM
|
||||
// от агента на ПК и рисует их на встроенном OLED 72x40. Сама ничего не меряет.
|
||||
//
|
||||
// Два экрана: процессор и оперативная память, переключение кнопкой BOOT.
|
||||
// Все настройки — в config.h.
|
||||
|
||||
#include "config.h"
|
||||
#include "link.h"
|
||||
#include "ui.h"
|
||||
|
||||
static CpuStats stats = {0.0f, 0};
|
||||
static CpuStats stats = {0.0f, 0, -1};
|
||||
static uint32_t lastData = 0;
|
||||
static uint32_t lastSample = 0;
|
||||
static uint32_t lastDraw = 0;
|
||||
@@ -16,6 +17,8 @@ static bool alarmActive = false;
|
||||
static bool wasFresh = false;
|
||||
static float tempShown = 0.0f; // усреднённое значение, именно оно на экране
|
||||
|
||||
static UiScreen screen = UI_SCREEN_CPU;
|
||||
|
||||
// Скользящее среднее температуры. Сумма пересчитывается заново, а не копится:
|
||||
// на пяти числах это бесплатно, зато нет накопления ошибки округления за сутки
|
||||
// непрерывной работы.
|
||||
@@ -41,6 +44,7 @@ static float tempAvgPush(float v) {
|
||||
void setup() {
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
digitalWrite(PIN_LED, !LED_ACTIVE_LEVEL);
|
||||
pinMode(PIN_BUTTON, INPUT_PULLUP);
|
||||
|
||||
linkBegin();
|
||||
uiBegin();
|
||||
@@ -52,6 +56,38 @@ static void updateAlarm(float tempC) {
|
||||
else if (alarmActive && tempC <= ALARM_OFF_C) alarmActive = false;
|
||||
}
|
||||
|
||||
// Экран памяти показываем, только если данные по ней действительно приходят:
|
||||
// со старым агентом ключа M в строке нет вовсе.
|
||||
static bool ramAvailable() {
|
||||
return stats.memPct >= 0;
|
||||
}
|
||||
|
||||
static void onButtonPress() {
|
||||
// Тревога в приоритете: уйти с экрана процессора нельзя, пока он горячий.
|
||||
if (alarmActive) return;
|
||||
if (!ramAvailable()) return;
|
||||
|
||||
screen = (screen == UI_SCREEN_CPU) ? UI_SCREEN_RAM : UI_SCREEN_CPU;
|
||||
}
|
||||
|
||||
// Кнопка BOOT замыкает пин на землю, поэтому нажатию соответствует LOW.
|
||||
static void pollButton() {
|
||||
static bool stable = HIGH;
|
||||
static bool lastRead = HIGH;
|
||||
static uint32_t changedAt = 0;
|
||||
|
||||
const bool now = digitalRead(PIN_BUTTON);
|
||||
if (now != lastRead) {
|
||||
lastRead = now;
|
||||
changedAt = millis();
|
||||
}
|
||||
|
||||
if (millis() - changedAt >= BUTTON_DEBOUNCE_MS && now != stable) {
|
||||
stable = now;
|
||||
if (stable == LOW) onButtonPress();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
CpuStats fresh;
|
||||
if (linkPoll(fresh)) {
|
||||
@@ -75,24 +111,31 @@ void loop() {
|
||||
alarmActive = false; // без данных о перегреве судить не по чему
|
||||
}
|
||||
|
||||
pollButton();
|
||||
|
||||
// Тревога перебивает выбор пользователя и возвращает на экран процессора.
|
||||
if (alarmActive) screen = UI_SCREEN_CPU;
|
||||
if (screen == UI_SCREEN_RAM && !ramAvailable()) screen = UI_SCREEN_CPU;
|
||||
|
||||
// Общая фаза для экрана и светодиода, чтобы они моргали синхронно.
|
||||
const bool blinkOn = (millis() / ALARM_BLINK_MS) % 2 == 0;
|
||||
|
||||
// Тревога — мигает, нет данных — горит ровно, норма — погашен.
|
||||
bool ledOn;
|
||||
if (!isFresh) ledOn = true;
|
||||
else if (alarmActive) ledOn = blinkOn;
|
||||
else if (alarmActive) ledOn = blinkOn;
|
||||
else ledOn = false;
|
||||
digitalWrite(PIN_LED, ledOn ? LED_ACTIVE_LEVEL : !LED_ACTIVE_LEVEL);
|
||||
|
||||
// Историю дописываем только при живой связи: пауза не должна рисоваться провалом.
|
||||
if (isFresh && millis() - lastSample >= SAMPLE_MS) {
|
||||
lastSample = millis();
|
||||
uiPushSample(stats.tempC); // график по сырым: 5 градусов на пиксель, шум там не виден
|
||||
uiPushCpuSample(stats.tempC); // график по сырым: 5 градусов на пиксель, шум там не виден
|
||||
if (ramAvailable()) uiPushRamSample(stats.memPct);
|
||||
}
|
||||
|
||||
if (millis() - lastDraw >= DRAW_MS) {
|
||||
lastDraw = millis();
|
||||
uiRender(isFresh, tempShown, stats.loadPct, alarmActive && blinkOn);
|
||||
uiRender(screen, isFresh, tempShown, stats.loadPct, stats.memPct, alarmActive && blinkOn);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user