Преглед на файлове

Car Heater and a lot of other changes

pi преди 2 месеца
родител
ревизия
18fc30d6bb

+ 1 - 0
.gitignore

@@ -41,3 +41,4 @@ www/._card-mod.js
 www/._mushroom.js
 www/card-mod.js
 www/mushroom.js
+backups/Automatic_*.tar

+ 111 - 41
automations.yaml

@@ -100,70 +100,140 @@
   - service: input_boolean.turn_on
     target:
       entity_id: input_boolean.ext_light_has_been_day
-- id: '1736267995882'
-  alias: Control Exterior Light
-  description: Control the external lights
-  trigger:
-  - platform: state
-    entity_id: input_boolean.exterior_lights_wanted_state
-  action:
-  - repeat:
-      until:
-      - condition: template
-        value_template: "{{ (states('sensor.exterior_light_status') == states('input_boolean.exterior_lights_wanted_state')
-          and \n   states('light.exterior_lights') == states('input_boolean.exterior_lights_wanted_state')
-          ) or \n   repeat.index >= 5 }}\n"
-      sequence:
-      - service: light.turn_{{ 'off' if is_state('input_boolean.exterior_lights_wanted_state',
-          'off') else 'on' }}
-        target:
-          entity_id:
-          - light.exterior_lights
-          - light.exterior_lights_repeater
-      - delay: 00:01:00
 - id: '1736268207142'
   alias: Turn on Exterial Lights In The Evening
   description: ''
-  trigger:
-  - platform: numeric_state
-    entity_id:
-    - sensor.lux_hallby
+  triggers:
+  - entity_id:
+    - sensor.outside_lux
     below: 500
     for:
       seconds: 0
-  condition:
+    trigger: numeric_state
+  conditions:
   - condition: state
     entity_id: input_boolean.ext_light_has_been_day
     state: 'on'
-  action:
-  - service: input_boolean.turn_off
-    target:
+  actions:
+  - target:
       entity_id: input_boolean.ext_light_has_been_day
     data: {}
-  - service: input_boolean.turn_on
-    target:
+    action: input_boolean.turn_off
+  - target:
       entity_id: input_boolean.exterior_lights_wanted_state
     data: {}
+    action: input_boolean.turn_on
 - id: '1736268250686'
   alias: Turn Off Exterior Lights In The Morning
   description: ''
-  trigger:
-  - platform: numeric_state
-    entity_id:
-    - sensor.lux_hallby
+  triggers:
+  - entity_id:
+    - sensor.outside_lux
     above: 710
     for:
       seconds: 0
-  condition:
+    trigger: numeric_state
+  conditions:
   - condition: state
     entity_id: input_boolean.ext_light_has_been_night
     state: 'on'
-  action:
-  - service: input_boolean.turn_off
-    target:
+  actions:
+  - target:
       entity_id: input_boolean.ext_light_has_been_night
     data: {}
-  - service: input_boolean.turn_off
-    target:
+    action: input_boolean.turn_off
+  - target:
       entity_id: input_boolean.exterior_lights_wanted_state
     data: {}
+    action: input_boolean.turn_off
+- id: '1746203002786'
+  alias: Mouse Counter Incrementer
+  description: Increments the Mouse detection Counter when an event has happened
+  trigger:
+  - platform: state
+    entity_id:
+    - binary_sensor.mouse_detector
+    from: 'off'
+    to: 'on'
+  condition: []
+  action:
+  - service: counter.increment
+    target:
+      entity_id: counter.kitchen_mouse_detection_counter
+    data: {}
+  mode: single
+- id: '1758118011846'
+  alias: Sync Exterior Lights With Wanted State
+  description: ''
+  triggers:
+  - entity_id: input_boolean.exterior_lights_wanted_state
+    trigger: state
+  actions:
+  - choose:
+    - conditions:
+      - condition: state
+        entity_id: input_boolean.exterior_lights_wanted_state
+        state: 'on'
+      sequence:
+      - target:
+          entity_id:
+          - switch.1_mini_gen3_wood_shed
+          - switch.1_mini_gen3_front_door
+        action: switch.turn_on
+    - conditions:
+      - condition: state
+        entity_id: input_boolean.exterior_lights_wanted_state
+        state: 'off'
+      sequence:
+      - target:
+          entity_id:
+          - switch.1_mini_gen3_wood_shed
+          - switch.1_mini_gen3_front_door
+        action: switch.turn_off
+  mode: single
+- id: '1759053358355'
+  alias: Turn lights ON when on vacation
+  description: ''
+  triggers:
+  - trigger: time
+    at: '18:36:00'
+    weekday:
+    - mon
+    - tue
+    - wed
+    - thu
+    - fri
+    - sat
+    - sun
+  conditions: []
+  actions:
+  - action: input_select.select_option
+    metadata: {}
+    data:
+      option: All on
+    target:
+      entity_id: input_select.light_mode
+  mode: single
+- id: '1759053894661'
+  alias: Turn lights OFF when on vacation
+  description: ''
+  triggers:
+  - trigger: time
+    at: '23:20:00'
+    weekday:
+    - mon
+    - tue
+    - wed
+    - thu
+    - fri
+    - sat
+    - sun
+  conditions: []
+  actions:
+  - action: input_select.select_option
+    metadata: {}
+    data:
+      option: All off
+    target:
+      entity_id: input_select.light_mode
+  mode: single

+ 27 - 0
blueprints/template/homeassistant/inverted_binary_sensor.yaml

@@ -0,0 +1,27 @@
+blueprint:
+  name: Invert a binary sensor
+  description: Creates a binary_sensor which holds the inverted value of a reference binary_sensor
+  domain: template
+  source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml
+  input:
+    reference_entity:
+      name: Binary sensor to be inverted
+      description: The binary_sensor which needs to have its value inverted
+      selector:
+        entity:
+          domain: binary_sensor
+variables:
+  reference_entity: !input reference_entity
+binary_sensor:
+  state: >
+    {% if states(reference_entity) == 'on' %}
+      off
+    {% elif states(reference_entity) == 'off' %}
+      on
+    {% else %}
+      {{ states(reference_entity) }}
+    {% endif %}
+  # delay_on: not_used in this example
+  # delay_off: not_used in this example
+  # auto_off: not_used in this example
+  availability: "{{ states(reference_entity) not in ('unknown', 'unavailable') }}"

+ 20 - 2
configuration.yaml

@@ -1,6 +1,8 @@
 config:
 #frontend:
 homeassistant:
+  packages: !include_dir_named packages
+
 counter:
 logbook:
 notify:
@@ -9,10 +11,20 @@ input_select:
 input_text:
 timer:
 recorder:
-  purge_keep_days: 5
+  purge_keep_days: 30
   db_url: mysql://hassio:hassiopassword@192.168.1.110/hass?charset=utf8
+
+  # include:
+  #   entities:
+  #     - sensor.garage_ambiant_temperature
+  #     - sensor.temperatur_hallby
+  #     - sensor.frank_temperature
+  
 logger:
   default: info
+  logs:
+    habluetooth.wrappers: warning
+
 #  logs:
 #    custom_components.philips_airpurifier_coap: debug
 #    aioairctrl: debug
@@ -24,9 +36,16 @@ logger:
 #    rflink: debug
 #    homeassistant.components.rflink: debug
 
+#lovelace:
+#    resources:
+#    - url: /local/time-picker-card.js
+#      type: module
+
 frontend:
   extra_module_url:
     - /local/card-mod.js
+    - /local/time-picker-card.js
+
 
 mobile_app:
 
@@ -56,7 +75,6 @@ rflink:
 #  scandinavian_miles: true
 #  mutable: false
 
-
 binary_sensor:
    - platform: rflink
      devices:

+ 1 - 0
lights.yaml

@@ -1,5 +1,6 @@
 
 
+
 - platform: rflink
   device_defaults:
     signal_repetitions: 1

+ 189 - 3
mqtt.yaml

@@ -2,22 +2,108 @@ sensor:
   - name: "Temperatur Hällby"
     state_topic: "micke/outside/temperature"
     unit_of_measurement: "°C"
+
   - name: Lux Hällby
     state_topic: "micke/lux/lux_calibrated"
     unit_of_measurement: "Lux"
+
   - name: Lux outside the garage
-    state_topic: "sensors/outside/lux"
-    unit_of_measurement: "Lux"
+    state_topic: "garage/ambient/lux"
+    unit_of_measurement: "lx"
+    device_class: illuminance
+    state_class: measurement
+    availability:
+      - topic: "garage/multi-sensor/status"
+        payload_available: "online"
+        payload_not_available: "offline"
+
   - name: SMHI Temperature
     state_topic: "sensors/outside/smhiTemperature"
     unit_of_measurement: "°C"
+
   - name: Frank Temperature
     state_topic: "sensors/outside/frankTemperature"
     unit_of_measurement: "°C"
+
   - name: Garage Ambiant Temperature
-    state_topic: "garage/temps/6f0000106475c728"
+    state_topic: "garage/ambiant-temperature"
+    unit_of_measurement: "°C"
+    device_class: temperature
+    state_class: measurement
+    availability:
+      - topic: "garage/multi-sensor/status"
+        payload_available: "online"
+        payload_not_available: "offline"
+
+  - name: Garage Inside Temperature
+    state_topic: "garage/temperature"
+    unit_of_measurement: "°C"
+    device_class: temperature
+    state_class: measurement
+    availability:
+      - topic: "garage/multi-sensor/status"
+        payload_available: "online"
+        payload_not_available: "offline"
+  
+  - name: Garage Multi Sensor WiFi Signal Strength
+    state_topic: "garage/multi-sensor/wifi-signal-strength"
+    unit_of_measurement: "dBm"
+    availability:
+      - topic: "garage/multi-sensor/status"
+        payload_available: "online"
+        payload_not_available: "offline"
+
+  - name: LVP Heater Accumulated Energy
+    state_topic: "shellypmminig3-5432046a16e8/status/pm1:0"
+    value_template: "{{ value_json.aenergy.total }}"
+    unit_of_measurement: "Wh"
+    device_class: energy
+    state_class: total_increasing
+
+  - name: LVP Heater Power
+    state_topic: "shellypmminig3-5432046a16e8/status/pm1:0"
+    value_template: "{{ value_json.apower }}"
+    unit_of_measurement: "W"
+    device_class: power
+    state_class: measurement
+  - name: Test of Ext Antenna WiFi Signal Strength
+    state_topic: "ext-wifi-antenna-test/sensor/wifi_signal_strength/state"
+    availability:
+      - topic: "ext-wifi-antenna-test/status"
+        payload_available: "online"
+        payload_not_available: "offline"
+    unit_of_measurement: "dBm"
+    #device_class: signal_strength
+    #state_class: measurement
+
+  - name: Test TSL2561 Light Sensor
+    state_topic: "ext-wifi-antenna-test/sensor/tsl2561_light_sensor/state"
+    unit_of_measurement: "lx"
+    availability:
+      - topic: "ext-wifi-antenna-test/status"
+        payload_available: "online"
+        payload_not_available: "offline"
+    #device_class: illuminance
+    #state_class: measurement
+
+  - name: Test DS18B20 Temp Sensor
+    state_topic: "ext-wifi-antenna-test/sensor/garage_temperature/state"
+    availability:
+      - topic: "ext-wifi-antenna-test/status"
+        payload_available: "online"
+        payload_not_available: "offline"
     unit_of_measurement: "°C"
 
+
+# --------------------------------------- For Test ---------------------------------------
+
+  #- name: Limedal Hottub Water Temperature
+  #  state_topic: "limedal-hottub/sensor/water_temperature/state"
+  #  unit_of_measurement: "°C"
+  #- name: Limedal Hottub WiFi Signal Strength
+  #  state_topic: "limedal-hottub/sensor/wifi_signal_strength/state"
+  #  unit_of_measurement: "dBm"
+
 light:
   - schema: json
     name: "Kitchen worktop 4K"
@@ -45,6 +131,34 @@ light:
     brightness_scale: 1000
     brightness: true
 
+
+
+
+
+
+
+#light:
+#    - name: "Front Door Light"
+#      schema: template
+#      unique_id: "shelly1minig3_front_door_light"
+#      command_topic: "shelly1minig3-5432046e1e88/rpc"
+#      command_on_template: '{"id":124, "src":"my_shelly", "method":"Switch.Set", "params":{"id":0, "on":true}}'
+#      command_off_template: '{"id":124, "src":"my_shelly", "method":"Switch.Set", "params":{"id":0, "on":false}}'
+#      state_topic: "shelly1minig3-5432046e1e88/events/rpc"
+#      state_template: >
+#        {% if value_json.method == "NotifyStatus" %}
+#          {{ "on" if value_json.params["switch:0"].output else "off" }}
+#        {% else %}
+#          {{ states("light.front_door_light") }}
+#        {% endif %}
+#      retain: false
+#      optimistic: false
+#      availability_topic: "shelly1minig3-5432046e1e88/online"
+#      payload_available: "true"
+#      payload_not_available: "false"
+
+
+
 binary_sensor:
   - name: "Kitchen worklight switch"
     unique_id: "kitchen_worklight_switch"
@@ -74,6 +188,17 @@ binary_sensor:
     payload_on: "open"
     payload_off: "closed"
 
+  - name: "Mouse Detector"
+    unique_id: "kitchen_mouse_detector"
+    state_topic: "kitchen/mouse/motion"
+    payload_on: "ON"
+    payload_off: "OFF"
+    availability_topic: "kitchen/mouse/status"
+    payload_available: "online"
+    payload_not_available: "offline"
+    device_class: motion
+
+
 switch:
     - name: "Kitchen Audible Alarm"
       state_topic: "kitchen/alarm/state"
@@ -87,6 +212,67 @@ switch:
       retain: true
       qos: 1
       unique_id: "kitchen_audible_alarm"
+    #- name: "Limedal Hottub Strong LED Light"
+    #  state_topic: "limedal-hottub/switch/strong_led_switch/state"
+    #  command_topic: "limedal-hottub/switch/strong_led_switch/command"
+    #  payload_on: "ON"
+    #  payload_off: "OFF"
+    #  state_on: "ON"
+    #  state_off: "OFF"
+    #  unique_id: "limedal_hottub_strong_led_light"
+    
+    #- name: "Front Door Light"
+    #  unique_id: "shelly1minig3_front_door_light"
+    #  state_topic: "shelly1minig3-front-door/relay/0/status"
+    #  command_topic: "shelly1minig3-front-door/command/switch:0"
+    #  availability:
+    #  - topic: "shelly1minig3-front-door/online"
+    #  payload_on: "on"
+    #  payload_off: "off"
+    #  state_on: "on"
+    #  state_off: "off"
+    #  retain: true
+    #  qos: 1
+
+    #- unique_id: shelly1minig3_front_door_light
+    #  name: "Front Door Light"
+    #  state_topic: "shelly1minig3-5432046e1e88/events/rpc"
+    #  value_template: >
+    #    {% if value_json.method == "NotifyStatus" %}
+    #      {{ "ON" if value_json.params["switch:0"].output else "ON" }}
+    #    {% else %}
+    #      {{ states("switch.front_door_light") }}
+    #    {% endif %}
+    #  command_topic: "shelly1minig3-5432046e1e88/rpc"
+    #  payload_on: '{"id":1,"src":"ha","method":"Switch.Set","params":{"id":0,"on":true}}'
+    #  payload_off: '{"id":1,"src":"ha","method":"Switch.Set","params":{"id":0,"on":false}}'
+    #  qos: 1
+    #  retain: true
+    #  payload_available: "true"
+    #  state_on: "ON"
+    #  state_off: "OFF"
+
+#    - unique_id: shelly1minig3_wood_shed_light
+#      name: "Wood Shed Light"
+#      state_topic: "shelly1minig3-5432046da248/events/rpc"
+#      value_template: >
+#        {% if value_json.method == "NotifyStatus" %}
+#          {{ "ON" if value_json.params["switch:0"].output else "ON" }}
+#        {% else %}
+#          {{ states("switch.wood_shed_light") }}
+#        {% endif %}
+#      #command_topic: "shelly1minig3-5432046da248/command/switch:0"
+#      #payload_on: "on"
+#      #payload_off: "off"
+#      command_topic: "shelly1minig3-5432046da248/rpc"
+#      payload_on: '{"id":1,"src":"ha","method":"Switch.Set","params":{"id":0,"on":true}}'
+#      payload_off: '{"id":1,"src":"ha","method":"Switch.Set","params":{"id":0,"on":false}}'
+#      qos: 1
+#      retain: true
+#      payload_available: "true"
+#      state_on: "ON"
+#      state_off: "OFF"
+
 
 button:
     - name: "Kitchen Alarm Notification"

+ 63 - 0
packages/car_heater.yaml

@@ -0,0 +1,63 @@
+###############################################################################
+# CAR HEATER HELPERS
+###############################################################################
+
+# -------------------------
+# INPUT DATETIMES (TIME ONLY)
+# -------------------------
+input_datetime:
+  heater_time1:
+    name: Heater A Time 1
+    has_date: false
+    has_time: true
+  heater_time2:
+    name: Heater A Time 2
+    has_date: false
+    has_time: true
+  heater_time3:
+    name: Heater A Time 3
+    has_date: false
+    has_time: true
+  heater_time4:
+    name: Heater A Time 4
+    has_date: false
+    has_time: true
+
+  heater_once_time:
+    name: Heater A One-Time Departure
+    has_date: false
+    has_time: true
+
+# -------------------------
+# INPUT BOOLEANS (ENABLE/FLAGS)
+# -------------------------
+input_boolean:
+  heater_time1_active:
+    name: Heater A Time 1 Active
+  heater_time2_active:
+    name: Heater A Time 2 Active
+  heater_time3_active:
+    name: Heater A Time 3 Active
+  heater_time4_active:
+    name: Heater A Time 4 Active
+
+  heater_once_active:
+    name: Heater A One-Time Active
+
+  heater_force_on:
+    name: Heater A Force ON
+
+# -------------------------
+# OPTIONAL: INPUT NUMBER (AFTER HEAT TIME)
+# If you want the 25min window adjustable in UI.
+# -------------------------
+input_number:
+  heater_afterheat_minutes:
+    name: Heater Afterheat Time
+    min: 0
+    max: 120
+    step: 1
+    unit_of_measurement: "min"
+    mode: slider
+    icon: mdi:timer-outline
+    initial: 25

+ 23 - 0
pyscript/backup_alarm.py

@@ -0,0 +1,23 @@
+
+
+# Heartbeat should be OFF at the start of the day. At 00:01, reset it
+@time_trigger("once(00:01:00)")
+def reset_backup_alarm_if_no_heartbeat(**kwargs):
+    log.info("Resetting backup heartbeat at 00:01")
+    input_boolean.backup_heartbeat = 'off'
+
+
+
+# Automation: Raise alarm if no heartbeat after 24h
+@time_trigger("once(06:15:00)")
+@state_active("input_boolean.backup_heartbeat == 'off'")
+def raise_backup_alarm_if_no_heartbeat(**kwargs):
+    log.warning("No backup heartbeat detected in last 24h! Raising alarm!")
+    input_boolean.backup_alarm = 'on'
+
+@time_trigger("once(06:30:00)")
+@state_active("input_boolean.backup_alarm == 'on'")
+def turn_on_hallway_light_if_backup_alarm_on(**kwargs):
+    light.turn_on(entity_id='light.hall_door', brightness=25, color_temp_kelvin=2202)
+
+

+ 121 - 0
pyscript/car_heater.py

@@ -0,0 +1,121 @@
+#
+# CAR HEATER - One-minute loop, using template sensor for preheat time
+#
+
+from datetime import datetime, timedelta
+
+
+@time_trigger("cron(* * * * *)")     # run every minute
+def car_heater_minute_job():
+
+    now = datetime.now()
+
+    #
+    # 1. READ PREHEAT TIME FROM TEMPLATE SENSOR
+    #
+    preheat_raw = state.get("sensor.car_heater_preheat_time")
+    try:
+        preheat = int(float(preheat_raw))
+    except (ValueError, TypeError):
+        preheat = 60   # safe fallback
+        log.error(f"Car heater: preheat sensor returned invalid value '{preheat_raw}'")
+
+    after_heat = 25  # minutes after departure to keep heater on
+
+
+    #
+    # 2. DETERMINE IF HEATER SHOULD BE ON
+    #
+    should_on = False
+
+    # Force ON overrides everything
+    if state.get("input_boolean.heater_force_on") == "on":
+        should_on = True
+
+    # Helper function
+    def in_window(dt_entity, active_entity):
+        """Return True if current time is inside the heating window."""
+        if state.get(active_entity) != "on":
+            return False
+
+        tstr = state.get(dt_entity)
+        if not tstr:
+            return False
+
+        try:
+            dep_time = datetime.strptime(tstr, "%H:%M:%S").time()
+        except (ValueError, TypeError):
+            log.error(f"Car heater: invalid datetime '{tstr}' for {dt_entity}")
+            return False
+
+        departure = datetime.combine(now.date(), dep_time)
+        start = departure - timedelta(minutes=preheat)
+        stop = departure + timedelta(minutes=after_heat)
+
+        return start <= now <= stop
+
+
+    #
+    # WEEKDAY SCHEDULES (Mon–Fri only)
+    #
+    if now.weekday() <= 4:
+
+        if in_window("input_datetime.heater_time1", "input_boolean.heater_time1_active"):
+            should_on = True
+
+        if in_window("input_datetime.heater_time2", "input_boolean.heater_time2_active"):
+            should_on = True
+
+        if in_window("input_datetime.heater_time3", "input_boolean.heater_time3_active"):
+            should_on = True
+
+        if in_window("input_datetime.heater_time4", "input_boolean.heater_time4_active"):
+            should_on = True
+
+
+    #
+    # ONE-TIME SCHEDULE (any day)
+    #
+    if in_window("input_datetime.heater_once_time", "input_boolean.heater_once_active"):
+        should_on = True
+
+
+    #
+    # 3. WRITE VIRTUAL SENSOR
+    #
+    state.set("binary_sensor.car_heater_should_be_on",
+              "on" if should_on else "off")
+
+
+    #
+    # 4. CONTROL THE REAL SHELLY RELAY
+    #
+    current_state = state.get("switch.shelly_car_heater")
+    desired_state = "on" if should_on else "off"
+    
+    if desired_state != current_state:
+        action = "turn_on" if should_on else "turn_off"
+        log.info(f"Car heater: switching relay to {action}")
+        service.call("switch", action, entity_id="switch.shelly_car_heater")
+
+
+    #
+    # 5. RESET ONE-TIME DEPARTURE IF WINDOW PASSED
+    #
+    if state.get("input_boolean.heater_once_active") == "on":
+
+        tstr = state.get("input_datetime.heater_once_time")
+        if tstr:
+            try:
+                dep_time = datetime.strptime(tstr, "%H:%M:%S").time()
+            except (ValueError, TypeError):
+                log.error(f"Car heater: failed to parse heater_once_time '{tstr}'")
+                return
+
+            departure = datetime.combine(now.date(), dep_time)
+            stop = departure + timedelta(minutes=after_heat)
+
+            if now > stop:
+                log.info("Car heater: resetting one-time schedule")
+                service.call("input_boolean", "turn_off",
+                             entity_id="input_boolean.heater_once_active")

+ 60 - 54
pyscript/ext_lights_control.py

@@ -1,57 +1,63 @@
 
-#state.persist('pyscript.ext_light_has_been_day','false')
-#state.persist('pyscript.ext_light_has_been_night','false')
-
-# Set that it has been midnight, and that it's ok to turn the light on
-#@time_trigger("once(00:30:00)")
-#def ext_light_has_been_night():
-#    pyscript.ext_light_has_been_night = 'true'
-#    pyscript.home_auto_lights_off_has_been_night = 'true'
-
-# Set that it has been noon, and that it's ok to turn off the light
-#@time_trigger("once(12:30:00)")
-#def ext_light_has_been_day():
-#    pyscript.ext_light_has_been_day = 'true'
-
-
-#@state_trigger("sensor.lux_outside_the_garage != 'unknown' and int(sensor.lux_outside_the_garage) < 100", state_hold_false=0 )
-#def automate_exterior_lights_on_in_evening():
-#    if( pyscript.ext_light_has_been_day == 'true' ):
-#        pyscript.ext_light_has_been_day = 'false'
-#        input_boolean.exterior_lights_wanted_state = 'on'
-
-#@state_trigger("sensor.lux_outside_the_garage != 'unknown' and int(sensor.lux_outside_the_garage) > 50", state_hold_false=0)
-#def automate_exterior_lights_off_in_morning():
-#    if( pyscript.ext_light_has_been_night == 'true' ):
-#        pyscript.ext_light_has_been_night = 'false'
-#        input_boolean.exterior_lights_wanted_state = 'off'
-
-
-
-#@state_trigger("input_boolean.exterior_lights_wanted_state")
-#@task_unique("control_ext_light", kill_me=True)
-#def control_ext_light():
-
-    # Set max number of iterations
-#    maxIterations = 5
-
-    # Loop until we have succeeded to controlling the light
-"""
-    while(
+@time_trigger("once(12:30:00)")
+def set_has_been_day_flag(**kwargs):
+    # Set ext_light_has_been_day flag at 12:30 every day.
+    log.info("Setting ext_light_has_been_day = ON (daily reset at 12:30)")
+    input_boolean.ext_light_has_been_day = 'on'
+
+@time_trigger("once(00:30:00)")
+def set_has_been_night_flag(**kwargs):
+    # Set night flags at 00:30 every day.
+    log.info("Setting has_been_night flags = ON (00:30)")
+    input_boolean.ext_light_has_been_night = 'on'
+    input_boolean.home_auto_lights_off_has_been_night = 'on'
+
+@state_trigger("sensor.outside_lux != 'unknown' and int(sensor.outside_lux) > 710", state_hold_false=0)
+@state_active("input_boolean.ext_light_has_been_night == 'on'")
+def turn_off_exterior_lights_morning(**kwargs):
+    # Turn off exterior lights in the morning when lux goes above 710.
+    log.info(f"Lux {sensor.outside_lux} > 710, turning off exterior lights")
+    input_boolean.ext_light_has_been_night = 'off'
+    input_boolean.exterior_lights_wanted_state = 'off'
+
+@state_trigger("sensor.outside_lux != 'unknown' and int(sensor.outside_lux) < 500", state_hold_false=0)
+@state_active("input_boolean.ext_light_has_been_day == 'on'")
+def turn_on_exterior_lights_evening(**kwargs):
+    # Turn on exterior lights in the evening when lux drops below 500.
+    log.info(f"Lux {sensor.outside_lux} < 500, turning on exterior lights")
+    input_boolean.ext_light_has_been_day = 'off'
+    input_boolean.exterior_lights_wanted_state = 'on'
+
+@state_trigger("input_boolean.exterior_lights_wanted_state")
+@task_unique("control_ext_light", kill_me=True)
+def sync_exterior_lights(value=None, **kwargs):
+    
+    # Keep exterior lights in sync with wanted state (input_boolean).
+    action = "turn_off" if input_boolean.exterior_lights_wanted_state == 'off' else "turn_on"
+    isChristmas  = True if input_boolean.christmas == 'on' else False
+    log.info(f"sync_exterior_lights input: {value}  Action: {action}  Christmas:{isChristmas}")
+
+    service.call("switch", action, entity_id="switch.1_mini_gen3_wood_shed")
+    service.call("switch", action, entity_id="switch.1_mini_gen3_front_door")
+
+    if isChristmas:
+        max_iterations = 3
+        iteration = 1
+        while(
              ( (sensor.exterior_light_status != input_boolean.exterior_lights_wanted_state) or 
-               (        light.exterior_lights!= input_boolean.exterior_lights_wanted_state) ) and maxIterations > 0
-    ):
+               (light.exterior_lights != input_boolean.exterior_lights_wanted_state) ) and
+               iteration < max_iterations ):
+                log.info(f"Christmas lights sync iteration {iteration} of {max_iterations}. Action: {action}, exterior_light_status: {sensor.exterior_light_status}, exterior_lights: {light.exterior_lights}, wanted_state: {input_boolean.exterior_lights_wanted_state}")
+                service.call("light", action, entity_id="light.exterior_lights")
+                service.call("light", action, entity_id="light.exterior_lights_repeater")
+
+                log.info("Sleeping 6s.....")
+                trig_info = task.wait_until(state_trigger=["input_boolean.exterior_lights_wanted_state","sensor.exterior_light_status"], timeout=6)
+                log.info(f"Woke up from wait_until: {trig_info}")
+                iteration += 1
+
+@service
+def thomas_testing():
+    log.info(f"Running Thomas testing service {int(sensor.outside_lux)}")
+    #set_has_been_day_flag()
 
-        # Find out if we should turn the light on or off
-        action = "turn_off" if input_boolean.exterior_lights_wanted_state == 'off' else "turn_on"
-        
-        service.call("light", action, entity_id="light.exterior_lights")
-        service.call("light", action, entity_id="light.exterior_lights_repeater")
-
-        log.info("Sleeping 60s.....")
-        trig_info = task.wait_until(state_trigger=["input_boolean.exterior_lights_wanted_state","sensor.exterior_light_status"], timeout=60)
-
-        maxIterations -= 1
-    
-    log.info(f"Exit control_ext_light")
-"""

+ 3 - 6
pyscript/home_automations.py

@@ -3,14 +3,11 @@
 #state.persist('pyscript.home_auto_lights_off_has_been_night','false')
 
 
-#@state_trigger("sensor.lux_outside_the_garage is not none and sensor.lux_outside_the_garage != 'unknown' and input_number.auto_light_off_lux_value is not none and int(float(sensor.lux_outside_the_garage)) > int(float(input_number.auto_light_off_lux_value))", state_hold_false=0)
-
-
-@state_trigger("sensor.lux_outside_the_garage != None and sensor.lux_outside_the_garage != 'unknown' and int(sensor.lux_outside_the_garage) > int(float(input_number.auto_light_off_lux_value))", state_hold_false=0)
+@state_trigger("sensor.outside_lux != None and sensor.outside_lux != 'unknown' and int(sensor.outside_lux) > int(float(input_number.auto_light_off_lux_value))", state_hold_false=0)
 def automate_home_auto_lights_off():
     if( input_boolean.home_auto_lights_off_has_been_night == 'true' ):
 
-        log.info(f"********* AUTO LIGHTS OFF *********** Lux: " + str(sensor.lux_outside_the_garage) + " Limit:"+str(input_number.auto_light_off_lux_value))
+        log.info(f"********* AUTO LIGHTS OFF *********** Lux: " + str(sensor.outside_lux) + " Limit:"+str(input_number.auto_light_off_lux_value))
 
         input_boolean.home_auto_lights_off_has_been_night = 'false'
         
@@ -23,6 +20,6 @@ def automate_home_auto_lights_off():
 def morning_lights_thomas():
     if binary_sensor.workday_sensor == 'on' and input_boolean.auto_work_day_lights_on == 'on':
         if switch.eng_heat_a == 'on' or switch.eng_heat_b == 'on':
-            if sensor.lux_outside_the_garage != 'unknown' and int(sensor.lux_outside_the_garage) < 3:
+            if sensor.outside_lux != 'unknown' and int(sensor.outside_lux) < 3:
                 log.info(f"********* THOMAS MORNING ON ***********")
                 input_select.light_mode = 'Morning'

+ 13 - 19
pyscript/kitchen.py

@@ -86,33 +86,27 @@ def handle_kitchen_worklights():
 #def light_turned_on(**kwargs):
 #    log.info(f"got arguments {kwargs}")
 
+def waitForFridgeDoorToClose(timeout=10):
+    t_info = task.wait_until(state_trigger="binary_sensor.kitchen_fridge_door == 'off'",timeout=timeout)
+    if t_info["trigger_type"] != "timeout":
+        log.info("Fridge door closed before {timeout} seconds.")
+        return True
+    log.info(f"Fridge door still open after {timeout} seconds.")
+    return False
+
 @state_trigger("binary_sensor.kitchen_fridge_door == 'on'")
 @task_unique('fridge_open_too_long', kill_me=True)
 def fridge_door_open_too_long():
     log.info("Fridge door has been opened")
+    service.call("counter", "increment", entity_id="counter.fridge_door_counter")
     #service.call("button", "press", entity_id="button.kitchen_alarm_notification")
-    t_info = task.wait_until(state_trigger="binary_sensor.kitchen_fridge_door == 'off'",timeout=30)
-    if t_info["trigger_type"] != "timeout":
-        log.info("Fridge door closed before timeout")
-        return
-    log.info("Fridge door has been opened for 30 seconds — do a notification.")
+    if waitForFridgeDoorToClose(30): return
     service.call("button", "press", entity_id="button.kitchen_alarm_notification")
-    t_info = task.wait_until(state_trigger="binary_sensor.kitchen_fridge_door == 'off'",timeout=10)
-    if t_info["trigger_type"] != "timeout":
-        log.info("Fridge door closed before timeout")
-        return
-    log.info("Fridge door has been opened for 40 seconds — do a notification.")
+    if waitForFridgeDoorToClose(10): return
     service.call("button", "press", entity_id="button.kitchen_alarm_notification")
-    t_info = task.wait_until(state_trigger="binary_sensor.kitchen_fridge_door == 'off'",timeout=10)
-    if t_info["trigger_type"] != "timeout":
-        log.info("Fridge door closed before timeout")
-        return
-    log.info("Fridge door has been opened for 50 seconds — do a notification.")
+    if waitForFridgeDoorToClose(10): return
     service.call("button", "press", entity_id="button.kitchen_alarm_notification")
-    t_info = task.wait_until(state_trigger="binary_sensor.kitchen_fridge_door == 'off'",timeout=10)
-    if t_info["trigger_type"] != "timeout":
-        log.info("Fridge door closed before timeout")
-        return
+    if waitForFridgeDoorToClose(10): return
     log.info("Fridge door has been opened for 60 seconds — activate alarm.")
     service.call("switch", "turn_on", entity_id="switch.kitchen_audible_alarm")
     t_info = task.wait_until(state_trigger="binary_sensor.kitchen_fridge_door == 'off'",timeout=5*60)