Files
ha-elegoo-spaghetti-detection/examples/dashboard_hacs.yaml
T
2026-05-03 23:47:54 +03:00

493 lines
16 KiB
YAML

type: vertical-stack
cards:
- type: custom:button-card
entity: sensor.elegoo_spaghetti_detection_status
show_name: false
show_icon: false
show_state: false
tap_action:
action: more-info
custom_fields:
content: |
[[[
const state = (id) => states[id]?.state ?? 'unknown';
const numberState = (id) => Number.parseFloat(state(id)) || 0;
const formatDate = (value, fallback = 'Not available') => {
if (!value || ['unknown', 'unavailable', 'none'].includes(value)) return fallback;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
};
const status = state('sensor.elegoo_spaghetti_detection_status').trim();
const detected = state('binary_sensor.elegoo_spaghetti_detection_spaghetti_detected') === 'on';
const confidence = numberState('sensor.elegoo_spaghetti_detection_confidence');
const raw = numberState('sensor.elegoo_spaghetti_detection_raw_score');
const detections = state('sensor.elegoo_spaghetti_detection_detections');
const printStatus = state('sensor.elegoo_centauri_carbon2_print_status');
const nextRun = state('sensor.elegoo_spaghetti_detection_next_run');
const lastRun = state('sensor.elegoo_spaghetti_detection_last_run');
const lastError = state('sensor.elegoo_spaghetti_detection_last_error');
const automation = state('automation.elegoo_cc2_spaghetti_pause_and_notify');
const statusMap = {
clear: {
label: 'Clear',
color: '#34c759',
bg: 'rgba(52,199,89,.14)',
desc: 'No spaghetti was detected in the last check.'
},
detected: {
label: 'Failure detected',
color: '#ff453a',
bg: 'rgba(255,69,58,.18)',
desc: 'Check the camera before continuing.'
},
warning: {
label: 'Warning',
color: '#ff9f0a',
bg: 'rgba(255,159,10,.16)',
desc: 'The score is above the warning threshold but below failure.'
},
checking: {
label: 'Checking',
color: '#0a84ff',
bg: 'rgba(10,132,255,.16)',
desc: 'The current camera image is being analyzed.'
},
waiting_for_print: {
label: 'Waiting for print',
color: '#8e8e93',
bg: 'rgba(142,142,147,.14)',
desc: 'Waiting for an active print.'
},
status_unavailable: {
label: 'Print status unavailable',
color: '#ff9f0a',
bg: 'rgba(255,159,10,.14)',
desc: 'Check the selected print status entity in integration options.'
},
busy: {
label: 'Busy',
color: '#0a84ff',
bg: 'rgba(10,132,255,.14)',
desc: 'Another detection request is still running.'
},
error: {
label: 'Error',
color: '#ff453a',
bg: 'rgba(255,69,58,.16)',
desc: 'The last run failed. Check the error field below.'
},
idle: {
label: 'Idle',
color: '#8e8e93',
bg: 'rgba(142,142,147,.14)',
desc: 'The detector is idle.'
}
};
const view = statusMap[status] || {
label: status.replaceAll('_', ' '),
color: '#8e8e93',
bg: 'rgba(142,142,147,.14)',
desc: 'The detector state is currently unknown.'
};
const automationColor = automation === 'on' ? '#34c759' : automation === 'off' ? '#ff9f0a' : '#8e8e93';
const automationText = automation === 'on'
? 'Automation enabled'
: automation === 'off'
? 'Automation disabled'
: 'Automation not found';
const errorText =
lastError && !['none', 'unknown', 'unavailable', ''].includes(lastError)
? lastError
: 'None';
return `
<div class="wrap">
<div class="header">
<div class="status-dot" style="background:${view.color}; box-shadow:0 0 24px ${view.color}66;"></div>
<div class="header-main">
<div class="title">Spaghetti Detection</div>
<div class="subtitle">${view.label}</div>
</div>
<div class="automation-pill" style="border-color:${automationColor}55; background:${automationColor}18; color:${automationColor};">
${automationText}
</div>
</div>
<div class="desc" style="background:${view.bg}; border-color:${view.color}33;">
${view.desc}
</div>
<div class="confidence">
<div class="confidence-top">
<span>Confidence</span>
<b>${confidence.toFixed(1)}%</b>
</div>
<div class="bar">
<div class="bar-fill" style="
width:${Math.max(0, Math.min(100, confidence))}%;
background:${detected ? '#ff453a' : view.color};
box-shadow:0 0 18px ${detected ? '#ff453a' : view.color};
"></div>
</div>
</div>
<div class="metrics">
<div class="metric">
<div class="metric-label">Print</div>
<div class="metric-value">${printStatus}</div>
</div>
<div class="metric">
<div class="metric-label">Next check</div>
<div class="metric-value">${formatDate(nextRun, 'Not scheduled')}</div>
</div>
<div class="metric">
<div class="metric-label">Detections</div>
<div class="metric-value">${detections}</div>
</div>
<div class="metric">
<div class="metric-label">Raw score</div>
<div class="metric-value">${raw.toFixed(3)}</div>
</div>
</div>
<div class="footer">
<div><b>Last check:</b> ${formatDate(lastRun, 'Never')}</div>
<div><b>Last error:</b> ${errorText}</div>
</div>
</div>
`;
]]]
styles:
card:
- border-radius: 22px
- padding: 14px
- background: rgba(255,255,255,.04)
- border: 1px solid rgba(255,255,255,.08)
- box-shadow: none
- overflow: hidden
grid:
- grid-template-areas: "\"content\""
- grid-template-columns: 1fr
- grid-template-rows: auto
custom_fields:
content:
- width: 100%
- justify-self: stretch
- text-align: left
extra_styles: |
.wrap {
width: 100%;
box-sizing: border-box;
}
.header {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: center;
}
.status-dot {
width: 18px;
height: 18px;
border-radius: 999px;
}
.title {
font-size: 21px;
font-weight: 800;
color: rgba(255,255,255,.96);
line-height: 1.15;
}
.subtitle {
margin-top: 3px;
font-size: 14px;
opacity: .74;
font-weight: 700;
text-transform: capitalize;
}
.automation-pill {
border: 1px solid;
border-radius: 999px;
padding: 7px 10px;
font-size: 12px;
font-weight: 800;
white-space: nowrap;
}
.desc {
margin-top: 14px;
padding: 11px 12px;
border: 1px solid;
border-radius: 14px;
color: rgba(255,255,255,.82);
font-size: 13px;
line-height: 1.35;
white-space: normal;
overflow-wrap: anywhere;
}
.confidence {
margin-top: 14px;
}
.confidence-top {
display: flex;
justify-content: space-between;
margin-bottom: 7px;
font-size: 13px;
opacity: .85;
}
.bar {
height: 12px;
border-radius: 999px;
background: rgba(255,255,255,.11);
overflow: hidden;
}
.bar-fill {
height: 100%;
border-radius: 999px;
transition: width .3s ease;
}
.metrics {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.metric {
border-radius: 14px;
padding: 10px;
background: rgba(255,255,255,.06);
text-align: center;
min-width: 0;
}
.metric-label {
opacity: .62;
font-size: 12px;
font-weight: 700;
}
.metric-value {
margin-top: 4px;
font-size: 14px;
font-weight: 800;
color: rgba(255,255,255,.96);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.footer {
margin-top: 13px;
font-size: 12px;
opacity: .70;
line-height: 1.45;
}
@media (max-width: 520px) {
.header {
grid-template-columns: auto 1fr;
}
.automation-pill {
grid-column: 1 / -1;
justify-self: start;
}
.metrics {
grid-template-columns: 1fr 1fr;
}
}
- type: conditional
conditions:
- entity: binary_sensor.elegoo_spaghetti_detection_spaghetti_detected
state: "on"
card:
type: custom:mushroom-template-card
entity: binary_sensor.elegoo_spaghetti_detection_spaghetti_detected
primary: Spaghetti detected
secondary: >-
Confidence: {{ states('sensor.elegoo_spaghetti_detection_confidence') }}%
• Detections: {{ states('sensor.elegoo_spaghetti_detection_detections') }}
icon: mdi:alert-octagon
icon_color: red
badge_icon: mdi:pause
badge_color: red
layout: horizontal
tap_action:
action: more-info
card_mod:
style: |
ha-card {
border-radius: 18px;
background: rgba(255,69,58,.16);
border: 1px solid rgba(255,69,58,.34);
animation: spaghettiPulse 1.6s ease-in-out infinite;
}
@keyframes spaghettiPulse {
0% { box-shadow: 0 0 0 rgba(255,69,58,0); }
50% { box-shadow: 0 0 28px rgba(255,69,58,.30); }
100% { box-shadow: 0 0 0 rgba(255,69,58,0); }
}
- type: grid
columns: 2
square: false
cards:
# Replace this placeholder automation with your own notification/pause
# automation entity before using the toggle card below.
- type: custom:mushroom-template-card
entity: automation.elegoo_cc2_spaghetti_pause_and_notify
primary: Auto pause
secondary: >-
{% if is_state('automation.elegoo_cc2_spaghetti_pause_and_notify', 'on') %}
Enabled: pause and notify on failure
{% elif is_state('automation.elegoo_cc2_spaghetti_pause_and_notify', 'off') %}
Disabled: notification-only or manual review mode
{% else %}
Replace this entity with your own automation
{% endif %}
icon: >-
{% if is_state('automation.elegoo_cc2_spaghetti_pause_and_notify', 'on') %}
mdi:shield-check
{% else %}
mdi:shield-off
{% endif %}
icon_color: >-
{% if is_state('automation.elegoo_cc2_spaghetti_pause_and_notify', 'on') %}
green
{% else %}
amber
{% endif %}
layout: vertical
tap_action:
action: toggle
hold_action:
action: more-info
card_mod:
style: |
ha-card {
border-radius: 18px;
background: rgba(255,255,255,.045);
border: 1px solid rgba(255,255,255,.08);
}
- type: custom:mushroom-template-card
entity: button.elegoo_spaghetti_detection_test_spaghetti_detection
primary: Manual test
secondary: Take one snapshot and run detection
icon: mdi:camera-iris
icon_color: blue
layout: vertical
tap_action:
action: perform-action
perform_action: button.press
target:
entity_id: button.elegoo_spaghetti_detection_test_spaghetti_detection
card_mod:
style: |
ha-card {
border-radius: 18px;
background: rgba(10,132,255,.10);
border: 1px solid rgba(10,132,255,.24);
}
- type: custom:mushroom-template-card
entity: button.elegoo_spaghetti_detection_reset_detection_state
primary: Reset detector
secondary: Clear confidence and detected state
icon: mdi:restart
icon_color: purple
layout: vertical
tap_action:
action: perform-action
perform_action: button.press
target:
entity_id: button.elegoo_spaghetti_detection_reset_detection_state
card_mod:
style: |
ha-card {
border-radius: 18px;
background: rgba(175,82,222,.10);
border: 1px solid rgba(175,82,222,.24);
}
- type: custom:mushroom-template-card
entity: sensor.elegoo_spaghetti_detection_last_error
primary: Last error
secondary: >-
{% set e = states('sensor.elegoo_spaghetti_detection_last_error') %}
{% if e in ['none', 'unknown', 'unavailable', ''] %}
None
{% else %}
{{ e }}
{% endif %}
icon: >-
{% set e = states('sensor.elegoo_spaghetti_detection_last_error') %}
{% if e in ['none', 'unknown', 'unavailable', ''] %}
mdi:check-circle
{% else %}
mdi:alert-circle
{% endif %}
icon_color: >-
{% set e = states('sensor.elegoo_spaghetti_detection_last_error') %}
{% if e in ['none', 'unknown', 'unavailable', ''] %}
green
{% else %}
red
{% endif %}
layout: vertical
multiline_secondary: true
tap_action:
action: more-info
card_mod:
style: |
ha-card {
border-radius: 18px;
background: rgba(255,255,255,.045);
border: 1px solid rgba(255,255,255,.08);
}
- type: entities
title: Spaghetti detector details
show_header_toggle: false
entities:
- entity: sensor.elegoo_spaghetti_detection_status
name: Status
- entity: sensor.elegoo_spaghetti_detection_next_run
name: Next scheduled check
- entity: binary_sensor.elegoo_spaghetti_detection_spaghetti_detected
name: Detected
- entity: sensor.elegoo_spaghetti_detection_confidence
name: Confidence
- entity: sensor.elegoo_spaghetti_detection_raw_score
name: Raw score
- entity: sensor.elegoo_spaghetti_detection_detections
name: Detection count
- entity: sensor.elegoo_spaghetti_detection_last_run
name: Last check
- entity: automation.elegoo_cc2_spaghetti_pause_and_notify
name: Auto pause automation
card_mod:
style: |
ha-card {
border-radius: 18px;
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.08);
}