255 lines
8.3 KiB
C++
255 lines
8.3 KiB
C++
|
|
#include <Arduino.h>
|
||
|
|
#include <M5Unified.h>
|
||
|
|
#include <WiFi.h>
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
constexpr uint16_t BG = 0x10A2;
|
||
|
|
constexpr uint16_t CARD = 0x2145;
|
||
|
|
constexpr uint16_t BORDER = 0x39E7;
|
||
|
|
constexpr uint16_t MUTED = 0xAD55;
|
||
|
|
constexpr uint16_t ACCENT = 0x4D7F;
|
||
|
|
|
||
|
|
struct Rect {
|
||
|
|
int32_t x, y, w, h;
|
||
|
|
bool contains(int32_t px, int32_t py) const {
|
||
|
|
return px >= x && px < x + w && py >= y && py < y + h;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
struct Button {
|
||
|
|
Rect area;
|
||
|
|
const char* label;
|
||
|
|
uint16_t color;
|
||
|
|
};
|
||
|
|
|
||
|
|
constexpr Rect colorPanel{28, 108, 760, 326};
|
||
|
|
constexpr Rect infoPanel{812, 108, 440, 506};
|
||
|
|
constexpr Rect slider{74, 661, 1132, 22};
|
||
|
|
|
||
|
|
Button buttons[] = {
|
||
|
|
{{28, 462, 174, 126}, "RED", TFT_RED},
|
||
|
|
{{220, 462, 174, 126}, "GREEN", TFT_GREEN},
|
||
|
|
{{412, 462, 174, 126}, "BLUE", TFT_BLUE},
|
||
|
|
{{604, 462, 184, 126}, "RESET", 0x7BEF},
|
||
|
|
};
|
||
|
|
|
||
|
|
uint16_t panelColor = ACCENT;
|
||
|
|
uint32_t pressCount = 0;
|
||
|
|
uint32_t lastInfoUpdate = 0;
|
||
|
|
uint8_t brightness = 160;
|
||
|
|
int32_t lastTouchX = -1;
|
||
|
|
int32_t lastTouchY = -1;
|
||
|
|
|
||
|
|
void centered(const char* text, const Rect& area, uint16_t color,
|
||
|
|
float size = 1.0f) {
|
||
|
|
M5.Display.setTextDatum(middle_center);
|
||
|
|
M5.Display.setTextColor(color);
|
||
|
|
M5.Display.setTextSize(size);
|
||
|
|
M5.Display.drawString(text, area.x + area.w / 2, area.y + area.h / 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
void drawHeader() {
|
||
|
|
M5.Display.fillRect(0, 0, M5.Display.width(), 82, CARD);
|
||
|
|
M5.Display.fillRect(0, 80, M5.Display.width(), 2, ACCENT);
|
||
|
|
M5.Display.setTextDatum(middle_left);
|
||
|
|
M5.Display.setTextColor(TFT_WHITE);
|
||
|
|
M5.Display.setTextSize(1.8f);
|
||
|
|
M5.Display.drawString("M5Stack Tab5", 28, 40);
|
||
|
|
M5.Display.setTextDatum(middle_right);
|
||
|
|
M5.Display.setTextColor(MUTED);
|
||
|
|
M5.Display.setTextSize(1.0f);
|
||
|
|
M5.Display.drawString("TOUCH DASHBOARD", M5.Display.width() - 28, 40);
|
||
|
|
}
|
||
|
|
|
||
|
|
void drawColorPanel() {
|
||
|
|
M5.Display.fillRoundRect(colorPanel.x, colorPanel.y, colorPanel.w,
|
||
|
|
colorPanel.h, 22, panelColor);
|
||
|
|
M5.Display.drawRoundRect(colorPanel.x, colorPanel.y, colorPanel.w,
|
||
|
|
colorPanel.h, 22, TFT_WHITE);
|
||
|
|
centered("DISPLAY + TOUCH READY",
|
||
|
|
{colorPanel.x, colorPanel.y + 78, colorPanel.w, 150}, TFT_WHITE,
|
||
|
|
2.0f);
|
||
|
|
char text[48];
|
||
|
|
snprintf(text, sizeof(text), "Button presses: %lu",
|
||
|
|
static_cast<unsigned long>(pressCount));
|
||
|
|
centered(text, {colorPanel.x, colorPanel.y + 220, colorPanel.w, 52},
|
||
|
|
TFT_WHITE, 1.1f);
|
||
|
|
}
|
||
|
|
|
||
|
|
void drawButton(const Button& button, bool pressed = false) {
|
||
|
|
uint16_t fill = pressed ? TFT_WHITE : button.color;
|
||
|
|
uint16_t text = pressed ? button.color : TFT_WHITE;
|
||
|
|
M5.Display.fillRoundRect(button.area.x, button.area.y, button.area.w,
|
||
|
|
button.area.h, 18, fill);
|
||
|
|
M5.Display.drawRoundRect(button.area.x, button.area.y, button.area.w,
|
||
|
|
button.area.h, 18, TFT_WHITE);
|
||
|
|
centered(button.label, button.area, text, 1.35f);
|
||
|
|
}
|
||
|
|
|
||
|
|
void drawSlider() {
|
||
|
|
M5.Display.fillRect(0, 630, M5.Display.width(), 90, BG);
|
||
|
|
M5.Display.setTextDatum(middle_left);
|
||
|
|
M5.Display.setTextColor(MUTED, BG);
|
||
|
|
M5.Display.setTextSize(0.9f);
|
||
|
|
M5.Display.drawString("BRIGHTNESS", 28, slider.y + slider.h / 2);
|
||
|
|
M5.Display.fillRoundRect(slider.x, slider.y, slider.w, slider.h,
|
||
|
|
slider.h / 2, BORDER);
|
||
|
|
int32_t filled = map(brightness, 20, 255, 0, slider.w);
|
||
|
|
if (filled > 0) {
|
||
|
|
M5.Display.fillRoundRect(slider.x, slider.y, filled, slider.h,
|
||
|
|
slider.h / 2, ACCENT);
|
||
|
|
}
|
||
|
|
int32_t knobX = slider.x + constrain(filled, 0, slider.w);
|
||
|
|
M5.Display.fillCircle(knobX, slider.y + slider.h / 2, 18, TFT_WHITE);
|
||
|
|
M5.Display.drawCircle(knobX, slider.y + slider.h / 2, 18, BORDER);
|
||
|
|
char value[8];
|
||
|
|
snprintf(value, sizeof(value), "%u", brightness);
|
||
|
|
M5.Display.setTextDatum(middle_right);
|
||
|
|
M5.Display.setTextColor(TFT_WHITE, BG);
|
||
|
|
M5.Display.drawString(value, M5.Display.width() - 28,
|
||
|
|
slider.y + slider.h / 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
void infoLine(int32_t y, const char* label, const char* value) {
|
||
|
|
M5.Display.fillRect(infoPanel.x + 18, y, infoPanel.w - 36, 38, CARD);
|
||
|
|
M5.Display.setTextSize(0.95f);
|
||
|
|
M5.Display.setTextDatum(middle_left);
|
||
|
|
M5.Display.setTextColor(MUTED, CARD);
|
||
|
|
M5.Display.drawString(label, infoPanel.x + 24, y + 19);
|
||
|
|
M5.Display.setTextDatum(middle_right);
|
||
|
|
M5.Display.setTextColor(TFT_WHITE, CARD);
|
||
|
|
M5.Display.drawString(value, infoPanel.x + infoPanel.w - 24, y + 19);
|
||
|
|
}
|
||
|
|
|
||
|
|
void updateInfo() {
|
||
|
|
char value[64];
|
||
|
|
uint32_t seconds = millis() / 1000;
|
||
|
|
snprintf(value, sizeof(value), "%02lu:%02lu:%02lu",
|
||
|
|
static_cast<unsigned long>(seconds / 3600),
|
||
|
|
static_cast<unsigned long>((seconds / 60) % 60),
|
||
|
|
static_cast<unsigned long>(seconds % 60));
|
||
|
|
infoLine(infoPanel.y + 82, "Uptime", value);
|
||
|
|
snprintf(value, sizeof(value), "%d x %d", M5.Display.width(),
|
||
|
|
M5.Display.height());
|
||
|
|
infoLine(infoPanel.y + 124, "Display", value);
|
||
|
|
snprintf(value, sizeof(value), "%ld, %ld", static_cast<long>(lastTouchX),
|
||
|
|
static_cast<long>(lastTouchY));
|
||
|
|
infoLine(infoPanel.y + 166, "Last touch", value);
|
||
|
|
snprintf(value, sizeof(value), "%.1f MB",
|
||
|
|
ESP.getFreeHeap() / (1024.0 * 1024.0));
|
||
|
|
infoLine(infoPanel.y + 208, "Free heap", value);
|
||
|
|
snprintf(value, sizeof(value), "%.1f MB",
|
||
|
|
ESP.getFreePsram() / (1024.0 * 1024.0));
|
||
|
|
infoLine(infoPanel.y + 250, "Free PSRAM", value);
|
||
|
|
int32_t mv = M5.Power.getBatteryVoltage();
|
||
|
|
int32_t ma = M5.Power.getBatteryCurrent();
|
||
|
|
if (mv > 0) {
|
||
|
|
snprintf(value, sizeof(value), "%.2f V / %ld mA", mv / 1000.0,
|
||
|
|
static_cast<long>(ma));
|
||
|
|
} else {
|
||
|
|
snprintf(value, sizeof(value), "not detected");
|
||
|
|
}
|
||
|
|
infoLine(infoPanel.y + 292, "Battery", value);
|
||
|
|
snprintf(value, sizeof(value), "%s",
|
||
|
|
WiFi.status() == WL_CONNECTED ? "connected" : "not connected");
|
||
|
|
infoLine(infoPanel.y + 334, "Wi-Fi", value);
|
||
|
|
float ax = 0, ay = 0, az = 0;
|
||
|
|
M5.Imu.update();
|
||
|
|
M5.Imu.getAccelData(&ax, &ay, &az);
|
||
|
|
snprintf(value, sizeof(value), "%+.2f %+.2f %+.2f", ax, ay, az);
|
||
|
|
infoLine(infoPanel.y + 376, "Accel XYZ", value);
|
||
|
|
}
|
||
|
|
|
||
|
|
void drawInfoPanel() {
|
||
|
|
M5.Display.fillRoundRect(infoPanel.x, infoPanel.y, infoPanel.w,
|
||
|
|
infoPanel.h, 22, CARD);
|
||
|
|
M5.Display.drawRoundRect(infoPanel.x, infoPanel.y, infoPanel.w,
|
||
|
|
infoPanel.h, 22, BORDER);
|
||
|
|
M5.Display.setTextDatum(top_left);
|
||
|
|
M5.Display.setTextColor(TFT_WHITE);
|
||
|
|
M5.Display.setTextSize(1.25f);
|
||
|
|
M5.Display.drawString("LIVE DEVICE INFO", infoPanel.x + 24,
|
||
|
|
infoPanel.y + 22);
|
||
|
|
M5.Display.drawFastHLine(infoPanel.x + 24, infoPanel.y + 64,
|
||
|
|
infoPanel.w - 48, BORDER);
|
||
|
|
updateInfo();
|
||
|
|
}
|
||
|
|
|
||
|
|
void setBrightness(int32_t x) {
|
||
|
|
x = constrain(x, slider.x, slider.x + slider.w);
|
||
|
|
brightness = static_cast<uint8_t>(
|
||
|
|
map(x, slider.x, slider.x + slider.w, 20, 255));
|
||
|
|
M5.Display.setBrightness(brightness);
|
||
|
|
drawSlider();
|
||
|
|
}
|
||
|
|
|
||
|
|
void activateButton(size_t index) {
|
||
|
|
drawButton(buttons[index], true);
|
||
|
|
delay(55);
|
||
|
|
if (index < 3) {
|
||
|
|
panelColor = buttons[index].color;
|
||
|
|
++pressCount;
|
||
|
|
} else {
|
||
|
|
panelColor = ACCENT;
|
||
|
|
pressCount = 0;
|
||
|
|
}
|
||
|
|
drawColorPanel();
|
||
|
|
drawButton(buttons[index]);
|
||
|
|
Serial.printf("Button %s pressed; count=%lu\n", buttons[index].label,
|
||
|
|
static_cast<unsigned long>(pressCount));
|
||
|
|
}
|
||
|
|
|
||
|
|
void drawScreen() {
|
||
|
|
M5.Display.startWrite();
|
||
|
|
M5.Display.fillScreen(BG);
|
||
|
|
drawHeader();
|
||
|
|
drawColorPanel();
|
||
|
|
for (const auto& button : buttons) drawButton(button);
|
||
|
|
drawInfoPanel();
|
||
|
|
drawSlider();
|
||
|
|
M5.Display.endWrite();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
void setup() {
|
||
|
|
Serial.begin(115200);
|
||
|
|
auto config = M5.config();
|
||
|
|
M5.begin(config);
|
||
|
|
M5.Display.setRotation(1);
|
||
|
|
M5.Display.setColorDepth(16);
|
||
|
|
M5.Display.setBrightness(brightness);
|
||
|
|
drawScreen();
|
||
|
|
Serial.println("M5Stack Tab5 Touch Dashboard ready");
|
||
|
|
}
|
||
|
|
|
||
|
|
void loop() {
|
||
|
|
M5.update();
|
||
|
|
auto touch = M5.Touch.getDetail();
|
||
|
|
if (touch.isPressed()) {
|
||
|
|
lastTouchX = touch.x;
|
||
|
|
lastTouchY = touch.y;
|
||
|
|
Rect sliderHit{slider.x - 20, slider.y - 28, slider.w + 40,
|
||
|
|
slider.h + 56};
|
||
|
|
if (sliderHit.contains(touch.x, touch.y)) {
|
||
|
|
setBrightness(touch.x);
|
||
|
|
} else if (colorPanel.contains(touch.x, touch.y)) {
|
||
|
|
M5.Display.drawCircle(touch.x, touch.y, 16, TFT_WHITE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (touch.wasReleased()) {
|
||
|
|
for (size_t i = 0; i < sizeof(buttons) / sizeof(buttons[0]); ++i) {
|
||
|
|
if (buttons[i].area.contains(touch.x, touch.y)) {
|
||
|
|
activateButton(i);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (millis() - lastInfoUpdate >= 500) {
|
||
|
|
lastInfoUpdate = millis();
|
||
|
|
updateInfo();
|
||
|
|
}
|
||
|
|
delay(8);
|
||
|
|
}
|