Heaty will die, Schedy be born!

Ive just started using Schedy to control the heating and have a group for me and the wife setup in home assistant so if either one of us are in or out the heating adjusts accordingly.

schedy_heating: # This is our app instance name.
  module: hass_apps_loader
  class: SchedyApp
  actor_type: thermostat
  watched_entities:
    - "group.wife_and_me"
  schedule_append: # temp called if no schedule set
    - v: 18
  schedule_prepend:
    - x: "Add(-1) if state('group.wife_and_me') == 'not_home' else Next()"
  rooms:
    home:
      actors:
        climate.home:
      rescheduling_delay: 30 # return back to schedule after x minutes
      schedule:
        - v: 20
          rules:
            - weekdays: 1-5
              rules:
                - { start: "06:30", end: "10:00" }
            - weekdays: 6
              rules:
                - { start: "07:00", end: "10:00" }
            - weekdays: 7
              rules:
                - { start: "07:30", end: "10:00" }
        - v: 20.5
          rules:
            - weekdays: 1-5
              rules:
                - { start: "10:00", end: "14:00" }
            - weekdays: 6
              rules:
                - { start: "10:00", end: "14:00" }
            - weekdays: 7
              rules:
                - { start: "10:00", end: "14:00" }
        - v: 21
          rules:
            - weekdays: 1-7
              rules:
                - { start: "14:00", end: "18:00" }
        - v: 21.5
          rules:
            - weekdays: 1-7
              rules:
                - { start: "18:00", end: "21:45" }
  statistics:
    home_temp_delta:
      type: temp_delta 

Still tinkering with it but it works well at the moment.

1 Like

@denver Thanks for this example, looks great! Your requirements on temperatures and times are quite complex, but I don’t know if flattening the sub-schedules would make it more readable.

EDIT: Since you only ever have one single rule in the innermost sub-schedules, you could of course remove one level of nesting and put the weekdays specification right into the inner rule…

Thank you for your advice roschi, I spent a long time sorting the presence detection out and for Schedy to respond to it that now Ive got that working I need to play around with getting the correct temperatures set. One we are happy with the temp setting, I have ago at consolidating the code.

I will read up on Schedy again and work through your suggestions as they sound good. Regards

Thank you for your advice roschi, I spent a long time sorting the presence detection out and for Schedy to respond to it that now Ive got that working I need to play around with getting the correct temperatures set. One we are happy with the temp setting, I have ago at consolidating the code.

I know the more advanced features can be a bit overwhelming at first, but once you’ve understood how schedules are evaluated and you’re able to use sub-schedules, conditions and tie Home Assistant sensors and Schedy together, that gives you a lot of flexibility and then adding new features is often just a matter of minutes.

Wow, think ive gone and done it with no errors in the logs, all looks good I think:

  module: hass_apps_loader
  class: SchedyApp
  actor_type: thermostat
  watched_entities:
    - "group.wife_and_me"
  schedule_append: # temp called if no schedule set
    - v: 18
  schedule_prepend:
    - x: "Add(-1) if state('group.wife_and_me') == 'not_home' else Next()"
  rooms:
    home:
      actors:
        climate.home:
      rescheduling_delay: 30 # return back to schedule after x minutes
      schedule:
        - v: 20
          weekdays: 1-5
          rules:
            - { start: "06:30", end: "10:00" }
          weekdays: 6
          rules:
            - { start: "07:00", end: "10:00" }
          weekdays: 7
          rules:
            - { start: "07:30", end: "10:00" }
          weekdays: 1-7
          rules:
            - { start: "10:00", end: "14:00", v: 20.5 }
            - { start: "14:00", end: "18:00", v: 21 }
            - { start: "18:00", end: "21:45", v: 21.5 }
  statistics:
    home_temp_delta:
      type: temp_delta

What do you think roschi?

Hmm, that was the wrong way :slight_smile:

You should have collapsed the innermost level of sub-schedules, not the outer one. What you’ve got now is basically a single rule with v: something and multiple weekdays and rules parameters… of which the YAML parser only respects the last occurrences. I’m surprised that this doesn’t lead to a parsing error, but this seems to be the way PyYAML interprets it.

I expected you to come up with something like:

- v: 20
  rules:
  - { weekdays: 1-5, start: "06:30", end: "10:00" }
  - ...

which seems much more readable.

EDIT: Actually, PyYAML is violating the YAML specification here, which says that keys must be unique, however, this is a long known thing and they don’t seem to want to correct it.

hahahaha, found that out this morning when the wife wasn’t happy because the heating didn’t come on. Back to the drawing board.

hahahaha, found that out this morning when the wife wasn’t happy because the heating didn’t come on. Back to the drawing board.

Oh, I hope this doesn’t hit the WAF too much :slight_smile:

as a rule of thumb: If you start getting things in Schedy done by trial and error rather than by understanding the docs, that’s (almost for sure) the wrong way and will lead you nowhere.

WAF fine, just reinstalled the old code. Will work on new code when I have more time. Regards.

Thank you!

I restared using your tutorial as template and now schedy works!

I restared using your tutorial as template and now schedy works!

Great. Good luck for the future!

1 Like

Seems to be working fine now after flattening the sub-schedules:

schedy_heating: # This is our app instance name.
  module: hass_apps_loader
  class: SchedyApp
  actor_type: thermostat
  watched_entities:
    - "group.angela_and_trevor"
  schedule_append: # temp called if no schedule set
    - v: 18
  schedule_prepend:
    - x: "Add(-1) if state('group.angela_and_trevor') == 'not_home' else Next()"
  rooms:
    home:
      actors:
        climate.home:
      rescheduling_delay: 30 # return back to schedule after x minutes
      schedule:
        - v: 20
          rules:
            - { weekdays: 1-5, start: "06:30", end: "10:00" }
            - { weekdays: 6, start: "07:00", end: "10:00" }
            - { weekdays: 7, start: "07:30", end: "10:00" }
        - weekdays: 1-7
          rules:
            - { start: "10:00", end: "14:00", v: 20.5 }
            - { start: "14:00", end: "18:00", v: 21 }
            - { start: "18:00", end: "21:45", v: 21.5 }
  statistics:
    home_temp_delta:
      type: temp_delta
1 Like

@denver Looks great and readable. You probably can’t get it any shorter.

Wow!

This appdaemon app just made my whole setup so much sleeker! I noticed this a while ago but couldn’t wrap my mind around how it would cater to my needs, so I started building my own solution (for appdaemon). While building I felt like I needed to check this out again too see if it would save me some time and it did!

For now I’ve automated all my lights both by using time, lux and motion inputs. I’ve also managed to make some custom schedules for the livingroom to turn off lights when a movie is started.

The scheduling part is really dynamic! The only thing i’m missing is a way for the schedules to turn on for a limited time when motion happens. But I solved this by adding custom binary sensors of my real motion sensor that turns off only when the real sensor has been off for 2-3 minutes.

Leaving my current configuration if anyone wonders what it looks like or maybe someone have an idéa of how I could improve it.

schedy_lights:
  module: hass_apps_loader
  class: SchedyApp
  debug: false

  actor_type: generic

  actor_templates:
    default:
      template: light

    light:
      attributes:
        - attribute: state
          values:
            "on":
              service: light.turn_on
              service_data: {}
              include_entity_id: true
              value_parameter: null
            "off":
              service: light.turn_off
              service_data: {}
              include_entity_id: true
              value_parameter: null
        - attribute: brightness_pct
          values:
            "_other_":
              service: light.turn_on
              service_data: {}
              include_entity_id: true
              value_parameter: brightness_pct
      short_values:
        - ['off']
        - ['on', '_other_']
      send_retries: 1

    switch:
      attributes:
        - attribute: state
          values:
            "on":
              service: switch.turn_on
              service_data: {}
              include_entity_id: true
              value_parameter: null
            "off":
              service: switch.turn_off
              service_data: {}
              include_entity_id: true
              value_parameter: null
      short_values:
        - ['off']
        - ['on']
      send_retries: 1

  schedule_append:
    - v: 'off'

  schedule_snippets:
    motion:
      - v: ['on', 85]
        start: '07:00'
        end: '23:30'
        rules:
          - x: "Next() if float(state(next(x for x in filter_entities('sensor', lux_room=room_name)))) < 6 else Break()"
          - x: "Next() if not is_empty(filter_entities('binary_sensor', motion_room=room_name, state='on')) else Break()"
          - x: "Inherit()"
      - v: ['on', 35]
        start: '23:30'
        end: '07:00'
        rules:
          - x: "Next() if float(state(next(x for x in filter_entities('sensor', lux_room=room_name)))) < 4 else Break()"
          - x: "Next() if not is_empty(filter_entities('binary_sensor', motion_room=room_name, state='on')) else Break()"
          - x: "Inherit()"
    outdoor_home_north:
      - v: 'on'
        rules:
          - { x: "Next() if state('group.family') == 'home' else Break()" }
          - { x: "Next() if float(state('sensor.zigbee_006_lux')) < 45 else Break()" }
          - { v: ['on', 75], start: '08:00', end: '21:00' }
          - { v: ['on', 10], start: '21:00', end: '08:00' }
    outdoor_home_south:
      - v: 'on'
        rules:
          - { x: "Next() if state('group.family') == 'home' else Break()" }
          - { x: "Next() if float(state('sensor.zigbee_006_lux')) < 45 else Break()" }
          - { v: ['on', 75], start: '08:00', end: '18:00' }
          - { v: ['on', 5], start: '18:00', end: '08:00' }
    outdoor_not_home:
      - v: 'on'
        rules:
          - { x: "Next() if state('group.family') == 'not_home' else Break()" }
          - { x: "Next() if float(state('sensor.zigbee_006_lux')) < 45 else Break()" }
          - { v: ['on', 75], start: '08:00', end: '21:00' }
          - { v: ['on', 40], start: '21:00', end: '08:00' }
    indoor_home:
      - v: 'on'
        rules:
          - { x: "Next() if state('group.family') == 'home' else Break()" }
          - { x: "Next() if float(state('sensor.zigbee_006_lux')) < 95 else Break()" }
          - { v: ['on', 85], start: '15:00', end: '20:00' }
          - { v: ['on', 65], start: '20:00', end: '23:30' }
          - { v: ['on', 75], start: '08:00', end: '10:00', weekdays: 1-7 }
          - { v: ['on', 65], start: '06:45', end: '08:00', weekdays: 1-5 }
    bedroom_home:
      - v: 'on'
        rules:
          - { x: "Next() if state('group.family') == 'home' else Break()" }
          - { x: "Next() if float(state('sensor.zigbee_006_lux')) < 95 else Break()" }
          - { v: ['on', 85], start: '15:00', end: '18:00' }
          - { v: ['on', 10], start: '18:00', end: '19:00' }
          - { v: ['on', 75], start: '08:00', end: '10:00', weekdays: 1-7 }
    livingroom_tv:
      - v: 'off'
        rules:
          - { x: "Next() if state('media_player.livingroom_tv') == 'playing' else Break()" }
          - { x: "Next() if state('media_player.livingroom_tv', 'media_content_type') == 'movie' else Break()" }
          - { x: "Inherit()" }

  rooms:
    basementhall:
      friendly_name: 'Kallarhallen'
      allow_manual_changes: false
      rescheduling_delay: 0
      actors:
        light.cellar_hallway_lights:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['motion'])"
      watched_entities:
        - 'sensor.zigbee_008_lux:state:ignore'
        - 'binary_sensor.zigbee_008_motion_timed'

    bedroom:
      friendly_name: 'Sovrum'
      allow_manual_changes: true
      rescheduling_delay: 210
      actors:
        light.masterbedroom_window_light:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['bedroom_home'])"

    garage:
      friendly_name: 'Garage'
      allow_manual_changes: true
      rescheduling_delay: 60
      actors:
        switch.esphome_004_switch:
          template: switch
      schedule:
        - x: "Postprocess(lambda result: (result[0]))"
        - x: "IncludeSchedule(schedule_snippets['motion'])"
      watched_entities:
        - 'binary_sensor.zigbee_011_motion_timed'
        - 'sensor.zigbee_011_lux:state:ignore'

    gillestugan:
      friendly_name: 'Gillestugan'
      allow_manual_changes: true
      rescheduling_delay: 60
      actors:
        light.gillestugan_lights:
      schedule:
        - x: "Postprocess(lambda result: (result[0], min(result[1], 35)) if result != 'off' else 'off')"
        - x: "IncludeSchedule(schedule_snippets['motion'])"
        - x: "IncludeSchedule(schedule_snippets['indoor_home'])"
      watched_entities:
        - 'binary_sensor.zigbee_003_motion_timed'
        - 'sensor.zigbee_003_lux:state:ignore'

    hallway:
      friendly_name: 'Hallen'
      allow_manual_changes: true
      rescheduling_delay: 60
      actors:
        light.upper_hallway_lights:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['motion'])"
        - x: "IncludeSchedule(schedule_snippets['indoor_home'])"
      watched_entities:
        - 'binary_sensor.zigbee_009_motion_timed'
        - 'sensor.zigbee_009_lux:state:ignore'

    kitchen:
      friendly_name: 'Koket'
      allow_manual_changes: true
      rescheduling_delay: 210
      actors:
        light.kitchen_window_lights:
      schedule:
        - v: ['on', 100]
          start: '16:00'
          end: '18:00'
          months: 1-4, 9-12
          rules:
            - x: "Inherit() if state('group.family') == 'home' else Break()"
        - x: "IncludeSchedule(schedule_snippets['indoor_home'])"

    livingroom:
      friendly_name: 'Vardagsrum'
      allow_manual_changes: true
      rescheduling_delay: 210
      actors:
        light.livingroom_wall_lights:
        light.livingroom_window_lights:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['livingroom_tv'])"
        - x: "IncludeSchedule(schedule_snippets['motion'])"
        - x: "IncludeSchedule(schedule_snippets['indoor_home'])"
      watched_entities:
        - 'binary_sensor.zigbee_010_motion_timed'
        - 'sensor.zigbee_010_lux:state:ignore'
        - 'media_player.livingroom_tv'

    lucas:
      friendly_name: 'Lucas'
      allow_manual_changes: true
      rescheduling_delay: 210
      actors:
        light.kid_room_window:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['bedroom_home'])"

    office:
      friendly_name: 'Kontoret'
      allow_manual_changes: true
      rescheduling_delay: 60
      actors:
        light.office_table_light:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['motion'])"
        - x: "IncludeSchedule(schedule_snippets['indoor_home'])"
      watched_entities:
        - 'binary_sensor.zigbee_007_motion_timed'
        - 'sensor.zigbee_007_lux:state:ignore'

    outdoor_north:
      friendly_name: 'Utomhus Norra'
      allow_manual_changes: true
      rescheduling_delay: 60
      actors:
        light.outdoor_north_side_lights:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['outdoor_home_north'])"
        - x: "IncludeSchedule(schedule_snippets['outdoor_not_home'])"

    outdoor_south:
      friendly_name: 'Utomhus Södra'
      allow_manual_changes: true
      rescheduling_delay: 60
      actors:
        light.outdoor_south_side_lights:
      schedule:
        - x: "IncludeSchedule(schedule_snippets['outdoor_home_south'])"
        - x: "IncludeSchedule(schedule_snippets['outdoor_not_home'])"

  watched_entities:
    - 'sensor.zigbee_006_lux'
    - 'group.family'
2 Likes

@seaQ Thanks for sharing your quite advanced config!

Your solution using custom binary sensors for reaction to motion is currently the only way to do it because I want to keep Schedy as slim as possible and don’t want to invent a new template sensor subsystem, which is what HA already offers.

If you have any idea for improving the experience anyway, just let me know.

@seaQ Just some notes:

- x: "Next() if float(state(next(x for x in filter_entities('sensor', lux_room=room_name)))) < 6 else Break()"

Using a generator expression here isn’t required because filter_entities() already is a generator. You can just do next(filter_entities(...))

You have some redundancy in your actor templates, especially ['on', 'brightness_pt'] doesn’t need to be marked as short value because in fact, it specifies all defined attributes. Also, value_parameter, include_entity_id and service_data are explicitly set to their defaults. You really don’t need to change anything of this, but I thought I’d better mention it.

After all, really a cool config!

1 Like

Thought I tried this before trying the “x for x” solution. Will certainly take a look at it again. Thanks for pointing that out!

Regarding the custom binary sensor that solution works really well and I don’t know if you actually would need to improve that. The only thing that works bad with my motion setup is when the lux sensors don’t update the value fast enough.

Example: Walk by, lights go on, lux sensor gets updated with newer higher value, lights turn off after 2-3 minutes, lux sensor does not update to the new low light value fast enough, you walk by again but lights don’t come on cause it still think it’s bright enough.

I know about the redundant values but I plan on adding color or temperature to make sure the lights have the right settings. Good point about the two other parameters though, might remove that to make it slimmer.

Something I have noticed is that since i’m sending a tuple with 2 values to my lights, schedy does not seem to understand that it successfully updated the state and tries to re-send it until the send_retries are 0.

I tried setting send_retries to zero for my light actor but it still warns in the log:

2019-10-29 00:03:04.144181 WARNING schedy_lights: !!! [R:Kontoret] [A:light.office_table_light] Gave up sending value after 0 tries.

Is there anyway to fix this so the re-send functionality works or perhaps a way to disable it for an actor?

Something I have noticed is that since i’m sending a tuple with 2 values to my lights, schedy does not seem to understand that it successfully updated the state and tries to re-send it until the send_retries are 0.

A log captured with debug: true and send_retries: 0 of that scenario would be extremely helpful to understand what’s causing the resending. Maybe you could upload it to GitHub Gists or similar.

Seems like its trying to set two values:

2019-10-29 20:26:07.037009 INFO schedy_lights: <-- [R:Hallen] [A:light.upper_hallway_lights] Setting value ('on', 65) (left tries = 0).
2019-10-29 20:26:07.037805 INFO schedy_lights: <-- [R:Hallen] [A:light.upper_hallway_lights] Calling service 'light/turn_on', data = {'entity_id': 'light.upper_hallway_lights'}.
2019-10-29 20:26:07.111776 INFO schedy_lights: <-- [R:Hallen] [A:light.upper_hallway_lights] Calling service 'light/turn_on', data = {'entity_id': 'light.upper_hallway_lights', 'brightness_pct': 65}.

Log is available here: