Add Elegoo printers to ESPHome fleet display
This commit is contained in:
@@ -23,6 +23,13 @@ DUAL = {
|
||||
EXTRA_FANS = {'aux_fan': 'aux_fan_speed', 'exhaust_fan': 'chamber_fan_speed'}
|
||||
TEXT = {'status': 'print_status', 'task': 'task_name', 'stage': 'current_stage'}
|
||||
BINARY = {'online': 'online', 'print_error': 'print_error', 'hms_error': 'hms_errors'}
|
||||
ELEGOO_COMMON = {'progress': 'percent_complete', 'time_left': 'remaining_print_time',
|
||||
'layer': 'current_layer', 'total_layers': 'total_layers'}
|
||||
ELEGOO_FDM = {'nozzle': 'nozzle_temperature', 'bed': 'bed_temperature',
|
||||
'nozzle_target': ('number', 'target_nozzle_temp'),
|
||||
'bed_target': ('number', 'target_bed_temp'), 'fan': 'model_fan_speed',
|
||||
'aux_fan': 'auxiliary_fan_speed', 'exhaust_fan': 'enclosure_fan_speed'}
|
||||
ELEGOO_RESIN = {'uv': 'uv_led_temp', 'elapsed': 'current_print_time'}
|
||||
BG, CARD, TRACK, TEXT_COLOR, MUTED, GREEN = (
|
||||
0x000000, 0x000000, 0x19232B, 0xEEEEEE, 0xAEB3B7, 0x00FF00)
|
||||
ORANGE, CYAN = 0xFFAA00, 0x00E5E5
|
||||
@@ -65,6 +72,11 @@ def validate_printers(printers):
|
||||
if not re.fullmatch(r'[a-z][a-z0-9_]*', pid) or pid in ids:
|
||||
raise ValueError(f'Invalid or duplicate printer id: {pid}')
|
||||
ids.add(pid)
|
||||
profile = printer.get('profile', 'bambu')
|
||||
if profile not in ('bambu', 'elegoo_fdm', 'elegoo_resin'):
|
||||
raise ValueError(f'{pid}: unsupported profile: {profile}')
|
||||
if profile != 'bambu' and (printer.get('dual_nozzle') or trays_for(printer) or printer.get('extra_fans')):
|
||||
raise ValueError(f'{pid}: dual_nozzle, filament slots and extra_fans require the bambu profile')
|
||||
if len(trays_for(printer)) > 8:
|
||||
raise ValueError(f'{pid}: the filament strip fits up to eight slots')
|
||||
if 'dual_nozzle' in printer and not isinstance(printer['dual_nozzle'], bool):
|
||||
@@ -72,7 +84,7 @@ def validate_printers(printers):
|
||||
if any(key not in EXTRA_FANS for key in printer.get('extra_fans', [])):
|
||||
raise ValueError(f'{pid}: extra_fans supports aux_fan and exhaust_fan')
|
||||
for value in printer.get('entities', {}).values():
|
||||
if not isinstance(value, str) or not re.fullmatch(r'(sensor|binary_sensor)\.[a-z0-9_]+', value):
|
||||
if not isinstance(value, str) or not re.fullmatch(r'(sensor|binary_sensor|number)\.[a-z0-9_]+', value):
|
||||
raise ValueError(f'{pid}: invalid entity override: {value}')
|
||||
|
||||
|
||||
@@ -96,8 +108,22 @@ def gauge(pid, key, title, x, y, color, maximum):
|
||||
]
|
||||
|
||||
|
||||
def fdm_gauges(printer):
|
||||
pid = printer['id']
|
||||
widgets = gauge(pid, 'nozzle', 'Nozzle L' if printer.get('dual_nozzle') else 'Nozzle',
|
||||
202, 62, ORANGE, 300)
|
||||
widgets += gauge(pid, 'bed', 'Bed', 388, 62, CYAN, 120)
|
||||
widgets += gauge(pid, 'fan', 'Part', 16, 229, 0xEEEEEE, 100)
|
||||
widgets += gauge(pid, 'aux', 'Nozzle R' if printer.get('dual_nozzle') else 'Aux',
|
||||
202, 229, ORANGE if printer.get('dual_nozzle') else 0xEEEEEE,
|
||||
300 if printer.get('dual_nozzle') else 100)
|
||||
widgets += gauge(pid, 'exhaust', 'Exhaust', 388, 229, GREEN, 100)
|
||||
return widgets
|
||||
|
||||
|
||||
def page_for(printer, index, total):
|
||||
pid = printer['id']
|
||||
resin = printer.get('profile') == 'elegoo_resin'
|
||||
widgets = [
|
||||
{'bar': {'id': f'{pid}_top_progress', 'x': 0, 'y': 0, 'width': 800, 'height': 5,
|
||||
'min_value': 0, 'max_value': 100, 'value': 0, 'bg_color': TRACK,
|
||||
@@ -110,16 +136,17 @@ def page_for(printer, index, total):
|
||||
'border_width': 0, 'pad_all': 0, 'scrollable': False, 'gesture_bubble': True}},
|
||||
]
|
||||
widgets += gauge(pid, 'progress', 'Progress', 16, 62, GREEN, 100)
|
||||
widgets += gauge(pid, 'nozzle', 'Nozzle L' if printer.get('dual_nozzle') else 'Nozzle',
|
||||
202, 62, ORANGE, 300)
|
||||
widgets += gauge(pid, 'bed', 'Bed', 388, 62, CYAN, 120)
|
||||
widgets += gauge(pid, 'fan', 'Part', 16, 229, 0xEEEEEE, 100)
|
||||
widgets += gauge(pid, 'aux', 'Nozzle R' if printer.get('dual_nozzle') else 'Aux',
|
||||
202, 229, ORANGE if printer.get('dual_nozzle') else 0xEEEEEE,
|
||||
300 if printer.get('dual_nozzle') else 100)
|
||||
widgets += gauge(pid, 'exhaust', 'Exhaust', 388, 229, GREEN, 100)
|
||||
if resin:
|
||||
widgets += gauge(pid, 'uv', 'UV LED', 202, 62, ORANGE, 100)
|
||||
widgets += gauge(pid, 'layer', 'Layer', 388, 62, CYAN, 100)
|
||||
widgets += [label('FINISH / TIME REMAINING', 16, 260, 544, 18,
|
||||
text_align='CENTER', text_color=MUTED),
|
||||
label('--', 16, 354, 544, 20, id=f'{pid}_elapsed_label',
|
||||
text_align='CENTER', text_color=MUTED)]
|
||||
else:
|
||||
widgets += fdm_gauges(printer)
|
||||
widgets += [
|
||||
label('--', 16, 401, 544, 26, id=f'{pid}_remaining_label', text_color=GREEN, text_align='CENTER'),
|
||||
label('--', 16, 300 if resin else 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', long_mode='SCROLL_CIRCULAR', clickable=True,
|
||||
on_click=[{'lvgl.widget.show': f'{pid}_health_panel'}]),
|
||||
@@ -146,7 +173,10 @@ def page_for(printer, index, total):
|
||||
id=f'{pid}_tray_{i}_label', text_align='CENTER'),
|
||||
]}})
|
||||
if not trays:
|
||||
widgets.append(label('No AMS', 594, 150, 190, 20, text_color=MUTED, text_align='CENTER'))
|
||||
widgets.append(label('ELEGOO' if printer.get('profile', '').startswith('elegoo_') else 'No AMS', 594, 150, 190, 20,
|
||||
text_color=MUTED, text_align='CENTER'))
|
||||
widgets.append(label(printer['subtitle'], 594, 185, 190, 16,
|
||||
text_color=MUTED, text_align='CENTER'))
|
||||
for direction, x, text in (('previous', 16, '<'), ('next', 720, '>')):
|
||||
widgets.append({'button': {'x': x, 'y': 446, 'width': 64, 'height': 30,
|
||||
'bg_color': TRACK, 'radius': 8, 'border_width': 0,
|
||||
@@ -208,30 +238,44 @@ def generate(printers):
|
||||
'interval': [{'interval': '1s', 'then': []}], 'lvgl': {'pages': []}}
|
||||
for index, printer in enumerate(printers):
|
||||
pid = printer['id']
|
||||
fields = dict(NUMERIC)
|
||||
profile = printer.get('profile', 'bambu')
|
||||
elegoo = profile.startswith('elegoo_')
|
||||
resin = profile == 'elegoo_resin'
|
||||
fields = {**ELEGOO_COMMON, **(ELEGOO_RESIN if resin else ELEGOO_FDM)} if elegoo else dict(NUMERIC)
|
||||
if printer.get('dual_nozzle'):
|
||||
fields.update(DUAL)
|
||||
fields.update({key: EXTRA_FANS[key] for key in printer.get('extra_fans', [])})
|
||||
expressions = {}
|
||||
for key, suffix in fields.items():
|
||||
sid = f'{pid}_{key}'
|
||||
eid = entity(printer, key, suffix)
|
||||
domain, suffix = suffix if isinstance(suffix, tuple) else ('sensor', suffix)
|
||||
eid = entity(printer, key, suffix, domain)
|
||||
package['sensor'].append({'platform': 'homeassistant', 'id': sid,
|
||||
'entity_id': eid, 'internal': True})
|
||||
expressions[key] = f'id({sid}).state'
|
||||
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')
|
||||
if elegoo:
|
||||
text_fields = {key: (entity(printer, key, suffix), None) for key, suffix in {
|
||||
'status': 'print_status', 'task': 'file_name', 'stage': 'current_status',
|
||||
'print_error': 'print_error', 'print_error_reason': 'error_status_reason'}.items()}
|
||||
text_fields['online'] = (entity(printer, 'online', 'sdcp_status', 'binary_sensor'), None)
|
||||
else:
|
||||
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'), ('uv_unit', 'uv'), ('elapsed_unit', 'elapsed')]:
|
||||
if numeric_key in fields:
|
||||
suffix = fields[numeric_key]
|
||||
domain, suffix = suffix if isinstance(suffix, tuple) else ('sensor', suffix)
|
||||
text_fields[key] = (entity(printer, numeric_key, suffix, domain), 'unit_of_measurement')
|
||||
trays = trays_for(printer)
|
||||
for i, tray in enumerate(trays):
|
||||
for key, attr in [('type', 'type'), ('color', 'color'), ('active', 'active')]:
|
||||
@@ -248,9 +292,14 @@ def generate(printers):
|
||||
for key, expr in expressions.items():
|
||||
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 elegoo:
|
||||
lines.append('data.elegoo = true;')
|
||||
else:
|
||||
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 resin:
|
||||
lines.append('data.resin = true;')
|
||||
if printer.get('dual_nozzle'):
|
||||
lines.append('data.dual_nozzle = true;')
|
||||
lines += [
|
||||
@@ -270,8 +319,13 @@ def generate(printers):
|
||||
'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(f'id({pid}_{key}_{part})' for part in ('arc', 'label', 'detail')) + '}'
|
||||
if not resin or key == 'progress' else '{}')
|
||||
refs.append('{' + ', '.join(tray_widgets) + '}')
|
||||
if resin:
|
||||
for key in ('uv', 'layer'):
|
||||
refs.append('{' + ', '.join(f'id({pid}_{key}_{part})' for part in ('arc', 'label', 'detail')) + '}')
|
||||
refs.append(f'id({pid}_elapsed_label)')
|
||||
lines.append('bambu_display::render(data, {' + ', '.join(refs) + '});')
|
||||
package['script'].append({'id': f'{pid}_refresh', 'then': [{'lambda': Lambda('\n'.join(lines))}]})
|
||||
package['interval'][0]['then'].append({'if': {
|
||||
|
||||
Reference in New Issue
Block a user