Pretty display
This commit is contained in:
+42
-10
@@ -4,16 +4,33 @@
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Callback signature: void(device_id, cloud_alert, defrost_mode)
|
||||
using DisplayUpdateCallback = std::function<void(const std::string &device_id, bool cloud_alert, bool defrost_mode)>;
|
||||
struct MicrowaveData {
|
||||
std::string id;
|
||||
std::string name;
|
||||
int state;
|
||||
bool paused;
|
||||
int remaining_time;
|
||||
int progress;
|
||||
};
|
||||
|
||||
// Global states parsed from the API
|
||||
static std::vector<MicrowaveData> g_microwaves;
|
||||
static bool g_cloud_alert = false;
|
||||
static std::string g_update_time = "--:--";
|
||||
static bool g_received_update = false;
|
||||
|
||||
// Callback signature for refreshing UI
|
||||
using DisplayUpdateCallback = std::function<void()>;
|
||||
static DisplayUpdateCallback g_display_callback = nullptr;
|
||||
|
||||
class DisplayApiHandler : public esphome::web_server_idf::AsyncWebHandler {
|
||||
public:
|
||||
bool canHandle(esphome::web_server_idf::AsyncWebServerRequest *request) const override {
|
||||
return request->url_to() == "/api/display";
|
||||
// Provide the 513-byte buffer expected by ESPHome's new memory-safe API
|
||||
char url_buf[513];
|
||||
return request->url_to(url_buf) == "/api/display";
|
||||
}
|
||||
|
||||
void handleRequest(esphome::web_server_idf::AsyncWebServerRequest *request) override {
|
||||
@@ -24,16 +41,31 @@ class DisplayApiHandler : public esphome::web_server_idf::AsyncWebHandler {
|
||||
std::string body((char *)data, len);
|
||||
|
||||
esphome::json::parse_json(body, [](JsonObject root) -> bool {
|
||||
std::string device_id = root["device_id"] | "unknown";
|
||||
bool cloud_alert = root["cloud_alert"] | false;
|
||||
bool defrost_mode = root["defrost_mode"] | false;
|
||||
g_cloud_alert = root["cloud_alert"] | false;
|
||||
g_update_time = root["update_time"] | "--:--";
|
||||
|
||||
g_microwaves.clear();
|
||||
JsonArray mw_array = root["microwaves"];
|
||||
|
||||
for (JsonObject mw : mw_array) {
|
||||
MicrowaveData md;
|
||||
md.id = mw["id"] | "unknown";
|
||||
md.name = mw["name"] | "Microwave";
|
||||
md.state = mw["state"] | 4;
|
||||
md.paused = mw["paused"] | false;
|
||||
md.remaining_time = mw["remaining_time"] | 0;
|
||||
md.progress = mw["progress"] | 0;
|
||||
|
||||
g_microwaves.push_back(md);
|
||||
}
|
||||
|
||||
g_received_update = true;
|
||||
|
||||
ESP_LOGI("custom_api", "Received update for %s (Alert: %d, Defrost: %d)",
|
||||
device_id.c_str(), cloud_alert, defrost_mode);
|
||||
ESP_LOGI("custom_api", "Received update. Cloud Alert: %d, Microwaves: %d",
|
||||
g_cloud_alert, g_microwaves.size());
|
||||
|
||||
// Execute the callback passed from YAML
|
||||
if (g_display_callback != nullptr) {
|
||||
g_display_callback(device_id, cloud_alert, defrost_mode);
|
||||
g_display_callback();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -10,30 +10,11 @@ esphome:
|
||||
priority: -100.0
|
||||
then:
|
||||
- lambda: |-
|
||||
setup_display_api([](const std::string &dev_id, bool alert, bool defrost) {
|
||||
// Update global state
|
||||
id(cloud_alert) = alert;
|
||||
id(defrost_mode) = defrost;
|
||||
|
||||
// Set status message text
|
||||
std::string msg = "Microwave " + dev_id;
|
||||
id(status_message).publish_state(msg.c_str());
|
||||
|
||||
setup_display_api([]() {
|
||||
// Force immediate e-Paper refresh!
|
||||
id(my_display).update();
|
||||
});
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# State Globals
|
||||
# -------------------------------------------------------------------
|
||||
globals:
|
||||
- id: cloud_alert
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: defrost_mode
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
|
||||
web_server:
|
||||
port: 5000
|
||||
|
||||
@@ -102,22 +83,13 @@ openthread:
|
||||
# Template Text Sensor
|
||||
# -------------------------------------------------------------------
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Display Status Message"
|
||||
id: status_message
|
||||
- platform: openthread_info
|
||||
ip_address:
|
||||
name: "Thread IP"
|
||||
id: thread_ip
|
||||
role:
|
||||
name: "Thread Role"
|
||||
id: thread_role
|
||||
channel:
|
||||
name: "Thread Channel"
|
||||
id: thread_channel
|
||||
rloc16:
|
||||
name: "Thread RLOC16"
|
||||
id: thread_rloc16
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Hardware SPI Pin Mapping (ESP32-H2)
|
||||
@@ -139,12 +111,18 @@ font:
|
||||
- file:
|
||||
type: gfonts
|
||||
family: Overpass
|
||||
weight: 400
|
||||
weight: 600
|
||||
id: font_body
|
||||
size: 14
|
||||
- file:
|
||||
type: gfonts
|
||||
family: Overpass
|
||||
weight: 500
|
||||
id: font_small
|
||||
size: 12
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Waveshare 2.13-inch e-Paper Display (V4 panel = 2.13inv3 in ESPHome)
|
||||
# Waveshare 2.13-inch e-Paper Display (250x122 standard resolution)
|
||||
# -------------------------------------------------------------------
|
||||
display:
|
||||
- platform: waveshare_epaper
|
||||
@@ -157,35 +135,75 @@ display:
|
||||
update_interval: 30min
|
||||
id: my_display
|
||||
lambda: |-
|
||||
if (id(status_message).has_state()) {
|
||||
// Header
|
||||
it.print(5, 3, id(font_header), id(status_message).state.c_str());
|
||||
it.line(0, 26, it.get_width(), 26);
|
||||
|
||||
// Mode Status
|
||||
if (id(defrost_mode)) {
|
||||
it.print(5, 32, id(font_header), "MODE: DEFROST");
|
||||
} else {
|
||||
it.print(5, 32, id(font_header), "MODE: NORMAL");
|
||||
}
|
||||
|
||||
// Cloud Alert Status Box
|
||||
if (id(cloud_alert)) {
|
||||
it.filled_rectangle(5, 60, 240, 24, COLOR_OFF); // Inverse background if supported, or frame box
|
||||
it.print(10, 62, id(font_body), "CLOUD ALERT ACTIVE");
|
||||
} else {
|
||||
it.print(10, 62, id(font_body), "System Normal");
|
||||
}
|
||||
|
||||
// Footer: Thread Info
|
||||
it.line(0, 95, it.get_width(), 95);
|
||||
it.printf(5, 100, id(font_body), "Role: %s | Ch: %s",
|
||||
id(thread_role).state.c_str(),
|
||||
id(thread_channel).state.c_str());
|
||||
} else {
|
||||
// --- STARTUP SCREEN ---
|
||||
if (!g_received_update) {
|
||||
int center_x = it.get_width() / 2;
|
||||
int center_y = it.get_height() / 2;
|
||||
|
||||
it.printf(center_x, center_y - 10, id(font_header), TextAlign::CENTER, "SmartWave");
|
||||
it.printf(center_x, center_y + 15, id(font_body), TextAlign::CENTER, "Waiting for Pi updates...");
|
||||
}
|
||||
it.printf(center_x, center_y + 15, id(font_body), TextAlign::CENTER, "Waiting for updates...");
|
||||
return;
|
||||
}
|
||||
|
||||
// --- MICROWAVE LAYOUT ---
|
||||
int y_offset = 0;
|
||||
for (const auto& mw : g_microwaves) {
|
||||
// Name
|
||||
it.print(0, y_offset, id(font_header), mw.name.c_str());
|
||||
|
||||
// Decode State Text
|
||||
std::string state_str;
|
||||
switch(mw.state) {
|
||||
case 0: state_str = mw.paused ? "PAUSED" : "COOKING"; break;
|
||||
case 1: state_str = "STIRRING REQ."; break;
|
||||
case 2: state_str = "DONE"; break;
|
||||
case 3: state_str = "ALERT"; break;
|
||||
case 4: state_str = "IDLE"; break;
|
||||
default: state_str = "UNKNOWN"; break;
|
||||
}
|
||||
|
||||
// Print Status (Top Right Align)
|
||||
it.printf(it.get_width(), y_offset + 5, id(font_body), TextAlign::TOP_RIGHT, "%s", state_str.c_str());
|
||||
|
||||
// Process active/cooking visualisations
|
||||
if (mw.state == 0) {
|
||||
int bar_y = y_offset + 28;
|
||||
int bar_w = 175;
|
||||
int bar_h = 16;
|
||||
|
||||
// Draw progress bar outline & fill
|
||||
it.rectangle(0, bar_y, bar_w, bar_h);
|
||||
if (mw.progress > 0) {
|
||||
int fill_w = (mw.progress * bar_w) / 100;
|
||||
it.filled_rectangle(0, bar_y, fill_w, bar_h);
|
||||
}
|
||||
|
||||
// Draw time remaining (or paused state)
|
||||
if (mw.paused) {
|
||||
it.printf(185, bar_y + 1, id(font_body), "PAUSED");
|
||||
} else {
|
||||
int mins = mw.remaining_time / 60;
|
||||
int secs = mw.remaining_time % 60;
|
||||
it.printf(185, bar_y + 1, id(font_body), "%02d:%02d", mins, secs);
|
||||
}
|
||||
}
|
||||
else if (mw.state == 1) { // Stirring Required
|
||||
it.filled_rectangle(0, y_offset + 28, 250, 20);
|
||||
it.printf(125, y_offset + 28, id(font_body), COLOR_OFF, TextAlign::TOP_CENTER, "ACTION REQUIRED: STIR");
|
||||
}
|
||||
|
||||
// Move cursor down (if we have more than 1 microwave on future larger screens)
|
||||
y_offset += 60;
|
||||
}
|
||||
|
||||
// --- SYSTEM FOOTER ---
|
||||
// Line separator right above footer
|
||||
it.line(0, 102, it.get_width(), 102);
|
||||
|
||||
// Bottom Left: Cloud Reachability & Last Update Time
|
||||
std::string cloud_status = g_cloud_alert ? "Cloud: OK" : "Cloud: ERR";
|
||||
it.printf(0, 105, id(font_small), "%s | %s", cloud_status.c_str(), g_update_time.c_str());
|
||||
|
||||
// Bottom Right: OpenThread Diagnostics
|
||||
std::string role = id(thread_role).state;
|
||||
it.printf(it.get_width(), 105, id(font_small), TextAlign::TOP_RIGHT,
|
||||
"Th: %s (Ch %s)", role.c_str(), id(thread_channel).state.c_str());
|
||||
Reference in New Issue
Block a user