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 `
Spaghetti Detection
${view.label}
${automationText}
${view.desc}
Confidence ${confidence.toFixed(1)}%
Print
${printStatus}
Next check
${formatDate(nextRun, 'Not scheduled')}
Detections
${detections}
Raw score
${raw.toFixed(3)}
`; ]]] 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); }