Show a grey ring behind black filament gauges so they stay visible.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Joshua King
2026-10-20 20:46:41 -04:00
co-authored by Cursor
parent 5b8a3f24c2
commit 12f8ba2def
6 changed files with 208 additions and 11 deletions
+2 -1
View File
@@ -18,7 +18,8 @@ Home Assistant must be running and its printer integrations must have current pr
The configured swipe order is: The configured swipe order is:
1. Print Status Overview — one steam gauge per printer. Each ring uses the 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. 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. 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. 2. PrintsCharming — P1S, Office, both four-slot AMS units.
+14 -1
View File
@@ -17,6 +17,7 @@ constexpr uint32_t MUTED = 0xAEB3B7;
constexpr uint32_t RED = 0xFF737B; constexpr uint32_t RED = 0xFF737B;
constexpr uint32_t AMBER = 0xF2C572; constexpr uint32_t AMBER = 0xF2C572;
constexpr uint32_t TRACK = 0x19232B; constexpr uint32_t TRACK = 0x19232B;
constexpr uint32_t RING_BACKING = 0x6E767E;
inline std::string lower(std::string text) { inline std::string lower(std::string text) {
for (char &c : text) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); for (char &c : text) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
@@ -104,6 +105,11 @@ inline uint32_t filament_color(std::string value) {
return rgb; 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 { struct Tray {
std::string label, type, color, active; std::string label, type, color, active;
}; };
@@ -299,7 +305,7 @@ struct Widgets {
lv_obj_t *elapsed = nullptr; lv_obj_t *elapsed = nullptr;
}; };
struct OverviewWidgets { 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) { 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_MAIN);
lv_obj_set_style_arc_color(widgets.arc, lv_color_hex(filament), LV_PART_INDICATOR); lv_obj_set_style_arc_color(widgets.arc, lv_color_hex(filament), LV_PART_INDICATOR);
lv_arc_set_value(widgets.arc, 100); 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)); set_text(widgets.value, number(shown));
lv_obj_set_style_text_color(widgets.value, lv_color_hex(0xFFFFFF), 0); 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 : "--"); set_text(widgets.finish, live && !data.finish_time.empty() ? data.finish_time : "--");
+10 -1
View File
@@ -32,6 +32,7 @@ ELEGOO_FDM = {'nozzle': 'nozzle_temperature', 'bed': 'bed_temperature',
ELEGOO_RESIN = {'uv': 'uv_led_temp', 'elapsed': 'current_print_time'} ELEGOO_RESIN = {'uv': 'uv_led_temp', 'elapsed': 'current_print_time'}
BG, CARD, TRACK, TEXT_COLOR, MUTED, GREEN = ( BG, CARD, TRACK, TEXT_COLOR, MUTED, GREEN = (
0x000000, 0x000000, 0x19232B, 0xEEEEEE, 0xAEB3B7, 0x00FF00) 0x000000, 0x000000, 0x19232B, 0xEEEEEE, 0xAEB3B7, 0x00FF00)
RING_BACKING = 0x6E767E
ORANGE, CYAN = 0xFFAA00, 0x00E5E5 ORANGE, CYAN = 0xFFAA00, 0x00E5E5
@@ -272,7 +273,15 @@ def overview_gauge(printer, x, y, arc):
inner = max(10, int(round(13 * scale))) inner = max(10, int(round(13 * scale)))
inner_x = x + int(round(8 * scale)) inner_x = x + int(round(8 * scale))
number_y = y + int(round(arc * 0.30)) number_y = y + int(round(arc * 0.30))
pad = 8
return [ 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, {'arc': {'id': f'{pid}_overview_arc', 'x': inner_x, 'y': y,
'width': arc, 'height': arc, 'min_value': 0, 'max_value': 100, 'value': 100, 'width': arc, 'height': arc, 'min_value': 0, 'max_value': 100, 'value': 100,
'start_angle': 135, 'end_angle': 45, 'arc_width': inner, 'arc_color': GREEN, '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.extend(snapshot)
lines.append('bambu_display::render_overview(data, {' + ', '.join([ lines.append('bambu_display::render_overview(data, {' + ', '.join([
f'id({pid}_overview_arc)', f'id({pid}_overview_value)', 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.append('}')
lines += [ lines += [
'{', '{',
+161 -7
View File
@@ -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({"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({"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}); 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; bambu_display::Snapshot data;
@@ -1565,7 +1565,7 @@ script:
if (clock.is_valid() && seconds_left >= 0) if (clock.is_valid() && seconds_left >= 0)
data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M"); 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}); 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; 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({"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({"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}); 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; 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({"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({"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}); 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; 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({"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({"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}); 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; bambu_display::Snapshot data;
@@ -1716,7 +1716,7 @@ script:
const auto clock = id(ha_time).now(); const auto clock = id(ha_time).now();
if (clock.is_valid() && seconds_left >= 0) if (clock.is_valid() && seconds_left >= 0)
data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M"); 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; bambu_display::Snapshot data;
@@ -1742,7 +1742,7 @@ script:
const auto clock = id(ha_time).now(); const auto clock = id(ha_time).now();
if (clock.is_valid() && seconds_left >= 0) if (clock.is_valid() && seconds_left >= 0)
data.finish_time = esphome::ESPTime::from_epoch_local(clock.timestamp + seconds_left).strftime("%H:%M"); 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(); const auto clock = id(ha_time).now();
@@ -1841,6 +1841,28 @@ lvgl:
gesture_bubble: true gesture_bubble: true
id: fleet_clock id: fleet_clock
text_align: RIGHT 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: - arc:
id: prints_charming_overview_arc id: prints_charming_overview_arc
x: 27 x: 27
@@ -1921,6 +1943,28 @@ lvgl:
clickable: true clickable: true
on_click: on_click:
- lvgl.page.show: prints_charming_page - 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: - arc:
id: polly_pocket_overview_arc id: polly_pocket_overview_arc
x: 227 x: 227
@@ -2001,6 +2045,28 @@ lvgl:
clickable: true clickable: true
on_click: on_click:
- lvgl.page.show: polly_pocket_page - 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: - arc:
id: ds_turd_flinger_overview_arc id: ds_turd_flinger_overview_arc
x: 427 x: 427
@@ -2081,6 +2147,28 @@ lvgl:
clickable: true clickable: true
on_click: on_click:
- lvgl.page.show: ds_turd_flinger_page - 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: - arc:
id: jordyns_layer_slayer_overview_arc id: jordyns_layer_slayer_overview_arc
x: 627 x: 627
@@ -2161,6 +2249,28 @@ lvgl:
clickable: true clickable: true
on_click: on_click:
- lvgl.page.show: jordyns_layer_slayer_page - 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: - arc:
id: constipation_orion_overview_arc id: constipation_orion_overview_arc
x: 127 x: 127
@@ -2241,6 +2351,28 @@ lvgl:
clickable: true clickable: true
on_click: on_click:
- lvgl.page.show: constipation_orion_page - 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: - arc:
id: centauri_carbon_overview_arc id: centauri_carbon_overview_arc
x: 327 x: 327
@@ -2321,6 +2453,28 @@ lvgl:
clickable: true clickable: true
on_click: on_click:
- lvgl.page.show: centauri_carbon_page - 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: - arc:
id: saturn_overview_arc id: saturn_overview_arc
x: 527 x: 527
+6
View File
@@ -130,6 +130,12 @@ int main() {
assert(data.active_filament_color() == 0xFF8800); assert(data.active_filament_color() == 0xFF8800);
data.trays.clear(); data.trays.clear();
assert(data.active_filament_color() == GREEN); 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; FleetCounts counts;
data.connected = true; data.connected = true;
+15 -1
View File
@@ -152,9 +152,22 @@ class FleetTests(unittest.TestCase):
page = package['lvgl']['pages'][0] page = package['lvgl']['pages'][0]
self.assertEqual(page['id'], 'fleet_page') self.assertEqual(page['id'], 'fleet_page')
self.assertFalse(page.get('skip')) 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], self.assertEqual([arc['id'][:-len('_overview_arc')] for arc in arcs],
[printer['id'] for printer in self.printers]) [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): for arc, printer in zip(arcs, self.printers):
self.assertEqual(arc['on_click'], [{'lvgl.page.show': printer['id'] + '_page'}]) self.assertEqual(arc['on_click'], [{'lvgl.page.show': printer['id'] + '_page'}])
self.assertGreaterEqual(arc['width'], 120) self.assertGreaterEqual(arc['width'], 120)
@@ -166,6 +179,7 @@ class FleetTests(unittest.TestCase):
for printer in self.printers: for printer in self.printers:
self.assertIn(f'id({printer["id"]}_overview_arc)', code) 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_finish)', code)
self.assertIn(f'id({printer["id"]}_overview_ring)', code)
titles = [next(iter(widget.values())).get('text') for widget in page['widgets']] titles = [next(iter(widget.values())).get('text') for widget in page['widgets']]
self.assertIn('Print Status Overview', titles) self.assertIn('Print Status Overview', titles)
self.assertNotIn('fleet_summary', {next(iter(widget.values())).get('id') for widget in page['widgets']}) self.assertNotIn('fleet_summary', {next(iter(widget.values())).get('id') for widget in page['widgets']})