Heaty - a flexible heating control, facilitating schedules and manual intervention

Well, I see schedule: defined twice, once in my example, and once your own. Try removing

schedule:
  # ...

But the problem probably lies below your commented out schedule rules.

EDIT: Oh, sorry, the example is already commented out, so forget about deleting it. But the problem does really probably come from something below the rules you commented out.

EDIT 2: To make it clear: Make sure that the next line which is not commented out isn’t indented deeper than the schedule: line.

The line

#      - { temp: 16,   start: "00:00", end: "00:00", end_plus_days: 1 }

is the last line of the file. Not even a new line after.

This is not a big issue! I wouldn’t waste time on it.

Ok, interesting. Then I’m going to look at it later.

Just as a last note… Have you pulled the version 0.20180201.0? It seems that you haven’t installed as root after all, according to the paths shown in the traceback.

You could run pip3 install hass_apps --upgrade to make sure.

I ran sudo pip3 install hass_apps --upgrade

and here is the result:

Requirement already up-to-date: hass_apps in /usr/local/lib/python3.5/dist-packages
Requirement already up-to-date: appdaemon<3.0,>=2.1.12 in /usr/local/lib/python3.5/dist-packages (from hass_apps)
Requirement already up-to-date: voluptuous>=0.10.5 in /usr/local/lib/python3.5/dist-packages (from hass_apps)
Requirement already up-to-date: websocket-client in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: astral in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: Jinja2>=2.9.5 in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: feedparser in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: pyyaml in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: bcrypt in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: aiohttp-jinja2 in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: configparser in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: iso8601 in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: sseclient in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: async in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Collecting aiohttp>=2.2.3 (from appdaemon<3.0,>=2.1.12->hass_apps)
  Downloading aiohttp-2.3.10.tar.gz (848kB)
    100% |████████████████████████████████| 849kB 239kB/s 
Requirement already up-to-date: requests>=2.6.0 in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: daemonize in /usr/local/lib/python3.5/dist-packages (from appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: six in /usr/local/lib/python3.5/dist-packages (from websocket-client->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: pytz in /usr/local/lib/python3.5/dist-packages (from astral->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: MarkupSafe>=0.23 in /usr/local/lib/python3.5/dist-packages (from Jinja2>=2.9.5->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: cffi>=1.1 in /usr/local/lib/python3.5/dist-packages (from bcrypt->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: async_timeout>=1.2.0 in /usr/local/lib/python3.5/dist-packages (from aiohttp>=2.2.3->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: chardet in /usr/local/lib/python3.5/dist-packages (from aiohttp>=2.2.3->appdaemon<3.0,>=2.1.12->hass_apps)
Collecting idna-ssl>=1.0.0 (from aiohttp>=2.2.3->appdaemon<3.0,>=2.1.12->hass_apps)
  Downloading https://www.piwheels.hostedpi.com/simple/idna-ssl/idna_ssl-1.0.0-py3-none-any.whl
Requirement already up-to-date: multidict>=4.0.0 in /usr/local/lib/python3.5/dist-packages (from aiohttp>=2.2.3->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: yarl>=1.0.0 in /usr/local/lib/python3.5/dist-packages (from aiohttp>=2.2.3->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: urllib3<1.23,>=1.21.1 in /usr/local/lib/python3.5/dist-packages (from requests>=2.6.0->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: certifi>=2017.4.17 in /usr/local/lib/python3.5/dist-packages (from requests>=2.6.0->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: idna<2.7,>=2.5 in /usr/local/lib/python3.5/dist-packages (from requests>=2.6.0->appdaemon<3.0,>=2.1.12->hass_apps)
Requirement already up-to-date: pycparser in /usr/local/lib/python3.5/dist-packages (from cffi>=1.1->bcrypt->appdaemon<3.0,>=2.1.12->hass_apps)
Building wheels for collected packages: aiohttp
  Running setup.py bdist_wheel for aiohttp ... done
  Stored in directory: /root/.cache/pip/wheels/5d/e3/61/e5840c9dcf18b5e27c9d5652b4fa632985b711203a6285bedb
Successfully built aiohttp
Installing collected packages: idna-ssl, aiohttp
  Found existing installation: aiohttp 2.3.9
    Uninstalling aiohttp-2.3.9:
      Successfully uninstalled aiohttp-2.3.9
Successfully installed aiohttp-2.3.10 idna-ssl-1.0.0

It said that hass_apps is up to date in /usr/local/lib/python3.5/dist-packages, but in the error message you posted above it sais that python uses hass_apps from /home/pi/.local/lib/python3.5/site-packages/.

Hence, you have two copies installed in different locations.

Since you run the appdaemon from ~/.local/lib, I suggest uninstalling the system-wide and upgrading the locally installed one:

sudo pip3 uninstall hass_apps
pip3 install hass_apps --upgrade

And then restarting appdaemon.

Ah!

But I think your suggestion is the wrong way around, though. I ran

pip3 uninstall hass_apps
sudo pip3 install hass_apps --upgrade

The first removed the .local install, the second completed with no update of the global install (of course, since I just upgraded).

I then

sudo systemctl restart [email protected] 

and now see that heaty is now reporting version 0.9.1 (0.9.0 previously). I then removed the comments from the schedule program - no error - and then put them back - still no error :slight_smile:

Thanks.

EDIT: Oh, I see! You meant for me to remove the global install and keep the local one. Der!

Good to hear that it works now! :slight_smile:

It actually doesn’t matter which one you keep, as long as you not have two and always upgrade the right one.

But, if you just uninstalled the local hass_apps, the other stuff like appdaemon, voluptuous etc is still there and could cause confusion. Hence my suggestion would be to remove ~/.local/lib/python* completely, and only use the system-wide approach.

Excellent idea - I have now deleted .local (and restarted appdaemon to be sure).

In preparation for actually having a working central heating system again, I have cleaned up my apps.yaml and started engaging brain on what it will ultimately look like for heaty.

I have (for now!) one question - as I mentioned, I have three heating loops from my boiler - upstairs, downstairs and hot water. The first two are standard thermostats, but the hot water is a binary on/off (ie a timer system heats water in the hot water tank to a temperature set exclusively by hardware). In particular, the thermostats have HA operation_lists of ["auto", "heat", "off"] while the hot water has ["auto", "on", "off"] Is it possible to have specific opmode for my hot water thermostat: in heaty? And what should a schedule: look like for that loop?

Regarding the hot water setup, you could either utilize Home Assistant’s generic thermostat platform and build a thermostat that could then be controlled by Heaty, or leave the control out of Heaty at all.

OK, that’s something to worry about next week then :slight_smile:

In the mean time, my apps.yaml now contains

  schedule_snippets:
    downstairs_winter_default:
      - { weekdays: 1-5, start: "06:00", end: "08:30",                   temp: 23 }
      - { weekdays: 1-5, start: "08:30", end: "16.30",                   temp: 21 }
      - { weekdays: 1-5, start: "08:30", end: "16:30",                   temp: "Add(2) if app.get_state('input_boolean.janeathome') == 'on' else Ignore()" }
      - { weekdays: 1-5, start: "16:30", end: "22:00",                   temp: 23 }
      - { weekdays: 1-5, start: "00:00", end: "00:00", end_plus_days: 1, temp: 18 }
      - { weekdays: 6,   start: "06:00", end: "10:30",                   temp: 23 }
      - { weekdays: 6,   start: "08:30", end: "16.30",                   temp: 21 }
      - { weekdays: 6,   start: "08:30", end: "16:30",                   temp: "Add(2) if app.get_state('input_boolean.janeathome') == 'on' else Ignore()" }
      - { weekdays: 6,   start: "16:30", end: "22:00",                   temp: 23 }
      - { weekdays: 6,   start: "00:00", end: "00:00", end_plus_days: 1, temp: 18 }
      - { weekdays: 7,   start: "06:00", end: "22:00",                   temp: 23 }
      - { weekdays: 7,   start: "00:00", end: "00:00", end_plus_days: 1, temp: 18 }

  rooms:
    downstairs:
      friendly_name: Downstairs
#      thermostats:
#        climate.downstairs_heating:
      schedule:
      - temp: IncludeSchedule(schedule_snippets["downstairs_winter_default"]) 

but I am getting the following error (which seems to indicate a probelm with ‘end’?)

2018-02-02 11:24:23.275814 WARNING ------------------------------------------------------------
2018-02-02 11:24:23.276412 WARNING Unexpected error:
2018-02-02 11:24:23.277435 WARNING ------------------------------------------------------------
2018-02-02 11:24:23.279775 WARNING Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/appdaemon/appdaemon.py", line 837, in check_config
    new_config[name]["module"], new_config[name]
  File "/usr/local/lib/python3.5/dist-packages/appdaemon/appdaemon.py", line 583, in init_object
    conf.objects[name]["object"].initialize()
  File "/usr/local/lib/python3.5/dist-packages/hass_apps/common.py", line 44, in initialize
    self.cfg = self.Meta.config_schema(self.args)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 221, in __call__
    return self._compiled([], data)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 716, in validate_callable
    return schema(data)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/validators.py", line 264, in __call__
    raise e if self.msg is None else AllInvalid(self.msg)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/validators.py", line 262, in __call__
    v = schema(v)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 221, in __call__
    return self._compiled([], data)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 716, in validate_callable
    return schema(data)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 221, in __call__
    return self._compiled([], data)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 538, in validate_dict
    return base_validate(path, iteritems(data), out)
  File "/usr/local/lib/python3.5/dist-packages/voluptuous/schema_builder.py", line 370, in validate_mapping
    raise er.MultipleInvalid(errors)
voluptuous.error.MultipleInvalid: does not match regular expression for dictionary value @ data['schedule_snippets']['downstairs_winter_default'][1]['end']

2018-02-02 11:24:23.280420 WARNING ------------------------------------------------------------

Any thoughts?

Yes, you sometimes use “16.30” instead of “16:30”, which is not supported. Simply correct that and it should work.

What also comes to my mind when looking at your rules… They are really complex and include a lot of redundant overhead.

You can, for instance, always leave out start: "00:00", end: "00:00", end_plus_days: 1, as this is assumed automatically if neither start nor end are given.

Second, I’d suggest adding a fallback rule like { temp: 18 } to the end of your schedule and simply remove any other rule that sets 18 degrees explicitly.

Oh, and third, the Add(2) rule needs to come before the 21 rule, because as soon as a final value like 21 is found, evaluation stops at that point.

:unamused:
Sorry. And thanks for the tips

OK, so this is a good start.

  schedule_snippets:
    downstairs_winter_default:
      - {                                              temp: 18 }
      - { weekdays: 1-5, start: "06:00", end: "08:30", temp: 23 }
      - { weekdays: 1-5, start: "08:30", end: "16:30", temp: "Add(2) if app.get_state('input_boolean.janeathome') == 'on' else Ignore()" }
      - { weekdays: 1-5, start: "08:30", end: "16:30", temp: 21 }
      - { weekdays: 1-5, start: "16:30", end: "22:00", temp: 23 }
      - { weekdays: 6,   start: "06:00", end: "10:30", temp: 23 }
      - { weekdays: 6,   start: "08:30", end: "16:30", temp: "Add(2) if app.get_state('input_boolean.janeathome') == 'on' else Ignore()" }
      - { weekdays: 6,   start: "08:30", end: "16:30", temp: 21 }
      - { weekdays: 6,   start: "16:30", end: "22:00", temp: 23 }
      - { weekdays: 7,   start: "06:00", end: "22:00", temp: 23 }

No, the fallback rule needs to go to the end, otherwise you’d end up always having 18 degrees.

There is still room for shortening this one, I’ll show you how later today.

Oh, I see. Thanks.

It’s perhaps worth noting that I’d value readability (and adaptability) over efficiency.

Sure, readability and flexibility are most important, but consider this one which does exactly the same as the schedule you posted above, but utilizes the fact that earlier rules override later ones:

 schedule_snippets:
    downstairs_winter_default:
      - { weekdays: 1-5, start: "08:30", end: "16:30", temp: "21 if app.get_state('input_boolean.janeathome') == 'off' else Ignore()" }
      - { weekdays: 1-5, start: "06:00", end: "22:00", temp: 23 }

      - { weekdays: 6,   start: "08:30", end: "16:30", temp: "21 if app.get_state('input_boolean.janeathome') == 'off' else Ignore()" }
      - { weekdays: 6,   start: "06:00", end: "22:00", temp: 23 }

      - { weekdays: 7,   start: "06:00", end: "22:00", temp: 23 }

      - {                                              temp: 18 }

Oh, I see. So the trick is to read from the bottom up.

Would this work too:

      - { weekdays: 1-5, start: "06:00", end: "08:30", temp: 23 }
      - { weekdays: 1-5, start: "08:30", end: "16:30", temp: "23 if app.get_state('input_boolean.janeathome') == 'on' else 21" }
      - { weekdays: 1-5, start: "16:30", end: "22:00", temp: 23 }

Less efficient, but (to my mind, at least) more readable as it can be read bottom-up or top-down?

Sure, that would work as well.

You just need to keep in mind that the first matching rule, when read from top to bottom, wins, unless there is an Add(...), in which case evaluation is continued until there comes a rule with a real number as its result…

I’d suggest reading the chapter about schedules and temperature expressions in the README. While you now basically know how to write rules, there just may sometimes be tricks that can save you a lot of rules and thus make your whole schedules more readable.

Especially in your case, as you want to create more than just one schedule, this might save you some headache.

Hi @PianSom,

Just wanted to let you know that I added two new schedule rule constraints, start_date and end_date. You might want to use them for deciding which schedule snippet to use when.

The error that occured when no schedule was configured is gone now as well.

Best regards
Robert