From 12f8ba2defdca255897ba18ebbbba3334f7b80c4 Mon Sep 17 00:00:00 2001 From: Joshua King Date: Tue, 20 Oct 2026 20:46:41 -0400 Subject: [PATCH] Show a grey ring behind black filament gauges so they stay visible. Co-authored-by: Cursor --- esphome/bambu-display/README.md | 3 +- esphome/bambu-display/dashboard.h | 15 +- esphome/bambu-display/generate.py | 11 +- esphome/bambu-display/generated.yaml | 168 ++++++++++++++++++++++- esphome/bambu-display/test_dashboard.cpp | 6 + esphome/bambu-display/test_generate.py | 16 ++- 6 files changed, 208 insertions(+), 11 deletions(-) diff --git a/esphome/bambu-display/README.md b/esphome/bambu-display/README.md index e87e67c..9005acd 100644 --- a/esphome/bambu-display/README.md +++ b/esphome/bambu-display/README.md @@ -18,7 +18,8 @@ Home Assistant must be running and its printer integrations must have current pr The configured swipe order is: 1. Print Status Overview — one steam gauge per printer. Each ring uses the - color of that printer's active filament. The center shows the percent + color of that printer's active filament. Black filament keeps a grey ring + behind the gauge so it stays visible on the black page. The center shows the percent complete in white, with the printer name and estimated finish time underneath. The local time is in the top-right corner. Tap a gauge to open that printer. 2. PrintsCharming — P1S, Office, both four-slot AMS units. diff --git a/esphome/bambu-display/dashboard.h b/esphome/bambu-display/dashboard.h index 8a64bbc..9111e4b 100644 --- a/esphome/bambu-display/dashboard.h +++ b/esphome/bambu-display/dashboard.h @@ -17,6 +17,7 @@ constexpr uint32_t MUTED = 0xAEB3B7; constexpr uint32_t RED = 0xFF737B; constexpr uint32_t AMBER = 0xF2C572; constexpr uint32_t TRACK = 0x19232B; +constexpr uint32_t RING_BACKING = 0x6E767E; inline std::string lower(std::string text) { for (char &c : text) c = static_cast(std::tolower(static_cast(c))); @@ -104,6 +105,11 @@ inline uint32_t filament_color(std::string value) { return rgb; } +// A black ring disappears on the black page. Anything this dark needs a grey arc behind it. +inline bool filament_is_black(uint32_t color) { + return ((color >> 16) & 0xFF) <= 0x28 && ((color >> 8) & 0xFF) <= 0x28 && (color & 0xFF) <= 0x28; +} + struct Tray { std::string label, type, color, active; }; @@ -299,7 +305,7 @@ struct Widgets { lv_obj_t *elapsed = nullptr; }; struct OverviewWidgets { - lv_obj_t *arc, *value, *finish; + lv_obj_t *arc, *value, *finish, *backing; }; inline void set_text(lv_obj_t *label, const std::string &text) { @@ -376,6 +382,13 @@ inline void render_overview(const Snapshot &data, const OverviewWidgets &widgets lv_obj_set_style_arc_color(widgets.arc, lv_color_hex(filament), LV_PART_MAIN); lv_obj_set_style_arc_color(widgets.arc, lv_color_hex(filament), LV_PART_INDICATOR); lv_arc_set_value(widgets.arc, 100); + if (filament_is_black(filament)) { + lv_obj_set_style_arc_color(widgets.backing, lv_color_hex(RING_BACKING), LV_PART_MAIN); + lv_obj_set_style_arc_color(widgets.backing, lv_color_hex(RING_BACKING), LV_PART_INDICATOR); + lv_obj_clear_flag(widgets.backing, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_add_flag(widgets.backing, LV_OBJ_FLAG_HIDDEN); + } set_text(widgets.value, number(shown)); lv_obj_set_style_text_color(widgets.value, lv_color_hex(0xFFFFFF), 0); set_text(widgets.finish, live && !data.finish_time.empty() ? data.finish_time : "--"); diff --git a/esphome/bambu-display/generate.py b/esphome/bambu-display/generate.py index 72bb865..a28734a 100644 --- a/esphome/bambu-display/generate.py +++ b/esphome/bambu-display/generate.py @@ -32,6 +32,7 @@ ELEGOO_FDM = {'nozzle': 'nozzle_temperature', 'bed': 'bed_temperature', ELEGOO_RESIN = {'uv': 'uv_led_temp', 'elapsed': 'current_print_time'} BG, CARD, TRACK, TEXT_COLOR, MUTED, GREEN = ( 0x000000, 0x000000, 0x19232B, 0xEEEEEE, 0xAEB3B7, 0x00FF00) +RING_BACKING = 0x6E767E ORANGE, CYAN = 0xFFAA00, 0x00E5E5 @@ -272,7 +273,15 @@ def overview_gauge(printer, x, y, arc): inner = max(10, int(round(13 * scale))) inner_x = x + int(round(8 * scale)) number_y = y + int(round(arc * 0.30)) + pad = 8 return [ + {'arc': {'id': f'{pid}_overview_ring', 'x': inner_x - pad // 2, 'y': y - pad // 2, + 'width': arc + pad, 'height': arc + pad, 'min_value': 0, 'max_value': 100, + 'value': 100, 'start_angle': 135, 'end_angle': 45, + 'arc_width': inner + pad, 'arc_color': RING_BACKING, + 'indicator': {'arc_color': RING_BACKING, 'arc_width': inner + pad}, + 'adjustable': False, 'hidden': True, 'gesture_bubble': True, 'clickable': True, + 'on_click': open_page}}, {'arc': {'id': f'{pid}_overview_arc', 'x': inner_x, 'y': y, 'width': arc, 'height': arc, 'min_value': 0, 'max_value': 100, 'value': 100, 'start_angle': 135, 'end_angle': 45, 'arc_width': inner, 'arc_color': GREEN, @@ -324,7 +333,7 @@ def fleet_refresh_lines(snapshots): lines.extend(snapshot) lines.append('bambu_display::render_overview(data, {' + ', '.join([ f'id({pid}_overview_arc)', f'id({pid}_overview_value)', - f'id({pid}_overview_finish)']) + '});') + f'id({pid}_overview_finish)', f'id({pid}_overview_ring)']) + '});') lines.append('}') lines += [ '{', diff --git a/esphome/bambu-display/generated.yaml b/esphome/bambu-display/generated.yaml index d6e082d..db39cda 100644 --- a/esphome/bambu-display/generated.yaml +++ b/esphome/bambu-display/generated.yaml @@ -1531,7 +1531,7 @@ script: data.trays.push_back({"B2", id(prints_charming_tray_5_type).state, id(prints_charming_tray_5_color).state, id(prints_charming_tray_5_active).state}); data.trays.push_back({"B3", id(prints_charming_tray_6_type).state, id(prints_charming_tray_6_color).state, id(prints_charming_tray_6_active).state}); data.trays.push_back({"B4", id(prints_charming_tray_7_type).state, id(prints_charming_tray_7_color).state, id(prints_charming_tray_7_active).state}); - bambu_display::render_overview(data, {id(prints_charming_overview_arc), id(prints_charming_overview_value), id(prints_charming_overview_finish)}); + bambu_display::render_overview(data, {id(prints_charming_overview_arc), id(prints_charming_overview_value), id(prints_charming_overview_finish), id(prints_charming_overview_ring)}); } { bambu_display::Snapshot data; @@ -1565,7 +1565,7 @@ script: if (clock.is_valid() && seconds_left >= 0) data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M"); data.trays.push_back({"EXT", id(polly_pocket_tray_0_type).state, id(polly_pocket_tray_0_color).state, id(polly_pocket_tray_0_active).state}); - bambu_display::render_overview(data, {id(polly_pocket_overview_arc), id(polly_pocket_overview_value), id(polly_pocket_overview_finish)}); + bambu_display::render_overview(data, {id(polly_pocket_overview_arc), id(polly_pocket_overview_value), id(polly_pocket_overview_finish), id(polly_pocket_overview_ring)}); } { bambu_display::Snapshot data; @@ -1602,7 +1602,7 @@ script: data.trays.push_back({"A2", id(ds_turd_flinger_tray_1_type).state, id(ds_turd_flinger_tray_1_color).state, id(ds_turd_flinger_tray_1_active).state}); data.trays.push_back({"A3", id(ds_turd_flinger_tray_2_type).state, id(ds_turd_flinger_tray_2_color).state, id(ds_turd_flinger_tray_2_active).state}); data.trays.push_back({"A4", id(ds_turd_flinger_tray_3_type).state, id(ds_turd_flinger_tray_3_color).state, id(ds_turd_flinger_tray_3_active).state}); - bambu_display::render_overview(data, {id(ds_turd_flinger_overview_arc), id(ds_turd_flinger_overview_value), id(ds_turd_flinger_overview_finish)}); + bambu_display::render_overview(data, {id(ds_turd_flinger_overview_arc), id(ds_turd_flinger_overview_value), id(ds_turd_flinger_overview_finish), id(ds_turd_flinger_overview_ring)}); } { bambu_display::Snapshot data; @@ -1639,7 +1639,7 @@ script: data.trays.push_back({"A2", id(jordyns_layer_slayer_tray_1_type).state, id(jordyns_layer_slayer_tray_1_color).state, id(jordyns_layer_slayer_tray_1_active).state}); data.trays.push_back({"A3", id(jordyns_layer_slayer_tray_2_type).state, id(jordyns_layer_slayer_tray_2_color).state, id(jordyns_layer_slayer_tray_2_active).state}); data.trays.push_back({"A4", id(jordyns_layer_slayer_tray_3_type).state, id(jordyns_layer_slayer_tray_3_color).state, id(jordyns_layer_slayer_tray_3_active).state}); - bambu_display::render_overview(data, {id(jordyns_layer_slayer_overview_arc), id(jordyns_layer_slayer_overview_value), id(jordyns_layer_slayer_overview_finish)}); + bambu_display::render_overview(data, {id(jordyns_layer_slayer_overview_arc), id(jordyns_layer_slayer_overview_value), id(jordyns_layer_slayer_overview_finish), id(jordyns_layer_slayer_overview_ring)}); } { bambu_display::Snapshot data; @@ -1686,7 +1686,7 @@ script: data.trays.push_back({"B2", id(constipation_orion_tray_5_type).state, id(constipation_orion_tray_5_color).state, id(constipation_orion_tray_5_active).state}); data.trays.push_back({"B3", id(constipation_orion_tray_6_type).state, id(constipation_orion_tray_6_color).state, id(constipation_orion_tray_6_active).state}); data.trays.push_back({"B4", id(constipation_orion_tray_7_type).state, id(constipation_orion_tray_7_color).state, id(constipation_orion_tray_7_active).state}); - bambu_display::render_overview(data, {id(constipation_orion_overview_arc), id(constipation_orion_overview_value), id(constipation_orion_overview_finish)}); + bambu_display::render_overview(data, {id(constipation_orion_overview_arc), id(constipation_orion_overview_value), id(constipation_orion_overview_finish), id(constipation_orion_overview_ring)}); } { bambu_display::Snapshot data; @@ -1716,7 +1716,7 @@ script: const auto clock = id(ha_time).now(); if (clock.is_valid() && seconds_left >= 0) data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M"); - bambu_display::render_overview(data, {id(centauri_carbon_overview_arc), id(centauri_carbon_overview_value), id(centauri_carbon_overview_finish)}); + bambu_display::render_overview(data, {id(centauri_carbon_overview_arc), id(centauri_carbon_overview_value), id(centauri_carbon_overview_finish), id(centauri_carbon_overview_ring)}); } { bambu_display::Snapshot data; @@ -1742,7 +1742,7 @@ script: const auto clock = id(ha_time).now(); if (clock.is_valid() && seconds_left >= 0) data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M"); - bambu_display::render_overview(data, {id(saturn_overview_arc), id(saturn_overview_value), id(saturn_overview_finish)}); + bambu_display::render_overview(data, {id(saturn_overview_arc), id(saturn_overview_value), id(saturn_overview_finish), id(saturn_overview_ring)}); } { const auto clock = id(ha_time).now(); @@ -1841,6 +1841,28 @@ lvgl: gesture_bubble: true id: fleet_clock text_align: RIGHT + - arc: + id: prints_charming_overview_ring + x: 23 + y: 42 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: prints_charming_page - arc: id: prints_charming_overview_arc x: 27 @@ -1921,6 +1943,28 @@ lvgl: clickable: true on_click: - lvgl.page.show: prints_charming_page + - arc: + id: polly_pocket_overview_ring + x: 223 + y: 42 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: polly_pocket_page - arc: id: polly_pocket_overview_arc x: 227 @@ -2001,6 +2045,28 @@ lvgl: clickable: true on_click: - lvgl.page.show: polly_pocket_page + - arc: + id: ds_turd_flinger_overview_ring + x: 423 + y: 42 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: ds_turd_flinger_page - arc: id: ds_turd_flinger_overview_arc x: 427 @@ -2081,6 +2147,28 @@ lvgl: clickable: true on_click: - lvgl.page.show: ds_turd_flinger_page + - arc: + id: jordyns_layer_slayer_overview_ring + x: 623 + y: 42 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: jordyns_layer_slayer_page - arc: id: jordyns_layer_slayer_overview_arc x: 627 @@ -2161,6 +2249,28 @@ lvgl: clickable: true on_click: - lvgl.page.show: jordyns_layer_slayer_page + - arc: + id: constipation_orion_overview_ring + x: 123 + y: 235 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: constipation_orion_page - arc: id: constipation_orion_overview_arc x: 127 @@ -2241,6 +2351,28 @@ lvgl: clickable: true on_click: - lvgl.page.show: constipation_orion_page + - arc: + id: centauri_carbon_overview_ring + x: 323 + y: 235 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: centauri_carbon_page - arc: id: centauri_carbon_overview_arc x: 327 @@ -2321,6 +2453,28 @@ lvgl: clickable: true on_click: - lvgl.page.show: centauri_carbon_page + - arc: + id: saturn_overview_ring + x: 523 + y: 235 + width: 153 + height: 153 + min_value: 0 + max_value: 100 + value: 100 + start_angle: 135 + end_angle: 45 + arc_width: 20 + arc_color: 7239294 + indicator: + arc_color: 7239294 + arc_width: 20 + adjustable: false + hidden: true + gesture_bubble: true + clickable: true + on_click: + - lvgl.page.show: saturn_page - arc: id: saturn_overview_arc x: 527 diff --git a/esphome/bambu-display/test_dashboard.cpp b/esphome/bambu-display/test_dashboard.cpp index de9adb0..6389051 100644 --- a/esphome/bambu-display/test_dashboard.cpp +++ b/esphome/bambu-display/test_dashboard.cpp @@ -130,6 +130,12 @@ int main() { assert(data.active_filament_color() == 0xFF8800); data.trays.clear(); assert(data.active_filament_color() == GREEN); + assert(filament_is_black(0x000000)); + assert(filament_is_black(filament_color("#000000FF"))); + assert(filament_is_black(0x202020)); + assert(!filament_is_black(0xFFFFFF)); + assert(!filament_is_black(GREEN)); + assert(!filament_is_black(0xFF8800)); FleetCounts counts; data.connected = true; diff --git a/esphome/bambu-display/test_generate.py b/esphome/bambu-display/test_generate.py index a1270d0..bf7d67f 100644 --- a/esphome/bambu-display/test_generate.py +++ b/esphome/bambu-display/test_generate.py @@ -152,9 +152,22 @@ class FleetTests(unittest.TestCase): page = package['lvgl']['pages'][0] self.assertEqual(page['id'], 'fleet_page') self.assertFalse(page.get('skip')) - arcs = [widget['arc'] for widget in page['widgets'] if 'arc' in widget] + arcs = [widget['arc'] for widget in page['widgets'] + if widget.get('arc', {}).get('id', '').endswith('_overview_arc')] self.assertEqual([arc['id'][:-len('_overview_arc')] for arc in arcs], [printer['id'] for printer in self.printers]) + backings = [widget['arc'] for widget in page['widgets'] + if widget.get('arc', {}).get('id', '').endswith('_overview_ring')] + self.assertEqual([arc['id'][:-len('_overview_ring')] for arc in backings], + [printer['id'] for printer in self.printers]) + for backing, arc in zip(backings, arcs): + self.assertTrue(backing['hidden']) + self.assertEqual(backing['arc_color'], 0x6E767E) + self.assertGreater(backing['width'], arc['width']) + self.assertGreaterEqual(backing['x'], 0) + self.assertGreaterEqual(backing['y'], 0) + self.assertLessEqual(backing['x'] + backing['width'], 800) + self.assertLessEqual(backing['y'] + backing['height'], 480) for arc, printer in zip(arcs, self.printers): self.assertEqual(arc['on_click'], [{'lvgl.page.show': printer['id'] + '_page'}]) self.assertGreaterEqual(arc['width'], 120) @@ -166,6 +179,7 @@ class FleetTests(unittest.TestCase): for printer in self.printers: self.assertIn(f'id({printer["id"]}_overview_arc)', code) self.assertIn(f'id({printer["id"]}_overview_finish)', code) + self.assertIn(f'id({printer["id"]}_overview_ring)', code) titles = [next(iter(widget.values())).get('text') for widget in page['widgets']] self.assertIn('Print Status Overview', titles) self.assertNotIn('fleet_summary', {next(iter(widget.values())).get('id') for widget in page['widgets']})