% Home Automation with Home Assistant
%
{height=40 style=vertical-align:middle} jjmontes@gmail.com -
{height=40 style=vertical-align:middle} jjmontesl
% PyDay Galicia 2017 (CC BY-SA 3.0)
Home Automation allows us to control
and automate devices at home.
- Currently there is no standard for (home) IoT
- Different apps and clouds for each vendor
- Impossible to automate across boundaries
A Home Automation Hub connects to different devices and manages events and commands across them.
- Automate lighting and appliances based on presence, schedule, sun position...
- Wakeup alarms: lights, TV, music, blinds, coffee...
- Control heating using sensors, weather predictions, schedule or presence of family members.
- Alarm systems, presence detection, fake presence during holidays.
- Automate gardens using weather predictions and sensors (temperature, humidity...).
- Check if the car is low on fuel (set alarm 10 minutes earlier, send a notification).
Home Assistant is an open-source home automation platform running on Python 3. Track and control all devices at home and automate control. Perfect to run on a Raspberry Pi.
{style=float:right;margin-top:100px;width:33%;}
- Lights, switches, covers, fans, sensors
- Thermostates, cameras, alarms
- Media Players, TVs
- DIY: Arduino, GPIO...
- Weather, ITTT, API.AI, Voice, TTS
- Track things on a map
Needs Python3. Using PIP:
python3 -m venv env
. env/bin/activate
pip install homeassistant
- Configuration is by default in the
~/.homeassistantdirectory. - Configuration is defined in YAML format.
- Configuration starts in the
configuration.yamlfile.
Tips:
- Fixed IP addresses (configure your router or DHCP server)
- Names for hosts (prepare to edit /etc/hosts where needed)
- Avoid monolithic config file: use homeassistant packages
configuration.yaml
latitude: !secret home_lat longitude: !secret home_lon
elevation: 75
unit_system: metric
Timezone (from: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
time_zone: Europe/Madrid
</div>
---
<div style="font-size: 75%;">
```yaml
# Track the sun
sun:
# Enables the frontend
frontend:
# HTTP configuration
http:
api_password: !secret api_password
use_x_forwarded_for: True
#base_url: !secret base_url
- component attributes are defined as a dictionary.
!secretallows to define secrets in a separate file.
-
name: Home latitude: !secret home_lat longitude: !secret home_lon radius: 100 icon: mdi:home
-
name: Work latitude: !secret work_lat longitude: !secret work_lon radius: 100 icon: mdi:worker
</div>
Zones are used for device or user tracking.
## Running
Run homeassistant:
. env/bin/activate
hass -c configdir
Connect to:
[http://localhost:8123](http://localhost:8123)
## Devices: Sensors, Lights, Switches...
Add an item for each device to be controlled:
<div style="font-size: 75%;">
```yaml
switch:
platform: command_line
switches:
kitchen_light:
command_on: touch /tmp/kitchen_light
command_off: rm /tmp/kitchen_light
sensor:
- platform: command_line
name: CPU Temp
command: "/bin/cat /sys/class/thermal/thermal_zone1/temp"
unit_of_measurement: "ºC"
value_template: '{{ value | multiply(0.001) }}'
You can split configuration per platform (switches, lights, zones...).
However, using packages is recommended.
Many players and TVs are supported, implementing several methods (services).
- platform: lg_netcast #name: tvlg host: !secret tv_ip access_token: !secret tv_password
</div>
## Grouping and presentation
* Each group is presented as a panel in the UI.
* A group can be used as view using `view: True`.
* Default view can be overrided declaring a view with name `default_view`.
* Items not included in any group are shown in the default view.
* Use `group: !include groups.yaml` to split config.
* Groups are scriptable (`turn_on`, `turn_off`).
---
```yaml
media:
name: Media
switches:
name: Interruptores
entities:
- switch.pi_display
default_view:
view: True
name: Casa
entities:
- group.datetimeweather
- group.media
- group.switches
Keeps historic data, and shows the logbook and history entries in the frontend.
# Recorder
recorder:
# Enables support for tracking state changes over time.
history:
# View all events in a logbook
logbook:Device tracking can be achieved in several ways:
- Ping
- Network scanning
- Router integration
- Owntracks or GPSLogger
{style=float:right;width:50%;}
Components:
- Have state
- Trigger events
- Support actions
\
{style=float:right;width:38%;}
Ingredients of a HASS automation:
- Events that trigger the automation
- Conditions (optional)
- Commands / actions to run
{style=float:right;width:100%;}
automation:
- alias: Turn on music on light
trigger:
platform: state
entity_id: switch.kitchen_light
from: 'off'
to: 'on'
action:
- service: script.play_musicscript:
play_music:
sequence:
#- condition: state
# entity_id: input_boolean.welcome_music
# state: 'on'
- service: media_player.volume_set
entity_id:
- media_player.clementine_remote
data_template:
volume_level: "1.0"
- service: media_player.media_play
entity_id:
- media_player.clementine_remote
- delay: 00:00:08
- service: tts.google_say
entity_id: media_player.mpdlaptop
data_template:
message: "¡Hola Master Supremo! ¡Bienvenido!"Some options already include OpenZWave,
MQTT Broker (Mosquitto), HDMI-CEC for Raspberry Pi.
- Raspbian all-in-one (fabric)
- Hassbian
- Docker
- Custom scripts
Do not forget about SSL!
import asyncio
from datetime import timedelta
import logging
import time
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.media_player import (
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA,
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_MUSIC,
SUPPORT_VOLUME_SET, MediaPlayerDevice)
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, CONF_ACCESS_TOKEN,
STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN)
REQUIREMENTS = ['python-clementine-remote==1.0.1']
DEFAULT_NAME = 'Clementine Remote'
DEFAULT_PORT = 5500
SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \
SUPPORT_NEXT_TRACK | \
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_ACCESS_TOKEN, default=None): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Clementine platform."""
from clementineremote import ClementineRemote
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
token = config.get(CONF_ACCESS_TOKEN)
client = ClementineRemote(host, port, token, reconnect=True)
add_devices([ClementineDevice(client, config[CONF_NAME])])
class ClementineDevice(MediaPlayerDevice):
"""Representation of Clementine Player."""
def __init__(self, client, name):
"""Initialize the Clementine device."""
self._client = client
self._name = name
self._muted = False
self._volume = 0.0
self._track_id = 0
self._last_track_id = 0
self._track_name = ''
self._track_artist = ''
self._track_album_name = ''
self._state = STATE_UNKNOWN
def update(self):
"""Retrieve the latest data from the Clementine Player."""
try:
client = self._client
if client.state == 'Playing':
self._state = STATE_PLAYING
elif client.state == 'Paused':
self._state = STATE_PAUSED
elif client.state == 'Disconnected':
self._state = STATE_OFF
else:
self._state = STATE_PAUSED
if client.last_update and (time.time() - client.last_update > 40):
self._state = STATE_OFF
self._volume = float(client.volume) if client.volume else 0.0
if client.current_track:
self._track_id = client.current_track['track_id']
self._track_name = client.current_track['title']
self._track_artist = client.current_track['track_artist']
self._track_album_name = client.current_track['track_album']
except:
self._state = STATE_OFF
raise
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume / 100.0
@property
def source(self):
"""Return current source name."""
source_name = "Unknown"
client = self._client
if client.active_playlist_id in client.playlists:
source_name = client.playlists[client.active_playlist_id]['name']
return source_name
@property
def source_list(self):
"""List of available input sources."""
source_names = [s["name"] for s in self._client.playlists.values()]
return source_names
def select_source(self, source):
"""Select input source."""
client = self._client
sources = [s for s in client.playlists.values() if s['name'] == source]
if len(sources) == 1:
client.change_song(sources[0]['id'], 0)
@property
def media_content_type(self):
"""Content type of current playing media."""
return MEDIA_TYPE_MUSIC
@property
def media_title(self):
"""Title of current playing media."""
return self._track_name
@property
def media_artist(self):
"""Artist of current playing media, music track only."""
return self._track_artist
@property
def media_album_name(self):
"""Album name of current playing media, music track only."""
return self._track_album_name
@property
def supported_features(self):
"""Flag media player features that are supported."""
return SUPPORT_CLEMENTINE
@property
def media_image_hash(self):
"""Hash value for media image."""
if self._client.current_track:
return self._client.current_track['track_id']
return None
@asyncio.coroutine
def async_get_media_image(self):
"""Fetch media image of current playing image."""
if self._client.current_track:
image = bytes(self._client.current_track['art'])
return (image, 'image/png')
return None, None
def volume_up(self):
"""Volume up the media player."""
newvolume = min(self._client.volume + 4, 100)
self._client.set_volume(newvolume)
def volume_down(self):
"""Volume down media player."""
newvolume = max(self._client.volume - 4, 0)
self._client.set_volume(newvolume)
def mute_volume(self, mute):
"""Send mute command."""
self._client.set_volume(0)
def set_volume_level(self, volume):
"""Set volume level."""
self._client.set_volume(int(100 * volume))
def media_play_pause(self):
"""Simulate play pause media player."""
if self._state == STATE_PLAYING:
self.media_pause()
else:
self.media_play()
def media_play(self):
"""Send play command."""
self._state = STATE_PLAYING
self._client.play()
def media_pause(self):
"""Send media pause command to media player."""
self._state = STATE_PAUSED
self._client.pause()
def media_next_track(self):
"""Send next track command."""
self._client.next()
def media_previous_track(self):
"""Send the previous track command."""
self._client.previous()Pros:
- Clean and useful frontend.
- Support for hundreds of devices.
- Free and Open Source.
- Runs on Raspberry Pi.
- Your data is not sent to third parties.
Gotchas:
- 3 configuration styles (per item, per platform, per package).
- Different typing of variables and states.
- Components inconsistent behavior, may cause race conditions.
- API and config is still changing.
- Not limited to home: greenhouses, workshops, customer premises, IoT.
- Privacy, security and availability considerations.
- Risk of loss of control (cloud services).
-
Home Assistant: https://home-assistant.io/
-
This talk: https://github.com/jjmontesl/talk-hass-pydaygalicia2017
-
My config: https://github.com/jjmontesl/home-assistant-config
Thanks!


