diff --git a/config/dashboards/infrastructure/partials/home_sections.yaml b/config/dashboards/infrastructure/partials/home_sections.yaml index 08945b2d..45229283 100644 --- a/config/dashboards/infrastructure/partials/home_sections.yaml +++ b/config/dashboards/infrastructure/partials/home_sections.yaml @@ -12,10 +12,11 @@ # Notes: Network trend panels live on Network so Overview stays focused on broad house status. # Notes: Power & Solar shows realtime flow; accumulated history remains in Energy. # Notes: Scheduled Proxmox, Docker host, and HVAC maintenance plus sustained WAN quality/reset warnings render amber; critical conditions remain red. -# Notes: Active Issue rows enumerate faults, scheduled maintenance, and reset urgency so drill-down is optional for initial triage. -# Notes: BearClaw scheduled-job warnings/errors roll up under Joanna and appear as an exceptions-only Active Issue row. +# Notes: Active Issue rows enumerate actionable faults and maintenance; optional notices stay in the blue banner. +# Notes: Scheduled-job warnings/errors appear individually, using the same rows as Joanna and the Systems count. # Notes: Water and Systems surface normalized Rheem faults; telemetry-only loss renders amber. # Notes: Back to Overview is the first control in the landing-view navigation strip. +# Notes: The landing strip omits the redundant Systems tile and keeps twelve category tiles. # Notes: UPS health covers both Garage and Office NUT servers and drills into the shared UPS view. # Notes: UPS alarms and telemetry loss roll up alongside utility outages; other submenu faults and Repairs gate Systems all-clear. # Notes: Systems color follows the shared highest severity, with warnings amber and any critical issue red. @@ -45,6 +46,7 @@ "(max-width: 900px)": grid-template-columns: repeat(2, minmax(0, 1fr)) cards: + - !include /config/dashboards/infrastructure/partials/informational_notices.yaml - type: custom:button-card template: bearstone_nav_back name: Back to Overview @@ -55,18 +57,6 @@ action: navigate navigation_path: /lovelace/home - - type: custom:button-card - template: bearstone_infra_status_kpi - entity: sensor.infra_dashboard_active_issue_count - name: Systems - icon: mdi:shield-check-outline - variables: - ok_state: '0' - healthy_text: ALL CLEAR - warning_text: '[[[ return `${entity.state} ACTIVE`; ]]]' - raw_suffix: ACTIVE - severity_attribute: severity - - type: custom:button-card template: bearstone_infra_status_kpi entity: binary_sensor.home_water_problem @@ -281,9 +271,9 @@ if (app === 'on') return 'MINI APP ERROR'; if (entity?.state === 'error') return 'JOB ERROR'; const security = states['sensor.joanna_security_updates']; - if (security?.attributes?.severity === 'critical') return 'CRITICAL REVIEW'; + if (security?.attributes?.severity === 'critical') return 'URGENT CODEX'; if (security?.state === 'waiting_vendor_patch') return 'VENDOR PATCHES'; - if (security?.state !== 'review') return security?.state === 'needs_attention' ? 'SECURITY REVIEW' : 'SECURITY COVERAGE'; + if (security?.state !== 'review') return security?.state === 'needs_attention' ? 'NEEDS CODEX' : 'CODEX SCAN CHECK'; if (app !== 'off') return 'MINI APP UNKNOWN'; return ({ ok: 'HEALTHY', warning: 'JOB WARNING', error: 'JOB ERROR' })[entity?.state] || 'UNKNOWN'; ]]] @@ -319,12 +309,12 @@ cards: - type: custom:button-card template: bearstone_infra_panel_header - name: Active Issue + name: Active Issues - type: custom:button-card template: bearstone_infra_alert_row entity: sensor.joanna_security_updates - name: Security & Updates review + name: Security update work for Codex icon: mdi:shield-search variables: alert_kind: state_not @@ -332,10 +322,10 @@ alert_severity: '[[[ return entity?.attributes?.severity === "critical" ? "critical" : "warning"; ]]]' exception_detail: > [[[ return entity?.state === 'needs_attention' - ? `${entity?.attributes?.services_need_review ?? 0} SERVICES TO REVIEW · OPEN JOANNA FOR DETAILS` - : 'SCAN COVERAGE NEEDS REVIEW · OPEN JOANNA FOR DETAILS'; ]]] + ? `${entity?.attributes?.services_need_review ?? 0} SERVICES NEED CODEX | OPEN JOANNA TO COPY PROMPT` + : 'SCAN COVERAGE NEEDS CODEX | OPEN JOANNA TO COPY PROMPT'; ]]] state_display: > - [[[ return entity?.attributes?.severity === 'critical' ? 'CRITICAL REVIEW' : 'REVIEW'; ]]] + [[[ return entity?.attributes?.severity === 'critical' ? 'URGENT CODEX' : 'NEEDS CODEX'; ]]] tap_action: action: navigate navigation_path: /dashboard-infrastructure/joanna @@ -563,30 +553,7 @@ action: navigate navigation_path: /dashboard-infrastructure/kiosks - - type: custom:button-card - template: bearstone_infra_alert_row - entity: sensor.bearclaw_scheduled_job_health - name: > - [[[ - const issues = entity?.attributes?.issues; - return Array.isArray(issues) && issues.length === 1 - ? issues[0].label : 'BearClaw scheduled jobs need attention'; - ]]] - icon: mdi:calendar-alert - variables: - alert_kind: state_not - ok_state: ok - alert_severity: '[[[ return entity?.state === "warning" ? "warning" : "critical"; ]]]' - exception_detail: > - [[[ - const issues = entity?.attributes?.issues; - const detail = Array.isArray(issues) && issues.length === 1 ? issues[0].detail : null; - return String(detail || entity?.attributes?.summary || 'SCHEDULED JOB STATUS REQUIRES REVIEW'); - ]]] - state_display: '[[[ return entity?.state === "warning" ? "WARNING" : "ERROR"; ]]]' - tap_action: - action: navigate - navigation_path: /dashboard-infrastructure/joanna + - !include /config/dashboards/infrastructure/partials/scheduled_job_issues.yaml - type: custom:button-card template: bearstone_infra_alert_row @@ -927,7 +894,8 @@ icon: mdi:notebook-alert-outline variables: alert_kind: state_not - ok_state: ok + ok_states: [ok, indexing, recovering] + exception_detail: '[[[ return entity?.attributes?.detail || "OneNote index needs operator review"; ]]]' alert_severity: '[[[ return entity?.state === "warning" ? "warning" : "critical"; ]]]' tap_action: action: navigate diff --git a/config/dashboards/infrastructure/partials/informational_notices.yaml b/config/dashboards/infrastructure/partials/informational_notices.yaml new file mode 100644 index 00000000..92e913f6 --- /dev/null +++ b/config/dashboards/infrastructure/partials/informational_notices.yaml @@ -0,0 +1,28 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Dashboard notices - optional appliance information +# A quiet full-width banner, separate from actionable health warnings. +# ------------------------------------------------------------------- +# Notes: Native Markdown supplies HA's info alert and server-side expiry +# handling; button-card templates remain the status-card renderer. +# Notes: Reuses live-only telemetry; no extra sensor, history, or dispatch. +###################################################################### + +type: markdown +grid_options: + columns: full +view_layout: + grid-column: 1 / -1 +text_only: true +show_empty: false +entity_id: sensor.bearclaw_status_telemetry +content: >- + {%- set health = state_attr('sensor.bearclaw_status_telemetry', 'scheduledJobsHealth') | default({}, true) -%} + {%- if is_state('sensor.bearclaw_status_telemetry', 'ok') -%} + {%- for notice in health.get('notices', []) if notice.get('severity') == 'info' and as_timestamp(notice.get('expiresAt'), 0) > as_timestamp(now()) -%} + {{ notice.get('label', '') | e }}
{{ notice.get('detail', '') | e }}
+ {%- endfor -%} + {%- endif -%} diff --git a/config/dashboards/infrastructure/partials/joanna_sections.yaml b/config/dashboards/infrastructure/partials/joanna_sections.yaml index e5761e8b..d93920df 100644 --- a/config/dashboards/infrastructure/partials/joanna_sections.yaml +++ b/config/dashboards/infrastructure/partials/joanna_sections.yaml @@ -115,23 +115,50 @@ ok_state: review healthy_text: CLEAR severity_attribute: severity - warning_text: NEEDS REVIEW - critical_text: CRITICAL REVIEW + warning_text: NEEDS CODEX + critical_text: URGENT CODEX state_display: > - [[[ return entity?.attributes?.severity === 'critical' ? 'CRITICAL REVIEW' : entity?.state === 'needs_attention' ? 'NEEDS REVIEW' : entity?.state === 'review' ? 'CLEAR' : String(entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]] + [[[ return entity?.attributes?.severity === 'critical' ? 'URGENT CODEX' : entity?.state === 'needs_attention' ? 'NEEDS CODEX' : entity?.state === 'review' ? 'CLEAR' : String(entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]] tap_action: action: url url_path: !secret bearclaw_security_admin_url - type: custom:button-card template: bearstone_infra_kpi entity: sensor.joanna_security_updates - name: Services need review - icon: mdi:shield-alert-outline + name: Copy Codex prompt + icon: mdi:content-copy + state_display: > + [[[ return entity?.attributes?.codex_prompt ? `${entity?.attributes?.services_need_review ?? 0} SERVICES` : 'NOT NEEDED'; ]]] tap_action: + action: javascript + javascript: > + [[[ + const text = entity?.attributes?.codex_prompt; + const toast = (message) => this.dispatchEvent(new CustomEvent('hass-notification', { + bubbles: true, composed: true, detail: { message } + })); + if (['unknown', 'unavailable'].includes(entity?.state) || typeof text !== 'string' || !text.trim()) { + toast('No current Codex prompt is available.'); + return; + } + const manual = () => window.prompt('Copy this prompt into Codex:', text); + const copied = () => toast('Copied. Paste into Codex to start the review.'); + if (window.isSecureContext && navigator.clipboard?.writeText) { + navigator.clipboard.writeText(text).then(copied).catch(manual); + } else { + const area = document.createElement('textarea'); + area.value = text; + document.body.appendChild(area); + area.focus(); area.select(); + let ok = false; + try { ok = document.execCommand('copy'); } catch {} + area.remove(); + if (ok) copied(); else manual(); + } + ]]] + hold_action: action: url url_path: !secret bearclaw_security_admin_url - state_display: > - [[[ return entity?.attributes?.services_need_review ?? '—'; ]]] - type: custom:button-card template: bearstone_infra_kpi entity: sensor.joanna_security_updates @@ -300,27 +327,7 @@ return parts.join(' · ') || 'STATUS UNKNOWN'; ]]] - - type: custom:button-card - template: bearstone_infra_alert_row - entity: sensor.bearclaw_scheduled_job_health - name: > - [[[ - const issue = Array.isArray(entity?.attributes?.issues) ? entity.attributes.issues[0] : null; - return issue?.label || 'Scheduled job needs attention'; - ]]] - icon: mdi:alert-circle-outline - variables: - alert_kind: state_not - ok_state: ok - alert_severity: > - [[[ return entity?.state === 'warning' ? 'warning' : 'critical'; ]]] - exception_detail: > - [[[ - const issue = Array.isArray(entity?.attributes?.issues) ? entity.attributes.issues[0] : null; - return String(issue?.detail || entity?.attributes?.summary || 'SCHEDULED JOB STATUS REQUIRES REVIEW'); - ]]] - state_display: > - [[[ return entity?.state === 'warning' ? 'WARNING' : 'ERROR'; ]]] + - !include /config/dashboards/infrastructure/partials/scheduled_job_issues.yaml - type: custom:button-card template: bearstone_infra_status_kpi @@ -343,12 +350,17 @@ entity: sensor.joanna_onenote_kb_health name: OneNote account indexes icon: mdi:notebook-multiple + show_label: true + label: '[[[ return entity?.attributes?.detail || ""; ]]]' variables: - ok_state: ok - warning_state: warning - healthy_text: CURRENT - warning_text: SYNC STALE - critical_text: INDEX ERROR + show_detail: true + status_severity: > + [[[ + const status = String(entity?.state ?? 'unknown'); + if (['indexing', 'recovering'].includes(status)) return 'info'; + if (status === 'ok') return 'healthy'; + return status === 'warning' ? 'warning' : 'critical'; + ]]] state_display: > [[[ const docs = Number(entity?.attributes?.docs ?? 0).toLocaleString(); diff --git a/config/dashboards/infrastructure/partials/kiosk_sections.yaml b/config/dashboards/infrastructure/partials/kiosk_sections.yaml index 52b5e6f5..ed655382 100644 --- a/config/dashboards/infrastructure/partials/kiosk_sections.yaml +++ b/config/dashboards/infrastructure/partials/kiosk_sections.yaml @@ -4,9 +4,13 @@ # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- # Infrastructure Partial - kiosks +# Related Issue: 2030 # Exception-first health, device telemetry, and live dashboard previews. # ------------------------------------------------------------------- # Notes: Included by config/dashboards/infrastructure/views/05z_kiosks.yaml. +# Notes: Living Room TV is an on-demand kiosk, outside fleet health checks. +# Notes: Raven app status replaces the retired Samsung Haven browser preview. +# Notes: Kitchen Show power uses its charging sensor; battery telemetry is absent. ###################################################################### # ------------------------------------------------------------------- @@ -131,63 +135,7 @@ [[[ return String(entity?.attributes?.status ?? entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]] # ------------------------------------------------------------------- -# Latest device screenshots -# ------------------------------------------------------------------- -# Native picture cards provide live device screenshots; button-card cannot render camera images. -- type: grid - column_span: 4 - columns: 4 - square: false - cards: - - camera_view: auto - type: picture-glance - title: Bedroom Tablet - image: https://demo.home-assistant.io/stub_config/kitchen.png - entities: - - entity: binary_sensor.bedroom_kiosk_needs_attention - image_entity: image.alarm_panel_1_screenshot - tap_action: - action: navigate - navigation_path: /dashboard-bedroom/home - - - camera_view: auto - type: picture-glance - title: Kitchen Show - image: https://demo.home-assistant.io/stub_config/kitchen.png - entities: - - entity: binary_sensor.kitchen_kiosk_needs_attention - - entity: sensor.kitchen_show_last_screenshot - camera_image: camera.kitchen_show_screenshot - tap_action: - action: navigate - navigation_path: /dashboard-kiosk-satellite-staging/home - - - camera_view: auto - type: picture-glance - title: Upstairs Show - image: https://demo.home-assistant.io/stub_config/kitchen.png - entities: - - entity: binary_sensor.upstairs_kiosk_needs_attention - - entity: sensor.upstairs_show_last_screenshot - camera_image: camera.upstairs_show_screenshot - tap_action: - action: navigate - navigation_path: /dashboard-kiosk-satellite-staging/echo-show5 - - - camera_view: auto - type: picture-glance - title: Office Show - image: https://demo.home-assistant.io/stub_config/kitchen.png - entities: - - entity: binary_sensor.office_kiosk_needs_attention - - entity: sensor.office_show_last_screenshot - camera_image: camera.office_show_screenshot - tap_action: - action: navigate - navigation_path: /dashboard-kiosk-satellite-staging/echo-show5 - -# ------------------------------------------------------------------- -# Living Room Nest - actual circular framebuffer preview +# Screens - device captures and native Roku Raven status # ------------------------------------------------------------------- - type: grid column_span: 4 @@ -199,13 +147,62 @@ columns: full layout_type: custom:grid-layout layout: - grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) + grid-template-columns: repeat(3, minmax(0, 1fr)) grid-gap: 12px margin: 0 mediaquery: - "(max-width: 900px)": + "(max-width: 700px)": grid-template-columns: minmax(0, 1fr) + "(max-width: 1200px)": + grid-template-columns: repeat(2, minmax(0, 1fr)) cards: + - camera_view: auto + type: picture-glance + title: Bedroom Tablet + image: https://demo.home-assistant.io/stub_config/kitchen.png + entities: + - entity: binary_sensor.bedroom_kiosk_needs_attention + image_entity: image.alarm_panel_1_screenshot + tap_action: + action: navigate + navigation_path: /dashboard-bedroom/home + + - camera_view: auto + type: picture-glance + title: Kitchen Show + image: https://demo.home-assistant.io/stub_config/kitchen.png + entities: + - entity: binary_sensor.kitchen_kiosk_needs_attention + - entity: sensor.kitchen_show_last_screenshot + camera_image: camera.kitchen_show_screenshot + tap_action: + action: navigate + navigation_path: /dashboard-kiosk-satellite-staging/home + + - camera_view: auto + type: picture-glance + title: Upstairs Show + image: https://demo.home-assistant.io/stub_config/kitchen.png + entities: + - entity: binary_sensor.upstairs_kiosk_needs_attention + - entity: sensor.upstairs_show_last_screenshot + camera_image: camera.upstairs_show_screenshot + tap_action: + action: navigate + navigation_path: /dashboard-kiosk-satellite-staging/echo-show5 + + - camera_view: auto + type: picture-glance + title: Office Show + image: https://demo.home-assistant.io/stub_config/kitchen.png + entities: + - entity: binary_sensor.office_kiosk_needs_attention + - entity: sensor.office_show_last_screenshot + camera_image: camera.office_show_screenshot + tap_action: + action: navigate + navigation_path: /dashboard-kiosk-satellite-staging/echo-show5 + - type: custom:button-card template: bearstone_infra_round_display entity: image.living_room_nest_screen @@ -219,6 +216,368 @@ timestamp_entity: sensor.living_room_nest_last_screenshot capture_entity: button.living_room_nest_capture_screen + - type: custom:vertical-stack-in-card + card_mod: + style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml + cards: + - type: custom:button-card + template: bearstone_infra_panel_header + name: Raven - Living Room Roku + icon: mdi:television + - type: custom:button-card + template: bearstone_mobile_control + entity: sensor.living_room_ultra_active_app_id + name: Open Raven + show_entity_picture: true + entity_picture: /local/custom_ui/floorplan/images/branding/vCloudInfoCircle.png + triggers_update: + - media_player.samsung_q70_series_65 + variables: + status_severity: > + [[[ + const tv = states['media_player.samsung_q70_series_65']?.state; + if (tv === 'off') return 'active'; + if (['unknown', 'unavailable', undefined].includes(entity?.state)) return 'warning'; + return tv === 'on' && entity?.state === 'dev' ? 'healthy' : 'active'; + ]]] + state_display: > + [[[ + const tv = states['media_player.samsung_q70_series_65']?.state; + if (tv === 'off') return 'TV OFF'; + if (['unknown', 'unavailable', undefined].includes(entity?.state)) return 'ROKU UNAVAILABLE'; + return entity?.state === 'dev' ? 'RAVEN RUNNING' : 'TAP TO OPEN'; + ]]] + tap_action: + action: perform-action + perform_action: rest_command.samsung_living_room_launch_haven + hold_action: + action: navigate + navigation_path: /dashboard-infrastructure/roku-remote + double_tap_action: + action: none + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.living_room_ultra_active_app + name: Current Roku app + icon: mdi:apps + triggers_update: + - sensor.living_room_ultra_active_app_id + state_display: > + [[[ return states['sensor.living_room_ultra_active_app_id']?.state === '562859' ? 'Roku Home' : entity?.state ?? 'Unknown'; ]]] + - type: custom:button-card + template: bearstone_infra_list_row + name: Roku Remote + icon: mdi:remote + show_state: false + tap_action: + action: navigate + navigation_path: /dashboard-infrastructure/roku-remote + +# ------------------------------------------------------------------- +# Device details - status and occasional controls +# ------------------------------------------------------------------- +- type: grid + column_span: 4 + columns: 1 + square: false + cards: + - type: custom:layout-card + grid_options: + columns: full + layout_type: custom:grid-layout + layout: + grid-template-columns: repeat(3, minmax(0, 1fr)) + grid-gap: 12px + margin: 0 + mediaquery: + "(max-width: 700px)": + grid-template-columns: minmax(0, 1fr) + "(max-width: 1200px)": + grid-template-columns: repeat(2, minmax(0, 1fr)) + cards: + - type: custom:vertical-stack-in-card + card_mod: + style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml + cards: + - type: custom:button-card + template: bearstone_infra_panel_header + name: Bedroom Tablet + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.alarm_panel_1_battery + name: Battery + icon: mdi:battery + + - type: custom:button-card + template: bearstone_infra_list_row + entity: binary_sensor.alarm_panel_1_plugged_in + name: Power + icon: mdi:power-plug + state_display: > + [[[ return entity?.state === 'on' ? 'PLUGGED IN' : 'ON BATTERY'; ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.alarm_panel_1_current_page + name: Dashboard + icon: mdi:web + state_display: > + [[[ + const page = String(entity?.state ?? ''); + return page.includes('/local/bedroom-kiosk.html') ? 'PAGE OK' : 'CHECK PAGE'; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.alarm_panel_1_foreground_app + name: Foreground App + icon: mdi:application-outline + state_display: > + [[[ return entity?.state === 'de.ozerov.fully' ? 'FULLY KIOSK' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] + + - type: custom:vertical-stack-in-card + card_mod: + style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml + cards: + - type: custom:button-card + template: bearstone_infra_panel_header + name: Kitchen Show + + - type: custom:button-card + template: bearstone_infra_list_row + entity: binary_sensor.kitchen_show_charging + name: Power + icon: mdi:power-plug + state_display: > + [[[ return entity?.state === 'on' ? 'PLUGGED IN' : entity?.state === 'off' ? 'ON BATTERY' : 'UNKNOWN'; ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.kitchen_show_last_seen + name: Last Seen + icon: mdi:clock-check-outline + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.kitchen_show_current_page + name: Dashboard + icon: mdi:web + state_display: > + [[[ + const page = String(entity?.state ?? ''); + return page.includes('/dashboard-kiosk-satellite-staging/home') ? 'PAGE OK' : 'CHECK PAGE'; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: update.kitchen_show_update + name: Kiosk Satellite + icon: mdi:update + state_display: > + [[[ + const installed = entity?.attributes?.installed_version ?? 'unknown'; + const latest = entity?.attributes?.latest_version ?? installed; + return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.kitchen_show_remote_admin + name: Remote Admin + icon: mdi:remote-desktop + state_display: AVAILABLE + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.kitchen_show_voice_satellite + name: Voice Assistant + icon: mdi:microphone + + - type: custom:button-card + template: bearstone_infra_list_row + entity: select.kitchen_show_assistant + name: Assist Pipeline + icon: mdi:account-voice + + - type: custom:button-card + template: bearstone_infra_list_row + entity: select.kitchen_show_wake_word + name: Wake Word + icon: mdi:ear-hearing + + - type: custom:vertical-stack-in-card + card_mod: + style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml + cards: + - type: custom:button-card + template: bearstone_infra_panel_header + name: Upstairs Show + + - type: custom:button-card + template: bearstone_infra_list_row + entity: binary_sensor.upstairs_show_charging + name: Power + icon: mdi:power-plug + state_display: > + [[[ return states["binary_sensor.upstairs_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.upstairs_show_last_seen + name: Last Seen + icon: mdi:clock-check-outline + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.upstairs_show_current_page + name: Dashboard + icon: mdi:web + state_display: > + [[[ + const page = String(entity?.state ?? ''); + return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE'; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.upstairs_show_foreground_app + name: Foreground App + icon: mdi:application-outline + state_display: > + [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: light.upstairs_show_screen + name: Screen + icon: mdi:monitor + state_display: > + [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: update.upstairs_show_update + name: Kiosk Satellite + icon: mdi:update + state_display: > + [[[ + const installed = entity?.attributes?.installed_version ?? 'unknown'; + const latest = entity?.attributes?.latest_version ?? installed; + return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.upstairs_show_remote_admin + name: Remote Admin + icon: mdi:remote-desktop + state_display: AVAILABLE + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.upstairs_show_voice_satellite + name: Voice Assistant + icon: mdi:microphone + + - type: custom:button-card + template: bearstone_infra_list_row + entity: select.upstairs_show_assistant + name: Assist Pipeline + icon: mdi:account-voice + + - type: custom:button-card + template: bearstone_infra_list_row + entity: select.upstairs_show_wake_word + name: Wake Word + icon: mdi:ear-hearing + + - type: custom:vertical-stack-in-card + card_mod: + style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml + cards: + - type: custom:button-card + template: bearstone_infra_panel_header + name: Office Show + + - type: custom:button-card + template: bearstone_infra_list_row + entity: binary_sensor.office_show_charging + name: Power + icon: mdi:power-plug + state_display: > + [[[ return states["binary_sensor.office_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.office_show_last_seen + name: Last Seen + icon: mdi:clock-check-outline + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.office_show_current_page + name: Dashboard + icon: mdi:web + state_display: > + [[[ + const page = String(entity?.state ?? ''); + return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE'; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.office_show_foreground_app + name: Foreground App + icon: mdi:application-outline + state_display: > + [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: light.office_show_screen + name: Screen + icon: mdi:monitor + state_display: > + [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: update.office_show_update + name: Kiosk Satellite + icon: mdi:update + state_display: > + [[[ + const installed = entity?.attributes?.installed_version ?? 'unknown'; + const latest = entity?.attributes?.latest_version ?? installed; + return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`; + ]]] + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.office_show_remote_admin + name: Remote Admin + icon: mdi:remote-desktop + state_display: AVAILABLE + + - type: custom:button-card + template: bearstone_infra_list_row + entity: sensor.office_show_voice_satellite + name: Voice Assistant + icon: mdi:microphone + + - type: custom:button-card + template: bearstone_infra_list_row + entity: select.office_show_assistant + name: Assist Pipeline + icon: mdi:account-voice + + - type: custom:button-card + template: bearstone_infra_list_row + entity: select.office_show_wake_word + name: Wake Word + icon: mdi:ear-hearing + - type: custom:vertical-stack-in-card card_mod: style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml @@ -289,291 +648,70 @@ target: entity_id: button.living_room_nest_restart_display -# ------------------------------------------------------------------- -# Device telemetry -# ------------------------------------------------------------------- -- type: grid - column_span: 4 - columns: 4 - square: false - cards: - - type: custom:vertical-stack-in-card - card_mod: - style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml - cards: - - type: custom:button-card - template: bearstone_infra_panel_header - name: Bedroom Tablet - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.alarm_panel_1_battery - name: Battery - icon: mdi:battery - - - type: custom:button-card - template: bearstone_infra_list_row - entity: binary_sensor.alarm_panel_1_plugged_in - name: Power - icon: mdi:power-plug - state_display: > - [[[ return entity?.state === 'on' ? 'PLUGGED IN' : 'ON BATTERY'; ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.alarm_panel_1_current_page - name: Dashboard - icon: mdi:web - state_display: > - [[[ - const page = String(entity?.state ?? ''); - return page.includes('/local/bedroom-kiosk.html') ? 'PAGE OK' : 'CHECK PAGE'; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.alarm_panel_1_foreground_app - name: Foreground App - icon: mdi:application-outline - state_display: > - [[[ return entity?.state === 'de.ozerov.fully' ? 'FULLY KIOSK' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] - - - type: custom:vertical-stack-in-card - card_mod: - style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml - cards: - - type: custom:button-card - template: bearstone_infra_panel_header - name: Kitchen Show - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.kitchen_show_battery - name: Battery - icon: mdi:battery - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.kitchen_show_last_seen - name: Last Seen - icon: mdi:clock-check-outline - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.kitchen_show_current_page - name: Dashboard - icon: mdi:web - state_display: > - [[[ - const page = String(entity?.state ?? ''); - return page.includes('/dashboard-kiosk-satellite-staging/home') ? 'PAGE OK' : 'CHECK PAGE'; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: update.kitchen_show_update - name: Kiosk Satellite - icon: mdi:update - state_display: > - [[[ - const installed = entity?.attributes?.installed_version ?? 'unknown'; - const latest = entity?.attributes?.latest_version ?? installed; - return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.kitchen_show_remote_admin - name: Remote Admin - icon: mdi:remote-desktop - state_display: AVAILABLE - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.kitchen_show_voice_satellite - name: Voice Assistant - icon: mdi:microphone - - - type: custom:button-card - template: bearstone_infra_list_row - entity: select.kitchen_show_assistant - name: Assist Pipeline - icon: mdi:account-voice - - - type: custom:button-card - template: bearstone_infra_list_row - entity: select.kitchen_show_wake_word - name: Wake Word - icon: mdi:ear-hearing - - - type: custom:vertical-stack-in-card - card_mod: - style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml - cards: - - type: custom:button-card - template: bearstone_infra_panel_header - name: Upstairs Show - - - type: custom:button-card - template: bearstone_infra_list_row - entity: binary_sensor.upstairs_show_charging - name: Power - icon: mdi:power-plug - state_display: > - [[[ return states["binary_sensor.upstairs_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.upstairs_show_last_seen - name: Last Seen - icon: mdi:clock-check-outline - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.upstairs_show_current_page - name: Dashboard - icon: mdi:web - state_display: > - [[[ - const page = String(entity?.state ?? ''); - return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE'; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.upstairs_show_foreground_app - name: Foreground App - icon: mdi:application-outline - state_display: > - [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: light.upstairs_show_screen - name: Screen - icon: mdi:monitor - state_display: > - [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: update.upstairs_show_update - name: Kiosk Satellite - icon: mdi:update - state_display: > - [[[ - const installed = entity?.attributes?.installed_version ?? 'unknown'; - const latest = entity?.attributes?.latest_version ?? installed; - return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.upstairs_show_remote_admin - name: Remote Admin - icon: mdi:remote-desktop - state_display: AVAILABLE - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.upstairs_show_voice_satellite - name: Voice Assistant - icon: mdi:microphone - - - type: custom:button-card - template: bearstone_infra_list_row - entity: select.upstairs_show_assistant - name: Assist Pipeline - icon: mdi:account-voice - - - type: custom:button-card - template: bearstone_infra_list_row - entity: select.upstairs_show_wake_word - name: Wake Word - icon: mdi:ear-hearing - - - type: custom:vertical-stack-in-card - card_mod: - style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml - cards: - - type: custom:button-card - template: bearstone_infra_panel_header - name: Office Show - - - type: custom:button-card - template: bearstone_infra_list_row - entity: binary_sensor.office_show_charging - name: Power - icon: mdi:power-plug - state_display: > - [[[ return states["binary_sensor.office_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.office_show_last_seen - name: Last Seen - icon: mdi:clock-check-outline - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.office_show_current_page - name: Dashboard - icon: mdi:web - state_display: > - [[[ - const page = String(entity?.state ?? ''); - return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE'; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.office_show_foreground_app - name: Foreground App - icon: mdi:application-outline - state_display: > - [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: light.office_show_screen - name: Screen - icon: mdi:monitor - state_display: > - [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: update.office_show_update - name: Kiosk Satellite - icon: mdi:update - state_display: > - [[[ - const installed = entity?.attributes?.installed_version ?? 'unknown'; - const latest = entity?.attributes?.latest_version ?? installed; - return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`; - ]]] - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.office_show_remote_admin - name: Remote Admin - icon: mdi:remote-desktop - state_display: AVAILABLE - - - type: custom:button-card - template: bearstone_infra_list_row - entity: sensor.office_show_voice_satellite - name: Voice Assistant - icon: mdi:microphone - - - type: custom:button-card - template: bearstone_infra_list_row - entity: select.office_show_assistant - name: Assist Pipeline - icon: mdi:account-voice - - - type: custom:button-card - template: bearstone_infra_list_row - entity: select.office_show_wake_word - name: Wake Word - icon: mdi:ear-hearing + - type: custom:vertical-stack-in-card + card_mod: + style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml + cards: + - type: custom:button-card + template: bearstone_infra_panel_header + name: Raven - Living Room TV + icon: mdi:television + - type: custom:button-card + template: bearstone_infra_list_row + entity: media_player.samsung_q70_series_65 + name: TV power + icon: mdi:power + state_display: > + [[[ return entity?.state === 'off' ? 'OFF (NORMAL)' : String(entity?.state ?? 'unknown').toUpperCase(); ]]] + - type: custom:button-card + template: bearstone_infra_list_row + entity: timer.roku_haven_manual_exit + name: Manual-exit pause + icon: mdi:pause-circle-outline + state_display: > + [[[ return entity?.state === 'active' ? 'PAUSED AFTER EXIT' : entity?.state === 'idle' ? 'READY' : 'UNKNOWN'; ]]] + - type: custom:button-card + template: bearstone_infra_list_row + entity: input_boolean.haven + name: Automatic Raven launch + icon: mdi:play-circle-outline + state_display: > + [[[ return entity?.state === 'on' ? 'ENABLED' : entity?.state === 'off' ? 'PAUSED UNTIL 3 AM' : 'UNKNOWN'; ]]] + tap_action: + action: toggle + - type: custom:button-card + template: bearstone_infra_list_row + entity: automation.living_room_tv_haven_on_roku_screensaver + name: Last automatic launch + icon: mdi:history + state_display: > + [[[ + const last = entity?.attributes?.last_triggered; + if (!last) return 'NOT YET'; + const date = new Date(last); + return Number.isNaN(date.getTime()) ? 'UNKNOWN' : date.toLocaleString(undefined, {month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'}); + ]]] + - type: custom:button-card + template: bearstone_infra_list_row + name: Open Raven + icon: mdi:view-dashboard-outline + show_state: false + tap_action: + action: perform-action + perform_action: rest_command.samsung_living_room_launch_haven + - type: custom:button-card + template: bearstone_infra_list_row + name: Roku Remote + icon: mdi:remote + show_state: false + tap_action: + action: navigate + navigation_path: /dashboard-infrastructure/roku-remote + - type: custom:button-card + template: bearstone_infra_list_row + name: TV Remote + icon: mdi:remote-tv + show_state: false + tap_action: + action: navigate + navigation_path: /dashboard-infrastructure/tv-remote diff --git a/config/dashboards/infrastructure/partials/scheduled_job_issues.yaml b/config/dashboards/infrastructure/partials/scheduled_job_issues.yaml new file mode 100644 index 00000000..efebb529 --- /dev/null +++ b/config/dashboards/infrastructure/partials/scheduled_job_issues.yaml @@ -0,0 +1,44 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Scheduled Job Issues - shared exceptions for Systems and Joanna +# Notes: Existing alert-row styling preserves each job's own severity. +# Notes: Auto-entities is needed to render a changing number of job rows. +###################################################################### + +type: custom:auto-entities +show_empty: false +card: + type: grid + columns: 1 + square: false +card_param: cards +filter: + template: >- + {% from 'infrastructure_health.jinja' import scheduled_job_issues %} + {% set issues = (scheduled_job_issues | as_function)() %} + {% set ns = namespace(cards=[]) %} + {% for issue in issues %} + {% set next_run = as_datetime(issue.get('nextRunAt'), none) %} + {% set detail = issue.get('detail') or 'Open BearClaw Admin > Activity > Scheduled Jobs to review this warning.' %} + {% if next_run is not none %} + {% set detail = detail ~ ' Next check: ' ~ as_local(next_run).strftime('%b %d, %I:%M %p') ~ '.' %} + {% endif %} + {% set warning = issue.get('severity') == 'warning' %} + {% set ns.cards = ns.cards + [{ + 'type': 'custom:button-card', 'template': 'bearstone_infra_alert_row', + 'entity': 'sensor.bearclaw_scheduled_job_health', + 'name': issue.get('label') or 'Scheduled job needs attention', + 'icon': 'mdi:calendar-alert', + 'variables': { + 'alert_kind': 'state_not', 'ok_state': 'ok', + 'alert_severity': 'warning' if warning else 'critical', + 'exception_detail': detail + }, + 'state_display': 'WARNING' if warning else 'ERROR', + 'tap_action': {'action': 'more-info'} + }] %} + {% endfor %} + {{ ns.cards | to_json }} diff --git a/config/dashboards/infrastructure/templates/button_card_templates.yaml b/config/dashboards/infrastructure/templates/button_card_templates.yaml index dd42ffd9..657fcd98 100644 --- a/config/dashboards/infrastructure/templates/button_card_templates.yaml +++ b/config/dashboards/infrastructure/templates/button_card_templates.yaml @@ -5,6 +5,7 @@ # ------------------------------------------------------------------- # Infrastructure Templates - custom:button-card # Related Issue: 1560 +# Related Issue: 2029 # Shared `button_card_templates` used across Infrastructure and Overview views. # ------------------------------------------------------------------- # Notes: Keep templates generic; view-specific tuning stays in the view partials. @@ -749,7 +750,12 @@ bearstone_infra_container_row: return (hasRestart && isMonitored) ? 'block' : 'none'; ]]] +# Seasonal paint stays inside the existing header; dimensions and content are unchanged. bearstone_infra_panel_header: + variables: + seasonal: '[[[ return window.BearStoneKioskAmbient?.seasonal?.(states) || {mode: "standard", wash: "none", emblem: ""}; ]]]' + triggers_update: + - sensor.holiday_lighting_mode show_icon: false show_state: false show_label: false @@ -758,6 +764,7 @@ bearstone_infra_panel_header: styles: card: - background: transparent + - background-image: '[[[ return variables.seasonal.wash; ]]]' - border: none - box-shadow: none - padding: 6px 2px 10px 2px @@ -770,6 +777,8 @@ bearstone_infra_panel_header: bearstone_infra_panel_header_icon: template: bearstone_infra_panel_header + show_entity_picture: '[[[ return Boolean(variables.seasonal.emblem); ]]]' + entity_picture: '[[[ return variables.seasonal.emblem || ""; ]]]' show_icon: true show_name: true icon: mdi:view-dashboard-outline @@ -917,7 +926,7 @@ bearstone_infra_alert_row: else if (kind === 'binary_off') show = (stateStr === 'off'); else if (kind === 'state_not') { const okState = String(variables?.ok_state ?? '').toLowerCase(); - show = stateStr.toLowerCase() !== okState; + show = !(Array.isArray(variables?.ok_states) ? variables.ok_states.map(value => String(value).toLowerCase()) : [okState]).includes(stateStr.toLowerCase()); } else if (kind === 'state_is') { const alertState = String(variables?.alert_state ?? '').toLowerCase(); @@ -1126,10 +1135,21 @@ bearstone_infra_status_kpi: return 'ATTENTION'; ]]] styles: + grid: + - grid-template-areas: '[[[ return variables?.show_detail ? `"i n" "s s" "l l"` : `"i n" "s s"`; ]]]' + - grid-template-rows: '[[[ return variables?.show_detail ? "20px min-content min-content" : "20px min-content"; ]]]' + label: + - grid-area: l + - white-space: normal + - text-align: center + - font-size: 12px + - line-height: 16px + - opacity: 0.8 card: - border-color: > [[[ const severity = String(variables?.status_severity ?? ''); + if (severity === 'info') return 'rgba(30,136,229,0.40)'; if (severity === 'healthy') return 'rgba(67,160,71,0.45)'; if (severity === 'critical') return 'rgba(229,57,53,0.40)'; return 'rgba(245,124,0,0.45)'; @@ -1137,6 +1157,7 @@ bearstone_infra_status_kpi: - background: > [[[ const severity = String(variables?.status_severity ?? ''); + if (severity === 'info') return 'rgba(227,242,253,0.85)'; if (severity === 'healthy') return 'rgba(232,245,233,0.85)'; if (severity === 'critical') return 'rgba(255,235,238,0.85)'; return 'rgba(255,243,224,0.85)'; @@ -1145,6 +1166,7 @@ bearstone_infra_status_kpi: - color: > [[[ const severity = String(variables?.status_severity ?? ''); + if (severity === 'info') return 'rgba(21,101,192,1)'; if (severity === 'healthy') return 'rgba(46,125,50,1)'; if (severity === 'critical') return 'rgba(198,40,40,1)'; return 'rgba(230,81,0,1)'; @@ -1153,6 +1175,7 @@ bearstone_infra_status_kpi: - color: > [[[ const severity = String(variables?.status_severity ?? ''); + if (severity === 'info') return 'rgba(21,101,192,1)'; if (severity === 'healthy') return 'rgba(46,125,50,1)'; if (severity === 'critical') return 'rgba(198,40,40,1)'; return 'rgba(230,81,0,1)'; diff --git a/config/dashboards/infrastructure/views/05za_tv_remote.yaml b/config/dashboards/infrastructure/views/05za_tv_remote.yaml new file mode 100644 index 00000000..8846036b --- /dev/null +++ b/config/dashboards/infrastructure/views/05za_tv_remote.yaml @@ -0,0 +1,268 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Infrastructure Subview - Living Room TV Remote +# Related Issue: 2030 +# Large navigation, input/settings menus, and the Raven app launcher. +# ------------------------------------------------------------------- +# Notes: Open from Living Room TV on the Kiosks page. +# Notes: Each tap sends one key; Back and Exit control the TV menu. +# Notes: TV Home exposes Sources; Settings opens TV sound and Anynet+ options. +# Notes: KEY_SOURCE did not open this TV's input menu; KEY_HOME is verified. +# Notes: Reuses the 112px mobile controls without automatic key presses. +# Notes: Raven launches the native Roku app through the display bridge. +###################################################################### + +path: tv-remote +title: TV Remote +icon: mdi:remote-tv +subview: true +type: sections +max_columns: 4 +badges: [] +sections: +- type: grid + column_span: 4 + columns: 1 + square: false + cards: + - type: custom:layout-card + grid_options: + columns: full + layout_type: custom:grid-layout + layout: + grid-template-columns: repeat(6, minmax(0, 1fr)) + grid-auto-flow: row + grid-auto-rows: min-content + align-items: stretch + grid-gap: 10px + margin: 0 + cards: + - type: custom:button-card + template: bearstone_mobile_header + name: Living Room TV + icon: mdi:television + view_layout: + grid-column: 1 / -1 + + - type: custom:button-card + template: bearstone_mobile_control + entity: media_player.living_room_ultra + name: Raven + icon: mdi:view-dashboard-outline + show_state: false + variables: + status_severity: healthy + view_layout: + grid-column: 1 / -1 + tap_action: + action: perform-action + perform_action: rest_command.samsung_living_room_launch_haven + hold_action: + action: none + double_tap_action: + action: none + + # Samsung menus stay accessible without a physical TV remote. + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: TV Home + icon: mdi:home-outline + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_HOME + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Settings + icon: mdi:cog + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_MENU + hold_action: + action: none + double_tap_action: + action: none + + # Full-width Up and Down keep every touch target large without spacers. + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Up + icon: mdi:arrow-up-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: 1 / -1 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_UP + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Left + icon: mdi:arrow-left-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 2 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_LEFT + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: OK + icon: mdi:check-bold + show_state: false + variables: + status_severity: healthy + view_layout: + grid-column: span 2 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_ENTER + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Right + icon: mdi:arrow-right-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 2 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_RIGHT + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Down + icon: mdi:arrow-down-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: 1 / -1 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_DOWN + hold_action: + action: none + double_tap_action: + action: none + + # These buttons navigate the TV, rather than the Home Assistant screen. + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Back + icon: mdi:undo + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_RETURN + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.samsung_q70_series_65 + name: Exit + icon: mdi:close-circle-outline + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.samsung_q70_series_65 + data: + command: KEY_EXIT + hold_action: + action: none + double_tap_action: + action: none +cards: [] diff --git a/config/dashboards/infrastructure/views/05zb_roku_remote.yaml b/config/dashboards/infrastructure/views/05zb_roku_remote.yaml new file mode 100644 index 00000000..9ec69dde --- /dev/null +++ b/config/dashboards/infrastructure/views/05zb_roku_remote.yaml @@ -0,0 +1,266 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Infrastructure Subview - Living Room Roku Remote +# Related Issue: 2030 +# Native Roku navigation, playback, and the Raven app launcher. +# ------------------------------------------------------------------- +# Notes: Open from the Raven card or device panel on Systems > Kiosks. +# Notes: Each tap sends one Roku command; Back/Home exit Raven natively. +# Notes: TV menus remain on the separate Samsung TV Remote subview. +# Notes: Reuses the existing large mobile controls and launch action. +###################################################################### + +path: roku-remote +title: Roku Remote +icon: mdi:remote-tv +subview: true +type: sections +max_columns: 4 +badges: [] +sections: +- type: grid + column_span: 4 + columns: 1 + square: false + cards: + - type: custom:layout-card + grid_options: + columns: full + layout_type: custom:grid-layout + layout: + grid-template-columns: repeat(6, minmax(0, 1fr)) + grid-auto-flow: row + grid-auto-rows: min-content + align-items: stretch + grid-gap: 10px + margin: 0 + cards: + - type: custom:button-card + template: bearstone_mobile_header + name: Living Room Roku + icon: mdi:television + view_layout: + grid-column: 1 / -1 + + - type: custom:button-card + template: bearstone_mobile_control + entity: media_player.living_room_ultra + name: Raven + icon: mdi:view-dashboard-outline + show_state: false + variables: + status_severity: healthy + view_layout: + grid-column: 1 / -1 + tap_action: + action: perform-action + perform_action: rest_command.samsung_living_room_launch_haven + hold_action: + action: none + double_tap_action: + action: none + + # Roku controls never send Samsung input or menu commands. + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Roku Home + icon: mdi:home-outline + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: home + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Options + icon: mdi:dots-horizontal + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: info + hold_action: + action: none + double_tap_action: + action: none + + # Full-width Up and Down keep every touch target large without spacers. + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Up + icon: mdi:arrow-up-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: 1 / -1 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: up + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Left + icon: mdi:arrow-left-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 2 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: left + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: OK + icon: mdi:check-bold + show_state: false + variables: + status_severity: healthy + view_layout: + grid-column: span 2 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: select + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Right + icon: mdi:arrow-right-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 2 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: right + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Down + icon: mdi:arrow-down-bold + show_state: false + variables: + status_severity: active + view_layout: + grid-column: 1 / -1 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: down + hold_action: + action: none + double_tap_action: + action: none + + # Back exits Raven; playback controls act on the current Roku app. + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Back + icon: mdi:undo + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: back + hold_action: + action: none + double_tap_action: + action: none + + - type: custom:button-card + template: bearstone_mobile_control + entity: remote.living_room_ultra + name: Play / Pause + icon: mdi:play-pause + show_state: false + variables: + status_severity: active + view_layout: + grid-column: span 3 + tap_action: + action: perform-action + perform_action: remote.send_command + target: + entity_id: remote.living_room_ultra + data: + command: play + hold_action: + action: none + double_tap_action: + action: none +cards: [] diff --git a/config/dashboards/kiosk_satellite_staging/templates.yaml b/config/dashboards/kiosk_satellite_staging/templates.yaml index 8ef59abd..7c4bc765 100644 --- a/config/dashboards/kiosk_satellite_staging/templates.yaml +++ b/config/dashboards/kiosk_satellite_staging/templates.yaml @@ -4,12 +4,14 @@ # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- # Kiosk Satellite Templates - Shared alerts and compact Echo Show 5 +# Related Issue: 2029 # One household alert renderer with opt-in small-screen presentation. # ------------------------------------------------------------------- # Notes: Kitchen keeps its existing dimensions and interactive details. # Notes: Echo Show 5 uses compact interactive alerts and minute-updated time. -# Notes: Shared phone notifications join the ambient rotation with sent times and ten-minute expiry. +# Notes: Shared phone notifications retain the source feed's expiry and sent times. # Notes: Direct touch release handles kiosk taps before button-card suppresses synthetic clicks. +# Notes: Shared holiday accents decorate ambient panels only; alert colors retain their meaning. ###################################################################### kiosk_ambient_alerts: @@ -25,75 +27,18 @@ kiosk_ambient_alerts: action: none double_tap_action: action: none - triggers_update: - - binary_sensor.garage_phyn_battery_alert - - binary_sensor.garage_phyn_freeze_warning_alert - - binary_sensor.garage_phyn_high_pressure_alert - - binary_sensor.garage_phyn_leak_alert - - binary_sensor.garage_phyn_offline_leak_shutoff_alert - - binary_sensor.garage_phyn_pinhole_leak_alert - - binary_sensor.garage_phyn_recurring_flow_alert - - binary_sensor.garage_phyn_temperature_alert - - valve.phyn_shutoff_valve - - sensor.phyn_current_water_pressure - - sensor.phyn_average_water_flow_rate - - sensor.nws_alerts_alerts - - group.garage_doors - - group.entry_points - - group.locks - - binary_sensor.front_door_packages_present - - sensor.rheem_wh_available_hot_water - - sensor.rheem_wh_alert_count - - sensor.rheem_wh_diagnostic_status - - binary_sensor.rheem_wh_problem - - binary_sensor.rheem_wh_active_alert - - binary_sensor.rheem_wh_telemetry_stale - - binary_sensor.rheem_wh_running - - sensor.rheem_wh_tank_health - - sensor.rheem_wh_compressor_health - - water_heater.rheem_wh - - light.sink - - input_boolean.garbage_cans_out - - input_datetime.water_delivery_date - - sensor.water_softener_salt_days_since_last_add - - sensor.water_softener_salt_average_days_between_refills - - sensor.spaceship_battery_level - - switch.spaceship_charge - - input_datetime.tesla_model_y_last_garage_close - - input_datetime.tesla_model_y_plug_in_snooze_until - - sensor.next_booked_cruise_countdown - - sensor.bearclaw_status_telemetry - - sensor.kiosk_recent_personal_records - - sensor.kiosk_recent_phone_notifications - - person.carlo - - person.stacey - - person.justin - - person.paige - - sensor.carlo_location_display - - sensor.stacey_location_display - - sensor.justin_location_display - - sensor.paige_location_display - - binary_sensor.kiosk_rain_radar_weather_event - - binary_sensor.kiosk_rain_radar_active - - timer.kitchen_kiosk_radar_display - - input_text.kitchen_kiosk_detail_view - - timer.kitchen_kiosk_detail_display - - sensor.kiosk_active_camera_view - - camera.frontdoorbell + # Model dependencies may grow; a bounded refresh keeps every Show current. + update_timer: 5s + triggers_update: update_timer custom_fields: ambient: > [[[ - const state = (entityId) => states[entityId]?.state; - const valid = (value) => !['unknown', 'unavailable', 'none', '', undefined].includes(value); - const pretty = (value) => String(value || 'Unknown').replaceAll('_', ' '); - const dateKey = (date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; - }; - const items = []; const now = new Date(); + const model = window.BearStoneKioskAmbient; + if (!model) return html`
Loading household notifications...
`; + const feed = model.build(states, now); + const seasonal = model.seasonal ? model.seasonal(states) : {mode: 'standard'}; + const {items, state, valid, pretty, dateKey, cleanAlertText, alertSection, limitAlertText, uniqueWeatherAlerts, phynAlerts, rheemProblem} = feed; const detailValue = valid(state('input_text.kitchen_kiosk_detail_view')) ? state('input_text.kitchen_kiosk_detail_view') : 'none'; @@ -111,434 +56,6 @@ kiosk_ambient_alerts: }); }; - const radarWeatherEvent = states['binary_sensor.kiosk_rain_radar_weather_event']; - const radarDisplayTimer = states['timer.kitchen_kiosk_radar_display']; - const radarReplayReady = - radarWeatherEvent?.state === 'on' && - state('binary_sensor.kiosk_rain_radar_active') !== 'on' && - radarDisplayTimer?.state === 'idle' && - new Date(radarDisplayTimer.last_changed).getTime() > - new Date(radarWeatherEvent.last_changed).getTime(); - if (radarReplayReady) { - items.push({ - kicker: 'WEATHER RADAR', - title: 'Storms Approaching', - detail: 'Tap for radar', - icon: 'mdi:radar', - accent: '#0288d1', - action: { - service: 'kitchen_kiosk_show_event', - data: {event_type: 'radar'} - } - }); - } - - const phynAlerts = [ - 'binary_sensor.garage_phyn_battery_alert', - 'binary_sensor.garage_phyn_freeze_warning_alert', - 'binary_sensor.garage_phyn_high_pressure_alert', - 'binary_sensor.garage_phyn_leak_alert', - 'binary_sensor.garage_phyn_offline_leak_shutoff_alert', - 'binary_sensor.garage_phyn_pinhole_leak_alert', - 'binary_sensor.garage_phyn_recurring_flow_alert', - 'binary_sensor.garage_phyn_temperature_alert' - ].filter((entityId) => state(entityId) === 'on'); - const phynValveState = state('valve.phyn_shutoff_valve'); - if (phynAlerts.length > 0 || (valid(phynValveState) && phynValveState !== 'open')) { - const alertNames = phynAlerts.map((entityId) => - states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1]) - ); - items.push({ - kicker: 'WATER SYSTEM', - title: phynValveState === 'closed' ? 'Main water is off' : 'Phyn needs attention', - detail: `${alertNames.join(' · ') || `Valve is ${pretty(phynValveState)}`} · Tap for water details`, - icon: 'mdi:water-alert-outline', - accent: '#c62828', - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'phyn'} - } - }); - } - - const weatherAlertEntity = states['sensor.nws_alerts_alerts']; - const weatherAlertCount = Number.parseInt(weatherAlertEntity?.state, 10); - const weatherAlerts = Array.isArray(weatherAlertEntity?.attributes?.Alerts) - ? weatherAlertEntity.attributes.Alerts - : []; - const cleanAlertText = (value) => String(value || '').replace(/\s+/g, ' ').trim(); - const alertSection = (description, label) => { - const pattern = new RegExp( - `\\*\\s*${label}\\.{3}\\s*([\\s\\S]*?)(?=\\n\\s*\\n\\s*\\*\\s*[A-Z ]+\\.{3}|$)`, - 'i' - ); - return cleanAlertText(String(description || '').match(pattern)?.[1]); - }; - const limitAlertText = (value, maxLength = 240) => { - const text = cleanAlertText(value); - if (text.length <= maxLength) return text; - return `${text.slice(0, maxLength - 3).replace(/\s+\S*$/, '')}...`; - }; - const severityOrder = { Extreme: 0, Severe: 1, Moderate: 2, Minor: 3, Unknown: 4 }; - const severityColors = { - Extreme: '#b71c1c', - Severe: '#c62828', - Moderate: '#d84315', - Minor: '#f9a825', - Unknown: '#546e7a' - }; - const uniqueWeatherAlerts = weatherAlerts - .filter((alert, index, alerts) => { - const alertKey = alert.ID || alert.URL || `${alert.Event}|${alert.Expires}`; - return alerts.findIndex((candidate) => - (candidate.ID || candidate.URL || `${candidate.Event}|${candidate.Expires}`) === alertKey - ) === index; - }) - .sort((left, right) => - (severityOrder[left.Severity] ?? severityOrder.Unknown) - - (severityOrder[right.Severity] ?? severityOrder.Unknown) - ); - - if (uniqueWeatherAlerts.length > 0) { - uniqueWeatherAlerts.forEach((alert, index) => { - const severity = severityColors[alert.Severity] ? alert.Severity : 'Unknown'; - const detailParts = [ - alertSection(alert.Description, 'WHAT'), - alertSection(alert.Description, 'WHERE'), - alertSection(alert.Description, 'WHEN') - ].filter(Boolean); - const detail = detailParts.length > 0 - ? detailParts.join(' · ') - : alert.Headline || alert.Description || 'Active National Weather Service alert'; - const position = uniqueWeatherAlerts.length > 1 - ? ` ${index + 1}/${uniqueWeatherAlerts.length}` - : ''; - items.push({ - kicker: `WEATHER ALERT${position} · ${severity.toUpperCase()}`, - title: alert.Event || `${weatherAlertCount} active alerts`, - detail: limitAlertText(detail), - icon: 'mdi:weather-lightning-rainy', - accent: severityColors[severity], - action: { - service: 'kitchen_kiosk_show_detail', - data: { - detail_type: 'weather', - detail_subject: String(alert.ID || index) - } - } - }); - }); - } else if (Number.isFinite(weatherAlertCount) && weatherAlertCount > 0) { - items.push({ - kicker: 'WEATHER ALERT', - title: `${weatherAlertCount} active ${weatherAlertCount === 1 ? 'alert' : 'alerts'}`, - detail: 'Alert details are temporarily unavailable', - icon: 'mdi:weather-lightning-rainy', - accent: severityColors.Unknown, - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'weather', detail_subject: '0'} - } - }); - } - - if (valid(state('group.garage_doors')) && !['closed', 'off'].includes(state('group.garage_doors'))) { - const openGarages = ['cover.large_garage_door', 'cover.small_garage_door'] - .filter((entityId) => valid(state(entityId)) && state(entityId) !== 'closed') - .map((entityId) => states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1])); - items.push({ - kicker: 'GARAGE', - title: 'Garage door open', - detail: openGarages.join(' · ') || 'Check both garage doors', - icon: 'mdi:garage-open-variant', - accent: '#ef6c00' - }); - } - - if (state('group.entry_points') === 'on') { - const openEntries = (states['group.entry_points']?.attributes?.entity_id || []) - .filter((entityId) => state(entityId) === 'on') - .map((entityId) => states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1])); - const extraCount = Math.max(0, openEntries.length - 3); - const entryDetail = openEntries.slice(0, 3).join(' · ') + (extraCount ? ` · +${extraCount} more` : ''); - items.push({ - kicker: 'ENTRY POINT', - title: openEntries.length === 1 ? 'Door or window open' : `${openEntries.length} openings detected`, - detail: entryDetail || 'Check the exterior doors and windows', - icon: 'mdi:door-open', - accent: '#ef6c00', - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'entry_points'} - } - }); - } - - if (valid(state('group.locks')) && state('group.locks') !== 'locked') { - items.push({ - kicker: 'SECURITY', - title: 'Check the front door lock', - detail: `Lock group is ${pretty(state('group.locks'))}`, - icon: 'mdi:lock-alert-outline', - accent: '#c62828' - }); - } - - if (state('binary_sensor.front_door_packages_present') === 'on') { - items.push({ - kicker: 'FRONT DOOR', - title: 'Package waiting', - detail: 'Tap for the front door view', - icon: 'mdi:package-variant-closed', - accent: '#8e5a2b', - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'package'} - } - }); - } - - const rheemAlertCount = Number.parseInt(state('sensor.rheem_wh_alert_count'), 10); - const rheemStatus = state('sensor.rheem_wh_diagnostic_status'); - const rheemProblem = state('binary_sensor.rheem_wh_problem') === 'on'; - if (rheemProblem) { - const activeAlert = state('binary_sensor.rheem_wh_active_alert') === 'on'; - const telemetryStale = state('binary_sensor.rheem_wh_telemetry_stale') === 'on'; - items.push({ - kicker: telemetryStale && !activeAlert ? 'WATER HEATER TELEMETRY' : 'WATER HEATER ALERT', - title: activeAlert && Number.isFinite(rheemAlertCount) - ? `${rheemAlertCount} active ${rheemAlertCount === 1 ? 'alert' : 'alerts'}` - : `Rheem status: ${pretty(rheemStatus)}`, - detail: telemetryStale - ? 'EcoNet data has been unavailable for 10 minutes · Tap for diagnostics' - : 'Home Assistant Repairs has the diagnostic snapshot · Tap for details', - icon: 'mdi:water-boiler-alert', - accent: telemetryStale && !activeAlert ? '#ef6c00' : '#c62828', - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'hot_water'} - } - }); - } - - const hotWater = Number.parseFloat(state('sensor.rheem_wh_available_hot_water')); - if (!rheemProblem && Number.isFinite(hotWater) && hotWater < 30) { - items.push({ - kicker: 'HOT WATER', - title: 'Hot water is running low', - detail: `${Math.round(hotWater)}% available · Tap for recovery details`, - icon: 'mdi:water-thermometer-outline', - accent: '#0277bd', - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'hot_water'} - } - }); - } - - const weekday = now.toLocaleDateString('en-US', {weekday: 'short'}); - if (['Wed', 'Sun'].includes(weekday) && state('input_boolean.garbage_cans_out') !== 'on') { - items.push({ - kicker: 'TODAY', - title: weekday === 'Wed' ? 'Garbage + recycling day' : 'Garbage day', - detail: 'Put the cans at the curb', - icon: 'mdi:trash-can-outline', - accent: '#2e7d32' - }); - } - - const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); - if (state('input_datetime.water_delivery_date') === dateKey(tomorrow)) { - items.push({ - kicker: 'WATER DELIVERY TOMORROW', - title: 'Put the bottles out', - detail: 'Stage them before leaving the house', - icon: 'mdi:bottle-soda-classic-outline', - accent: '#0277bd' - }); - } - - const battery = Number.parseFloat(state('sensor.spaceship_battery_level')); - const lastClose = new Date(String(state('input_datetime.tesla_model_y_last_garage_close')).replace(' ', 'T')); - const snoozeUntil = new Date(String(state('input_datetime.tesla_model_y_plug_in_snooze_until')).replace(' ', 'T')); - const recentArrival = Number.isFinite(lastClose.getTime()) && (now.getTime() - lastClose.getTime()) < 43200000; - const snoozeExpired = !Number.isFinite(snoozeUntil.getTime()) || snoozeUntil <= now; - if ( - Number.isFinite(battery) && battery < 50 && recentArrival && snoozeExpired && - state('switch.spaceship_charge') === 'off' && - state('person.carlo') === 'home' && state('person.stacey') === 'home' - ) { - items.push({ - kicker: 'MODEL Y', - title: 'Plug in the Tesla', - detail: `${Math.round(battery)}% battery after the latest garage arrival`, - icon: 'mdi:ev-plug-tesla', - accent: '#c62828' - }); - } - - if (state('light.sink') === 'unavailable') { - items.push({ - kicker: 'CHECK WALL SWITCH', - title: 'Kitchen sink light', - detail: 'Please turn its physical switch back on.', - icon: 'mdi:lightbulb-alert-outline', - accent: '#ef6c00' - }); - } - - const saltDays = Number.parseFloat(state('sensor.water_softener_salt_days_since_last_add')); - const saltAverage = Number.parseFloat(state('sensor.water_softener_salt_average_days_between_refills')); - if ( - Number.isFinite(saltDays) && Number.isFinite(saltAverage) && saltAverage > 0 && - saltDays >= saltAverage * 0.9 - ) { - items.push({ - kicker: 'WATER SOFTENER', - title: 'Salt refill coming due', - detail: `${Math.round(saltDays)} days since the last refill`, - icon: 'mdi:shaker-outline', - accent: '#00838f' - }); - } - - const bowlingHighlights = states['sensor.bearclaw_status_telemetry']?.attributes?.bowlingHighlights; - if (bowlingHighlights?.active && Array.isArray(bowlingHighlights.items)) { - bowlingHighlights.items.forEach((highlight) => { - const expiresAt = new Date(highlight?.expiresAt || ''); - const games = (Array.isArray(highlight?.games) ? highlight.games : []) - .map((score) => Number.parseInt(score, 10)) - .filter(Number.isFinite); - const highGame = Number.parseInt(highlight?.highGame, 10); - const seriesTotal = Number.parseInt(highlight?.seriesTotal, 10); - if (!Number.isFinite(expiresAt.getTime()) || expiresAt <= now || !games.length || !Number.isFinite(highGame)) return; - const records = new Set(Array.isArray(highlight?.personalBestIds) ? highlight.personalBestIds : []); - const highGameRecord = records.has('bowling_high_game'); - const highSeriesRecord = records.has('bowling_high_series') && Number.isFinite(seriesTotal); - const title = highGameRecord - ? `${highlight.displayName}'s new high game: ${highGame}` - : (highSeriesRecord - ? `${highlight.displayName}'s new high series: ${seriesTotal}` - : `${highlight.displayName}'s high game: ${highGame}`); - const detail = [ - games.join(' / '), - Number.isFinite(seriesTotal) ? `${seriesTotal} series` : '', - Number.isFinite(Number(highlight?.weekNum)) ? `Week ${highlight.weekNum}` : '' - ].filter(Boolean).join(' · '); - items.push({ - kicker: highGameRecord || highSeriesRecord ? 'BOWLING PERSONAL BEST' : 'TUESDAY BOWLING', - title, - detail, - icon: 'mdi:bowling', - accent: '#7e57c2' - }); - }); - } - - const phoneFeed = states['sensor.kiosk_recent_phone_notifications']; - if (Array.isArray(phoneFeed?.attributes?.items)) { - phoneFeed.attributes.items.forEach((notification) => { - if (Number(notification.expires_at) * 1000 <= now.getTime()) return; - items.push({ - kicker: `PHONE NOTIFICATION · ${notification.sent_time}`, - title: notification.title, - detail: notification.message, - icon: notification.icon, - accent: notification.color - }); - }); - } - - const recordFeed = states['sensor.kiosk_recent_personal_records']; - const personalRecords = recordFeed?.state === 'ready' ? recordFeed.attributes?.items : null; - if (Array.isArray(personalRecords)) { - personalRecords.forEach((record) => { - const occurredAt = new Date(record?.occurredAt || ''); - if (!Number.isFinite(occurredAt.getTime()) || - occurredAt > now || now - occurredAt > 7 * 24 * 60 * 60 * 1000 || - !record?.person || !record?.label || !record?.displayValue) return; - items.push({ - kicker: 'NEW PERSONAL BEST', - title: `${record.person}: ${record.label}`, - detail: [record.displayValue, record.activityType, record.dayKey].filter(Boolean).join(' · '), - icon: 'mdi:trophy-outline', - accent: '#2e7d32' - }); - }); - } - - const cruiseDays = Number.parseInt(state('sensor.next_booked_cruise_countdown'), 10); - const cruiseEntity = states['sensor.next_booked_cruise_countdown']; - const cruiseShip = String(cruiseEntity?.attributes?.ship || '').trim(); - const cruiseDate = String(cruiseEntity?.attributes?.sail_date || '').trim(); - if (Number.isInteger(cruiseDays) && cruiseDays >= 0 && cruiseShip && cruiseDate) { - const sailDate = new Date(`${cruiseDate}T12:00:00`); - const sailLabel = Number.isFinite(sailDate.getTime()) - ? sailDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) - : cruiseDate; - const cruiseNights = Number.parseInt(cruiseEntity?.attributes?.nights, 10); - const cruisePort = String(cruiseEntity?.attributes?.port || '').trim(); - const detail = [ - `Departs ${sailLabel}`, - Number.isInteger(cruiseNights) && cruiseNights > 0 - ? `${cruiseNights} ${cruiseNights === 1 ? 'night' : 'nights'}` - : '', - cruisePort - ].filter(Boolean).join(' · '); - items.push({ - kicker: 'NEXT BOOKED CRUISE', - title: cruiseDays === 0 - ? `${cruiseShip} sails today` - : `${cruiseDays} ${cruiseDays === 1 ? 'day' : 'days'} until ${cruiseShip}`, - detail, - icon: 'mdi:ferry', - accent: '#00838f' - }); - } - - [ - ['Carlo', 'person.carlo', 'sensor.carlo_location_display'], - ['Stacey', 'person.stacey', 'sensor.stacey_location_display'], - ['Justin', 'person.justin', 'sensor.justin_location_display'], - ['Paige', 'person.paige', 'sensor.paige_location_display'] - ].forEach(([name, personId, locationId]) => { - const personState = state(personId); - if (valid(personState) && personState !== 'home') { - const locationEntity = states[locationId]; - const rawLocation = pretty(valid(state(locationId)) ? state(locationId) : personState); - const withoutName = rawLocation.replace(new RegExp(`^${name}\\s+`, 'i'), ''); - const sinceMatch = withoutName.match(/\s*\(since\s+([^)]+)\)\s*$/i); - const location = withoutName.replace(/\s*\(since\s+[^)]+\)\s*$/i, '').trim(); - const changedAt = new Date(locationEntity?.last_changed || states[personId]?.last_changed); - const since = sinceMatch?.[1] || ( - Number.isFinite(changedAt.getTime()) - ? changedAt.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) - : 'recently' - ); - items.push({ - kicker: 'FAMILY LOCATION', - title: `${name} - ${location || pretty(personState)}`, - detail: `Since ${since}`, - icon: 'mdi:map-marker-account-outline', - accent: '#6a1b9a', - action: { - service: 'kitchen_kiosk_show_detail', - data: {detail_type: 'family', detail_subject: personId} - } - }); - } - }); - - if (items.length === 0) { - items.push({ - kicker: 'HOUSEHOLD', - title: 'Everything is handled', - detail: 'Everyone is home and there are no active reminders', - icon: 'mdi:check-circle-outline', - accent: '#2e7d32' - }); - } - const formatSince = (entity) => { const changed = new Date(entity?.last_changed); if (!Number.isFinite(changed.getTime())) return 'recently'; @@ -800,6 +317,8 @@ kiosk_ambient_alerts: line-height: 1.1; letter-spacing: 0.1em; } + .kitchen-ambient-rotator.seasonal .kitchen-ambient-kicker { padding-right: 92px; } + .kitchen-ambient-rotator.compact.seasonal .kitchen-ambient-kicker { padding-right: 56px; } .kitchen-ambient-title { margin-top: 6px; color: var(--primary-text-color); @@ -1074,7 +593,7 @@ kiosk_ambient_alerts: .kitchen-detail-overlay.compact .kitchen-detail-hot-percent { font-size: 90px; } .kitchen-detail-overlay.compact .kitchen-detail-hot-percent span { font-size: 44px; } -
+
${items.map((item, index) => html`