From 9e4bbc7a445c6c0966579f899e7cd614279d44af Mon Sep 17 00:00:00 2001 From: jessikitty Date: Tue, 2 Jun 2026 18:27:37 +1000 Subject: [PATCH] Fix unavailable_entities self-reference loop + oversized recorder attribute; remove duplicate devices_currently_blocked/system_health_score block --- sensors.yaml | 513 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 sensors.yaml diff --git a/sensors.yaml b/sensors.yaml new file mode 100644 index 0000000..d61f675 --- /dev/null +++ b/sensors.yaml @@ -0,0 +1,513 @@ +# sensors.yaml - All sensors for monitoring, device management, and household automation + +# Hardware monitoring sensors +- platform: openhardwaremonitor + host: 10.0.0.245 + port: 8085 + +# SNMP sensors for NVX monitoring +- platform: snmp + host: 10.0.0.232 + baseoid: 1.3.6.1.2.1.1.5.0 + name: "NVX Name" + +- platform: snmp + host: 10.0.0.232 + baseoid: 1.3.6.1.4.1.4526.18.4.1.2.1 + name: "NVX Fan RPM" + +- platform: snmp + host: 10.0.0.232 + baseoid: 1.3.6.1.4.1.4526.18.5.1.2.1 + name: "NVX Temp Value" + +# Command line sensors +- platform: command_line + name: "Chore Summary" + command: "cat /config/www/chore_summary.txt" + scan_interval: 60 + +- platform: command_line + name: "Chore Log Raw" + command: "cat /config/www/chore_log.txt" + scan_interval: 60 + +# Time and date sensors +- platform: time_date + display_options: + - "time" + - "date" + +# All template sensors (using legacy format for compatibility) +- platform: template + sensors: + # Waste collection sensors + green_bin: + friendly_name: "Greens Bin" + value_template: > + {% set start_date = as_timestamp('2026-04-12 00:00:00') %} + {% set now_ts = now().timestamp() %} + {% set weeks_since_start = ((now_ts - start_date) / 604800) | round(0, 'floor') | int %} + {{ now().weekday() == 6 and weeks_since_start % 2 == 0 }} + + recycling_bin: + friendly_name: "Recycling Bin" + value_template: > + {% set start_date = as_timestamp('2026-04-12 00:00:00') %} + {% set now_ts = now().timestamp() %} + {% set weeks_since_start = ((now_ts - start_date) / 604800) | round(0, 'floor') | int %} + {{ now().weekday() == 6 and weeks_since_start % 2 == 1 }} + + # Time-based sensors + is_weekday: + friendly_name: "Is Weekday" + value_template: > + {% set now = now() %} + {{ now.weekday() >= 0 and now.weekday() <= 4 }} + + current_day_name: + friendly_name: "Current Day Name" + value_template: > + {% set now = now() %} + {% set day_number = now.weekday() %} + {{ ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][day_number] }} + + is_morning: + friendly_name: "Is Morning" + value_template: > + {% set now = now() %} + {{ now.hour >= 6 and now.hour < 12 }} + + is_afternoon: + friendly_name: "Is Afternoon" + value_template: > + {% set now = now() %} + {{ now.hour >= 12 and now.hour < 18 }} + + is_evening: + friendly_name: "Is Evening" + value_template: > + {% set now = now() %} + {{ now.hour >= 18 and now.hour < 21 }} + + is_bedtime: + friendly_name: "Is Bedtime" + value_template: > + {% set now = now() %} + {{ now.hour >= 21 and now.hour <= 23 }} + + is_weekend: + friendly_name: "Is Weekend" + value_template: > + {% set now = now() %} + {{ now.weekday() >= 5 }} + + # School term sensors + school_term_1: + friendly_name: "School Term 1" + value_template: > + {% set now = as_timestamp(now()) %} + {% set start = as_timestamp(strptime('2026-01-27', '%Y-%m-%d')) %} + {% set end = as_timestamp(strptime('2026-04-02', '%Y-%m-%d')) %} + {{ now >= start and now <= end }} + + school_term_2: + friendly_name: "School Term 2" + value_template: > + {% set now = as_timestamp(now()) %} + {% set start = as_timestamp(strptime('2026-04-20', '%Y-%m-%d')) %} + {% set end = as_timestamp(strptime('2026-06-26', '%Y-%m-%d')) %} + {{ now >= start and now <= end }} + + school_term_3: + friendly_name: "School Term 3" + value_template: > + {% set now = as_timestamp(now()) %} + {% set start = as_timestamp(strptime('2026-07-13', '%Y-%m-%d')) %} + {% set end = as_timestamp(strptime('2026-09-18', '%Y-%m-%d')) %} + {{ now >= start and now <= end }} + + school_term_4: + friendly_name: "School Term 4" + value_template: > + {% set now = as_timestamp(now()) %} + {% set start = as_timestamp(strptime('2026-10-05', '%Y-%m-%d')) %} + {% set end = as_timestamp(strptime('2026-12-18', '%Y-%m-%d')) %} + {{ now >= start and now <= end }} + + is_school_term: + friendly_name: "Is School Term" + value_template: > + {{ states('sensor.school_term_1') == 'True' or + states('sensor.school_term_2') == 'True' or + states('sensor.school_term_3') == 'True' or + states('sensor.school_term_4') == 'True' }} + + current_date_debug: + friendly_name: "Current Date Debug" + value_template: > + {{ now().date() }} + + # Task management sensors + active_tasks_count: + friendly_name: "Active Tasks Count" + value_template: > + {% set tasks = [ + 'input_boolean.task_dishwasher_unload_pending', + 'input_boolean.task_washing_machine_unload_pending', + 'input_boolean.task_dryer_unload_pending', + 'input_boolean.task_bins_out_pending', + 'input_boolean.task_bins_in_pending', + 'input_boolean.task_kitty_litter_clean_pending', + 'input_boolean.task_kitty_litter_change_pending' + ] %} + {{ tasks | select('is_state', 'on') | list | count }} + + pending_tasks_list: + friendly_name: "Pending Tasks" + value_template: > + {% set task_names = { + 'input_boolean.task_dishwasher_unload_pending': 'Unload Dishwasher', + 'input_boolean.task_washing_machine_unload_pending': 'Unload Washing Machine', + 'input_boolean.task_dryer_unload_pending': 'Unload Dryer', + 'input_boolean.task_bins_out_pending': 'Put Bins Out', + 'input_boolean.task_bins_in_pending': 'Bring Bins In', + 'input_boolean.task_kitty_litter_clean_pending': 'Clean Kitty Litter', + 'input_boolean.task_kitty_litter_change_pending': 'Change Kitty Litter' + } %} + {% set pending = [] %} + {% for entity, name in task_names.items() %} + {% if is_state(entity, 'on') %} + {% set pending = pending + [name] %} + {% endif %} + {% endfor %} + {{ pending | join(', ') }} + + last_kitty_litter_change: + friendly_name: "Last Kitty Litter Change" + value_template: > + {{ states('input_datetime.last_kitty_litter_change') | as_timestamp | timestamp_custom('%Y-%m-%d') + if states('input_datetime.last_kitty_litter_change') != 'unknown' else 'Never' }} + + # Weekly task totals for each person + lou_weekly_tasks: + friendly_name: "Lou's Weekly Tasks" + value_template: "{{ states('counter.chores_completed_lou') | int }}" + icon_template: mdi:account + + jess_weekly_tasks: + friendly_name: "Jess's Weekly Tasks" + value_template: "{{ states('counter.chores_completed_jess') | int }}" + icon_template: mdi:account + + william_weekly_tasks: + friendly_name: "William's Weekly Tasks" + value_template: "{{ states('counter.chores_completed_william') | int }}" + icon_template: mdi:account + + xander_weekly_tasks: + friendly_name: "Xander's Weekly Tasks" + value_template: "{{ states('counter.chores_completed_xander') | int }}" + icon_template: mdi:account + + bella_weekly_tasks: + friendly_name: "Bella's Weekly Tasks" + value_template: "{{ states('counter.chores_completed_bella') | int }}" + icon_template: mdi:account + + # Total weekly tasks + total_weekly_tasks: + friendly_name: "Total Weekly Tasks" + value_template: > + {{ states('counter.chores_completed_lou') | int + + states('counter.chores_completed_jess') | int + + states('counter.chores_completed_william') | int + + states('counter.chores_completed_xander') | int + + states('counter.chores_completed_bella') | int }} + icon_template: mdi:clipboard-list + + # Percentage calculations for pie chart + lou_percentage: + friendly_name: "Lou's Percentage" + value_template: > + {% set total = states('sensor.total_weekly_tasks') | int %} + {% set lou_tasks = states('counter.chores_completed_lou') | int %} + {% if total > 0 %} + {{ ((lou_tasks / total) * 100) | round(1) }} + {% else %} + 0 + {% endif %} + unit_of_measurement: "%" + + jess_percentage: + friendly_name: "Jess's Percentage" + value_template: > + {% set total = states('sensor.total_weekly_tasks') | int %} + {% set jess_tasks = states('counter.chores_completed_jess') | int %} + {% if total > 0 %} + {{ ((jess_tasks / total) * 100) | round(1) }} + {% else %} + 0 + {% endif %} + unit_of_measurement: "%" + + william_percentage: + friendly_name: "William's Percentage" + value_template: > + {% set total = states('sensor.total_weekly_tasks') | int %} + {% set william_tasks = states('counter.chores_completed_william') | int %} + {% if total > 0 %} + {{ ((william_tasks / total) * 100) | round(1) }} + {% else %} + 0 + {% endif %} + unit_of_measurement: "%" + + xander_percentage: + friendly_name: "Xander's Percentage" + value_template: > + {% set total = states('sensor.total_weekly_tasks') | int %} + {% set xander_tasks = states('counter.chores_completed_xander') | int %} + {% if total > 0 %} + {{ ((xander_tasks / total) * 100) | round(1) }} + {% else %} + 0 + {% endif %} + unit_of_measurement: "%" + + bella_percentage: + friendly_name: "Bella's Percentage" + value_template: > + {% set total = states('sensor.total_weekly_tasks') | int %} + {% set bella_tasks = states('counter.chores_completed_bella') | int %} + {% if total > 0 %} + {{ ((bella_tasks / total) * 100) | round(1) }} + {% else %} + 0 + {% endif %} + unit_of_measurement: "%" + + # Network and device management sensors + total_devices_online: + friendly_name: "Total Devices Online" + icon_template: mdi:devices + unit_of_measurement: "devices" + value_template: > + {{ [ + states('device_tracker.william_pc'), + states('device_tracker.william_laptop'), + states('device_tracker.william_phone'), + states('device_tracker.jess_laptop_1'), + states('device_tracker.jess_phone'), + states('device_tracker.bella_desktop'), + states('device_tracker.bella_phone'), + states('device_tracker.xander_gaming_pc'), + states('device_tracker.xander_console') + ] | select('eq', 'home') | list | count }} + availability_template: > + {{ has_value('device_tracker.william_pc') + and has_value('device_tracker.jess_laptop_1') + and has_value('device_tracker.bella_phone') }} + + # NOTE: This sensor MUST exclude its own entity_id, otherwise every + # recalculation changes its own state, which retriggers the template, + # causing a "Template loop detected" storm. The full entity list is + # intentionally NOT stored as an attribute (it exceeded the 16384-byte + # recorder limit and was rejected every cycle); only a capped preview + # is exposed for dashboards. + unavailable_entities: + friendly_name: "Unavailable Entities" + icon_template: > + {{ 'mdi:check-circle' if states('sensor.unavailable_entities')|int == 0 else 'mdi:alert-circle' }} + unit_of_measurement: "entities" + value_template: > + {{ states | selectattr('state', 'in', ['unavailable', 'unknown']) + | rejectattr('domain', 'eq', 'group') + | rejectattr('entity_id', 'eq', 'sensor.unavailable_entities') + | rejectattr('entity_id', 'search', 'sun\.') + | list | count }} + attribute_templates: + # Capped to first 50 entity_ids to stay well under the 16 KB + # recorder attribute limit. Drop this block entirely if you don't + # need the preview on a dashboard. + entities_preview: > + {{ (states | selectattr('state', 'in', ['unavailable', 'unknown']) + | rejectattr('domain', 'eq', 'group') + | rejectattr('entity_id', 'eq', 'sensor.unavailable_entities') + | rejectattr('entity_id', 'search', 'sun\.') + | map(attribute='entity_id') | list | sort)[:50] }} + + family_devices_status: + friendly_name: "Family Devices Status" + icon_template: mdi:account-multiple + value_template: > + {% set devices = [ + 'device_tracker.william_pc', + 'device_tracker.william_phone', + 'device_tracker.jess_laptop_1', + 'device_tracker.jess_phone', + 'device_tracker.bella_desktop', + 'device_tracker.bella_phone', + 'device_tracker.xander_gaming_pc' + ] %} + {% set online = devices | select('is_state', 'home') | list | count %} + {% set total = devices | count %} + {{ online }}/{{ total }} + + network_performance_score: + friendly_name: "Network Performance Score" + unit_of_measurement: "%" + icon_template: > + {% set score = states('sensor.network_performance_score') | int(0) %} + {% if score >= 80 %}mdi:speedometer + {% elif score >= 60 %}mdi:speedometer-medium + {% else %}mdi:speedometer-slow + {% endif %} + value_template: > + {% if is_state('binary_sensor.ping_router', 'on') %} + {% set ping_time = state_attr('binary_sensor.ping_router', 'round_trip_time_avg') | float(0) %} + {% if ping_time < 10 %}100 + {% elif ping_time < 50 %}80 + {% elif ping_time < 100 %}60 + {% elif ping_time < 200 %}40 + {% else %}20 + {% endif %} + {% else %} + 0 + {% endif %} + + blocked_devices_count: + friendly_name: "Blocked Devices Count" + icon_template: mdi:block-helper + unit_of_measurement: "devices" + value_template: > + {{ states.input_boolean + | selectattr('entity_id', 'match', '.*_blocked$') + | selectattr('state', 'eq', 'on') + | list | count }} + attribute_templates: + blocked_devices: > + {{ states.input_boolean + | selectattr('entity_id', 'match', '.*_blocked$') + | selectattr('state', 'eq', 'on') + | map(attribute='name') + | list }} + + bella_devices_status: + friendly_name: "Bella Devices Status" + icon_template: mdi:account-child + value_template: > + {% set blocked = states.input_boolean + | selectattr('entity_id', 'match', '^input_boolean\.bella_.*_blocked$') + | selectattr('state', 'eq', 'on') + | list | count %} + {% set total = 7 %} + {% if blocked == 0 %}All Access + {% elif blocked == total %}Fully Blocked + {% else %}{{ blocked }}/{{ total }} Blocked + {% endif %} + + xander_devices_status: + friendly_name: "Xander Devices Status" + icon_template: mdi:account-child + value_template: > + {% set blocked = states.input_boolean + | selectattr('entity_id', 'match', '^input_boolean\.xander_.*_blocked$') + | selectattr('state', 'eq', 'on') + | list | count %} + {% set total = 7 %} + {% if blocked == 0 %}All Access + {% elif blocked == total %}Fully Blocked + {% else %}{{ blocked }}/{{ total }} Blocked + {% endif %} + + william_devices_status: + friendly_name: "William Devices Status" + icon_template: mdi:account-child + value_template: > + {% set blocked = states.input_boolean + | selectattr('entity_id', 'match', '^input_boolean\.william_.*_blocked$') + | selectattr('state', 'eq', 'on') + | list | count %} + {% set total = 7 %} + {% if blocked == 0 %}All Access + {% elif blocked == total %}Fully Blocked + {% else %}{{ blocked }}/{{ total }} Blocked + {% endif %} + +# Device blocking statistics + devices_currently_blocked: + friendly_name: "Devices Currently Blocked" + icon_template: mdi:shield-lock + unit_of_measurement: "devices" + value_template: > + {{ states.input_boolean + | selectattr('entity_id', 'match', '.*_blocked$') + | selectattr('state', 'eq', 'on') + | list | count }} + + # System health monitoring + system_health_score: + friendly_name: "System Health Score" + unit_of_measurement: "%" + icon_template: > + {% set score = states('sensor.system_health_score') | int(0) %} + {% if score >= 90 %}mdi:heart-pulse + {% elif score >= 70 %}mdi:heart-half-full + {% else %}mdi:heart-broken + {% endif %} + value_template: > + {% set router_ok = 1 if is_state('binary_sensor.ping_router', 'on') else 0 %} + {% set auth_ok = 1 if states('input_text.router_token') not in ['unknown', '', 'unavailable'] else 0 %} + {% set internet_ok = 1 if is_state('binary_sensor.internet_connectivity', 'on') else 0 %} + {% set unavailable = states('sensor.unavailable_entities') | int(0) %} + {% set health_penalty = (unavailable * 2) | int %} + {% set base_score = ((router_ok + auth_ok + internet_ok) * 30) | int %} + {{ [base_score - health_penalty, 0] | max }} + + # Network security level + network_security_level: + friendly_name: "Network Security Level" + icon_template: > + {% set level = states('sensor.network_security_level') %} + {% if level == 'High' %}mdi:shield-check + {% elif level == 'Medium' %}mdi:shield-half-full + {% else %}mdi:shield-alert + {% endif %} + value_template: > + {% set blocked = states('sensor.devices_currently_blocked') | int(0) %} + {% if blocked >= 10 %}High + {% elif blocked >= 5 %}Medium + {% elif blocked >= 1 %}Low + {% else %}Off + {% endif %} + + # Router device count + router_device_count: + friendly_name: "Router Connected Devices" + icon_template: mdi:devices + unit_of_measurement: "devices" + value_template: > + {{ states.input_boolean + | selectattr('entity_id', 'match', '.*_online$') + | selectattr('state', 'eq', 'on') + | list | count }} + + # Router access control rules count + router_access_control_rules: + friendly_name: "Active Access Control Rules" + icon_template: mdi:shield-check + unit_of_measurement: "rules" + value_template: > + {{ states('sensor.devices_currently_blocked') | int(0) }} + + # MAC address change detection + devices_with_changed_macs: + friendly_name: "Devices with Changed MACs" + icon_template: mdi:alert-circle + unit_of_measurement: "devices" + value_template: > + {{ states.input_boolean + | selectattr('entity_id', 'match', '.*_mac_changed$') + | selectattr('state', 'eq', 'on') + | list | count }}