feat: testowanie nastrojów za pomocą rest api

This commit is contained in:
2026-06-05 23:08:31 +02:00
parent 856990f232
commit a7ed8f6cdd
3 changed files with 212 additions and 42 deletions
+112 -22
View File
@@ -99,7 +99,7 @@ static void drawEye(uint8_t cx, uint8_t cy, uint8_t effRy,
u8g2.drawFilledEllipse(cx, cy, EYE_RX, ry, U8G2_DRAW_ALL);
if (ry >= 5) {
u8g2.setDrawColor(0);
int8_t gx = (int8_t)cx + 3 + pdx / 3;
int8_t gx = (int8_t)cx - 3 + pdx / 3;
int8_t gy = (int8_t)cy - 3 + pdy / 3;
u8g2.drawDisc((uint8_t)gx, (uint8_t)gy, 2);
u8g2.setDrawColor(1);
@@ -117,20 +117,28 @@ static void drawEye(uint8_t cx, uint8_t cy, uint8_t effRy,
u8g2.drawFilledEllipse(cx, cy, r, ry, U8G2_DRAW_ALL);
if (ry >= 6) {
u8g2.setDrawColor(0);
u8g2.drawDisc(cx + 5, cy - 5, 3);
u8g2.drawDisc(cx - 5, cy - 5, 3);
u8g2.setDrawColor(1);
}
break;
}
case MOOD_ANGRY: {
const uint8_t w = EYE_RX + 3;
const uint8_t h = min(effRy, (uint8_t)6);
for (int8_t row = 0; row <= (int8_t)(h * 2); row++) {
int8_t y = (int8_t)cy - (int8_t)h + row;
uint8_t lineW = (row < (int8_t)h) ? w - (uint8_t)((int8_t)h - row) : w;
if (lineW < 2) lineW = 2;
uint8_t x = isLeft ? cx - w : (uint8_t)(cx + w - lineW);
u8g2.drawHLine(x, (uint8_t)y, lineW);
// Oko — dolna połowa elipsy (jak sad)
uint8_t ry = min(effRy, EYE_RX);
u8g2.drawFilledEllipse(cx, cy - 2, EYE_RX, ry,
U8G2_DRAW_LOWER_LEFT | U8G2_DRAW_LOWER_RIGHT);
// Źrenica
if (ry >= 4) {
u8g2.setDrawColor(0);
u8g2.drawDisc(cx - 3, cy + 2, 2);
u8g2.setDrawColor(1);
}
// Brew ściągnięta do środka (V kształt nad okiem)
uint8_t browY = cy - ry - 3;
if (isLeft) {
u8g2.drawLine(cx - EYE_RX + 2, browY, cx + EYE_RX - 2, browY + 3);
} else {
u8g2.drawLine(cx - EYE_RX + 2, browY + 3, cx + EYE_RX - 2, browY);
}
break;
}
@@ -138,6 +146,19 @@ static void drawEye(uint8_t cx, uint8_t cy, uint8_t effRy,
uint8_t ry = min(effRy, EYE_RX);
u8g2.drawFilledEllipse(cx, cy - 2, EYE_RX, ry,
U8G2_DRAW_LOWER_LEFT | U8G2_DRAW_LOWER_RIGHT);
// Źrenica
if (ry >= 4) {
u8g2.setDrawColor(0);
u8g2.drawDisc(cx - 3, cy + 2, 2);
u8g2.setDrawColor(1);
}
// Brew opadająca na zewnątrz (smutna — odwrotnie niż angry)
uint8_t browY = cy - ry - 3;
if (isLeft) {
u8g2.drawLine(cx - EYE_RX + 2, browY + 3, cx + EYE_RX - 2, browY);
} else {
u8g2.drawLine(cx - EYE_RX + 2, browY, cx + EYE_RX - 2, browY + 3);
}
break;
}
case MOOD_EXCITED: {
@@ -145,10 +166,10 @@ static void drawEye(uint8_t cx, uint8_t cy, uint8_t effRy,
u8g2.drawFilledEllipse(cx, cy, EYE_RX, ry, U8G2_DRAW_ALL);
if (ry >= 5) {
u8g2.setDrawColor(0);
u8g2.drawLine(cx - 5, cy - 5, cx + 5, cy + 5);
u8g2.drawLine(cx + 5, cy - 5, cx - 5, cy + 5);
u8g2.drawLine(cx - 7, cy - 7, cx + 7, cy + 7);
u8g2.drawLine(cx + 7, cy - 7, cx - 7, cy + 7);
u8g2.setDrawColor(1);
u8g2.drawDisc(cx, cy, 2);
u8g2.drawDisc(cx, cy, 5);
}
break;
}
@@ -167,8 +188,8 @@ static void drawEye(uint8_t cx, uint8_t cy, uint8_t effRy,
u8g2.drawFilledEllipse(cx, cy, EYE_RX, ry, U8G2_DRAW_ALL);
if (ry >= 5) {
u8g2.setDrawColor(0);
u8g2.drawDisc(cx + 4, cy - 4, 3);
u8g2.drawDisc(cx - 3, cy + 3, 1);
u8g2.drawDisc(cx - 4, cy - 4, 3);
u8g2.drawDisc(cx + 3, cy + 3, 1);
u8g2.setDrawColor(1);
}
break;
@@ -178,8 +199,19 @@ static void drawEye(uint8_t cx, uint8_t cy, uint8_t effRy,
u8g2.drawFilledEllipse(cx, cy, EYE_RX, ry, U8G2_DRAW_ALL);
if (ry >= 5) {
u8g2.setDrawColor(0);
u8g2.drawDisc(cx - 4, cy - 2, 2);
u8g2.drawDisc(cx + 4, cy + 2, 2);
u8g2.drawDisc(cx + 4, cy - 2, 2);
u8g2.drawDisc(cx - 4, cy + 2, 2);
u8g2.setDrawColor(1);
}
break;
}
case MOOD_WINK_L:
case MOOD_WINK_R: {
uint8_t ry = min(effRy, EYE_RX);
u8g2.drawFilledEllipse(cx, cy, EYE_RX, ry, U8G2_DRAW_ALL);
if (ry >= 5) {
u8g2.setDrawColor(0);
u8g2.drawDisc(cx - 3, cy - 3, 2);
u8g2.setDrawColor(1);
}
break;
@@ -968,10 +1000,41 @@ static String buildHtml()
"});"
"</script>"
"<hr style='border-color:#222;margin:16px 0'>"
// ── Mood test panel ───────────────────────────────────────────────
"<h3 style='color:#4f4;margin:0 0 10px'>Test nastroju</h3>"
"<div style='margin-bottom:8px;display:flex;flex-wrap:wrap;gap:6px'>"
"<button type='button' onclick='tm(0)' style='background:#333'>Normal</button>"
"<button type='button' onclick='tm(1)' style='background:#185'>Happy</button>"
"<button type='button' onclick='tm(2)' style='background:#226'>Sleepy</button>"
"<button type='button' onclick='tm(3)' style='background:#550'>Surprised</button>"
"<button type='button' onclick='tm(4)' style='background:#622'>Angry</button>"
"<button type='button' onclick='tm(5)' style='background:#246'>Sad</button>"
"<button type='button' onclick='tm(6)' style='background:#185'>Excited</button>"
"<button type='button' onclick='tm(7)' style='background:#444'>Wink L</button>"
"<button type='button' onclick='tm(8)' style='background:#444'>Wink R</button>"
"<button type='button' onclick='tm(9)' style='background:#532'>Hungry</button>"
"<button type='button' onclick='tm(10)' style='background:#245'>Playful</button>"
"<button type='button' onclick='tm(11)' style='background:#432'>Dirty</button>"
"</div>"
"<div id='mmsg' style='color:#4f4;font-size:12px;height:16px'></div>"
"<script>"
"function tm(m){"
"fetch('/api/mood/test?m='+m,{method:'POST'})"
".then(r=>r.json()).then(d=>{"
"var el=document.getElementById('mmsg');"
"el.textContent=d.msg||'OK';"
"setTimeout(function(){el.textContent='';},3000);"
"});"
"}"
"</script>"
"<hr style='border-color:#222;margin:16px 0'>"
"<p style='color:#555;font-size:11px'>GET /api/config &nbsp; POST /api/config (JSON)"
" &nbsp;|&nbsp; GET /api/tama &nbsp; POST /api/tama/{feed,play,clean}"
" &nbsp;|&nbsp; GET /api/weather &nbsp; POST /weather/save</p>"
" &nbsp;|&nbsp; GET /api/weather &nbsp; POST /weather/save"
" &nbsp;|&nbsp; POST /api/mood/test?m={0-11}</p>"
"</body></html>";
return html;
}
@@ -1104,6 +1167,19 @@ void setupHttpServer()
req->send(200, "application/json", buf);
});
// POST /api/mood/test?m=N — ustaw nastoj testowy na 10s
httpServer.on("/api/mood/test", HTTP_POST, [](AsyncWebServerRequest *req) {
uint32_t now = millis();
uint8_t m = 0;
if (req->hasParam("m"))
m = (uint8_t)constrain(req->getParam("m")->value().toInt(), 0, 11);
setBuddyMood(buddy, (Mood)m, now, 10000);
Serial.printf("[Web] Mood test: %s (%d)\n", MOOD_LABELS[m], m);
char buf[64];
snprintf(buf, sizeof(buf), "{\"ok\":true,\"msg\":\"Nastoj: %s (10s)\"}", MOOD_LABELS[m]);
req->send(200, "application/json", buf);
});
// GET /api/weather — current weather data + config
httpServer.on("/api/weather", HTTP_GET, [](AsyncWebServerRequest *req) {
char buf[180];
@@ -1271,10 +1347,24 @@ void loop()
setDim(night && idle);
}
static uint32_t lastWifi = 0;
if (now - lastWifi > 30000) {
lastWifi = now;
if (WiFi.status() != WL_CONNECTED) WiFi.reconnect();
// WiFi watchdog: próba reconnect co 30 s, restart po 5 min bez połączenia
static uint32_t lastWifiCheck = 0;
static uint32_t wifiDownSince = 0; // 0 = połączony lub nie śledzony
if (now - lastWifiCheck > 30000) {
lastWifiCheck = now;
if (WiFi.status() == WL_CONNECTED) {
wifiDownSince = 0;
} else {
if (wifiDownSince == 0) wifiDownSince = now ? now : 1;
uint32_t downMs = now - wifiDownSince;
Serial.printf("[WiFi] Brak polaczenia od %lu s, proba reconnect...\n", downMs / 1000);
WiFi.reconnect();
if (downMs >= 300000UL) {
Serial.println("[WiFi] Brak WiFi > 5 min — restart");
delay(200);
ESP.restart();
}
}
}
// Weather: show face periodically, re-fetch at most every 60 s