From 1a1b4431afc16640fffc34dbc9580dbc622a0f07 Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Sun, 2 Jul 2023 11:14:17 +0200 Subject: [PATCH 01/11] Setup automation hallway --- home-assistant/config/automations.yaml | 8 ++++---- home-assistant/esphome-data/hallway-epo.yaml | 12 ++++++++++++ ips.md | 2 ++ 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 home-assistant/esphome-data/hallway-epo.yaml diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 5cca7a32e..540b89444 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -539,7 +539,7 @@ hours: 3 minutes: 0 seconds: 0 - illuminance_threshold: 200 + illuminance_threshold: 150 - id: "1687446669605" alias: Living Room Kitchen - Lights description: "" @@ -550,7 +550,6 @@ light_and_switch_entity_ids: - light.kitchen_lights illuminance_entity_id: sensor.living_room_illuminance - illuminance_threshold: 200 away_timer_entity_id: timer.living_room_kitchen_away_timer away_timer_duration: hours: 0 @@ -561,6 +560,7 @@ hours: 3 minutes: 0 seconds: 0 + illuminance_threshold: 150 - id: "1687446932202" alias: Living Room Reading - Lights description: "" @@ -571,7 +571,6 @@ light_and_switch_entity_ids: - light.living_room_monkey_light illuminance_entity_id: sensor.living_room_illuminance - illuminance_threshold: 200 away_timer_entity_id: timer.living_room_reading_away_timer away_timer_duration: hours: 0 @@ -582,6 +581,7 @@ hours: 3 minutes: 0 seconds: 0 + illuminance_threshold: 150 - id: "1687447110495" alias: Living Room Sofa - Lights description: "" @@ -593,7 +593,6 @@ - light.living_room_corner_light - light.living_room_tv_light illuminance_entity_id: sensor.living_room_illuminance - illuminance_threshold: 200 away_timer_entity_id: timer.living_room_sofa_away_timer away_timer_duration: hours: 0 @@ -604,6 +603,7 @@ hours: 3 minutes: 0 seconds: 0 + illuminance_threshold: 150 - id: "1687452619492" alias: Bathroom Shower - Lights description: "" diff --git a/home-assistant/esphome-data/hallway-epo.yaml b/home-assistant/esphome-data/hallway-epo.yaml new file mode 100644 index 000000000..7baa39a44 --- /dev/null +++ b/home-assistant/esphome-data/hallway-epo.yaml @@ -0,0 +1,12 @@ +substitutions: + device_name: "hallway-epo" + entity_id: "hallway_epo" + static_ip: "192.168.1.90" + +packages: + Everything_Smart_Technology.Everything_Presence_One: github://everythingsmarthome/presence-one/everything-presence-one.yaml@main + base: !include .base.yaml + +esphome: + name: ${name} + name_add_mac_suffix: false diff --git a/ips.md b/ips.md index 72565ca73..4676bcacc 100755 --- a/ips.md +++ b/ips.md @@ -46,6 +46,8 @@ 192.168.1.84 - test-shelly +192.168.1.90 - hallway-epo + 192.168.1.100 - 192.168.1.254 DHCP Range Unifi subnet mask: 255.255.255.0 From 20489c42e671bbf2b8399ea055518681b04d057d Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Sun, 2 Jul 2023 11:28:54 +0200 Subject: [PATCH 02/11] add another substitution for package --- home-assistant/esphome-data/hallway-epo.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/home-assistant/esphome-data/hallway-epo.yaml b/home-assistant/esphome-data/hallway-epo.yaml index 7baa39a44..641a9b76a 100644 --- a/home-assistant/esphome-data/hallway-epo.yaml +++ b/home-assistant/esphome-data/hallway-epo.yaml @@ -1,4 +1,5 @@ substitutions: + name: "hallway-epo" device_name: "hallway-epo" entity_id: "hallway_epo" static_ip: "192.168.1.90" From 5a62dec4b8e5852dce5662fa87b4a970f91db7fe Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Sun, 2 Jul 2023 16:23:10 +0200 Subject: [PATCH 03/11] remove unused automations --- home-assistant/config/automations.yaml | 78 -------------------------- 1 file changed, 78 deletions(-) diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 540b89444..516e2f8e3 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -56,27 +56,6 @@ presence_indicator_entity_ids: - binary_sensor.hallway_motion_occupancy - binary_sensor.hallway_door_contact -- id: "1655752840815" - alias: "Landing - Presence: Manage" - description: "" - use_blueprint: - path: diy/presence.yaml - input: - presence_entity_id: variable.landing_presence - presence_hint_entity_ids: - - binary_sensor.landing_shelly_input - - binary_sensor.office_door_contact - - binary_sensor.baby_room_door_contact - - binary_sensor.bedroom_door_contact - - binary_sensor.bathroom_door_contact - presence_indicator_entity_ids: - - binary_sensor.baby_room_motion_occupancy - - binary_sensor.bedroom_motion_occupancy - - binary_sensor.laundry_room_motion_occupancy - - binary_sensor.office_motion_occupancy - - binary_sensor.bathroom_motion_occupancy - - binary_sensor.landing_motion_occupancy - presence_timeout: 120 - id: "1655752934365" alias: "Laundry Room - Presence: Manage" description: "" @@ -101,20 +80,6 @@ presence_hint_entity_ids: - binary_sensor.office_door_contact - binary_sensor.office_shelly_input -- id: "1655753220517" - alias: "Baby Room - Presence: Manage" - description: "" - use_blueprint: - path: diy/presence.yaml - input: - presence_entity_id: variable.baby_room_presence - presence_indicator_entity_ids: - - binary_sensor.baby_room_motion_occupancy - - binary_sensor.baby_room_human_presence - presence_hint_entity_ids: - - binary_sensor.baby_room_shelly_input - - binary_sensor.baby_room_door_contact - presence_timeout: 60 - id: "1655755184933" alias: "Driveway - Light: Manage" description: "" @@ -128,20 +93,6 @@ end_time: "23:59:59" illuminance_entity_id: sensor.illuminance illuminance_threshold: 1000 -- id: "1655798480666" - alias: "Baby Room - Light: Manage" - description: "" - use_blueprint: - path: diy/auto_lights.yaml - input: - presence_entity_id: variable.baby_room_presence - illuminance_entity_id: sensor.baby_room_motion_illuminance_2 - light_and_switch_entity_ids: - - light.baby_room_light - - switch.baby_room_power - start_time: 00:00:00 - end_time: "23:59:59" - illuminance_threshold: 40 - id: "1655798724103" alias: "Hallway - Light: Manage" description: "" @@ -155,19 +106,6 @@ start_time: 00:00:00 end_time: "23:59:59" illuminance_threshold: 1000 -- id: "1655799812663" - alias: "Landing - Light: Manage" - description: "" - use_blueprint: - path: diy/auto_lights.yaml - input: - presence_entity_id: variable.landing_presence - illuminance_entity_id: sensor.landing_motion_illuminance_lux - illuminance_threshold: 60 - light_and_switch_entity_ids: - - light.landing_lights - start_time: 06:00:00 - end_time: "23:59:59" - id: "1655799885346" alias: "Laundry Room - Light: Manage" description: "" @@ -320,22 +258,6 @@ target: entity_id: input_boolean.bedroom_is_sleeping mode: single -- id: "1657638476657" - alias: "Kitchen - Lights: Auto Off" - description: "" - trigger: - - platform: state - entity_id: - - variable.kitchen_presence - to: "off" - condition: [] - action: - - service: light.turn_off - data: - transition: 2 - target: - entity_id: light.kitchen_lights - mode: single - id: "1658226673278" alias: "Adaptive lighting: toggle 'sleep mode'" description: "" From 705484b7aceea82127b604c1d2d5865d6614926b Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Sun, 2 Jul 2023 18:00:31 +0200 Subject: [PATCH 04/11] upgrade variables hacs --- home-assistant/config/automations.yaml | 74 +- .../automation/diy/auto_lights.yaml | 30 +- .../blueprints/automation/diy/presence.yaml | 10 +- home-assistant/config/configuration.yaml | 92 +- .../custom_components/variable/__init__.py | 415 ++++---- .../variable/binary_sensor.py | 324 +++++++ .../custom_components/variable/config_flow.py | 882 ++++++++++++++++++ .../custom_components/variable/const.py | 29 + .../custom_components/variable/helpers.py | 182 ++++ .../custom_components/variable/manifest.json | 24 +- .../custom_components/variable/sensor.py | 379 ++++++++ .../custom_components/variable/services.yaml | 113 ++- .../custom_components/variable/strings.json | 107 +++ .../variable/translations/en.json | 107 +++ 14 files changed, 2449 insertions(+), 319 deletions(-) create mode 100644 home-assistant/config/custom_components/variable/binary_sensor.py create mode 100644 home-assistant/config/custom_components/variable/config_flow.py create mode 100644 home-assistant/config/custom_components/variable/const.py create mode 100644 home-assistant/config/custom_components/variable/helpers.py create mode 100644 home-assistant/config/custom_components/variable/sensor.py create mode 100644 home-assistant/config/custom_components/variable/strings.json create mode 100644 home-assistant/config/custom_components/variable/translations/en.json diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 516e2f8e3..6876f22e0 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -4,7 +4,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.supply_closet_presence + presence_entity_id: sensor.supply_closet_presence presence_indicator_entity_ids: - binary_sensor.supply_closet_door_contact presence_hint_entity_ids: @@ -16,7 +16,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.toilet_presence + presence_entity_id: sensor.toilet_presence presence_indicator_entity_ids: - binary_sensor.toilet_motion_occupancy presence_hint_entity_ids: @@ -30,7 +30,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.driveway_presence + presence_entity_id: sensor.driveway_presence presence_indicator_entity_ids: - binary_sensor.driveway_doorbell_motion - binary_sensor.hallway_door_contact @@ -43,7 +43,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.hallway_presence + presence_entity_id: sensor.hallway_presence presence_hint_entity_ids: - binary_sensor.hallway_shelly_input - binary_sensor.toilet_door_contact @@ -62,7 +62,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.laundry_room_presence + presence_entity_id: sensor.laundry_room_presence presence_indicator_entity_ids: - binary_sensor.laundry_room_motion_occupancy presence_hint_entity_ids: @@ -73,7 +73,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.office_presence + presence_entity_id: sensor.office_presence presence_indicator_entity_ids: - binary_sensor.office_human_presence - binary_sensor.office_motion_occupancy @@ -86,7 +86,7 @@ use_blueprint: path: diy/auto_lights.yaml input: - presence_entity_id: variable.driveway_presence + presence_entity_id: sensor.driveway_presence light_and_switch_entity_ids: - switch.driveway_shelly start_time: 00:00:00 @@ -99,7 +99,7 @@ use_blueprint: path: diy/auto_lights.yaml input: - presence_entity_id: variable.hallway_presence + presence_entity_id: sensor.hallway_presence illuminance_entity_id: sensor.hallway_motion_illuminance_lux light_and_switch_entity_ids: - light.hallway_lights @@ -112,7 +112,7 @@ use_blueprint: path: diy/auto_lights.yaml input: - presence_entity_id: variable.laundry_room_presence + presence_entity_id: sensor.laundry_room_presence illuminance_entity_id: sensor.laundry_room_motion_illuminance_lux illuminance_threshold: 100 light_and_switch_entity_ids: @@ -125,7 +125,7 @@ use_blueprint: path: diy/auto_lights.yaml input: - presence_entity_id: variable.office_presence + presence_entity_id: sensor.office_presence illuminance_entity_id: sensor.office_illuminance_illuminance_lux light_and_switch_entity_ids: - switch.office_shelly @@ -138,7 +138,7 @@ use_blueprint: path: diy/auto_lights.yaml input: - presence_entity_id: variable.supply_closet_presence + presence_entity_id: sensor.supply_closet_presence illuminance_entity_id: sensor.illuminance illuminance_threshold: 200000 light_and_switch_entity_ids: @@ -151,7 +151,7 @@ use_blueprint: path: diy/auto_lights.yaml input: - presence_entity_id: variable.toilet_presence + presence_entity_id: sensor.toilet_presence illuminance_entity_id: sensor.illuminance illuminance_threshold: 200000 light_and_switch_entity_ids: @@ -164,7 +164,7 @@ use_blueprint: path: diy/presence.yaml input: - presence_entity_id: variable.kitchen_presence + presence_entity_id: sensor.kitchen_presence presence_indicator_entity_ids: - binary_sensor.kitchen_human_presence - id: "1657026314050" @@ -344,7 +344,7 @@ - if: - condition: template value_template: - "{% set the_past = state_attr('variable.apple_watch_1_location', + "{% set the_past = state_attr('sensor.apple_watch_1_location', 'last_updated') %} {% set the_present = now() %} @@ -355,9 +355,10 @@ {{ seconds_passed > 120 }}" then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location data: - variable: apple_watch_1_location value: away attributes: next_location: away @@ -370,14 +371,15 @@ - if: - condition: template value_template: - "{{ trigger.payload_json.location == states('variable.apple_watch_1_location') + "{{ trigger.payload_json.location == states('sensor.apple_watch_1_location') }}" then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location data: - variable: apple_watch_1_location attributes: - next_location: "{{ states('variable.apple_watch_1_location') }}" + next_location: "{{ states('sensor.apple_watch_1_location') }}" next_location_iteration: 0 icon: mdi:home last_updated: "{{ now() }}" @@ -387,11 +389,12 @@ - condition: template value_template: "{{ trigger.payload_json.guesses[0].probability < 0.5 }}" then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location data: - variable: apple_watch_1_location attributes: - next_location: "{{ states('variable.apple_watch_1_location') }}" + next_location: "{{ states('sensor.apple_watch_1_location') }}" next_location_iteration: 0 icon: mdi:home last_updated: "{{ now() }}" @@ -400,41 +403,44 @@ - if: - condition: template value_template: - "{{ trigger.payload_json.location != state_attr('variable.apple_watch_1_location', + "{{ trigger.payload_json.location != state_attr('sensor.apple_watch_1_location', 'next_location') }}" then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location data: - variable: apple_watch_1_location attributes: next_location: "{{ trigger.payload_json.location }}" next_location_iteration: 0 icon: mdi:home last_updated: "{{ now() }}" alias: Update next location - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location data: - variable: apple_watch_1_location attributes: next_location_iteration: - "{{ (state_attr('variable.apple_watch_1_location', + "{{ (state_attr('sensor.apple_watch_1_location', 'next_location_iteration') | int(default=0)) + 1 }}" alias: Next location iteration increment - if: - condition: template value_template: - "{{ (state_attr('variable.apple_watch_1_location', 'next_location_iteration') + "{{ (state_attr('sensor.apple_watch_1_location', 'next_location_iteration') | int(default=0)) > 1 }}" then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location data: - variable: apple_watch_1_location value: - "{{ state_attr('variable.apple_watch_1_location', 'next_location') + "{{ state_attr('sensor.apple_watch_1_location', 'next_location') }}" attributes: next_location: - "{{ state_attr('variable.apple_watch_1_location', 'next_location') + "{{ state_attr('sensor.apple_watch_1_location', 'next_location') }}" next_location_iteration: 0 icon: mdi:home diff --git a/home-assistant/config/blueprints/automation/diy/auto_lights.yaml b/home-assistant/config/blueprints/automation/diy/auto_lights.yaml index 66f5410e2..728f21894 100644 --- a/home-assistant/config/blueprints/automation/diy/auto_lights.yaml +++ b/home-assistant/config/blueprints/automation/diy/auto_lights.yaml @@ -130,9 +130,10 @@ action: {% set are_entities_on = expand(light_and_switch_entity_ids) | selectattr("state", "equalto", "on") | list | length > 0 %} {{ are_entities_on }} then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" attributes: lights_manual_override: "on" - stop: > @@ -158,9 +159,10 @@ action: then: - stop: Light triggered by automation. Stopping execution. else: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" attributes: lights_manual_override: "on" - stop: Light triggered externally, flagging manual override. Stopping execution. @@ -170,9 +172,10 @@ action: entity_id: !input presence_entity_id state: "off" then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" attributes: lights_manual_override: "off" lights_triggered_by_automation: "off" @@ -222,16 +225,18 @@ action: - condition: template value_template: '{{ trigger.platform == "state" }}' then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" attributes: trigger_context_id: "{{ trigger.to_state.context.id }}" lights_manual_override: "off" - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" attributes: lights_triggered_by_automation: "on" @@ -254,9 +259,10 @@ action: - condition: template value_template: '{{ trigger.platform == "state" }}' then: - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" attributes: trigger_context_id: "{{ trigger.to_state.context.id }}" lights_manual_override: "off" diff --git a/home-assistant/config/blueprints/automation/diy/presence.yaml b/home-assistant/config/blueprints/automation/diy/presence.yaml index cc2f36a73..72d3e21f1 100644 --- a/home-assistant/config/blueprints/automation/diy/presence.yaml +++ b/home-assistant/config/blueprints/automation/diy/presence.yaml @@ -109,9 +109,10 @@ action: {% set has_presence = presence_hint_entities | selectattr("last_changed", "greaterthan", now() - timedelta(seconds = presence_timeout)) | list | length > 0 %} {{ has_presence }} - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" value: "on" attributes: icon: mdi:home @@ -131,9 +132,10 @@ action: {{ has_no_presence }} continue_on_timeout: false - - service: variable.set_variable + - service: variable.update_sensor + target: + entity_id: !input presence_entity_id data: - variable: "{{ presence_variable }}" value: "off" attributes: icon: mdi:home-outline diff --git a/home-assistant/config/configuration.yaml b/home-assistant/config/configuration.yaml index dc423cf25..c3e44b1b0 100644 --- a/home-assistant/config/configuration.yaml +++ b/home-assistant/config/configuration.yaml @@ -38,6 +38,38 @@ timer: bedroom_away_timer: bedroom_manual_timer: +input_select: + hallway_presence: + name: Hallway Presence + options: + - absent + - entering + - present + - leaving + icon: mdi:panda + +template: + - binary_sensor: + - name: living_room_door + state: > + {% set door_id = 'binary_sensor.living_room_door_contact' %} + {% set motion_id = 'binary_sensor.living_room_motion_occupancy' %} + {% set timeout = 10 %} + + {% set door_last_updated = states[door_id].last_updated %} + {% set door_is_open = is_state(door_id, 'on') %} + {% set door_activity_timeout = now() - timedelta(seconds = timeout) %} + {% set door_recently_updated = door_last_updated >= door_activity_timeout %} + {% set motion_is_active = is_state(motion_id, 'on') %} + + {% if door_recently_updated %} + on + {% elif door_is_open and motion_is_active %} + on + {% else %} + off + {% endif %} + automation: !include automations.yaml script: !include scripts.yaml scene: !include scenes.yaml @@ -168,36 +200,6 @@ variable: next_location: "away" next_location_iteration: 0 - baby_room_presence: - value: "off" - restore: true - attributes: - friendly_name: "Baby Room Presence" - icon: mdi:home-outline - lights_manual_override: "off" - lights_triggered_by_automation: "off" - trigger_context_id: "" - - bathroom_presence: - value: "off" - restore: true - attributes: - friendly_name: "Bathroom Presence" - icon: mdi:home-outline - lights_manual_override: "off" - lights_triggered_by_automation: "off" - trigger_context_id: "" - - bedroom_presence: - value: "off" - restore: true - attributes: - friendly_name: "Bedroom Presence" - icon: mdi:home-outline - lights_manual_override: "off" - lights_triggered_by_automation: "off" - trigger_context_id: "" - driveway_presence: value: "off" restore: true @@ -218,26 +220,6 @@ variable: lights_triggered_by_automation: "off" trigger_context_id: "" - kitchen_presence: - value: "off" - restore: true - attributes: - friendly_name: "Kitchen Presence" - icon: mdi:home-outline - lights_manual_override: "off" - lights_triggered_by_automation: "off" - trigger_context_id: "" - - landing_presence: - value: "off" - restore: true - attributes: - friendly_name: "Landing Presence" - icon: mdi:home-outline - lights_manual_override: "off" - lights_triggered_by_automation: "off" - trigger_context_id: "" - laundry_room_presence: value: "off" restore: true @@ -248,16 +230,6 @@ variable: lights_triggered_by_automation: "off" trigger_context_id: "" - living_room_presence: - value: "off" - restore: true - attributes: - friendly_name: "Living Room Presence" - icon: mdi:home-outline - lights_manual_override: "off" - lights_triggered_by_automation: "off" - trigger_context_id: "" - office_presence: value: "off" restore: true diff --git a/home-assistant/config/custom_components/variable/__init__.py b/home-assistant/config/custom_components/variable/__init__.py index ee38c5858..319c3968f 100644 --- a/home-assistant/config/custom_components/variable/__init__.py +++ b/home-assistant/config/custom_components/variable/__init__.py @@ -1,242 +1,269 @@ -"""variable implementation for Home Assistant.""" +"""Variable implementation for Home Assistant.""" +import json import logging -from homeassistant.const import ATTR_ICON, CONF_NAME +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_FRIENDLY_NAME, CONF_ICON, CONF_NAME, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.typing import ConfigType import voluptuous as vol -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "variable" -ENTITY_ID_FORMAT = DOMAIN + ".{}" - -CONF_ATTRIBUTES = "attributes" -CONF_VALUE = "value" -CONF_RESTORE = "restore" -CONF_FORCE_UPDATE = "force_update" -CONF_DOMAIN = "domain" +from .const import ( + ATTR_ATTRIBUTES, + ATTR_ENTITY, + ATTR_REPLACE_ATTRIBUTES, + ATTR_VALUE, + ATTR_VARIABLE, + CONF_ATTRIBUTES, + CONF_ENTITY_PLATFORM, + CONF_FORCE_UPDATE, + CONF_RESTORE, + CONF_VALUE, + CONF_VARIABLE_ID, + DEFAULT_REPLACE_ATTRIBUTES, + DOMAIN, + PLATFORMS, +) -ATTR_ENTITY = "entity" -ATTR_VARIABLE = "variable" -ATTR_VALUE = "value" -ATTR_ATTRIBUTES = "attributes" -ATTR_REPLACE_ATTRIBUTES = "replace_attributes" -ATTR_DOMAIN = "domain" +_LOGGER = logging.getLogger(__name__) -SERVICE_SET_ENTITY = "set_entity" -SERVICE_SET_VARIABLE = "set_variable" +SERVICE_SET_VARIABLE_LEGACY = "set_variable" +SERVICE_SET_ENTITY_LEGACY = "set_entity" -SERVICE_SET_ENTITY_SCHEMA = vol.Schema( +SERVICE_SET_VARIABLE_LEGACY_SCHEMA = vol.Schema( { - vol.Required(ATTR_ENTITY): cv.string, + vol.Required(ATTR_VARIABLE): cv.string, vol.Optional(ATTR_VALUE): cv.match_all, vol.Optional(ATTR_ATTRIBUTES): dict, - vol.Optional(ATTR_REPLACE_ATTRIBUTES): cv.boolean, + vol.Optional( + ATTR_REPLACE_ATTRIBUTES, default=DEFAULT_REPLACE_ATTRIBUTES + ): cv.boolean, } ) -SERVICE_SET_VARIABLE_SCHEMA = vol.Schema( + +SERVICE_SET_ENTITY_LEGACY_SCHEMA = vol.Schema( { - vol.Required(ATTR_VARIABLE): cv.string, + vol.Required(ATTR_ENTITY): cv.string, vol.Optional(ATTR_VALUE): cv.match_all, vol.Optional(ATTR_ATTRIBUTES): dict, - vol.Optional(ATTR_REPLACE_ATTRIBUTES): cv.boolean, + vol.Optional( + ATTR_REPLACE_ATTRIBUTES, default=DEFAULT_REPLACE_ATTRIBUTES + ): cv.boolean, } ) -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - cv.slug: vol.Any( - { - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_VALUE): cv.match_all, - vol.Optional(CONF_ATTRIBUTES): dict, - vol.Optional(CONF_RESTORE): cv.boolean, - vol.Optional(CONF_FORCE_UPDATE): cv.boolean, - vol.Optional(ATTR_DOMAIN): cv.string, - }, - None, - ) - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -def get_entity_id_format(domain: str) -> str: - """Get the entity id format.""" - return domain + ".{}" - async def async_setup(hass: HomeAssistant, config: ConfigType): - """Set up variables.""" - component = EntityComponent(_LOGGER, DOMAIN, hass) - - entities = [] + """Set up the Variable services.""" - for variable_id, variable_config in config[DOMAIN].items(): - if not variable_config: - variable_config = {} + async def async_set_variable_legacy_service(call): + """Handle calls to the set_variable legacy service.""" - name = variable_config.get(CONF_NAME) - value = variable_config.get(CONF_VALUE) - attributes = variable_config.get(CONF_ATTRIBUTES) - restore = variable_config.get(CONF_RESTORE, False) - force_update = variable_config.get(CONF_FORCE_UPDATE, False) - domain = variable_config.get(CONF_DOMAIN, DOMAIN) + ENTITY_ID_FORMAT = Platform.SENSOR + ".{}" - entities.append( - Variable( - variable_id, name, value, attributes, restore, force_update, domain - ) - ) + # _LOGGER.debug("[async_set_variable_legacy_service] call: " + str(call)) - async def async_set_variable_service(call): - """Handle calls to the set_variable service.""" entity_id = ENTITY_ID_FORMAT.format(call.data.get(ATTR_VARIABLE)) - entity = component.get_entity(entity_id) - - if entity: - await entity.async_set_variable( - call.data.get(ATTR_VALUE), - call.data.get(ATTR_ATTRIBUTES), - call.data.get(ATTR_REPLACE_ATTRIBUTES, False), + # _LOGGER.debug("[async_set_variable_legacy_service] entity_id: " + str(entity_id)) + entity_registry = er.async_get(hass) + entity = entity_registry.async_get(entity_id) + + # _LOGGER.debug("[async_set_variable_legacy_service] entity: " + str(entity)) + if entity and entity.platform == DOMAIN: + _LOGGER.debug("[async_set_variable_legacy_service] Updating variable") + pre_state = hass.states.get(entity_id=entity_id) + pre_attr = hass.states.get(entity_id=entity_id).attributes + _LOGGER.debug( + f"[async_set_variable_legacy_service] Previous state: {pre_state.as_dict()}" + ) + _LOGGER.debug( + f"[async_set_variable_legacy_service] Previous attr: {pre_attr}" + ) + if not call.data.get(ATTR_REPLACE_ATTRIBUTES, False): + if call.data.get(ATTR_ATTRIBUTES): + new_attr = pre_attr | call.data.get(ATTR_ATTRIBUTES) + else: + new_attr = pre_attr + else: + new_attr = call.data.get(ATTR_ATTRIBUTES) + _LOGGER.debug( + f"[async_set_variable_legacy_service] Updated attr: {new_attr}" + ) + hass.states.async_set( + entity_id=entity_id, + new_state=call.data.get(ATTR_VALUE), + attributes=new_attr, + ) + _LOGGER.debug( + f"[async_set_variable_legacy_service] Post state: " + f"{hass.states.get(entity_id=entity_id).as_dict()}" ) else: - _LOGGER.warning("Failed to set unknown variable: %s", entity_id) + _LOGGER.warning( + f"variable.set_variable Service Failed. Unknown Variable: {entity_id}" + ) - async def async_set_entity_service(call): - """Handle calls to the set_entity service.""" + async def async_set_entity_legacy_service(call): + """Handle calls to the set_entity legacy service.""" - entity_id: str = call.data.get(ATTR_ENTITY) - state_value = call.data.get(ATTR_VALUE) - attributes = call.data.get(ATTR_ATTRIBUTES, {}) - replace_attributes = call.data.get(ATTR_REPLACE_ATTRIBUTES, False) + # _LOGGER.debug(f"[async_set_entity_legacy_service] call: {call}") - if replace_attributes: - updated_attributes = attributes - else: - cur_state = hass.states.get(entity_id) - if cur_state is None or cur_state.attributes is None: - updated_attributes = attributes + entity_id: str = call.data.get(ATTR_ENTITY) + # _LOGGER.debug(f"[async_set_entity_legacy_service] entity_id: {entity_id}") + entity_registry = er.async_get(hass) + entity = entity_registry.async_get(entity_id) + + # _LOGGER.debug(f"[async_set_entity_legacy_service] entity: {entity}") + if entity and entity.platform == DOMAIN: + _LOGGER.debug("[async_set_entity_legacy_service] Updating variable") + pre_state = hass.states.get(entity_id=entity_id) + pre_attr = hass.states.get(entity_id=entity_id).attributes + _LOGGER.debug( + f"[async_set_entity_legacy_service] Previous state: " + f"{pre_state.as_dict()}" + ) + _LOGGER.debug( + f"[async_set_entity_legacy_service] Previous attr: {pre_attr}" + ) + if not call.data.get(ATTR_REPLACE_ATTRIBUTES, False): + if call.data.get(ATTR_ATTRIBUTES): + new_attr = pre_attr | call.data.get(ATTR_ATTRIBUTES) + else: + new_attr = pre_attr else: - updated_attributes = dict(cur_state.attributes) - updated_attributes.update(attributes) - - hass.states.async_set(entity_id, state_value, updated_attributes) + new_attr = call.data.get(ATTR_ATTRIBUTES) + _LOGGER.debug(f"[async_set_entity_legacy_service] Updated attr: {new_attr}") + hass.states.async_set( + entity_id=entity_id, + new_state=call.data.get(ATTR_VALUE), + attributes=new_attr, + ) + _LOGGER.debug( + f"[async_set_entity_legacy_service] Post state: " + f"{hass.states.get(entity_id=entity_id).as_dict()}" + ) + else: + _LOGGER.warning( + f"variable.set_entity Service Failed. Unknown Variable: {entity_id}" + ) hass.services.async_register( DOMAIN, - SERVICE_SET_VARIABLE, - async_set_variable_service, - schema=SERVICE_SET_VARIABLE_SCHEMA, + SERVICE_SET_VARIABLE_LEGACY, + async_set_variable_legacy_service, + schema=SERVICE_SET_VARIABLE_LEGACY_SCHEMA, ) + hass.services.async_register( DOMAIN, - SERVICE_SET_ENTITY, - async_set_entity_service, - schema=SERVICE_SET_ENTITY_SCHEMA, + SERVICE_SET_ENTITY_LEGACY, + async_set_entity_legacy_service, + schema=SERVICE_SET_ENTITY_LEGACY_SCHEMA, ) - await component.async_add_entities(entities) + variables = json.loads(json.dumps(config.get(DOMAIN, {}))) + + for var, var_fields in variables.items(): + + if var is not None: + _LOGGER.debug(f"[YAML] variable_id: {var}") + _LOGGER.debug(f"[YAML] var_fields: {var_fields}") + + for key_empty, var_empty in var_fields.copy().items(): + if var_empty is None: + var_fields.pop(key_empty) + + attr = var_fields.get(CONF_ATTRIBUTES, {}) + icon = attr.pop(CONF_ICON, None) + name = var_fields.get(CONF_NAME, attr.pop(CONF_FRIENDLY_NAME, None)) + attr.pop(CONF_FRIENDLY_NAME, None) + + if var not in { + entry.data.get(CONF_VARIABLE_ID) + for entry in hass.config_entries.async_entries(DOMAIN) + }: + _LOGGER.warning(f"[YAML Import] Creating New Sensor Variable: {var}") + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_ENTITY_PLATFORM: Platform.SENSOR, + CONF_VARIABLE_ID: var, + CONF_NAME: name, + CONF_VALUE: var_fields.get(CONF_VALUE), + CONF_RESTORE: var_fields.get(CONF_RESTORE), + CONF_FORCE_UPDATE: var_fields.get(CONF_FORCE_UPDATE), + CONF_ATTRIBUTES: attr, + CONF_ICON: icon, + }, + ) + ) + else: + _LOGGER.info(f"[YAML Update] Updating Existing Sensor Variable: {var}") + + entry_id = None + for ent in hass.config_entries.async_entries(DOMAIN): + if var == ent.data.get(CONF_VARIABLE_ID): + entry_id = ent.entry_id + break + _LOGGER.debug(f"[YAML Update] entry_id: {entry_id}") + if entry_id: + entry = ent + _LOGGER.debug(f"[YAML Update] entry before: {entry.as_dict()}") + + for m in dict(entry.data).keys(): + var_fields.setdefault(m, entry.data[m]) + _LOGGER.debug(f"[YAML Update] updated var_fields: {var_fields}") + entry.options = {} + hass.config_entries.async_update_entry( + entry, data=var_fields, options=entry.options + ) + + hass.config_entries.async_reload(entry_id) + + else: + _LOGGER.error( + f"YAML Update Error. Could not find entry_id for: {var}" + ) + return True -class Variable(RestoreEntity): - """Representation of a variable.""" - - def __init__( - self, variable_id, name, value, attributes, restore, force_update, domain - ): - """Initialize a variable.""" - - self.entity_id = get_entity_id_format(domain).format(variable_id) - self._name = name - self._value = value - self._attributes = attributes - self._restore = restore - self._force_update = force_update - - async def async_added_to_hass(self): - """Run when entity about to be added.""" - await super().async_added_to_hass() - if self._restore is True: - # If variable state have been saved. - state = await self.async_get_last_state() - if state: - # restore state - self._value = state.state - # restore value - self._attributes = state.attributes - - @property - def should_poll(self): - """If entity should be polled.""" - return False - - @property - def name(self): - """Return the name of the variable.""" - return self._name - - @property - def icon(self): - """Return the icon to be used for this entity.""" - if self._attributes is not None: - return self._attributes.get(ATTR_ICON) - return None - - @property - def state(self): - """Return the state of the component.""" - return self._value - - @property - def state_attributes(self): - """Return the state attributes.""" - return self._attributes - - @property - def force_update(self) -> bool: - """Force an update.""" - return self._force_update - - @property - def unique_id(self): - """Make UID""" - return None if self._name is None else "variable_" + self._name - - async def async_set_variable( - self, - value, - attributes, - replace_attributes, - ): - """Update variable.""" - updated_attributes = None - updated_value = None - - if not replace_attributes and self._attributes is not None: - updated_attributes = dict(self._attributes) - - if attributes is not None: - if updated_attributes is not None: - updated_attributes.update(attributes) - else: - updated_attributes = attributes +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up from a config entry.""" - if value is not None: - updated_value = value + entry.options = {} + _LOGGER.debug(f"[init async_setup_entry] entry: {entry.data}") + hass.data.setdefault(DOMAIN, {}) + hass_data = dict(entry.data) + hass.data[DOMAIN][entry.entry_id] = hass_data + if hass_data.get(CONF_ENTITY_PLATFORM) in PLATFORMS: + await hass.config_entries.async_forward_entry_setups( + entry, [hass_data.get(CONF_ENTITY_PLATFORM)] + ) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + + _LOGGER.info(f"Unloading: {entry.data}") + hass_data = dict(entry.data) + unload_ok = False + if hass_data.get(CONF_ENTITY_PLATFORM) in PLATFORMS: + unload_ok = await hass.config_entries.async_unload_platforms( + entry, [hass_data.get(CONF_ENTITY_PLATFORM)] + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) - self._attributes = updated_attributes + return unload_ok - if updated_value is not None: - self._value = updated_value - await self.async_update_ha_state() +# async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: +# """Handle options update.""" +# +# _LOGGER.debug(f"[init update_listener] entry: {entry.as_dict()}") +# await hass.config_entries.async_reload(entry.entry_id) diff --git a/home-assistant/config/custom_components/variable/binary_sensor.py b/home-assistant/config/custom_components/variable/binary_sensor.py new file mode 100644 index 000000000..d3a72098e --- /dev/null +++ b/home-assistant/config/custom_components/variable/binary_sensor.py @@ -0,0 +1,324 @@ +from collections.abc import MutableMapping +import copy +import logging + +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.recorder import DATA_INSTANCE as RECORDER_INSTANCE +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ATTR_ICON, + CONF_DEVICE_CLASS, + CONF_ICON, + CONF_NAME, + STATE_OFF, + STATE_ON, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_platform, selector +from homeassistant.helpers.entity import generate_entity_id +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.util import slugify +import voluptuous as vol + +from .const import ( + ATTR_ATTRIBUTES, + ATTR_REPLACE_ATTRIBUTES, + ATTR_VALUE, + CONF_ATTRIBUTES, + CONF_EXCLUDE_FROM_RECORDER, + CONF_FORCE_UPDATE, + CONF_RESTORE, + CONF_VALUE, + CONF_VARIABLE_ID, + CONF_YAML_VARIABLE, + DEFAULT_EXCLUDE_FROM_RECORDER, + DEFAULT_FORCE_UPDATE, + DEFAULT_ICON, + DEFAULT_REPLACE_ATTRIBUTES, + DEFAULT_RESTORE, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +PLATFORM = Platform.BINARY_SENSOR +ENTITY_ID_FORMAT = PLATFORM + ".{}" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_VARIABLE_ID): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ICON, default=DEFAULT_ICON): cv.string, + vol.Optional(CONF_VALUE): cv.boolean, + vol.Optional(CONF_ATTRIBUTES): dict, + vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): cv.boolean, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + vol.Optional( + CONF_EXCLUDE_FROM_RECORDER, default=DEFAULT_EXCLUDE_FROM_RECORDER + ): cv.boolean, + } +) + +SERVICE_UPDATE_VARIABLE = "update_" + PLATFORM + +VARIABLE_ATTR_SETTINGS = {ATTR_FRIENDLY_NAME: "_attr_name", ATTR_ICON: "_attr_icon"} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities, +) -> None: + + """Setup the Binary Sensor Variable entity with a config_entry (config_flow).""" + + config_entry.options = {} + platform = entity_platform.async_get_current_platform() + + platform.async_register_entity_service( + SERVICE_UPDATE_VARIABLE, + { + vol.Optional(CONF_VALUE): selector.SelectSelector( + selector.SelectSelectorConfig( + options=["None", "true", "false"], + translation_key="boolean_options", + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.LIST, + ) + ), + vol.Optional(ATTR_ATTRIBUTES): dict, + vol.Optional( + ATTR_REPLACE_ATTRIBUTES, default=DEFAULT_REPLACE_ATTRIBUTES + ): cv.boolean, + }, + "async_update_variable", + ) + + config = hass.data.get(DOMAIN).get(config_entry.entry_id) + unique_id = config_entry.entry_id + # _LOGGER.debug(f"[async_setup_entry] config_entry: {config_entry.as_dict()}") + # _LOGGER.debug(f"[async_setup_entry] config: {config}") + # _LOGGER.debug(f"[async_setup_entry] unique_id: {unique_id}") + + async_add_entities([Variable(hass, config, config_entry, unique_id)]) + + return True + + +class Variable(BinarySensorEntity, RestoreEntity): + """Representation of a Binary Sensor Variable.""" + + def __init__( + self, + hass, + config, + config_entry, + unique_id, + ): + """Initialize a Binary Sensor Variable.""" + _LOGGER.debug( + f"({config.get(CONF_NAME, config.get(CONF_VARIABLE_ID))}) [init] config: {config}" + ) + if config.get(CONF_VALUE) is None or ( + isinstance(config.get(CONF_VALUE), str) + and config.get(CONF_VALUE).lower() in ["", "none", "unknown", "unavailable"] + ): + self._attr_is_on = None + elif isinstance(config.get(CONF_VALUE), str): + if config.get(CONF_VALUE).lower() in ["true", "1", "t", "y", "yes", "on"]: + self._attr_is_on = True + else: + self._attr_is_on = False + else: + self._attr_is_on = config.get(CONF_VALUE) + self._hass = hass + self._config = config + self._config_entry = config_entry + self._attr_has_entity_name = True + self._variable_id = slugify(config.get(CONF_VARIABLE_ID).lower()) + self._attr_unique_id = unique_id + if config.get(CONF_NAME) is not None: + self._attr_name = config.get(CONF_NAME) + else: + self._attr_name = config.get(CONF_VARIABLE_ID) + self._attr_icon = config.get(CONF_ICON) + self._attr_device_class = config.get(CONF_DEVICE_CLASS) + self._restore = config.get(CONF_RESTORE) + self._force_update = config.get(CONF_FORCE_UPDATE) + self._yaml_variable = config.get(CONF_YAML_VARIABLE) + self._exclude_from_recorder = config.get(CONF_EXCLUDE_FROM_RECORDER) + if ( + config.get(CONF_ATTRIBUTES) is not None + and config.get(CONF_ATTRIBUTES) + and isinstance(config.get(CONF_ATTRIBUTES), MutableMapping) + ): + self._attr_extra_state_attributes = self._update_attr_settings( + config.get(CONF_ATTRIBUTES) + ) + else: + self._attr_extra_state_attributes = None + self.entity_id = generate_entity_id( + ENTITY_ID_FORMAT, self._variable_id, hass=self._hass + ) + if self._exclude_from_recorder: + self.disable_recorder() + + def disable_recorder(self): + if RECORDER_INSTANCE in self._hass.data: + ha_history_recorder = self._hass.data[RECORDER_INSTANCE] + _LOGGER.info(f"({self._attr_name}) [disable_recorder] Disabling Recorder") + if self.entity_id: + try: + ha_history_recorder.entity_filter._exclude_e.add(self.entity_id) + except AttributeError: + pass + else: + _LOGGER.debug( + f"({self._attr_name}) [disable_recorder] _exclude_e: {ha_history_recorder.entity_filter._exclude_e}" + ) + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + if self._restore is True: + _LOGGER.info(f"({self._attr_name}) Restoring after Reboot") + state = await self.async_get_last_state() + if state: + _LOGGER.debug(f"({self._attr_name}) Restored state: {state.as_dict()}") + if ( + hasattr(state, "attributes") + and state.attributes + and isinstance(state.attributes, MutableMapping) + ): + self._attr_extra_state_attributes = self._update_attr_settings( + state.attributes.copy() + ) + if hasattr(state, "state"): + if state.state is None or ( + isinstance(state.state, str) + and state.state.lower() + in ["", "none", "unknown", "unavailable"] + ): + self._attr_is_on = None + elif state.state == STATE_OFF: + self._attr_is_on = False + elif state.state == STATE_ON: + self._attr_is_on = True + else: + self._attr_is_on = state.state + else: + self._attr_is_on = None + + async def async_will_remove_from_hass(self) -> None: + """Run when entity will be removed from hass.""" + if RECORDER_INSTANCE in self._hass.data: + ha_history_recorder = self._hass.data[RECORDER_INSTANCE] + if self.entity_id: + try: + ha_history_recorder.entity_filter._exclude_e.discard(self.entity_id) + except AttributeError: + pass + else: + _LOGGER.debug( + f"({self._attr_name}) Removing entity exclusion from recorder: {self.entity_id}" + ) + + @property + def should_poll(self): + """If entity should be polled.""" + return False + + @property + def force_update(self) -> bool: + """Force update status of the entity.""" + return self._force_update + + def _update_attr_settings(self, new_attributes=None): + if new_attributes is not None: + if isinstance(new_attributes, MutableMapping): + attributes = copy.deepcopy(new_attributes) + for attrib, setting in VARIABLE_ATTR_SETTINGS.items(): + if attrib in attributes.keys(): + _LOGGER.debug( + f"({self._attr_name}) [update_attr_settings] attrib: {attrib} / setting: {setting} / value: {attributes.get(attrib)}" + ) + setattr(self, setting, attributes.pop(attrib, None)) + return copy.deepcopy(attributes) + else: + _LOGGER.error( + f"({self._attr_name}) AttributeError: Attributes must be a dictionary: {new_attributes}" + ) + return new_attributes + else: + return None + + async def async_update_variable(self, **kwargs) -> None: + """Update Binary Sensor Variable.""" + + updated_attributes = None + + replace_attributes = kwargs.get(ATTR_REPLACE_ATTRIBUTES, False) + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] Replace Attributes: {replace_attributes}" + ) + + if ( + not replace_attributes + and hasattr(self, "_attr_extra_state_attributes") + and self._attr_extra_state_attributes is not None + ): + updated_attributes = copy.deepcopy(self._attr_extra_state_attributes) + + attributes = kwargs.get(ATTR_ATTRIBUTES) + if attributes is not None: + if isinstance(attributes, MutableMapping): + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] New Attributes: {attributes}" + ) + extra_attributes = self._update_attr_settings(attributes) + if updated_attributes is not None: + updated_attributes.update(extra_attributes) + else: + updated_attributes = extra_attributes + else: + _LOGGER.error( + f"({self._attr_name}) AttributeError: Attributes must be a dictionary: {attributes}" + ) + + if updated_attributes is not None: + self._attr_extra_state_attributes = copy.deepcopy(updated_attributes) + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] Final Attributes: {updated_attributes}" + ) + else: + self._attr_extra_state_attributes = None + + if ATTR_VALUE in kwargs: + if kwargs.get(ATTR_VALUE) is None or ( + isinstance(kwargs.get(ATTR_VALUE), str) + and kwargs.get(ATTR_VALUE).lower() + in ["", "none", "unknown", "unavailable"] + ): + self._attr_is_on = None + elif isinstance(kwargs.get(ATTR_VALUE), str): + if kwargs.get(ATTR_VALUE).lower() in [ + "true", + "1", + "t", + "y", + "yes", + "on", + ]: + self._attr_is_on = True + else: + self._attr_is_on = False + else: + self._attr_is_on = kwargs.get(ATTR_VALUE) + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] New Value: {self._attr_is_on}" + ) + + self.async_write_ha_state() diff --git a/home-assistant/config/custom_components/variable/config_flow.py b/home-assistant/config/custom_components/variable/config_flow.py new file mode 100644 index 000000000..4ebeba04b --- /dev/null +++ b/home-assistant/config/custom_components/variable/config_flow.py @@ -0,0 +1,882 @@ +from __future__ import annotations + +from enum import Enum +import logging +from typing import Any + +from homeassistant import config_entries +from homeassistant.components import binary_sensor, sensor +from homeassistant.const import ( + CONF_DEVICE_CLASS, + CONF_ICON, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + Platform, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import selector +import homeassistant.helpers.config_validation as cv +from iso4217 import Currency +import voluptuous as vol + +from .const import ( + CONF_ATTRIBUTES, + CONF_ENTITY_PLATFORM, + CONF_EXCLUDE_FROM_RECORDER, + CONF_FORCE_UPDATE, + CONF_RESTORE, + CONF_VALUE, + CONF_VALUE_TYPE, + CONF_VARIABLE_ID, + CONF_YAML_VARIABLE, + DEFAULT_EXCLUDE_FROM_RECORDER, + DEFAULT_FORCE_UPDATE, + DEFAULT_ICON, + DEFAULT_RESTORE, + DOMAIN, + PLATFORMS, +) +from .helpers import value_to_type + +_LOGGER = logging.getLogger(__name__) + +COMPONENT_CONFIG_URL = "https://github.com/Wibias/hass-variables" + +# Note the input displayed to the user will be translated. See the +# translations/.json file and strings.json. See here for further information: +# https://developers.home-assistant.io/docs/config_entries_config_flow_handler/#translations + +SENSOR_DEVICE_CLASS_SELECT_LIST = [] +SENSOR_DEVICE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label="None", value="None") +) +for el in sensor.SensorDeviceClass: + if el != sensor.SensorDeviceClass.ENUM: + SENSOR_DEVICE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el.name), value=str(el.value)) + ) + +BINARY_SENSOR_DEVICE_CLASS_SELECT_LIST = [] +BINARY_SENSOR_DEVICE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label="None", value="None") +) +for el in binary_sensor.BinarySensorDeviceClass: + BINARY_SENSOR_DEVICE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el.name), value=str(el.value)) + ) + +ADD_SENSOR_SCHEMA = vol.Schema( + { + vol.Required(CONF_VARIABLE_ID): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ICON, default=DEFAULT_ICON): selector.IconSelector( + selector.IconSelectorConfig() + ), + vol.Optional(CONF_DEVICE_CLASS): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SENSOR_DEVICE_CLASS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): selector.BooleanSelector( + selector.BooleanSelectorConfig() + ), + vol.Optional( + CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_EXCLUDE_FROM_RECORDER, default=DEFAULT_EXCLUDE_FROM_RECORDER + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + } +) + +ADD_BINARY_SENSOR_SCHEMA = vol.Schema( + { + vol.Required(CONF_VARIABLE_ID): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ICON, default=DEFAULT_ICON): selector.IconSelector( + selector.IconSelectorConfig() + ), + vol.Optional(CONF_VALUE, default="None"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=["None", "true", "false"], + translation_key="boolean_options", + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.LIST, + ) + ), + vol.Optional(CONF_ATTRIBUTES): selector.ObjectSelector( + selector.ObjectSelectorConfig() + ), + vol.Optional(CONF_DEVICE_CLASS): selector.SelectSelector( + selector.SelectSelectorConfig( + options=BINARY_SENSOR_DEVICE_CLASS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): selector.BooleanSelector( + selector.BooleanSelectorConfig() + ), + vol.Optional( + CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_EXCLUDE_FROM_RECORDER, default=DEFAULT_EXCLUDE_FROM_RECORDER + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + } +) + + +async def validate_sensor_input(hass: HomeAssistant, data: dict) -> dict[str, Any]: + """Validate the user input""" + + # _LOGGER.debug(f"[config_flow validate_sensor_input] data: {data}") + if data.get(CONF_NAME): + return {"title": data.get(CONF_NAME)} + else: + return {"title": data.get(CONF_VARIABLE_ID, "")} + + +class VariableConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + + VERSION = 1 + # Connection classes in homeassistant/config_entries.py are now deprecated + + async def async_step_user(self, user_input=None) -> FlowResult: + """Handle the initial step.""" + + return self.async_show_menu( + step_id="user", + menu_options=["add_" + p for p in PLATFORMS], + ) + + async def async_step_add_sensor( + self, user_input=None, errors=None, yaml_variable=False + ): + if user_input is not None: + user_input.update({CONF_ENTITY_PLATFORM: Platform.SENSOR}) + user_input.update({CONF_YAML_VARIABLE: yaml_variable}) + _LOGGER.debug(f"[New Sensor Variable] page_1_input: {user_input}") + self.add_sensor_input = user_input + return await self.async_step_sensor_page_2() + + # If there is no user input or there were errors, show the form again, including any errors that were found with the input. + return self.async_show_form( + step_id="add_sensor", + data_schema=ADD_SENSOR_SCHEMA, + errors=errors, + description_placeholders={ + "component_config_url": COMPONENT_CONFIG_URL, + }, + ) + + async def async_step_sensor_page_2(self, user_input=None): + errors = {} + if user_input is not None: + _LOGGER.debug(f"[New Sensor Page 2] page_1_input: {self.add_sensor_input}") + _LOGGER.debug(f"[New Sensor Page 2] page_2_input: {user_input}") + + try: + newval = value_to_type( + user_input.get(CONF_VALUE), + self.add_sensor_input.get(CONF_VALUE_TYPE), + ) + except ValueError: + errors["base"] = "invalid_value_type" + else: + user_input[CONF_VALUE] = newval + + _LOGGER.debug( + f"[New Sensor Page 2] value_type: {self.add_sensor_input.get(CONF_VALUE_TYPE)}" + ) + _LOGGER.debug( + f"[New Sensor Page 2] type of value: {type(user_input.get(CONF_VALUE))}" + ) + + if not errors: + if self.add_sensor_input is not None and self.add_sensor_input: + user_input.update(self.add_sensor_input) + if user_input is not None: + for k, v in list(user_input.items()): + if v is None or (isinstance(v, str) and v.lower() == "none"): + user_input.pop(k, None) + _LOGGER.debug(f"[New Sensor Page 2] Final user_input: {user_input}") + info = await validate_sensor_input(self.hass, user_input) + return self.async_create_entry( + title=info.get("title", ""), data=user_input + ) + + _LOGGER.debug(f"[New Sensor Page 2] Initial user_input: {user_input}") + _LOGGER.debug( + f"[New Sensor Page 2] device_class: {self.add_sensor_input.get(CONF_DEVICE_CLASS)}" + ) + + SENSOR_PAGE_2_SCHEMA = self.build_add_sensor_page_2() + + if self.add_sensor_input.get(CONF_NAME) is None or self.add_sensor_input.get( + CONF_NAME + ) == self.add_sensor_input.get(CONF_VARIABLE_ID): + disp_name = self.add_sensor_input.get(CONF_VARIABLE_ID) + else: + disp_name = f"{self.add_sensor_input.get(CONF_NAME)} ({self.add_sensor_input.get(CONF_VARIABLE_ID)})" + # If there is no user input or there were errors, show the form again, including any errors that were found with the input. + return self.async_show_form( + step_id="sensor_page_2", + data_schema=SENSOR_PAGE_2_SCHEMA, + errors=errors, + description_placeholders={ + "device_class": self.add_sensor_input.get(CONF_DEVICE_CLASS, "None"), + "disp_name": disp_name, + "value_type": self.add_sensor_input.get(CONF_VALUE_TYPE, "None"), + }, + ) + + def build_add_sensor_page_2(self): + SENSOR_STATE_CLASS_SELECT_LIST = [] + SENSOR_STATE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label="None", value="None") + ) + SENSOR_UNITS_SELECT_LIST = [] + SENSOR_UNITS_SELECT_LIST.append( + selector.SelectOptionDict(label="None", value="None") + ) + + SENSOR_PAGE_2_SCHEMA = vol.Schema({}) + if ( + self.add_sensor_input.get(CONF_DEVICE_CLASS) is not None + and self.add_sensor_input.get(CONF_DEVICE_CLASS).lower() != "none" + ): + for el in sensor.DEVICE_CLASS_STATE_CLASSES.get( + self.add_sensor_input.get(CONF_DEVICE_CLASS), Enum + ): + SENSOR_STATE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el.name), value=str(el.value)) + ) + if ( + self.add_sensor_input.get(CONF_DEVICE_CLASS) + == sensor.SensorDeviceClass.MONETARY + ): + for el in Currency: + if el.code not in ["XTS", "XXX"]: + SENSOR_UNITS_SELECT_LIST.append( + selector.SelectOptionDict( + label=f"{el.currency_name} [{el.code}]", + value=str(el.code), + ) + ) + else: + for el in sensor.DEVICE_CLASS_UNITS.get( + self.add_sensor_input.get(CONF_DEVICE_CLASS), [] + ): + if el is not None and el != "None": + SENSOR_UNITS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el), value=str(el)) + ) + if self.add_sensor_input.get(CONF_DEVICE_CLASS) in [ + sensor.SensorDeviceClass.DATE + ]: + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_VALUE): selector.DateSelector( + selector.DateSelectorConfig() + ) + } + ) + value_type = "date" + elif self.add_sensor_input.get(CONF_DEVICE_CLASS) in [ + sensor.SensorDeviceClass.TIMESTAMP + ]: + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_VALUE): selector.DateTimeSelector( + selector.DateTimeSelectorConfig() + ) + } + ) + value_type = "datetime" + else: + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_VALUE): selector.TextSelector( + selector.TextSelectorConfig() + ) + } + ) + value_type = "number" + else: + for el in sensor.SensorStateClass: + SENSOR_STATE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el.name), value=str(el.value)) + ) + + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_VALUE): selector.TextSelector( + selector.TextSelectorConfig() + ) + } + ) + value_type = "string" + + # _LOGGER.debug( + # f"[New Sensor Page 2] SENSOR_STATE_CLASS_SELECT_LIST: {SENSOR_STATE_CLASS_SELECT_LIST}" + # ) + # _LOGGER.debug( + # f"[New Sensor Page 2] SENSOR_UNITS_SELECT_LIST: {SENSOR_UNITS_SELECT_LIST}" + # ) + + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_ATTRIBUTES): selector.ObjectSelector( + selector.ObjectSelectorConfig() + ) + } + ) + if len(SENSOR_STATE_CLASS_SELECT_LIST) > 1: + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(sensor.CONF_STATE_CLASS): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SENSOR_STATE_CLASS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ) + + if len(SENSOR_UNITS_SELECT_LIST) > 1: + SENSOR_PAGE_2_SCHEMA = SENSOR_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_UNIT_OF_MEASUREMENT): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SENSOR_UNITS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ) + + _LOGGER.debug(f"[New Sensor Page 2] value_type: {value_type}") + self.add_sensor_input.update({CONF_VALUE_TYPE: value_type}) + return SENSOR_PAGE_2_SCHEMA + + async def async_step_add_binary_sensor( + self, user_input=None, errors=None, yaml_variable=False + ): + if user_input is not None: + + try: + user_input.update({CONF_ENTITY_PLATFORM: Platform.BINARY_SENSOR}) + user_input.update({CONF_YAML_VARIABLE: yaml_variable}) + info = await validate_sensor_input(self.hass, user_input) + _LOGGER.debug(f"[New Binary Sensor] updated user_input: {user_input}") + return self.async_create_entry( + title=info.get("title", ""), data=user_input + ) + except Exception as err: + _LOGGER.exception( + f"[config_flow async_step_add_binary_sensor] Unexpected exception: {err}" + ) + errors["base"] = "unknown" + + # If there is no user input or there were errors, show the form again, including any errors that were found with the input. + return self.async_show_form( + step_id="add_binary_sensor", + data_schema=ADD_BINARY_SENSOR_SCHEMA, + errors=errors, + description_placeholders={ + "component_config_url": COMPONENT_CONFIG_URL, + }, + ) + + # this is run to import the configuration.yaml parameters\ + async def async_step_import(self, import_config=None) -> FlowResult: + """Import a config entry from configuration.yaml.""" + + # _LOGGER.debug(f"[async_step_import] import_config: {import_config)}") + return await self.async_step_add_sensor( + user_input=import_config, yaml_variable=True + ) + + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ): + """Get the options flow.""" + return VariableOptionsFlowHandler(config_entry) + + +class VariableOptionsFlowHandler(config_entries.OptionsFlow): + """Options for the component.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Init object.""" + self.config_entry = config_entry + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + + # _LOGGER.debug("Starting Options") + # _LOGGER.debug(f"[Options] initial config: {self.config_entry.data)}") + # _LOGGER.debug(f"[Options] initial options: {self.config_entry.options)}") + + if not self.config_entry.data.get(CONF_YAML_VARIABLE): + if self.config_entry.data.get(CONF_ENTITY_PLATFORM) in PLATFORMS and ( + new_func := getattr( + self, + "async_step_" + + self.config_entry.data.get(CONF_ENTITY_PLATFORM) + + "_options", + False, + ) + ): + return await new_func() + else: + _LOGGER.debug("No Options for YAML Created Variables") + return self.async_abort(reason="yaml_variable") + + async def async_step_sensor_options( + self, user_input=None, errors=None + ) -> FlowResult: + + if user_input is not None: + _LOGGER.debug(f"[Sensor Options Page 1] page_1_input: {user_input}") + self.sensor_options_page_1 = user_input + return await self.async_step_sensor_options_page_2() + + SENSOR_OPTIONS_PAGE_1_SCHEMA = self.build_sensor_options_page_1() + + if self.config_entry.data.get(CONF_NAME) is None or self.config_entry.data.get( + CONF_NAME + ) == self.config_entry.data.get(CONF_VARIABLE_ID): + disp_name = self.config_entry.data.get(CONF_VARIABLE_ID) + else: + disp_name = f"{self.config_entry.data.get(CONF_NAME)} ({self.config_entry.data.get(CONF_VARIABLE_ID)})" + + return self.async_show_form( + step_id="sensor_options", + data_schema=SENSOR_OPTIONS_PAGE_1_SCHEMA, + errors=errors, + description_placeholders={ + "component_config_url": COMPONENT_CONFIG_URL, + "disp_name": disp_name, + }, + ) + + def build_sensor_options_page_1(self): + return vol.Schema( + { + vol.Optional( + CONF_DEVICE_CLASS, + default=self.config_entry.data.get(CONF_DEVICE_CLASS, "None"), + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SENSOR_DEVICE_CLASS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Optional( + CONF_RESTORE, + default=self.config_entry.data.get(CONF_RESTORE, DEFAULT_RESTORE), + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_FORCE_UPDATE, + default=self.config_entry.data.get( + CONF_FORCE_UPDATE, DEFAULT_FORCE_UPDATE + ), + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_EXCLUDE_FROM_RECORDER, + default=self.config_entry.data.get( + CONF_EXCLUDE_FROM_RECORDER, DEFAULT_EXCLUDE_FROM_RECORDER + ), + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + } + ) + + async def async_step_sensor_options_page_2(self, user_input=None): + errors = {} + if user_input is not None: + _LOGGER.debug(f"[Sensor Options Page 2] user_input: {user_input}") + try: + newval = value_to_type( + user_input.get(CONF_VALUE), + self.sensor_options_page_1.get(CONF_VALUE_TYPE), + ) + except ValueError: + errors["base"] = "invalid_value_type" + else: + user_input[CONF_VALUE] = newval + + _LOGGER.debug( + f"[Sensor Options Page 2] value_type: {self.sensor_options_page_1.get(CONF_VALUE_TYPE)}" + ) + _LOGGER.debug( + f"[Sensor Options Page 2] type of value: {type(user_input.get(CONF_VALUE))}" + ) + + if not errors: + if ( + self.sensor_options_page_1 is not None + and self.sensor_options_page_1 + ): + user_input.update(self.sensor_options_page_1) + for m in dict(self.config_entry.data).keys(): + user_input.setdefault(m, self.config_entry.data[m]) + if user_input is not None: + for k, v in list(user_input.items()): + if v is None or (isinstance(v, str) and v.lower() == "none"): + user_input.pop(k, None) + _LOGGER.debug(f"[Sensor Options Page 2] Final user_input: {user_input}") + self.config_entry.options = {} + + self.hass.config_entries.async_update_entry( + self.config_entry, + data=user_input, + options=self.config_entry.options, + ) + await self.hass.config_entries.async_reload(self.config_entry.entry_id) + return self.async_create_entry(title="", data=user_input) + + _LOGGER.debug(f"[Sensor Options Page 2] Initial user_input: {user_input}") + _LOGGER.debug( + f"[Sensor Options Page 2] device_class: {self.sensor_options_page_1.get(CONF_DEVICE_CLASS)}" + ) + + SENSOR_OPTIONS_PAGE_2_SCHEMA = self.build_sensor_options_page_2() + + if self.config_entry.data.get(CONF_NAME) is None or self.config_entry.data.get( + CONF_NAME + ) == self.config_entry.data.get(CONF_VARIABLE_ID): + disp_name = self.config_entry.data.get(CONF_VARIABLE_ID) + else: + disp_name = f"{self.config_entry.data.get(CONF_NAME)} ({self.config_entry.data.get(CONF_VARIABLE_ID)})" + + return self.async_show_form( + step_id="sensor_options_page_2", + data_schema=SENSOR_OPTIONS_PAGE_2_SCHEMA, + errors=errors, + description_placeholders={ + "disp_name": disp_name, + "value_type": self.sensor_options_page_1.get(CONF_VALUE_TYPE, "None"), + "device_class": self.sensor_options_page_1.get( + CONF_DEVICE_CLASS, "None" + ), + }, + ) + + def check_value_default(self, new_device_class): + val_default_value = None + if self.config_entry.data.get(CONF_VALUE) is None or ( + isinstance(self.config_entry.data.get(CONF_VALUE), str) + and self.config_entry.data.get(CONF_VALUE).lower() == "none" + ): + val_default = False + elif self.config_entry.data.get(CONF_DEVICE_CLASS) != new_device_class: + val_default = False + else: + val_default = True + val_default_value = self.config_entry.data.get(CONF_VALUE) + return val_default, val_default_value + + def build_sensor_options_page_2(self): + SENSOR_STATE_CLASS_SELECT_LIST = [] + SENSOR_STATE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label="None", value="None") + ) + SENSOR_UNITS_SELECT_LIST = [] + SENSOR_UNITS_SELECT_LIST.append( + selector.SelectOptionDict(label="None", value="None") + ) + + val_default, val_default_value = self.check_value_default( + self.sensor_options_page_1.get(CONF_DEVICE_CLASS) + ) + + SENSOR_OPTIONS_PAGE_2_SCHEMA = vol.Schema({}) + if ( + self.sensor_options_page_1.get(CONF_DEVICE_CLASS) is not None + and self.sensor_options_page_1.get(CONF_DEVICE_CLASS).lower() != "none" + ): + for el in sensor.DEVICE_CLASS_STATE_CLASSES.get( + self.sensor_options_page_1.get(CONF_DEVICE_CLASS), Enum + ): + SENSOR_STATE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el.name), value=str(el.value)) + ) + if ( + self.sensor_options_page_1.get(CONF_DEVICE_CLASS) + == sensor.SensorDeviceClass.MONETARY + ): + for el in Currency: + if el.code not in ["XTS", "XXX"]: + SENSOR_UNITS_SELECT_LIST.append( + selector.SelectOptionDict( + label=f"{el.currency_name} [{el.code}]", + value=str(el.code), + ) + ) + else: + for el in sensor.DEVICE_CLASS_UNITS.get( + self.sensor_options_page_1.get(CONF_DEVICE_CLASS), [] + ): + if el is not None and el != "None": + SENSOR_UNITS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el), value=str(el)) + ) + + if self.sensor_options_page_1.get(CONF_DEVICE_CLASS) in [ + sensor.SensorDeviceClass.DATE + ]: + value_type = "date" + if val_default: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + default=val_default_value, + ): selector.DateSelector(selector.DateSelectorConfig()) + } + ) + else: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + ): selector.DateSelector(selector.DateSelectorConfig()) + } + ) + + elif self.sensor_options_page_1.get(CONF_DEVICE_CLASS) in [ + sensor.SensorDeviceClass.TIMESTAMP + ]: + value_type = "datetime" + if val_default: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + default=val_default_value, + ): selector.DateTimeSelector( + selector.DateTimeSelectorConfig() + ) + } + ) + else: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional(CONF_VALUE,): selector.DateTimeSelector( + selector.DateTimeSelectorConfig() + ) + } + ) + else: + value_type = "number" + if val_default: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + default=str(val_default_value), + ): selector.TextSelector(selector.TextSelectorConfig()) + } + ) + else: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + ): selector.TextSelector(selector.TextSelectorConfig()) + } + ) + else: + for el in sensor.SensorStateClass: + SENSOR_STATE_CLASS_SELECT_LIST.append( + selector.SelectOptionDict(label=str(el.name), value=str(el.value)) + ) + value_type = "string" + if val_default: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + default=val_default_value, + ): selector.TextSelector(selector.TextSelectorConfig()) + } + ) + else: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_VALUE, + ): selector.TextSelector(selector.TextSelectorConfig()) + } + ) + + # _LOGGER.debug( + # f"[Sensor Options Page 2] SENSOR_STATE_CLASS_SELECT_LIST: {SENSOR_STATE_CLASS_SELECT_LIST}" + # ) + # _LOGGER.debug( + # f"[Sensor Options Page 2] SENSOR_UNITS_SELECT_LIST: {SENSOR_UNITS_SELECT_LIST}" + # ) + + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_ATTRIBUTES, default=self.config_entry.data.get(CONF_ATTRIBUTES) + ): selector.ObjectSelector(selector.ObjectSelectorConfig()) + } + ) + if len(SENSOR_STATE_CLASS_SELECT_LIST) > 1: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + sensor.CONF_STATE_CLASS, + default=self.config_entry.data.get( + sensor.CONF_STATE_CLASS, "None" + ) + if ( + self.config_entry.data.get(CONF_DEVICE_CLASS) + == self.sensor_options_page_1.get(CONF_DEVICE_CLASS) + ) + else "None", + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SENSOR_STATE_CLASS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ) + else: + self.sensor_options_page_1[sensor.CONF_STATE_CLASS] = None + + if len(SENSOR_UNITS_SELECT_LIST) > 1: + SENSOR_OPTIONS_PAGE_2_SCHEMA = SENSOR_OPTIONS_PAGE_2_SCHEMA.extend( + { + vol.Optional( + CONF_UNIT_OF_MEASUREMENT, + default=self.config_entry.data.get( + CONF_UNIT_OF_MEASUREMENT, "None" + ) + if ( + self.config_entry.data.get(CONF_DEVICE_CLASS) + == self.sensor_options_page_1.get(CONF_DEVICE_CLASS) + ) + else "None", + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=SENSOR_UNITS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ) + else: + self.sensor_options_page_1[CONF_UNIT_OF_MEASUREMENT] = None + + _LOGGER.debug(f"[Sensor Options Page 2] value_type: {value_type}") + self.sensor_options_page_1.update({CONF_VALUE_TYPE: value_type}) + return SENSOR_OPTIONS_PAGE_2_SCHEMA + + async def async_step_binary_sensor_options( + self, user_input=None, errors=None + ) -> FlowResult: + + if user_input is not None: + _LOGGER.debug(f"[Binary Sensor Options] user_input: {user_input}") + for m in dict(self.config_entry.data).keys(): + user_input.setdefault(m, self.config_entry.data[m]) + _LOGGER.debug(f"[Binary Sensor Options] updated user_input: {user_input}") + self.config_entry.options = {} + + self.hass.config_entries.async_update_entry( + self.config_entry, data=user_input, options=self.config_entry.options + ) + await self.hass.config_entries.async_reload(self.config_entry.entry_id) + return self.async_create_entry(title="", data=user_input) + + BINARY_SENSOR_OPTIONS_SCHEMA = vol.Schema( + { + vol.Optional( + CONF_VALUE, + default=self.config_entry.data.get(CONF_VALUE) + if self.config_entry.data.get(CONF_VALUE) is not None + else "None", + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=["None", "true", "false"], + translation_key="boolean_options", + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.LIST, + ) + ), + vol.Optional( + CONF_ATTRIBUTES, default=self.config_entry.data.get(CONF_ATTRIBUTES) + ): selector.ObjectSelector(selector.ObjectSelectorConfig()), + vol.Optional( + CONF_DEVICE_CLASS, + default=self.config_entry.data.get(CONF_DEVICE_CLASS, "None"), + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=BINARY_SENSOR_DEVICE_CLASS_SELECT_LIST, + multiple=False, + custom_value=False, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Optional( + CONF_RESTORE, + default=self.config_entry.data.get(CONF_RESTORE, DEFAULT_RESTORE), + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_FORCE_UPDATE, + default=self.config_entry.data.get( + CONF_FORCE_UPDATE, DEFAULT_FORCE_UPDATE + ), + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + vol.Optional( + CONF_EXCLUDE_FROM_RECORDER, + default=self.config_entry.data.get( + CONF_EXCLUDE_FROM_RECORDER, DEFAULT_EXCLUDE_FROM_RECORDER + ), + ): selector.BooleanSelector(selector.BooleanSelectorConfig()), + } + ) + + if self.config_entry.data.get(CONF_NAME) is None or self.config_entry.data.get( + CONF_NAME + ) == self.config_entry.data.get(CONF_VARIABLE_ID): + disp_name = self.config_entry.data.get(CONF_VARIABLE_ID) + else: + disp_name = f"{self.config_entry.data.get(CONF_NAME)} ({self.config_entry.data.get(CONF_VARIABLE_ID)})" + + return self.async_show_form( + step_id="binary_sensor_options", + data_schema=BINARY_SENSOR_OPTIONS_SCHEMA, + errors=errors, + description_placeholders={ + "component_config_url": COMPONENT_CONFIG_URL, + "disp_name": disp_name, + }, + ) diff --git a/home-assistant/config/custom_components/variable/const.py b/home-assistant/config/custom_components/variable/const.py new file mode 100644 index 000000000..1b2130f2b --- /dev/null +++ b/home-assistant/config/custom_components/variable/const.py @@ -0,0 +1,29 @@ +from homeassistant.const import Platform + +PLAFORM_NAME = "Variables+History" +DOMAIN = "variable" + +PLATFORMS: list[str] = [Platform.SENSOR, Platform.BINARY_SENSOR] + +# Defaults +DEFAULT_FORCE_UPDATE = False +DEFAULT_ICON = "mdi:variable" +DEFAULT_REPLACE_ATTRIBUTES = False +DEFAULT_RESTORE = True +DEFAULT_EXCLUDE_FROM_RECORDER = False + +CONF_ATTRIBUTES = "attributes" +CONF_ENTITY_PLATFORM = "entity_platform" +CONF_FORCE_UPDATE = "force_update" +CONF_RESTORE = "restore" +CONF_VALUE = "value" +CONF_VALUE_TYPE = "value_type" +CONF_VARIABLE_ID = "variable_id" +CONF_YAML_VARIABLE = "yaml_variable" +CONF_EXCLUDE_FROM_RECORDER = "exclude_from_recorder" + +ATTR_ATTRIBUTES = "attributes" +ATTR_ENTITY = "entity" +ATTR_REPLACE_ATTRIBUTES = "replace_attributes" +ATTR_VALUE = "value" +ATTR_VARIABLE = "variable" diff --git a/home-assistant/config/custom_components/variable/helpers.py b/home-assistant/config/custom_components/variable/helpers.py new file mode 100644 index 000000000..93149de76 --- /dev/null +++ b/home-assistant/config/custom_components/variable/helpers.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +import datetime +import logging + +_LOGGER = logging.getLogger(__name__) + + +def to_num(s): + try: + return int(s) + except ValueError: + try: + return float(s) + except ValueError: + return None + + +def value_to_type(init_val, dest_type): # noqa: C901 + + if init_val is None or ( + isinstance(init_val, str) + and init_val.lower() in ["", "none", "unknown", "unavailable"] + ): + _LOGGER.debug(f"[value_to_type] value: {init_val}, returning None") + return None + + _LOGGER.debug( + f"[value_to_type] initial value: {init_val}, initial type: {type(init_val)}, dest type: {dest_type}" + ) + if isinstance(init_val, str): + if dest_type is None or dest_type == "string": + _LOGGER.debug( + f"[value_to_type] return value: {init_val}, type: {type(init_val)}" + ) + return init_val + + elif dest_type == "date": + try: + value_date = datetime.date.fromisoformat(init_val) + except ValueError: + _LOGGER.debug( + f"Cannot convert string to {dest_type}: {init_val}, returning None" + ) + raise ValueError(f"Cannot convert string to {dest_type}: {init_val}") + return None + else: + _LOGGER.debug( + f"[value_to_type] return value: {value_date}, type: {type(value_date)}" + ) + return value_date + + elif dest_type == "datetime": + try: + value_datetime = datetime.datetime.fromisoformat(init_val) + except ValueError: + _LOGGER.debug( + f"Cannot convert string to {dest_type}: {init_val}, returning None" + ) + raise ValueError(f"Cannot convert string to {dest_type}: {init_val}") + return None + else: + _LOGGER.debug( + f"[value_to_type] return value: {value_datetime}, type: {type(value_datetime)}" + ) + return value_datetime + + elif dest_type == "number": + if (value_num := to_num(init_val)) is not None: + _LOGGER.debug( + f"[value_to_type] return value: {value_num}, type: {type(value_num)}" + ) + return value_num + else: + _LOGGER.debug( + f"Cannot convert string to {dest_type}: {init_val}, returning None" + ) + raise ValueError(f"Cannot convert string to {dest_type}: {init_val}") + else: + _LOGGER.debug(f"Invalid dest_type: {dest_type}, returning None") + raise ValueError(f"Invalid dest_type: {dest_type}") + return None + elif isinstance(init_val, int) or isinstance(init_val, float): + if dest_type is None or dest_type == "string": + _LOGGER.debug( + f"[value_to_type] return value: {str(init_val)}, type: {type(str(init_val))}" + ) + return str(init_val) + elif dest_type == "date": + try: + value_date = datetime.date.fromisoformat(str(init_val)) + except ValueError: + _LOGGER.debug( + f"Cannot convert number to {dest_type}: {init_val}, returning None" + ) + raise ValueError(f"Cannot convert number to {dest_type}: {init_val}") + return None + else: + _LOGGER.debug( + f"[value_to_type] return value: {value_date}, type: {type(value_date)}" + ) + return value_date + + elif dest_type == "datetime": + try: + value_datetime = datetime.datetime.fromisoformat(str(init_val)) + except ValueError: + _LOGGER.debug( + f"Cannot convert number to {dest_type}: {init_val}, returning None" + ) + raise ValueError(f"Cannot convert number to {dest_type}: {init_val}") + return None + else: + _LOGGER.debug( + f"[value_to_type] return value: {value_datetime}, type: {type(value_datetime)}" + ) + return value_datetime + elif dest_type == "number": + _LOGGER.debug( + f"[value_to_type] return value: {init_val}, type: {type(init_val)}" + ) + return init_val + else: + _LOGGER.debug(f"Invalid dest_type: {dest_type}, returning None") + raise ValueError(f"Invalid dest_type: {dest_type}") + return None + elif isinstance(init_val, datetime.date): + if dest_type is None or dest_type == "string": + _LOGGER.debug( + f"[value_to_type] return value: {init_val.isoformat()}, type: {type(init_val.isoformat())}" + ) + return init_val.isoformat() + elif dest_type == "date": + _LOGGER.debug( + f"[value_to_type] return value: {init_val}, type: {type(init_val)}" + ) + return init_val + elif dest_type == "datetime": + _LOGGER.debug( + f"[value_to_type] return value: {datetime.datetime.combine(init_val, datetime.time.min)}, " + + f"type: {type(datetime.datetime.combine(init_val, datetime.time.min))}" + ) + return datetime.datetime.combine(init_val, datetime.time.min) + elif dest_type == "number": + _LOGGER.debug( + f"[value_to_type] return value: {datetime.datetime.combine(init_val, datetime.time.min).timestamp()}, " + + f"type: {type(datetime.datetime.combine(init_val, datetime.time.min).timestamp())}" + ) + return datetime.datetime.combine(init_val, datetime.time.min).timestamp() + else: + _LOGGER.debug(f"Invalid dest_type: {dest_type}, returning None") + raise ValueError(f"Invalid dest_type: {dest_type}") + return None + elif isinstance(init_val, datetime.datetime): + if dest_type is None or dest_type == "string": + _LOGGER.debug( + f"[value_to_type] return value: {init_val.isoformat()}, type: {type(init_val.isoformat())}" + ) + return init_val.isoformat() + elif dest_type == "date": + _LOGGER.debug( + f"[value_to_type] return value: {init_val.date()}, type: {type(init_val.date())}" + ) + return init_val.date() + elif dest_type == "datetime": + _LOGGER.debug( + f"[value_to_type] return value: {init_val}, type: {type(init_val)}" + ) + return init_val + elif dest_type == "number": + _LOGGER.debug( + f"[value_to_type] return value: {init_val.timestamp()}, type: {type(init_val.timestamp())}" + ) + return init_val.timestamp() + else: + _LOGGER.debug(f"Invalid dest_type: {dest_type}, returning None") + raise ValueError(f"Invalid dest_type: {dest_type}") + return None + else: + _LOGGER.debug(f"Invalid initial type: {type(init_val)}, returning None") + raise ValueError(f"Invalid initial type: {type(init_val)}") + return None diff --git a/home-assistant/config/custom_components/variable/manifest.json b/home-assistant/config/custom_components/variable/manifest.json index 864d9b1b4..1eeeed4d8 100644 --- a/home-assistant/config/custom_components/variable/manifest.json +++ b/home-assistant/config/custom_components/variable/manifest.json @@ -1,11 +1,17 @@ { - "domain": "variable", - "name": "Variables+History", - "documentation": "https://github.com/Wibias/hass-variables", - "issue_tracker": "https://github.com/Wibias/hass-variables/issues", - "requirements": [], - "dependencies": [], - "codeowners": ["@rogro82", "@wibias"], - "iot_class": "local_push", - "version": "2.3.1" + "domain": "variable", + "name": "Variables+History", + "after_dependencies": ["recorder"], + "codeowners": [ + "@rogro82", + "@wibias", + "@Snuffy2" + ], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/Wibias/hass-variables", + "iot_class": "local_push", + "issue_tracker": "https://github.com/Wibias/hass-variables/issues", + "requirements": ["iso4217==1.11.20220401"], + "version": "3.3.0" } diff --git a/home-assistant/config/custom_components/variable/sensor.py b/home-assistant/config/custom_components/variable/sensor.py new file mode 100644 index 000000000..b710245c0 --- /dev/null +++ b/home-assistant/config/custom_components/variable/sensor.py @@ -0,0 +1,379 @@ +from collections.abc import MutableMapping +import copy +import logging + +from homeassistant.components.recorder import DATA_INSTANCE as RECORDER_INSTANCE +from homeassistant.components.sensor import ( + CONF_STATE_CLASS, + PLATFORM_SCHEMA, + RestoreSensor, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ATTR_ICON, + CONF_DEVICE_CLASS, + CONF_ICON, + CONF_NAME, + CONF_UNIT_OF_MEASUREMENT, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers.entity import generate_entity_id +from homeassistant.util import slugify +import voluptuous as vol + +from .const import ( + ATTR_ATTRIBUTES, + ATTR_REPLACE_ATTRIBUTES, + ATTR_VALUE, + CONF_ATTRIBUTES, + CONF_EXCLUDE_FROM_RECORDER, + CONF_FORCE_UPDATE, + CONF_RESTORE, + CONF_VALUE, + CONF_VALUE_TYPE, + CONF_VARIABLE_ID, + CONF_YAML_VARIABLE, + DEFAULT_EXCLUDE_FROM_RECORDER, + DEFAULT_FORCE_UPDATE, + DEFAULT_ICON, + DEFAULT_REPLACE_ATTRIBUTES, + DEFAULT_RESTORE, + DOMAIN, +) +from .helpers import value_to_type + +_LOGGER = logging.getLogger(__name__) + +PLATFORM = Platform.SENSOR +ENTITY_ID_FORMAT = PLATFORM + ".{}" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_VARIABLE_ID): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ICON, default=DEFAULT_ICON): cv.string, + vol.Optional(CONF_VALUE): cv.match_all, + vol.Optional(CONF_ATTRIBUTES): dict, + vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): cv.boolean, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + vol.Optional( + CONF_EXCLUDE_FROM_RECORDER, default=DEFAULT_EXCLUDE_FROM_RECORDER + ): cv.boolean, + } +) + +SERVICE_UPDATE_VARIABLE = "update_" + PLATFORM + +VARIABLE_ATTR_SETTINGS = {ATTR_FRIENDLY_NAME: "_attr_name", ATTR_ICON: "_attr_icon"} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities, +) -> None: + + """Setup the Sensor Variable entity with a config_entry (config_flow).""" + + config_entry.options = {} + platform = entity_platform.async_get_current_platform() + + platform.async_register_entity_service( + SERVICE_UPDATE_VARIABLE, + { + vol.Optional(ATTR_VALUE): cv.string, + vol.Optional(ATTR_ATTRIBUTES): dict, + vol.Optional( + ATTR_REPLACE_ATTRIBUTES, default=DEFAULT_REPLACE_ATTRIBUTES + ): cv.boolean, + }, + "async_update_variable", + ) + + config = hass.data.get(DOMAIN).get(config_entry.entry_id) + unique_id = config_entry.entry_id + # _LOGGER.debug(f"[async_setup_entry] config_entry: {config_entry.as_dict()}") + # _LOGGER.debug(f"[async_setup_entry] config: {config}") + # _LOGGER.debug(f"[async_setup_entry] unique_id: {unique_id}") + + async_add_entities([Variable(hass, config, config_entry, unique_id)]) + + return True + + +class Variable(RestoreSensor): + """Representation of a Sensor Variable.""" + + def __init__( + self, + hass, + config, + config_entry, + unique_id, + ): + """Initialize a Sensor Variable.""" + _LOGGER.debug( + f"({config.get(CONF_NAME, config.get(CONF_VARIABLE_ID))}) [init] config: {config}" + ) + self._hass = hass + self._config = config + self._config_entry = config_entry + self._attr_has_entity_name = True + self._variable_id = slugify(config.get(CONF_VARIABLE_ID).lower()) + self._attr_unique_id = unique_id + if config.get(CONF_NAME) is not None: + self._attr_name = config.get(CONF_NAME) + else: + self._attr_name = config.get(CONF_VARIABLE_ID) + self._attr_icon = config.get(CONF_ICON) + self._restore = config.get(CONF_RESTORE) + self._force_update = config.get(CONF_FORCE_UPDATE) + self._yaml_variable = config.get(CONF_YAML_VARIABLE) + self._exclude_from_recorder = config.get(CONF_EXCLUDE_FROM_RECORDER) + if ( + config.get(CONF_ATTRIBUTES) is not None + and config.get(CONF_ATTRIBUTES) + and isinstance(config.get(CONF_ATTRIBUTES), MutableMapping) + ): + self._attr_extra_state_attributes = self._update_attr_settings( + config.get(CONF_ATTRIBUTES) + ) + else: + self._attr_extra_state_attributes = None + self._value_type = config.get(CONF_VALUE_TYPE) + self._attr_device_class = config.get(CONF_DEVICE_CLASS) + self._attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT) + self._attr_state_class = config.get(CONF_STATE_CLASS) + if config.get(CONF_VALUE) is None or ( + isinstance(config.get(CONF_VALUE), str) + and config.get(CONF_VALUE).lower() in ["", "none", "unknown", "unavailable"] + ): + self._attr_native_value = None + else: + try: + self._attr_native_value = value_to_type( + config.get(CONF_VALUE), self._value_type + ) + except ValueError: + self._attr_native_value = None + self.entity_id = generate_entity_id( + ENTITY_ID_FORMAT, self._variable_id, hass=self._hass + ) + if self._exclude_from_recorder: + self.disable_recorder() + + def disable_recorder(self): + if RECORDER_INSTANCE in self._hass.data: + ha_history_recorder = self._hass.data[RECORDER_INSTANCE] + _LOGGER.info(f"({self._attr_name}) [disable_recorder] Disabling Recorder") + if self.entity_id: + try: + ha_history_recorder.entity_filter._exclude_e.add(self.entity_id) + except AttributeError as e: + _LOGGER.warning( + f"({self._attr_name}) [disable_recorder] AttributeError trying to disable Recorder: {e}" + ) + else: + _LOGGER.debug( + f"({self._attr_name}) [disable_recorder] _exclude_e: {ha_history_recorder.entity_filter._exclude_e}" + ) + + async def async_added_to_hass(self): + """Run when entity about to be added.""" + await super().async_added_to_hass() + if self._restore is True: + _LOGGER.info(f"({self._attr_name}) Restoring after Reboot") + sensor = await self.async_get_last_sensor_data() + if sensor and hasattr(sensor, "native_value"): + _LOGGER.debug( + f"({self._attr_name}) Restored sensor: {sensor.as_dict()}" + ) + + if sensor.native_value is None or ( + isinstance(sensor.native_value, str) + and sensor.native_value.lower() + in [ + "", + "none", + "unknown", + "unavailable", + ] + ): + self._attr_native_value = None + else: + try: + self._attr_native_value = value_to_type( + sensor.native_value, self._value_type + ) + except ValueError: + self._attr_native_value = None + # self._attr_native_unit_of_measurement = ( + # sensor.native_unit_of_measurement + # ) + state = await self.async_get_last_state() + if state: + _LOGGER.debug(f"({self._attr_name}) Restored state: {state.as_dict()}") + if ( + hasattr(state, "attributes") + and state.attributes + and isinstance(state.attributes, MutableMapping) + ): + self._attr_extra_state_attributes = self._update_attr_settings( + state.attributes.copy() + ) + + # Unsure how to deal with state vs native_value on restore. + # Setting Restored state to override native_value for now. + # self._state = state.state + if (sensor is None and hasattr(state, "state")) or ( + sensor + and hasattr(state, "state") + and state.state is not None + and hasattr(sensor, "native_value") + and sensor.native_value != state.state + ): + if state.state is None or ( + isinstance(state.state, str) + and state.state.lower() + in [ + "", + "none", + "unknown", + "unavailable", + ] + ): + newval = None + else: + try: + newval = value_to_type(state.state, self._value_type) + except ValueError: + newval = None + + _LOGGER.debug(f"({self._attr_name}) Updated state: |{newval}|") + if sensor.native_value is None or ( + isinstance(sensor.native_value, str) + and sensor.native_value.lower() + in [ + "", + "none", + "unknown", + "unavailable", + ] + ): + nat_val = None + else: + try: + nat_val = value_to_type( + sensor.native_value, self._value_type + ) + except ValueError: + nat_val = None + if nat_val != newval: + _LOGGER.info( + f"({self._attr_name}) Restored values are different. " + f"native_value: {nat_val} | state: {newval}" + ) + self._attr_native_value = newval + + async def async_will_remove_from_hass(self) -> None: + """Run when entity will be removed from hass.""" + if RECORDER_INSTANCE in self._hass.data: + ha_history_recorder = self._hass.data[RECORDER_INSTANCE] + if self.entity_id: + try: + ha_history_recorder.entity_filter._exclude_e.discard(self.entity_id) + except AttributeError: + pass + else: + _LOGGER.debug( + f"({self._attr_name}) Removing entity exclusion from recorder: {self.entity_id}" + ) + + @property + def should_poll(self): + """If entity should be polled.""" + return False + + @property + def force_update(self) -> bool: + """Force update status of the entity.""" + return self._force_update + + def _update_attr_settings(self, new_attributes=None): + if new_attributes is not None: + if isinstance(new_attributes, MutableMapping): + attributes = copy.deepcopy(new_attributes) + for attrib, setting in VARIABLE_ATTR_SETTINGS.items(): + if attrib in attributes.keys(): + _LOGGER.debug( + f"({self._attr_name}) [update_attr_settings] attrib: {attrib} / setting: {setting} / value: {attributes.get(attrib)}" + ) + setattr(self, setting, attributes.pop(attrib, None)) + return copy.deepcopy(attributes) + else: + _LOGGER.error( + f"({self._attr_name}) AttributeError: Attributes must be a dictionary: {new_attributes}" + ) + return new_attributes + else: + return None + + # async def async_update_variable(self, value, attributes, replace_attributes=False) -> None: + async def async_update_variable(self, **kwargs) -> None: + """Update Sensor Variable.""" + + updated_attributes = None + + replace_attributes = kwargs.get(ATTR_REPLACE_ATTRIBUTES, False) + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] Replace Attributes: {replace_attributes}" + ) + + if ( + not replace_attributes + and hasattr(self, "_attr_extra_state_attributes") + and self._attr_extra_state_attributes is not None + ): + updated_attributes = copy.deepcopy(self._attr_extra_state_attributes) + + attributes = kwargs.get(ATTR_ATTRIBUTES) + if attributes is not None: + if isinstance(attributes, MutableMapping): + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] New Attributes: {attributes}" + ) + extra_attributes = self._update_attr_settings(attributes) + if updated_attributes is not None: + updated_attributes.update(extra_attributes) + else: + updated_attributes = extra_attributes + else: + _LOGGER.error( + f"({self._attr_name}) AttributeError: Attributes must be a dictionary: {attributes}" + ) + + if ATTR_VALUE in kwargs: + try: + newval = value_to_type(kwargs.get(ATTR_VALUE), self._value_type) + except ValueError: + ERROR = f"The value entered is not compatible with the selected device_class: {self._attr_device_class}. Expected: {self._value_type}. Value: {kwargs.get(ATTR_VALUE)}" + raise ValueError(ERROR) + return + else: + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] New Value: {newval}" + ) + self._attr_native_value = newval + + if updated_attributes is not None: + self._attr_extra_state_attributes = copy.deepcopy(updated_attributes) + _LOGGER.debug( + f"({self._attr_name}) [async_update_variable] Final Attributes: {updated_attributes}" + ) + else: + self._attr_extra_state_attributes = None + + self.async_write_ha_state() diff --git a/home-assistant/config/custom_components/variable/services.yaml b/home-assistant/config/custom_components/variable/services.yaml index de9ec4373..d04cd6581 100644 --- a/home-assistant/config/custom_components/variable/services.yaml +++ b/home-assistant/config/custom_components/variable/services.yaml @@ -1,32 +1,133 @@ -# Example services.yaml entry +update_sensor: + name: Update Sensor Variable + description: Update a Sensor Variable value and/or its attributes. + target: + entity: + integration: variable + domain: sensor + fields: + value: + name: New Value + description: New value to set (optional) + example: 9 + selector: + text: + attributes: + name: New Attributes + description: Attributes to set or update [dictionary] (optional) + example: "{'key': 'value'}" + selector: + object: + replace_attributes: + name: Replace Attributes + description: Replace or merge current attributes [boolean] (optional) (default false = merge) + required: true + default: false + example: "false" + selector: + boolean: + +update_binary_sensor: + name: Update Binary Sensor Variable + description: Update a Binary Sensor Variable value and/or its attributes. + target: + entity: + integration: variable + domain: binary_sensor + fields: + value: + name: New Value + description: New value to set [boolean] (optional) + required: false + example: "false" + selector: + select: + mode: list + translation_key: "boolean_options" + options: + - "None" + - "true" + - "false" + attributes: + name: New Attributes + description: Attributes to set or update [dictionary] (optional) + example: "{'key': 'value'}" + selector: + object: + replace_attributes: + name: Replace Attributes + description: Replace or merge current attributes [boolean] (optional) (default false = merge) + required: true + default: false + example: "false" + selector: + boolean: set_variable: # Description of the service - description: Update a variables value and/or its attributes. + name: Set Variable (Legacy) + description: "Legacy service: Update a Sensor Variable value and/or its attributes. Will only work on Sensor Variables. Use one of the variable.update_ services for additional options." # Different fields that your service accepts fields: # Key of the field variable: - description: string (required) The name of the variable to update + name: Variable ID + description: string (required) The name of the Sensor Variable to update + required: true example: test_counter + selector: + text: value: + name: New Value description: any (optional) New value to set example: 9 + selector: + text: attributes: + name: New Attributes description: dictionary (optional) Attributes to set or update + example: "{'key': 'value'}" + selector: + object: replace_attributes: + name: Replace Attributes description: boolean (optional) Replace or merge current attributes (default false = merge) + required: true + default: false + example: "false" + selector: + boolean: set_entity: - description: Update an entity value and/or its attributes. + name: Set Entity (Legacy) + description: "Legacy service: Update a Sensor Variable value and/or its attributes. Will only work on Sensor Variables. Use one of the variable.update_ services for additional options." fields: entity: - description: string (required) The id of the entity to update + name: Entity ID + description: string (required) The entity_id of the Sensor Variable to update example: sensor.test_sensor + required: true + selector: + entity: + integration: variable + domain: sensor value: + name: New Value description: any (optional) New value to set example: 9 + selector: + text: attributes: + name: New Attributes description: dictionary (optional) Attributes to set or update + example: "{'key': 'value'}" + selector: + object: replace_attributes: - description: boolean (optional) Replace or merge current attributes (default false = merge) \ No newline at end of file + name: Replace Attributes + description: boolean (optional) Replace or merge current attributes (default false = merge) + required: true + default: false + example: "false" + selector: + boolean: diff --git a/home-assistant/config/custom_components/variable/strings.json b/home-assistant/config/custom_components/variable/strings.json new file mode 100644 index 000000000..6a3ce3deb --- /dev/null +++ b/home-assistant/config/custom_components/variable/strings.json @@ -0,0 +1,107 @@ +{ + "config": { + "step": { + "user": { + "menu_options": { + "add_sensor": "Create a Sensor Variable", + "add_binary_sensor": "Create a Binary Sensor Variable" + } + }, + "add_sensor": { + "title": "Variables+History - Sensor", + "data": { + "name": "[%key:common::config_flow::data::name%]", + "variable_id": "Variable ID", + "icon": "Icon", + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "Create a new Sensor Variable" + }, + "sensor_page_2": { + "title": "Variables+History - Sensor Page 2", + "data": { + "value": "Initial Value", + "attributes": "Initial Attributes", + "state_class": "State Class", + "unit_of_measurement": "Unit of Measurement" + }, + "description": "Create a new Sensor Variable Page 2" + }, + "add_binary_sensor": { + "title": "Variables+History - Binary Sensor", + "data": { + "name": "[%key:common::config_flow::data::name%]", + "variable_id": "Variable ID", + "icon": "Icon", + "value": "Initial Value", + "attributes": "Initial Attributes", + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "Create a new Binary Sensor Variable" + } + }, + "error": { + "invalid_value_type": "The value entered is not compatible with the selected device_class", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": {}, + "sensor_options": { + "title": "Variables+History - Sensor", + "data": { + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "Update existing Variable Sensor" + }, + "sensor_options_page_2": { + "title": "Variables+History - Sensor Page 2", + "data": { + "value": "Value (typically only useful if Restore on Restart is False)", + "attributes": "Attributes (typically only useful if Restore on Restart is False)", + "state_class": "State Class", + "unit_of_measurement": "Unit of Measurement" + }, + "description": "Update existing Variable Sensor" + }, + "binary_sensor_options": { + "title": "Variables+History - Binary Sensor", + "data": { + "value": "Value (typically only useful if Restore on Restart is False)", + "attributes": "Attributes (typically only useful if Restore on Restart is False)", + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "Update existing Variable Binary Sensor" + } + }, + "abort": { + "yaml_variable": "Cannot change options here for Variables created by YAML.\n\nTo use the User Interface to manage this Variable, you will need to:\n1. Remove it from YAML\n2. Delete the imported Variable entity from Home Assistant\n3. Restart Home Assistant\n4. Manually recreate it in Home Assistant, Integrations, +Add Integration.", + "yaml_update_error": "Unable to update YAML Sensor Variable" + }, + "error": { + "invalid_value_type": "The value entered is not compatible with the selected device_class", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "selector": { + "boolean_options": { + "options": { + "true": "true", + "false": "false" + } + } + } +} diff --git a/home-assistant/config/custom_components/variable/translations/en.json b/home-assistant/config/custom_components/variable/translations/en.json new file mode 100644 index 000000000..9d40d76f9 --- /dev/null +++ b/home-assistant/config/custom_components/variable/translations/en.json @@ -0,0 +1,107 @@ +{ + "config": { + "step": { + "user": { + "menu_options": { + "add_sensor": "Create a Sensor Variable", + "add_binary_sensor": "Create a Binary Sensor Variable" + } + }, + "add_sensor": { + "title": "Variables+History - Sensor", + "data": { + "name": "Variable Name", + "variable_id": "Variable ID", + "icon": "Icon", + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "Create a new Sensor Variable\nSee [Configuration Options]({component_config_url}) on GitHub for details" + }, + "sensor_page_2": { + "title": "Variables+History - Sensor Page 2", + "data": { + "value": "Initial Value", + "attributes": "Initial Attributes", + "state_class": "State Class", + "unit_of_measurement": "Unit of Measurement" + }, + "description": "Create a new Sensor Variable Page 2\n\n**Variable: {disp_name}**\n**Device Class: {device_class}**\n**Value Type: {value_type}**" + }, + "add_binary_sensor": { + "title": "Variables+History - Binary Sensor", + "data": { + "name": "Variable Name", + "variable_id": "Variable ID", + "icon": "Icon", + "value": "Initial Value", + "attributes": "Initial Attributes", + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "Create a new Binary Sensor Variable\nSee [Configuration Options]({component_config_url}) on GitHub for details" + } + }, + "error": { + "invalid_value_type": "The value entered is not compatible with the selected device_class: {device_class}. Expected {value_type}.", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": {}, + "sensor_options": { + "title": "Variables+History - Sensor", + "data": { + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "**Updating Sensor: {disp_name}**\nSee [Configuration Options]({component_config_url}) on GitHub for details" + }, + "sensor_options_page_2": { + "title": "Variables+History - Sensor Page 2", + "data": { + "value": "Value (typically only useful if Restore on Restart is False)", + "attributes": "Attributes (typically only useful if Restore on Restart is False)", + "state_class": "State Class", + "unit_of_measurement": "Unit of Measurement" + }, + "description": "Updating Sensor Variable Page 2\n\n**Variable: {disp_name}**\n**Device Class: {device_class}**\n**Value Type: {value_type}**" + }, + "binary_sensor_options": { + "title": "Variables+History - Binary Sensor", + "data": { + "value": "Value (typically only useful if Restore on Restart is False)", + "attributes": "Attributes (typically only useful if Restore on Restart is False)", + "device_class": "Device Class", + "restore": "Restore on Restart", + "force_update": "Force Update", + "exclude_from_recorder": "Exclude from Recorder" + }, + "description": "**Updating Binary Sensor: {disp_name}**\nSee [Configuration Options]({component_config_url}) on GitHub for details" + } + }, + "abort": { + "yaml_variable": "Cannot change options here for Variables created by YAML.\n\nTo use the User Interface to manage this Variable, you will need to:\n1. Remove it from YAML\n2. Delete the imported Variable entity from Home Assistant\n3. Restart Home Assistant\n4. Manually recreate it in Home Assistant, Integrations, +Add Integration.", + "yaml_update_error": "Unable to update YAML Sensor Variable" + }, + "error": { + "invalid_value_type": "The value entered is not compatible with the selected device_class: {device_class}. Expected {value_type}.", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "selector": { + "boolean_options": { + "options": { + "true": "true", + "false": "false" + } + } + } +} From a45dad958477cad18c78b415c4ba4ef8233a4eca Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Sun, 2 Jul 2023 18:31:19 +0200 Subject: [PATCH 05/11] fix bug --- home-assistant/config/blueprints/automation/diy/auto_lights.yaml | 1 - home-assistant/config/blueprints/automation/diy/presence.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/home-assistant/config/blueprints/automation/diy/auto_lights.yaml b/home-assistant/config/blueprints/automation/diy/auto_lights.yaml index 728f21894..10a75e32f 100644 --- a/home-assistant/config/blueprints/automation/diy/auto_lights.yaml +++ b/home-assistant/config/blueprints/automation/diy/auto_lights.yaml @@ -109,7 +109,6 @@ variables: switch_entity_ids: "{{ expand(light_and_switch_entity_ids) | selectattr('domain', 'equalto', 'switch') | map(attribute='entity_id') | list }}" light_entity_ids: "{{ expand(light_and_switch_entity_ids) | selectattr('domain', 'equalto', 'light') | map(attribute='entity_id') | list }}" presence_entity_id: !input presence_entity_id - presence_variable: "{{ expand(presence_entity_id)[0].object_id }}" any_light_on: "{{ expand(light_and_switch_entity_ids) | selectattr('state', 'equalto', 'on') | list | length > 0 }}" any_light_off: "{{ expand(light_and_switch_entity_ids) | selectattr('state', 'equalto', 'off') | list | length > 0 }}" diff --git a/home-assistant/config/blueprints/automation/diy/presence.yaml b/home-assistant/config/blueprints/automation/diy/presence.yaml index 72d3e21f1..1cd4bcc8f 100644 --- a/home-assistant/config/blueprints/automation/diy/presence.yaml +++ b/home-assistant/config/blueprints/automation/diy/presence.yaml @@ -59,7 +59,6 @@ trace: variables: presence_entity_id: !input presence_entity_id - presence_variable: "{{ expand(presence_entity_id)[0].object_id }}" presence_indicator_entity_ids: !input presence_indicator_entity_ids presence_hint_entity_ids: !input presence_hint_entity_ids presence_timeout: !input presence_timeout From d080d7aa386ca97f0cb23439627d426a0d3914fc Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Sun, 2 Jul 2023 21:02:45 +0200 Subject: [PATCH 06/11] create door automation --- home-assistant/config/automations.yaml | 80 ++++++++++++++++++++++++ home-assistant/config/configuration.yaml | 31 +++------ 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 6876f22e0..3008e524a 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -598,3 +598,83 @@ hours: 9 minutes: 0 seconds: 0 +- id: "1688322983719" + alias: Living Room - Door Occupancy + description: "" + trigger: + - platform: state + entity_id: + - binary_sensor.living_room_door_contact + from: + to: + - platform: state + entity_id: + - binary_sensor.living_room_motion_occupancy + from: + to: + - platform: homeassistant + event: start + condition: [] + action: + - choose: + - conditions: + - condition: template + value_template: + "{% set door_id = 'binary_sensor.living_room_door_contact' + %}\n{% set timeout = 10 %}\n\n{% set door_activity_timeout = now() - timedelta(seconds + = timeout) %}\n{% set door_last_updated = states[door_id].last_updated %} + \n{% set door_recently_updated = door_last_updated >= door_activity_timeout + %}\n\n{{ door_recently_updated }}" + alias: Door contact recently had activity + sequence: + - service: variable.update_sensor + data: + replace_attributes: false + attributes: + icon: mdi:motion-sensor + value: "on" + target: + entity_id: sensor.living_room_door_occupancy + alias: Set door activity + - delay: + hours: 0 + minutes: 0 + seconds: 10 + milliseconds: 0 + - service: variable.update_sensor + data: + replace_attributes: false + value: "off" + attributes: + icon: mdi:motion-sensor-off + target: + entity_id: sensor.living_room_door_occupancy + alias: Unset door activity + - conditions: + - condition: state + entity_id: binary_sensor.living_room_door_contact + state: "on" + - condition: state + entity_id: binary_sensor.living_room_motion_occupancy + state: "on" + sequence: + - service: variable.update_sensor + data: + replace_attributes: false + value: "on" + attributes: + icon: mdi:motion-sensor + target: + entity_id: sensor.living_room_door_occupancy + alias: Set door activity + default: + - service: variable.update_sensor + data: + replace_attributes: false + value: "off" + attributes: + icon: mdi:motion-sensor-off + target: + entity_id: sensor.living_room_door_occupancy + alias: Unset door activity + mode: single diff --git a/home-assistant/config/configuration.yaml b/home-assistant/config/configuration.yaml index c3e44b1b0..f12a0d395 100644 --- a/home-assistant/config/configuration.yaml +++ b/home-assistant/config/configuration.yaml @@ -48,28 +48,6 @@ input_select: - leaving icon: mdi:panda -template: - - binary_sensor: - - name: living_room_door - state: > - {% set door_id = 'binary_sensor.living_room_door_contact' %} - {% set motion_id = 'binary_sensor.living_room_motion_occupancy' %} - {% set timeout = 10 %} - - {% set door_last_updated = states[door_id].last_updated %} - {% set door_is_open = is_state(door_id, 'on') %} - {% set door_activity_timeout = now() - timedelta(seconds = timeout) %} - {% set door_recently_updated = door_last_updated >= door_activity_timeout %} - {% set motion_is_active = is_state(motion_id, 'on') %} - - {% if door_recently_updated %} - on - {% elif door_is_open and motion_is_active %} - on - {% else %} - off - {% endif %} - automation: !include automations.yaml script: !include scripts.yaml scene: !include scenes.yaml @@ -200,6 +178,15 @@ variable: next_location: "away" next_location_iteration: 0 + living_room_door_occupancy: + value: "off" + restore: true + attributes: + state_class: measurement + device_class: "door" + friendly_name: "Living Room Door Occupancy" + icon: mdi:motion-sensor + driveway_presence: value: "off" restore: true From 1957480149237b51343741d8d31600848edb76ef Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Mon, 3 Jul 2023 20:38:22 +0200 Subject: [PATCH 07/11] working starting automation --- home-assistant/config/automations.yaml | 87 ++++++++++++++++++++++++ home-assistant/config/configuration.yaml | 2 + 2 files changed, 89 insertions(+) diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 3008e524a..3d5130d83 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -678,3 +678,90 @@ entity_id: sensor.living_room_door_occupancy alias: Unset door activity mode: single +- id: "1688367427268" + alias: Hallway - Presence + description: "" + trigger: + - platform: state + entity_id: + - sensor.living_room_door_occupancy + id: doors + from: "off" + to: "on" + - platform: state + entity_id: + - input_select.hallway_presence + from: + to: + id: state + - platform: state + entity_id: + - timer.hallway_transition_timer + from: + to: + id: timer + condition: [] + action: + - choose: + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: absent + sequence: + - choose: + - conditions: + - condition: trigger + id: doors + sequence: + - service: input_select.select_option + data: + option: entering + target: + entity_id: input_select.hallway_presence + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: entering + sequence: + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.start + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: timer + - condition: state + entity_id: timer.hallway_transition_timer + state: idle + sequence: + - service: input_select.select_option + data: + option: absent + target: + entity_id: input_select.hallway_presence + - conditions: + - condition: trigger + id: doors + sequence: + - service: timer.change + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: present + sequence: [] + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: leaving + sequence: [] + mode: queued diff --git a/home-assistant/config/configuration.yaml b/home-assistant/config/configuration.yaml index f12a0d395..580bb290c 100644 --- a/home-assistant/config/configuration.yaml +++ b/home-assistant/config/configuration.yaml @@ -38,6 +38,8 @@ timer: bedroom_away_timer: bedroom_manual_timer: + hallway_transition_timer: + input_select: hallway_presence: name: Hallway Presence From 7dae5901cb7d8eeffa7d68ead01734a3f39c57aa Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Mon, 3 Jul 2023 21:27:14 +0200 Subject: [PATCH 08/11] first working version --- home-assistant/config/automations.yaml | 82 +++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 3d5130d83..fccacf691 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -700,6 +700,12 @@ from: to: id: timer + - platform: state + entity_id: + - binary_sensor.hallway_human + from: + to: + id: human condition: [] action: - choose: @@ -718,6 +724,14 @@ option: entering target: entity_id: input_select.hallway_presence + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.cancel + data: {} + target: + entity_id: timer.hallway_transition_timer - conditions: - condition: state entity_id: input_select.hallway_presence @@ -745,6 +759,7 @@ option: absent target: entity_id: input_select.hallway_presence + - stop: Back to absent. - conditions: - condition: trigger id: doors @@ -754,14 +769,77 @@ duration: 00:00:10 target: entity_id: timer.hallway_transition_timer + - if: + - condition: state + entity_id: binary_sensor.hallway_human + state: "on" + then: + - service: input_select.select_option + data: + option: present + target: + entity_id: input_select.hallway_presence - conditions: - condition: state entity_id: input_select.hallway_presence state: present - sequence: [] + sequence: + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.cancel + data: {} + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: doors + sequence: + - service: input_select.select_option + data: + option: leaving + target: + entity_id: input_select.hallway_presence - conditions: - condition: state entity_id: input_select.hallway_presence state: leaving - sequence: [] + sequence: + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.start + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: timer + - condition: state + entity_id: timer.hallway_transition_timer + state: idle + sequence: + - service: input_select.select_option + data: + option: present + target: + entity_id: input_select.hallway_presence + alias: Back to present + - stop: Back to present + - if: + - condition: state + entity_id: binary_sensor.hallway_human + state: "off" + then: + - service: input_select.select_option + data: + option: absent + target: + entity_id: input_select.hallway_presence + alias: Select absent mode: queued From 6961aee54c660605a200bff1aba36ff4615b76dc Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Thu, 24 Aug 2023 15:11:32 +0200 Subject: [PATCH 09/11] changes --- home-assistant/config/automations.yaml | 1120 ++++++++--------- .../zigbee2mqtt-data/configuration.yaml | 57 +- .../terminal/files/sheldon/plugins.lock | 14 +- 3 files changed, 595 insertions(+), 596 deletions(-) diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index fccacf691..930305e3d 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -1,114 +1,114 @@ -- id: "1655749208565" - alias: "Supply Closet - Presence: Manage" - description: "" +- id: '1655749208565' + alias: 'Supply Closet - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.supply_closet_presence presence_indicator_entity_ids: - - binary_sensor.supply_closet_door_contact + - binary_sensor.supply_closet_door_contact presence_hint_entity_ids: - - binary_sensor.supply_closet_shelly_input + - binary_sensor.supply_closet_shelly_input presence_timeout: 0 -- id: "1655751397719" - alias: "Toilet - Presence: Manage" - description: "" +- id: '1655751397719' + alias: 'Toilet - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.toilet_presence presence_indicator_entity_ids: - - binary_sensor.toilet_motion_occupancy + - binary_sensor.toilet_motion_occupancy presence_hint_entity_ids: - - binary_sensor.toilet_shelly_input_1 - - binary_sensor.toilet_shelly_input_2 - - binary_sensor.toilet_door_contact + - binary_sensor.toilet_shelly_input_1 + - binary_sensor.toilet_shelly_input_2 + - binary_sensor.toilet_door_contact presence_timeout: 180 -- id: "1655751783256" - alias: "Driveway - Presence: Manage" - description: "" +- id: '1655751783256' + alias: 'Driveway - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.driveway_presence presence_indicator_entity_ids: - - binary_sensor.driveway_doorbell_motion - - binary_sensor.hallway_door_contact + - binary_sensor.driveway_doorbell_motion + - binary_sensor.hallway_door_contact presence_hint_entity_ids: - - binary_sensor.driveway_shelly_input + - binary_sensor.driveway_shelly_input presence_timeout: 120 -- id: "1655752570246" - alias: "Hallway - Presence: Manage" - description: "" +- id: '1655752570246' + alias: 'Hallway - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.hallway_presence presence_hint_entity_ids: - - binary_sensor.hallway_shelly_input - - binary_sensor.toilet_door_contact - - binary_sensor.living_room_door_contact - - binary_sensor.driveway_shelly_input - - binary_sensor.landing_motion_occupancy - - binary_sensor.living_room_motion_occupancy - - binary_sensor.toilet_motion_occupancy + - binary_sensor.hallway_shelly_input + - binary_sensor.toilet_door_contact + - binary_sensor.living_room_door_contact + - binary_sensor.driveway_shelly_input + - binary_sensor.landing_motion_occupancy + - binary_sensor.living_room_motion_occupancy + - binary_sensor.toilet_motion_occupancy presence_timeout: 120 presence_indicator_entity_ids: - - binary_sensor.hallway_motion_occupancy - - binary_sensor.hallway_door_contact -- id: "1655752934365" - alias: "Laundry Room - Presence: Manage" - description: "" + - binary_sensor.hallway_motion_occupancy + - binary_sensor.hallway_door_contact +- id: '1655752934365' + alias: 'Laundry Room - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.laundry_room_presence presence_indicator_entity_ids: - - binary_sensor.laundry_room_motion_occupancy + - binary_sensor.laundry_room_motion_occupancy presence_hint_entity_ids: - - binary_sensor.laundry_room_shelly_input -- id: "1655753137217" - alias: "Office - Presence: Manage" - description: "" + - binary_sensor.laundry_room_shelly_input +- id: '1655753137217' + alias: 'Office - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.office_presence presence_indicator_entity_ids: - - binary_sensor.office_human_presence - - binary_sensor.office_motion_occupancy + - binary_sensor.office_human_presence + - binary_sensor.office_motion_occupancy presence_hint_entity_ids: - - binary_sensor.office_door_contact - - binary_sensor.office_shelly_input -- id: "1655755184933" - alias: "Driveway - Light: Manage" - description: "" + - binary_sensor.office_door_contact + - binary_sensor.office_shelly_input +- id: '1655755184933' + alias: 'Driveway - Light: Manage' + description: '' use_blueprint: path: diy/auto_lights.yaml input: presence_entity_id: sensor.driveway_presence light_and_switch_entity_ids: - - switch.driveway_shelly + - switch.driveway_shelly start_time: 00:00:00 - end_time: "23:59:59" + end_time: '23:59:59' illuminance_entity_id: sensor.illuminance illuminance_threshold: 1000 -- id: "1655798724103" - alias: "Hallway - Light: Manage" - description: "" +- id: '1655798724103' + alias: 'Hallway - Light: Manage' + description: '' use_blueprint: path: diy/auto_lights.yaml input: presence_entity_id: sensor.hallway_presence illuminance_entity_id: sensor.hallway_motion_illuminance_lux light_and_switch_entity_ids: - - light.hallway_lights + - light.hallway_lights start_time: 00:00:00 - end_time: "23:59:59" + end_time: '23:59:59' illuminance_threshold: 1000 -- id: "1655799885346" - alias: "Laundry Room - Light: Manage" - description: "" +- id: '1655799885346' + alias: 'Laundry Room - Light: Manage' + description: '' use_blueprint: path: diy/auto_lights.yaml input: @@ -116,25 +116,25 @@ illuminance_entity_id: sensor.laundry_room_motion_illuminance_lux illuminance_threshold: 100 light_and_switch_entity_ids: - - switch.laundry_room_shelly + - switch.laundry_room_shelly start_time: 07:00:00 - end_time: "21:00:00" -- id: "1655800011564" - alias: "Office - Light: Manage" - description: "" + end_time: '21:00:00' +- id: '1655800011564' + alias: 'Office - Light: Manage' + description: '' use_blueprint: path: diy/auto_lights.yaml input: presence_entity_id: sensor.office_presence illuminance_entity_id: sensor.office_illuminance_illuminance_lux light_and_switch_entity_ids: - - switch.office_shelly + - switch.office_shelly start_time: 07:00:00 - end_time: "23:00:00" + end_time: '23:00:00' illuminance_threshold: 40 -- id: "1655800068148" - alias: "Supply Closet - Light: Manage" - description: "" +- id: '1655800068148' + alias: 'Supply Closet - Light: Manage' + description: '' use_blueprint: path: diy/auto_lights.yaml input: @@ -142,12 +142,12 @@ illuminance_entity_id: sensor.illuminance illuminance_threshold: 200000 light_and_switch_entity_ids: - - switch.supply_closet_shelly + - switch.supply_closet_shelly start_time: 00:00:00 - end_time: "23:59:59" -- id: "1655800128048" - alias: "Toilet - Light: Manage" - description: "" + end_time: '23:59:59' +- id: '1655800128048' + alias: 'Toilet - Light: Manage' + description: '' use_blueprint: path: diy/auto_lights.yaml input: @@ -155,307 +155,300 @@ illuminance_entity_id: sensor.illuminance illuminance_threshold: 200000 light_and_switch_entity_ids: - - light.toilet_lights + - light.toilet_lights start_time: 00:00:00 - end_time: "23:59:59" -- id: "1655926470261" - alias: "Kitchen - Presence: Manage" - description: "" + end_time: '23:59:59' +- id: '1655926470261' + alias: 'Kitchen - Presence: Manage' + description: '' use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.kitchen_presence presence_indicator_entity_ids: - - binary_sensor.kitchen_human_presence -- id: "1657026314050" - alias: "Bedroom - Sleeping: Manage" - description: "" + - binary_sensor.kitchen_human_presence +- id: '1657026314050' + alias: 'Bedroom - Sleeping: Manage' + description: '' trigger: - - platform: state - entity_id: - - sensor.bedroom_1_button_action - to: "on" - id: button_1 - - platform: state - entity_id: - - sensor.bedroom_2_button_action - id: button_2 - to: "on" - - platform: time - at: "22:00:00" - id: evening_time - - platform: time - at: 07:00:00 - id: morning_time + - platform: state + entity_id: + - sensor.bedroom_1_button_action + to: 'on' + id: button_1 + - platform: state + entity_id: + - sensor.bedroom_2_button_action + id: button_2 + to: 'on' + - platform: time + at: '22:00:00' + id: evening_time + - platform: time + at: 07:00:00 + id: morning_time condition: [] action: + - if: + - condition: trigger + id: button_1 + then: + - wait_for_trigger: + - platform: state + entity_id: + - sensor.bedroom_1_button_action + to: 'on' + timeout: '1' - if: - - condition: trigger - id: button_1 + - condition: template + value_template: '{{ wait.remaining > 0 }} ' then: - - wait_for_trigger: - - platform: state - entity_id: - - sensor.bedroom_1_button_action - to: "on" - timeout: "1" - - if: - - condition: template - value_template: "{{ wait.remaining > 0 }} " - then: - - service: input_boolean.turn_off - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - else: - - service: input_boolean.turn_on - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping + - service: input_boolean.turn_off + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + else: + - service: input_boolean.turn_on + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + - if: + - condition: trigger + id: button_2 + then: + - wait_for_trigger: + - platform: state + entity_id: + - sensor.bedroom_2_button_action + to: 'on' + timeout: '1' - if: - - condition: trigger - id: button_2 + - condition: template + value_template: '{{ wait.remaining > 0 }} ' then: - - wait_for_trigger: - - platform: state - entity_id: - - sensor.bedroom_2_button_action - to: "on" - timeout: "1" - - if: - - condition: template - value_template: "{{ wait.remaining > 0 }} " - then: - - service: input_boolean.turn_off - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - else: - - service: input_boolean.turn_on - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping + - service: input_boolean.turn_off + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + else: + - service: input_boolean.turn_on + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + - if: + - condition: or + conditions: + - condition: trigger + id: evening_time + - condition: trigger + id: morning_time + then: - if: - - condition: or - conditions: - - condition: trigger - id: evening_time - - condition: trigger - id: morning_time + - condition: time + after: '22:00:00' + before: 07:00:00 then: - - if: - - condition: time - after: "22:00:00" - before: 07:00:00 - then: - - service: input_boolean.turn_on - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - else: - - service: input_boolean.turn_off - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping + - service: input_boolean.turn_on + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + else: + - service: input_boolean.turn_off + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping mode: single -- id: "1658226673278" - alias: "Adaptive lighting: toggle 'sleep mode'" - description: "" +- id: '1658226673278' + alias: 'Adaptive lighting: toggle ''sleep mode''' + description: '' trigger: - - platform: state - entity_id: - - input_boolean.bedroom_is_sleeping - - platform: homeassistant - event: start + - platform: state + entity_id: + - input_boolean.bedroom_is_sleeping + - platform: homeassistant + event: start variables: - sleep_mode: "{{ states('input_boolean.bedroom_is_sleeping') }}" + sleep_mode: '{{ states(''input_boolean.bedroom_is_sleeping'') }}' condition: [] action: service: switch.turn_{{ sleep_mode }} entity_id: - - switch.adaptive_lighting_sleep_mode_baby_room - - switch.adaptive_lighting_sleep_mode_landing - - switch.adaptive_lighting_sleep_mode_hallway + - switch.adaptive_lighting_sleep_mode_baby_room + - switch.adaptive_lighting_sleep_mode_landing + - switch.adaptive_lighting_sleep_mode_hallway mode: single -- id: "1664274065748" +- id: '1664274065748' alias: Driveway - Doorbell Play Chime - description: "" + description: '' trigger: - - platform: state - entity_id: - - binary_sensor.driveway_doorbell_doorbell - from: "off" - to: "on" + - platform: state + entity_id: + - binary_sensor.driveway_doorbell_doorbell + from: 'off' + to: 'on' condition: [] action: - - parallel: - - service: sonos.snapshot - data: - entity_id: media_player.woonkamer - - service: sonos.snapshot - data: - entity_id: media_player.sonos_move - - service: media_player.volume_set + - parallel: + - service: sonos.snapshot data: - volume_level: 0.65 - target: - entity_id: - - media_player.woonkamer - - media_player.sonos_move - - service: media_player.play_media + entity_id: media_player.woonkamer + - service: sonos.snapshot data: - media_content_type: music - media_content_id: http://hypervisor:8123/local/sounds_sounds_ring_button_Chime.mp3 - target: - entity_id: - - media_player.woonkamer - - media_player.sonos_move - - delay: - hours: 0 - minutes: 0 - seconds: 4 - milliseconds: 0 - - parallel: - - service: sonos.restore - data: - entity_id: media_player.woonkamer - - service: sonos.restore - data: - entity_id: media_player.sonos_move + entity_id: media_player.sonos_move + - service: media_player.volume_set + data: + volume_level: 0.65 + target: + entity_id: + - media_player.woonkamer + - media_player.sonos_move + - service: media_player.play_media + data: + media_content_type: music + media_content_id: http://hypervisor:8123/local/sounds_sounds_ring_button_Chime.mp3 + target: + entity_id: + - media_player.woonkamer + - media_player.sonos_move + - delay: + hours: 0 + minutes: 0 + seconds: 4 + milliseconds: 0 + - parallel: + - service: sonos.restore + data: + entity_id: media_player.woonkamer + - service: sonos.restore + data: + entity_id: media_player.sonos_move mode: single -- id: "1674554618790" +- id: '1674554618790' alias: Apple Watch Location 1 trace: stored_traces: 300 - description: "" + description: '' trigger: - - platform: mqtt - topic: home/location/bluetooth-00:00:00:00:00:00 - id: mqtt - - platform: time_pattern - seconds: /10 - id: time + - platform: mqtt + topic: home/location/bluetooth-00:00:00:00:00:00 + id: mqtt + - platform: time_pattern + seconds: /10 + id: time condition: [] action: + - if: + - condition: trigger + id: time + then: - if: - - condition: trigger - id: time - then: - - if: - - condition: template - value_template: - "{% set the_past = state_attr('sensor.apple_watch_1_location', - 'last_updated') %} + - condition: template + value_template: '{% set the_past = state_attr(''sensor.apple_watch_1_location'', + ''last_updated'') %} - {% set the_present = now() %} + {% set the_present = now() %} - {% set seconds_passed = as_timestamp(the_present)-as_timestamp(the_past) - %} + {% set seconds_passed = as_timestamp(the_present)-as_timestamp(the_past) + %} - {{ seconds_passed > 120 }}" - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - value: away - attributes: - next_location: away - next_location_iteration: 0 - next_location_duration: 0 - icon: mdi:home-outline - last_updated: "{{ now() }}" - - stop: Done checking away status - alias: Check for away status - - if: - - condition: template - value_template: - "{{ trigger.payload_json.location == states('sensor.apple_watch_1_location') - }}" - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location: "{{ states('sensor.apple_watch_1_location') }}" - next_location_iteration: 0 - icon: mdi:home - last_updated: "{{ now() }}" - - stop: Nothing changed - alias: same location - - if: - - condition: template - value_template: "{{ trigger.payload_json.guesses[0].probability < 0.5 }}" + {{ seconds_passed > 120 }}' then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location: "{{ states('sensor.apple_watch_1_location') }}" - next_location_iteration: 0 - icon: mdi:home - last_updated: "{{ now() }}" - - stop: Probability too low - alias: low probability - - if: - - condition: template - value_template: - "{{ trigger.payload_json.location != state_attr('sensor.apple_watch_1_location', - 'next_location') }}" - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location: "{{ trigger.payload_json.location }}" - next_location_iteration: 0 - icon: mdi:home - last_updated: "{{ now() }}" - alias: Update next location + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + value: away + attributes: + next_location: away + next_location_iteration: 0 + next_location_duration: 0 + icon: mdi:home-outline + last_updated: '{{ now() }}' + - stop: Done checking away status + alias: Check for away status + - if: + - condition: template + value_template: '{{ trigger.payload_json.location == states(''sensor.apple_watch_1_location'') + }}' + then: - service: variable.update_sensor target: entity_id: sensor.apple_watch_1_location data: attributes: - next_location_iteration: - "{{ (state_attr('sensor.apple_watch_1_location', - 'next_location_iteration') | int(default=0)) + 1 }}" - alias: Next location iteration increment - - if: - - condition: template - value_template: - "{{ (state_attr('sensor.apple_watch_1_location', 'next_location_iteration') - | int(default=0)) > 1 }}" - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - value: - "{{ state_attr('sensor.apple_watch_1_location', 'next_location') - }}" - attributes: - next_location: - "{{ state_attr('sensor.apple_watch_1_location', 'next_location') - }}" - next_location_iteration: 0 - icon: mdi:home - last_updated: "{{ now() }}" - alias: Change location + next_location: '{{ states(''sensor.apple_watch_1_location'') }}' + next_location_iteration: 0 + icon: mdi:home + last_updated: '{{ now() }}' + - stop: Nothing changed + alias: same location + - if: + - condition: template + value_template: '{{ trigger.payload_json.guesses[0].probability < 0.5 }}' + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + attributes: + next_location: '{{ states(''sensor.apple_watch_1_location'') }}' + next_location_iteration: 0 + icon: mdi:home + last_updated: '{{ now() }}' + - stop: Probability too low + alias: low probability + - if: + - condition: template + value_template: '{{ trigger.payload_json.location != state_attr(''sensor.apple_watch_1_location'', + ''next_location'') }}' + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + attributes: + next_location: '{{ trigger.payload_json.location }}' + next_location_iteration: 0 + icon: mdi:home + last_updated: '{{ now() }}' + alias: Update next location + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + attributes: + next_location_iteration: '{{ (state_attr(''sensor.apple_watch_1_location'', + ''next_location_iteration'') | int(default=0)) + 1 }}' + alias: Next location iteration increment + - if: + - condition: template + value_template: '{{ (state_attr(''sensor.apple_watch_1_location'', ''next_location_iteration'') + | int(default=0)) > 1 }}' + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + value: '{{ state_attr(''sensor.apple_watch_1_location'', ''next_location'') + }}' + attributes: + next_location: '{{ state_attr(''sensor.apple_watch_1_location'', ''next_location'') + }}' + next_location_iteration: 0 + icon: mdi:home + last_updated: '{{ now() }}' + alias: Change location mode: single -- id: "1687445709673" +- id: '1687445709673' alias: Living Room Dining Table - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_dining_table_human_presence light_and_switch_entity_ids: - - light.living_room_dining_table_light + - light.living_room_dining_table_light illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_dining_table_away_timer away_timer_duration: @@ -468,15 +461,15 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: "1687446669605" +- id: '1687446669605' alias: Living Room Kitchen - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_kitchen_human_presence light_and_switch_entity_ids: - - light.kitchen_lights + - light.kitchen_lights illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_kitchen_away_timer away_timer_duration: @@ -489,15 +482,15 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: "1687446932202" +- id: '1687446932202' alias: Living Room Reading - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_reading_human_presence light_and_switch_entity_ids: - - light.living_room_monkey_light + - light.living_room_monkey_light illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_reading_away_timer away_timer_duration: @@ -510,16 +503,16 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: "1687447110495" +- id: '1687447110495' alias: Living Room Sofa - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_sofa_human_presence light_and_switch_entity_ids: - - light.living_room_corner_light - - light.living_room_tv_light + - light.living_room_corner_light + - light.living_room_tv_light illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_sofa_away_timer away_timer_duration: @@ -532,9 +525,9 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: "1687452619492" +- id: '1687452619492' alias: Bathroom Shower - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: @@ -551,20 +544,20 @@ minutes: 0 seconds: 0 light_and_switch_entity_ids: - - light.bathroom_1_light - - light.bathroom_2_light - - light.bathroom_3_light + - light.bathroom_1_light + - light.bathroom_2_light + - light.bathroom_3_light illuminance_threshold: 500 -- id: "1687452837238" +- id: '1687452837238' alias: Bathroom Washing - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: light_and_switch_entity_ids: - - light.bathroom_4_light - - light.bathroom_5_light - - light.bathroom_6_light + - light.bathroom_4_light + - light.bathroom_5_light + - light.bathroom_6_light illuminance_entity_id: sensor.bathroom_illuminance away_timer_entity_id: timer.bathroom_washing_away_timer away_timer_duration: @@ -578,15 +571,15 @@ seconds: 0 illuminance_threshold: 500 presence_entity_id: binary_sensor.bathroom_washing_human_presence -- id: "1687512815133" +- id: '1687512815133' alias: Bedroom - Lights - description: "" + description: '' use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.bedroom_human_presence light_and_switch_entity_ids: - - switch.bedroom_shelly + - switch.bedroom_shelly illuminance_entity_id: sensor.bedroom_illuminance away_timer_entity_id: timer.bedroom_away_timer away_timer_duration: @@ -598,248 +591,247 @@ hours: 9 minutes: 0 seconds: 0 -- id: "1688322983719" +- id: '1688322983719' alias: Living Room - Door Occupancy - description: "" + description: '' trigger: - - platform: state - entity_id: - - binary_sensor.living_room_door_contact - from: - to: - - platform: state - entity_id: - - binary_sensor.living_room_motion_occupancy - from: - to: - - platform: homeassistant - event: start + - platform: state + entity_id: + - binary_sensor.living_room_door_contact + from: + to: + - platform: state + entity_id: + - binary_sensor.living_room_motion_occupancy + from: + to: + - platform: homeassistant + event: start condition: [] action: - - choose: - - conditions: - - condition: template - value_template: - "{% set door_id = 'binary_sensor.living_room_door_contact' - %}\n{% set timeout = 10 %}\n\n{% set door_activity_timeout = now() - timedelta(seconds - = timeout) %}\n{% set door_last_updated = states[door_id].last_updated %} - \n{% set door_recently_updated = door_last_updated >= door_activity_timeout - %}\n\n{{ door_recently_updated }}" - alias: Door contact recently had activity - sequence: - - service: variable.update_sensor - data: - replace_attributes: false - attributes: - icon: mdi:motion-sensor - value: "on" - target: - entity_id: sensor.living_room_door_occupancy - alias: Set door activity - - delay: - hours: 0 - minutes: 0 - seconds: 10 - milliseconds: 0 - - service: variable.update_sensor - data: - replace_attributes: false - value: "off" - attributes: - icon: mdi:motion-sensor-off - target: - entity_id: sensor.living_room_door_occupancy - alias: Unset door activity - - conditions: - - condition: state - entity_id: binary_sensor.living_room_door_contact - state: "on" - - condition: state - entity_id: binary_sensor.living_room_motion_occupancy - state: "on" - sequence: - - service: variable.update_sensor - data: - replace_attributes: false - value: "on" - attributes: - icon: mdi:motion-sensor - target: - entity_id: sensor.living_room_door_occupancy - alias: Set door activity - default: - - service: variable.update_sensor - data: - replace_attributes: false - value: "off" - attributes: - icon: mdi:motion-sensor-off - target: - entity_id: sensor.living_room_door_occupancy - alias: Unset door activity + - choose: + - conditions: + - condition: template + value_template: "{% set door_id = 'binary_sensor.living_room_door_contact' + %}\n{% set timeout = 10 %}\n\n{% set door_activity_timeout = now() - timedelta(seconds + = timeout) %}\n{% set door_last_updated = states[door_id].last_updated %} + \n{% set door_recently_updated = door_last_updated >= door_activity_timeout + %}\n\n{{ door_recently_updated }}" + alias: Door contact recently had activity + sequence: + - service: variable.update_sensor + data: + replace_attributes: false + attributes: + icon: mdi:motion-sensor + value: 'on' + target: + entity_id: sensor.living_room_door_occupancy + alias: Set door activity + - delay: + hours: 0 + minutes: 0 + seconds: 10 + milliseconds: 0 + - service: variable.update_sensor + data: + replace_attributes: false + value: 'off' + attributes: + icon: mdi:motion-sensor-off + target: + entity_id: sensor.living_room_door_occupancy + alias: Unset door activity + - conditions: + - condition: state + entity_id: binary_sensor.living_room_door_contact + state: 'on' + - condition: state + entity_id: binary_sensor.living_room_motion_occupancy + state: 'on' + sequence: + - service: variable.update_sensor + data: + replace_attributes: false + value: 'on' + attributes: + icon: mdi:motion-sensor + target: + entity_id: sensor.living_room_door_occupancy + alias: Set door activity + default: + - service: variable.update_sensor + data: + replace_attributes: false + value: 'off' + attributes: + icon: mdi:motion-sensor-off + target: + entity_id: sensor.living_room_door_occupancy + alias: Unset door activity mode: single -- id: "1688367427268" +- id: '1688367427268' alias: Hallway - Presence - description: "" + description: '' trigger: - - platform: state - entity_id: - - sensor.living_room_door_occupancy - id: doors - from: "off" - to: "on" - - platform: state - entity_id: - - input_select.hallway_presence - from: - to: - id: state - - platform: state - entity_id: - - timer.hallway_transition_timer - from: - to: - id: timer - - platform: state - entity_id: - - binary_sensor.hallway_human - from: - to: - id: human + - platform: state + entity_id: + - sensor.living_room_door_occupancy + id: doors + from: 'off' + to: 'on' + - platform: state + entity_id: + - input_select.hallway_presence + from: + to: + id: state + - platform: state + entity_id: + - timer.hallway_transition_timer + from: + to: + id: timer + - platform: state + entity_id: + - binary_sensor.hallway_human + from: + to: + id: human condition: [] action: - - choose: + - choose: + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: absent + sequence: + - choose: - conditions: - - condition: state + - condition: trigger + id: doors + sequence: + - service: input_select.select_option + data: + option: entering + target: entity_id: input_select.hallway_presence - state: absent + - conditions: + - condition: trigger + id: state sequence: - - choose: - - conditions: - - condition: trigger - id: doors - sequence: - - service: input_select.select_option - data: - option: entering - target: - entity_id: input_select.hallway_presence - - conditions: - - condition: trigger - id: state - sequence: - - service: timer.cancel - data: {} - target: - entity_id: timer.hallway_transition_timer + - service: timer.cancel + data: {} + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: entering + sequence: + - choose: - conditions: - - condition: state - entity_id: input_select.hallway_presence - state: entering + - condition: trigger + id: state sequence: - - choose: - - conditions: - - condition: trigger - id: state - sequence: - - service: timer.start - data: - duration: 00:00:10 - target: - entity_id: timer.hallway_transition_timer - - conditions: - - condition: trigger - id: timer - - condition: state - entity_id: timer.hallway_transition_timer - state: idle - sequence: - - service: input_select.select_option - data: - option: absent - target: - entity_id: input_select.hallway_presence - - stop: Back to absent. - - conditions: - - condition: trigger - id: doors - sequence: - - service: timer.change - data: - duration: 00:00:10 - target: - entity_id: timer.hallway_transition_timer - - if: - - condition: state - entity_id: binary_sensor.hallway_human - state: "on" - then: - - service: input_select.select_option - data: - option: present - target: - entity_id: input_select.hallway_presence + - service: timer.start + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer - conditions: - - condition: state + - condition: trigger + id: timer + - condition: state + entity_id: timer.hallway_transition_timer + state: idle + sequence: + - service: input_select.select_option + data: + option: absent + target: entity_id: input_select.hallway_presence - state: present + - stop: Back to absent. + - conditions: + - condition: trigger + id: doors + sequence: + - service: timer.change + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - if: + - condition: state + entity_id: binary_sensor.hallway_human + state: 'on' + then: + - service: input_select.select_option + data: + option: present + target: + entity_id: input_select.hallway_presence + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: present + sequence: + - choose: + - conditions: + - condition: trigger + id: state sequence: - - choose: - - conditions: - - condition: trigger - id: state - sequence: - - service: timer.cancel - data: {} - target: - entity_id: timer.hallway_transition_timer - - conditions: - - condition: trigger - id: doors - sequence: - - service: input_select.select_option - data: - option: leaving - target: - entity_id: input_select.hallway_presence + - service: timer.cancel + data: {} + target: + entity_id: timer.hallway_transition_timer - conditions: - - condition: state + - condition: trigger + id: doors + sequence: + - service: input_select.select_option + data: + option: leaving + target: entity_id: input_select.hallway_presence - state: leaving + - conditions: + - condition: state + entity_id: input_select.hallway_presence + state: leaving + sequence: + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.start + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: timer + - condition: state + entity_id: timer.hallway_transition_timer + state: idle sequence: - - choose: - - conditions: - - condition: trigger - id: state - sequence: - - service: timer.start - data: - duration: 00:00:10 - target: - entity_id: timer.hallway_transition_timer - - conditions: - - condition: trigger - id: timer - - condition: state - entity_id: timer.hallway_transition_timer - state: idle - sequence: - - service: input_select.select_option - data: - option: present - target: - entity_id: input_select.hallway_presence - alias: Back to present - - stop: Back to present - - if: - - condition: state - entity_id: binary_sensor.hallway_human - state: "off" - then: - - service: input_select.select_option - data: - option: absent - target: - entity_id: input_select.hallway_presence - alias: Select absent + - service: input_select.select_option + data: + option: present + target: + entity_id: input_select.hallway_presence + alias: Back to present + - stop: Back to present + - if: + - condition: state + entity_id: binary_sensor.hallway_human + state: 'off' + then: + - service: input_select.select_option + data: + option: absent + target: + entity_id: input_select.hallway_presence + alias: Select absent mode: queued diff --git a/home-assistant/zigbee2mqtt-data/configuration.yaml b/home-assistant/zigbee2mqtt-data/configuration.yaml index 861bfe822..1fc620d0e 100644 --- a/home-assistant/zigbee2mqtt-data/configuration.yaml +++ b/home-assistant/zigbee2mqtt-data/configuration.yaml @@ -4,7 +4,7 @@ mqtt: base_topic: zigbee2mqtt server: mqtt://mqtt keepalive: 60 - password: "!secret mqtt_password" + password: '!secret mqtt_password' reject_unauthorized: true user: zigbee2mqtt version: 4 @@ -13,66 +13,63 @@ serial: frontend: port: 8080 advanced: - network_key: "!secret network_key" + network_key: '!secret network_key' pan_id: 6755 homeassistant_legacy_entity_attributes: false legacy_api: false legacy_availability_payload: false + log_level: info channel: 25 device_options: legacy: false homeassistant: true devices: - "0x54ef4410004d9481": + '0x54ef4410004d9481': friendly_name: office_human - "0x00158d000232d0f6": + '0x00158d000232d0f6': friendly_name: supply_closet_door - "0x00158d0002386344": + '0x00158d0002386344': friendly_name: toilet_door - "0x00158d0002286aa4": + '0x00158d0002286aa4': friendly_name: living_room_door - "0x0017880103298e01": + '0x0017880103298e01': friendly_name: laundry_room_motion - "0x54ef441000491b56": + '0x54ef441000491b56': friendly_name: bathroom_motion - "0x00158d000854274f": + '0x00158d000854274f': friendly_name: garden_door - "0x54ef4410004973e7": + '0x54ef4410004973e7': friendly_name: landing_motion - "0x54ef4410004972a3": + '0x54ef4410004972a3': friendly_name: living_room_motion - "0x00158d0007e75b53": + '0x00158d0007e75b53': friendly_name: bathroom_door - "0x00158d0007ef9f06": + '0x00158d0007ef9f06': friendly_name: bedroom_door - "0x84fd27fffe953a35": + '0x84fd27fffe953a35': friendly_name: bedroom_2_button - "0x2c1165fffe6dc628": + '0x2c1165fffe6dc628': friendly_name: bedroom_1_button - "0x54ef441000490f71": + '0x54ef441000490f71': friendly_name: toilet_motion - "0x00158d000854aea1": + '0x00158d000854aea1': friendly_name: baby_room_door - "0x54ef4410004919b1": + '0x54ef4410004919b1': friendly_name: hallway_motion - "0x54ef441000491875": + '0x54ef441000491875': friendly_name: living_room_garden_motion - "0x00158d0002286a8a": + '0x00158d0002286a8a': friendly_name: hallway_door - "0x54ef4410001ff9a4": + '0x54ef4410001ff9a4': friendly_name: office_illuminance - "0x54ef4410001ff3da": + '0x54ef4410001ff3da': friendly_name: baby_room_illuminance - "0x54ef4410001ff967": - friendly_name: hallway_illuminance - "0x54ef4410004e44a9": + '0x54ef4410004e44a9': friendly_name: bedroom_motion - "0x54ef4410004e39af": + '0x54ef4410004e39af': friendly_name: office_motion - "0x54ef4410004e3ea7": + '0x54ef4410004e3ea7': friendly_name: baby_room_motion - "0x00158d00085428bc": + '0x00158d00085428bc': friendly_name: office_door - "0x00124b00258f1af0": - friendly_name: hallway_router diff --git a/workstation/deploys/terminal/files/sheldon/plugins.lock b/workstation/deploys/terminal/files/sheldon/plugins.lock index a1328b14c..63d9fa573 100644 --- a/workstation/deploys/terminal/files/sheldon/plugins.lock +++ b/workstation/deploys/terminal/files/sheldon/plugins.lock @@ -1,4 +1,4 @@ -version = "0.7.1" +version = "0.7.3" home = "/Users/maarten" config_dir = "/Users/maarten/.sheldon" data_dir = "/Users/maarten/.sheldon" @@ -10,32 +10,42 @@ source_dir = "/Users/maarten/.sheldon/repos/github.com/sindresorhus/pure" files = ["/Users/maarten/.sheldon/repos/github.com/sindresorhus/pure/async.zsh", "/Users/maarten/.sheldon/repos/github.com/sindresorhus/pure/pure.zsh"] apply = ["source"] +[plugins.hooks] + [[plugins]] name = "zsh-autosuggestions" source_dir = "/Users/maarten/.sheldon/repos/github.com/zsh-users/zsh-autosuggestions" files = ["/Users/maarten/.sheldon/repos/github.com/zsh-users/zsh-autosuggestions/zsh-autosuggestions.zsh"] apply = ["source"] +[plugins.hooks] + [[plugins]] name = "zsh-syntax-highlighting" source_dir = "/Users/maarten/.sheldon/repos/github.com/zsh-users/zsh-syntax-highlighting" files = ["/Users/maarten/.sheldon/repos/github.com/zsh-users/zsh-syntax-highlighting/zsh-syntax-highlighting.plugin.zsh"] apply = ["source"] +[plugins.hooks] + [[plugins]] name = "zsh-z" source_dir = "/Users/maarten/.sheldon/repos/github.com/agkozak/zsh-z" files = ["/Users/maarten/.sheldon/repos/github.com/agkozak/zsh-z/zsh-z.plugin.zsh"] apply = ["source"] +[plugins.hooks] + [[plugins]] name = "atuin" source_dir = "/Users/maarten/.sheldon/repos/github.com/ellie/atuin" files = ["/Users/maarten/.sheldon/repos/github.com/ellie/atuin/atuin.plugin.zsh"] apply = ["source"] +[plugins.hooks] + [templates] PATH = "export PATH=\"{{ dir }}:$PATH\"" path = "path=( \"{{ dir }}\" $path )" fpath = "fpath=( \"{{ dir }}\" $fpath )" -source = "{% for file in files %}source \"{{ file }}\"\n{% endfor %}" +source = "{{ hooks | get: \"pre\" | nl }}{% for file in files %}source \"{{ file }}\"\n{% endfor %}{{ hooks | get: \"post\" | nl }}" From 49c5a7597d91e2ef071fc68cd08fefc17bdf8488 Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Thu, 24 Aug 2023 15:31:20 +0200 Subject: [PATCH 10/11] changes --- .devcontainer.json | 3 +- .vscode/settings.json | 4 +- BUILD.bazel | 5 +- .../zigbee2mqtt-data/configuration.yaml | 53 +++++++++---------- tools/devcontainer/Dockerfile | 9 ---- 5 files changed, 33 insertions(+), 41 deletions(-) diff --git a/.devcontainer.json b/.devcontainer.json index 8c2c4ee9a..298806c13 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -28,8 +28,7 @@ "files.associations": { ".bazelrc": "plaintext" }, - "editor.formatOnSave": true, - "prettier.prettierPath": "/opt/bin/prettier" + "editor.formatOnSave": true } } }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a2c35e0f..9092b8663 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,6 @@ "search.exclude": { "home-assistant/config/custom_components/**/*": true }, - "prettier.prettierPath": "/opt/bin/prettier", - "bazel.buildifierExecutable": "/opt/bin/buildifier" + "bazel.buildifierExecutable": "/opt/bin/buildifier", + "prettier.resolveGlobalModules": false } diff --git a/BUILD.bazel b/BUILD.bazel index 2a5d7a0ac..01a7d865d 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -45,7 +45,10 @@ prettier_bin.prettier_binary( task( name = "prettier", cmds = [ - cmd.executable(":prettier_bin"), + cmd.shell( + cmd.executable(":prettier_bin"), + "$CLI_ARGS", + ), ], cwd = "{{ os.environ.get('BUILD_WORKING_DIRECTORY', os.getcwd()) }}", env = { diff --git a/home-assistant/zigbee2mqtt-data/configuration.yaml b/home-assistant/zigbee2mqtt-data/configuration.yaml index 1fc620d0e..cf69d320f 100644 --- a/home-assistant/zigbee2mqtt-data/configuration.yaml +++ b/home-assistant/zigbee2mqtt-data/configuration.yaml @@ -4,7 +4,7 @@ mqtt: base_topic: zigbee2mqtt server: mqtt://mqtt keepalive: 60 - password: '!secret mqtt_password' + password: "!secret mqtt_password" reject_unauthorized: true user: zigbee2mqtt version: 4 @@ -13,63 +13,62 @@ serial: frontend: port: 8080 advanced: - network_key: '!secret network_key' + network_key: "!secret network_key" pan_id: 6755 homeassistant_legacy_entity_attributes: false legacy_api: false legacy_availability_payload: false - log_level: info channel: 25 device_options: legacy: false homeassistant: true devices: - '0x54ef4410004d9481': + "0x54ef4410004d9481": friendly_name: office_human - '0x00158d000232d0f6': + "0x00158d000232d0f6": friendly_name: supply_closet_door - '0x00158d0002386344': + "0x00158d0002386344": friendly_name: toilet_door - '0x00158d0002286aa4': + "0x00158d0002286aa4": friendly_name: living_room_door - '0x0017880103298e01': + "0x0017880103298e01": friendly_name: laundry_room_motion - '0x54ef441000491b56': + "0x54ef441000491b56": friendly_name: bathroom_motion - '0x00158d000854274f': + "0x00158d000854274f": friendly_name: garden_door - '0x54ef4410004973e7': + "0x54ef4410004973e7": friendly_name: landing_motion - '0x54ef4410004972a3': + "0x54ef4410004972a3": friendly_name: living_room_motion - '0x00158d0007e75b53': + "0x00158d0007e75b53": friendly_name: bathroom_door - '0x00158d0007ef9f06': + "0x00158d0007ef9f06": friendly_name: bedroom_door - '0x84fd27fffe953a35': + "0x84fd27fffe953a35": friendly_name: bedroom_2_button - '0x2c1165fffe6dc628': + "0x2c1165fffe6dc628": friendly_name: bedroom_1_button - '0x54ef441000490f71': + "0x54ef441000490f71": friendly_name: toilet_motion - '0x00158d000854aea1': + "0x00158d000854aea1": friendly_name: baby_room_door - '0x54ef4410004919b1': + "0x54ef4410004919b1": friendly_name: hallway_motion - '0x54ef441000491875': + "0x54ef441000491875": friendly_name: living_room_garden_motion - '0x00158d0002286a8a': + "0x00158d0002286a8a": friendly_name: hallway_door - '0x54ef4410001ff9a4': + "0x54ef4410001ff9a4": friendly_name: office_illuminance - '0x54ef4410001ff3da': + "0x54ef4410001ff3da": friendly_name: baby_room_illuminance - '0x54ef4410004e44a9': + "0x54ef4410004e44a9": friendly_name: bedroom_motion - '0x54ef4410004e39af': + "0x54ef4410004e39af": friendly_name: office_motion - '0x54ef4410004e3ea7': + "0x54ef4410004e3ea7": friendly_name: baby_room_motion - '0x00158d00085428bc': + "0x00158d00085428bc": friendly_name: office_door diff --git a/tools/devcontainer/Dockerfile b/tools/devcontainer/Dockerfile index e129df3b6..0e70daad8 100644 --- a/tools/devcontainer/Dockerfile +++ b/tools/devcontainer/Dockerfile @@ -93,15 +93,6 @@ RUN case ${TARGETARCH} in \ && curl --fail -L https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-${BAZELISK_ARCH} --output /usr/local/bin/bazel \ && chmod +x /usr/local/bin/bazel -# Install bazel remote for Bazel S3 caching in the CI -ENV BAZEL_REMOTE_VERSION="2.3.9" -RUN case ${TARGETARCH} in \ - "amd64") BAZEL_REMOTE_ARCH=x86_64 ;; \ - "arm64") BAZEL_REMOTE_ARCH=arm64 ;; \ - esac \ - && curl --fail -L https://github.com/buchgr/bazel-remote/releases/download/v$BAZEL_REMOTE_VERSION/bazel-remote-$BAZEL_REMOTE_VERSION-linux-$BAZEL_REMOTE_ARCH --output /usr/local/bin/bazel-remote \ - && chmod +x /usr/local/bin/bazel-remote - # Install devcontainer specific bazelrc file ADD --chown=devcontainer:devcontainer tools/devcontainer/.bazelrc /home/devcontainer/.bazelrc From c4a6311ebb101a4e400cb99eac0068f309d69e59 Mon Sep 17 00:00:00 2001 From: mvgijssel <6029816+mvgijssel@users.noreply.github.com> Date: Thu, 24 Aug 2023 15:33:20 +0200 Subject: [PATCH 11/11] fix prettier --- home-assistant/config/automations.yaml | 1120 ++++++++++++------------ 1 file changed, 564 insertions(+), 556 deletions(-) diff --git a/home-assistant/config/automations.yaml b/home-assistant/config/automations.yaml index 930305e3d..fccacf691 100644 --- a/home-assistant/config/automations.yaml +++ b/home-assistant/config/automations.yaml @@ -1,114 +1,114 @@ -- id: '1655749208565' - alias: 'Supply Closet - Presence: Manage' - description: '' +- id: "1655749208565" + alias: "Supply Closet - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.supply_closet_presence presence_indicator_entity_ids: - - binary_sensor.supply_closet_door_contact + - binary_sensor.supply_closet_door_contact presence_hint_entity_ids: - - binary_sensor.supply_closet_shelly_input + - binary_sensor.supply_closet_shelly_input presence_timeout: 0 -- id: '1655751397719' - alias: 'Toilet - Presence: Manage' - description: '' +- id: "1655751397719" + alias: "Toilet - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.toilet_presence presence_indicator_entity_ids: - - binary_sensor.toilet_motion_occupancy + - binary_sensor.toilet_motion_occupancy presence_hint_entity_ids: - - binary_sensor.toilet_shelly_input_1 - - binary_sensor.toilet_shelly_input_2 - - binary_sensor.toilet_door_contact + - binary_sensor.toilet_shelly_input_1 + - binary_sensor.toilet_shelly_input_2 + - binary_sensor.toilet_door_contact presence_timeout: 180 -- id: '1655751783256' - alias: 'Driveway - Presence: Manage' - description: '' +- id: "1655751783256" + alias: "Driveway - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.driveway_presence presence_indicator_entity_ids: - - binary_sensor.driveway_doorbell_motion - - binary_sensor.hallway_door_contact + - binary_sensor.driveway_doorbell_motion + - binary_sensor.hallway_door_contact presence_hint_entity_ids: - - binary_sensor.driveway_shelly_input + - binary_sensor.driveway_shelly_input presence_timeout: 120 -- id: '1655752570246' - alias: 'Hallway - Presence: Manage' - description: '' +- id: "1655752570246" + alias: "Hallway - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.hallway_presence presence_hint_entity_ids: - - binary_sensor.hallway_shelly_input - - binary_sensor.toilet_door_contact - - binary_sensor.living_room_door_contact - - binary_sensor.driveway_shelly_input - - binary_sensor.landing_motion_occupancy - - binary_sensor.living_room_motion_occupancy - - binary_sensor.toilet_motion_occupancy + - binary_sensor.hallway_shelly_input + - binary_sensor.toilet_door_contact + - binary_sensor.living_room_door_contact + - binary_sensor.driveway_shelly_input + - binary_sensor.landing_motion_occupancy + - binary_sensor.living_room_motion_occupancy + - binary_sensor.toilet_motion_occupancy presence_timeout: 120 presence_indicator_entity_ids: - - binary_sensor.hallway_motion_occupancy - - binary_sensor.hallway_door_contact -- id: '1655752934365' - alias: 'Laundry Room - Presence: Manage' - description: '' + - binary_sensor.hallway_motion_occupancy + - binary_sensor.hallway_door_contact +- id: "1655752934365" + alias: "Laundry Room - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.laundry_room_presence presence_indicator_entity_ids: - - binary_sensor.laundry_room_motion_occupancy + - binary_sensor.laundry_room_motion_occupancy presence_hint_entity_ids: - - binary_sensor.laundry_room_shelly_input -- id: '1655753137217' - alias: 'Office - Presence: Manage' - description: '' + - binary_sensor.laundry_room_shelly_input +- id: "1655753137217" + alias: "Office - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.office_presence presence_indicator_entity_ids: - - binary_sensor.office_human_presence - - binary_sensor.office_motion_occupancy + - binary_sensor.office_human_presence + - binary_sensor.office_motion_occupancy presence_hint_entity_ids: - - binary_sensor.office_door_contact - - binary_sensor.office_shelly_input -- id: '1655755184933' - alias: 'Driveway - Light: Manage' - description: '' + - binary_sensor.office_door_contact + - binary_sensor.office_shelly_input +- id: "1655755184933" + alias: "Driveway - Light: Manage" + description: "" use_blueprint: path: diy/auto_lights.yaml input: presence_entity_id: sensor.driveway_presence light_and_switch_entity_ids: - - switch.driveway_shelly + - switch.driveway_shelly start_time: 00:00:00 - end_time: '23:59:59' + end_time: "23:59:59" illuminance_entity_id: sensor.illuminance illuminance_threshold: 1000 -- id: '1655798724103' - alias: 'Hallway - Light: Manage' - description: '' +- id: "1655798724103" + alias: "Hallway - Light: Manage" + description: "" use_blueprint: path: diy/auto_lights.yaml input: presence_entity_id: sensor.hallway_presence illuminance_entity_id: sensor.hallway_motion_illuminance_lux light_and_switch_entity_ids: - - light.hallway_lights + - light.hallway_lights start_time: 00:00:00 - end_time: '23:59:59' + end_time: "23:59:59" illuminance_threshold: 1000 -- id: '1655799885346' - alias: 'Laundry Room - Light: Manage' - description: '' +- id: "1655799885346" + alias: "Laundry Room - Light: Manage" + description: "" use_blueprint: path: diy/auto_lights.yaml input: @@ -116,25 +116,25 @@ illuminance_entity_id: sensor.laundry_room_motion_illuminance_lux illuminance_threshold: 100 light_and_switch_entity_ids: - - switch.laundry_room_shelly + - switch.laundry_room_shelly start_time: 07:00:00 - end_time: '21:00:00' -- id: '1655800011564' - alias: 'Office - Light: Manage' - description: '' + end_time: "21:00:00" +- id: "1655800011564" + alias: "Office - Light: Manage" + description: "" use_blueprint: path: diy/auto_lights.yaml input: presence_entity_id: sensor.office_presence illuminance_entity_id: sensor.office_illuminance_illuminance_lux light_and_switch_entity_ids: - - switch.office_shelly + - switch.office_shelly start_time: 07:00:00 - end_time: '23:00:00' + end_time: "23:00:00" illuminance_threshold: 40 -- id: '1655800068148' - alias: 'Supply Closet - Light: Manage' - description: '' +- id: "1655800068148" + alias: "Supply Closet - Light: Manage" + description: "" use_blueprint: path: diy/auto_lights.yaml input: @@ -142,12 +142,12 @@ illuminance_entity_id: sensor.illuminance illuminance_threshold: 200000 light_and_switch_entity_ids: - - switch.supply_closet_shelly + - switch.supply_closet_shelly start_time: 00:00:00 - end_time: '23:59:59' -- id: '1655800128048' - alias: 'Toilet - Light: Manage' - description: '' + end_time: "23:59:59" +- id: "1655800128048" + alias: "Toilet - Light: Manage" + description: "" use_blueprint: path: diy/auto_lights.yaml input: @@ -155,300 +155,307 @@ illuminance_entity_id: sensor.illuminance illuminance_threshold: 200000 light_and_switch_entity_ids: - - light.toilet_lights + - light.toilet_lights start_time: 00:00:00 - end_time: '23:59:59' -- id: '1655926470261' - alias: 'Kitchen - Presence: Manage' - description: '' + end_time: "23:59:59" +- id: "1655926470261" + alias: "Kitchen - Presence: Manage" + description: "" use_blueprint: path: diy/presence.yaml input: presence_entity_id: sensor.kitchen_presence presence_indicator_entity_ids: - - binary_sensor.kitchen_human_presence -- id: '1657026314050' - alias: 'Bedroom - Sleeping: Manage' - description: '' + - binary_sensor.kitchen_human_presence +- id: "1657026314050" + alias: "Bedroom - Sleeping: Manage" + description: "" trigger: - - platform: state - entity_id: - - sensor.bedroom_1_button_action - to: 'on' - id: button_1 - - platform: state - entity_id: - - sensor.bedroom_2_button_action - id: button_2 - to: 'on' - - platform: time - at: '22:00:00' - id: evening_time - - platform: time - at: 07:00:00 - id: morning_time + - platform: state + entity_id: + - sensor.bedroom_1_button_action + to: "on" + id: button_1 + - platform: state + entity_id: + - sensor.bedroom_2_button_action + id: button_2 + to: "on" + - platform: time + at: "22:00:00" + id: evening_time + - platform: time + at: 07:00:00 + id: morning_time condition: [] action: - - if: - - condition: trigger - id: button_1 - then: - - wait_for_trigger: - - platform: state - entity_id: - - sensor.bedroom_1_button_action - to: 'on' - timeout: '1' - if: - - condition: template - value_template: '{{ wait.remaining > 0 }} ' + - condition: trigger + id: button_1 then: - - service: input_boolean.turn_off - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - else: - - service: input_boolean.turn_on - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - - if: - - condition: trigger - id: button_2 - then: - - wait_for_trigger: - - platform: state - entity_id: - - sensor.bedroom_2_button_action - to: 'on' - timeout: '1' + - wait_for_trigger: + - platform: state + entity_id: + - sensor.bedroom_1_button_action + to: "on" + timeout: "1" + - if: + - condition: template + value_template: "{{ wait.remaining > 0 }} " + then: + - service: input_boolean.turn_off + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + else: + - service: input_boolean.turn_on + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping - if: - - condition: template - value_template: '{{ wait.remaining > 0 }} ' + - condition: trigger + id: button_2 then: - - service: input_boolean.turn_off - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - else: - - service: input_boolean.turn_on - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - - if: - - condition: or - conditions: - - condition: trigger - id: evening_time - - condition: trigger - id: morning_time - then: + - wait_for_trigger: + - platform: state + entity_id: + - sensor.bedroom_2_button_action + to: "on" + timeout: "1" + - if: + - condition: template + value_template: "{{ wait.remaining > 0 }} " + then: + - service: input_boolean.turn_off + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + else: + - service: input_boolean.turn_on + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping - if: - - condition: time - after: '22:00:00' - before: 07:00:00 + - condition: or + conditions: + - condition: trigger + id: evening_time + - condition: trigger + id: morning_time then: - - service: input_boolean.turn_on - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping - else: - - service: input_boolean.turn_off - data: {} - target: - entity_id: input_boolean.bedroom_is_sleeping + - if: + - condition: time + after: "22:00:00" + before: 07:00:00 + then: + - service: input_boolean.turn_on + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping + else: + - service: input_boolean.turn_off + data: {} + target: + entity_id: input_boolean.bedroom_is_sleeping mode: single -- id: '1658226673278' - alias: 'Adaptive lighting: toggle ''sleep mode''' - description: '' +- id: "1658226673278" + alias: "Adaptive lighting: toggle 'sleep mode'" + description: "" trigger: - - platform: state - entity_id: - - input_boolean.bedroom_is_sleeping - - platform: homeassistant - event: start + - platform: state + entity_id: + - input_boolean.bedroom_is_sleeping + - platform: homeassistant + event: start variables: - sleep_mode: '{{ states(''input_boolean.bedroom_is_sleeping'') }}' + sleep_mode: "{{ states('input_boolean.bedroom_is_sleeping') }}" condition: [] action: service: switch.turn_{{ sleep_mode }} entity_id: - - switch.adaptive_lighting_sleep_mode_baby_room - - switch.adaptive_lighting_sleep_mode_landing - - switch.adaptive_lighting_sleep_mode_hallway + - switch.adaptive_lighting_sleep_mode_baby_room + - switch.adaptive_lighting_sleep_mode_landing + - switch.adaptive_lighting_sleep_mode_hallway mode: single -- id: '1664274065748' +- id: "1664274065748" alias: Driveway - Doorbell Play Chime - description: '' + description: "" trigger: - - platform: state - entity_id: - - binary_sensor.driveway_doorbell_doorbell - from: 'off' - to: 'on' + - platform: state + entity_id: + - binary_sensor.driveway_doorbell_doorbell + from: "off" + to: "on" condition: [] action: - - parallel: - - service: sonos.snapshot - data: - entity_id: media_player.woonkamer - - service: sonos.snapshot - data: - entity_id: media_player.sonos_move - - service: media_player.volume_set - data: - volume_level: 0.65 - target: - entity_id: - - media_player.woonkamer - - media_player.sonos_move - - service: media_player.play_media - data: - media_content_type: music - media_content_id: http://hypervisor:8123/local/sounds_sounds_ring_button_Chime.mp3 - target: - entity_id: - - media_player.woonkamer - - media_player.sonos_move - - delay: - hours: 0 - minutes: 0 - seconds: 4 - milliseconds: 0 - - parallel: - - service: sonos.restore + - parallel: + - service: sonos.snapshot + data: + entity_id: media_player.woonkamer + - service: sonos.snapshot + data: + entity_id: media_player.sonos_move + - service: media_player.volume_set data: - entity_id: media_player.woonkamer - - service: sonos.restore + volume_level: 0.65 + target: + entity_id: + - media_player.woonkamer + - media_player.sonos_move + - service: media_player.play_media data: - entity_id: media_player.sonos_move + media_content_type: music + media_content_id: http://hypervisor:8123/local/sounds_sounds_ring_button_Chime.mp3 + target: + entity_id: + - media_player.woonkamer + - media_player.sonos_move + - delay: + hours: 0 + minutes: 0 + seconds: 4 + milliseconds: 0 + - parallel: + - service: sonos.restore + data: + entity_id: media_player.woonkamer + - service: sonos.restore + data: + entity_id: media_player.sonos_move mode: single -- id: '1674554618790' +- id: "1674554618790" alias: Apple Watch Location 1 trace: stored_traces: 300 - description: '' + description: "" trigger: - - platform: mqtt - topic: home/location/bluetooth-00:00:00:00:00:00 - id: mqtt - - platform: time_pattern - seconds: /10 - id: time + - platform: mqtt + topic: home/location/bluetooth-00:00:00:00:00:00 + id: mqtt + - platform: time_pattern + seconds: /10 + id: time condition: [] action: - - if: - - condition: trigger - id: time - then: - if: - - condition: template - value_template: '{% set the_past = state_attr(''sensor.apple_watch_1_location'', - ''last_updated'') %} + - condition: trigger + id: time + then: + - if: + - condition: template + value_template: + "{% set the_past = state_attr('sensor.apple_watch_1_location', + 'last_updated') %} - {% set the_present = now() %} + {% set the_present = now() %} - {% set seconds_passed = as_timestamp(the_present)-as_timestamp(the_past) - %} + {% set seconds_passed = as_timestamp(the_present)-as_timestamp(the_past) + %} - {{ seconds_passed > 120 }}' + {{ seconds_passed > 120 }}" + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + value: away + attributes: + next_location: away + next_location_iteration: 0 + next_location_duration: 0 + icon: mdi:home-outline + last_updated: "{{ now() }}" + - stop: Done checking away status + alias: Check for away status + - if: + - condition: template + value_template: + "{{ trigger.payload_json.location == states('sensor.apple_watch_1_location') + }}" then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - value: away - attributes: - next_location: away - next_location_iteration: 0 - next_location_duration: 0 - icon: mdi:home-outline - last_updated: '{{ now() }}' - - stop: Done checking away status - alias: Check for away status - - if: - - condition: template - value_template: '{{ trigger.payload_json.location == states(''sensor.apple_watch_1_location'') - }}' - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location: '{{ states(''sensor.apple_watch_1_location'') }}' - next_location_iteration: 0 - icon: mdi:home - last_updated: '{{ now() }}' - - stop: Nothing changed - alias: same location - - if: - - condition: template - value_template: '{{ trigger.payload_json.guesses[0].probability < 0.5 }}' - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location: '{{ states(''sensor.apple_watch_1_location'') }}' - next_location_iteration: 0 - icon: mdi:home - last_updated: '{{ now() }}' - - stop: Probability too low - alias: low probability - - if: - - condition: template - value_template: '{{ trigger.payload_json.location != state_attr(''sensor.apple_watch_1_location'', - ''next_location'') }}' - then: - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location: '{{ trigger.payload_json.location }}' - next_location_iteration: 0 - icon: mdi:home - last_updated: '{{ now() }}' - alias: Update next location - - service: variable.update_sensor - target: - entity_id: sensor.apple_watch_1_location - data: - attributes: - next_location_iteration: '{{ (state_attr(''sensor.apple_watch_1_location'', - ''next_location_iteration'') | int(default=0)) + 1 }}' - alias: Next location iteration increment - - if: - - condition: template - value_template: '{{ (state_attr(''sensor.apple_watch_1_location'', ''next_location_iteration'') - | int(default=0)) > 1 }}' - then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + attributes: + next_location: "{{ states('sensor.apple_watch_1_location') }}" + next_location_iteration: 0 + icon: mdi:home + last_updated: "{{ now() }}" + - stop: Nothing changed + alias: same location + - if: + - condition: template + value_template: "{{ trigger.payload_json.guesses[0].probability < 0.5 }}" + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + attributes: + next_location: "{{ states('sensor.apple_watch_1_location') }}" + next_location_iteration: 0 + icon: mdi:home + last_updated: "{{ now() }}" + - stop: Probability too low + alias: low probability + - if: + - condition: template + value_template: + "{{ trigger.payload_json.location != state_attr('sensor.apple_watch_1_location', + 'next_location') }}" + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + attributes: + next_location: "{{ trigger.payload_json.location }}" + next_location_iteration: 0 + icon: mdi:home + last_updated: "{{ now() }}" + alias: Update next location - service: variable.update_sensor target: entity_id: sensor.apple_watch_1_location data: - value: '{{ state_attr(''sensor.apple_watch_1_location'', ''next_location'') - }}' attributes: - next_location: '{{ state_attr(''sensor.apple_watch_1_location'', ''next_location'') - }}' - next_location_iteration: 0 - icon: mdi:home - last_updated: '{{ now() }}' - alias: Change location + next_location_iteration: + "{{ (state_attr('sensor.apple_watch_1_location', + 'next_location_iteration') | int(default=0)) + 1 }}" + alias: Next location iteration increment + - if: + - condition: template + value_template: + "{{ (state_attr('sensor.apple_watch_1_location', 'next_location_iteration') + | int(default=0)) > 1 }}" + then: + - service: variable.update_sensor + target: + entity_id: sensor.apple_watch_1_location + data: + value: + "{{ state_attr('sensor.apple_watch_1_location', 'next_location') + }}" + attributes: + next_location: + "{{ state_attr('sensor.apple_watch_1_location', 'next_location') + }}" + next_location_iteration: 0 + icon: mdi:home + last_updated: "{{ now() }}" + alias: Change location mode: single -- id: '1687445709673' +- id: "1687445709673" alias: Living Room Dining Table - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_dining_table_human_presence light_and_switch_entity_ids: - - light.living_room_dining_table_light + - light.living_room_dining_table_light illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_dining_table_away_timer away_timer_duration: @@ -461,15 +468,15 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: '1687446669605' +- id: "1687446669605" alias: Living Room Kitchen - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_kitchen_human_presence light_and_switch_entity_ids: - - light.kitchen_lights + - light.kitchen_lights illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_kitchen_away_timer away_timer_duration: @@ -482,15 +489,15 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: '1687446932202' +- id: "1687446932202" alias: Living Room Reading - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_reading_human_presence light_and_switch_entity_ids: - - light.living_room_monkey_light + - light.living_room_monkey_light illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_reading_away_timer away_timer_duration: @@ -503,16 +510,16 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: '1687447110495' +- id: "1687447110495" alias: Living Room Sofa - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.living_room_sofa_human_presence light_and_switch_entity_ids: - - light.living_room_corner_light - - light.living_room_tv_light + - light.living_room_corner_light + - light.living_room_tv_light illuminance_entity_id: sensor.living_room_illuminance away_timer_entity_id: timer.living_room_sofa_away_timer away_timer_duration: @@ -525,9 +532,9 @@ minutes: 0 seconds: 0 illuminance_threshold: 150 -- id: '1687452619492' +- id: "1687452619492" alias: Bathroom Shower - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: @@ -544,20 +551,20 @@ minutes: 0 seconds: 0 light_and_switch_entity_ids: - - light.bathroom_1_light - - light.bathroom_2_light - - light.bathroom_3_light + - light.bathroom_1_light + - light.bathroom_2_light + - light.bathroom_3_light illuminance_threshold: 500 -- id: '1687452837238' +- id: "1687452837238" alias: Bathroom Washing - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: light_and_switch_entity_ids: - - light.bathroom_4_light - - light.bathroom_5_light - - light.bathroom_6_light + - light.bathroom_4_light + - light.bathroom_5_light + - light.bathroom_6_light illuminance_entity_id: sensor.bathroom_illuminance away_timer_entity_id: timer.bathroom_washing_away_timer away_timer_duration: @@ -571,15 +578,15 @@ seconds: 0 illuminance_threshold: 500 presence_entity_id: binary_sensor.bathroom_washing_human_presence -- id: '1687512815133' +- id: "1687512815133" alias: Bedroom - Lights - description: '' + description: "" use_blueprint: path: diy/better_auto_lights.yaml input: presence_entity_id: binary_sensor.bedroom_human_presence light_and_switch_entity_ids: - - switch.bedroom_shelly + - switch.bedroom_shelly illuminance_entity_id: sensor.bedroom_illuminance away_timer_entity_id: timer.bedroom_away_timer away_timer_duration: @@ -591,247 +598,248 @@ hours: 9 minutes: 0 seconds: 0 -- id: '1688322983719' +- id: "1688322983719" alias: Living Room - Door Occupancy - description: '' + description: "" trigger: - - platform: state - entity_id: - - binary_sensor.living_room_door_contact - from: - to: - - platform: state - entity_id: - - binary_sensor.living_room_motion_occupancy - from: - to: - - platform: homeassistant - event: start + - platform: state + entity_id: + - binary_sensor.living_room_door_contact + from: + to: + - platform: state + entity_id: + - binary_sensor.living_room_motion_occupancy + from: + to: + - platform: homeassistant + event: start condition: [] action: - - choose: - - conditions: - - condition: template - value_template: "{% set door_id = 'binary_sensor.living_room_door_contact' - %}\n{% set timeout = 10 %}\n\n{% set door_activity_timeout = now() - timedelta(seconds - = timeout) %}\n{% set door_last_updated = states[door_id].last_updated %} - \n{% set door_recently_updated = door_last_updated >= door_activity_timeout - %}\n\n{{ door_recently_updated }}" - alias: Door contact recently had activity - sequence: - - service: variable.update_sensor - data: - replace_attributes: false - attributes: - icon: mdi:motion-sensor - value: 'on' - target: - entity_id: sensor.living_room_door_occupancy - alias: Set door activity - - delay: - hours: 0 - minutes: 0 - seconds: 10 - milliseconds: 0 - - service: variable.update_sensor - data: - replace_attributes: false - value: 'off' - attributes: - icon: mdi:motion-sensor-off - target: - entity_id: sensor.living_room_door_occupancy - alias: Unset door activity - - conditions: - - condition: state - entity_id: binary_sensor.living_room_door_contact - state: 'on' - - condition: state - entity_id: binary_sensor.living_room_motion_occupancy - state: 'on' - sequence: - - service: variable.update_sensor - data: - replace_attributes: false - value: 'on' - attributes: - icon: mdi:motion-sensor - target: - entity_id: sensor.living_room_door_occupancy - alias: Set door activity - default: - - service: variable.update_sensor - data: - replace_attributes: false - value: 'off' - attributes: - icon: mdi:motion-sensor-off - target: - entity_id: sensor.living_room_door_occupancy - alias: Unset door activity + - choose: + - conditions: + - condition: template + value_template: + "{% set door_id = 'binary_sensor.living_room_door_contact' + %}\n{% set timeout = 10 %}\n\n{% set door_activity_timeout = now() - timedelta(seconds + = timeout) %}\n{% set door_last_updated = states[door_id].last_updated %} + \n{% set door_recently_updated = door_last_updated >= door_activity_timeout + %}\n\n{{ door_recently_updated }}" + alias: Door contact recently had activity + sequence: + - service: variable.update_sensor + data: + replace_attributes: false + attributes: + icon: mdi:motion-sensor + value: "on" + target: + entity_id: sensor.living_room_door_occupancy + alias: Set door activity + - delay: + hours: 0 + minutes: 0 + seconds: 10 + milliseconds: 0 + - service: variable.update_sensor + data: + replace_attributes: false + value: "off" + attributes: + icon: mdi:motion-sensor-off + target: + entity_id: sensor.living_room_door_occupancy + alias: Unset door activity + - conditions: + - condition: state + entity_id: binary_sensor.living_room_door_contact + state: "on" + - condition: state + entity_id: binary_sensor.living_room_motion_occupancy + state: "on" + sequence: + - service: variable.update_sensor + data: + replace_attributes: false + value: "on" + attributes: + icon: mdi:motion-sensor + target: + entity_id: sensor.living_room_door_occupancy + alias: Set door activity + default: + - service: variable.update_sensor + data: + replace_attributes: false + value: "off" + attributes: + icon: mdi:motion-sensor-off + target: + entity_id: sensor.living_room_door_occupancy + alias: Unset door activity mode: single -- id: '1688367427268' +- id: "1688367427268" alias: Hallway - Presence - description: '' + description: "" trigger: - - platform: state - entity_id: - - sensor.living_room_door_occupancy - id: doors - from: 'off' - to: 'on' - - platform: state - entity_id: - - input_select.hallway_presence - from: - to: - id: state - - platform: state - entity_id: - - timer.hallway_transition_timer - from: - to: - id: timer - - platform: state - entity_id: - - binary_sensor.hallway_human - from: - to: - id: human + - platform: state + entity_id: + - sensor.living_room_door_occupancy + id: doors + from: "off" + to: "on" + - platform: state + entity_id: + - input_select.hallway_presence + from: + to: + id: state + - platform: state + entity_id: + - timer.hallway_transition_timer + from: + to: + id: timer + - platform: state + entity_id: + - binary_sensor.hallway_human + from: + to: + id: human condition: [] action: - - choose: - - conditions: - - condition: state - entity_id: input_select.hallway_presence - state: absent - sequence: - - choose: + - choose: - conditions: - - condition: trigger - id: doors - sequence: - - service: input_select.select_option - data: - option: entering - target: + - condition: state entity_id: input_select.hallway_presence - - conditions: - - condition: trigger - id: state - sequence: - - service: timer.cancel - data: {} - target: - entity_id: timer.hallway_transition_timer - - conditions: - - condition: state - entity_id: input_select.hallway_presence - state: entering - sequence: - - choose: - - conditions: - - condition: trigger - id: state + state: absent sequence: - - service: timer.start - data: - duration: 00:00:10 - target: - entity_id: timer.hallway_transition_timer + - choose: + - conditions: + - condition: trigger + id: doors + sequence: + - service: input_select.select_option + data: + option: entering + target: + entity_id: input_select.hallway_presence + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.cancel + data: {} + target: + entity_id: timer.hallway_transition_timer - conditions: - - condition: trigger - id: timer - - condition: state - entity_id: timer.hallway_transition_timer - state: idle - sequence: - - service: input_select.select_option - data: - option: absent - target: + - condition: state entity_id: input_select.hallway_presence - - stop: Back to absent. - - conditions: - - condition: trigger - id: doors - sequence: - - service: timer.change - data: - duration: 00:00:10 - target: - entity_id: timer.hallway_transition_timer - - if: - - condition: state - entity_id: binary_sensor.hallway_human - state: 'on' - then: - - service: input_select.select_option - data: - option: present - target: - entity_id: input_select.hallway_presence - - conditions: - - condition: state - entity_id: input_select.hallway_presence - state: present - sequence: - - choose: - - conditions: - - condition: trigger - id: state + state: entering sequence: - - service: timer.cancel - data: {} - target: - entity_id: timer.hallway_transition_timer + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.start + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: timer + - condition: state + entity_id: timer.hallway_transition_timer + state: idle + sequence: + - service: input_select.select_option + data: + option: absent + target: + entity_id: input_select.hallway_presence + - stop: Back to absent. + - conditions: + - condition: trigger + id: doors + sequence: + - service: timer.change + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - if: + - condition: state + entity_id: binary_sensor.hallway_human + state: "on" + then: + - service: input_select.select_option + data: + option: present + target: + entity_id: input_select.hallway_presence - conditions: - - condition: trigger - id: doors - sequence: - - service: input_select.select_option - data: - option: leaving - target: + - condition: state entity_id: input_select.hallway_presence - - conditions: - - condition: state - entity_id: input_select.hallway_presence - state: leaving - sequence: - - choose: - - conditions: - - condition: trigger - id: state + state: present sequence: - - service: timer.start - data: - duration: 00:00:10 - target: - entity_id: timer.hallway_transition_timer + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.cancel + data: {} + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: doors + sequence: + - service: input_select.select_option + data: + option: leaving + target: + entity_id: input_select.hallway_presence - conditions: - - condition: trigger - id: timer - - condition: state - entity_id: timer.hallway_transition_timer - state: idle - sequence: - - service: input_select.select_option - data: - option: present - target: + - condition: state entity_id: input_select.hallway_presence - alias: Back to present - - stop: Back to present - - if: - - condition: state - entity_id: binary_sensor.hallway_human - state: 'off' - then: - - service: input_select.select_option - data: - option: absent - target: - entity_id: input_select.hallway_presence - alias: Select absent + state: leaving + sequence: + - choose: + - conditions: + - condition: trigger + id: state + sequence: + - service: timer.start + data: + duration: 00:00:10 + target: + entity_id: timer.hallway_transition_timer + - conditions: + - condition: trigger + id: timer + - condition: state + entity_id: timer.hallway_transition_timer + state: idle + sequence: + - service: input_select.select_option + data: + option: present + target: + entity_id: input_select.hallway_presence + alias: Back to present + - stop: Back to present + - if: + - condition: state + entity_id: binary_sensor.hallway_human + state: "off" + then: + - service: input_select.select_option + data: + option: absent + target: + entity_id: input_select.hallway_presence + alias: Select absent mode: queued