Fix Orion AMS mapping and show printer health details
@@ -21,7 +21,9 @@ The configured swipe order is:
|
||||
2. Polly-Pocket — A1 mini, Office, external spool.
|
||||
3. Ds-Turd-Flinger — A1, Upstairs, four-slot AMS.
|
||||
4. Jordyns-Layer-Slayer — A1, Upstairs, four-slot AMS.
|
||||
5. Constipation-Orion — Office, left/right nozzles and four-slot AMS.
|
||||
5. Constipation-Orion — Office, left/right nozzles and both four-slot AMS units.
|
||||
AMS A uses `constipation_orion_ams_1`; AMS B retains its original
|
||||
`h2c_31b8ap5a2200288_ams_2` entity prefix.
|
||||
|
||||
Swipe left for the next printer or right for the previous printer. Navigation wraps
|
||||
from the last page to the first. Previous/Next buttons provide the same navigation.
|
||||
@@ -34,9 +36,25 @@ stage, fan speeds and filament colors/types. The layout follows BambuHelper's
|
||||
classic gauges: black background, bright green accents, six arc gauges and a
|
||||
vertical AMS column. Nozzle arcs are orange and the bed arc is cyan. For dual-nozzle
|
||||
printers the auxiliary-fan position becomes the right-nozzle gauge. The active
|
||||
filament slot has a green outline. Print errors
|
||||
and HMS alerts change the status badge to red; paused prints use amber. The display
|
||||
shows an alert badge rather than the detailed HMS diagnostic text.
|
||||
filament slot has a green outline. Print errors use red; paused prints use amber.
|
||||
|
||||
**HMS means Health Management System.** Its binary sensor means the Bambu integration
|
||||
has one or more printer health notifications, which can include information or
|
||||
maintenance messages as well as faults. Tap the status in the upper-right corner
|
||||
or the scrolling message above the footer to open the health-details panel. It
|
||||
shows the codes, severities and descriptions reported by Home Assistant, for up to
|
||||
three current HMS notices, plus print-error details. Scroll vertically for long
|
||||
messages and tap Close to return. If more than three notices exist, the panel says
|
||||
how many additional notices are available in Home Assistant.
|
||||
|
||||
The status uses **HEALTH INFO** (cyan) for information-only notifications,
|
||||
**HEALTH NOTICE** (amber) for common/unknown severities or incomplete details, and
|
||||
**HEALTH ERROR** (red) for fatal/serious severities. Severity comes from the Bambu
|
||||
integration's `N-Severity` attributes. Missing or unloaded messages never establish
|
||||
an information-only status. The display does not dismiss or clear printer alerts.
|
||||
The [`hms_errors` sensor attributes](https://github.com/greghesp/ha-bambulab/blob/main/custom_components/bambu_lab/pybambu/models.py)
|
||||
provide `Count`, `1-Code`, `1-Error`, `1-Severity`, and the corresponding numbered
|
||||
fields for subsequent notices. Stale attributes are ignored when the sensor is off.
|
||||
|
||||
When Home Assistant disconnects or a printer is offline, its live metrics are cleared
|
||||
to `--` rather than continuing to show cached values as live. Unknown individual
|
||||
@@ -140,7 +158,8 @@ python esphome/bambu-display/preview.py
|
||||
```
|
||||
|
||||
`preview.py` compiles the same generated LVGL widgets for ESPHome's SDL host with
|
||||
clearly synthetic telemetry, then captures all five pages into `previews/`. It needs
|
||||
clearly synthetic telemetry, then captures all five pages and a synthetic health
|
||||
notice into `previews/`. The demo notice is not a diagnosis of any real printer. It needs
|
||||
ESPHome, Pillow, a C++ compiler and SDL2 (`sdl2-config` on PATH). It runs headlessly,
|
||||
without connecting to Home Assistant or any printer. It also injects LVGL pointer
|
||||
samples to check left/right swipes, page wraparound and consumption of a swipe that
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -99,9 +100,13 @@ struct Tray {
|
||||
std::string label, type, color, active;
|
||||
};
|
||||
|
||||
struct HealthNotice { std::string code, message, severity; };
|
||||
|
||||
struct Snapshot {
|
||||
bool connected = false;
|
||||
std::string online, status, task, stage, print_error, hms_error;
|
||||
std::string hms_count, print_error_message, print_error_code;
|
||||
std::vector<HealthNotice> health_notices;
|
||||
std::string remaining_unit, nozzle_unit, bed_unit;
|
||||
std::string finish_time;
|
||||
float progress = NAN, nozzle = NAN, nozzle_target = NAN, bed = NAN, bed_target = NAN;
|
||||
@@ -112,12 +117,70 @@ struct Snapshot {
|
||||
std::vector<Tray> trays;
|
||||
|
||||
bool live() const { return connected && is_on(online) && valid_text(status); }
|
||||
int notice_count() const {
|
||||
int loaded = 0;
|
||||
for (const auto ¬ice : health_notices)
|
||||
if (valid_text(notice.code) || valid_text(notice.message)) ++loaded;
|
||||
char *end;
|
||||
const long count = std::strtol(hms_count.c_str(), &end, 10);
|
||||
return end != hms_count.c_str() && *end == '\0' && count >= 0 && count < 10000
|
||||
? std::max(loaded, static_cast<int>(count)) : loaded;
|
||||
}
|
||||
int health_severity_rank() const {
|
||||
int rank = 1, loaded = 0;
|
||||
for (const auto ¬ice : health_notices) {
|
||||
if (!valid_text(notice.code) && !valid_text(notice.message)) continue;
|
||||
++loaded;
|
||||
const auto severity = lower(notice.severity);
|
||||
rank = std::max(rank, severity == "fatal" || severity == "serious" ? 3 : severity == "info" ? 1 : 2);
|
||||
}
|
||||
// Missing/unloaded messages cannot establish an info-only notification.
|
||||
return loaded == 0 || notice_count() > loaded ? std::max(rank, 2) : rank;
|
||||
}
|
||||
std::string alert_summary() const {
|
||||
if (is_on(print_error))
|
||||
return valid_text(print_error_message) ? print_error_message : "Print error reported; tap status for details";
|
||||
if (is_on(hms_error)) {
|
||||
for (const auto ¬ice : health_notices)
|
||||
if (valid_text(notice.message)) return notice.message;
|
||||
return "Printer health notification; tap status for details";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
std::string health_details() const {
|
||||
if (!live()) return "Printer health data is unavailable. Waiting for a live connection.";
|
||||
std::string details;
|
||||
if (is_on(print_error)) {
|
||||
details = "PRINT ERROR\n";
|
||||
if (valid_text(print_error_code)) details += print_error_code + "\n";
|
||||
details += valid_text(print_error_message) ? print_error_message : "Message not supplied by Home Assistant.";
|
||||
details += "\n\n";
|
||||
}
|
||||
if (is_on(hms_error)) {
|
||||
details += "HMS = Health Management System\n";
|
||||
int loaded = 0;
|
||||
for (const auto ¬ice : health_notices) {
|
||||
if (!valid_text(notice.code) && !valid_text(notice.message)) continue;
|
||||
details += "\nNotice " + std::to_string(++loaded);
|
||||
if (valid_text(notice.severity)) details += " / " + notice.severity;
|
||||
details += "\n";
|
||||
if (valid_text(notice.code)) details += notice.code + "\n";
|
||||
details += valid_text(notice.message) ? notice.message : "Message not supplied by Home Assistant.";
|
||||
details += "\n";
|
||||
}
|
||||
if (loaded == 0) details += "\nNotification is active; details have not arrived from Home Assistant.\n";
|
||||
if (notice_count() > loaded)
|
||||
details += "\n" + std::to_string(notice_count() - loaded) + " more notification(s). View all in Home Assistant.\n";
|
||||
}
|
||||
return details.empty() ? "No current printer health notifications." : details;
|
||||
}
|
||||
std::string status_text() const {
|
||||
if (!connected) return "HA OFFLINE";
|
||||
if (lower(online) == "off" || lower(online) == "false") return "PRINTER OFFLINE";
|
||||
if (!live()) return "SYNCING";
|
||||
if (is_on(print_error)) return "PRINT ERROR";
|
||||
if (is_on(hms_error)) return "HMS ALERT";
|
||||
if (is_on(hms_error))
|
||||
return health_severity_rank() >= 3 ? "HEALTH ERROR" : health_severity_rank() == 1 ? "HEALTH INFO" : "HEALTH NOTICE";
|
||||
const auto state = lower(status);
|
||||
if (state == "running") return "PRINTING";
|
||||
if (state == "pause" || state == "paused") return "PAUSED";
|
||||
@@ -129,7 +192,9 @@ struct Snapshot {
|
||||
uint32_t status_color() const {
|
||||
if (!live()) return MUTED;
|
||||
const auto state = lower(status);
|
||||
if (is_on(print_error) || is_on(hms_error) || state == "failed") return RED;
|
||||
if (is_on(print_error) || state == "failed") return RED;
|
||||
if (is_on(hms_error))
|
||||
return health_severity_rank() >= 3 ? RED : health_severity_rank() == 1 ? 0x00E5E5 : AMBER;
|
||||
if (state == "pause" || state == "paused") return AMBER;
|
||||
return GREEN;
|
||||
}
|
||||
@@ -145,7 +210,7 @@ namespace bambu_display {
|
||||
struct TrayWidgets { lv_obj_t *card, *swatch, *label; };
|
||||
struct GaugeWidgets { lv_obj_t *arc, *value, *detail; };
|
||||
struct Widgets {
|
||||
lv_obj_t *status, *task, *top_progress, *remaining, *layers, *stage;
|
||||
lv_obj_t *status, *task, *top_progress, *remaining, *layers, *stage, *health_details;
|
||||
GaugeWidgets progress, nozzle, bed, fan, aux, exhaust;
|
||||
std::vector<TrayWidgets> trays;
|
||||
};
|
||||
@@ -192,7 +257,10 @@ inline void render(const Snapshot &data, const Widgets &widgets) {
|
||||
set_text(widgets.remaining, time_line);
|
||||
set_text(widgets.layers, live ? "Layer " + number(data.layer) + " / " + number(data.total_layers) : "--");
|
||||
std::string stage = live ? humanize(data.stage) : "Telemetry unavailable";
|
||||
if (live && (is_on(data.print_error) || is_on(data.hms_error))) stage = data.alert_summary();
|
||||
set_text(widgets.stage, stage);
|
||||
lv_obj_set_style_text_color(widgets.stage, lv_color_hex(live && !data.alert_summary().empty() ? color : MUTED), 0);
|
||||
set_text(widgets.health_details, data.health_details());
|
||||
for (size_t i = 0; i < std::min(data.trays.size(), widgets.trays.size()); ++i) {
|
||||
const auto &tray = data.trays[i];
|
||||
const auto &widget = widgets.trays[i];
|
||||
|
||||
@@ -104,7 +104,8 @@ def page_for(printer, index, total):
|
||||
'indicator': {'bg_color': GREEN}, 'gesture_bubble': True}},
|
||||
label(printer['name'], 16, 17, 485, 26, text_color=GREEN),
|
||||
label(f'{index + 1} / {total}', 520, 24, 70, 14, text_color=MUTED, text_align='CENTER'),
|
||||
label('SYNCING', 598, 21, 186, 16, id=f'{pid}_status_label', text_align='RIGHT', text_color=MUTED),
|
||||
label('SYNCING', 598, 21, 186, 16, id=f'{pid}_status_label', text_align='RIGHT', text_color=MUTED,
|
||||
clickable=True, on_click=[{'lvgl.widget.show': f'{pid}_health_panel'}]),
|
||||
{'obj': {'x': 578, 'y': 64, 'width': 1, 'height': 362, 'bg_color': TRACK,
|
||||
'border_width': 0, 'pad_all': 0, 'scrollable': False, 'gesture_bubble': True}},
|
||||
]
|
||||
@@ -119,7 +120,9 @@ def page_for(printer, index, total):
|
||||
widgets += gauge(pid, 'exhaust', 'Exhaust', 388, 229, GREEN, 100)
|
||||
widgets += [
|
||||
label('--', 16, 401, 544, 26, id=f'{pid}_remaining_label', text_color=GREEN, text_align='CENTER'),
|
||||
label('Telemetry unavailable', 16, 430, 544, 12, id=f'{pid}_stage_label', text_color=MUTED, text_align='CENTER'),
|
||||
label('Telemetry unavailable', 16, 430, 544, 12, id=f'{pid}_stage_label', text_color=MUTED,
|
||||
text_align='CENTER', long_mode='SCROLL_CIRCULAR', clickable=True,
|
||||
on_click=[{'lvgl.widget.show': f'{pid}_health_panel'}]),
|
||||
label('--', 594, 397, 190, 16, id=f'{pid}_layers_label', text_color=MUTED, text_align='CENTER'),
|
||||
]
|
||||
trays = trays_for(printer)
|
||||
@@ -153,6 +156,22 @@ def page_for(printer, index, total):
|
||||
'on_click': [{f'lvgl.page.{direction}': {}}]}})
|
||||
widgets.append(label('Waiting for Home Assistant', 94, 449, 612, 14,
|
||||
id=f'{pid}_task_label', text_align='CENTER'))
|
||||
widgets.append({'obj': {
|
||||
'id': f'{pid}_health_panel', 'x': 24, 'y': 62, 'width': 752, 'height': 366,
|
||||
'hidden': True, 'bg_color': BG, 'bg_opa': 'COVER', 'border_width': 2,
|
||||
'border_color': MUTED, 'radius': 10, 'pad_all': 0, 'scrollable': False,
|
||||
'gesture_bubble': False,
|
||||
'widgets': [
|
||||
label('Printer health / ' + printer['name'], 16, 14, 716, 20),
|
||||
{'obj': {'x': 12, 'y': 50, 'width': 724, 'height': 246,
|
||||
'bg_color': BG, 'border_width': 0, 'pad_all': 0,
|
||||
'scroll_dir': 'VER', 'scrollbar_mode': 'AUTO', 'gesture_bubble': False,
|
||||
'widgets': [label('No current health notifications.', 0, 0, 700, 18,
|
||||
id=f'{pid}_health_details', long_mode='WRAP', height='SIZE_CONTENT')]}},
|
||||
{'button': {'x': 624, 'y': 310, 'width': 108, 'height': 40, 'bg_color': TRACK,
|
||||
'radius': 6, 'widgets': [{'label': {'text': 'CLOSE', 'align': 'CENTER'}}],
|
||||
'on_click': [{'lvgl.widget.hide': f'{pid}_health_panel'}]}},
|
||||
]}})
|
||||
return {'id': f'{pid}_page', 'bg_color': BG, 'bg_opa': 'COVER',
|
||||
'pad_all': 0, 'scrollable': False, 'scrollbar_mode': 'OFF',
|
||||
'on_swipe_left': [
|
||||
@@ -203,6 +222,14 @@ def generate(printers):
|
||||
text_fields = {key: (entity(printer, key, suffix), None) for key, suffix in TEXT.items()}
|
||||
text_fields.update({key: (entity(printer, key, suffix, 'binary_sensor'), None)
|
||||
for key, suffix in BINARY.items()})
|
||||
hms_entity = entity(printer, 'hms_error', BINARY['hms_error'], 'binary_sensor')
|
||||
text_fields['hms_count'] = (hms_entity, 'Count')
|
||||
for number in range(1, 4):
|
||||
for key, attribute in [('message', 'Error'), ('code', 'Code'), ('severity', 'Severity')]:
|
||||
text_fields[f'hms_{number}_{key}'] = (hms_entity, f'{number}-{attribute}')
|
||||
print_error_entity = entity(printer, 'print_error', BINARY['print_error'], 'binary_sensor')
|
||||
text_fields['print_error_message'] = (print_error_entity, 'error')
|
||||
text_fields['print_error_code'] = (print_error_entity, 'code')
|
||||
for key, numeric_key in [('remaining_unit', 'time_left'), ('nozzle_unit', 'nozzle'), ('bed_unit', 'bed')]:
|
||||
text_fields[key] = (entity(printer, numeric_key, NUMERIC[numeric_key]), 'unit_of_measurement')
|
||||
trays = trays_for(printer)
|
||||
@@ -219,8 +246,11 @@ def generate(printers):
|
||||
lines = ['bambu_display::Snapshot data;',
|
||||
'data.connected = id(ha_api).is_connected_with_state_subscription();']
|
||||
for key, expr in expressions.items():
|
||||
if not key.startswith('tray_'):
|
||||
if not key.startswith(('tray_', 'hms_1_', 'hms_2_', 'hms_3_')):
|
||||
lines.append(f'data.{key} = {expr};')
|
||||
for number in range(1, 4):
|
||||
lines.append('data.health_notices.push_back({' + ', '.join(
|
||||
expressions[f'hms_{number}_{key}'] for key in ('code', 'message', 'severity')) + '});')
|
||||
if printer.get('dual_nozzle'):
|
||||
lines.append('data.dual_nozzle = true;')
|
||||
lines += [
|
||||
@@ -237,7 +267,8 @@ def generate(printers):
|
||||
tray_widgets.append('{' + ', '.join(f'id({pid}_tray_{i}_{part})'
|
||||
for part in ('card', 'swatch', 'label')) + '}')
|
||||
refs = [f'id({pid}_{key})' for key in (
|
||||
'status_label', 'task_label', 'top_progress', 'remaining_label', 'layers_label', 'stage_label')]
|
||||
'status_label', 'task_label', 'top_progress', 'remaining_label', 'layers_label', 'stage_label',
|
||||
'health_details')]
|
||||
for key in ('progress', 'nozzle', 'bed', 'fan', 'aux', 'exhaust'):
|
||||
refs.append('{' + ', '.join(f'id({pid}_{key}_{part})' for part in ('arc', 'label', 'detail')) + '}')
|
||||
refs.append('{' + ', '.join(tray_widgets) + '}')
|
||||
|
||||
@@ -25,6 +25,7 @@ def main():
|
||||
'right_nozzle': 37, 'right_target': 0}
|
||||
demo_text = {'status': 'running', 'task': 'Workshop organizer - plate 2',
|
||||
'stage': 'printing', 'online': 'on', 'print_error': 'off', 'hms_error': 'off',
|
||||
'hms_count': '0', 'print_error_message': '', 'print_error_code': '',
|
||||
'remaining_unit': 'h', 'nozzle_unit': '°C', 'bed_unit': '°C'}
|
||||
colors = ['#60DE9AFF', '#F4C96BFF', '#74A9E8FF', '#F8F4ECFF',
|
||||
'#DF8BBFFF', '#FA936BFF', '#8E79CAFF', '#4CC9C1FF']
|
||||
@@ -42,6 +43,8 @@ def main():
|
||||
_, index, attribute = key.split('_')
|
||||
text = {'type': 'PLA', 'color': colors[int(index) % len(colors)],
|
||||
'active': 'on' if index == '0' else 'off'}[attribute]
|
||||
elif key.startswith(('hms_1_', 'hms_2_', 'hms_3_')):
|
||||
text = ''
|
||||
else:
|
||||
text = demo_text[key]
|
||||
value = 'std::string(' + json.dumps(text, ensure_ascii=False) + ')'
|
||||
@@ -85,8 +88,26 @@ inline void setup() {
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': printer['id'] + '.bmp'}}]
|
||||
steps += [{'lvgl.page.show': 'color_test_page'}, {'delay': '300ms'},
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': 'color_test.bmp'}}]
|
||||
demo_printer = printers[0]['id']
|
||||
for key, value in {'hms_error': 'on', 'hms_count': '1', 'hms_1_code': 'DEMO_CODE',
|
||||
'hms_1_severity': 'common',
|
||||
'hms_1_message': 'DEMO: A printer maintenance notification. This is synthetic preview data, not an actual fault diagnosis.'}.items():
|
||||
steps.append({'text_sensor.template.publish': {'id': f'{demo_printer}_{key}', 'state': value}})
|
||||
steps += [{'lvgl.page.show': demo_printer + '_page'},
|
||||
{'script.execute': demo_printer + '_refresh'},
|
||||
{'lvgl.widget.show': demo_printer + '_health_panel'}, {'delay': '300ms'},
|
||||
{'snapshot.take': {'id': 'panel_display', 'filename': 'health_notice.bmp'}},
|
||||
{'lvgl.widget.hide': demo_printer + '_health_panel'},
|
||||
{'text_sensor.template.publish': {'id': demo_printer + '_hms_error', 'state': 'off'}},
|
||||
{'script.execute': demo_printer + '_refresh'}]
|
||||
steps += [{'lvgl.page.show': printers[0]['id'] + '_page'}, {'delay': '250ms'},
|
||||
{'lambda': Lambda('bambu_preview::setup();')}]
|
||||
for x, y, expect_hidden in [(690, 30, False), (700, 394, True)]:
|
||||
steps += [
|
||||
{'lambda': Lambda(f'bambu_preview::x = {x}; bambu_preview::y = {y}; bambu_preview::pressed = true;')},
|
||||
{'delay': '70ms'}, {'lambda': Lambda('bambu_preview::pressed = false;')}, {'delay': '150ms'},
|
||||
{'lambda': Lambda(f'if (lv_obj_has_flag(id({demo_printer}_health_panel), LV_OBJ_FLAG_HIDDEN) != {str(expect_hidden).lower()}) {{ ESP_LOGE("preview", "Health panel tap failed"); exit(3); }}')},
|
||||
]
|
||||
if len(printers) > 1:
|
||||
# First -> second -> first -> last -> first, then a swipe starting
|
||||
# on NEXT must advance exactly once even when released over PREV.
|
||||
@@ -119,12 +140,13 @@ inline void setup() {
|
||||
subprocess.run(['esphome', 'compile', str(filename)], check=True)
|
||||
binary = root / '.esphome/build/bambu-display-preview/.pioenvs/bambu-display-preview/program'
|
||||
subprocess.run([str(binary)], check=True, timeout=30, cwd=root)
|
||||
for name in [printer['id'] for printer in printers] + ['color_test']:
|
||||
for name in [printer['id'] for printer in printers] + ['color_test', 'health_notice']:
|
||||
bmp = root / '.esphome/snapshots/bambu-display-preview' / (name + '.bmp')
|
||||
with Image.open(bmp) as image:
|
||||
image.save(args.output / (name + '.png'))
|
||||
print(f'Saved {len(printers)} demo screenshots to {args.output}')
|
||||
print('Passed LVGL pointer swipe tests, including wraparound and button gesture consumption.')
|
||||
print('Passed status-tap health panel open and Close-button tests.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@@ -46,3 +46,6 @@ printers:
|
||||
filament_banks:
|
||||
- label: A
|
||||
entity_prefix: constipation_orion_ams_1
|
||||
- label: B
|
||||
# This unit retains its original hardware-based HA entity names.
|
||||
entity_prefix: h2c_31b8ap5a2200288_ams_2
|
||||
|
||||
@@ -50,9 +50,25 @@ int main() {
|
||||
assert(data.status_color() == RED);
|
||||
data.print_error = "off";
|
||||
data.hms_error = "True";
|
||||
assert(data.status_text() == "HMS ALERT");
|
||||
assert(data.status_text() == "HEALTH NOTICE");
|
||||
assert(data.status_color() == AMBER);
|
||||
assert(data.health_details().find("have not arrived") != std::string::npos);
|
||||
data.hms_count = "1";
|
||||
data.health_notices = {{"DEMO_CODE", "A synthetic information message", "info"}};
|
||||
assert(data.status_text() == "HEALTH INFO");
|
||||
assert(data.health_details().find("DEMO_CODE") != std::string::npos);
|
||||
assert(data.alert_summary() == "A synthetic information message");
|
||||
data.health_notices.push_back({"DEMO_2", "A synthetic serious message", "serious"});
|
||||
data.hms_count = "2";
|
||||
assert(data.status_text() == "HEALTH ERROR");
|
||||
assert(data.status_color() == RED);
|
||||
assert(data.health_details().find("A synthetic serious message") != std::string::npos);
|
||||
data.health_notices.pop_back();
|
||||
data.hms_count = "5";
|
||||
assert(data.status_text() == "HEALTH NOTICE");
|
||||
assert(data.health_details().find("4 more") != std::string::npos);
|
||||
data.hms_error = "off";
|
||||
assert(data.health_details() == "No current printer health notifications.");
|
||||
data.status = "finish";
|
||||
assert(data.status_text() == "COMPLETE");
|
||||
assert(data.status_color() == GREEN);
|
||||
|
||||
@@ -41,6 +41,22 @@ class FleetTests(unittest.TestCase):
|
||||
entry = next(row for row in package[domain] if row['id'] == f'prints_charming_{key}')
|
||||
self.assertEqual(entry['entity_id'], 'sensor.custom_remaining')
|
||||
|
||||
def test_orion_has_both_ams_units_with_their_actual_entity_prefixes(self):
|
||||
package = generate(self.printers)
|
||||
types = [entry['entity_id'] for entry in package['text_sensor']
|
||||
if entry['id'].startswith('constipation_orion_tray_') and entry.get('attribute') == 'type']
|
||||
self.assertEqual(len(types), 8)
|
||||
self.assertIn('sensor.constipation_orion_ams_1_tray_1', types)
|
||||
self.assertIn('sensor.h2c_31b8ap5a2200288_ams_2_tray_4', types)
|
||||
|
||||
def test_health_messages_import_numbered_ha_attributes(self):
|
||||
package = generate(self.printers)
|
||||
subscriptions = {entry['id']: entry for entry in package['text_sensor']}
|
||||
message = subscriptions['prints_charming_hms_1_message']
|
||||
self.assertEqual(message['entity_id'], 'binary_sensor.p1s_01p09c470102673_hms_errors')
|
||||
self.assertEqual(message['attribute'], '1-Error')
|
||||
self.assertEqual(subscriptions['prints_charming_hms_3_severity']['attribute'], '3-Severity')
|
||||
|
||||
def test_reject_empty_duplicate_and_malformed_printer_ids(self):
|
||||
for invalid in ([], self.printers + [self.printers[0]],
|
||||
[{**self.printers[0], 'id': 'invalid-id'}]):
|
||||
|
||||