From 4feee5caf64af9b1492d915c555ba481a52e99ef Mon Sep 17 00:00:00 2001 From: Carlo Costanzo Date: Wed, 12 Aug 2026 13:54:08 -0400 Subject: [PATCH] feat: add purpose-built bedroom and kitchen kiosks --- config/automation/dark_rainy_day.yaml | 49 +- config/automation/kitchen_sink_circadian.yaml | 14 +- config/configuration.yaml | 38 +- config/dashboards/README.md | 18 +- .../{kiosk => bedroom}/dashboard.yaml | 9 +- .../bedroom/partials/home_cards.yaml | 130 ++++ .../partials/locator_sections.yaml | 4 +- config/dashboards/bedroom/views/01_home.yaml | 23 + .../{kiosk => bedroom}/views/02_locator.yaml | 4 +- .../partials/home_sections.yaml | 36 +- .../partials/kiosk_sections.yaml | 192 ++++++ .../views/05z_kiosks.yaml} | 25 +- .../partials/kiosk_oveview_sections.yaml | 67 --- .../kiosk/views/01_kiosk_oveview.yaml | 34 -- .../kiosk_satellite_staging/dashboard.yaml | 14 + .../partials/home_sections.yaml | 565 ++++++++++++++++++ .../views/01_home.yaml | 22 + config/packages/infrastructure.yaml | 10 +- config/packages/kiosk_tablet.yaml | 531 +++++++++++++++- config/packages/royalcaribbean.yaml | 25 +- config/themes/kiosk_satellite.yaml | 29 + config/www/bedroom-kiosk.html | 336 +++++++++++ config/www/custom-sidebar-loader.js | 9 + 23 files changed, 2020 insertions(+), 164 deletions(-) rename config/dashboards/{kiosk => bedroom}/dashboard.yaml (63%) create mode 100644 config/dashboards/bedroom/partials/home_cards.yaml rename config/dashboards/{kiosk => bedroom}/partials/locator_sections.yaml (97%) create mode 100644 config/dashboards/bedroom/views/01_home.yaml rename config/dashboards/{kiosk => bedroom}/views/02_locator.yaml (90%) create mode 100644 config/dashboards/infrastructure/partials/kiosk_sections.yaml rename config/dashboards/{kiosk/partials/kiosk_oveview_badges.yaml => infrastructure/views/05z_kiosks.yaml} (51%) delete mode 100644 config/dashboards/kiosk/partials/kiosk_oveview_sections.yaml delete mode 100644 config/dashboards/kiosk/views/01_kiosk_oveview.yaml create mode 100644 config/dashboards/kiosk_satellite_staging/dashboard.yaml create mode 100644 config/dashboards/kiosk_satellite_staging/partials/home_sections.yaml create mode 100644 config/dashboards/kiosk_satellite_staging/views/01_home.yaml create mode 100644 config/themes/kiosk_satellite.yaml create mode 100644 config/www/bedroom-kiosk.html create mode 100644 config/www/custom-sidebar-loader.js diff --git a/config/automation/dark_rainy_day.yaml b/config/automation/dark_rainy_day.yaml index 312bdd31..719ffcd3 100755 --- a/config/automation/dark_rainy_day.yaml +++ b/config/automation/dark_rainy_day.yaml @@ -8,6 +8,10 @@ # ------------------------------------------------------------------- # Notes: # Avoid trigger entity string-splitting; map trigger IDs safely. +# Lightning may turn on the lights here, but its safety speech is owned by +# the Lightning Notification automation to prevent back-to-back briefings. +# Kitchen Show illuminance confirms the occupied first floor is actually dim; +# weather behavior falls back unchanged if that MQTT sensor is unavailable. ###################################################################### - alias: 'Dark House Little extra light' @@ -24,6 +28,10 @@ - platform: numeric_state entity_id: sensor.blitzortung_lightning_counter above: 1 + - platform: numeric_state + entity_id: sensor.kitchen_show_ambient_light + below: 30 + for: "00:05:00" - platform: state entity_id: group.family to: 'home' @@ -44,6 +52,11 @@ - condition: numeric_state entity_id: sensor.blitzortung_lightning_counter above: 1 + - condition: template + value_template: >- + {% set lux = states('sensor.kitchen_show_ambient_light') %} + {{ lux in ['unknown', 'unavailable', 'none', ''] + or lux | float(0) < 35 }} - condition: state entity_id: binary_sensor.sleepnumber_carlo_carlo_is_in_bed state: 'off' @@ -66,21 +79,27 @@ brightness: 255 color_temp_kelvin: 5500 - - service: script.speech_engine - data: - value1: >- - {% set reason_map = { - 'sensor.pirateweather_precip': 'rain', - 'sensor.pirateweather_precip_intensity': 'heavy rain', - 'sensor.blitzortung_lightning_counter': 'lightning', - 'group.family': 'rain and clouds', - 'binary_sensor.sleepnumber_carlo_carlo_is_in_bed': 'rain and clouds' - } %} - {% set reason = reason_map.get(trigger.entity_id | default(''), 'weather changes') %} - Because of the {{ reason }} outside. I will turn on some extra lights in the living room. - call_outside_weather: 1 - call_window_check: 1 - call_garage_check: 1 + - choose: + - conditions: + - condition: template + value_template: >- + {{ trigger.entity_id | default('') != 'sensor.blitzortung_lightning_counter' }} + sequence: + - service: script.speech_engine + data: + value1: >- + {% set reason_map = { + 'sensor.pirateweather_precip': 'rain', + 'sensor.pirateweather_precip_intensity': 'heavy rain', + 'sensor.kitchen_show_ambient_light': 'rain and clouds', + 'group.family': 'rain and clouds', + 'binary_sensor.sleepnumber_carlo_carlo_is_in_bed': 'rain and clouds' + } %} + {% set reason = reason_map.get(trigger.entity_id | default(''), 'weather changes') %} + Because of the {{ reason }} outside. I will turn on some extra lights in the living room. + call_outside_weather: 1 + call_window_check: 1 + call_garage_check: 1 - delay: hours: 3 diff --git a/config/automation/kitchen_sink_circadian.yaml b/config/automation/kitchen_sink_circadian.yaml index 927b28b0..e72f1a91 100644 --- a/config/automation/kitchen_sink_circadian.yaml +++ b/config/automation/kitchen_sink_circadian.yaml @@ -10,6 +10,8 @@ # Notes: Uses color temperature only to avoid inherited color scenes. # TV playback keeps the sink off so media time does not re-light it. # Notes: Office lamp switch off-gates the sink while active PC work is in progress. +# Notes: Kitchen Show illuminance gates only new automatic turn-ons. Once the +# sink is on, its normal circadian tuning continues without lux-based shutoff. ###################################################################### - alias: "Kitchen Sink Circadian Daylight" @@ -59,6 +61,11 @@ - platform: time_pattern minutes: "/30" id: tune + - platform: numeric_state + entity_id: sensor.kitchen_show_ambient_light + below: 30 + for: "00:05:00" + id: tune - platform: homeassistant event: start id: tune @@ -82,6 +89,10 @@ {{ carlo_awake or stacey_awake }} working_active: >- {{ is_state('switch.office_lamp_switch', 'on') }} + ambient_dark: >- + {% set lux = states('sensor.kitchen_show_ambient_light') %} + {{ lux in ['unknown', 'unavailable', 'none', ''] + or lux | float(0) < 35 }} target_kelvin: >- {% set elevation = state_attr('sun.sun', 'elevation') | float(-90) %} {% if elevation <= -2 %} @@ -111,7 +122,8 @@ and not (tv_active | bool) and not (working_active | bool) and ((awake_home | bool) or is_state('light.sink', 'on')) - and (is_state('light.sink', 'on') or now().hour >= 4) }} + and (is_state('light.sink', 'on') + or ((ambient_dark | bool) and now().hour >= 4)) }} - choose: - conditions: - condition: template diff --git a/config/configuration.yaml b/config/configuration.yaml index bb4baef2..2abd8710 100755 --- a/config/configuration.yaml +++ b/config/configuration.yaml @@ -28,14 +28,10 @@ homeassistant: default_config: -# Temporary Rheem EcoNet/ClearBlade TLS workaround. -# Remove when https://github.com/CCOSTAN/Home-AssistantConfig/issues/1820 is resolved. -additional_ca: - rheem_econet_digicert_g1: DigiCertGlobalRootCA.crt.pem - frontend: + themes: !include_dir_merge_named themes extra_module_url: - - /hacsfiles/custom-sidebar/custom-sidebar-plugin.js?v=16.0.0 + - /local/custom-sidebar-loader.js?v=1.0.0 # ------------------------------------------------------------------- # Lovelace (YAML dashboards/resources) @@ -59,13 +55,20 @@ lovelace: show_in_sidebar: true require_admin: true filename: dashboards/infrastructure/dashboard.yaml - dashboard-kiosk: + dashboard-bedroom: + mode: yaml + title: "Bedroom" + icon: mdi:bed-outline + show_in_sidebar: false + require_admin: false + filename: dashboards/bedroom/dashboard.yaml + dashboard-kiosk-satellite-staging: mode: yaml - title: "KIOSK" + title: "Kiosk Satellite Preview" icon: mdi:tablet-dashboard - show_in_sidebar: true + show_in_sidebar: false require_admin: false - filename: dashboards/kiosk/dashboard.yaml + filename: dashboards/kiosk_satellite_staging/dashboard.yaml config: @@ -96,8 +99,13 @@ cloud: - light.upstairs_lamp - light.sink - camera.frontdoorbell + - camera.poolcam + - camera.frontlawn - camera.driveway - camera.garagecam + - camera.kidsgate + - camera.bedroomgate + - camera.filtergate - camera.birdseye - switch.kitchen_accent_1 - switch.kitchen_accent_2 @@ -130,10 +138,20 @@ cloud: name: Living Room Slider camera.frontdoorbell: name: Front Doorbell + camera.poolcam: + name: Pool Camera + camera.frontlawn: + name: Front Lawn Camera camera.driveway: name: Driveway camera.garagecam: name: Garage Camera + camera.kidsgate: + name: Kids Gate Camera + camera.bedroomgate: + name: Bedroom Gate Camera + camera.filtergate: + name: Filter Gate Camera camera.birdseye: name: Birdseye diff --git a/config/dashboards/README.md b/config/dashboards/README.md index 5ab0957f..bb23bf79 100644 --- a/config/dashboards/README.md +++ b/config/dashboards/README.md @@ -55,19 +55,29 @@ This folder holds YAML-managed Home Assistant Lovelace dashboards and UI resourc - Primary actions cover garage doors, the front-door lock, thermostats, household alerts, and entry-point status. - Packages opens the doorbell camera; Systems opens the desktop control center; destination views place a large Back to Home control at the top. -### Kiosk Camera Dashboard +### Event-Driven Kiosk Dashboards - Logic and recovery package: [![YAML source: kiosk_tablet](https://img.shields.io/static/v1?label=YAML&message=kiosk_tablet&color=lightgrey&logo=github&logoColor=181717)](../packages/kiosk_tablet.yaml) -- Dashboard entrypoint: [![YAML source: dashboard](https://img.shields.io/static/v1?label=YAML&message=dashboard&color=lightgrey&logo=github&logoColor=181717)](kiosk/dashboard.yaml) -- Camera view: [![YAML source: 01_kiosk_oveview](https://img.shields.io/static/v1?label=YAML&message=01_kiosk_oveview&color=lightgrey&logo=github&logoColor=181717)](kiosk/views/01_kiosk_oveview.yaml) -- Camera sections: [![YAML source: kiosk_oveview_sections](https://img.shields.io/static/v1?label=YAML&message=kiosk_oveview_sections&color=lightgrey&logo=github&logoColor=181717)](kiosk/partials/kiosk_oveview_sections.yaml) +- Bedroom dashboard: [![YAML source: dashboard](https://img.shields.io/static/v1?label=YAML&message=bedroom&color=lightgrey&logo=github&logoColor=181717)](bedroom/dashboard.yaml) +- Bedroom landscape view: [![YAML source: 01_home](https://img.shields.io/static/v1?label=YAML&message=01_home&color=lightgrey&logo=github&logoColor=181717)](bedroom/views/01_home.yaml) +- Android 5 renderer: [![HTML source: bedroom-kiosk](https://img.shields.io/static/v1?label=HTML&message=bedroom-kiosk&color=lightgrey&logo=html5&logoColor=E34F26)](../www/bedroom-kiosk.html) +- Kiosk Satellite preview: [![YAML source: dashboard](https://img.shields.io/static/v1?label=YAML&message=kiosk_satellite_staging&color=lightgrey&logo=github&logoColor=181717)](kiosk_satellite_staging/dashboard.yaml) +- Kitchen tablet view: [![YAML source: 01_home](https://img.shields.io/static/v1?label=YAML&message=01_home&color=lightgrey&logo=github&logoColor=181717)](kiosk_satellite_staging/views/01_home.yaml) +- Automatic day/night theme: [![YAML source: kiosk_satellite](https://img.shields.io/static/v1?label=YAML&message=kiosk_satellite_theme&color=lightgrey&logo=github&logoColor=181717)](../themes/kiosk_satellite.yaml) + +The displays share only the reusable event and screen-policy state they need. Kitchen keeps its clock/current-weather/forecast banner and rotating household context, including the next confirmed cruise countdown, while the fixed-landscape Bedroom dashboard omits the redundant clock and uses that space for the next wake alarm, security/weather context, larger Front Lawn and Driveway feeds, and a readable household alert. Both use the same Garage-then-Front-Door event priority. The canonical Bedroom Lovelace view and its Android 5 lightweight renderer consume the same camera, header, and alert sensors; the renderer bypasses only the unsupported Home Assistant frontend on the frozen Fire WebView. Kitchen uses native Kiosk Satellite camera overlays and idle dimming; Home Assistant handles only household sleep/away enforcement, volume, and event-camera wake instead of periodically forcing the screen on. True idle panel-off remains disabled until the Crown LineageOS device has a reliable motion-wake input. Its device schedule changes the Home Assistant theme between light at 07:00 and dark at 19:00. Other clients follow their own system preference. ### How it is wired into Home Assistant This folder is referenced from `config/configuration.yaml` via: - `lovelace.resource_mode: yaml` - `lovelace.resources: !include dashboards/resources.yaml` - `lovelace.dashboards: ...` +- `frontend.themes: !include_dir_merge_named themes` loads the shared tablet's light and dark palette. - Default Overview YAML dashboard: `lovelace.dashboards.lovelace.filename: ui-lovelace.yaml` - Additional YAML dashboards: `filename: dashboards//dashboard.yaml` + - `dashboard-bedroom` is the canonical Bedroom desktop-review and future-hardware route; it is hidden from the sidebar and linked from the Bedroom Tablet snapshot on Systems' Kiosks tab. The current Android 5 Fire uses the equivalent lightweight renderer under `www/`. + - `dashboard-kiosk-satellite-staging` is the hidden validation surface for the shared Kiosk Satellite camera view. + - Systems' visible Kiosks tab sits immediately before Joanna and combines attention telemetry with the latest Bedroom Tablet and Kitchen Show screenshots; tapping either snapshot opens that display's live dashboard. + - The Systems Joanna page links to BearClaw Admin from Dispatch Trend and to a hidden AI Diagnostics subview that shows the latest OpenAI instructions and response together; LLM Vision production diagnostics and retired QMD health telemetry are not shown. - Overview's second visible view is **Mobile**, a phone-first quick-control surface for garage doors, the front-door lock, thermostats, and compact household alerts. - Overview Home uses ordered production sections under `overview/sections/`; approved pilot edits now apply directly there. - Overview Home's **Windows** ribbon opens a dedicated GPIO entry-point subview with perimeter, MQTT, and individual contact status. diff --git a/config/dashboards/kiosk/dashboard.yaml b/config/dashboards/bedroom/dashboard.yaml similarity index 63% rename from config/dashboards/kiosk/dashboard.yaml rename to config/dashboards/bedroom/dashboard.yaml index cd2b677b..a57c7e8e 100644 --- a/config/dashboards/kiosk/dashboard.yaml +++ b/config/dashboards/bedroom/dashboard.yaml @@ -3,11 +3,12 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# KIOSK Dashboard - YAML entrypoint -# YAML-exported Lovelace dashboard (split into view files). +# Bedroom Dashboard - YAML entrypoint +# Dedicated fixed-landscape event-driven view for the Bedroom Fire tablet. # ------------------------------------------------------------------- -# Notes: Entry point for dashboard key `dashboard-kiosk`. Views are loaded from /config/dashboards/kiosk/views. +# Related Issue: 1875 +# Notes: Presentation is Bedroom-specific; camera event priority is shared. # Video: https://youtu.be/ChgEu0IDWzc ###################################################################### -views: !include_dir_list /config/dashboards/kiosk/views +views: !include_dir_list /config/dashboards/bedroom/views diff --git a/config/dashboards/bedroom/partials/home_cards.yaml b/config/dashboards/bedroom/partials/home_cards.yaml new file mode 100644 index 00000000..f3adf3ab --- /dev/null +++ b/config/dashboards/bedroom/partials/home_cards.yaml @@ -0,0 +1,130 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Bedroom Home Cards - Fixed 1024x600 landscape event display +# Shared camera priority with a Bedroom-specific read-only default view. +# ------------------------------------------------------------------- +# Related Issue: 1875 +# Notes: No current-time clock; both nightstands already provide visible clocks. +# Notes: Full-screen event cameras replace the default status/camera/alert bands. +# Video: https://youtu.be/ChgEu0IDWzc +###################################################################### + +- type: vertical-stack + cards: + - type: conditional + conditions: + - entity: sensor.kiosk_active_camera_view + state: garage + card: + type: picture-glance + title: Garage + camera_image: camera.garagecam + camera_view: auto + aspect_ratio: "2:1" + entities: [] + + - type: conditional + conditions: + - entity: sensor.kiosk_active_camera_view + state: front_door + card: + type: picture-glance + title: Front Door + camera_image: camera.frontdoorbell + camera_view: auto + aspect_ratio: "2:1" + entities: [] + + # Bedroom context is intentionally distinct from Kitchen's clock/forecast banner. + - type: conditional + conditions: + - entity: sensor.kiosk_active_camera_view + state: none + card: + type: markdown + content: >- + ## {{ states('sensor.bedroom_kiosk_header') }} + + {{ state_attr('sensor.bedroom_kiosk_header', 'detail') }} + card_mod: + style: | + ha-card { + height: 100px !important; + overflow: hidden !important; + } + ha-markdown { + padding: 12px 24px !important; + } + h2 { + font-size: 28px !important; + line-height: 1.15 !important; + margin: 0 0 8px !important; + } + p { + font-size: 18px !important; + line-height: 1.2 !important; + margin: 0 !important; + } + + # Built-in camera cards are the legacy-WebView-safe rendering path. + - type: grid + columns: 2 + square: false + cards: + - type: conditional + conditions: + - entity: sensor.kiosk_active_camera_view + state: none + card: + type: picture-glance + title: Front Lawn + camera_image: camera.frontlawn + camera_view: auto + aspect_ratio: "2:1" + entities: [] + + - type: conditional + conditions: + - entity: sensor.kiosk_active_camera_view + state: none + card: + type: picture-glance + title: Driveway + camera_image: camera.driveway + camera_view: auto + aspect_ratio: "2:1" + entities: [] + + # One large priority message remains readable from across the Bedroom. + - type: conditional + conditions: + - entity: sensor.kiosk_active_camera_view + state: none + card: + type: markdown + content: >- + ## {{ states('sensor.kiosk_household_alert') }} + + {{ state_attr('sensor.kiosk_household_alert', 'detail') }} + card_mod: + style: | + ha-card { + height: 120px !important; + overflow: hidden !important; + } + ha-markdown { + padding: 12px 24px !important; + } + h2 { + font-size: 26px !important; + line-height: 1.15 !important; + margin: 0 0 8px !important; + } + p { + font-size: 17px !important; + line-height: 1.25 !important; + margin: 0 !important; + } diff --git a/config/dashboards/kiosk/partials/locator_sections.yaml b/config/dashboards/bedroom/partials/locator_sections.yaml similarity index 97% rename from config/dashboards/kiosk/partials/locator_sections.yaml rename to config/dashboards/bedroom/partials/locator_sections.yaml index 18dbfc3f..f48cb95c 100644 --- a/config/dashboards/kiosk/partials/locator_sections.yaml +++ b/config/dashboards/bedroom/partials/locator_sections.yaml @@ -3,10 +3,10 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# Kiosk Partial - locator sections +# Bedroom Partial - locator sections # Reusable list extracted from a view for smaller diffs and safer edits. # ------------------------------------------------------------------- -# Notes: Extracted from config/dashboards/kiosk/views/02_locator.yaml key `sections`. +# Notes: Preserved from the legacy kiosk dashboard during the Bedroom route split. ###################################################################### - cards: diff --git a/config/dashboards/bedroom/views/01_home.yaml b/config/dashboards/bedroom/views/01_home.yaml new file mode 100644 index 00000000..2079cd6e --- /dev/null +++ b/config/dashboards/bedroom/views/01_home.yaml @@ -0,0 +1,23 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Bedroom Home - Landscape night-watch display +# Wake alarm, exterior cameras, and household exceptions without a clock. +# ------------------------------------------------------------------- +# Related Issue: 1875 +# Notes: Garage takes priority, followed by Front Door, then Bedroom Home. +# Notes: Core cards remain compatible with the Fire tablet's legacy WebView. +# Video: https://youtu.be/ChgEu0IDWzc +###################################################################### + +title: Bedroom +path: home +icon: mdi:bed-outline +theme: kiosk_satellite_auto +type: panel +cards: !include /config/dashboards/bedroom/partials/home_cards.yaml +visible: + - user: be280a93c9d7416e98d25d0470f414be + - user: 9543feb5402742a0ab90f301f09f1c0e diff --git a/config/dashboards/kiosk/views/02_locator.yaml b/config/dashboards/bedroom/views/02_locator.yaml similarity index 90% rename from config/dashboards/kiosk/views/02_locator.yaml rename to config/dashboards/bedroom/views/02_locator.yaml index 7f6d3009..07eb2988 100644 --- a/config/dashboards/kiosk/views/02_locator.yaml +++ b/config/dashboards/bedroom/views/02_locator.yaml @@ -3,7 +3,7 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# KIOSK View - Locator +# Bedroom View - Locator # YAML-exported Lovelace dashboard (split into view files). # ------------------------------------------------------------------- # Notes: Exported from config/.storage/lovelace.dashboard_kiosk view index 2. @@ -25,5 +25,5 @@ background: visible: - user: be280a93c9d7416e98d25d0470f414be - user: 9543feb5402742a0ab90f301f09f1c0e -sections: !include /config/dashboards/kiosk/partials/locator_sections.yaml +sections: !include /config/dashboards/bedroom/partials/locator_sections.yaml cards: [] diff --git a/config/dashboards/infrastructure/partials/home_sections.yaml b/config/dashboards/infrastructure/partials/home_sections.yaml index 07398bda..37558c72 100644 --- a/config/dashboards/infrastructure/partials/home_sections.yaml +++ b/config/dashboards/infrastructure/partials/home_sections.yaml @@ -28,7 +28,7 @@ columns: full layout_type: custom:grid-layout layout: - grid-template-columns: repeat(9, minmax(0, 1fr)) + grid-template-columns: repeat(10, minmax(0, 1fr)) grid-auto-flow: row grid-auto-rows: min-content grid-gap: 12px @@ -160,6 +160,19 @@ action: navigate navigation_path: /dashboard-infrastructure/mariadb + - type: custom:button-card + template: bearstone_infra_status_kpi + entity: binary_sensor.kiosk_devices_need_attention + name: Kiosks + icon: mdi:tablet-dashboard + variables: + ok_state: 'off' + healthy_text: ONLINE + alert_text: ATTENTION + tap_action: + action: navigate + navigation_path: /dashboard-infrastructure/kiosks + - type: custom:button-card template: bearstone_infra_status_kpi entity: sensor.bearclaw_status_telemetry @@ -300,6 +313,27 @@ action: navigate navigation_path: /dashboard-infrastructure/vacuum + - type: custom:button-card + template: bearstone_infra_alert_row + entity: binary_sensor.kiosk_devices_need_attention + name: Kiosk device needs attention + icon: mdi:tablet-alert + variables: + alert_kind: binary_on + exception_detail: > + [[[ + const bedroom = states['binary_sensor.bedroom_kiosk_needs_attention']?.attributes?.status; + const kitchen = states['binary_sensor.kitchen_kiosk_needs_attention']?.attributes?.status; + const issues = []; + if (states['binary_sensor.bedroom_kiosk_needs_attention']?.state === 'on') issues.push(`BEDROOM: ${String(bedroom ?? 'ATTENTION').toUpperCase()}`); + if (states['binary_sensor.kitchen_kiosk_needs_attention']?.state === 'on') issues.push(`KITCHEN: ${String(kitchen ?? 'ATTENTION').toUpperCase()}`); + return issues.join(' · ') || 'KIOSK TELEMETRY REQUIRES REVIEW'; + ]]] + state_display: '[[[ return "ATTENTION"; ]]]' + tap_action: + action: navigate + navigation_path: /dashboard-infrastructure/kiosks + - type: custom:button-card template: bearstone_infra_alert_row entity: binary_sensor.proxmox1_runtime_healthy diff --git a/config/dashboards/infrastructure/partials/kiosk_sections.yaml b/config/dashboards/infrastructure/partials/kiosk_sections.yaml new file mode 100644 index 00000000..74eb4d56 --- /dev/null +++ b/config/dashboards/infrastructure/partials/kiosk_sections.yaml @@ -0,0 +1,192 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Infrastructure Partial - kiosks +# Exception-first health, device telemetry, and live dashboard previews. +# ------------------------------------------------------------------- +# Notes: Included by config/dashboards/infrastructure/views/05z_kiosks.yaml. +###################################################################### + +# ------------------------------------------------------------------- +# Device health strip +# ------------------------------------------------------------------- +- 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(2, minmax(0, 1fr)) + grid-auto-flow: row + grid-auto-rows: min-content + grid-gap: 12px + margin: 0 + mediaquery: + "(max-width: 900px)": + grid-template-columns: repeat(1, minmax(0, 1fr)) + cards: + - type: custom:button-card + template: bearstone_infra_status_kpi + entity: binary_sensor.bedroom_kiosk_needs_attention + name: Bedroom Tablet + icon: mdi:tablet + variables: + ok_state: 'off' + healthy_text: ONLINE + alert_text: NEEDS ATTENTION + state_display: > + [[[ return String(entity?.attributes?.status ?? entity?.state ?? 'unknown').toUpperCase(); ]]] + tap_action: + action: navigate + navigation_path: /dashboard-bedroom/home + + - type: custom:button-card + template: bearstone_infra_status_kpi + entity: binary_sensor.kitchen_kiosk_needs_attention + name: Kitchen Show + icon: mdi:monitor-dashboard + variables: + ok_state: 'off' + healthy_text: ONLINE + alert_text: NEEDS ATTENTION + state_display: > + [[[ return String(entity?.attributes?.status ?? entity?.state ?? 'unknown').toUpperCase(); ]]] + tap_action: + action: navigate + navigation_path: /dashboard-kiosk-satellite-staging/home + +# ------------------------------------------------------------------- +# Latest device screenshots +# ------------------------------------------------------------------- +- type: grid + column_span: 4 + columns: 2 + 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 + +# ------------------------------------------------------------------- +# Device telemetry +# ------------------------------------------------------------------- +- type: grid + column_span: 4 + columns: 2 + 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 diff --git a/config/dashboards/kiosk/partials/kiosk_oveview_badges.yaml b/config/dashboards/infrastructure/views/05z_kiosks.yaml similarity index 51% rename from config/dashboards/kiosk/partials/kiosk_oveview_badges.yaml rename to config/dashboards/infrastructure/views/05z_kiosks.yaml index 1bbea1c9..adaf565f 100644 --- a/config/dashboards/kiosk/partials/kiosk_oveview_badges.yaml +++ b/config/dashboards/infrastructure/views/05z_kiosks.yaml @@ -3,18 +3,19 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# Kiosk Partial - kiosk_oveview badges -# Reusable list extracted from a view for smaller diffs and safer edits. +# Infrastructure View - Kiosks +# Kiosk health, device telemetry, screenshots, and live-dashboard links. # ------------------------------------------------------------------- -# Notes: Extracted from config/dashboards/kiosk/views/01_kiosk_oveview.yaml key `badges`. +# Notes: Sorted immediately before Joanna in the Systems view navigation. ###################################################################### -- type: entity - show_name: true - show_state: false - show_icon: true - entity: switch.alarm_panel_1_screensaver - tap_action: - action: toggle - show_entity_picture: false - name: Turn On ScreenSaver +title: Kiosks +path: kiosks +icon: mdi:tablet-dashboard +theme: default +type: sections +max_columns: 4 +dense_section_placement: true +badges: [] +sections: !include /config/dashboards/infrastructure/partials/kiosk_sections.yaml +cards: [] diff --git a/config/dashboards/kiosk/partials/kiosk_oveview_sections.yaml b/config/dashboards/kiosk/partials/kiosk_oveview_sections.yaml deleted file mode 100644 index d3c06578..00000000 --- a/config/dashboards/kiosk/partials/kiosk_oveview_sections.yaml +++ /dev/null @@ -1,67 +0,0 @@ -###################################################################### -# @CCOSTAN - Follow Me on X -# For more info visit https://www.vcloudinfo.com/click-here -# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig -# ------------------------------------------------------------------- -# Kiosk Partial - kiosk_oveview sections -# Reusable list extracted from a view for smaller diffs and safer edits. -# ------------------------------------------------------------------- -# Notes: Extracted from config/dashboards/kiosk/views/01_kiosk_oveview.yaml key `sections`. -# Keep this Fire 7 kiosk view on built-in cards; avoid live camera/custom-card resources on the old WebView. -# Video: https://youtu.be/ChgEu0IDWzc -###################################################################### - -- type: grid - cards: - - type: picture-glance - camera_image: camera.frontdoorbell - entities: [] - grid_options: - columns: 12 - rows: auto - column_span: 1 -- type: grid - column_span: 1 - cards: - - type: picture-glance - camera_image: camera.garagecam - entities: [] - grid_options: - columns: 6 - rows: auto - - type: picture-glance - camera_image: camera.driveway - entities: [] - grid_options: - columns: 6 - rows: auto - - type: picture-glance - camera_image: camera.kidsgate - entities: [] - grid_options: - columns: 6 - rows: auto - - type: picture-glance - camera_image: camera.frontlawn - entities: [] - grid_options: - columns: 6 - rows: auto - - type: picture-glance - camera_image: camera.bedroomgate - entities: [] - grid_options: - columns: 6 - rows: auto - - type: picture-glance - camera_image: camera.filtergate - entities: [] - grid_options: - columns: 6 - rows: auto - - type: picture-glance - camera_image: camera.poolcam - entities: [] - grid_options: - columns: 6 - rows: auto diff --git a/config/dashboards/kiosk/views/01_kiosk_oveview.yaml b/config/dashboards/kiosk/views/01_kiosk_oveview.yaml deleted file mode 100644 index 9d2db6c0..00000000 --- a/config/dashboards/kiosk/views/01_kiosk_oveview.yaml +++ /dev/null @@ -1,34 +0,0 @@ -###################################################################### -# @CCOSTAN - Follow Me on X -# For more info visit https://www.vcloudinfo.com/click-here -# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig -# ------------------------------------------------------------------- -# KIOSK View - Overview -# YAML-exported Lovelace dashboard (split into view files). -# ------------------------------------------------------------------- -# Notes: Exported from config/.storage/lovelace.dashboard_kiosk view index 1. -# Video: https://youtu.be/ChgEu0IDWzc -###################################################################### - -title: Overview -path: kiosk_oveview -icon: mdi:cctv -type: sections -max_columns: 2 -sections: !include /config/dashboards/kiosk/partials/kiosk_oveview_sections.yaml -badges: !include /config/dashboards/kiosk/partials/kiosk_oveview_badges.yaml -cards: [] -visible: -- user: be280a93c9d7416e98d25d0470f414be -- user: 9543feb5402742a0ab90f301f09f1c0e -dense_section_placement: true -background: - opacity: 100 - alignment: center - size: cover - repeat: no-repeat - attachment: fixed - image: /api/image/serve/caf7862354abd804ef658f899fb3218b/original -header: - layout: center - badges_position: top diff --git a/config/dashboards/kiosk_satellite_staging/dashboard.yaml b/config/dashboards/kiosk_satellite_staging/dashboard.yaml new file mode 100644 index 00000000..834ac6e8 --- /dev/null +++ b/config/dashboards/kiosk_satellite_staging/dashboard.yaml @@ -0,0 +1,14 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Kiosk Satellite Staging Dashboard - Shared tablet camera surface +# Landscape-first Home Assistant dashboard for Kiosk Satellite devices. +# ------------------------------------------------------------------- +# Notes: Hidden preview dashboard; the production Bedroom kiosk remains unchanged. +# Notes: Contains no tablet controls; Kiosk Satellite handles native camera overlays. +###################################################################### + +title: Kiosk Satellite Preview +views: !include_dir_list /config/dashboards/kiosk_satellite_staging/views diff --git a/config/dashboards/kiosk_satellite_staging/partials/home_sections.yaml b/config/dashboards/kiosk_satellite_staging/partials/home_sections.yaml new file mode 100644 index 00000000..c829fd74 --- /dev/null +++ b/config/dashboards/kiosk_satellite_staging/partials/home_sections.yaml @@ -0,0 +1,565 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Kiosk Satellite Partial - Ambient kitchen display +# Enlarged camera feeds plus rotating household reminders, cruise countdown, and locations. +# ------------------------------------------------------------------- +# Notes: Three-row landscape layout uses full-width clock and alert banners around the camera row. +# Notes: Camera cards use automatic streaming to limit idle tablet load. +# Notes: Native Garage and Front Door views take over for their matching live states. +# Notes: The lower panel is display-only and mirrors read-only household fact entities. +###################################################################### + +# Clock and camera wall +- 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(2, minmax(0, 1fr)) + grid-template-areas: | + "clock clock" + "frontlawn driveway" + "ambient ambient" + align-items: stretch + grid-gap: 10px + margin: 0 + mediaquery: + "(max-width: 700px)": + grid-template-columns: 1fr + grid-template-areas: | + "clock" + "frontlawn" + "driveway" + "ambient" + cards: + # The established clock-weather-card supplies a live clock; button-card has no minute ticker. + - type: custom:clock-weather-card + entity: weather.pirateweather + sun_entity: sun.sun + weather_icon_type: fill + animated_icon: true + forecast_rows: 3 + locale: en-US + time_pattern: h:mm + time_format: 12 + date_pattern: DDDD + hide_today_section: false + hide_forecast_section: false + hide_clock: false + hide_date: false + use_browser_time: true + card_mod: + style: | + ha-card { + display: block !important; + height: 150px !important; + padding: 12px 24px !important; + box-sizing: border-box !important; + overflow: hidden !important; + } + ha-card > div { + display: grid !important; + grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr); + align-items: center; + gap: 24px; + height: 126px; + } + clock-weather-card-today { + height: 126px !important; + } + clock-weather-card-forecast { + max-height: 126px; + overflow: hidden; + } + @media (max-width: 700px) { + ha-card > div { + grid-template-columns: 1fr; + } + clock-weather-card-forecast { + display: none; + } + } + view_layout: + grid-area: clock + + # Built-in picture-glance is required because button-card cannot render HA camera streams. + - type: picture-glance + title: Driveway + camera_image: camera.driveway + camera_view: auto + aspect_ratio: "21:9" + entities: [] + view_layout: + grid-area: driveway + + - type: picture-glance + title: Front Lawn + camera_image: camera.frontlawn + camera_view: auto + aspect_ratio: "21:9" + entities: [] + view_layout: + grid-area: frontlawn + + # CSS animation rotates only the currently relevant, read-only household facts. + - type: custom:button-card + show_icon: false + show_name: false + show_state: false + tap_action: + action: none + hold_action: + 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 + - sensor.nws_alerts_alerts + - group.garage_doors + - group.entry_points + - group.locks + - binary_sensor.front_door_packages_present + - sensor.rheem_wh_available_hot_water + - 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 + - person.carlo + - person.stacey + - person.justin + - person.paige + - sensor.carlo_location_display + - sensor.stacey_location_display + - sensor.justin_location_display + - sensor.paige_location_display + 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 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'); + if (phynAlerts.length > 0) { + const alertNames = phynAlerts.map((entityId) => + states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1]) + ); + items.push({ + kicker: 'WATER SYSTEM', + title: 'Phyn needs attention', + detail: alertNames.join(' · '), + icon: 'mdi:water-alert-outline', + accent: '#c62828' + }); + } + + 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] + }); + }); + } 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 + }); + } + + 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' + }); + } + + 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: 'Remember to bring it inside', + icon: 'mdi:package-variant-closed', + accent: '#8e5a2b' + }); + } + + const hotWater = Number.parseFloat(state('sensor.rheem_wh_available_hot_water')); + if (Number.isFinite(hotWater) && hotWater < 30) { + items.push({ + kicker: 'HOT WATER', + title: 'Hot water is running low', + detail: `${Math.round(hotWater)}% available — keep showers short`, + icon: 'mdi:water-thermometer-outline', + accent: '#0277bd' + }); + } + + 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' + }); + } + + 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 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' + }); + } + }); + + 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 secondsPerSlide = 10; + const duration = items.length * secondsPerSlide; + const phase = Math.floor(now.getTime() / 1000) % duration; + const slot = 100 / items.length; + const fade = Math.min(4, slot * 0.18); + return html` + +
+ ${items.map((item, index) => html` +
+
+
+
${item.kicker}
+
${item.title}
+
${item.detail}
+
+
+ `)} +
+ `; + ]]] + styles: + card: + - height: 185px + - overflow: hidden + - border-radius: 18px + - background: linear-gradient(120deg, rgba(33, 150, 243, 0.08), rgba(103, 58, 183, 0.08)) + grid: + - grid-template-areas: '"ambient"' + - grid-template-columns: 1fr + - grid-template-rows: 1fr + custom_fields: + ambient: + - width: 100% + - height: 100% + view_layout: + grid-area: ambient diff --git a/config/dashboards/kiosk_satellite_staging/views/01_home.yaml b/config/dashboards/kiosk_satellite_staging/views/01_home.yaml new file mode 100644 index 00000000..994b4237 --- /dev/null +++ b/config/dashboards/kiosk_satellite_staging/views/01_home.yaml @@ -0,0 +1,22 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Event-Driven Kiosk View - Home clock and cameras +# Shared landscape view for Kiosk Satellite and Fully Kiosk tablets. +# ------------------------------------------------------------------- +# Related Issue: 1875 +# Notes: The Kitchen Show is the reference implementation for this shared view. +# Notes: Device-specific kiosk controls stay out of the shared dashboard. +###################################################################### + +title: Home +path: home +icon: mdi:cctv +theme: kiosk_satellite_auto +type: sections +max_columns: 4 +dense_section_placement: true +sections: !include /config/dashboards/kiosk_satellite_staging/partials/home_sections.yaml +cards: [] diff --git a/config/packages/infrastructure.yaml b/config/packages/infrastructure.yaml index 2e6bbdf0..059df3ef 100644 --- a/config/packages/infrastructure.yaml +++ b/config/packages/infrastructure.yaml @@ -5,8 +5,8 @@ # ------------------------------------------------------------------- # Infrastructure - Observability, disk pressure, and Joanna review workflows # WAN/DNS/website/Docker host state normalized for dashboards, plus scheduled infrastructure reviews. -# Video: https://youtu.be/aceV7ObKJVA -# Blog: https://www.vcloudinfo.com/2026/07/using-codex-clean-up-home-assistant-entities.html +# Video: https://youtu.be/F-PpsKkzcrM +# Blog: https://www.vcloudinfo.com/2026/08/home-assistant-internet-speed-monitoring-ookla.html # ------------------------------------------------------------------- # Related Issue: 1584 # Notes: Home dashboard consumes `infra_*` entities for exceptions-only alerts. @@ -235,7 +235,8 @@ template: 'binary_sensor.infra_dns_pihole_degraded', 'binary_sensor.infra_compute_degraded', 'binary_sensor.infra_storage_degraded', - 'binary_sensor.infra_external_monitoring_degraded' + 'binary_sensor.infra_external_monitoring_degraded', + 'binary_sensor.kiosk_devices_need_attention' ] %} {{ expand(categories) | selectattr('state', 'eq', 'on') | list | count }} attributes: @@ -245,7 +246,8 @@ template: 'binary_sensor.infra_dns_pihole_degraded', 'binary_sensor.infra_compute_degraded', 'binary_sensor.infra_storage_degraded', - 'binary_sensor.infra_external_monitoring_degraded' + 'binary_sensor.infra_external_monitoring_degraded', + 'binary_sensor.kiosk_devices_need_attention' ] %} {{ expand(categories) | selectattr('state', 'eq', 'on') | map(attribute='entity_id') | list }} diff --git a/config/packages/kiosk_tablet.yaml b/config/packages/kiosk_tablet.yaml index 4a4b9e1f..619a7757 100644 --- a/config/packages/kiosk_tablet.yaml +++ b/config/packages/kiosk_tablet.yaml @@ -3,18 +3,31 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# Bedroom Kiosk Tablet - Fully Kiosk page recovery -# Keeps the Master Bedroom Fire tablet pinned to the kiosk dashboard. +# Kiosk Tablets - Fully recovery and Kiosk Satellite camera policy +# Keeps the Bedroom tablet stable and drives state-based Kitchen Show camera views. # ------------------------------------------------------------------- -# Notes: Baseline view path is the real typo-like `kiosk_oveview` slug. -# Notes: Uses Fully controls only; does not enable Fully Kiosk lockout mode. +# Related Issue: 1875 +# Notes: `/dashboard-bedroom/home` remains the canonical desktop/future-hardware route. +# Notes: The Android 5 Fully tablet uses `/local/bedroom-kiosk.html`, a lightweight renderer. +# Notes: Shared camera, header, and alert sensors keep both Bedroom renderers DRY. +# Notes: Fully controls apply only to the legacy Bedroom tablet. +# Notes: Kitchen Show uses Kiosk Satellite discovery entities; no screensaver helper is required. +# Notes: Kitchen Show stays dark while the house is empty or asleep and wakes only for occupied-house events. +# Notes: Garage takes over while either door is moving/open; Front Door takes over while unlocked. +# Notes: New Kiosk Satellite releases dispatch Joanna to install and verify the Kitchen Show update. # Notes: Wake events only restore brightness/focus; URL reloads require page drift. # Notes: Fully auto-reload on screen-on/screensaver-stop is disabled to avoid motion wake reload loops. # Notes: Startup recovery waits for HA's initial websocket state burst to settle. # Notes: Brightness recovery only runs when both SleepIQ in-bed sensors are off. +# Notes: Fully availability/page/app/power plus Kitchen last-seen/update/screenshot freshness feed Systems telemetry. # Video: https://youtu.be/ChgEu0IDWzc ###################################################################### +input_text: + kitchen_show_kiosk_satellite_last_dispatched_version: + name: "Kitchen Show Kiosk Satellite Last Dispatched Version" + max: 32 + automation: - alias: "Bedroom Kiosk Tablet - Return to Kiosk View" id: 11e50d2e-f66f-4fd0-86f4-bdca075c3a1c @@ -60,7 +73,7 @@ automation: {% set bed_clear = is_state('binary_sensor.sleepnumber_carlo_carlo_is_in_bed', 'off') and is_state('binary_sensor.sleepnumber_carlo_stacey_is_in_bed', 'off') %} {% set invalid = ['unknown', 'unavailable', 'none', ''] %} - {{ (current_page not in invalid and '/dashboard-kiosk/kiosk_oveview' not in current_page) + {{ (current_page not in invalid and '/local/bedroom-kiosk.html' not in current_page) or (foreground_app not in invalid and foreground_app != 'de.ozerov.fully') or (bed_clear and brightness not in invalid and brightness | float(100) < 40) }} @@ -90,7 +103,7 @@ automation: {{ previous_app not in invalid and previous_app != 'de.ozerov.fully' }} page_drift: >- {{ previous_page not in invalid - and '/dashboard-kiosk/kiosk_oveview' not in previous_page }} + and '/local/bedroom-kiosk.html' not in previous_page }} - choose: - conditions: - condition: template @@ -130,3 +143,509 @@ automation: Bedroom tablet reloaded after page drift from {{ previous_page }} (foreground app: {{ previous_app }}; brightness: {{ previous_brightness }}). + + - alias: "Kitchen Kiosk Satellite - State Camera Policy" + id: 4c3ddc5b-1fe2-4c11-a77c-e2c15d374e77 + mode: restart + trigger: + - platform: state + entity_id: sensor.kiosk_active_camera_view + + condition: + - condition: state + entity_id: binary_sensor.kitchen_kiosk_should_be_on + state: "on" + + action: + - service: script.kitchen_kiosk_apply_camera_policy + + - alias: "Kitchen Kiosk Satellite - Screen Policy" + id: 99d8d43b-9448-4916-b313-8117a038baaf + mode: restart + trigger: + - platform: state + entity_id: binary_sensor.kitchen_kiosk_should_be_on + to: "on" + - platform: state + entity_id: light.kitchen_show_screen + to: "on" + - platform: state + entity_id: binary_sensor.kitchen_kiosk_should_be_on + to: "off" + + action: + - condition: template + value_template: >- + {{ states('binary_sensor.kitchen_kiosk_should_be_on') in ['on', 'off'] }} + - variables: + desired_volume: >- + {{ 10 if is_state('binary_sensor.kitchen_kiosk_should_be_on', 'on') else 0 }} + # Android reports the nearest hardware volume step (13% for a 10% request). + - if: + - condition: template + value_template: >- + {{ (states('number.kitchen_show_volume') | int(-1) + - desired_volume | int(-1)) | abs > 3 }} + then: + - service: number.set_value + target: + entity_id: number.kitchen_show_volume + data: + value: "{{ desired_volume | int(0) }}" + - choose: + - conditions: + - condition: state + entity_id: binary_sensor.kitchen_kiosk_should_be_on + state: "on" + sequence: + # Kiosk Satellite owns idle dimming. This event-driven dismiss + # wakes to the dashboard without a poller repeatedly forcing the + # panel on during an occupied day. + - service: button.press + target: + entity_id: button.kitchen_show_postpone_screensaver + - service: script.kitchen_kiosk_apply_camera_policy + default: + - service: button.press + target: + entity_id: button.kitchen_show_close_camera_view + - service: light.turn_off + target: + entity_id: light.kitchen_show_screen + + - alias: "Kitchen Kiosk Satellite - Refresh Systems Snapshot" + id: e920cab1-59d3-4d45-8aad-67170127504f + mode: single + max_exceeded: silent + trigger: + - platform: event + event_type: homeassistant_started + id: startup + - platform: time_pattern + minutes: "/15" + id: periodic + action: + - delay: "{{ '00:02:00' if trigger.id == 'startup' else '00:00:00' }}" + - condition: template + value_template: >- + {% set last_seen = as_timestamp(states('sensor.kitchen_show_last_seen'), 0) %} + {{ last_seen > 0 and (as_timestamp(now()) - last_seen) < 300 }} + - service: button.press + target: + entity_id: button.kitchen_show_take_screenshot + + - alias: "Kitchen Kiosk Satellite - Update Joanna Dispatch" + id: 199e30f0-e28c-4842-953d-a65bc4b4e1d3 + description: "Dispatch Joanna when a new Kiosk Satellite release is available for the Kitchen Show." + mode: single + max_exceeded: silent + trigger: + - platform: state + entity_id: update.kitchen_show_update + from: "off" + to: "on" + for: "00:02:00" + + condition: + - condition: template + value_template: >- + {% set installed = state_attr('update.kitchen_show_update', 'installed_version') | default('', true) | string %} + {% set latest = state_attr('update.kitchen_show_update', 'latest_version') | default('', true) | string %} + {{ installed not in ['', 'unknown', 'unavailable'] + and latest not in ['', 'unknown', 'unavailable'] + and installed != latest + and latest != states('input_text.kitchen_show_kiosk_satellite_last_dispatched_version') }} + + action: + - variables: + installed_version: >- + {{ state_attr('update.kitchen_show_update', 'installed_version') | default('unknown', true) }} + latest_version: >- + {{ state_attr('update.kitchen_show_update', 'latest_version') | default('unknown', true) }} + release_url: >- + {{ state_attr('update.kitchen_show_update', 'release_url') | default('not provided', true) }} + structured_request: |- + KITCHEN_KIOSK_SATELLITE_UPDATE_REQUEST + target_device=Kitchen Show + update_entity=update.kitchen_show_update + installed_version={{ installed_version }} + target_version={{ latest_version }} + release_url={{ release_url }} + authorization=This dispatch authorizes installing the pending Kiosk Satellite application update on the Kitchen Show only. Do not update LineageOS, Home Assistant, or unrelated applications and do not factory-reset or re-enroll the tablet. + required_action=Inspect the live update entity and device availability, invoke the supported Home Assistant update.install action, and observe whether installation actually begins. If Android does not finish the installation, use the pre-authorized Joanna ops helper /opt/codex/bin/kitchen-kiosk-update --expected-version {{ latest_version }} to complete this same signed Kiosk Satellite update over the existing LAN-only management path. + required_verification=Do not stop at a successful service call. Verify update.kitchen_show_update reports installed_version equal to latest_version, in_progress is false, the Kitchen Show returns online, sensor.kitchen_show_current_page is the expected kiosk dashboard, sensor.kitchen_show_remote_admin is available, and a fresh screenshot can be requested. + failure_policy=If the signed release cannot be installed safely or the device does not recover, stop without changing unrelated software and report the blocking evidence. + - service: script.send_to_logbook + data: + topic: "KIOSK" + message: >- + Joanna dispatched to update Kitchen Show Kiosk Satellite from + {{ installed_version }} to {{ latest_version }}. + - service: script.joanna_dispatch + data: + trigger_context: "HA automation kitchen_kiosk_satellite_update_joanna_dispatch (Kitchen Kiosk Satellite - Update Joanna Dispatch)" + source: "home_assistant_automation.kitchen_kiosk_satellite_update_dispatch" + summary: >- + Kiosk Satellite {{ latest_version }} is available for Kitchen Show; + installed version is {{ installed_version }}. + request: "{{ structured_request }}" + entity_ids: + - update.kitchen_show_update + - sensor.kitchen_show_last_seen + - sensor.kitchen_show_current_page + - sensor.kitchen_show_remote_admin + - button.kitchen_show_take_screenshot + diagnostics: >- + update_state={{ states('update.kitchen_show_update') }}; + in_progress={{ state_attr('update.kitchen_show_update', 'in_progress') }}; + last_seen={{ states('sensor.kitchen_show_last_seen') }}; + current_page={{ states('sensor.kitchen_show_current_page') }}; + remote_admin={{ states('sensor.kitchen_show_remote_admin') }} + - service: input_text.set_value + target: + entity_id: input_text.kitchen_show_kiosk_satellite_last_dispatched_version + data: + value: "{{ latest_version }}" + +template: + - sensor: + - name: "Kiosk Active Camera View" + unique_id: kiosk_active_camera_view + icon: mdi:camera-switch + state: >- + {% set garage_states = [ + states('cover.large_garage_door'), + states('cover.small_garage_door') + ] %} + {% if garage_states | select('in', ['opening', 'open', 'closing']) | list | count > 0 %} + garage + {% elif is_state('lock.front_door', 'unlocked') %} + front_door + {% else %} + none + {% endif %} + + - name: "Bedroom Kiosk Header" + unique_id: bedroom_kiosk_header + icon: mdi:bed-clock + state: >- + {% set alarm = states('sensor.bedroom_next_wake_alarm') %} + {% set alarm_ts = as_timestamp(alarm, none) %} + {% set alarm_source = states('sensor.bedroom_next_wake_alarm_source') + | replace(' Bedroom', '') %} + {% if alarm_ts is not none %} + NEXT ALARM - {{ alarm_source }} · {{ alarm_ts | timestamp_custom('%a %-I:%M %p') }} + {% else %} + NO BEDROOM WAKE ALARM SET + {% endif %} + attributes: + detail: >- + {% set weather = states('weather.pirateweather') | replace('_', ' ') | title %} + {% set temperature = state_attr('weather.pirateweather', 'temperature') %} + {% set secure = is_state('group.garage_doors', 'closed') + and is_state('group.locks', 'locked') + and is_state('group.entry_points', 'off') %} + {{ 'House secured' if secure else 'Check house security' }} · + {{ temperature | round(0) if temperature is number else '--' }}° · {{ weather }} + + - name: "Kiosk Household Alert" + unique_id: kiosk_household_alert + icon: mdi:message-alert-outline + state: >- + {% set alerts = state_attr('sensor.nws_alerts_alerts', 'Alerts') | default([], true) %} + {% set hot_water = states('sensor.rheem_wh_available_hot_water') | float(100) %} + {% set tomorrow = (now() + timedelta(days=1)).strftime('%Y-%m-%d') %} + {% set battery = states('sensor.spaceship_battery_level') | float(100) %} + {% set ns = namespace(name='', raw='') %} + {% for name, person, location in [ + ('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') + ] %} + {% if not ns.name and states(person) not in ['home', 'unknown', 'unavailable'] %} + {% set ns.name = name %} + {% set ns.raw = states(location) if states(location) not in ['unknown', 'unavailable'] else states(person) %} + {% endif %} + {% endfor %} + {% if alerts | count > 0 %} + WEATHER ALERT - {{ alerts[0].Event | default('National Weather Service') }} + {% elif is_state('binary_sensor.front_door_packages_present', 'on') %} + PACKAGE AT THE FRONT DOOR + {% elif is_state('group.entry_points', 'on') %} + ENTRY POINT OPEN + {% elif states('group.locks') not in ['locked', 'unknown', 'unavailable'] %} + CHECK THE FRONT DOOR LOCK + {% elif hot_water < 30 %} + HOT WATER IS RUNNING LOW + {% elif now().strftime('%a') in ['Wed', 'Sun'] + and not is_state('input_boolean.garbage_cans_out', 'on') %} + {{ 'GARBAGE + RECYCLING' if now().strftime('%a') == 'Wed' else 'GARBAGE' }} DAY + {% elif states('input_datetime.water_delivery_date') == tomorrow %} + WATER DELIVERY TOMORROW + {% elif battery < 50 and is_state('switch.spaceship_charge', 'off') + and is_state('person.carlo', 'home') and is_state('person.stacey', 'home') %} + PLUG IN THE TESLA + {% elif ns.name %} + {% set location = ns.raw | replace(ns.name ~ ' ', '') %} + {{ ns.name }} - {{ location.split(' (since ')[0] }} + {% else %} + EVERYTHING IS HANDLED + {% endif %} + attributes: + detail: >- + {% set alerts = state_attr('sensor.nws_alerts_alerts', 'Alerts') | default([], true) %} + {% set hot_water = states('sensor.rheem_wh_available_hot_water') | float(100) %} + {% set tomorrow = (now() + timedelta(days=1)).strftime('%Y-%m-%d') %} + {% set battery = states('sensor.spaceship_battery_level') | float(100) %} + {% set ns = namespace(name='', raw='') %} + {% for name, person, location in [ + ('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') + ] %} + {% if not ns.name and states(person) not in ['home', 'unknown', 'unavailable'] %} + {% set ns.name = name %} + {% set ns.raw = states(location) if states(location) not in ['unknown', 'unavailable'] else states(person) %} + {% endif %} + {% endfor %} + {% if alerts | count > 0 %} + {{ (alerts[0].Headline | default(alerts[0].Description, true)) + | replace('\n', ' ') | truncate(300, true) }} + {% elif is_state('binary_sensor.front_door_packages_present', 'on') %} + Remember to bring it inside. + {% elif is_state('group.entry_points', 'on') %} + Check the exterior doors and windows. + {% elif states('group.locks') not in ['locked', 'unknown', 'unavailable'] %} + Lock status is {{ states('group.locks') | replace('_', ' ') }}. + {% elif hot_water < 30 %} + {{ hot_water | round(0) }}% is available - keep showers short. + {% elif now().strftime('%a') in ['Wed', 'Sun'] + and not is_state('input_boolean.garbage_cans_out', 'on') %} + Put the cans at the curb. + {% elif states('input_datetime.water_delivery_date') == tomorrow %} + Put the empty bottles out. + {% elif battery < 50 and is_state('switch.spaceship_charge', 'off') + and is_state('person.carlo', 'home') and is_state('person.stacey', 'home') %} + Model Y battery is {{ battery | round(0) }}%. + {% elif ns.name %} + {% set parts = ns.raw.split(' (since ') %} + {{ 'Since ' ~ (parts[1] | replace(')', '')) if parts | count > 1 else 'Away from home' }} + {% else %} + No active household alerts or reminders. + {% endif %} + + - binary_sensor: + - name: "Kitchen Kiosk Should Be On" + unique_id: kitchen_kiosk_should_be_on + icon: mdi:monitor-eye + state: >- + {% set guest = is_state('input_boolean.guest_mode', 'on') %} + {% set sitter = is_state('input_boolean.house_sitter_present', 'on') %} + {% set family_home = is_state('group.family', 'home') %} + {% set carlo_bed = is_state('binary_sensor.sleepnumber_carlo_carlo_is_in_bed', 'on') %} + {% set stacey_bed = is_state('binary_sensor.sleepnumber_carlo_stacey_is_in_bed', 'on') %} + {% set bedtime = (carlo_bed or stacey_bed) + and (carlo_bed or is_state('person.carlo', 'not_home')) + and (stacey_bed or is_state('person.stacey', 'not_home')) %} + {{ guest or sitter or (family_home and not bedtime) }} + attributes: + reason: >- + {% if is_state('input_boolean.guest_mode', 'on') %} + guest mode + {% elif is_state('input_boolean.house_sitter_present', 'on') %} + house sitter present + {% elif not is_state('group.family', 'home') %} + nobody home + {% elif is_state('binary_sensor.sleepnumber_carlo_carlo_is_in_bed', 'on') + and is_state('binary_sensor.sleepnumber_carlo_stacey_is_in_bed', 'on') %} + household asleep + {% elif is_state('binary_sensor.sleepnumber_carlo_carlo_is_in_bed', 'on') + and is_state('person.stacey', 'not_home') %} + Carlo asleep and Stacey away + {% elif is_state('binary_sensor.sleepnumber_carlo_stacey_is_in_bed', 'on') + and is_state('person.carlo', 'not_home') %} + Stacey asleep and Carlo away + {% else %} + household active + {% endif %} + + - name: "Bedroom Kiosk Needs Attention" + unique_id: bedroom_kiosk_needs_attention + device_class: problem + icon: mdi:tablet-alert + delay_on: "00:02:00" + delay_off: "00:00:30" + state: >- + {% set invalid = ['unknown', 'unavailable', 'none', ''] %} + {% set page = states('sensor.alarm_panel_1_current_page') %} + {% set app = states('sensor.alarm_panel_1_foreground_app') %} + {% set battery = states('sensor.alarm_panel_1_battery') | float(0) %} + {{ page in invalid + or app in invalid + or '/local/bedroom-kiosk.html' not in page + or app != 'de.ozerov.fully' + or (battery < 20 and not is_state('binary_sensor.alarm_panel_1_plugged_in', 'on')) }} + attributes: + status: >- + {% set invalid = ['unknown', 'unavailable', 'none', ''] %} + {% set page = states('sensor.alarm_panel_1_current_page') %} + {% set app = states('sensor.alarm_panel_1_foreground_app') %} + {% set battery = states('sensor.alarm_panel_1_battery') | float(0) %} + {% if page in invalid or app in invalid %} + offline + {% elif '/local/bedroom-kiosk.html' not in page %} + page drift + {% elif app != 'de.ozerov.fully' %} + app drift + {% elif battery < 20 and not is_state('binary_sensor.alarm_panel_1_plugged_in', 'on') %} + low battery + {% else %} + online + {% endif %} + current_page: "{{ states('sensor.alarm_panel_1_current_page') }}" + foreground_app: "{{ states('sensor.alarm_panel_1_foreground_app') }}" + battery: "{{ states('sensor.alarm_panel_1_battery') }}" + + - name: "Kitchen Kiosk Needs Attention" + unique_id: kitchen_kiosk_needs_attention + device_class: problem + icon: mdi:monitor-alert + delay_on: "00:02:00" + delay_off: "00:00:30" + state: >- + {% set invalid = ['unknown', 'unavailable', 'none', ''] %} + {% set last_seen = as_timestamp(states('sensor.kitchen_show_last_seen'), 0) %} + {% set screenshot = as_timestamp(states('sensor.kitchen_show_last_screenshot'), 0) %} + {% set page = states('sensor.kitchen_show_current_page') %} + {% set remote_admin = states('sensor.kitchen_show_remote_admin') %} + {% set battery = states('sensor.kitchen_show_battery') | float(0) %} + {% set offline = last_seen == 0 or (as_timestamp(now()) - last_seen) > 300 %} + {% set screenshot_stale = screenshot == 0 or (as_timestamp(now()) - screenshot) > 2700 %} + {{ offline + or page in invalid + or remote_admin in invalid + or '/dashboard-kiosk-satellite-staging/home' not in page + or (battery < 20 and not is_state('binary_sensor.kitchen_show_charging', 'on')) + or is_state('update.kitchen_show_update', 'on') + or screenshot_stale }} + attributes: + status: >- + {% set invalid = ['unknown', 'unavailable', 'none', ''] %} + {% set last_seen = as_timestamp(states('sensor.kitchen_show_last_seen'), 0) %} + {% set screenshot = as_timestamp(states('sensor.kitchen_show_last_screenshot'), 0) %} + {% set page = states('sensor.kitchen_show_current_page') %} + {% set remote_admin = states('sensor.kitchen_show_remote_admin') %} + {% set battery = states('sensor.kitchen_show_battery') | float(0) %} + {% if last_seen == 0 or (as_timestamp(now()) - last_seen) > 300 %} + offline + {% elif page in invalid or remote_admin in invalid %} + telemetry unavailable + {% elif '/dashboard-kiosk-satellite-staging/home' not in page %} + page drift + {% elif battery < 20 and not is_state('binary_sensor.kitchen_show_charging', 'on') %} + low battery + {% elif is_state('update.kitchen_show_update', 'on') %} + update available + {% elif screenshot == 0 or (as_timestamp(now()) - screenshot) > 2700 %} + screenshot stale + {% else %} + online + {% endif %} + last_seen: "{{ states('sensor.kitchen_show_last_seen') }}" + last_screenshot: "{{ states('sensor.kitchen_show_last_screenshot') }}" + current_page: "{{ states('sensor.kitchen_show_current_page') }}" + battery: "{{ states('sensor.kitchen_show_battery') }}" + + - name: "Kiosk Devices Need Attention" + unique_id: kiosk_devices_need_attention + device_class: problem + icon: mdi:tablet-cellphone + state: >- + {{ is_state('binary_sensor.bedroom_kiosk_needs_attention', 'on') + or is_state('binary_sensor.kitchen_kiosk_needs_attention', 'on') }} + attributes: + status: >- + {% set bedroom = is_state('binary_sensor.bedroom_kiosk_needs_attention', 'on') %} + {% set kitchen = is_state('binary_sensor.kitchen_kiosk_needs_attention', 'on') %} + {% if bedroom and kitchen %} + Bedroom and Kitchen need attention + {% elif bedroom %} + Bedroom needs attention + {% elif kitchen %} + Kitchen needs attention + {% else %} + All kiosks online + {% endif %} + devices: >- + {% set ns = namespace(items=[]) %} + {% if is_state('binary_sensor.bedroom_kiosk_needs_attention', 'on') %} + {% set ns.items = ns.items + ['Bedroom'] %} + {% endif %} + {% if is_state('binary_sensor.kitchen_kiosk_needs_attention', 'on') %} + {% set ns.items = ns.items + ['Kitchen'] %} + {% endif %} + {{ ns.items }} + +script: + kitchen_kiosk_apply_camera_policy: + alias: "Kitchen Kiosk - Apply Camera Policy" + mode: restart + variables: + desired_view: >- + {{ {'garage': 'Garage', 'front_door': 'Front Door'}.get( + states('sensor.kiosk_active_camera_view'), 'none') }} + desired_button: >- + {{ 'button.kitchen_show_show_garage' + if desired_view == 'Garage' + else 'button.kitchen_show_show_front_door' }} + sequence: + - choose: + - conditions: + - condition: template + value_template: "{{ desired_view in ['Garage', 'Front Door'] }}" + sequence: + - condition: template + value_template: >- + {{ states('sensor.kitchen_show_active_camera_view') != desired_view }} + - service: script.kitchen_kiosk_show_camera + data: + show_button: "{{ desired_button }}" + default: + - choose: + - conditions: + - condition: template + value_template: >- + {{ states('sensor.kitchen_show_active_camera_view') + not in ['none', 'unknown', 'unavailable'] }} + sequence: + - service: button.press + target: + entity_id: button.kitchen_show_close_camera_view + - service: button.press + target: + entity_id: button.kitchen_show_bring_to_front + + kitchen_kiosk_show_camera: + alias: "Kitchen Kiosk - Show Camera" + mode: restart + fields: + show_button: + name: Camera view button + description: Kiosk Satellite MQTT button for the native camera view. + required: true + selector: + entity: + domain: button + sequence: + - service: light.turn_on + target: + entity_id: light.kitchen_show_screen + - service: button.press + target: + entity_id: button.kitchen_show_bring_to_front + - delay: "00:00:01" + - service: button.press + target: + entity_id: "{{ show_button }}" diff --git a/config/packages/royalcaribbean.yaml b/config/packages/royalcaribbean.yaml index 753cc6c5..4dc5d563 100644 --- a/config/packages/royalcaribbean.yaml +++ b/config/packages/royalcaribbean.yaml @@ -3,12 +3,33 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# Royal Caribbean Price Alerts - Webhook-driven drop notifications -# Uses Cruise Tracker webhook to notify via notify_engine + HA UI. +# Royal Caribbean - Price alerts and a sanitized booked-cruise countdown +# Uses Cruise Tracker for webhook alerts and a read-only Kitchen Show fact. # ------------------------------------------------------------------- # Notes: Use script.notify_engine for app notifications. +# Notes: The REST endpoint omits confirmation, cabin, price, and watch-list data. ###################################################################### +sensor: + - platform: rest + name: Next Booked Cruise Countdown + unique_id: next_booked_cruise_countdown + resource: !secret cruise_tracker_next_booked_url + method: GET + scan_interval: 3600 + timeout: 20 + value_template: >- + {% set cruise = value_json.next_booked_cruise | default(none) %} + {{ cruise.days_until if cruise is mapping and cruise.days_until is number else 'unknown' }} + json_attributes_path: "$.next_booked_cruise" + json_attributes: + - ship + - sail_date + - nights + - port + icon: mdi:ferry + unit_of_measurement: "days" + automation: - alias: "Royal Caribbean Price Drop Alert" id: royal_caribbean_price_drop_alert diff --git a/config/themes/kiosk_satellite.yaml b/config/themes/kiosk_satellite.yaml new file mode 100644 index 00000000..6e3bf4a9 --- /dev/null +++ b/config/themes/kiosk_satellite.yaml @@ -0,0 +1,29 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Kiosk Satellite Theme - Automatic day and night palette +# Follows the Kiosk Satellite client theme schedule for shared kiosk views. +# ------------------------------------------------------------------- +# Notes: Kitchen Show switches light/dark variants at 07:00/19:00 device time. +###################################################################### + +kiosk_satellite_auto: + modes: + light: + primary-background-color: "#f4f6f8" + secondary-background-color: "#ffffff" + card-background-color: "#ffffff" + primary-text-color: "#15181d" + secondary-text-color: "#5d6673" + divider-color: "rgba(0, 0, 0, 0.12)" + lovelace-background: "#f4f6f8" + dark: + primary-background-color: "#0b0f14" + secondary-background-color: "#111821" + card-background-color: "#151d27" + primary-text-color: "#f3f6fa" + secondary-text-color: "#aab6c5" + divider-color: "rgba(255, 255, 255, 0.12)" + lovelace-background: "#0b0f14" diff --git a/config/www/bedroom-kiosk.html b/config/www/bedroom-kiosk.html new file mode 100644 index 00000000..cfe6bf05 --- /dev/null +++ b/config/www/bedroom-kiosk.html @@ -0,0 +1,336 @@ + + + + + + Bedroom Kiosk + + + +
Connecting to Home Assistant…
+
+
+

Bedroom status

+

Connecting to Home Assistant…

+
+
+
+ Front Lawn camera +
Front Lawn
+
+
+ Driveway camera +
Driveway
+
+
+
+

Household status

+

Loading alerts and reminders…

+
+
+
+ Active event camera +
+
+ + + + diff --git a/config/www/custom-sidebar-loader.js b/config/www/custom-sidebar-loader.js new file mode 100644 index 00000000..3a44780f --- /dev/null +++ b/config/www/custom-sidebar-loader.js @@ -0,0 +1,9 @@ +// Custom Sidebar is useful in interactive browsers, but wall kiosks run inside +// Android WebViews where the global sidebar extension is unnecessary and can +// delay Home Assistant startup on older hardware. +const isAndroidWebView = /\bAndroid\b/i.test(navigator.userAgent) + && /(?:;|\s)wv\)/i.test(navigator.userAgent); + +if (!isAndroidWebView) { + import("/hacsfiles/custom-sidebar/custom-sidebar-plugin.js?v=17.0.0"); +}