My Goal
I wanted to make a Home Assistant automation to watch the exchange rate of bitcoins. I wanted it to be shown in HA like this:
It should have these features:
- The warning levels can be set from two input sliders.
- If the exchange rate falls under the “low warning” level, I want to get a notification every 5 hours with the current rate.
- If the exchange rate goes over the “high warning” level, I want to get a notification every 5 hours with the current rate.
- When it comes back to the normal range i want to get notified. As long as it stays here, I don’t want any more notifications.
All this could probably be made with normal automations, but it would look rather ugly, so I decided to do it in pure python code.
Appdaemon
I found the great appdaemon project that allowed me to do automations in HA programmaticaly. It hooks into the eventbus of HA and allows me to listen to any events and states in HA. NEAT!
First I installed it in virtual env because I dont like running things as root:
sudo su -s /bin/bash homeassistant
source /srv/homeassistant/bin/activate
pip3 install appdaemon
Then i wanted it to start up at boot time by creating /etc/systemd/system/multi-user.target.wants/appdaemon.service:
[Unit] Description=AppDaemon After=homeassistant.service Requires=homeassistant.service [Service] Type=simple User=homeassistant ExecStart=/srv/homeassistant/bin/appdaemon -c /home/homeassistant/appdaemon/conf [Install] WantedBy=multi-user.target
And then enabled and started it:
systemctl enable appdaemon.service
systemctl start appdaemon.service
Appdaemon expects a configuration file in the installation directory. In my case /home/homeassistant/appdaemon/conf/appdaemon.yaml. This file has to be pointing to my HA:
AppDaemon: logfile: STDOUT errorfile: STDERR threads: 10 HASS: ha_url: http://127.0.0.1:8123
My bitcoin class setup
First I needed to tell appdaemon that I have some code for it. It is done by adding this to the conf/appdaemon.yaml file:
BitcoinWatch:
module: bitcoinwatch
class: BitcoinWatch
low_slider: input_slider.btc_low
high_slider: input_slider.btc_high
notify_who: pushover
warning_frequency: 300 # every 5 hours if outside limit
Line 2 tells appdaemon to load a file named “bitcoinwatch.py”
Line 3 tells appdaemon which classname inside that file to instanciate.
Line 4-7 are configuration parameters that I want to pass to my class. That means that I can configure my class without changing the actual code.
The cool thing about appdaemon is that it constantly scans this file for changes. As soon as you change it, appdaemon reloads it.
The actual bitcoin class
In the installation folder of appdaemon I created a file conf/apps/bitcoinwatch.py :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import appdaemon.appapi as appapi import time class BitcoinWatch(appapi.AppDaemon): has_warned=False last_warned_time=time.time() def initialize(self): self.btc_low=self.get_state( self.args["low_slider"] ) self.btc_high=self.get_state( self.args["high_slider"] ) self.log("Bitcoin is now watching exchange rate between {}$ and {}$".format(self.btc_low, self.btc_high)) self.listen_state(self.btc_low_slider_changed, self.args["low_slider"]) self.listen_state(self.btc_high_slider_changed, self.args["high_slider"]) self.listen_state(self.exchange_rate_changed, "sensor.exchange_rate_1_btc") def btc_low_slider_changed(self, entity, attribute, old, new_slider_value, kwargs): self.btc_low=new_slider_value self.log("Bitcoin Low Warning Limit Changed to: {}$".format(self.btc_low)) def btc_high_slider_changed(self, entity, attribute, old, new_slider_value, kwargs): self.btc_high=new_slider_value self.log("Bitcoin High Warning Limit Changed to: {}$".format(self.btc_high)) def exchange_rate_changed(self, entity, attribute, old, new_rate, kwargs): if self.has_warned == True: if time.time()-self.last_warned_time > self.args["warning_frequency"]*60: self.log("It has now been long time since we last warned. We can do it again.") self.last_warned_time=time.time() self.has_warned=False if new_rate < self.btc_high and new_rate > self.btc_low: msg="Bitcoinrate is now normalized again and is between {}$ and {}$".format(self.btc_low, self.btc_high) self.send_notification_msg(msg) self.has_warned=False if self.has_warned == False: if new_rate > self.btc_high: msg="Bitcoinrate is higher than {}$ It is {}".format(self.btc_high, new_rate) self.send_notification_msg(msg) self.has_warned=True if new_rate < self.btc_low: msg="Bitcoinrate is lower than {}$ It is {}".format(self.btc_low, new_rate) self.send_notification_msg(msg) self.has_warned=True def send_notification_msg(self, msg): self.notify(msg, name=self.args["notify_who"],title="Bitcoin watch") self.last_warned_time=time.time() self.log(msg) |
Line:
9: This method gets called when appdaemon loads the class.
10-11: Get the actual name of my two sliders from the appdaemon.yaml config file.
14-16: I register eventhandlers for the events when the input sliders change or the exchange rate changes.
18-24 updates internal values for low and high warnings whenever they are change in the HA user interface.
26-44 handles all the logic of when to warn depending on the exchange rate. This should be self explanatory.
46-49 Is the function called when a warning should be sent to me. It pulls the name of the service to notify (I have setup pushover in HA’s configuration.yaml to my phone)
Again appdaemon is very cool – as soon as you do any written changes to the code, it automatically reloads the class. This gives very fast feedback of the development process.
My HA configuration.
For completeness I here provide the necessary configuration that should be added to Home Assistant to make all this work.
notify: - name: pushover platform: pushover api_key: !secret pushover_api_key user_key: !secret pushover_user_key sensor: - platform: bitcoin display_options: - exchangerate - trade_volume_btc input_slider: btc_low: name: Bitcoin Low Warning min: 1000 max: 4000 step: 10 btc_high: name: Bitcoin High Warning min: 1000 max: 4000 step: 10 group: BTC: view: false entities: - sensor.exchange_rate_1_btc - input_slider.btc_low - input_slider.btc_high name: Bitcoins
Debugging
When changing code I was having a session open watching the following command:
journalctl -u appdaemon.service -f
It shows a very valuable output of the log commands in the class and python error messages. It looks like this:
Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.674121 INFO Got initial state Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.676985 INFO Loading Module: /home/homeassistant/appdaemon/conf/apps/bitcoinwatch.py Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.678677 INFO Loading Object BitcoinWatch using class BitcoinWatch from module bitcoinwatch Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.704705 INFO BitcoinWatch: Bitcoin is now watching exchange rate between 2510.0$ and 3000.0$ Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.705230 INFO App initialization complete Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.705669 INFO Dashboards are disabled Jul 22 08:33:26 homeauto appdaemon[18042]: 2017-07-22 08:33:26.710380 INFO Connected to Home Assistant 0.49.0 Jul 22 08:59:00 homeauto appdaemon[18042]: 2017-07-22 08:59:00.716227 INFO BitcoinWatch: Bitcoin Low Warning Limit Changed to: 2940.0$ Jul 22 08:59:02 homeauto appdaemon[18042]: 2017-07-22 08:59:02.073235 INFO BitcoinWatch: Bitcoin High Warning Limit Changed to: 3290.0$ Jul 22 09:03:19 homeauto appdaemon[18042]: 2017-07-22 09:03:19.613433 INFO BitcoinWatch: Bitcoinrate is lower than 2940.0$ It is 2752.18 Jul 22 09:03:41 homeauto appdaemon[18042]: 2017-07-22 09:03:41.221077 INFO BitcoinWatch: Bitcoin Low Warning Limit Changed to: 2310.0$ Jul 22 09:08:19 homeauto appdaemon[18042]: 2017-07-22 09:08:19.621481 INFO BitcoinWatch: Bitcoinrate is now normalized again and is between 2310.0$ and 3290.0$
Whats next?
I hope this can get you started writing more advanced automation. The yaml of HA can easily get ugly and out of hand in advanced automations. This is an easy way to avoid that.
Commens are welcome 🙂
sudo su -s /bin/bash homeassistant
source /srv/homeassistant/bin/activate
sudo pip3 install appdaemon
You probably didn’t mean ‘sudo pip3’ as this would put it out of the virtualenv. homeassistant user doesn’t normally have sudo access anyway.
Thanks for pointing that out. I have corrected it 🙂
good work! thanks for writing tutorial.
Can you please explain where does the “new_slider_value” argument come from? From the documentation i can only see that the typical arguments are self, entity, attribute, old, new, and kwargs. I am still struggling on understanding the syntax and structure.