From 22e5f9d76770ff7806e2747b9d80a1c9244c43c4 Mon Sep 17 00:00:00 2001 From: Commander1024 Date: Thu, 28 May 2026 22:09:54 +0200 Subject: [PATCH] Implemented rough 1st iteration of dc-load value screen. --- esphome/lab-ble-proxy-owon.h | 166 +++++++++++++++++++++++++---------- esphome/lab-ble-proxy.yaml | 67 +++++++++----- 2 files changed, 167 insertions(+), 66 deletions(-) diff --git a/esphome/lab-ble-proxy-owon.h b/esphome/lab-ble-proxy-owon.h index a217f1d..e47acea 100644 --- a/esphome/lab-ble-proxy-owon.h +++ b/esphome/lab-ble-proxy-owon.h @@ -1,5 +1,5 @@ /* - * ESPHome helper for OWON B35T/B35T+ BLE meter on M5Stack Core 1. + * ESPHome helper for OWON B35T/B35T+ BLE meter on M5Stack Core 2. * Parser is based on the standalone Arduino sketch by Reaper7 * (Beerware license, Revision 42) and Dean Cording's owonb35 notes. */ @@ -361,7 +361,11 @@ class Meter { if (this->selected_button < 6) this->selected_button++; } - void render(Display &it, esphome::display::BaseFont *font) { + void render(esphome::display::Display &it, esphome::display::BaseFont *font, + int display_page = 0, bool atorch_running = false, + float atorch_voltage = NAN, float atorch_current = NAN, float atorch_power = NAN, + float atorch_capacity = NAN, float atorch_energy = NAN, float atorch_temperature = NAN, + const char *atorch_runtime = "--:--:--") { const Color bg(0, 0, 0); const Color fg(210, 210, 210); // Chosen to map to a neutral dark gray in the RGB332 8-bit display palette. @@ -374,54 +378,98 @@ class Meter { const Color green(0, 220, 0); const Color orange(255, 165, 0); - it.fill(bg); - bool status_active = this->connected && this->has_reading; - this->draw_icon_(it, 12, 8, 16, 16, ACCU_BMP, status_active ? (this->low_battery ? red : green) : inactive); - this->draw_icon_(it, 46, 8, 16, 16, BLE_BMP, this->connected ? blue : inactive); - this->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive); - this->label_(it, font, 138, 8, "MAX", status_active && this->max_mode() ? red : inactive); - this->label_(it, font, 178, 8, "MIN", status_active && this->min_mode() ? green : inactive); - this->label_(it, font, 218, 8, "HOLD", status_active && this->hold() ? blue : inactive); - this->label_(it, font, 270, 8, "REL", status_active && this->relative() ? Color(128, 128, 0) : inactive); + if (display_page == 0) { + // --- PAGE 1: OWON Multimeter --- + it.fill(bg); + bool status_active = this->connected && this->has_reading; + this->draw_icon_(it, 12, 8, 16, 16, ACCU_BMP, status_active ? (this->low_battery ? red : green) : inactive); + this->draw_icon_(it, 46, 8, 16, 16, BLE_BMP, this->connected ? blue : inactive); + this->label_(it, font, 86, 8, "AUTO", status_active && this->auto_range() ? fg : inactive); + this->label_(it, font, 138, 8, "MAX", status_active && this->max_mode() ? red : inactive); + this->label_(it, font, 178, 8, "MIN", status_active && this->min_mode() ? green : inactive); + this->label_(it, font, 218, 8, "HOLD", status_active && this->hold() ? blue : inactive); + this->label_(it, font, 270, 8, "REL", status_active && this->relative() ? Color(128, 128, 0) : inactive); - this->label_(it, font, 8, 66, "DC", status_active && this->dc() ? cyan : inactive); - this->label_(it, font, 8, 102, "AC", status_active && this->ac() ? magenta : inactive); + this->label_(it, font, 8, 66, "DC", status_active && this->dc() ? cyan : inactive); + this->label_(it, font, 8, 102, "AC", status_active && this->ac() ? magenta : inactive); - if (!this->connected) { - this->draw_digits_(it, "----", false, inactive); - it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect"); - } else if (!this->has_reading) { - this->draw_digits_(it, "8888", false, inactive); - it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "waiting"); - } else if (this->overload) { - this->draw_digits_(it, " OL ", false, fg); + if (!this->connected) { + this->draw_digits_(it, "----", false, inactive); + it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "scan/connect"); + } else if (!this->has_reading) { + this->draw_digits_(it, "8888", false, inactive); + it.print(160, 148, font, inactive, esphome::display::TextAlign::CENTER, "waiting"); + } else if (this->overload) { + this->draw_digits_(it, " OL ", false, fg); + } else { + char d[5]; + d[0] = this->digit_char_(REGDIG1); + d[1] = this->digit_char_(REGDIG2); + d[2] = this->digit_char_(REGDIG3); + d[3] = this->digit_char_(REGDIG4); + d[4] = 0; + this->draw_digits_(it, d, this->negative(), fg); + this->draw_decimal_points_(it, fg); + } + + if (status_active) { + std::string unit_line = std::string(this->scale()) + this->unit(); + it.print(270, 140, font, yellow, esphome::display::TextAlign::CENTER, unit_line.c_str()); + } + + bool bargraph_active = status_active && !this->overload; + this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active); + this->draw_icon_(it, 300, 148, 16, 16, DIODE_BMP, status_active && this->diode() ? magenta : inactive); + this->draw_icon_(it, 300, 174, 16, 16, BUZZ_BMP, status_active && this->continuity() ? orange : inactive); + + it.filled_rectangle(34, 212, 40, 24, this->write_available ? fg : inactive); + it.filled_rectangle(108, 212, 100, 24, this->write_available ? fg : inactive); + it.filled_rectangle(242, 212, 40, 24, this->write_available ? fg : inactive); + it.print(54, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, "<"); + it.print(158, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, this->selected_button_name()); + it.print(262, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, ">"); } else { - char d[5]; - d[0] = this->digit_char_(REGDIG1); - d[1] = this->digit_char_(REGDIG2); - d[2] = this->digit_char_(REGDIG3); - d[3] = this->digit_char_(REGDIG4); - d[4] = 0; - this->draw_digits_(it, d, this->negative(), fg); - this->draw_decimal_points_(it, fg); + // --- PAGE 2: Atorch DL24 DC load --- + it.fill(bg); + const Color panel(18, 24, 32); + const Color panel_dim(10, 14, 20); + const Color border(55, 70, 86); + const Color white(245, 245, 245); + + char voltage_text[24]; + char current_text[24]; + char power_text[24]; + char capacity_text[24]; + char energy_text[24]; + char temperature_text[24]; + this->format_metric_(voltage_text, sizeof(voltage_text), atorch_voltage, "V", 2); + this->format_metric_(current_text, sizeof(current_text), atorch_current, "A", 3); + this->format_metric_(power_text, sizeof(power_text), atorch_power, "W", 2); + this->format_metric_(capacity_text, sizeof(capacity_text), atorch_capacity, "Ah", 3); + this->format_metric_(energy_text, sizeof(energy_text), atorch_energy, "Wh", 3); + this->format_metric_(temperature_text, sizeof(temperature_text), atorch_temperature, "°C", 1); + + it.filled_rectangle(0, 0, 320, 30, panel_dim); + it.print(10, 7, font, cyan, esphome::display::TextAlign::TOP_LEFT, "ATORCH DL24"); + it.filled_rectangle(230, 6, 80, 18, atorch_running ? green : inactive); + it.print(270, 8, font, bg, esphome::display::TextAlign::TOP_CENTER, atorch_running ? "RUNNING" : "STOPPED"); + + this->draw_metric_card_(it, font, 10, 42, 145, 70, "VOLTAGE", voltage_text, cyan, panel, border); + this->draw_metric_card_(it, font, 165, 42, 145, 70, "CURRENT", current_text, orange, panel, border); + this->draw_metric_card_(it, font, 10, 122, 145, 70, "POWER", power_text, yellow, panel, border); + this->draw_metric_card_(it, font, 165, 122, 145, 70, "TEMP", temperature_text, magenta, panel, border); + + it.filled_rectangle(10, 202, 300, 30, panel_dim); + it.filled_rectangle(10, 202, 300, 1, border); + it.filled_rectangle(10, 231, 300, 1, border); + it.filled_rectangle(10, 202, 1, 30, border); + it.filled_rectangle(309, 202, 1, 30, border); + it.print(24, 209, font, inactive, esphome::display::TextAlign::TOP_LEFT, "CAP"); + it.print(67, 209, font, white, esphome::display::TextAlign::TOP_LEFT, capacity_text); + it.print(143, 209, font, inactive, esphome::display::TextAlign::TOP_LEFT, "ENERGY"); + it.print(206, 209, font, white, esphome::display::TextAlign::TOP_LEFT, energy_text); + it.print(306, 209, font, green, esphome::display::TextAlign::TOP_RIGHT, atorch_runtime); } - - if (status_active) { - std::string unit_line = std::string(this->scale()) + this->unit(); - it.print(270, 140, font, yellow, esphome::display::TextAlign::CENTER, unit_line.c_str()); - } - - bool bargraph_active = status_active && !this->overload; - this->draw_bargraph_(it, bargraph_active ? this->digits_from_buffer_() : 0, bargraph_active); - this->draw_icon_(it, 300, 148, 16, 16, DIODE_BMP, status_active && this->diode() ? magenta : inactive); - this->draw_icon_(it, 300, 174, 16, 16, BUZZ_BMP, status_active && this->continuity() ? orange : inactive); - - it.filled_rectangle(34, 212, 40, 24, this->write_available ? fg : inactive); - it.filled_rectangle(108, 212, 100, 24, this->write_available ? fg : inactive); - it.filled_rectangle(242, 212, 40, 24, this->write_available ? fg : inactive); - it.print(54, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, "<"); - it.print(158, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, this->selected_button_name()); - it.print(262, 216, font, bg, esphome::display::TextAlign::TOP_CENTER, ">"); } private: @@ -551,6 +599,32 @@ class Meter { return (c >= '0' && c <= '9') ? static_cast(c) : ' '; } + void format_metric_(char *buffer, size_t size, float value, const char *unit, uint8_t decimals) const { + if (!std::isfinite(value)) { + snprintf(buffer, size, "-- %s", unit); + return; + } + char format[12]; + snprintf(format, sizeof(format), "%%.%uf %%s", decimals); + snprintf(buffer, size, format, value, unit); + } + + void draw_metric_card_(Display &it, esphome::display::BaseFont *font, int x, int y, int w, int h, + const char *title, const char *value, Color accent, Color fill, Color border) { + const Color bg(0, 0, 0); + const Color fg(235, 235, 235); + const Color inactive(90, 100, 110); + it.filled_rectangle(x, y, w, h, fill); + it.filled_rectangle(x, y, w, 2, accent); + it.filled_rectangle(x, y + h - 1, w, 1, border); + it.filled_rectangle(x, y, 1, h, border); + it.filled_rectangle(x + w - 1, y, 1, h, border); + it.print(x + 10, y + 10, font, inactive, esphome::display::TextAlign::TOP_LEFT, title); + it.print(x + w / 2, y + 38, font, fg, esphome::display::TextAlign::CENTER, value); + it.filled_rectangle(x + 10, y + h - 11, w - 20, 3, bg); + it.filled_rectangle(x + 10, y + h - 11, w - 20, 1, accent); + } + void label_(Display &it, esphome::display::BaseFont *font, int x, int y, const char *text, Color color) { it.print(x, y, font, color, esphome::display::TextAlign::TOP_LEFT, text); } diff --git a/esphome/lab-ble-proxy.yaml b/esphome/lab-ble-proxy.yaml index 7f5ea67..2e5ebf3 100644 --- a/esphome/lab-ble-proxy.yaml +++ b/esphome/lab-ble-proxy.yaml @@ -1,6 +1,7 @@ # Derived work based on https://github.com/reaper7/M5Stack_BLE_client_Owon_B35T by reaper7. -# AI (ChatGPT) has been used to adopt the Arduino sketch to ESPHome. +# AI (ChatGPT (GPT5.5), Qwen3.6) has been used to adopt the Arduino sketch to ESPHome. # Ported to M5Stack Core2 due to memory constraints. +# Integrated atorch ble proxy functionality from https://github.com/syssi/esphome-atorch-dl24 by syssi. substitutions: name: "lab-ble-proxy" @@ -68,6 +69,12 @@ wifi: captive_portal: +globals: + - id: display_page + type: int + initial_value: "0" + restore_value: no + interval: - interval: 10s then: @@ -146,6 +153,7 @@ font: "-", ".", "/", + ":", "0", "1", "2", @@ -182,6 +190,7 @@ font: "Y", "Z", "a", + "b", "c", "d", "e", @@ -190,9 +199,11 @@ font: "h", "i", "k", + "l", "m", "n", "o", + "p", "r", "s", "t", @@ -212,7 +223,19 @@ display: model: M5CORE2 update_interval: 500ms lambda: |- - owon_meter.render(it, id(meter_font)); + owon_meter.render( + it, + id(meter_font), + id(display_page), + id(atorch_running).state, + id(atorch_voltage).has_state() ? id(atorch_voltage).state : NAN, + id(atorch_current).has_state() ? id(atorch_current).state : NAN, + id(atorch_power).has_state() ? id(atorch_power).state : NAN, + id(atorch_capacity).has_state() ? id(atorch_capacity).state : NAN, + id(atorch_energy).has_state() ? id(atorch_energy).state : NAN, + id(atorch_temperature).has_state() ? id(atorch_temperature).state : NAN, + id(atorch_runtime_formatted).has_state() ? id(atorch_runtime_formatted).state.c_str() : "--:--:--" + ); touchscreen: - platform: ft63x6 @@ -220,6 +243,18 @@ touchscreen: display: lcd binary_sensor: + - platform: touchscreen + touchscreen_id: touch + id: btn_toggle_page + x_min: 70 + x_max: 250 + y_min: 45 + y_max: 195 + on_press: + then: + - lambda: |- + id(display_page) = 1 - id(display_page); + id(lcd).update(); - platform: touchscreen id: button_a touchscreen_id: touch @@ -296,11 +331,6 @@ binary_sensor: lambda: |- return owon_meter.low_battery; - - platform: atorch_dl24 - atorch_dl24_id: atorch0 - running: - name: "${friendly_name} Atorch Running" - sensor: - platform: ble_client type: characteristic @@ -323,20 +353,22 @@ sensor: atorch_dl24_id: atorch0 voltage: name: "${friendly_name} Atorch Voltage" + id: atorch_voltage current: name: "${friendly_name} Atorch Current" + id: atorch_current power: name: "${friendly_name} Atorch Power" + id: atorch_power capacity: name: "${friendly_name} Atorch Capacity" - energy: - name: "${friendly_name} Atorch Energy" + id: atorch_capacity temperature: name: "${friendly_name} Atorch Temperature" + id: atorch_temperature dim_backlight: name: "${friendly_name} Atorch Dim Backlight" - runtime: - name: "${friendly_name} Atorch Runtime" + id: atorch_dim_backlight text_sensor: - platform: template @@ -355,11 +387,6 @@ text_sensor: lambda: |- return owon_meter.mode_text(); - - platform: atorch_dl24 - atorch_dl24_id: atorch0 - runtime_formatted: - name: "${friendly_name} Atorch Runtime Formatted" - button: - platform: atorch_dl24 atorch_dl24_id: atorch0 @@ -400,7 +427,7 @@ button: value: !lambda "return std::vector({2, 0x01});" - platform: template - name: "OWON HLD/LIG" + name: "OWON HOLD | LIGHT" id: owon_btn_hold on_press: - ble_client.ble_write: @@ -410,7 +437,7 @@ button: value: !lambda "return std::vector({3, 0x01});" - platform: template - name: "OWON REL/BT" + name: "OWON RELATIVE | BT" id: owon_btn_rel on_press: - ble_client.ble_write: @@ -420,7 +447,7 @@ button: value: !lambda "return std::vector({4, 0x01});" - platform: template - name: "OWON HZ/DUTY" + name: "OWON HZ | DUTY" id: owon_btn_hz on_press: - ble_client.ble_write: @@ -430,7 +457,7 @@ button: value: !lambda "return std::vector({5, 0x01});" - platform: template - name: "OWON MAX/MIN" + name: "OWON MAX | MIN" id: owon_btn_maxmin on_press: - ble_client.ble_write: