From 220c8c453bf53a264c83e6b9c30a823d87c5f9d9 Mon Sep 17 00:00:00 2001 From: Carlo Costanzo Date: Sun, 8 Mar 2026 16:00:41 -0400 Subject: [PATCH] Update recorder configuration and enhance dashboard sections - Increased database retention period from 30 to 180 days in recorder.yaml. - Added additional exclusions for telemetry data in recorder.yaml to reduce noise. - Updated health_sections.yaml for improved formatting consistency. - Revised water_sections.yaml notes to clarify included telemetry. - Changed overview view title from "Utilities" to "Home Maintenance" in 07_water.yaml for better context. --- .../integrations/fully_kiosk/icon@2x.png | Bin 0 -> 6656 bytes .../overview/partials/health_sections.yaml | 8 +- .../overview/partials/water_sections.yaml | 1 + .../dashboards/overview/views/07_water.yaml | 5 +- config/packages/README.md | 1 + config/packages/maintenance_log.yaml | 315 ++++++++++++++++++ config/recorder.yaml | 15 +- 7 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 config/.cache/brands/integrations/fully_kiosk/icon@2x.png create mode 100644 config/packages/maintenance_log.yaml 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 0000000000000000000000000000000000000000..48cbd7674d9c697e7e77444dbeb453709df194d8 GIT binary patch literal 6656 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 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_*