add time, button ui
This commit is contained in:
1 parent
1d7af0615e
commit
dbfe5bf7cf
4 files changed
+63
-26
No files matched your search
+36
-11
@@ -13,18 +13,19 @@ struct Rect {
|
||||
|
||||
// Landscape 320x240 layout
|
||||
static const int kW = 320;
|
||||
static const Rect kBar = {20, 150, 280, 24};
|
||||
static const Rect kBarTouch = {0, 136, 320, 50}; // larger than the bar for easier dragging
|
||||
static const Rect kRelay = {60, 190, 200, 40};
|
||||
static const Rect kMinus = {10, 55, 60, 70};
|
||||
static const Rect kPlus = {250, 55, 60, 70};
|
||||
static const Rect kRelay = {10, 46, 120, 48};
|
||||
static const int kClockX = 310, kClockY = 70; // right-middle of the clock text
|
||||
static const Rect kMinus = {10, 108, 60, 70};
|
||||
static const Rect kPlus = {250, 108, 60, 70};
|
||||
static const int kVolumeY = 120;
|
||||
static const Rect kBar = {20, 196, 280, 24};
|
||||
static const Rect kBarTouch = {0, 184, 320, 50}; // larger than the bar for easier dragging
|
||||
|
||||
static const uint16_t kBg = TFT_BLACK;
|
||||
static const uint16_t kAccent = 0x3D3F; // light blue
|
||||
static const uint16_t kBarBg = 0x2945; // dark grey
|
||||
static const uint16_t kBtn = 0x2945;
|
||||
static const uint16_t kBtnPressed = 0x3D3F;
|
||||
static const uint16_t kOn = TFT_GREEN;
|
||||
static const uint16_t kOff = 0x4208; // grey
|
||||
|
||||
enum class TouchTarget { None, Minus, Plus, Bar, Relay };
|
||||
@@ -52,10 +53,10 @@ static void drawVolume(uint8_t volume) {
|
||||
tft.setTextColor(kAccent, kBg);
|
||||
tft.setTextDatum(TR_DATUM);
|
||||
tft.setTextPadding(tft.textWidth("100", 7));
|
||||
tft.drawNumber(volume, 200, 70, 7);
|
||||
tft.drawNumber(volume, 200, kVolumeY, 7);
|
||||
tft.setTextPadding(0);
|
||||
tft.setTextDatum(TL_DATUM);
|
||||
tft.drawString("%", 208, 94, 4);
|
||||
tft.drawString("%", 208, kVolumeY + 24, 4);
|
||||
|
||||
int fill = kBar.w * volume / 100;
|
||||
tft.fillRect(kBar.x, kBar.y, fill, kBar.h, kAccent);
|
||||
@@ -63,11 +64,29 @@ static void drawVolume(uint8_t volume) {
|
||||
}
|
||||
|
||||
static void drawRelay(bool on) {
|
||||
uint16_t bg = on ? kOn : kOff;
|
||||
uint16_t bg = on ? kAccent : kOff;
|
||||
tft.fillRoundRect(kRelay.x, kRelay.y, kRelay.w, kRelay.h, 10, bg);
|
||||
tft.setTextColor(on ? TFT_BLACK : TFT_WHITE, bg);
|
||||
tft.setTextColor(TFT_WHITE, bg);
|
||||
tft.setTextDatum(MC_DATUM);
|
||||
tft.drawString(on ? "POWER: ON" : "POWER: OFF", kRelay.x + kRelay.w / 2, kRelay.y + kRelay.h / 2 + 1, 4);
|
||||
tft.drawString(on ? "ON" : "OFF", kRelay.x + kRelay.w / 2, kRelay.y + kRelay.h / 2 + 1, 4);
|
||||
}
|
||||
|
||||
// Returns minutes since midnight, or -1 while the time is not synced via NTP
|
||||
static int currentMinutes() {
|
||||
struct tm t;
|
||||
if (!getLocalTime(&t, 0)) return -1;
|
||||
return t.tm_hour * 60 + t.tm_min;
|
||||
}
|
||||
|
||||
static void drawClock(int minutes) {
|
||||
char buf[6];
|
||||
if (minutes < 0) strcpy(buf, "--:--");
|
||||
else snprintf(buf, sizeof(buf), "%02d:%02d", minutes / 60, minutes % 60);
|
||||
tft.setTextColor(TFT_WHITE, kBg);
|
||||
tft.setTextDatum(MR_DATUM);
|
||||
tft.setTextPadding(tft.textWidth("88:88", 7));
|
||||
tft.drawString(buf, kClockX, kClockY, 7);
|
||||
tft.setTextPadding(0);
|
||||
}
|
||||
|
||||
static String networkText() {
|
||||
@@ -129,6 +148,7 @@ void displayTask(void *arg) {
|
||||
int lastVolume = -1;
|
||||
int lastRelay = -1;
|
||||
String lastNet;
|
||||
int lastMinutes = -2;
|
||||
TickType_t lastNetCheck = 0;
|
||||
|
||||
TouchTarget active = TouchTarget::None;
|
||||
@@ -204,6 +224,11 @@ void displayTask(void *arg) {
|
||||
drawNetwork(net);
|
||||
lastNet = net;
|
||||
}
|
||||
int minutes = currentMinutes();
|
||||
if (minutes != lastMinutes) {
|
||||
drawClock(minutes);
|
||||
lastMinutes = minutes;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(DISPLAY_UPDATE_MS));
|
||||
|
||||
+19
-13
@@ -2,13 +2,13 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
static const char INDEX_HTML[] PROGMEM = R"HTML(<!doctype html>
|
||||
<html lang="ru">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Громкость</title>
|
||||
<title>Volume</title>
|
||||
<style>
|
||||
:root { --bg:#111; --card:#1d1d1d; --fg:#eee; --accent:#3fa7ff; --on:#2ecc71; --off:#555; }
|
||||
:root { --bg:#111; --card:#1d1d1d; --fg:#eee; --accent:#3fa7ff; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font-family:system-ui,sans-serif; background:var(--bg); color:var(--fg);
|
||||
display:flex; justify-content:center; padding:24px 16px; }
|
||||
@@ -21,22 +21,28 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(<!doctype html>
|
||||
button { flex:1; font-size:22px; padding:14px; border:0; border-radius:12px;
|
||||
background:#2a2a2a; color:var(--fg); cursor:pointer; }
|
||||
button:active { transform:scale(.97); }
|
||||
#relay { margin-top:20px; width:100%; font-size:18px; background:var(--off); }
|
||||
#relay.on { background:var(--on); color:#000; }
|
||||
.power { display:flex; gap:4px; padding:4px; margin-bottom:20px; border-radius:14px;
|
||||
background:#2a2a2a; }
|
||||
.power button { font-size:18px; font-weight:600; padding:10px; background:transparent;
|
||||
color:#888; letter-spacing:.05em; }
|
||||
.power button.active { background:var(--accent); color:#fff; }
|
||||
.status { margin-top:16px; font-size:12px; opacity:.5; text-align:center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Регулятор громкости</h1>
|
||||
<h1>Volume control</h1>
|
||||
<div class="power">
|
||||
<button id="on" onclick="relay(1)">ON</button>
|
||||
<button id="off" onclick="relay(0)">OFF</button>
|
||||
</div>
|
||||
<div class="vol"><b id="val">--</b><span>%</span></div>
|
||||
<input id="slider" type="range" min="0" max="100" step="1">
|
||||
<div class="row">
|
||||
<button onclick="delta(-5)">−</button>
|
||||
<button onclick="delta(5)">+</button>
|
||||
</div>
|
||||
<button id="relay" onclick="relay()">Реле</button>
|
||||
<div class="status" id="status">подключение…</div>
|
||||
<div class="status" id="status">connecting…</div>
|
||||
</div>
|
||||
<script>
|
||||
const $ = id => document.getElementById(id);
|
||||
@@ -45,20 +51,20 @@ let dragging = false, sendTimer = null;
|
||||
function render(s) {
|
||||
$('val').textContent = s.volume;
|
||||
if (!dragging) $('slider').value = s.volume;
|
||||
$('relay').classList.toggle('on', s.relay);
|
||||
$('relay').textContent = s.relay ? 'Реле: ВКЛ' : 'Реле: ВЫКЛ';
|
||||
$('status').textContent = 'онлайн';
|
||||
$('on').classList.toggle('active', s.relay);
|
||||
$('off').classList.toggle('active', !s.relay);
|
||||
$('status').textContent = 'online';
|
||||
}
|
||||
|
||||
async function api(path, method = 'GET') {
|
||||
try {
|
||||
const r = await fetch(path, { method });
|
||||
if (r.ok) render(await r.json());
|
||||
} catch (e) { $('status').textContent = 'нет связи'; }
|
||||
} catch (e) { $('status').textContent = 'offline'; }
|
||||
}
|
||||
|
||||
const delta = d => api('/api/volume?delta=' + d, 'POST');
|
||||
const relay = () => api('/api/relay?state=toggle', 'POST');
|
||||
const relay = on => api('/api/relay?state=' + (on ? 'on' : 'off'), 'POST');
|
||||
|
||||
const slider = $('slider');
|
||||
slider.addEventListener('pointerdown', () => dragging = true);
|
||||
|
||||
@@ -59,6 +59,7 @@ static void startWifi() {
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.printf("[web] connected, IP: %s\n", WiFi.localIP().toString().c_str());
|
||||
configTzTime(TIME_ZONE, NTP_SERVER1, NTP_SERVER2);
|
||||
} else {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_AP);
|
||||
|
||||
Reference in new issue
Block a user