diff --git a/config/.cache/brands/integrations/fully_kiosk/icon@2x.png b/config/.cache/brands/integrations/fully_kiosk/icon@2x.png new file mode 100644 index 00000000..48cbd767 Binary files /dev/null and b/config/.cache/brands/integrations/fully_kiosk/icon@2x.png differ diff --git a/config/dashboards/overview/partials/health_sections.yaml b/config/dashboards/overview/partials/health_sections.yaml index 19943cb0..a48da5c8 100644 --- a/config/dashboards/overview/partials/health_sections.yaml +++ b/config/dashboards/overview/partials/health_sections.yaml @@ -318,8 +318,8 @@ points_per_hour: 1 line_width: 3 graph: line - show: - fill: false - points: false - extrema: true + show: + fill: false + points: false + extrema: true diff --git a/config/dashboards/overview/partials/water_sections.yaml b/config/dashboards/overview/partials/water_sections.yaml index ec3e5dea..daef02e9 100644 --- a/config/dashboards/overview/partials/water_sections.yaml +++ b/config/dashboards/overview/partials/water_sections.yaml @@ -7,6 +7,7 @@ # Reusable list extracted from a view for smaller diffs and safer edits. # ------------------------------------------------------------------- # Notes: Extracted from config/dashboards/overview/views/07_water.yaml key `sections`. +# Notes: Includes water and home utility telemetry (filters, power, hot water). ###################################################################### - cards: diff --git a/config/dashboards/overview/views/07_water.yaml b/config/dashboards/overview/views/07_water.yaml index d5cf5a12..02eaef2f 100644 --- a/config/dashboards/overview/views/07_water.yaml +++ b/config/dashboards/overview/views/07_water.yaml @@ -3,13 +3,14 @@ # For more info visit https://www.vcloudinfo.com/click-here # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # ------------------------------------------------------------------- -# Overview View - Utilities +# Overview View - Home Maintenance # YAML-exported Lovelace dashboard (split into view files). # ------------------------------------------------------------------- # Notes: Exported from config/.storage/lovelace.lovelace view index 7. +# Notes: Keep path as `water` to preserve existing links and automations. ###################################################################### -title: Utilities +title: Home Maintenance path: water icon: mdi:home-lightning-bolt type: sections diff --git a/config/packages/README.md b/config/packages/README.md index 09ebe124..8df47ee9 100755 --- a/config/packages/README.md +++ b/config/packages/README.md @@ -56,6 +56,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this | [telegram_bot.yaml](telegram_bot.yaml) | Telegram script wrappers used by BearClaw and other ops flows (UI integration remains the source for bot config). | `script.joanna_send_telegram`, `telegram_bot.send_message` | | [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with push + Activity feed + Repairs issues for leak events. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `repairs.create` | | [water_delivery.yaml](water_delivery.yaml) | ReadyRefresh delivery date helper with night-before + garage door Alexa reminders, plus helper-change audit logging and Telegram confirmations. | `input_datetime.water_delivery_date`, `script.send_to_logbook`, `script.joanna_send_telegram`, `notify.alexa_media_garage` | +| [maintenance_log.yaml](maintenance_log.yaml) | Joanna maintenance webhook ingest for water softener salt with idempotent event handling, Activity feed logging, and recorder-backed helper history for long-term graphing. | `automation.maintenance_log_joanna_webhook_ingest`, `input_number.water_softener_salt_total_added_lb`, `counter.water_softener_salt_event_count`, `sensor.water_softener_salt_days_since_last_add` | | [powerwall.yaml](powerwall.yaml) | Track Tesla Powerwall grid status and shed loads automatically when off-grid (alerts include Activity feed + Repairs). | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `repairs.create` | | [vacuum.yaml](vacuum.yaml) | Dreame vacuum orchestration with room tracking, push alerts, Activity feed, Repairs issues on errors, and Alexa one-off room-clean switches. | `input_select.l10s_vacuum_phase`, `sensor.l10s_vacuum_error`, `repairs.create` | | [hass_agent_homepc.yaml](hass_agent_homepc.yaml) | Mirrors PC lock/unlock state from HASS.Agent to the office lamp for instant desk presence cues. | `sensor.carlo_homepc_carlo_homepc_sessionstate`, `switch.office_lamp_switch` | diff --git a/config/packages/maintenance_log.yaml b/config/packages/maintenance_log.yaml new file mode 100644 index 00000000..f6f6e159 --- /dev/null +++ b/config/packages/maintenance_log.yaml @@ -0,0 +1,315 @@ +###################################################################### +# @CCOSTAN - Follow Me on X +# For more info visit https://www.vcloudinfo.com/click-here +# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig +# ------------------------------------------------------------------- +# Maintenance Logging - Joanna webhook ingest + graphable HA history +# Stores water softener salt maintenance events in recorder-backed helpers. +# ------------------------------------------------------------------- +# Notes: Webhook id is bearclaw_maintenance_log_v1 (Joanna -> HA contract). +# Notes: Duplicate event_id values are ignored to prevent double-count totals. +# Notes: Recent event history string format is "when|amount|note||...". +###################################################################### + +input_number: + water_softener_salt_last_amount_lb: + name: "Softener salt last amount" + min: 0 + max: 1000 + step: 0.1 + mode: box + unit_of_measurement: lb + icon: mdi:shaker + + water_softener_salt_total_added_lb: + name: "Softener salt total added" + min: 0 + max: 100000 + step: 0.1 + mode: box + unit_of_measurement: lb + icon: mdi:chart-line + +counter: + water_softener_salt_event_count: + name: "Softener salt event count" + step: 1 + icon: mdi:counter + +input_datetime: + water_softener_salt_last_occurred_at: + name: "Softener salt last occurred at" + has_date: true + has_time: true + icon: mdi:calendar-clock + +input_text: + water_softener_salt_last_note: + name: "Softener salt last note" + max: 255 + icon: mdi:text-box-outline + + water_softener_salt_recent_event_ids: + name: "Softener salt recent event ids" + max: 255 + icon: mdi:identifier + + water_softener_salt_recent_events: + name: "Softener salt recent events" + max: 255 + icon: mdi:history + +template: + - sensor: + - name: "Water Softener Salt Days Since Last Add" + unique_id: water_softener_salt_days_since_last_add + unit_of_measurement: d + state: >- + {% set raw = states('input_datetime.water_softener_salt_last_occurred_at') %} + {% if raw in ['unknown', 'unavailable', 'none', ''] %} + unknown + {% else %} + {% set event_ts = as_timestamp(as_local(as_datetime(raw)), default=none) %} + {% if event_ts is none %} + unknown + {% else %} + {{ [((as_timestamp(now()) - event_ts) / 86400), 0] | max | round(1) }} + {% endif %} + {% endif %} + + - name: "Water Softener Salt Last Summary" + unique_id: water_softener_salt_last_summary + icon: mdi:clipboard-text-clock-outline + state: >- + {% set raw = states('input_datetime.water_softener_salt_last_occurred_at') %} + {% set amount = states('input_number.water_softener_salt_last_amount_lb') | float(0) %} + {% set note = states('input_text.water_softener_salt_last_note') %} + {% if raw in ['unknown', 'unavailable', 'none', ''] %} + No salt events logged yet. + {% else %} + {% set when = as_datetime(raw).astimezone().strftime('%a %b %d, %Y %I:%M %p') | replace(' 0', ' ') %} + {% if note in ['unknown', 'unavailable', 'none', ''] %} + {{ amount | round(1) }} lb on {{ when }} + {% else %} + {{ amount | round(1) }} lb on {{ when }} - {{ note }} + {% endif %} + {% endif %} + + - name: "Water Softener Salt Average Days Between Refills" + unique_id: water_softener_salt_average_days_between_refills + unit_of_measurement: d + state: >- + {% set raw = states('input_text.water_softener_salt_recent_events') %} + {% if raw in ['unknown', 'unavailable', 'none', ''] %} + 150 + {% else %} + {% set entries = raw.split('||') %} + {% set ns = namespace(previous_ts=none, total_days=0, sample_count=0) %} + {% for entry in entries %} + {% set parts = entry.split('|') %} + {% set dt = as_datetime(parts[0] | default('', true) | trim, default=none) %} + {% if dt is not none %} + {% set ts = as_timestamp(dt, default=none) %} + {% if ts is not none %} + {% if ns.previous_ts is not none and ns.previous_ts > ts %} + {% set ns.total_days = ns.total_days + ((ns.previous_ts - ts) / 86400) %} + {% set ns.sample_count = ns.sample_count + 1 %} + {% endif %} + {% set ns.previous_ts = ts %} + {% endif %} + {% endif %} + {% endfor %} + {% if ns.sample_count > 0 %} + {{ (ns.total_days / ns.sample_count) | round(1) }} + {% else %} + 150 + {% endif %} + {% endif %} + +automation: + - alias: "Maintenance Log - Joanna Webhook Ingest" + id: 1c9fba4f-fef5-4da9-82d4-4049deff17cf + mode: queued + max: 30 + + trigger: + - platform: webhook + webhook_id: bearclaw_maintenance_log_v1 + allowed_methods: + - POST + - PUT + local_only: false + + variables: + payload: "{{ trigger.json if trigger.json is mapping else dict() }}" + source_item: "{{ payload.item_key | default('', true) | string | trim | lower }}" + item_key: >- + {% set aliases = { + 'water_softener_salt': 'water_softener_salt', + 'softener_salt': 'water_softener_salt', + 'water_softener': 'water_softener_salt', + 'softener': 'water_softener_salt', + 'water softener salt': 'water_softener_salt', + 'salt': 'water_softener_salt', + 'softner': 'water_softener_salt' + } %} + {{ aliases.get(source_item, source_item) }} + action: "{{ payload.action | default('add', true) | string | trim | lower }}" + source_unit: "{{ payload.amount_unit | default('lb', true) | string | trim | lower }}" + unit: >- + {% set units = { + 'lb': 'lb', + 'lbs': 'lb', + 'pound': 'lb', + 'pounds': 'lb' + } %} + {{ units.get(source_unit, source_unit) }} + amount_value: "{{ payload.amount_value | default(0, true) | float(0) }}" + amount_lb: >- + {% if unit == 'lb' %} + {{ amount_value | round(2) }} + {% else %} + 0 + {% endif %} + event_id: >- + {% set raw_id = payload.event_id | default('', true) | string | trim %} + {% if raw_id %} + {{ raw_id }} + {% else %} + maint_{{ now().timestamp() | int }} + {% endif %} + occurred_at_raw: "{{ payload.occurred_at | default(now().isoformat(), true) }}" + occurred_at: >- + {% set parsed = as_datetime(occurred_at_raw, default=none) %} + {% if parsed is none %} + {{ now().isoformat() }} + {% else %} + {{ parsed.isoformat() }} + {% endif %} + occurred_local_datetime: >- + {% set dt = as_local(as_datetime(occurred_at, default=now())) %} + {{ dt.strftime('%Y-%m-%d %H:%M:%S') }} + occurred_display: >- + {% set dt = as_datetime(occurred_local_datetime, default=now()) %} + {{ dt.strftime('%Y-%m-%d %I:%M %p') | replace(' 0', ' ') }} + actor: "{{ payload.actor | default('unknown', true) | string | trim }}" + raw_text: "{{ payload.raw_text | default('', true) | string | replace('|', '/') | trim }}" + parse_confidence: "{{ payload.parse_confidence | default('unknown', true) | string | trim }}" + is_duplicate: >- + {% set existing = (states('input_text.water_softener_salt_recent_event_ids') + | default('', true) | string).split('|') %} + {{ event_id in existing }} + is_supported_item: "{{ item_key == 'water_softener_salt' }}" + is_add_action: "{{ action in ['add', 'top_off', 'refill'] }}" + effective_amount_lb: >- + {% if item_key == 'water_softener_salt' and action in ['add', 'top_off', 'refill'] %} + 80 + {% else %} + {{ amount_lb }} + {% endif %} + note_value: >- + {% set text = raw_text if raw_text else 'Logged by Joanna webhook.' %} + {{ text | truncate(255, true, '') }} + recent_event_line: "{{ occurred_display }}|{{ effective_amount_lb | round(1) }} lb|{{ note_value }}" + next_recent_events: >- + {% set current = (states('input_text.water_softener_salt_recent_events') + | default('', true) | string).split('||') %} + {% set ns = namespace(items=[recent_event_line]) %} + {% for raw in current %} + {% set value = raw | trim %} + {% if value and value not in ['unknown', 'unavailable', 'none'] and value != recent_event_line and (ns.items | count) < 10 %} + {% set ns.items = ns.items + [value] %} + {% endif %} + {% endfor %} + {{ ns.items | join('||') }} + next_recent_ids: >- + {% set current = (states('input_text.water_softener_salt_recent_event_ids') + | default('', true) | string).split('|') %} + {% set ns = namespace(items=[event_id]) %} + {% for raw in current %} + {% set value = raw | trim %} + {% if value and value not in ['unknown', 'unavailable', 'none'] and value != event_id and (ns.items | count) < 8 %} + {% set ns.items = ns.items + [value] %} + {% endif %} + {% endfor %} + {{ ns.items | join('|') }} + current_total_lb: "{{ states('input_number.water_softener_salt_total_added_lb') | float(0) }}" + next_total_lb: >- + {% if is_add_action %} + {{ (current_total_lb + effective_amount_lb) | round(2) }} + {% else %} + {{ current_total_lb | round(2) }} + {% endif %} + + action: + - choose: + - conditions: + - condition: template + value_template: "{{ not is_supported_item }}" + sequence: + - service: script.send_to_logbook + data: + topic: MAINTENANCE + message: >- + Ignored unsupported maintenance item "{{ item_key }}" (event_id={{ event_id }}). + - conditions: + - condition: template + value_template: "{{ effective_amount_lb <= 0 }}" + sequence: + - service: script.send_to_logbook + data: + topic: MAINTENANCE + message: >- + Ignored maintenance payload with invalid amount (event_id={{ event_id }}, raw_amount={{ amount_value }} {{ unit }}). + - conditions: + - condition: template + value_template: "{{ is_duplicate }}" + sequence: + - service: script.send_to_logbook + data: + topic: MAINTENANCE + message: >- + Duplicate maintenance event ignored for softener salt (event_id={{ event_id }}). + default: + - service: input_number.set_value + data: + entity_id: input_number.water_softener_salt_last_amount_lb + value: "{{ effective_amount_lb }}" + + - service: input_number.set_value + data: + entity_id: input_number.water_softener_salt_total_added_lb + value: "{{ next_total_lb }}" + + - service: counter.increment + data: + entity_id: counter.water_softener_salt_event_count + + - service: input_datetime.set_datetime + data: + entity_id: input_datetime.water_softener_salt_last_occurred_at + datetime: "{{ occurred_local_datetime }}" + + - service: input_text.set_value + data: + entity_id: input_text.water_softener_salt_last_note + value: "{{ note_value }}" + + - service: input_text.set_value + data: + entity_id: input_text.water_softener_salt_recent_event_ids + value: "{{ next_recent_ids | truncate(255, true, '') }}" + + - service: input_text.set_value + data: + entity_id: input_text.water_softener_salt_recent_events + value: "{{ next_recent_events | truncate(255, true, '') }}" + + - service: script.send_to_logbook + data: + topic: MAINTENANCE + message: >- + Softener salt logged: {{ effective_amount_lb | round(1) }} lb at {{ occurred_display }}. + total={{ next_total_lb | round(1) }} lb, count={{ states('counter.water_softener_salt_event_count') | int(0) + 1 }}, + actor={{ actor }}, confidence={{ parse_confidence }}, event_id={{ event_id }}, + raw="{{ raw_text | truncate(110, true, '...') }}". diff --git a/config/recorder.yaml b/config/recorder.yaml index f4dc3d13..29a75c43 100755 --- a/config/recorder.yaml +++ b/config/recorder.yaml @@ -6,10 +6,10 @@ # Recorder Configuration - database retention and exclusions # Stores HA history while purging noise and controlling DB size. # ------------------------------------------------------------------- -# Notes: Keeps 30 days; excludes vcloudinfo pings and other high-churn entities; MariaDB via recorder_db_url. +# Notes: Keeps 180 days (1/2 year); excludes vcloudinfo pings, noisy connectivity telemetry, and other high-churn entities; MariaDB via recorder_db_url. ###################################################################### db_url: !secret recorder_db_url -purge_keep_days: 30 +purge_keep_days: 180 auto_purge: true commit_interval: 30 exclude: @@ -47,7 +47,18 @@ exclude: - sensor.*uptime* - sensor.sun_next_* - sensor.vpn_client_* + - sensor.*_linkquality + - sensor.*_link_quality + - sensor.*_lqi + - sensor.*_rssi + - sensor.*_signal + - sensor.*_signal_strength + - sensor.*_wi_fi_signal + - sensor.*_wifi_signal - sensor.*_wifi_signal_strength + - sensor.*_temperature_state + - sensor.*_humidity_state + - sensor.*_last_seen* - switch.*_do_not_disturb_* - switch.*_repeat_switch - input_text.l10s_vacuum_*