├── .gitignore ├── appdaemon └── apps │ ├── apps.yaml │ ├── cube.py │ ├── cube.yaml │ ├── helpers │ ├── base.py │ ├── entities.py │ ├── entity_manager.yaml │ ├── helpers.yaml │ └── timers.py │ ├── logger.py │ ├── logger.yaml │ ├── presence.py │ ├── presence.yaml │ ├── timed_lights.py │ ├── timed_lights.yaml │ ├── timeofday.py │ ├── timeofday.yaml │ ├── vacuum.py │ └── vacuum.yaml ├── configuration.yaml ├── docker-compose.yml ├── lovelace ├── cameras │ ├── barnrum.yaml │ ├── hallen.yaml │ ├── kontor.yaml │ └── vardagsrum.yaml ├── coffee_card.yaml ├── dev_card.yaml ├── floorplan.yaml ├── functions │ ├── car.yaml │ ├── popup.yaml │ └── presence.yaml ├── main.yaml ├── popups │ ├── doors.yaml │ └── vacuum.yaml ├── power_card.yaml ├── resources.yaml ├── temperatures_card.yaml ├── timeofday_card.yaml └── views │ ├── dashboard_view.yaml │ ├── dev_view.yaml │ ├── diverse_view.yaml │ └── security_view.yaml ├── packages ├── appd_dummies.yaml ├── cameras.yaml ├── coffeemaker.yaml ├── computers.yaml ├── environment.yaml ├── hardware │ ├── mysensors.yaml │ └── rflink.yaml ├── hvac.yaml ├── ios.yaml ├── lights.yaml ├── logging.yaml ├── lovelace.yaml ├── mower.yaml.disabled ├── presence.yaml ├── reminders.yaml ├── security.yaml ├── tts.yaml └── vacuum.yaml └── python_scripts └── event.py /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | /*/ 3 | !.gitignore 4 | !configuration.yaml 5 | !docker-compose.yml 6 | !packages/ 7 | **/__pycache__/ 8 | !/appdaemon/ 9 | /appdaemon/* 10 | !/appdaemon/apps 11 | *.json 12 | *.hidden.* 13 | *_hidden.* 14 | !python_scripts 15 | 16 | !/lovelace/ 17 | -------------------------------------------------------------------------------- /appdaemon/apps/apps.yaml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /appdaemon/apps/cube.py: -------------------------------------------------------------------------------- 1 | from entities import Entities 2 | 3 | 4 | class Cube(Entities): 5 | 6 | def initialize(self): 7 | super().initialize() 8 | 9 | self.lights = ['light.taklampa_kontoret', 'switch.varme_mhr', 'switch.varme_mhr_2'] 10 | self.register_entity('upside', 'sensor.cube_up', True, 0, {}) 11 | self.register_entity('angle', 'sensor.cube_rotaton', True, 0, {}) 12 | 13 | self.listen_event(self.event, 'deconz_event') 14 | 15 | 16 | 17 | def event(self, ev, data, kwargs): 18 | self.log(ev) 19 | self.log(data) 20 | if data.get('id', None) == 'mi_magic_cube': 21 | side = data.get('event', 0) // 1000 22 | action = data.get('event', 0) % 10 23 | if side < 7 and action == 0: # Push 24 | self.log("Push") 25 | color = ['red', 'blue', 'green', 'yellow', 'purple', 'white'] 26 | self.call_service("light/turn_on", entity_id = self.lights[0], color_name = color[side-1]) 27 | elif side == 7 and action == 7: # Shake 28 | self.log("Shake") 29 | pass 30 | elif side == 7 and action == 8: # Drop 31 | self.log("Drop") 32 | pass 33 | elif action == side: # Double tap 34 | self.log("Double tap") 35 | if side == 1: 36 | self.toggle(side) 37 | if 0 < side < 7: 38 | self.e['upside'].state = side 39 | self.e['upside'].push() 40 | if data.get('id', None) == 'switch_55': 41 | angle = data.get('event', 0) / 100 42 | 43 | oldangle = float(self.e['angle'].state) 44 | newangle = oldangle + angle 45 | newangle = newangle if newangle < 360 else 360 46 | newangle = newangle if newangle > 0 else 0 47 | self.e['angle'].state = f"{newangle:.2f}" 48 | self.e['angle'].push() 49 | 50 | self.call_service("light/turn_on", entity_id = self.lights[0], brightness_pct = newangle/3.6) 51 | 52 | 53 | def toggle(self, light): 54 | self.log("Toggling light") 55 | self.call_service("homeassistant/toggle", entity_id = self.lights[light-1]) 56 | 57 | 58 | -------------------------------------------------------------------------------- /appdaemon/apps/cube.yaml: -------------------------------------------------------------------------------- 1 | cube: 2 | module: cube 3 | class: Cube 4 | -------------------------------------------------------------------------------- /appdaemon/apps/helpers/base.py: -------------------------------------------------------------------------------- 1 | import appdaemon.plugins.hass.hassapi as hass 2 | 3 | class Base(hass.Hass): 4 | def initialize(self): 5 | if(getattr(super(), 'initialize', False)): 6 | super().initialize() 7 | -------------------------------------------------------------------------------- /appdaemon/apps/helpers/entities.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import base 4 | 5 | class EntityManager(base.Base): 6 | ENTITY_FILE = 'entities.json' 7 | 8 | def initialize(self): 9 | statedir = os.path.dirname(os.path.realpath(__file__)) 10 | self.statefile = os.path.join(statedir, self.ENTITY_FILE) 11 | 12 | if not os.path.exists(self.statefile): 13 | os.mknod(self.statefile) 14 | self.db = {} 15 | else: 16 | with open(self.statefile) as f: 17 | self.db = json.load(f) 18 | self.get = self.db.get 19 | 20 | def terminate(self): 21 | with open(self.statefile, 'w') as f: 22 | json.dump(self.db, f) 23 | 24 | def __getitem__(self, key): 25 | return self.db.get(key, None) 26 | def __setitem__(self, key, value): 27 | self.db[key] = value 28 | 29 | class Entities(base.Base): 30 | 31 | def initialize(self): 32 | if(getattr(super(), 'initialize', False)): 33 | super().initialize() 34 | self.e = {} 35 | 36 | def register_entity(self, name, entity, managed=False, default=None, attributes=None): 37 | domain, _ = entity.split('.') 38 | controller = { 39 | 'light': LightEntity, 40 | 'switch': SwitchEntity, 41 | 'input_number': InputNumberEntity, 42 | 'input_datetime': InputDateTimeEntity, 43 | 'input_select': InputSelectEntity, 44 | }.get(domain, Entity) 45 | self.e[name] = controller(self, entity, managed, default, attributes) 46 | return self.e[name] 47 | 48 | 49 | class Entity: 50 | 51 | def __init__(self, hass, entity, managed=False, state=None, attributes=None): 52 | self._hass = hass 53 | self.entity_id = entity 54 | self.domain, self.name = entity.split('.') 55 | self._managed = managed 56 | manager = hass.get_app('entity_manager') if managed else {} 57 | self.manager = manager 58 | self._state = manager.get(entity, state) 59 | self._laststate = None 60 | self._attributes = attributes 61 | self._listeners = [] 62 | 63 | hass.listen_state(self._listener, entity=entity, attribute='all') 64 | 65 | if managed: 66 | self.push() 67 | hass.listen_event(self._service_listener, event = 'call_service') 68 | else: 69 | self.pull() 70 | 71 | # State change listeners 72 | def listen(self, callback, kwarg=None): 73 | self._listeners.append({ 74 | 'callback': callback, 75 | 'kwarg': kwarg, 76 | }) 77 | return self._listeners[-1] 78 | 79 | def unlisten(self, handle): 80 | if handle in self._listeners: 81 | self._listeners.remove(handle) 82 | 83 | def _listener(self, entity, attribute, old, new, kwargs): 84 | self._state = new['state'] 85 | self._attributes = new['attributes'] 86 | old, new = self._laststate, self._state 87 | if old != new: 88 | self.manager[self.entity_id] = self._state 89 | self._laststate = new 90 | self._callback(old, new) 91 | 92 | def _callback(self, old, new): 93 | for l in self._listeners: 94 | l['callback'](old, new, l['kwarg']) 95 | pass 96 | 97 | # Updating state 98 | def pull(self): 99 | d = self._hass.get_state(self.entity_id, attribute='all') 100 | self._state = d['state'] 101 | self.manager[self.entity_id] = self._state 102 | self._laststate = self._state 103 | self._attributes = d['attributes'] 104 | 105 | def push(self): 106 | if self._state != self._laststate: 107 | self.set_state(self._laststate, self._state) 108 | self._laststate = self._state 109 | self._callback(self._laststate, self._state) 110 | self._hass.set_state(self.entity_id, state=self._state, attributes=self._attributes) 111 | self.manager[self.entity_id] = self._state 112 | 113 | def set_state(self, old, new): 114 | pass 115 | 116 | # If the entity is controller by appd, changes made in the GUI will be communicated via service calls 117 | def _service_listener(self, event, data, kwarg): 118 | if data['service_data'].get('entity_id', '') == self.entity_id: 119 | self.service_callback(data) 120 | 121 | def service_callback(self, data): 122 | pass 123 | 124 | # 125 | @property 126 | def state(self): 127 | return self._state 128 | @state.setter 129 | def state(self, value): 130 | self._state = value 131 | 132 | @property 133 | def attr(self): 134 | return self._attributes 135 | 136 | 137 | class LightEntity(Entity): 138 | 139 | def set_state(self, old, new): 140 | if new == "on" or new == True: 141 | self._hass.call_service("light/turn_on", entity_id = self.entity_id) 142 | self._state = "on" 143 | elif new == "off" or new == False: 144 | self._hass.call_service("light/turn_off", entity_id = self.entity_id) 145 | self._state = "off" 146 | 147 | 148 | class SwitchEntity(Entity): 149 | 150 | def set_state(self, old, new): 151 | if new == "on" or new == True: 152 | self._hass.call_service("switch/turn_on", entity_id = self.entity_id) 153 | self._state = "on" 154 | if new == "off" or new == False: 155 | self._hass.call_service("switch/turn_off", entity_id = self.entity_id) 156 | self._state = "off" 157 | 158 | def service_callback(self, data): 159 | if data['service'] == 'turn_on': 160 | self._state = "on" 161 | if data['service'] == 'turn_off': 162 | self._state = "off" 163 | self.push() 164 | 165 | 166 | class InputNumberEntity(Entity): 167 | 168 | def service_callback(self, data): 169 | if data['domain'] == 'input_number' and data['service'] == 'set_value': 170 | self._state = data['service_data']['value'] 171 | self.push() 172 | 173 | class InputDateTimeEntity(Entity): 174 | 175 | def set_state(self, old, new): 176 | time = new.split(':') 177 | time += ["00"] * (3 - len(time)) 178 | self._state = ':'.join(time) 179 | self.attr['hour'] = int(time[0], base=10) 180 | self.attr['minute'] = int(time[1], base=10) 181 | self.attr['seconf'] = int(time[2], base=10) 182 | 183 | def service_callback(self, data): 184 | if data['service'] == 'set_datetime': 185 | self._state = data['service_data'].get('time', "00:00:00") 186 | self.push() 187 | 188 | class InputSelectEntity(Entity): 189 | 190 | def service_callback(self, data): 191 | if data['service'] == 'select_option': 192 | self._state = data['service_data'].get('option', None) 193 | self.push() 194 | -------------------------------------------------------------------------------- /appdaemon/apps/helpers/entity_manager.yaml: -------------------------------------------------------------------------------- 1 | entity_manager: 2 | module: entities 3 | class: EntityManager 4 | global_dependencies: 5 | - base 6 | -------------------------------------------------------------------------------- /appdaemon/apps/helpers/helpers.yaml: -------------------------------------------------------------------------------- 1 | global_modules: 2 | - base 3 | - entities 4 | - timers 5 | -------------------------------------------------------------------------------- /appdaemon/apps/helpers/timers.py: -------------------------------------------------------------------------------- 1 | import base 2 | 3 | class Timers(base.Base): 4 | def initialize(self): 5 | if(getattr(super(), 'initialize', False)): 6 | super().initialize() 7 | self._timers = {} 8 | 9 | functions = [ 10 | 'run_in', 11 | 'run_once', 12 | 'run_at', 13 | 'run_daily', 14 | 'run_hourly', 15 | 'run_minutely', 16 | 'run_every', 17 | 'run_at_sunrise', 18 | 'run_at_sunset', 19 | ] 20 | for f in functions: 21 | self._override(f) 22 | 23 | setattr(self, '_cancel_timer', super().cancel_timer) 24 | 25 | def cancel_timer(self, name, *args, **kwargs): 26 | if type(name) is str: 27 | if name in self._timers: 28 | return super(Timers, self).cancel_timer(self._timers[name]) 29 | else: 30 | return super(Timers, self).cancel_timer(*args, **kwargs) 31 | 32 | def _override(self, f): 33 | setattr(self, f'_{f}', getattr(self, f)) 34 | def fn(name, *args, **kwargs): 35 | if type(name) is str: 36 | if name in self._timers: 37 | super(Timers, self).cancel_timer(self._timers[name]) 38 | self._timers[name] = getattr(self, f'_{f}')(*args, **kwargs) 39 | return self._timers[name] 40 | else: 41 | return getattr(self, f'_{f}')(name, *args, **kwargs) 42 | setattr(self, f, fn) 43 | -------------------------------------------------------------------------------- /appdaemon/apps/logger.py: -------------------------------------------------------------------------------- 1 | from base import Base 2 | import collections 3 | import requests 4 | import datetime 5 | 6 | class Logger(Base): 7 | def initialize(self): 8 | super().initialize() 9 | 10 | self.q = collections.deque() 11 | self.listen_event(self.log_event, 'LOG_WRITE') 12 | 13 | self.listen_event(self.event) 14 | self.listen_state(self.state) 15 | 16 | self.log_write("Appdaemon logger started") 17 | 18 | def send(self, kwargs): 19 | # Send messages from the queue, but keep track of the rate limit 20 | while len(self.q): 21 | msg = self.q.popleft() 22 | url = self.args['channels'][msg.get('channel', 'log')] 23 | if url: 24 | res = requests.post(url, json={ 25 | "content": msg['message'], 26 | "username": "Appdaemon", 27 | }) 28 | if res.status_code == 429 or (res.status_code == 200 and res.headers['X-RateLimit-Remaining'] == 0): 29 | # If rate limit exceeded or nearly so 30 | if res.status_code == 429: 31 | self.q.appendleft(msg) 32 | next_try = datetime.datetime.fromtimestamp(int(res.headers['X-RateLimit-Reset'])) 33 | self.run_at(self.send, next_try) 34 | break 35 | 36 | def log_write(self, message, channel = 'log'): 37 | if not message: 38 | return 39 | self.q.append({ 40 | 'message': message, 41 | 'channel': channel, 42 | }) 43 | if len(self.q) == 1: 44 | self.run_in(self.send, 1) 45 | def log_event(self, ev, data, kwargs): 46 | self.log_write(data['message'], data.get('channel', 'log')) 47 | 48 | 49 | def event(self, name, data, kwargs): 50 | if name == 'call_service' and data['service'] == 'discord_log': 51 | return 52 | self.log_write("EVENT: {} {}".format(name, data)) 53 | 54 | def state(self, entity, attribute, old, new, kwargs): 55 | tracked_domains = ['light', 'switch', 'device_tracker', 'binary_sensor'] 56 | domain = entity.split('.')[0] 57 | if domain in tracked_domains: 58 | self.log_write("STATE: {} {} {} =>{}".format(entity, attribute, old, new)) 59 | -------------------------------------------------------------------------------- /appdaemon/apps/logger.yaml: -------------------------------------------------------------------------------- 1 | logger: 2 | module: logger 3 | class: Logger 4 | global_dependencies: 5 | - base 6 | 7 | channels: 8 | log: !secret discord_log 9 | info: !secret discord_info 10 | 11 | -------------------------------------------------------------------------------- /appdaemon/apps/presence.py: -------------------------------------------------------------------------------- 1 | from entities import Entities 2 | from timers import Timers 3 | 4 | class Presence(Timers, Entities): 5 | 6 | HOME = 'home' 7 | AWAY = 'not_home' 8 | ARRIVED = 'just_arrived' 9 | LEFT = 'just_left' 10 | TIMEOUT = 300 11 | 12 | def initialize(self): 13 | super().initialize() 14 | 15 | attr = self.args.get('attributes', {}) 16 | 17 | self.register_entity('tracker', self.args['name'], managed=True, default="not_home", attributes=self.args.get('attributes',{})) 18 | self.tracker = self.e['tracker'] 19 | if(self.tracker.state in [self.ARRIVED, self.LEFT]): 20 | self.tracker.state = self.AWAY 21 | 22 | self.devices = [] 23 | for i,d in enumerate(self.args['devices']): 24 | name = f"device{i}" 25 | self.register_entity(name, d); 26 | self.e[name].listen(self.update, {"name": name}) 27 | self.devices.append(self.e[name]) 28 | 29 | self.update() 30 | 31 | def update(self, old=None, new=None, kwarg=None): 32 | if not old or isinstance(old, str): 33 | timeout = False 34 | elif old is None: 35 | timeout = False 36 | else: 37 | timeout = old.get('trigger', False) 38 | new_state = self.is_home() 39 | old_state = self.tracker.state 40 | if new_state: 41 | if old_state == self.AWAY: 42 | self.run_in('timeout', self.update, self.TIMEOUT, trigger = "timeout") 43 | state = self.ARRIVED 44 | elif old_state == self.LEFT: 45 | self.cancel_timer('timeout') 46 | state = self.HOME 47 | elif old_state == self.ARRIVED and timeout: 48 | state = self.HOME 49 | else: 50 | state = old_state 51 | else: 52 | if old_state == self.HOME: 53 | self.run_in('timeout', self.update, self.TIMEOUT, trigger = "timeout") 54 | state = self.LEFT 55 | elif old_state == self.ARRIVED: 56 | self.cancel_timer('timeout') 57 | state = self.AWAY 58 | elif old_state == self.LEFT and timeout: 59 | state = self.AWAY 60 | else: 61 | state = old_state 62 | 63 | self.tracker.state = state 64 | self.tracker.push() 65 | 66 | def is_home(self): 67 | for d in self.devices: 68 | if d.domain == 'device_tracker': 69 | if d.attr['source_type'] in ['router']: 70 | if d.state == 'home': 71 | return True 72 | if d.domain == 'sensor': 73 | if d.state != 'unknown' and int(d.state) > 0: 74 | return True 75 | 76 | return False 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /appdaemon/apps/presence.yaml: -------------------------------------------------------------------------------- 1 | presence_thomas: 2 | module: presence 3 | class: Presence 4 | global_dependencies: 5 | - entities 6 | - timers 7 | dependencies: 8 | - entity_manager 9 | 10 | name: device_tracker.thomas_presence 11 | attributes: {} 12 | 13 | devices: 14 | - device_tracker.thomas_iphone_beta 15 | - device_tracker.thomas_iphone_2 16 | - sensor.thomas_iphone_bt 17 | - sensor.thomas_iphone_bt2 18 | 19 | presence_anneli: 20 | module: presence 21 | class: Presence 22 | global_depencencies: 23 | - entities 24 | - timers 25 | dependencies: 26 | - entity_manager 27 | 28 | name: device_tracker.anneli_presence 29 | attributes: {} 30 | 31 | devices: 32 | - device_tracker.anneli_lovn 33 | - device_tracker.anneli_loven 34 | - sensor.anneli_iphone_bt 35 | - sensor.anneli_iphone_bt2 36 | # - sensor.anneli_klocka_bt 37 | # - sensor.anneli_klocka_bt2 38 | -------------------------------------------------------------------------------- /appdaemon/apps/timed_lights.py: -------------------------------------------------------------------------------- 1 | from entities import Entities 2 | 3 | class OutsideLights(Entities): 4 | def initialize(self): 5 | super().initialize() 6 | for l in self.args['lights']: 7 | e = self.register_entity(l, l) 8 | e.listen(self.found, {'entity': e}) 9 | 10 | 11 | self.listen_event(self.update, 'TOD_TOD') 12 | self.run_in(lambda *_: self.update(None, None, None), 2) 13 | 14 | def update(self, event, data, kwarg): 15 | self.state = self.get_app('timeofday').dark 16 | if self.get_app('timeofday').tod == 'night': 17 | self.state = False 18 | 19 | self.log(f"OUTSIDE LIGHTS - Turning lights {self.state}") 20 | for l in self.e: 21 | self.e[l].state = self.state 22 | self.e[l].push() 23 | 24 | def found(self, old, new, kwarg): 25 | entity = kwarg.get('entity', None) 26 | if not entity: return; 27 | 28 | if old == "unavailable": 29 | self.log(f"{entity.entity_id} showed up, setting to {self.state}") 30 | entity.state = self.state 31 | entity.push() 32 | 33 | class DecorativeLights(OutsideLights): 34 | def update(self, event, data, kwarg): 35 | self.state = self.get_app('timeofday').dark 36 | self.log(f"DECORATIVE LIGHTS - Turning lights {self.state}") 37 | for l in self.e: 38 | self.e[l].state = self.state 39 | self.e[l].push() 40 | 41 | -------------------------------------------------------------------------------- /appdaemon/apps/timed_lights.yaml: -------------------------------------------------------------------------------- 1 | outside_lights: 2 | module: timed_lights 3 | class: OutsideLights 4 | global_dependencies: 5 | - entities 6 | dependencies: 7 | - timeofday 8 | - entity_manager 9 | lights: 10 | - light.ytterbelysning 11 | - light.koksfonster 12 | 13 | 14 | decorative_lights: 15 | module: timed_lights 16 | class: DecorativeLights 17 | global_dependencies: 18 | - entities 19 | dependencies: 20 | - timeofday 21 | - entity_manager 22 | lights: 23 | - light.deko_vardagsrum 24 | - light.skotbord 25 | - light.trappbelysning 26 | - light.kontor_fonster 27 | - light.fasadbelysning 28 | -------------------------------------------------------------------------------- /appdaemon/apps/timeofday.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | from timers import Timers 4 | from entities import Entities 5 | class TimeOfDay(Timers, Entities): 6 | 7 | TOD = ['morning', 'day', 'evening', 'night'] 8 | 9 | def initialize(self): 10 | super().initialize() 11 | 12 | self.run_in(self._setup_inputs, 1) 13 | 14 | self.updating = False 15 | 16 | def _setup_inputs(self, kwargs): 17 | inputs = [ 18 | 'morning', 19 | 'day', 20 | 'evening', 21 | 'night', 22 | 'sunrise', 23 | 'sunset', 24 | 'tod', 25 | 'dark', 26 | ] 27 | for i in inputs: 28 | e = dict(self.args[i]) 29 | name = e['name'] 30 | default = e['default'] 31 | del e['name'] 32 | del e['default'] 33 | self.register_entity(i, name, True, default, e) 34 | for i in inputs: 35 | self.e[i].listen(self._update, {'trigger': 'setting', 'entity': i}) 36 | 37 | self._update(None, None, {'trigger': 'init'}) 38 | 39 | def _update(self, old=None, new=None, kwarg=None): 40 | 41 | if self.updating: 42 | return 43 | 44 | if kwarg is None: 45 | kwarg = old 46 | trigger = kwarg.get('trigger', None) 47 | 48 | self.log(f"TOD - updated by {trigger}") 49 | 50 | # Set up triggers for each TOD 51 | for t in self.TOD: 52 | if not trigger == t: 53 | self.run_once( 54 | t, 55 | self._update, 56 | self.parse_time(self.e[t].state), 57 | trigger=t 58 | ) 59 | 60 | # Set up triggers for sunrise and sunset 61 | sunrise = float(self.e['sunrise'].state) 62 | sunrise = timedelta(minutes=sunrise) 63 | sunrise = (self.sunrise() + sunrise).time() 64 | if trigger != 'sunrise': 65 | self.run_once( 66 | 'sunrise', 67 | self._update, 68 | sunrise, 69 | trigger='sunrise' 70 | ) 71 | sunset = float(self.e['sunset'].state) 72 | sunset = timedelta(minutes=sunset) 73 | sunset = (self.sunset() + sunset).time() 74 | if trigger != 'sunset': 75 | self.run_once( 76 | 'sunset', 77 | self._update, 78 | sunset, 79 | trigger='sunset' 80 | ) 81 | 82 | # Determine current time of day 83 | if trigger in self.TOD: 84 | tod = trigger 85 | else: 86 | tod = 'night' 87 | for t in self.TOD: 88 | if self.time() >= self.parse_time(self.e[t].state): 89 | tod = t 90 | 91 | # Determine if sun is down 92 | if trigger == 'sunrise': 93 | dark = False 94 | elif trigger == 'sunset': 95 | dark = True 96 | elif sunrise <= self.time() <= sunset: 97 | dark = False 98 | else: 99 | dark = True 100 | 101 | 102 | self.log(f"TOD - Time of day is {tod}, sun {'has set' if dark else 'is up'}") 103 | 104 | # Update outputs 105 | self.updating = True 106 | self.e['dark'].state = dark 107 | self.e['dark'].push() 108 | self.e['tod'].state = tod 109 | self.e['tod'].push() 110 | self.e['sunrise'].attr['time'] = sunrise.strftime("%H:%M:%S") 111 | self.e['sunrise'].push() 112 | self.e['sunset'].attr['time'] = sunset.strftime("%H:%M:%S") 113 | self.e['sunset'].push() 114 | self.updating = False 115 | 116 | # Tell listeners if Time Of Day or Dark has changed 117 | self.fire_event('TOD_TOD', old = old, new = new) 118 | 119 | @property 120 | def tod(self): 121 | return self.e['tod'].state 122 | @property 123 | def dark(self): 124 | return self.e['dark'].state == "on" 125 | -------------------------------------------------------------------------------- /appdaemon/apps/timeofday.yaml: -------------------------------------------------------------------------------- 1 | timeofday: 2 | module: timeofday 3 | class: TimeOfDay 4 | global_dependencies: 5 | - base 6 | - entities 7 | - timers 8 | 9 | morning: 10 | name: input_datetime.tod_morning 11 | default: "05:45" 12 | has_date: False 13 | has_time: True 14 | friendly_name: Morning 15 | icon: mdi:weather-sunset-up 16 | day: 17 | name: input_datetime.tod_day 18 | default: "08:00" 19 | has_date: False 20 | has_time: True 21 | friendly_name: Day 22 | icon: mdi:weather-sunny 23 | evening: 24 | name: input_datetime.tod_evening 25 | default: "19:00" 26 | has_date: False 27 | has_time: True 28 | friendly_name: Evening 29 | icon: mdi:weather-sunset-down 30 | night: 31 | name: input_datetime.tod_night 32 | default: "23:00" 33 | has_date: False 34 | has_time: True 35 | friendly_name: Night 36 | icon: mdi:minus 37 | sunrise: 38 | name: input_number.tod_sunrise_offset 39 | default: 30 40 | min: -120 41 | max: 120 42 | step: 5 43 | mode: slider 44 | sunset: 45 | name: input_number.tod_sunset_offset 46 | default: -30 47 | min: -120 48 | max: 120 49 | step: 5 50 | mode: slider 51 | 52 | tod: 53 | name: input_select.tod 54 | default: morning 55 | friendly_name: Time of Day 56 | options: 57 | - morning 58 | - day 59 | - evening 60 | - night 61 | dark: 62 | name: switch.tod_dark 63 | default: on 64 | friendly_name: Sun Down 65 | icon: mdi:weather-night 66 | -------------------------------------------------------------------------------- /appdaemon/apps/vacuum.py: -------------------------------------------------------------------------------- 1 | from entities import Entities 2 | 3 | class Vacuum(Entities): 4 | def initialize(self): 5 | super().initialize() 6 | self.entity_id = self.args['entity_id'] 7 | 8 | self.run_in(self._setup, 1) 9 | 10 | def _setup(self, kwargs): 11 | for zone in self.args['zones']: 12 | e = dict(self.args['zones'][zone]) 13 | name = f'switch.vacuum_{zone}' 14 | self.register_entity(zone, name, True, "off", e) 15 | 16 | self.listen_event(self.clean_zone, 'VACUUM_ZONE') 17 | self.listen_event(self.clean_all, 'VACUUM_ALL') 18 | self.listen_event(self.service, 'VACUUM_SERVICE') 19 | self.listen_event(self.home, 'VACUUM_HOME') 20 | 21 | def clean_zone(self, ev, data, kwargs): 22 | areas = [] 23 | for zone in self.args['zones']: 24 | if self.e[zone].state == "on": 25 | areas.append(self.e[zone].attr['area']) 26 | 27 | self.call_service("vacuum/xiaomi_clean_zone", repeats = 1, zone = areas) 28 | 29 | def clean_all(self, ev, data, kwargs): 30 | self.call_service("vacuum/start") 31 | def service(self, ev, data, kwargs): 32 | self.call_service("vacuum/send_command", entity_id = self.entity_id, command = 'app_goto_target', params = self.args['empty_spot']) 33 | def home(self, ev, data, kwargs): 34 | self.call_service("vacuum/return_to_base", entity_id = self.entity_id) 35 | -------------------------------------------------------------------------------- /appdaemon/apps/vacuum.yaml: -------------------------------------------------------------------------------- 1 | vacuum: 2 | module: vacuum 3 | class: Vacuum 4 | global_dependencies: 5 | - base 6 | - entities 7 | 8 | entity_id: vacuum.xiaomi_vacuum_cleaner 9 | 10 | empty_spot: [26000, 26000] 11 | 12 | zones: 13 | kitchen: 14 | # Left, bottom, right, top 15 | area: [23200, 21000, 27500, 25100] 16 | friendly_name: Köket 17 | icon: mdi:floor-plan 18 | hall: 19 | area: [23200, 25300, 27500, 28000] 20 | friendly_name: Hallen 21 | icon: mdi:floor-plan 22 | livingroom: 23 | area: [19000, 24000, 23000, 31200] 24 | friendly_name: Vardagsrum 25 | icon: mdi:floor-plan 26 | bedroom: 27 | area: [19000, 31500, 23500, 35500] 28 | friendly_name: Sovrum 29 | icon: mdi:floor-plan 30 | office: 31 | area: [23300, 31000, 27500, 35500] 32 | friendly_name: Kontoret 33 | icon: mdi:floor-plan 34 | 35 | -------------------------------------------------------------------------------- /configuration.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | name: Åvägen 3 | 4 | latitude: !secret latitude_home 5 | longitude: !secret longitude_home 6 | elevation: !secret elevation_home 7 | time_zone: !secret timezone_home 8 | unit_system: metric 9 | 10 | # Everything important is here 11 | packages: !include_dir_named packages 12 | 13 | default_config: 14 | discovery: 15 | frontend: 16 | themes: !include_dir_merge_named themes 17 | recorder: 18 | db_url: mysql://hass:hass@db:3306/hass?charset=utf8 19 | logger: 20 | default: info 21 | logs: 22 | homeassistant.components.http.view: warn 23 | influxdb: 24 | host: influx 25 | username: hass 26 | password: hass 27 | database: hass 28 | 29 | homekit: 30 | ip_address: !secret host_ip 31 | auto_start: false 32 | filter: 33 | include_domains: 34 | - light 35 | - switch 36 | 37 | http: 38 | # mydomain.com:443 - proxied by nginx to port 8123 39 | base_url: !secret base_url 40 | use_x_forwarded_for: true 41 | trusted_proxies: !secret proxy_hosts 42 | 43 | cloud: 44 | 45 | stream: 46 | 47 | python_script: 48 | 49 | hacs: 50 | token: !secret hacs_token 51 | 52 | lovelace_gen: 53 | 54 | automation: 55 | - alias: Hallampa på 56 | initial_state: true 57 | trigger: 58 | platform: state 59 | entity_id: binary_sensor.framdorr 60 | to: "on" 61 | condition: 62 | condition: state 63 | entity_id: switch.tod_dark 64 | state: "on" 65 | action: 66 | - service: light.turn_on 67 | entity_id: light.stora_hallen 68 | - alias: Start homekit 69 | trigger: 70 | - platform: homeassistant 71 | event: start 72 | action: 73 | - delay: 00:05 74 | - service: homekit.start 75 | 76 | browser_mod: 77 | devices: 78 | d94309ff-1b8be6a4: 79 | name: hallen 80 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.0' 2 | 3 | networks: 4 | # By adding the homeassistant image to this network, it gets a local ip in 5 | # the network and can use the discovery component 6 | host: 7 | driver: macvlan 8 | driver_opts: 9 | parent: vmbr1 10 | ipam: 11 | config: 12 | # The following values are overridden in docker-compose.override.yml 13 | - subnet: 14 | gateway: 15 | ip_range: /32 16 | 17 | services: 18 | mysensors: 19 | container_name: MySensors 20 | image: akshmakov/serialport-server:amd64 21 | restart: always 22 | devices: 23 | - "/dev/mysensors:/dev/ttyUSB0" 24 | environment: 25 | DEVICE: /dev/ttyUSB0 26 | BAUDRATE: 115200 27 | 28 | rflink: 29 | container_name: RFLink 30 | image: akshmakov/serialport-server:amd64 31 | restart: always 32 | devices: 33 | - "/dev/rflink:/dev/ttyUSB0" 34 | environment: 35 | DEVICE: /dev/ttyUSB0 36 | BAUDRATE: 57600 37 | 38 | deconz: 39 | container_name: deCONZ 40 | image: marthoc/deconz:amd64-2.05.OVERRIDE-ME 41 | restart: always 42 | ports: 43 | - "8082:8082" # Management port 44 | - "5900:5900" # VNC port 45 | devices: 46 | - "/dev/conbee:/dev/ttyUSB0" 47 | volumes: 48 | - /root/docker/deconz:/root/.local/share/dresden-elektronik/deCONZ 49 | environment: 50 | DECONZ_WEB_PORT: 8082 51 | DECONZ_WS_PORT: 10443 52 | DECONZ_VNC_MODE: 1 53 | # The following value is overridden in docker-compose.override.yml 54 | DECONZ_VNC_PASSWORD: secret 55 | 56 | db: 57 | container_name: MariaDB 58 | image: mariadb 59 | restart: always 60 | volumes: 61 | - /root/docker/mariadb:/var/lib/mysql 62 | environment: 63 | TZ: Europe/Stockholm 64 | MYSQL_DATABASE: hass 65 | MYSQL_USER: hass 66 | MYSQL_PASSWORD: hass 67 | # The following value is overridden in docker-compose.override.yml 68 | MYSQL_ROOT_PASSWORD: secret 69 | influx: 70 | container_name: InfluxDB 71 | image: influxdb 72 | restart: always 73 | volumes: 74 | - /root/docker/influxdb:/var/lib/influxdb 75 | environment: 76 | TZ: Europe/Stockholm 77 | INFLUXDB_DB: hass 78 | INFLUXDB_USER: hass 79 | INFLUXDB_USER_PASSWORD: hass 80 | 81 | grafana: 82 | container_name: Grafana 83 | image: grafana/grafana 84 | restart: always 85 | ports: 86 | - "3000:3000" 87 | volumes: 88 | - /root/docker/grafana:/var/lib/grafana 89 | user: "0" 90 | environment: 91 | TZ: Europe/Stockholm 92 | 93 | 94 | mosquitto: 95 | container_name: Mosquitto 96 | image: eclipse-mosquitto:latest 97 | restart: always 98 | ports: 99 | - "1883:1883" 100 | - "9001:9001" # Websocket port 101 | volumes: 102 | - /root/docker/mosquitto/data:/mosquitto/data 103 | - /root/docker/mosquitto/log:/mosquitto/log 104 | 105 | homeassistant: 106 | container_name: HomeAssistant 107 | # The following value is overridden in docker-compose.override.yml 108 | image: homeassistant/home-assistant:override_me 109 | restart: always 110 | networks: 111 | - default 112 | - host 113 | ports: 114 | - "8123:8123" 115 | volumes: 116 | - /root/docker/hass/home-assistant:/config 117 | working_dir: /config 118 | environment: 119 | TZ: Europe/Stockholm 120 | XDG_CACHE_HOME: /config/.data 121 | PIP_USER: "yes" 122 | PYTHONUSERBASE: /config/.local 123 | 124 | appdaemon: 125 | container_name: AppDaemon 126 | image: acockburn/appdaemon 127 | restart: always 128 | volumes: 129 | - /root/docker/hass/home-assistant/appdaemon:/conf 130 | - /root/docker/appdaemon/certs:/certs 131 | environment: 132 | TZ: Europe/Stockholm 133 | # The following values ar overridden in docker-compose.override.yml 134 | HA_URL: homeassistant:8123 135 | DASH_URL: appdaemon:5050 136 | TOKEN: secret_token 137 | 138 | glances: 139 | container_name: glances 140 | image: nicolargo/glances 141 | restart: always 142 | volumes: 143 | - /var/run/docker.sock:/var/run/docker.sock:ro 144 | environment: 145 | TZ: Europe/Stockholm 146 | GLANCES_OPT: -w 147 | pid: host 148 | ports: 149 | - 61208:61208 150 | - 61209:61209 151 | -------------------------------------------------------------------------------- /lovelace/cameras/barnrum.yaml: -------------------------------------------------------------------------------- 1 | anchors: 2 | camera: &camera camera.barnrum 3 | type: picture-elements 4 | title: Barnrum 5 | image: /local/images/placeholder-1280x720.png 6 | elements: 7 | - type: image 8 | camera_image: *camera 9 | style: 10 | left: 50% 11 | top: 50% 12 | height: 100% 13 | width: 100% 14 | 15 | - type: icon 16 | icon: mdi:chevron-left 17 | tap_action: 18 | action: call-service 19 | service: script.camera_ptz 20 | service_data: 21 | entity_id: *camera 22 | dir: left 23 | style: 24 | left: 30px 25 | top: 50% 26 | color: yellow 27 | 28 | - type: icon 29 | icon: mdi:chevron-right 30 | tap_action: 31 | action: call-service 32 | service: script.camera_ptz 33 | service_data: 34 | entity_id: *camera 35 | dir: right 36 | style: 37 | right: 6px 38 | top: 50% 39 | color: yellow 40 | 41 | - type: icon 42 | icon: mdi:chevron-up 43 | tap_action: 44 | action: call-service 45 | service: script.camera_ptz 46 | service_data: 47 | entity_id: *camera 48 | dir: up 49 | style: 50 | left: 50% 51 | top: 30px 52 | color: yellow 53 | 54 | - type: icon 55 | icon: mdi:chevron-down 56 | tap_action: 57 | action: call-service 58 | service: script.camera_ptz 59 | service_data: 60 | entity_id: *camera 61 | dir: down 62 | style: 63 | left: 50% 64 | bottom: 6px 65 | color: yellow 66 | 67 | - type: icon 68 | icon: mdi:numeric-1-box-outline 69 | tap_action: 70 | action: call-service 71 | service: rest_command.camera_position 72 | service_data: 73 | entity_id: *camera 74 | position: 0 75 | style: 76 | left: 30px 77 | bottom: 0px 78 | color: blue 79 | - type: icon 80 | icon: mdi:numeric-2-box-outline 81 | tap_action: 82 | action: call-service 83 | service: rest_command.camera_position 84 | service_data: 85 | entity_id: *camera 86 | position: 1 87 | style: 88 | left: 66px 89 | bottom: 0px 90 | color: green 91 | -------------------------------------------------------------------------------- /lovelace/cameras/hallen.yaml: -------------------------------------------------------------------------------- 1 | type: picture-elements 2 | title: Hallen 3 | image: /local/images/placeholder-800x600.png 4 | elements: 5 | - type: image 6 | camera_image: camera.hallen 7 | style: 8 | left: 50% 9 | top: 50% 10 | height: 100% 11 | width: 100% 12 | -------------------------------------------------------------------------------- /lovelace/cameras/kontor.yaml: -------------------------------------------------------------------------------- 1 | type: picture-elements 2 | title: Kontoret 3 | image: /local/images/placeholder-800x600.png 4 | elements: 5 | # Only show camera feed if noone is home 6 | - type: custom:hui-conditional-card 7 | conditions: 8 | - entity: binary_sensor.home_occupied 9 | state: "off" 10 | card: 11 | type: custom:hui-image-element 12 | camera_image: camera.kontor 13 | style: 14 | left: 50% 15 | top: 50% 16 | height: 100% 17 | width: 100% 18 | 19 | - type: icon 20 | icon: no:icon 21 | tap_action: 22 | action: none 23 | style: 24 | left: 50% 25 | top: 25px 26 | height: 50px 27 | width: 100% 28 | background: rgba(128,128,128,0.3) 29 | 30 | - type: state-icon 31 | entity: light.taklampa_kontoret 32 | tap_action: 33 | action: toggle 34 | style: 35 | left: 30px 36 | top: 25px 37 | 38 | - type: state-icon 39 | entity: light.skrivbordslampa_1 40 | tap_action: 41 | action: toggle 42 | style: 43 | left: 66px 44 | top: 25px 45 | -------------------------------------------------------------------------------- /lovelace/cameras/vardagsrum.yaml: -------------------------------------------------------------------------------- 1 | type: picture-elements 2 | title: Vardagsrum 3 | image: /local/images/placeholder-800x600.png 4 | elements: 5 | - type: custom:hui-conditional-card 6 | conditions: 7 | - entity: binary_sensor.home_occupied 8 | state: "off" 9 | card: 10 | type: custom:hui-image-element 11 | camera_image: camera.vardagsrum 12 | style: 13 | left: 50% 14 | top: 50% 15 | height: 100% 16 | width: 100% 17 | -------------------------------------------------------------------------------- /lovelace/coffee_card.yaml: -------------------------------------------------------------------------------- 1 | type: entities 2 | entities: 3 | - type: custom:hui-picture-entity-card 4 | entity: switch.kaffebryggare 5 | image: !file /local/images/moccamaster.jpg 6 | show_state: false 7 | show_name: false 8 | tap_action: { action: toggle } 9 | - type: custom:fold-entity-row 10 | head: 11 | entity: switch.kaffebryggare 12 | icon: mdi:coffee 13 | items: 14 | - entity: input_boolean.cfe_on 15 | style: | 16 | :host { 17 | --paper-item-icon-color: {% if is_state(config.entity, 'on') %} var(--paper-item-active-color) {% else %} var(--paper-item-color) {% endif %}; 18 | } 19 | 20 | - entity: input_datetime.cfe_on 21 | - type: section 22 | - entity: input_boolean.cfe_off 23 | debug_cardmod: true 24 | style: | 25 | :host { 26 | --paper-item-icon-color: {% if is_state(config.entity, 'on') %} var(--paper-item-active-color) {% else %} var(--paper-item-color) {% endif %}; 27 | } 28 | - input_number.cfe_off 29 | -------------------------------------------------------------------------------- /lovelace/dev_card.yaml: -------------------------------------------------------------------------------- 1 | type: entities 2 | title: Dev 3 | show_header_toggle: false 4 | entities: 5 | - type: custom:hui-horizontal-stack-card 6 | cards: 7 | - type: entity-button 8 | entity: sensor.time 9 | name: Reload config 10 | icon: mdi:refresh 11 | tap_action: 12 | action: call-service 13 | service: shell_command.lovelace_gen 14 | - type: entity-button 15 | entity: sensor.time 16 | name: States 17 | icon: mdi:tune 18 | tap_action: 19 | action: navigate 20 | navigation_path: /states 21 | -------------------------------------------------------------------------------- /lovelace/floorplan.yaml: -------------------------------------------------------------------------------- 1 | # lovelace_gen 2 | type: picture-elements 3 | image: !file /local/images/Bottenvåning.png 4 | elements: 5 | 6 | {% macro lamp(entity, x, y) -%} 7 | {% if not no_light %} 8 | - entity: {{entity}} 9 | style: { left: {{x}}%, top: {{y}}% } 10 | type: state-icon 11 | tap_action: { action: toggle } 12 | {% endif %} 13 | {%- endmacro %} 14 | {% macro dimlamp(entity, x, y) -%} 15 | {% if not no_light %} 16 | - entity: {{entity}} 17 | style: { left: {{x}}%, top: {{y}}% } 18 | type: state-icon 19 | tap_action: { action: toggle } 20 | hold_action: { action: more-info } 21 | {% endif %} 22 | {%- endmacro %} 23 | {% macro icon(entity, x, y) -%} 24 | {% if not no_other %} 25 | - entity: {{entity}} 26 | style: { left: {{x}}%, top: {{y}}% } 27 | type: state-icon 28 | {% endif %} 29 | {%- endmacro %} 30 | {% macro badge(entity, x, y) -%} 31 | {% if not no_other %} 32 | - entity: {{entity}} 33 | style: { left: {{x}}%, top: {{y}}% } 34 | type: state-badge 35 | {% endif %} 36 | {%- endmacro %} 37 | 38 | # Kontoret 39 | {{ dimlamp('light.taklampa_kontoret', 82, 68) }} 40 | {{ lamp('light.kontor_fonster', 83, 87) }} 41 | {{ dimlamp('light.skrivbordslampa_1', 88, 58) }} 42 | {{ badge('sensor.kontoret_temperature', 82, 80) }} 43 | 44 | # Lilla hallen 45 | {{ dimlamp('light.lilla_hallen', 72, 53) }} 46 | 47 | # Stora hallen 48 | {{ dimlamp('light.stora_hallen', 43, 70) }} 49 | {{ lamp('light.trappbelysning', 49, 61) }} 50 | {{ icon('binary_sensor.hall_ytterdorr', 43, 87) }} 51 | {{ badge('sensor.temperatursensor_atelje_1_0', 55, 54) }} 52 | 53 | # Köket 54 | {{ dimlamp('light.taklampa_koket', 32, 76) }} 55 | {{ lamp('light.koksfonster', 22, 87) }} 56 | {{ lamp('light.koksfonster', 28, 87) }} 57 | {{ icon('switch.kaffebryggare', 16, 84) }} 58 | 59 | # Vardagsrummet 60 | {{ dimlamp('light.vardagsrum', 60, 30) }} 61 | {{ dimlamp('light.takflakt', 43, 30) }} 62 | {{ lamp('light.bokhylla', 55, 45) }} 63 | {{ lamp('light.deko_vardagsrum', 49, 15) }} 64 | {{ badge('sensor.vardagsrum_0', 40, 36) }} 65 | {{ icon('binary_sensor.vardagsrum_ytterdorr', 38, 12) }} 66 | 67 | # Sovrum 68 | {{ dimlamp('light.sovrum', 81, 30) }} 69 | {{ icon('binary_sensor.sovrum_ytterdorr', 78, 12) }} 70 | 71 | # Tvättstuga 72 | {{ lamp('light.skotbord', 21, 15) }} 73 | {{ icon('binary_sensor.tvattstuga_ytterdorr', 13, 24) }} 74 | 75 | # # Badrum 76 | {{ badge('sensor.badrum_temp', 27, 30) }} 77 | {{ badge('sensor.badrum_humidity', 27, 40) }} 78 | {{ icon('switch.ventilation', 27, 20) }} 79 | 80 | # Utomhus 81 | {{ badge('sensor.utomhus_temp', 5, 10) }} 82 | {{ dimlamp('light.baksida', 35, 5) }} 83 | {{ dimlamp('light.mellangang', 10, 30) }} 84 | {{ dimlamp('light.uppfart', 5, 94) }} 85 | {{ dimlamp('light.entre', 44, 94) }} 86 | {{ dimlamp('light.fasad_1', 16, 94) }} 87 | {{ dimlamp('light.fasad_2', 60, 94) }} 88 | {{ dimlamp('light.fasad_3', 75, 94) }} 89 | {{ dimlamp('light.fasad_4', 88, 94) }} 90 | {{ dimlamp('light.fasad_5', 92, 70) }} 91 | {{ dimlamp('light.fasad_6', 97, 70) }} 92 | {{ dimlamp('light.fasad_7', 92, 30) }} 93 | {{ dimlamp('light.fasad_8', 97, 30) }} 94 | {{ badge('sensor.elmatare_0', 5, 55) }} 95 | -------------------------------------------------------------------------------- /lovelace/functions/car.yaml: -------------------------------------------------------------------------------- 1 | # lovelace_gen 2 | type: picture-elements 3 | entity: sensor.{{car}}_bt 4 | image: /local/images/{{car}}_clr.jpg 5 | elements: 6 | - type: image 7 | image: /local/images/{{car}}_clr.jpg 8 | entity: sensor.{{car}}_bt 9 | tap_action: 10 | action: call-service 11 | service: homeassistant.toggle 12 | service_data: 13 | entity_id: switch.varme_{{car}} 14 | style: 15 | left: 50% 16 | top: 50% 17 | width: 100% 18 | height: 100% 19 | - type: state-icon 20 | entity: switch.varme_{{car}} 21 | style: 22 | bottom: 0 23 | left: 0 24 | transform: translate(0,0) 25 | background: rgba(0,0,0,0.5) 26 | width: 100px 27 | icon: mdi:fan 28 | tap_action: 29 | action: toggle 30 | - type: state-label 31 | entity: sensor.varme_{{car}}_power 32 | graph: line 33 | style: 34 | bottom: 0 35 | left: 30px 36 | transform: translate(0,0) 37 | --primary-text-color: white 38 | color: white 39 | -------------------------------------------------------------------------------- /lovelace/functions/popup.yaml: -------------------------------------------------------------------------------- 1 | # lovelace_gen 2 | action: call-service 3 | service: browser_mod.command 4 | service_data: 5 | command: popup 6 | deviceID: 7 | - this 8 | title: {{ title }} 9 | card: !include {{ card }} 10 | large: {{ large | default("false") }} 11 | -------------------------------------------------------------------------------- /lovelace/functions/presence.yaml: -------------------------------------------------------------------------------- 1 | # lovelace_gen 2 | type: picture-entity 3 | entity: device_tracker.{{person}}_presence 4 | image: /local/images/{{person}}_bw.png 5 | show_name: false 6 | show_state: false 7 | state_image: 8 | "home": /local/images/{{person}}_clr.png 9 | "just_arrived": /local/images/{{person}}_clr.png 10 | tap_action: 11 | action: call-service 12 | service: browser_mod.command 13 | service_data: 14 | command: popup 15 | deviceID: [this] 16 | title: {{ person|capitalize }} 17 | large: true 18 | card: 19 | type: vertical-stack 20 | cards: 21 | - type: entities 22 | entities: &entities 23 | {% if person == "thomas" %} 24 | - device_tracker.thomas_iphone_beta 25 | - device_tracker.thomas_iphone_2 26 | - sensor.thomas_iphone_bt 27 | - sensor.thomas_iphone_bt2 28 | {% elif person == "anneli" %} 29 | - device_tracker.anneli_lovn 30 | - device_tracker.anneli_loven 31 | - sensor.anneli_iphone_bt 32 | - sensor.anneli_iphone_bt2 33 | - sensor.anneli_klocka_bt 34 | - sensor.anneli_klocka_bt2 35 | {% endif %} 36 | - type: history-graph 37 | entities: 38 | - device_tracker.{{person}}_presence 39 | - type: history-graph 40 | entities: *entities 41 | -------------------------------------------------------------------------------- /lovelace/main.yaml: -------------------------------------------------------------------------------- 1 | title: Åvägen 2 | resources: !include resources.yaml 3 | background: var(--lovelace-background) 4 | popup_cards: 5 | light.taklampor: 6 | title: Taklampor 7 | card: 8 | type: entities 9 | entities: 10 | - light.lilla_hallen 11 | - light.sovrum 12 | - light.stora_hallen 13 | - light.takflakt 14 | - light.taklampa_koket 15 | - light.taklampa_kontoret 16 | - light.vardagsrum 17 | light.takflakt: 18 | title: Takfläkt 19 | card: 20 | type: entities 21 | entities: 22 | - type: custom:slider-entity-row 23 | entity: light.takflakt 24 | name: All 25 | - type: section 26 | - type: custom:slider-entity-row 27 | entity: light.takflakt_1 28 | name: 1 29 | - type: custom:slider-entity-row 30 | entity: light.takflakt_2 31 | name: 2 32 | - type: custom:slider-entity-row 33 | entity: light.takflakt_3 34 | name: 3 35 | - type: custom:slider-entity-row 36 | entity: light.takflakt_4 37 | name: 4 38 | views: 39 | - !include views/dashboard_view.yaml 40 | - !include views/diverse_view.yaml 41 | - !include views/security_view.yaml 42 | - title: Floorplan 43 | panel: true 44 | icon: mdi:floor-plan 45 | cards: 46 | - type: vertical-stack 47 | cards: 48 | - !include floorplan.yaml 49 | - !include dev_card.yaml 50 | - !include views/dev_view.yaml 51 | -------------------------------------------------------------------------------- /lovelace/popups/doors.yaml: -------------------------------------------------------------------------------- 1 | type: vertical-stack 2 | cards: 3 | - type: entities 4 | entities: &door_entities 5 | - binary_sensor.hall_ytterdorr 6 | - binary_sensor.tvattstuga_ytterdorr 7 | - binary_sensor.vardagsrum_ytterdorr 8 | - binary_sensor.sovrum_ytterdorr 9 | - type: history-graph 10 | entities: *door_entities 11 | -------------------------------------------------------------------------------- /lovelace/popups/vacuum.yaml: -------------------------------------------------------------------------------- 1 | type: vertical-stack 2 | cards: 3 | - type: custom:auto-entities 4 | card: { type: entities } 5 | filter: 6 | include: 7 | - entity_id: switch.vacuum_* 8 | - type: horizontal-stack 9 | cards: 10 | - type: entity-button 11 | entity: sun.sun 12 | icon: mdi:broom 13 | name: Städa 14 | tap_action: 15 | action: call-service 16 | service: python_script.event 17 | service_data: 18 | event: VACUUM_ZONE 19 | - type: entity-button 20 | entity: sun.sun 21 | icon: mdi:broom 22 | name: Städa Allt 23 | tap_action: 24 | action: call-service 25 | service: python_script.event 26 | service_data: 27 | event: VACUUM_ALL 28 | - type: entity-button 29 | entity: sun.sun 30 | icon: mdi:wrench 31 | name: Töm 32 | tap_action: 33 | action: call-service 34 | service: python_script.event 35 | service_data: 36 | event: VACUUM_SERVICE 37 | - type: entity-button 38 | entity: sun.sun 39 | icon: mdi:sleep 40 | name: Vila 41 | tap_action: 42 | action: call-service 43 | service: python_script.event 44 | service_data: 45 | event: VACUUM_HOME 46 | 47 | - type: custom:more-info-card 48 | entity: vacuum.xiaomi_vacuum_cleaner 49 | -------------------------------------------------------------------------------- /lovelace/power_card.yaml: -------------------------------------------------------------------------------- 1 | type: entities 2 | show_header_toggle: false 3 | title: Elförbrukning 4 | entities: 5 | - type: section 6 | label: Senaste dygnet 7 | - type: custom:hui-history-graph-card 8 | hours_to_show: 24 9 | entities: 10 | - sensor.filtered_power 11 | - type: section 12 | label: Senaste veckan 13 | - type: custom:hui-history-graph-card 14 | hours_to_show: 168 15 | entities: 16 | - sensor.filtered_power 17 | - sensor.power_meter 18 | -------------------------------------------------------------------------------- /lovelace/resources.yaml: -------------------------------------------------------------------------------- 1 | # lovelace_gen 2 | 3 | - url: !file /local/lovelace-card-tools/card-tools.js 4 | type: js 5 | - url: !file /local/lovelace-auto-entities/auto-entities.js 6 | type: js 7 | - url: !file /local/lovelace-layout-card/layout-card.js 8 | type: js 9 | 10 | # Specially modified by helto 11 | # https://github.com/helto4real/hassio/tree/master/www/custom_cards 12 | - url: !file /local/weather-card.js 13 | type: module 14 | 15 | - url: !file /local/more-info-card.js 16 | type: js 17 | 18 | # Plugins installed through HACS 19 | - url: !file /local/community/lovelace-card-mod/card-mod.js 20 | type: module 21 | - url: !file /local/community/lovelace-fold-entity-row/fold-entity-row.js 22 | type: module 23 | -------------------------------------------------------------------------------- /lovelace/temperatures_card.yaml: -------------------------------------------------------------------------------- 1 | type: entities 2 | show_header_toggle: false 3 | title: Temperaturer 4 | entities: 5 | - type: section 6 | label: Senaste dygnet 7 | - type: custom:hui-history-graph-card 8 | hours_to_show: 24 9 | entities: 10 | - sensor.vardagsrum_0 11 | - sensor.kontoret_temperature 12 | - sensor.utomhus_temp 13 | - sensor.badrum_temp 14 | - type: section 15 | label: Senaste veckan 16 | - type: custom:hui-history-graph-card 17 | hours_to_show: 168 18 | entities: 19 | - sensor.vardagsrum_0 20 | - sensor.utomhus_temp 21 | # - sensor.elmatare_0 22 | - sensor.badrum_temp 23 | -------------------------------------------------------------------------------- /lovelace/timeofday_card.yaml: -------------------------------------------------------------------------------- 1 | type: entities 2 | title: Tid 3 | show_header_toggle: false 4 | entities: 5 | - entity: sensor.time 6 | name: Tid 7 | - entity: sensor.date 8 | name: Datum 9 | - entity: sensor.week 10 | name: Vecka 11 | - type: section 12 | label: Tid på dygnet 13 | - entity: input_select.tod 14 | name: ' ' 15 | - entity: input_datetime.tod_morning 16 | name: Morgon 17 | - entity: input_datetime.tod_day 18 | name: Dag 19 | - entity: input_datetime.tod_evening 20 | name: Kväll 21 | - entity: input_datetime.tod_night 22 | name: Natt 23 | 24 | - type: section 25 | - entity: switch.tod_dark 26 | name: Mörkt 27 | - entity: input_number.tod_sunrise_offset 28 | name: Soluppgång +/- (min) 29 | - entity: input_number.tod_sunset_offset 30 | name: Solnedgång +/- (min) 31 | -------------------------------------------------------------------------------- /lovelace/views/dashboard_view.yaml: -------------------------------------------------------------------------------- 1 | title: Dashboard 2 | path: dashboard 3 | icon: mdi:home-assistant 4 | panel: true 5 | cards: 6 | - type: custom:layout-card 7 | layout: vertical 8 | column_width: 350 9 | max_width: [200px,100%] 10 | rtl: true 11 | cards: 12 | - type: custom:layout-card 13 | column_width: 200 14 | max_width: 300 15 | cards: 16 | - !include 17 | - /config/lovelace/functions/car.yaml 18 | - car: peugeot 19 | - !include 20 | - /config/lovelace/functions/car.yaml 21 | - car: golf 22 | - break 23 | - type: custom:layout-card 24 | column_width: 350 25 | max_width: 960 26 | layout: vertical 27 | cards: 28 | - type: markdown 29 | style: | 30 | ha-card { 31 | padding-top: 24px; 32 | } 33 | h1 { 34 | text-align: center; 35 | font-size: 6em; 36 | margin-bottom: 0; 37 | } 38 | h2 { 39 | text-align: center; 40 | } 41 | content: | 42 | # {{ states("sensor.time") }} 43 | ## {{ states("sensor.date") }} V.{{ states("sensor.week") }} 44 | - type: custom:weather-card 45 | entity: weather.smhi_home 46 | icons: /local/weather_icons/ 47 | - break 48 | - type: horizontal-stack 49 | cards: 50 | - !include 51 | - /config/lovelace/functions/presence.yaml 52 | - person: thomas 53 | - !include 54 | - /config/lovelace/functions/presence.yaml 55 | - person: anneli 56 | - type: horizontal-stack 57 | cards: 58 | - type: entity-button 59 | entity: light.ytterbelysning 60 | tap_action: 61 | action: toggle 62 | - type: entity-button 63 | entity: light.fasadbelysning 64 | tap_action: 65 | action: toggle 66 | - type: entity-button 67 | entity: light.taklampor 68 | name: Lampor inne 69 | - type: horizontal-stack 70 | cards: 71 | - type: entity-button 72 | entity: vacuum.xiaomi_vacuum_cleaner 73 | name: Dammsugare 74 | tap_action: !include 75 | - /config/lovelace/functions/popup.yaml 76 | - title: Dammsugare 77 | card: /config/lovelace/popups/vacuum.yaml 78 | - type: entity-button 79 | entity: binary_sensor.sopor 80 | - type: entity-button 81 | entity: binary_sensor.door_open 82 | name: Öppen dörr 83 | tap_action: !include 84 | - /config/lovelace/functions/popup.yaml 85 | - title: Öppna dörrar 86 | card: /config/lovelace/popups/doors.yaml 87 | -------------------------------------------------------------------------------- /lovelace/views/dev_view.yaml: -------------------------------------------------------------------------------- 1 | title: Dev 2 | path: dev 3 | icon: mdi:settings 4 | panel: true 5 | cards: 6 | - type: custom:layout-card 7 | rebuild: 1000 8 | cards: 9 | - !include ../timeofday_card.yaml 10 | - type: custom:browser-player 11 | - !include ../local_services.hidden.yaml 12 | - type: custom:auto-entities 13 | card: 14 | type: entities 15 | title: Okänd status 16 | filter: 17 | include: 18 | - state: "unavailable" 19 | exclude: 20 | - attributes: 21 | type: browser_mod 22 | - type: custom:auto-entities 23 | card: 24 | type: entities 25 | title: Tända lampor 26 | filter: 27 | include: 28 | - domain: light 29 | state: "on" 30 | exclude: 31 | - attributes: 32 | hidden: true 33 | - type: custom:auto-entities 34 | card: 35 | type: entities 36 | title: Släckta lampor 37 | filter: 38 | include: 39 | - domain: light 40 | state: "off" 41 | exclude: 42 | - attributes: 43 | hidden: true 44 | - type: custom:auto-entities 45 | card: 46 | type: entities 47 | title: Lampor/Brytare 48 | filter: 49 | include: 50 | - domain: light 51 | options: 52 | secondary_info: entity-id 53 | - domain: switch 54 | options: 55 | secondary_info: entity-id 56 | exclude: 57 | - attributes: 58 | type: browser_mod 59 | - type: custom:auto-entities 60 | card: 61 | type: entities 62 | title: Batterier 63 | filter: 64 | include: 65 | - attributes: 66 | device_class: "battery" 67 | options: 68 | secondary_info: entity-id 69 | style: | 70 | {% if state_attr(config.entity, 'unit_of_measurement') == "%" %} 71 | :host { 72 | --paper-item-icon-color: 73 | {% if states(config.entity)|float < 25 %} 74 | red 75 | {% elif states(config.entity)|float < 40 %} 76 | orange 77 | {% else %} 78 | var(--text-light-color) 79 | {% endif %}; 80 | } 81 | {% endif %} 82 | - type: custom:auto-entities 83 | card: 84 | type: entities 85 | title: Övriga Sensorer 86 | filter: 87 | include: 88 | - domain: /.*sensor/ 89 | options: 90 | secondary_info: entity-id 91 | exclude: 92 | - attributes: 93 | device_class: "battery" 94 | - type: custom:auto-entities 95 | card: 96 | type: entities 97 | title: Automationer 98 | filter: 99 | include: 100 | - domain: automation 101 | options: 102 | secondary_info: entity-id 103 | - type: custom:more-info-card 104 | entity: vacuum.xiaomi_vacuum_cleaner 105 | title: Vacuum cleaner 106 | 107 | - type: custom:auto-entities 108 | card: 109 | type: entities 110 | title: Swarm 111 | filter: 112 | include: 113 | - entity_id: sensor.swarm_* 114 | -------------------------------------------------------------------------------- /lovelace/views/diverse_view.yaml: -------------------------------------------------------------------------------- 1 | title: Diverse 2 | icon: mdi:home-assistant 3 | cards: 4 | - !include 5 | - /config/lovelace/floorplan.yaml 6 | - {"no_other":"true"} 7 | - !include 8 | - /config/lovelace/floorplan.yaml 9 | - {"no_light":"true"} 10 | - !include /config/lovelace/coffee_card.yaml 11 | - !include /config/lovelace/temperatures_card.yaml 12 | - !include /config/lovelace/power_card.yaml 13 | -------------------------------------------------------------------------------- /lovelace/views/security_view.yaml: -------------------------------------------------------------------------------- 1 | title: Security 2 | icon: mdi:shield-home 3 | cards: 4 | - !include /config/lovelace/cameras/barnrum.yaml 5 | - !include /config/lovelace/cameras/kontor.yaml 6 | - !include /config/lovelace/cameras/vardagsrum.yaml 7 | - !include /config/lovelace/cameras/hallen.yaml 8 | - type: entities 9 | entities: 10 | - type: custom:hui-glance-card 11 | entities: 12 | - binary_sensor.hall_ytterdorr 13 | - binary_sensor.vardagsrum_ytterdorr 14 | - binary_sensor.sovrum_ytterdorr 15 | - binary_sensor.tvattstuga_ytterdorr 16 | - binary_sensor.koket_vatten 17 | - sensor.mold_indicator 18 | - sensor.tygvind_humidity 19 | -------------------------------------------------------------------------------- /packages/appd_dummies.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "cameras" 6 | hidden: true 7 | 8 | input_number.appd_dummy: 9 | <<: *common 10 | input_datetime.appd_dummy: 11 | <<: *common 12 | input_select.appd_dummy: 13 | <<: *common 14 | input_boolean.appd_dummy: 15 | <<: *common 16 | input_text.appd_dummy: 17 | <<: *common 18 | 19 | # Those things do nothing and will never be seen 20 | # Their only purpose is to make sure the services used to controll the corresponding entity types are loaded. 21 | input_number: 22 | appd_dummy: 23 | min: 0 24 | max: 10 25 | input_datetime: 26 | appd_dummy: 27 | has_date: False 28 | has_time: True 29 | input_select: 30 | appd_dummy: 31 | options: [1] 32 | input_boolean: 33 | appd_dummy: 34 | input_text: 35 | appd_dummy: 36 | name: dummy 37 | initial: dummy 38 | -------------------------------------------------------------------------------- /packages/cameras.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "cameras" 6 | 7 | camera.kontor: 8 | <<: *common 9 | camera.vardagsrum: 10 | <<: *common 11 | camera.barnrum: 12 | <<: *common 13 | host: !secret camera_barnrum_host 14 | 15 | switch.barnrum_ptz_left: 16 | <<: *common 17 | hidden: true 18 | icon: mdi:chevron-left 19 | switch.barnrum_ptz_right: 20 | <<: *common 21 | hidden: true 22 | icon: mdi:chevron-right 23 | switch.barnrum_ptz_up: 24 | <<: *common 25 | hidden: true 26 | icon: mdi:chevron-up 27 | switch.barnrum_ptz_down: 28 | <<: *common 29 | hidden: true 30 | icon: mdi:chevron-down 31 | 32 | script.camera_ptz: 33 | <<: *common 34 | 35 | ffmpeg: 36 | 37 | camera: 38 | - name: Kontor 39 | platform: mjpeg 40 | mjpeg_url: !secret camera_kontor_mjpeg 41 | still_image_url: !secret camera_kontor_still 42 | username: !secret camera_kontor_username 43 | password: !secret camera_kontor_password 44 | 45 | - name: Vardagsrum 46 | platform: mjpeg 47 | mjpeg_url: !secret camera_vardagsrum_mjpeg 48 | still_image_url: !secret camera_vardagsrum_still 49 | username: !secret camera_vardagsrum_username 50 | password: !secret camera_vardagsrum_password 51 | 52 | - name: Barnrum 53 | platform: onvif 54 | host: !secret camera_barnrum_host 55 | port: !secret camera_barnrum_port 56 | username: !secret camera_barnrum_username 57 | password: !secret camera_barnrum_password 58 | 59 | - name: Hallen 60 | platform: generic 61 | still_image_url: !secret camera_hallen_url 62 | 63 | # Script to step an onvif camera in the given direction. 64 | # Parameters: 65 | # entity_id - camera entity 66 | # dir - direction (left, right, up, down) 67 | script: 68 | camera_ptz: 69 | sequence: 70 | - service: camera.onvif_ptz 71 | data_template: 72 | entity_id: "{{ entity_id }}" 73 | pan: > 74 | {{ {"left":"LEFT", "right":"RIGHT", "up":"NONE", "down":"NONE"}[dir] }} 75 | tilt: > 76 | {{ {"left":"NONE", "right":"NONE", "up":"UP", "down":"DOWN"}[dir] }} 77 | - delay: 78 | milliseconds: 100 79 | - service: camera.onvif_ptz 80 | data_template: 81 | entity_id: "{{ entity_id }}" 82 | 83 | rest_command: 84 | camera_position: 85 | url: http://{{ state_attr(entity_id, 'host')}}/web/cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number={{ position }} 86 | username: !secret camera_barnrum_username 87 | password: !secret camera_barnrum_password 88 | -------------------------------------------------------------------------------- /packages/coffeemaker.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | coffee_switch: &cfe_switch switch.kaffebryggare 5 | common: &common 6 | package: coffeemaker 7 | 8 | input_datetime.cfe_on: 9 | <<: *common 10 | friendly_name: Tid 11 | input_number.cfe_off: 12 | <<: *common 13 | friendly_name: Tid (min) 14 | input_boolean.cfe_on: 15 | <<: *common 16 | friendly_name: Morgonkaffe 17 | input_boolean.cfe_off: 18 | <<: *common 19 | friendly_name: Slå av automatiskt 20 | script.cfe_off: 21 | <<: *common 22 | automation.coffee_timer_on: 23 | <<: *common 24 | automation.coffee_auto_off: 25 | <<: *common 26 | 27 | input_datetime: 28 | cfe_on: 29 | has_date: false 30 | has_time: true 31 | initial: "06:00" 32 | input_number: 33 | cfe_off: 34 | initial: 15 35 | min: 5 36 | max: 60 37 | step: 5 38 | input_boolean: 39 | cfe_on: 40 | initial: off 41 | cfe_off: 42 | initial: on 43 | 44 | script: 45 | cfe_off: 46 | sequence: 47 | - delay: 00:{{ states('input_number.cfe_off') | int}}:00 48 | - service: homeassistant.turn_off 49 | data: 50 | entity_id: *cfe_switch 51 | - service: script.info_message 52 | data_template: 53 | message: "Slår av kaffebryggaren ({{ states('input_number.cfe_off') | int }} minuter)" 54 | 55 | automation: 56 | - alias: Coffee - Timer On 57 | initial_state: True 58 | trigger: 59 | - platform: template 60 | value_template: > 61 | {{ states('sensor.time') == states('input_datetime.cfe_on')[:5] }} 62 | condition: 63 | condition: state 64 | entity_id: input_boolean.cfe_on 65 | state: 'on' 66 | action: 67 | - service: homeassistant.turn_off 68 | data: 69 | entity_id: input_boolean.cfe_on 70 | - service: homeassistant.turn_on 71 | data: 72 | entity_id: *cfe_switch 73 | - service: script.info_message 74 | data: {message: "Slår på kaffebryggaren (timer)"} 75 | 76 | - alias: Coffee - Auto Off 77 | initial_state: True 78 | trigger: 79 | - platform: state 80 | entity_id: *cfe_switch 81 | to: 'on' 82 | condition: 83 | condition: state 84 | entity_id: input_boolean.cfe_off 85 | state: 'on' 86 | action: 87 | - service: script.cfe_off 88 | 89 | -------------------------------------------------------------------------------- /packages/computers.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "computers" 6 | binary_sensor.arrakis: 7 | <<: *common 8 | automation.desk_lamp_follows_computer: 9 | <<: *common 10 | 11 | # Switched off/on when computer enters/leaves screen sleep 12 | binary_sensor: 13 | - platform: mqtt 14 | state_topic: "computer/arrakis/on" 15 | name: Arrakis 16 | 17 | 18 | sensor: 19 | - platform: glances 20 | host: glances 21 | version: 3 22 | name: swarm 23 | resources: 24 | - disk_use_percent 25 | - memory_use_percent 26 | - processor_load 27 | - cpu_use_percent 28 | - cpu_temp 29 | - docker_active 30 | - docker_cpu_use 31 | - docker_memory_use 32 | 33 | 34 | automation: 35 | - alias: Desk lamp follows computer 36 | initial_state: True 37 | trigger: 38 | - platform: state 39 | entity_id: binary_sensor.arrakis 40 | to: "on" 41 | for: 42 | hours: 0 43 | minutes: 0 44 | seconds: 1 45 | - platform: state 46 | entity_id: binary_sensor.arrakis 47 | to: "off" 48 | for: 49 | hours: 0 50 | minutes: 0 51 | seconds: 1 52 | action: 53 | service_template: >- 54 | {% if trigger.to_state.state == 'on' %} 55 | light.turn_on 56 | {% else %} 57 | light.turn_off 58 | {% endif %} 59 | data: 60 | entity_id: light.skrivbordslampa_1 61 | -------------------------------------------------------------------------------- /packages/environment.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "environment" 6 | 7 | sensor.time: 8 | <<: *common 9 | sensor.date: 10 | <<: *common 11 | 12 | sensor: 13 | - platform: time_date 14 | display_options: 15 | - time 16 | - date 17 | - platform: rest 18 | name: week 19 | resource: https://vecka.nu 20 | headers: 21 | Accept: application/json 22 | value_template: '{{ value_json.week | int }}' 23 | -------------------------------------------------------------------------------- /packages/hardware/mysensors.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "hardware/mysensors" 6 | 7 | customize_glob: 8 | sensor.vardagsrum_0: 9 | <<: *common 10 | friendly_name: Vardagsrummet 11 | sensor.elmatare_*: 12 | <<: *common 13 | sensor.elmatare_20*: 14 | hidden: true 15 | sensor.elmatare_0: 16 | friendly_name: Utomhus 17 | sensor.power_meter: 18 | <<: *common 19 | friendly_name: Elförbrukning 20 | 21 | light.skrivbordslampa_1: 22 | <<: *common 23 | friendly_name: Skrivbordslampa 24 | icon: mdi:desk-lamp 25 | 26 | # logger: 27 | # logs: 28 | # mysensors: debug 29 | # homeassistant.components.mysensors: debug 30 | 31 | # mysensors: 32 | # gateways: 33 | # - device: mysensors 34 | # persistence_file: "/config/mysensors.json" 35 | # tcp_port: 2000 36 | # nodes: 37 | # 2: { name: Vardagsrum } 38 | # 24: { name: Skrivbordslampa } 39 | # 26: { name: Elmätare } 40 | # version: "2.0" 41 | 42 | # The power meter has two values for one sensor, and which one is logged is a bit random. Therefore replace it with a custom template sensor which always gets the correct value 43 | sensor: 44 | - platform: template 45 | sensors: 46 | power_meter: 47 | value_template: "{{ states.sensor.elmatare_20.attributes.V_WATT }}" 48 | unit_of_measurement: 'W' 49 | - platform: filter 50 | name: "Filtered power" 51 | entity_id: sensor.power_meter 52 | filters: 53 | - filter: lowpass 54 | -------------------------------------------------------------------------------- /packages/hardware/rflink.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "hardware/rflink" 6 | 7 | # light.ute_framsidan: 8 | # <<: *common 9 | light.trappbelysning: 10 | <<: *common 11 | light.kontor_fonster: 12 | <<: *common 13 | light.deko_vardagsrum: 14 | <<: *common 15 | light.skotbord: 16 | <<: *common 17 | light.bokhylla: 18 | <<: *common 19 | # switch.motorvarmare: 20 | # <<: *common 21 | light.koksfonster: 22 | <<: *common 23 | 24 | rflink: 25 | host: rflink 26 | port: 2000 27 | 28 | light: 29 | - platform: rflink 30 | automatic_add: false 31 | devices: 32 | # newkaku_0008f252_1: 33 | # name: Ute Framsidan 34 | ab400d_000042_2: 35 | name: Trappbelysning 36 | unitec_e1b8_01: 37 | name: Kontor Fönster 38 | unitec_e1b8_02: 39 | name: Deko Vardagsrum 40 | newkaku_01b029f6_1: 41 | name: Skötbord 42 | newkaku_01b029f6_3: 43 | name: Bokhylla 44 | newkaku_01b029f7_3: 45 | name: Köksfönster 46 | 47 | # switch: 48 | # - platform: rflink 49 | # devices: 50 | # newkaku_018cf7f6_10: 51 | # name: Motorvärmare 52 | 53 | -------------------------------------------------------------------------------- /packages/hvac.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | hvac_switch: &hvac_switch switch.ventilation 5 | hvac_sensor: &hvac_sensor sensor.badrum_humidity 6 | hvac_ref: &hvac_ref sensor.utomhus_humidity 7 | common: &common 8 | package: hvac 9 | sensor.vpump_dir0: 10 | <<: *common 11 | sensor.vpump_dir1: 12 | <<: *common 13 | sensor.vpump_dir2: 14 | <<: *common 15 | 16 | automation: 17 | - alias: HVAC - Timer off 18 | initial_state: False 19 | trigger: 20 | - platform: state 21 | entity_id: *hvac_switch 22 | to: 'on' 23 | for: 24 | hours: 1 25 | action: 26 | - service: homeassistant.turn_off 27 | data: 28 | entity_id: *hvac_switch 29 | - service: script.info_message 30 | data: { message: "Stänger av takfläkt"} 31 | 32 | - alias: HVAC - Auto on 33 | initial_state: True 34 | trigger: 35 | - platform: template 36 | value_template: "{{ states('sensor.badrum_humidity')|int > (states('sensor.vardagsrum_humidity')|int + 10) }}" 37 | action: 38 | - service: homeassistant.turn_on 39 | data: 40 | entity_id: *hvac_switch 41 | - service: script.info_message 42 | data: { message: "Fuktigt - takfläkt på" } 43 | - alias: HVAC - Auto off 44 | initial_state: True 45 | trigger: 46 | - platform: template 47 | value_template: "{{ states('sensor.badrum_humidity')|int < (states('sensor.vardagsrum_humidity')|int + 10) }}" 48 | for: 49 | minutes: 15 50 | action: 51 | - service: homeassistant.turn_off 52 | data: 53 | entity_id: *hvac_switch 54 | - service: script.info_message 55 | data: { message: "Torrt - takfläkt av" } 56 | 57 | sensor: 58 | - platform: template 59 | sensors: 60 | vpump_dir0: 61 | value_template: "{{ state_attr('binary_sensor.vibration_sensor', 'orientation')[0]|float }}" 62 | unit_of_measurement: "r.u." 63 | vpump_dir1: 64 | value_template: "{{ state_attr('binary_sensor.vibration_sensor', 'orientation')[1]|float }}" 65 | unit_of_measurement: "r.u." 66 | vpump_dir2: 67 | value_template: "{{ state_attr('binary_sensor.vibration_sensor', 'orientation')[2]|float }}" 68 | unit_of_measurement: "r.u." 69 | -------------------------------------------------------------------------------- /packages/ios.yaml: -------------------------------------------------------------------------------- 1 | ios: 2 | push: 3 | categories: 4 | - name: YesNo 5 | identifier: yesno 6 | actions: 7 | - identifier: 'YES' 8 | title: 'Ja' 9 | - identifier: 'NO' 10 | title: 'Nej' 11 | - name: YesLater 12 | identifier: yeslater 13 | actions: 14 | - identifier: 'YES' 15 | title: 'OK' 16 | - identifier: 'LATER' 17 | title: 'Vänta' 18 | - name: YesLaterNo 19 | identifier: yeslaterno 20 | actions: 21 | - identifier: 'YES' 22 | title: 'Ja' 23 | - identifier: 'LATER' 24 | title: 'Vänta' 25 | - identifier: 'NO' 26 | title: 'Nej' 27 | notify: 28 | - platform: ios 29 | -------------------------------------------------------------------------------- /packages/lights.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "lights" 6 | grouped: &grouped 7 | hidden: true 8 | 9 | light.sovrum: 10 | <<: *common 11 | light.sovrum1: 12 | <<: *grouped 13 | light.sovrum2: 14 | <<: *grouped 15 | light.sovrum3: 16 | <<: *grouped 17 | light.takflakt: 18 | <<: *common 19 | light.fasadbelysning: 20 | <<: *common 21 | light.ytterbelysning: 22 | <<: *common 23 | light.taklampor: 24 | <<: *common 25 | 26 | customize_glob: 27 | light.takflakt_*: 28 | <<: *grouped 29 | light.fasad_*: 30 | <<: *grouped 31 | 32 | 33 | light: 34 | - platform: group 35 | name: Sovrum 36 | entities: 37 | - light.sovrum1 38 | - light.sovrum2 39 | - light.sovrum3 40 | 41 | - platform: group 42 | name: Takfläkt 43 | entities: 44 | - light.takflakt_1 45 | - light.takflakt_2 46 | - light.takflakt_3 47 | - light.takflakt_4 48 | 49 | - platform: group 50 | name: Fasadbelysning 51 | entities: 52 | - light.fasad_1 53 | - light.fasad_2 54 | - light.fasad_3 55 | - light.fasad_4 56 | - light.fasad_5 57 | - light.fasad_6 58 | - light.fasad_7 59 | - light.fasad_8 60 | 61 | - platform: group 62 | name: Ytterbelysning 63 | entities: 64 | - light.entre 65 | - light.baksida 66 | - light.mellangang 67 | - light.uppfart 68 | 69 | - platform: group 70 | name: Taklampor 71 | entities: 72 | - light.lilla_hallen 73 | - light.sovrum 74 | - light.stora_hallen 75 | - light.takflakt 76 | - light.taklampa_koket 77 | - light.taklampa_kontoret 78 | - light.vardagsrum 79 | 80 | circadian_lighting: 81 | 82 | switch: 83 | - platform: circadian_lighting 84 | lights_ct: 85 | - light.taklampa_kontoret 86 | - light.lilla_hallen 87 | - light.stora_hallen 88 | - light.takflakt_1 89 | - light.takflakt_2 90 | - light.takflakt_3 91 | - light.takflakt_4 92 | - light.vardagsrum 93 | - light.taklampa_koket 94 | -------------------------------------------------------------------------------- /packages/logging.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "logging" 6 | 7 | script.log_message: 8 | <<: *common 9 | script.info_message: 10 | <<: *common 11 | automation.log_home_assistant_starting: 12 | <<: *common 13 | 14 | script: 15 | log_message: 16 | sequence: 17 | - event: LOG_WRITE 18 | event_data_template: 19 | message: "{{ message }}" 20 | info_message: 21 | sequence: 22 | - event: LOG_WRITE 23 | event_data_template: 24 | message: "{{ message }}" 25 | channel: "info" 26 | - event: LOG_WRITE 27 | event_data_template: 28 | message: "INFO: {{ message }}" 29 | 30 | automation: 31 | alias: LOG - Home Assistant Starting 32 | initial_state: true 33 | trigger: 34 | platform: homeassistant 35 | event: start 36 | action: 37 | - service: script.info_message 38 | data: 39 | message: "Home Assistant started!" 40 | -------------------------------------------------------------------------------- /packages/lovelace.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: "computers" 6 | 7 | shell_command.lovelace_gen: 8 | <<: *common 9 | 10 | lovelace: 11 | mode: yaml 12 | 13 | # Regenerate ui_lovelace.yaml 14 | shell_command: 15 | lovelace_gen: '(python /config/www/homeassistant-lovelace-gen/lovelace-gen.py && echo "OK" || echo "FAILED") > /config/lovelace-gen.log' 16 | -------------------------------------------------------------------------------- /packages/mower.yaml.disabled: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: tcp 3 | name: mower_battery 4 | host: 192.168.0.228 5 | port: 23 6 | payload: "?B" 7 | unit_of_measurement: "mV" 8 | - platform: tcp 9 | name: mower_state 10 | host: 192.168.0.228 11 | port: 23 12 | payload: "?M" 13 | value_template: "{{ ['MOWING','LAUNCHING','DOCKING', 'CHARGING', 'LOOKING FOR BWF', 'SETUP', 'IDLE'][value|int] }}" 14 | -------------------------------------------------------------------------------- /packages/presence.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: 'security' 6 | 7 | binary_sensor.home_occupied: 8 | <<: *common 9 | automation.adm_rescan_monitor_on_restart: 10 | <<: *common 11 | 12 | binary_sensor: 13 | - platform: template 14 | sensors: 15 | home_occupied: 16 | friendly_name: Anyone home 17 | value_template: >- 18 | {{ (is_state('device_tracker.thomas_presence', 'home') or 19 | is_state('device_tracker.anneli_presence', 'home')) }} 20 | 21 | device_tracker: 22 | - platform: unifi 23 | host: unifi 24 | username: !secret unifi_username 25 | password: !secret unifi_password 26 | verify_ssl: false 27 | ssid_filter: !secret unifi_ssids 28 | 29 | automation: 30 | - alias: ADM - Rescan monitor on restart 31 | trigger: 32 | platform: homeassistant 33 | event: start 34 | action: 35 | - service: mqtt.publish 36 | data: 37 | topic: "monitor/scan/restart" 38 | -------------------------------------------------------------------------------- /packages/reminders.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: 'reminders' 6 | 7 | binary_sensor.sopor: 8 | <<: *common 9 | automation.rem_kaffet_ar_klart: 10 | <<: *common 11 | 12 | 13 | binary_sensor: 14 | - platform: template 15 | sensors: 16 | sopor: 17 | entity_id: sensor.time 18 | friendly_name: Sopor 19 | icon_template: >- 20 | {% if is_state('binary_sensor.sopor', 'on') %} 21 | mdi:delete 22 | {% else %} 23 | mdi:delete-outline 24 | {% endif %} 25 | value_template: >- 26 | {% set retval = False %} 27 | {% if (states('sensor.week') | int) % 2 == 0 %} 28 | {% if now().weekday() == 6 %} 29 | {% set retval = True %} 30 | {% endif %} 31 | {% else %} 32 | {% if now().weekday() == 0 and now().hour < 12 %} 33 | {% set retval = True %} 34 | {% endif %} 35 | {% endif %} 36 | {{retval}} 37 | 38 | automation: 39 | - alias: REM - Kaffet är klart 40 | initial_state: True 41 | trigger: 42 | platform: state 43 | entity_id: switch.kaffebryggare 44 | to: 'on' 45 | for: 46 | minutes: 5 47 | action: 48 | - service: notify.mobile_app_thomas_iphone 49 | data: 50 | title: "Fikadax!" 51 | message: "Kaffet är färdigt!" 52 | - service: script.info_message 53 | data: 54 | message: "Kaffet är färdigt!" 55 | -------------------------------------------------------------------------------- /packages/security.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | package.node_anchors: 4 | common: &common 5 | package: 'security' 6 | door: &door 7 | <<: *common 8 | device_class: door 9 | 10 | binary_sensor.door_open: 11 | <<: *common 12 | binary_sensor.hall_ytterdorr: 13 | <<: *door 14 | binary_sensor.tvattstuga_ytterdorr: 15 | <<: *door 16 | binary_sensor.vardagsrum_ytterdorr: 17 | <<: *door 18 | binary_sensor.sovrum_ytterdorr: 19 | <<: *door 20 | 21 | binary_sensor: 22 | - platform: template 23 | sensors: 24 | door_open: 25 | entity_id: 26 | - binary_sensor.hall_ytterdorr 27 | - binary_sensor.tvattstuga_ytterdorr 28 | - binary_sensor.vardagsrum_ytterdorr 29 | - binary_sensor.sovrum_ytterdorr 30 | value_template: >- 31 | {{ (is_state('binary_sensor.hall_ytterdorr', 'on') or 32 | is_state('binary_sensor.tvattstuga_ytterdorr', 'on') or 33 | is_state('binary_sensor.vardagsrum_ytterdorr', 'on') or 34 | is_state('binary_sensor.sovrum_ytterdorr', 'on')) }} 35 | device_class: door 36 | 37 | sensor: 38 | - platform: mold_indicator 39 | indoor_temp_sensor: sensor.tygvind_temp 40 | indoor_humidity_sensor: sensor.tygvind_humidity 41 | outdoor_temp_sensor: sensor.utomhus_temp 42 | calibration_factor: 3.0 43 | 44 | # 2019-06-11 22:45 45 | # Ute 14.6 46 | # Inne 18.8 47 | # Kritisk 15.0 48 | # (18.8-14.6)/(16.0-14.6) = 4.2/1.4 = 10.5 49 | 50 | automation: 51 | - alias: SEQ - Door opened 52 | trigger: 53 | platform: state 54 | entity_id: binary_sensor.door_open 55 | to: 'on' 56 | condition: 57 | - condition: state 58 | entity_id: binary_sensor.home_occupied 59 | state: 'off' 60 | action: 61 | - service: notify.mobile_app_thomas_iphone 62 | data: 63 | title: "LARM!" 64 | message: "En dörr öppnades!" 65 | - alias: SEQ - Water leak 66 | trigger: 67 | platform: state 68 | entity_id: binary_sensor.koket_vatten 69 | to: 'on' 70 | action: 71 | - service: notify.mobile_app_thomas_iphone 72 | data: 73 | title: "LARM!" 74 | message: "Blött i köksskåpet!" 75 | - alias: SEQ - Mold warning 76 | trigger: 77 | platform: numeric_state 78 | entity_id: sensor.mold_indicator 79 | above: 80 80 | for: 81 | hours: 1 82 | action: 83 | - service: notify.mobile_app_thomas_iphone 84 | data_template: 85 | title: "LARM!" 86 | message: "Hög mögelrisk på vinden! {{ states('sensor.mold_indicator') }}%" 87 | -------------------------------------------------------------------------------- /packages/tts.yaml: -------------------------------------------------------------------------------- 1 | tts: 2 | - platform: google_translate 3 | language: 'sv' 4 | # Use local address for tts in order to work with chromecasts 5 | # http://:8123 6 | base_url: !secret tts_base_url 7 | -------------------------------------------------------------------------------- /packages/vacuum.yaml: -------------------------------------------------------------------------------- 1 | vacuum: 2 | - platform: xiaomi_miio 3 | host: !secret vacuum_host 4 | token: !secret vacuum_token 5 | 6 | script: 7 | vacuum_hall: 8 | sequence: 9 | - service: vacuum.xiaomi_clean_zone 10 | data: 11 | repeats: 1 12 | zone: 13 | - [23200, 25200, 27500, 28000] 14 | vacuum_kitchen: 15 | sequence: 16 | - service: vacuum.xiaomi_clean_zone 17 | data: 18 | repeats: 1 19 | zone: 20 | - [23200, 21000, 27500, 25200] 21 | vacuum_daily: 22 | sequence: 23 | - service: vacuum.xiaomi_clean_zone 24 | data: 25 | repeats: 1 26 | zone: 27 | - [23200, 25200, 27500, 28000] 28 | - [23200, 21000, 27500, 25200] 29 | vacuum_all: 30 | sequence: 31 | - service: vacuum.start 32 | vacuum_empty: 33 | sequence: 34 | - service: vacuum.send_command 35 | data: 36 | entity_id: vacuum.xiaomi_vacuum_cleaner 37 | command: app_goto_target 38 | params: [26000, 26000] 39 | vacuum_home: 40 | sequence: 41 | - service: vacuum.return_to_base 42 | data: 43 | entity_id: vacuum.xiaomi_vacuum_cleaner 44 | -------------------------------------------------------------------------------- /python_scripts/event.py: -------------------------------------------------------------------------------- 1 | ev = data.get('event', None) 2 | ev_data = data.get('data', {}) 3 | if ev: 4 | hass.bus.fire(ev, ev_data) 5 | --------------------------------------------------------------------------------