├── .gitignore
├── .pylintrc
├── LICENSE
├── README.md
├── ad-ench
└── ench.py
├── alarmClock
├── alarmClock.py
└── alarmClock.yaml
├── alexa
├── README.md
├── alexa.yaml
├── alexa_api.py
├── custom_skill.json
├── lightState
│ ├── lightStateIntent-utterances_DE.csv
│ ├── lightStateIntent-utterances_EN.csv
│ ├── lightStateIntent.py
│ └── lightStateIntent.yaml
├── listService
│ ├── listService.py
│ └── listService.yaml
├── nextBus
│ ├── nextBusIntent.py
│ └── nextBusIntent.yaml
├── remindMeOfXWhenZone
│ ├── remindMeOfXWhenZoneIntent.py
│ └── remindMeOfXWhenZoneIntent.yaml
├── temperatureState
│ ├── temperatureStateIntent-utterances_DE.csv
│ ├── temperatureStateIntent-utterances_EN.csv
│ ├── temperatureStateIntent.py
│ └── temperatureStateIntent.yaml
├── turnEntityOffInX
│ ├── requirements.txt
│ ├── turnEntityOffInXIntent-utterances_DE.csv
│ ├── turnEntityOffInXIntent-utterances_EN.csv
│ ├── turnEntityOffInXIntent.py
│ └── turnEntityOffInXIntent.yaml
└── windowsOpen
│ ├── windowsOpenIntent-utterances_DE.csv
│ ├── windowsOpenIntent-utterances_EN.csv
│ ├── windowsOpenIntent.py
│ └── windowsOpenIntent.yaml
├── alexaSpeakerConnector
├── alexaSpeakerConnector.py
└── alexaSpeakerConnector.yaml
├── appWatcher
├── appWatcher.py
└── appWatcher.yaml
├── apps.yaml
├── buttonClicked
├── buttonClicked.py
└── buttonClicked.yaml
├── comingHome
├── comingHome.py
└── comingHome.yaml
├── deconz_xiaomi_button.yaml
├── deconz_xiaomi_button
└── deconz_xiaomi_button.py
├── detectWrongState
├── detectWrongState.py
└── detectWrongState.yaml
├── ench.yaml
├── eventMonitor
├── eventMonitor.py
└── eventMonitor.yaml
├── faceRecognitionBot
├── faceRecognitionBot.py
└── faceRecognitionBot.yaml
├── globals.py
├── heartbeat
├── heartbeat.py
└── heartbeat.yaml
├── homeArrivalNotifier
├── homeArrivalNotifier.py
└── homeArrivalNotifier.yaml
├── images
├── alarmClock.PNG
├── dishWasherNotify.PNG
├── failedLogin.PNG
├── googleTravelTimes.PNG
├── logo-pretty.png
├── logo-round-192x192.png
├── next_appoint_leave_modifier.PNG
├── next_appoint_leave_modifier_notification.PNG
├── notifyOfActionWhenAway.PNG
├── plantWateringReminder.PNG
├── plantWateringReminderAcknowledged.PNG
├── roggenNotify.PNG
├── ventilatorAutomation.PNG
└── washingMachineStart.PNG
├── isHomeDeterminer
├── isHomeDeterminer.py
└── isHomeDeterminer.yaml
├── isUserHomeDeterminer
├── isUserHomeDeterminer.py
└── isUserHomeDeterminer.yaml
├── leavingZoneNotifier
├── leavingZoneNotifier.py
└── leavingZoneNotifier.yaml
├── motionTrigger
├── motionTrigger.py
└── motionTrigger.yaml
├── newWifiDeviceNotify
├── newWifiDeviceNotify.py
├── newWifiDeviceNotify.yaml
└── requirements.txt
├── nextAppointmentLeaveNotifier
├── nextAppointmentLeaveNotifier.py
└── nextAppointmentLeaveNotifier.yaml
├── notifier
├── notifier.py
└── notifier.yaml
├── notifyOfActionWhenAway
├── notifyOfActionWhenAway.py
└── notifyOfActionWhenAway.yaml
├── plantWateringNotifier
├── plantWateringNotifier.py
└── plantWateringNotifier.yaml
├── pollenNotifier
├── pollenNotifier.py
└── pollenNotifier.yaml
├── powerUsageNotification
├── powerUsageNotification.py
└── powerUsageNotification.yaml
├── reminder
├── reminder.py
└── reminder.yaml
├── requirements.txt
├── seqSink
├── requirements.txt
├── seqSink.py
└── seqSink.yaml
├── setThermostat
├── setThermostat.py
└── setThermostat.yaml
├── setThermostatOnStateChange
├── setThermostatOnStateChange.py
└── setThermostatOnStateChange.yaml
├── sleepModeHandler
├── sleepModeHandler.py
├── sleepModeHandler.yaml
├── userSleepModeHandler.py
└── userSleepModeHandler.yaml
├── travelTimeNotifier
├── travelTimeNotifier.py
└── travelTimeNotifier.yaml
├── turnFanOnWhenHot
├── turnFanOnWhenHot.py
└── turnFanOnWhenHot.yaml
└── turnOffBarAfterRestart
├── turnOffBarAfterRestart.py
└── turnOffBarAfterRestart.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | #appdaemon
107 | secrets.py
108 |
109 | #VS Code
110 | .vscode
111 |
112 | #PyCharm
113 | .idea
114 |
115 | #AppDaemon
116 | secrets.yaml
117 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/.pylintrc
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Kevin Eifinger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/alarmClock/alarmClock.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 | import math
4 |
5 |
6 | #
7 | # Alarm Clock App
8 | #
9 | #
10 | # Args:
11 | # alarm_time: entity which holds the alarm time. example: sensor.alarm_time
12 | # wakemeup: entity which enables the alarm. example: input_boolean.wakemeup
13 | # naturalwakeup: entity which enables the natural wake up fade in. example: input_number.alarm_natural_wakeup_fade_in
14 | # alarmweekday: entity which enables alarm only on weekdays. example: input_boolean.alarmweekday
15 | # radiowakeup: entity which enables radio wake up. example: input_boolean.radiowakeup
16 | # TODO radioplayer: entity which holds the information which radio player to select. example: input_select.wakeup_radioplayer
17 | # wakeup_light: light to fade in. example: light.bedroom_yeelight
18 | # isweekday: entity which holds the information whether today is a week day. example: binary_sensor.workday_today
19 | # notify_name: Who to notify. example: group_notifications
20 | # message: localized message to use in notification. e.g. "You left open {} Dummy."
21 | #
22 | # Release Notes
23 | #
24 | # Version 1.4:
25 | # Catch unknown
26 | #
27 | # Version 1.3.1:
28 | # Use consistent message variable
29 | #
30 | # Version 1.3:
31 | # Use new formatted alarm_time
32 | #
33 | # Version 1.2:
34 | # use Notify App
35 | #
36 | # Version 1.1:
37 | # message now directly in own yaml instead of message module
38 | #
39 | # Version 1.0:
40 | # Initial Version
41 |
42 |
43 | class AlarmClock(hass.Hass):
44 | def initialize(self):
45 |
46 | self.timer_handle_list = []
47 | self.listen_event_handle_list = []
48 | self.listen_state_handle_list = []
49 |
50 | self.alarm_time = self.args["alarm_time"]
51 | self.wakemeup = self.args["wakemeup"]
52 | self.naturalwakeup = self.args["naturalwakeup"]
53 | self.alarmweekday = self.args["alarmweekday"]
54 | self.radiowakeup = self.args["radiowakeup"]
55 | self.isweekday = self.args["isweekday"]
56 | self.notify_name = self.args["notify_name"]
57 | self.wakeup_light = self.args["wakeup_light"]
58 | self.fade_in_time_multiplicator = self.args["fade_in_time_multiplicator"]
59 | self.message = self.args["message"]
60 | self.button = self.args["button"]
61 |
62 | self.notifier = self.get_app("Notifier")
63 |
64 | self.brightness = 100
65 | self.rgb_color = [255, 120, 0]
66 | self.alarm_timer = None
67 |
68 | self.cached_alarm_time = self.get_state(self.alarm_time)
69 | self.cached_fade_in_time = self.get_state(self.naturalwakeup)
70 | self.add_timer()
71 |
72 | self.listen_state_handle_list.append(
73 | self.listen_state(self.alarm_change, self.alarm_time)
74 | )
75 | self.listen_state_handle_list.append(
76 | self.listen_state(self.naturalwakeup_change, self.naturalwakeup)
77 | )
78 |
79 | self.listen_event_handle_list.append(
80 | self.listen_event(self.button_clicked, "xiaomi_aqara.click")
81 | )
82 |
83 | def alarm_change(self, entity, attributes, old, new, kwargs):
84 | if new is not None and new != old and new != self.cached_alarm_time:
85 | if self.alarm_timer is not None:
86 | if self.alarm_timer in self.timer_handle_list:
87 | self.timer_handle_list.remove(self.alarm_timer)
88 | self.cancel_timer(self.alarm_timer)
89 | self.log("Alarm time change: {}".format(new))
90 | self.cached_alarm_time = new
91 | self.add_timer()
92 |
93 | def naturalwakeup_change(self, entity, attributes, old, new, kwargs):
94 | if new is not None and new != old and new != self.cached_fade_in_time:
95 | if self.alarm_timer is not None:
96 | if self.alarm_timer in self.timer_handle_list:
97 | self.timer_handle_list.remove(self.alarm_timer)
98 | self.cancel_timer(self.alarm_timer)
99 | self.log("Fade-In time change: {}".format(new))
100 | self.cached_fade_in_time = new
101 | self.add_timer()
102 |
103 | def add_timer(self):
104 | self.log("cached_alarm_time: {}".format(self.cached_alarm_time))
105 | self.log("cached_fade_in_time: {}".format(self.cached_fade_in_time))
106 | offset = self.cached_fade_in_time.split(".", 1)[0]
107 |
108 | if (
109 | self.cached_alarm_time is not None
110 | and self.cached_alarm_time != ""
111 | and self.cached_alarm_time != "unknown"
112 | ):
113 | run_datetime = datetime.datetime.strptime(
114 | self.cached_alarm_time, "%Y-%m-%d %H:%M:%S"
115 | )
116 | event_time = run_datetime - datetime.timedelta(minutes=int(offset))
117 | try:
118 | self.alarm_timer = self.run_at(self.trigger_alarm, event_time)
119 | self.timer_handle_list.append(self.alarm_timer)
120 | self.log("Alarm will trigger at {}".format(event_time))
121 | except ValueError:
122 | self.log("New trigger time would be in the past: {}".format(event_time))
123 |
124 | def trigger_alarm(self, kwargs):
125 | if self.get_state(self.wakemeup) == "on":
126 | if self.get_state(self.alarmweekday) == "off" or (
127 | self.get_state(self.alarmweekday) == "on"
128 | and self.get_state(self.isweekday) == "on"
129 | ):
130 | if float(self.cached_fade_in_time) > 0:
131 | self.log(
132 | "Turning on {}".format(self.friendly_name(self.wakeup_light))
133 | )
134 | self.call_service(
135 | "light/turn_on", entity_id=self.wakeup_light, brightness_pct=1
136 | )
137 | transition = int(
138 | float(self.cached_fade_in_time)
139 | * int(self.fade_in_time_multiplicator)
140 | )
141 | self.log(
142 | "Transitioning light in over {} seconds".format(transition)
143 | )
144 | self.timer_handle_list.append(
145 | self.run_in(
146 | self.run_fade_in, 1, transition=transition, brightness_pct=1
147 | )
148 | )
149 | self.timer_handle_list.append(
150 | self.run_in(self.run_alarm, float(self.cached_fade_in_time))
151 | )
152 |
153 | def button_clicked(self, event_name, data, kwargs):
154 | """Extra callback method to trigger the wakeup light on demand by pressing a Xiaomi Button"""
155 | if data["entity_id"] == self.button:
156 | if data["click_type"] == "single":
157 | if float(self.cached_fade_in_time) > 0:
158 | self.log(
159 | "Turning on {}".format(self.friendly_name(self.wakeup_light))
160 | )
161 | self.call_service(
162 | "light/turn_on", entity_id=self.wakeup_light, brightness_pct=1
163 | )
164 | transition = int(
165 | float(self.cached_fade_in_time)
166 | * int(self.fade_in_time_multiplicator)
167 | )
168 | self.log(
169 | "Transitioning light in over {} seconds".format(transition)
170 | )
171 | self.timer_handle_list.append(
172 | self.run_in(
173 | self.run_fade_in, 1, transition=transition, brightness_pct=1
174 | )
175 | )
176 |
177 | def run_fade_in(self, kwargs):
178 | """
179 | Callback / recursion style because the transition feature does not seem to work well with Yeelight for
180 | transition values greater than 10s.
181 | :param kwargs:
182 | :return:
183 | """
184 | wait_factor = 1
185 | transition = kwargs["transition"]
186 | brightness_pct = kwargs["brightness_pct"]
187 | pct_increase = 1 / transition
188 | self.log("pct_increase: {}".format(pct_increase), level="DEBUG")
189 | if pct_increase < 0.01:
190 | wait_factor = math.ceil(0.01 / pct_increase)
191 | pct_increase = 0.01
192 | self.log(
193 | "pct_increase smaller than 1% next run_in in {} seconds".format(
194 | wait_factor
195 | ),
196 | level="DEBUG",
197 | )
198 | brightness_pct_old = brightness_pct
199 | self.log("brightness_pct_old: {}".format(brightness_pct_old), level="DEBUG")
200 | brightness_pct_new = int((brightness_pct_old + pct_increase * 100))
201 | self.log("brightness_pct_new: {}".format(brightness_pct_new), level="DEBUG")
202 | if brightness_pct_new < 100:
203 | self.call_service(
204 | "light/turn_on",
205 | entity_id=self.wakeup_light,
206 | rgb_color=self.rgb_color,
207 | brightness_pct=brightness_pct_new,
208 | )
209 | self.timer_handle_list.append(
210 | self.run_in(
211 | self.run_fade_in,
212 | wait_factor,
213 | transition=transition,
214 | brightness_pct=brightness_pct_new,
215 | )
216 | )
217 |
218 | def run_alarm(self, kwargs):
219 | self.notifier.notify(self.notify_name, self.message)
220 | # TODO radio
221 |
222 | def terminate(self):
223 | for timer_handle in self.timer_handle_list:
224 | self.cancel_timer(timer_handle)
225 |
226 | for listen_event_handle in self.listen_event_handle_list:
227 | self.cancel_listen_event(listen_event_handle)
228 |
229 | for listen_state_handle in self.listen_state_handle_list:
230 | self.cancel_listen_state(listen_state_handle)
231 |
--------------------------------------------------------------------------------
/alarmClock/alarmClock.yaml:
--------------------------------------------------------------------------------
1 | # Alarm Clock App
2 | # alarmClock:
3 | # module: alarmClock
4 | # class: AlarmClock
5 | # alarm_time: sensor.alarm_time
6 | # wakemeup: input_boolean.wakemeup
7 | # naturalwakeup: input_number.alarm_natural_wakeup_fade_in
8 | # alarmweekday: input_boolean.alarmweekday
9 | # radiowakeup: input_boolean.radiowakeup
10 | # #TODO radioplayer: input_select.wakeup_radioplayer
11 | # wakeup_light: light.bedroom_yeelight
12 | # fade_in_time_multiplicator: 60
13 | # isweekday: binary_sensor.workday_today
14 | # notify_name: group_notifications
15 | # message: "Guten Morgen!"
16 | # #message: "Good Morning!"
17 | # button: binary_sensor.switch_158d000215aa28
18 | # dependencies:
19 | # - Notifier
20 |
--------------------------------------------------------------------------------
/alexa/README.md:
--------------------------------------------------------------------------------
1 | # Alexa Intents
2 |
3 | Intents for [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App) from [Rene Tode](https://github.com/ReneTode).
4 |
5 | To set it up for yourself follow [this](https://github.com/ReneTode/Alexa-Appdaemon-App/blob/master/alexa%20skill%20tutorial.md) tutorial
6 |
7 | ## listService
8 |
9 | Supply friendly names and known entities for other alexa skills.
10 | This is needed as this is a custom Alexa App and has nothing to do with HA Cloud / Alexa integration
11 |
12 | ## turnEntityOffInX
13 |
14 | Ask Alexa to turn something off in a set amount of minutes.
15 |
16 | Only works with entities defined under *switchable* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml)
17 |
18 | ``Alexa tell Home Assistant to turn off Ventilator in 10 Minutes``
19 |
20 | ```yaml
21 | turnEntityOffInXIntent:
22 | module: turnEntityOffInXIntent
23 | class: TurnEntityOffInXIntent
24 | language: DE
25 | textLine: "Okay Homeassistant schaltet {{device}} in"
26 | Error:
Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?
27 | unreadableState: "unlesbar fuer mich"
28 | dependencies:
29 | - listService
30 | ```
31 |
32 | ## windowsOpen
33 |
34 | Will tell you if any windows / doors are open and/or tilted
35 |
36 | Only works with entities defined under *window*/*door*/*door_tilted* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml)
37 |
38 | ``Alexa ask Home Assistant whether all windows are closed``
39 |
40 | ```yaml
41 | windowsOpenIntent:
42 | module: windowsOpenIntent
43 | class: WindowsOpenIntent
44 | language: DE
45 | textLineClosed: "Alle Fenster und Türen sind zu"
46 | #textLineClosed: "All windows and doors are closed"
47 | textLineWindowOpen: "Folgende Fenster sind noch offen"
48 | #textLineWindowOpen: "The following windows are stil open..."
49 | textLineDoorOpen: "Folgende Türen sind noch offen"
50 | #textLineDoorOpen: "The following doors are still open"
51 | textLineDoorTilted: "Die folgenden Türen sind noch gekippt"
52 | #textLineDoorTilted: "The following doors are tilted"
53 | Error: Ich habe dich nicht richtig verstanden
54 | unreadableState: "unlesbar fuer mich"
55 | dependencies:
56 | - listService
57 | ```
58 |
59 | ## nextBusIntent
60 |
61 | Will tell you the next departure of a bus/train of a [RMV](https://www.home-assistant.io/components/sensor.rmvtransport/) sensor
62 |
63 | ``Alexa ask Home Assistant when the next bus departs``
64 |
65 | ```yaml
66 | nextBusIntent:
67 | module: nextBusIntent
68 | class: nextBusIntent
69 | textLine: "Linie {} fährt in {} Minuten"
70 | #textLine: "Line {} departs in {} minutes"
71 | Error: Ich habe nicht richtig verstanden was du meinst
72 | sensor: sensor.nach_bruckenkopf
73 | global_dependencies:
74 | - globals
75 | ```
76 |
77 | ## remindMeOfXWhenZoneIntent
78 |
79 | CURRENTLY DOES NOT WORK BECAUSE ALEXA DOES NOT ALLOW INTENTS CONTAINING REMINDERS
80 |
81 | Will send you a reminder over the notification service when you leave/enter a zone.
82 |
83 | ``Alexa tell Home Assistant to remind me of <> when entering work``
84 |
85 | ```yaml
86 | remindMeOfXWhenZoneIntent:
87 | module: remindMeOfXWhenZoneIntent
88 | class: RemindMeOfXWhenZoneIntent
89 | device_tracker: person.kevin
90 | notify_name: group_notifications
91 | Error: Es ist etwas schief gegangen
92 | textLine: "Okay ich erinnere dich an {{reminder}} wenn du {{zone}} "
93 | textEnter: "erreichst"
94 | textLeave: "verlässt"
95 | remindMessageSkeleton: "Ich sollte dich erinnern an "
96 | zoneMapping:
97 | arbeit: work
98 | hause: home
99 | elmo: elmo
100 | dependencies:
101 | - Notifier
102 | global_dependencies:
103 | - globals
104 | ```
105 |
--------------------------------------------------------------------------------
/alexa/alexa.yaml:
--------------------------------------------------------------------------------
1 | alexa_api: # appdaemon skill
2 | module: alexa_api
3 | class: alexa_api
4 | cardTitle: Your Card Title
5 | devices:
6 | unknownDevice: ein unbekannte platze
7 | logfile: /conf/logs/alexaAD.log
8 | responselogfile: /conf/logs/alexaResponse.log
9 | language: DE
10 | temperatureUnit: "Grad"
11 | logging: "True"
12 | launchRequest:
13 | - bonjour, bist du wieder in {{device}}?
wie kann ich dir helfen?
14 | - aloha, wie gehts es in {{device}}?
kann ich etwas fuer dich tun?
15 | - hey, du bist wieder in {{device}}.
was kan ich machen?
16 | - moin. Schoen das du wieder im {{device}} bist,
was ist dein wunsch?
17 | - mazzel tov. Ich sage hallo zu alle in {{device}}
Kann ich etwas fuer dich tun?
18 | nextConversationQuestion:
19 | - Was kann ich als naechstes fuer dich tun?
20 | - Mit was willst du das ich dir helfe?
21 | intentEnd:
22 | - Kann ich noch etwas fuer dich machen?
23 | - Willst du das ich noch etwas anderes mache?
24 | - Kann ich dir mit noch etwas helfen?
25 | - Gibt es noch etwas was ich fuer dich tun kann?
26 | conversationEnd:
27 | - Bis zum naechsten mal
28 | - arrivederci
29 | - bis bald
30 | - bis dann
31 | - mach's gut
32 | - tschoe
33 | - viel glueck
34 | - Schoen mit dir gesprochen zu haben
35 | responseError:
36 | - Es tut mir leit aber etwas ist falsch gegangen
37 | - Leider meldet home assistant einen Fehler
--------------------------------------------------------------------------------
/alexa/lightState/lightStateIntent-utterances_DE.csv:
--------------------------------------------------------------------------------
1 | Ist {device} aus
2 | Ist {device} an
3 | Was ist der Status von {device}
--------------------------------------------------------------------------------
/alexa/lightState/lightStateIntent-utterances_EN.csv:
--------------------------------------------------------------------------------
1 | Is {device} off
2 | Is {device} on
3 | What is the state of {device}
4 |
--------------------------------------------------------------------------------
/alexa/lightState/lightStateIntent.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import random
3 |
4 |
5 | class lightStateIntent(hass.Hass):
6 | def initialize(self):
7 | return
8 |
9 | def getIntentResponse(self, slots, devicename):
10 | ############################################
11 | # an Intent to give back the state from a light.
12 | # but it also can be any other kind of entity
13 | ############################################
14 | try:
15 | entityname = self.args["entities"][slots["device"]]
16 | state = self.get_state(entityname)
17 | if isinstance(state, float):
18 | if self.args["language"] == "DE":
19 | state = self.floatToStr(state)
20 | else:
21 | state = str(state)
22 | elif isinstance(state, str):
23 | state = state
24 | else:
25 | state = self.args["unreadableState"]
26 | text = self.random_arg(self.args["textLine"]) + state
27 | except:
28 | text = self.random_arg(self.args["Error"])
29 | return text
30 |
31 | def floatToStr(self, myfloat):
32 | ############################################
33 | # replace . with , for better speech
34 | ############################################
35 | floatstr = str(myfloat)
36 | floatstr = floatstr.replace(".", ",")
37 | return floatstr
38 |
--------------------------------------------------------------------------------
/alexa/lightState/lightStateIntent.yaml:
--------------------------------------------------------------------------------
1 | lightStateIntent:
2 | module: lightStateIntent
3 | class: lightStateIntent
4 | language: DE
5 | temperatureUnit: "Grad"
6 | textLine:
7 | - "Der Status von {{device}} ist "
8 | - "{{device}} ist in diesem Moment "
9 | - "{{device}} ist "
10 | Error: Ich habe nicht richtig verstanden welches geraet oder sensor du wissen wolltest
11 | unreadableState: "unlesbar fuer mich"
12 | entities:
13 | wohnzimmer temperatur: sensor.large_lamp_temperature
14 | deckenlampe: light.livingroom_yeelight
15 | ventilator: switch.ventilator
--------------------------------------------------------------------------------
/alexa/listService/listService.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # Provide the list of HA entities for Alexa Apps
5 | #
6 | #
7 | # Args:
8 | #
9 | # switchable: dict of switchable entities
10 | # temperature: dict of temperature sensors
11 | # door: dict of reed sensors showing if the door is completely open
12 | # door_tilted: dict of reed sensors showing if a door is partially/leaning open
13 | # window: dict of reed sensors showing if a window is open
14 | #
15 | #
16 | # Release Notes
17 | #
18 | # Version 1.0:
19 | # Initial Version
20 |
21 |
22 | class ListService(hass.Hass):
23 | def initialize(self):
24 | return
25 |
26 | def getSwitchable(self):
27 | return self.args["switchable"]
28 |
29 | def getTemperature(self):
30 | return self.args["temperature"]
31 |
32 | def getDoor(self):
33 | return self.args["door"]
34 |
35 | def getWindow(self):
36 | return self.args["window"]
37 |
38 | def getDoorTilted(self):
39 | return self.args["door_tilted"]
40 |
--------------------------------------------------------------------------------
/alexa/listService/listService.yaml:
--------------------------------------------------------------------------------
1 | listService:
2 | module: listService
3 | class: ListService
4 | switchable:
5 | große lampe: switch.large_lamp
6 | kleine lampe: switch.small_lamp
7 | deckenlampe: light.livingroom_yeelight
8 | ventilator: switch.ventilator
9 | großer ventilator: switch.large_ventilator
10 | markise: switch.markise
11 | fernseher: switch.tv
12 | schlafzimmer receiver: switch.bedroom_receiver
13 | snowboard: switch.snowboard
14 | receiver: media_player.denon_avr_x1300w
15 | wohnzimmer: group.livingroom
16 | wohnung: group.all
17 | schlafzimmer lampe: light.bedroom_yeelight
18 | schlafzimmer: light.bedroom_yeelight
19 | bar: light.bar_table
20 | bar licht: light.bar_table
21 | treppe unten: light.stairs_lower_yeelight
22 | treppe oben: light.treppe_oben
23 | leselampe: light.reading_lamp_yeelight
24 | lese lampe: light.reading_lamp_yeelight
25 | runde lampe: light.reading_lamp_yeelight
26 | garderobe: light.lobby_yeelight
27 | garderoben licht: light.lobby_yeelight
28 | garderobenlicht: light.lobby_yeelight
29 | flur: switch.lobby
30 | flur licht: switch.lobby
31 | bad: light.lower_bathroom_yeelight
32 | bad licht: light.lower_bathroom_yeelight
33 | treppe: group.stair_lights
34 | treppen licht: group.stair_lights
35 | treppenlicht: group.stair_lights
36 |
37 | temperature:
38 | große lampe thermostat: sensor.large_lamp_temperature
39 | kleine lampe thermostat: sensor.small_lamp_temperature
40 | ventilator thermostat: sensor.ventilator_temperature
41 | großer ventilator thermostat: sensor.large_ventilator_temperature
42 | door:
43 | wohnungstuer: binary_sensor.contact_door
44 | terassentuer schlafzimmer: binary_sensor.contact_bedroom_door
45 | terassentuer: binary_sensor.contact_terrace_door
46 | terassentuer arbeitszimmer: binary_sensor.contact_studyroom_door
47 | window:
48 | kuechenfenster: binary_sensor.contact_kitchen_window
49 | badfenster oben: binary_sensor.contact_upper_bathroom_window
50 | badfenster unten: binary_sensor.contact_lower_bathroom_window
51 | gaestezimmerfenster: binary_sensor.contact_guestroom_window
52 | door_tilted:
53 | kuechenfenster gekippt: binary_sensor.contact_kitchen_window_tilted
54 | terassentuer arbeitszimmer gekippt: binary_sensor.contact_studyroom_door_tilted
55 | terassentuer schlafzimmer gekippt: binary_sensor.contact_bedroom_door_tilted
56 | terrassentuer gekippt: binary_sensor.contact_terrace_door_tilted
57 | badfenster unten gekippt: binary_sensor.contact_lower_bathroom_window_tilted
58 | badfenster oben gekippt: binary_sensor.contact_upper_bathroom_window_tilted
59 | gaestezimmerfenster gekippt: binary_sensor.contact_guestroom_window_tilted
60 |
61 |
--------------------------------------------------------------------------------
/alexa/nextBus/nextBusIntent.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import random
3 |
4 |
5 | class nextBusIntent(hass.Hass):
6 | def initialize(self):
7 | self.sensor = self.args["sensor"]
8 | self.textLine = self.args["textLine"]
9 | self.error = self.args["error"]
10 |
11 | def getIntentResponse(self, slots, devicename):
12 | ############################################
13 | # give next bus departure
14 | ############################################
15 | try:
16 | state = self.get_state(self.sensor, attribute="all")
17 | line = state["attributes"]["line"]
18 | minutes = state["attributes"]["minutes"]
19 | text = self.textLine.format(line, minutes)
20 | except:
21 | text = self.error
22 | return text
23 |
--------------------------------------------------------------------------------
/alexa/nextBus/nextBusIntent.yaml:
--------------------------------------------------------------------------------
1 | nextBusIntent:
2 | module: nextBusIntent
3 | class: nextBusIntent
4 | textLine: "Linie {} fährt in {} Minuten"
5 | #textLine: "Line {} departs in {} minutes"
6 | error: Ich habe nicht richtig verstanden was du meinst
7 | sensor: sensor.nach_bruckenkopf
--------------------------------------------------------------------------------
/alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | __ZONE_ACTION_ENTER__ = "kommen"
5 | __ZONE_ACTION_LEAVE__ = "verlassen"
6 |
7 |
8 | class RemindMeOfXWhenZoneIntent(hass.Hass):
9 | def initialize(self):
10 | self.timer_handle_list = []
11 | self.listen_state_handle_list = []
12 |
13 | self.device_tracker = self.args["device_tracker"]
14 | self.notify_name = self.args["notify_name"]
15 | self.remindMessageSkeleton = self.args["remindMessageSkeleton"]
16 |
17 | self.notifier = self.get_app("Notifier")
18 | return
19 |
20 | def getIntentResponse(self, slots, devicename):
21 | ############################################
22 | # an Intent to give back the state from a light.
23 | # but it also can be any other kind of entity
24 | ############################################
25 | try:
26 | # get zone_name for friendly name used when talking to alexa
27 | zone_name = None
28 | for key, value in self.args["zoneMapping"].items():
29 | if key == slots["zone"].lower():
30 | zone_name = value
31 | # listen to a state change of the zone
32 | if zone_name == None:
33 | raise Exception(
34 | "Could not find zonemapping for: {}".format(slots["zone"].lower())
35 | )
36 | else:
37 | self.listen_state_handle_list.append(
38 | self.listen_state(
39 | self.remind_callback,
40 | self.device_tracker,
41 | zone=slots["zone"],
42 | zoneAction=slots["zoneAction"],
43 | reminder=slots["reminder"],
44 | )
45 | )
46 | # set correct zoneAction response
47 | if slots["zoneAction"] == __ZONE_ACTION_ENTER__:
48 | text = self.args["textLine"] + self.args["textEnter"]
49 | else:
50 | text = self.args["textLine"] + self.args["textLeave"]
51 | except Exception as e:
52 | self.log("Exception: {}".format(e))
53 | self.log("slots: {}".format(slots))
54 | text = self.random_arg(self.args["Error"])
55 | return text
56 |
57 | def remind_callback(self, entity, attribute, old, new, kwargs):
58 | if kwargs["zoneAction"] == __ZONE_ACTION_ENTER__:
59 | if new != old and new == kwargs["zone"]:
60 | self.log("Notifying")
61 | self.notifier.notify(
62 | self.notify_name,
63 | self.remindMessageSkeleton + kwargs["reminder"],
64 | useAlexa=False,
65 | )
66 | elif kwargs["zoneAction"] == __ZONE_ACTION_LEAVE__:
67 | if new != old and old == kwargs["zone"]:
68 | self.log("Notifying")
69 | self.notifier.notify(
70 | self.notify_name,
71 | self.remindMessageSkeleton + kwargs["reminder"],
72 | useAlexa=False,
73 | )
74 |
75 | def terminate(self):
76 | for timer_handle in self.timer_handle_list:
77 | self.cancel_timer(timer_handle)
78 |
79 | for listen_state_handle in self.listen_state_handle_list:
80 | self.cancel_listen_state(listen_state_handle)
81 |
--------------------------------------------------------------------------------
/alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.yaml:
--------------------------------------------------------------------------------
1 | remindMeOfXWhenZoneIntent:
2 | module: remindMeOfXWhenZoneIntent
3 | class: RemindMeOfXWhenZoneIntent
4 | device_tracker: person.kevin
5 | notify_name: group_notifications
6 | Error: Es ist etwas schief gegangen
7 | textLine: "Okay ich erinnere dich an {{reminder}} wenn du {{zone}} "
8 | textEnter: "erreichst"
9 | textLeave: "verlässt"
10 | remindMessageSkeleton: "Ich sollte dich erinnern an "
11 | zoneMapping:
12 | arbeit: work
13 | hause: home
14 | elmo: elmo
15 | dependencies:
16 | - Notifier
--------------------------------------------------------------------------------
/alexa/temperatureState/temperatureStateIntent-utterances_DE.csv:
--------------------------------------------------------------------------------
1 | wie viel grad ist es in {location}
2 | was ist die temperatur in {location}
--------------------------------------------------------------------------------
/alexa/temperatureState/temperatureStateIntent-utterances_EN.csv:
--------------------------------------------------------------------------------
1 | how many degree is it in {location}
2 | what is the temperture in {location}
3 |
--------------------------------------------------------------------------------
/alexa/temperatureState/temperatureStateIntent.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import random
3 |
4 |
5 | class temperatureStateIntent(hass.Hass):
6 | def initialize(self):
7 | return
8 |
9 | def getIntentResponse(self, slots, devicename):
10 | ############################################
11 | # give temperature for a list of temperature sensors
12 | ############################################
13 | try:
14 | if self.args["language"] == "DE":
15 | temp = (
16 | self.floatToStr(
17 | self.get_state(self.args["sensors"][slots["location"]])
18 | )
19 | + self.args["temperatureUnit"]
20 | )
21 | else:
22 | temp = (
23 | str(self.get_state(self.args["sensors"][slots["location"]]))
24 | + self.args["temperatureUnit"]
25 | )
26 | text = self.random_arg(self.args["textLine"]) + temp
27 | except:
28 | text = self.random_arg(self.args["Error"])
29 | return text
30 |
31 | def floatToStr(self, myfloat):
32 | ############################################
33 | # replace . with , for better speech
34 | ############################################
35 | floatstr = str(myfloat)
36 | floatstr = floatstr.replace(".", ",")
37 | return floatstr
38 |
39 | def random_arg(self, argName):
40 | ############################################
41 | # pick a random text from a list
42 | ############################################
43 | if isinstance(argName, list):
44 | text = random.choice(argName)
45 | else:
46 | text = argName
47 | return text
48 |
--------------------------------------------------------------------------------
/alexa/temperatureState/temperatureStateIntent.yaml:
--------------------------------------------------------------------------------
1 | temperatureStateIntent:
2 | module: temperatureStateIntent
3 | class: temperatureStateIntent
4 | language: DE
5 | temperatureUnit: "Grad"
6 | textLine:
7 | - "Die Temperatur im {{location}} ist "
8 | - "In diesem Moment ist es im {{location}} "
9 | - "Im Moment ist die Temperatur "
10 | Error: Ich habe nicht richtig verstanden welche Temperatur du wissen wolltest
11 | sensors:
12 | wohnzimmer: sensor.large_lamp_temperature
--------------------------------------------------------------------------------
/alexa/turnEntityOffInX/requirements.txt:
--------------------------------------------------------------------------------
1 | isodate
--------------------------------------------------------------------------------
/alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_DE.csv:
--------------------------------------------------------------------------------
1 | In {duration} {device} ausschalten
2 | {device} in {duration} ausschalten
3 | er soll {device} in {duration} ausschalten
4 | es soll {device} in {duration} ausschalten
5 | Schalte {device} in {duration} aus
--------------------------------------------------------------------------------
/alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_EN.csv:
--------------------------------------------------------------------------------
1 | In {duration} turn off {device}
2 | turn off {device} in {duration}
3 | it should turn off {device} in {duration}
4 |
--------------------------------------------------------------------------------
/alexa/turnEntityOffInX/turnEntityOffInXIntent.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import random
3 | import isodate
4 | import datetime
5 |
6 |
7 | class TurnEntityOffInXIntent(hass.Hass):
8 | def initialize(self):
9 | self.timer_handle_list = []
10 | self.listService = self.get_app("listService")
11 | return
12 |
13 | def getIntentResponse(self, slots, devicename):
14 | ############################################
15 | # an Intent to give back the state from a light.
16 | # but it also can be any other kind of entity
17 | ############################################
18 | try:
19 |
20 | entityname = self.listService.getSwitchable()[slots["device"]]
21 | # to upper because of https://github.com/gweis/isodate/issues/52
22 | duration = isodate.parse_duration(slots["duration"].upper())
23 | self.timer_handle_list.append(
24 | self.run_in(
25 | self.turn_off_callback,
26 | duration.total_seconds(),
27 | entityname=entityname,
28 | )
29 | )
30 | minutes, seconds = divmod(duration.total_seconds(), 60)
31 | minutes = int(minutes)
32 | seconds = int(seconds)
33 | if minutes == 0:
34 | if seconds == 1:
35 | timeText = " einer Sekunde"
36 | else:
37 | timeText = " {} Sekunden".format(seconds)
38 | elif minutes == 1:
39 | if seconds == 1:
40 | timeText = " einer Minute und einer Sekunde"
41 | elif seconds == 0:
42 | timeText = " einer Minute"
43 | else:
44 | timeText = " einer Minute und {} Sekunden".format(seconds)
45 | else:
46 | if seconds == 1:
47 | timeText = " {} Minuten und einer Sekunde".format(minutes)
48 | elif seconds == 0:
49 | timeText = " {} Minuten".format(minutes)
50 | else:
51 | timeText = " {} Minuten und {} Sekunden".format(minutes, seconds)
52 | text = self.args["textLine"] + timeText + " ab."
53 | except Exception as e:
54 | self.log("Exception: {}".format(e))
55 | self.log("slots: {}".format(slots))
56 | text = self.random_arg(self.args["Error"])
57 | return text
58 |
59 | def turn_off_callback(self, kwargs):
60 | entityname = kwargs["entityname"]
61 | self.log("Turning off {}".format(entityname))
62 | self.turn_off(entityname)
63 |
64 | def random_arg(self, argName):
65 | ############################################
66 | # pick a random text from a list
67 | ############################################
68 | if isinstance(argName, list):
69 | text = random.choice(argName)
70 | else:
71 | text = argName
72 | return text
73 |
74 | def terminate(self):
75 | for timer_handle in self.timer_handle_list:
76 | self.cancel_timer(timer_handle)
77 |
--------------------------------------------------------------------------------
/alexa/turnEntityOffInX/turnEntityOffInXIntent.yaml:
--------------------------------------------------------------------------------
1 | turnEntityOffInXIntent:
2 | module: turnEntityOffInXIntent
3 | class: TurnEntityOffInXIntent
4 | language: DE
5 | textLine: "Okay Homeassistant schaltet {{device}} in"
6 | Error: Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?
7 | unreadableState: "unlesbar fuer mich"
8 | dependencies:
9 | - listService
--------------------------------------------------------------------------------
/alexa/windowsOpen/windowsOpenIntent-utterances_DE.csv:
--------------------------------------------------------------------------------
1 | ob alles zu ist
2 | ob noch etwas offen ist
3 | ist noch etwas offen
4 | ist alles zu
5 | sind alle türen zu
6 | ob noch türen offen sind
7 | ob alle türen zu sind
8 | ob alle fenster zu sind
9 | ob noch Fenster offen sind
10 | ob die Fenster zu sind
11 | Sind die Fenster zu
12 | Sind alle Fenster zu
13 | Welche Fenster sind offen
--------------------------------------------------------------------------------
/alexa/windowsOpen/windowsOpenIntent-utterances_EN.csv:
--------------------------------------------------------------------------------
1 | whether everything is closed
2 | whether something is still open
3 | is something still open
4 | is everthing closed
5 | are all doors closed
6 | whether some doors are still open
7 | whether all doors are closed
8 | whether all windows are closed
9 | whether some windows are open
10 | whether the windows are closed
11 | Are the Windows closed
12 | Are all windows closed
13 | Which Windows are open
14 |
--------------------------------------------------------------------------------
/alexa/windowsOpen/windowsOpenIntent.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import random
3 | import isodate
4 | import datetime
5 |
6 |
7 | class WindowsOpenIntent(hass.Hass):
8 | def initialize(self):
9 | self.listService = self.get_app("listService")
10 | return
11 |
12 | def getIntentResponse(self, slots, devicename):
13 | ############################################
14 | # an Intent to give back the state of windows
15 | ############################################
16 | try:
17 | windows_dict = self.listService.getWindow()
18 | doors_dict = self.listService.getDoor()
19 | doors_tilted_dict = self.listService.getDoorTilted()
20 | window_open_list = []
21 | door_open_list = []
22 | door_tilted_list = []
23 | # iterate over all window entities
24 | for key, value in windows_dict.items():
25 | # if a window is open ("on") add it to the window_open_list
26 | if self.get_state(value) == "on":
27 | window_open_list.append(value)
28 | # iterate over all door entities
29 | for key, value in doors_dict.items():
30 | # if a door is open ("on") add it to the door_open_list
31 | if self.get_state(value) == "on":
32 | door_open_list.append(value)
33 | # iterate over all door_tilted entities
34 | for key, value in doors_tilted_dict.items():
35 | # if a door is tilted ("on") add it to the door_tilted_list
36 | if self.get_state(value) == "on":
37 | door_tilted_list.append(value)
38 |
39 | text = ""
40 | # add open windows to response
41 | if len(window_open_list) > 0:
42 | if text != "":
43 | text = text + ' '
44 | text = text + self.args["textLineWindowOpen"]
45 | for entity in window_open_list:
46 | text = (
47 | text + ' ' + self.friendly_name(entity)
48 | )
49 | # add open doors to response
50 | if len(door_open_list) > 0:
51 | if text != "":
52 | text = text + ' '
53 | text = text + self.args["textLineDoorOpen"]
54 | for entity in door_open_list:
55 | text = (
56 | text + ' ' + self.friendly_name(entity)
57 | )
58 | # add tilted doors to reponse
59 | if len(door_tilted_list) > 0:
60 | if text != "":
61 | text = text + ' '
62 | text = text + self.args["textLineDoorTilted"]
63 | for entity in door_tilted_list:
64 | friendly_name = self.friendly_name(entity)
65 | # remove "gekippt" (german for tilted) from the friendly name
66 | friendly_name = friendly_name.replace(" gekippt", "")
67 | friendly_name = friendly_name.replace(" Gekippt", "")
68 | text = text + ' ' + friendly_name
69 | # if all closed response
70 | if text == "":
71 | text = self.args["textLineClosed"]
72 | except Exception as e:
73 | self.log("Exception: {}".format(e))
74 | self.log("slots: {}".format(slots))
75 | text = self.random_arg(self.args["Error"])
76 | return text
77 |
78 | def random_arg(self, argName):
79 | ############################################
80 | # pick a random text from a list
81 | ############################################
82 | if isinstance(argName, list):
83 | text = random.choice(argName)
84 | else:
85 | text = argName
86 | return text
87 |
--------------------------------------------------------------------------------
/alexa/windowsOpen/windowsOpenIntent.yaml:
--------------------------------------------------------------------------------
1 | windowsOpenIntent:
2 | module: windowsOpenIntent
3 | class: WindowsOpenIntent
4 | language: DE
5 | textLineClosed: "Alle Fenster und Türen sind zu"
6 | #textLineClosed: "All windows and doors are closed"
7 | textLineWindowOpen: "Folgende Fenster sind noch offen"
8 | #textLineWindowOpen: "The following windows are stil open..."
9 | textLineDoorOpen: "Folgende Türen sind noch offen"
10 | #textLineDoorOpen: "The following doors are still open"
11 | textLineDoorTilted: "Die folgenden Türen sind noch gekippt"
12 | #textLineDoorTilted: "The following doors are tilted"
13 | Error: Ich habe dich nicht richtig verstanden
14 | unreadableState: "unlesbar fuer mich"
15 | dependencies:
16 | - listService
--------------------------------------------------------------------------------
/alexaSpeakerConnector/alexaSpeakerConnector.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers
5 | #
6 | # Args:
7 | #
8 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
9 | # alexa_entity: the alexa media player entity. example: media_player.kevins_echo_dot_oben
10 | # alexa_entity_source: source to set alexa to. example: Denon AVR-X1300W
11 | # receiver: Receiver to turn on. example: media_player.denon_avr_x1300w
12 | # receiver_source: source to set receiver to. example: Bluetooth
13 | #
14 | # Release Notes
15 | #
16 | # Version 1.2.0:
17 | # Introduce INITIAL_VOLUME
18 | #
19 | # Version 1.1.1:
20 | # Fix WAITING_TIME
21 | #
22 | # Version 1.1:
23 | # Introduce WAITING_TIME
24 | #
25 | # Version 1.0:
26 | # Initial Version
27 |
28 | WAITING_TIME = 10
29 | INITIAL_VOLUME = 30
30 |
31 |
32 | class AlexaSpeakerConnector(hass.Hass):
33 | def initialize(self):
34 | self.listen_state_handle_list = []
35 | self.timer_handle_list = []
36 |
37 | self.app_switch = self.args["app_switch"]
38 | self.alexa_entity = self.args["alexa_entity"]
39 | self.alexa_entity_source = self.args["alexa_entity_source"]
40 | self.receiver = self.args["receiver"]
41 | self.receiver_source = self.args["receiver_source"]
42 |
43 | self.listen_state_handle_list.append(
44 | self.listen_state(self.state_change, self.alexa_entity)
45 | )
46 |
47 | def state_change(self, entity, attribute, old, new, kwargs):
48 | if self.get_state(self.app_switch) == "on":
49 | if new.lower() == "playing" and old.lower() != "playing":
50 | self.log("{} changed to {}".format(self.alexa_entity, new))
51 | # Only trigger when the receiver is off. Otherwise its probably playing something
52 | if self.get_state(self.receiver) == "off":
53 | self.log(
54 | "Setting source of {} to: {}".format(
55 | self.receiver, self.receiver_source
56 | )
57 | )
58 | self.call_service(
59 | "media_player/select_source",
60 | entity_id=self.receiver,
61 | source=self.receiver_source,
62 | )
63 | self.log(f"Setting volume of {self.receiver} to: {INITIAL_VOLUME}")
64 | self.call_service(
65 | "media_player/volume_set",
66 | entity_id=self.receiver,
67 | volume_level=INITIAL_VOLUME,
68 | )
69 | self.timer_handle_list.append(
70 | self.run_in(self.run_in_callback, WAITING_TIME)
71 | )
72 |
73 | def run_in_callback(self, kwargs):
74 | """
75 | Callback method to introduce a waiting time for the receiver to come 'online'
76 | :return:
77 | """
78 | self.log(
79 | "Setting source of {} to: {}".format(
80 | self.alexa_entity, self.alexa_entity_source
81 | )
82 | )
83 | self.call_service(
84 | "media_player/select_source",
85 | entity_id=self.alexa_entity,
86 | source=self.alexa_entity_source,
87 | )
88 |
89 | def terminate(self):
90 | for listen_state_handle in self.listen_state_handle_list:
91 | self.cancel_listen_state(listen_state_handle)
92 |
93 | for timer_handle in self.timer_handle_list:
94 | self.cancel_timer(timer_handle)
95 |
--------------------------------------------------------------------------------
/alexaSpeakerConnector/alexaSpeakerConnector.yaml:
--------------------------------------------------------------------------------
1 | #App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers
2 | # alexaSpeakerConnector:
3 | # module: alexaSpeakerConnector
4 | # class: AlexaSpeakerConnector
5 | # app_switch: input_boolean.alexa_speaker_connector
6 | # alexa_entity: media_player.kevins_echo_dot_oben
7 | # alexa_entity_source: Denon AVR-X1300W
8 | # receiver: media_player.denon_avr_x1300w
9 | # receiver_source: Bluetooth
--------------------------------------------------------------------------------
/appWatcher/appWatcher.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App which listens on the log for App crashes and notifies via telegram
5 | #
6 | # Args:
7 | #
8 | # Release Notes
9 | #
10 | # Version 2.0:
11 | # Updates for Appdaemon Version 4.0.3
12 | #
13 | # Version 1.0:
14 | # Initial Version
15 |
16 |
17 | class AppWatcher(hass.Hass):
18 | def initialize(self):
19 | self.notify_name = self.args["notify_name"]
20 | self.notify_message = self.args["notify_message"]
21 | try:
22 | self.exclude_apps = self.args["exclude_apps"].split(",")
23 | except KeyError:
24 | self.exclude_apps = None
25 |
26 | # App dependencies
27 | self.notifier = self.get_app("Notifier")
28 |
29 | self.handle = self.listen_log(self.log_message_callback)
30 |
31 | def log_message_callback(self, app_name, ts, level, log_type, message, kwargs):
32 | if level == "WARNING" or level == "ERROR" or level == "CRITICAL":
33 | if app_name == "AppDaemon":
34 | if "Unexpected error" in message:
35 | self.notifier.notify(
36 | self.notify_name,
37 | self.notify_message.format(message),
38 | useAlexa=False,
39 | )
40 |
41 | def terminate(self):
42 | self.cancel_listen_log(self.handle)
43 |
--------------------------------------------------------------------------------
/appWatcher/appWatcher.yaml:
--------------------------------------------------------------------------------
1 | appWatcher:
2 | module: appWatcher
3 | class: AppWatcher
4 | notify_name: kevin
5 | notify_message: "AppDaemon error: {}"
6 | #notify_message: "Appdaemon reported an error: {}"
7 | dependencies:
8 | - Notifier
--------------------------------------------------------------------------------
/apps.yaml:
--------------------------------------------------------------------------------
1 | #################################################################
2 | ## Global
3 | #################################################################
4 | global_modules:
5 | - globals
--------------------------------------------------------------------------------
/buttonClicked/buttonClicked.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App which toggles entities for single/double presses of xiaomi buttons
6 | #
7 | # Args:
8 | #
9 | # sensor: sensor to monitor e.g. sensor.upstairs_smoke
10 | # actor_single: actor to toggle on single click
11 | # actor_double: actor to toggle on double click
12 | # actor_hold: actor to dim on hold
13 | # Release Notes
14 | #
15 | # Version 1.2:
16 | # All actors optional
17 | #
18 | # Version 1.1:
19 | # added actor_hold
20 | #
21 | # Version 1.0:
22 | # Initial Version
23 |
24 |
25 | class ButtonClicked(hass.Hass):
26 | def initialize(self):
27 | self.listen_event_handle_list = []
28 | self.timer_handle_list = []
29 |
30 | self.actor_single = self.args.get("actor_single")
31 | self.actor_double = self.args.get("actor_double")
32 | self.actor_hold = self.args.get("actor_hold")
33 |
34 | self.dimmer_timer_handle = None
35 |
36 | self.listen_event_handle_list.append(
37 | self.listen_event(self.event_detected, "xiaomi_aqara.click")
38 | )
39 |
40 | def event_detected(self, event_name, data, kwargs):
41 | if data["entity_id"] == self.args["sensor"]:
42 | if data["click_type"] == "single" and self.actor_single != None:
43 | self.log("ButtonClicked: {}".format(data["entity_id"]))
44 | # Is on
45 | if self.get_state(self.actor_single) == "on":
46 | self.log("Turning {} off".format(self.actor_single))
47 | # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4
48 | if self.actor_single.startswith("light"):
49 | self.call_service(
50 | "light/turn_on",
51 | entity_id=self.actor_single,
52 | transition=1,
53 | brightness_pct=1,
54 | )
55 | self.timer_handle_list.append(
56 | self.run_in(self.turn_off_workaround, 2)
57 | )
58 | else:
59 | self.turn_off(self.actor_single)
60 | # Is off
61 | if self.get_state(self.actor_single) == "off":
62 | self.log("Turning {} on".format(self.actor_single))
63 | if self.actor_single.startswith("light"):
64 | self.call_service(
65 | "light/turn_on",
66 | entity_id=self.actor_single,
67 | transition=1,
68 | brightness_pct=100,
69 | )
70 | else:
71 | self.turn_on(self.actor_single)
72 |
73 | if data["click_type"] == "double" and self.actor_double != None:
74 | self.log("Double Button Click: {}".format(data["entity_id"]))
75 | self.log("Toggling {}".format(self.actor_double))
76 | # Is on
77 | if self.get_state(self.actor_double) == "on":
78 | # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4
79 | if self.actor_single.startswith("light"):
80 | self.call_service(
81 | "light/turn_on",
82 | entity_id=self.actor_single,
83 | transition=1,
84 | brightness_pct=1,
85 | )
86 | self.timer_handle_list.append(
87 | self.run_in(self.turn_off_workaround, 2)
88 | )
89 | else:
90 | self.turn_off(self.actor_single)
91 | # Is off
92 | if self.get_state(self.actor_double) == "off":
93 | self.log("Turning {} on".format(self.actor_single))
94 | if self.actor_single.startswith("light"):
95 | self.call_service(
96 | "light/turn_on",
97 | entity_id=self.actor_single,
98 | transition=1,
99 | brightness_pct=100,
100 | )
101 | else:
102 | self.turn_on(self.actor_single)
103 |
104 | if data["click_type"] == "long_click_press" and self.actor_hold != None:
105 | self.log("Long Button Click: {}".format(data["entity_id"]))
106 | self.log("Starting Dimmer")
107 | self.dimmer_timer_handle = self.run_every(
108 | self.dimmer_callback,
109 | datetime.datetime.now(),
110 | 0.5,
111 | entity_id=self.actor_hold,
112 | )
113 | self.timer_handle_list.append(self.dimmer_timer_handle)
114 |
115 | if data["click_type"] == "hold" and self.actor_hold != None:
116 | self.log("Button Release: {}".format(data["entity_id"]))
117 | self.log("Stopping Dimmer")
118 | if self.dimmer_timer_handle != None:
119 | self.cancel_timer(self.dimmer_timer_handle)
120 |
121 | def dimmer_callback(self, kwargs):
122 | """Dimm the by 10% light. If it would dim above 100% start again at 10%"""
123 | brightness_pct_old = (
124 | int(
125 | self.get_state(self.actor_hold, attribute="all")["attributes"][
126 | "brightness"
127 | ]
128 | )
129 | / 255
130 | )
131 | brightness_pct_new = brightness_pct_old + 0.1
132 | if brightness_pct_new > 1:
133 | brightness_pct_new = 0.1
134 | self.call_service(
135 | "light/turn_on",
136 | entity_id=kwargs["entity_id"],
137 | brightness_pct=brightness_pct_new * 100,
138 | )
139 |
140 | def turn_off_workaround(self, *kwargs):
141 | self.call_service("light/turn_off", entity_id=self.actor_single)
142 |
143 | def terminate(self):
144 | for listen_event_handle in self.listen_event_handle_list:
145 | self.cancel_listen_event(listen_event_handle)
146 |
147 | for timer_handle in self.timer_handle_list:
148 | self.cancel_timer(timer_handle)
149 |
--------------------------------------------------------------------------------
/buttonClicked/buttonClicked.yaml:
--------------------------------------------------------------------------------
1 | # App which toggles entities for single/double presses of xiaomi buttons
2 | # xiaomiroundButtonBedroomClicked:
3 | # module: buttonClicked
4 | # class: ButtonClicked
5 | # sensor: binary_sensor.switch_158d0001b12a12
6 | # actor_single: light.bedroom_yeelight
7 | # actor_double: group.all
8 | # actor_hold: light.bedroom_yeelight
9 | # dependencies:
10 | # - Notifier
11 |
12 | # xiaomisquareButtonLobbyClicked:
13 | # module: buttonClicked
14 | # class: ButtonClicked
15 | # sensor: binary_sensor.switch_158d00021329bc
16 | # actor_single: switch.lobby
17 | # actor_double: switch.lobby
18 | # dependencies:
19 | # - Notifier
20 |
21 | # xiaomiroundButtonBathroomClicked:
22 | # module: buttonClicked
23 | # class: ButtonClicked
24 | # sensor: sensor.0x00158d00012db9e5_click
25 | # actor_single: light.lower_bathroom_yeelight
26 | # actor_double: light.lower_bathroom_yeelight
27 | # actor_hold: light.lower_bathroom_yeelight
28 | # dependencies:
29 | # - Notifier
30 |
--------------------------------------------------------------------------------
/comingHome/comingHome.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App to Turn on Lobby Lamp when Door openes and no one is Home
6 | #
7 | # Args:
8 | #
9 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
10 | # sensor: door sensor
11 | # isHome: input_boolean which shows if someone is home eg input_boolean.isHome
12 | # actor (optional): actor to turn on. example: script.receiver_set_source_bluetooth
13 | # service (optional): service to call. example: media_player.volume_set
14 | # service_data (optional): dictionary of attributes for the service call.
15 | # after_sundown (optional): whether to only trigger after sundown. example: True
16 | # Release Notes
17 | #
18 | # Version 1.4.2:
19 | # unwrap service_data
20 | #
21 | # Version 1.4.1:
22 | # fix duplicate line for self.actor
23 | #
24 | # Version 1.4:
25 | # Add service and service_data and make actor optional
26 | #
27 | # Version 1.3.2:
28 | # Check for new != old
29 | #
30 | # Version 1.3.1:
31 | # Actually implement isHome
32 | #
33 | # Version 1.3:
34 | # Added app_switch
35 | #
36 | # Version 1.2:
37 | # Added after_sundown
38 | #
39 | # Version 1.1:
40 | # Using globals
41 | #
42 | # Version 1.0:
43 | # Initial Version
44 |
45 |
46 | class ComingHome(hass.Hass):
47 | def initialize(self):
48 | self.listen_state_handle_list = []
49 |
50 | self.app_switch = self.args["app_switch"]
51 | self.sensor = self.args["sensor"]
52 | self.isHome = self.args["isHome"]
53 | self.actor = self.args.get("actor")
54 | self.service = self.args.get("service")
55 | self.service_data = self.args.get("service_data")
56 | self.after_sundown = self.args.get("after_sundown")
57 |
58 | self.delay = 2
59 |
60 | self.listen_state_handle_list.append(
61 | self.listen_state(self.state_change, self.sensor)
62 | )
63 |
64 | def state_change(self, entity, attribute, old, new, kwargs):
65 | if self.get_state(self.app_switch) == "on":
66 | if new != "" and new != old:
67 | isHome_attributes = self.get_state(self.isHome, attribute="all")
68 | isHome_state = isHome_attributes["state"]
69 | last_changed = self.convert_utc(isHome_attributes["last_changed"])
70 | if isHome_state == "off" or (
71 | datetime.datetime.now(datetime.timezone.utc) - last_changed
72 | <= datetime.timedelta(seconds=self.delay)
73 | ):
74 | if self.after_sundown is not None and self.after_sundown:
75 | if self.sun_down():
76 | self.turn_on_actor(self.actor, entity, new)
77 | self.my_call_service(
78 | self.service, self.service_data, entity, new
79 | )
80 | else:
81 | self.turn_on_actor(self.actor, entity, new)
82 | self.my_call_service(
83 | self.service, self.service_data, entity, new
84 | )
85 |
86 | def turn_on_actor(self, actor, entity, new):
87 | if self.actor is not None:
88 | self.log("{} changed to {}".format(self.friendly_name(entity), new))
89 | self.turn_on(actor)
90 |
91 | def my_call_service(self, service, service_data, entity, new):
92 | if self.service is not None:
93 | if self.service_data is not None:
94 | self.log("{} changed to {}".format(self.friendly_name(entity), new))
95 | self.call_service(service, **service_data)
96 |
97 | def terminate(self):
98 | for listen_state_handle in self.listen_state_handle_list:
99 | self.cancel_listen_state(listen_state_handle)
100 |
--------------------------------------------------------------------------------
/comingHome/comingHome.yaml:
--------------------------------------------------------------------------------
1 | #Switch on Lobby lamp when the first person is coming home and the sun is down
2 | # comingHomeYeelight:
3 | # module: comingHome
4 | # class: ComingHome
5 | # app_switch: input_boolean.coming_home_yeelight
6 | # sensor: binary_sensor.contact_door
7 | # isHome: input_boolean.is_home
8 | # actor: switch.large_lamp
9 | # after_sundown: True
10 |
11 | # comingHomeSetVolume:
12 | # module: comingHome
13 | # class: ComingHome
14 | # app_switch: input_boolean.coming_home_set_volume
15 | # sensor: binary_sensor.contact_door
16 | # isHome: input_boolean.is_home
17 | # service: media_player/volume_set
18 | # service_data:
19 | # entity_id: media_player.denon_avr_x1300w
20 | # volume_level: 0.3
--------------------------------------------------------------------------------
/deconz_xiaomi_button.yaml:
--------------------------------------------------------------------------------
1 | DeconzXiaomiButtonBedroom:
2 | module: deconz_xiaomi_button
3 | class: DeconzXiaomiButton
4 | id: round_button_schlafzimmer
5 | actor_single: light.bedroom_yeelight
6 | actor_double: group.all
7 | actor_hold: light.bedroom_yeelight
8 |
9 | DeconzXiaomiButtonLobby:
10 | module: deconz_xiaomi_button
11 | class: DeconzXiaomiButton
12 | id: flur_switch
13 | actor_single: switch.lobby
14 | actor_double: switch.lobby
15 |
16 | DeconzXiaomiButtonLobby:
17 | module: deconz_xiaomi_button
18 | class: DeconzXiaomiButton
19 | id: flur_switch
20 | actor_single: switch.lobby
21 | actor_double: switch.lobby
22 |
23 | DeconzXiaomiButtonBathroom:
24 | module: deconz_xiaomi_button
25 | class: DeconzXiaomiButton
26 | id: round_button_bad
27 | actor_single: light.lower_bathroom_yeelight
28 | actor_double: light.lower_bathroom_yeelight
29 | actor_hold: light.lower_bathroom_yeelight
--------------------------------------------------------------------------------
/deconz_xiaomi_button/deconz_xiaomi_button.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz
6 | #
7 | # Args:
8 | #
9 | # id: id of the xiaomi button
10 | # actor_single: actor to toggle on single click
11 | # actor_double: actor to toggle on double click
12 | # actor_hold: actor to dim on hold
13 | #
14 | # Release Notes
15 | #
16 | # Version 2.0.2
17 | # use else when toggling
18 | #
19 | # Version 2.0.1
20 | # use elif when toggling
21 | #
22 | # Version 2.0:
23 | # Removed unneeded workaround for yeelight
24 | #
25 | # Version 1.0:
26 | # Initial Version
27 |
28 |
29 | class DeconzXiaomiButton(hass.Hass):
30 | def initialize(self):
31 | self.listen_event_handle_list = []
32 | self.timer_handle_list = []
33 |
34 | self.actor_single = self.args.get("actor_single")
35 | self.actor_double = self.args.get("actor_double")
36 | self.actor_hold = self.args.get("actor_hold")
37 | self.id = self.args["id"]
38 |
39 | self.dimmer_timer_handle = None
40 |
41 | self.listen_event_handle_list.append(
42 | self.listen_event(self.event_detected, "deconz_event")
43 | )
44 |
45 | def event_detected(self, event_name, data, kwargs):
46 | if data["id"] == self.id:
47 | if data["event"] == 1002 and self.actor_single is not None:
48 | self.log("ButtonClicked: {}".format(data["id"]))
49 | self.log("Toggling {}".format(self.actor_double))
50 | # Is on
51 | if self.get_state(self.actor_single) == "on":
52 | self.log("Turning {} off".format(self.actor_single))
53 | self.turn_off(self.actor_single)
54 | # Is off
55 | else:
56 | self.log("Turning {} on".format(self.actor_single))
57 | self.turn_on(self.actor_single)
58 |
59 | if data["event"] == 1004 and self.actor_double is not None:
60 | self.log("Double Button Click: {}".format(data["id"]))
61 | self.log("Toggling {}".format(self.actor_double))
62 | # Is on
63 | if self.get_state(self.actor_double) == "on":
64 | self.turn_off(self.actor_double)
65 | # Is off
66 | else:
67 | self.log("Turning {} on".format(self.actor_double))
68 | self.turn_on(self.actor_double)
69 |
70 | if data["event"] == 1001 and self.actor_hold is not None:
71 | self.log("Long Button Click: {}".format(data["id"]))
72 | self.log("Starting Dimmer")
73 | self.dimmer_timer_handle = self.run_every(
74 | self.dimmer_callback,
75 | datetime.datetime.now(),
76 | 0.5,
77 | entity_id=self.actor_hold,
78 | )
79 | self.timer_handle_list.append(self.dimmer_timer_handle)
80 |
81 | if data["event"] == 1003 and self.actor_hold is not None:
82 | self.log("Button Release: {}".format(data["id"]))
83 | self.log("Stopping Dimmer")
84 | if self.dimmer_timer_handle is not None:
85 | self.cancel_timer(self.dimmer_timer_handle)
86 |
87 | def dimmer_callback(self, kwargs):
88 | """Dimm the by 10% light. If it would dim above 100% start again at 10%"""
89 | brightness_pct_old = (
90 | int(
91 | self.get_state(self.actor_hold, attribute="all")["attributes"][
92 | "brightness"
93 | ]
94 | )
95 | / 255
96 | )
97 | brightness_pct_new = brightness_pct_old + 0.1
98 | if brightness_pct_new > 1:
99 | brightness_pct_new = 0.1
100 | self.call_service(
101 | "light/turn_on",
102 | entity_id=kwargs["entity_id"],
103 | brightness_pct=brightness_pct_new * 100,
104 | )
105 |
106 | def terminate(self):
107 | for listen_event_handle in self.listen_event_handle_list:
108 | self.cancel_listen_event(listen_event_handle)
109 |
110 | for timer_handle in self.timer_handle_list:
111 | self.cancel_timer(timer_handle)
112 |
--------------------------------------------------------------------------------
/detectWrongState/detectWrongState.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App which notifies of wrong states based on a state change
5 | #
6 | # Args:
7 | #
8 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
9 | # entities_on (optional): list of entities which should be on
10 | # entities_off (optional): list of entities which should off
11 | # trigger_entity: entity which triggers this app. example: input_boolean.is_home
12 | # trigger_state: new state of trigger_entity which triggers this app. example: "off"
13 | # after_sundown (optional): Only trigger after sundown. example: True
14 | # message_: message to use in notification
15 | # message_off_: message to use in notification
16 | # message_reed_: message to use in notification
17 | # message_reed_off_: message to use in notification
18 | # notify_name: who to notify. example: group_notifications
19 | # use_alexa: use alexa for notification. example: False
20 | #
21 | # Release Notes
22 | #
23 | # Version 2.1:
24 | # More off_states to support alexa_media
25 | #
26 | # Version 2.0:
27 | # Renamed to detectWrongState, notification optional
28 | #
29 | # Version 1.9:
30 | # check unavailable when using get_state
31 | #
32 | # Version 1.8:
33 | # check None when using get_state
34 | #
35 | # Version 1.7:
36 | # check for != off instead of == on
37 | #
38 | # Version 1.6.1:
39 | # fix wrong key access for attributes
40 | #
41 | # Version 1.6:
42 | # garage_door to device_classes of reed sensors
43 | #
44 | # Version 1.5:
45 | # distinguish normal and reed switches by device_class
46 | #
47 | # Version 1.4.1:
48 | # fix wrong assignment of app_switch
49 | #
50 | # Version 1.4:
51 | # Generalize to detectWrongState
52 | #
53 | # Version 1.3:
54 | # use Notify App
55 | #
56 | # Version 1.2:
57 | # message now directly in own yaml instead of message module
58 | #
59 | # Version 1.1:
60 | # Using globals and app_switch
61 | #
62 | # Version 1.0:
63 | # Initial Version
64 |
65 |
66 | class DetectWrongState(hass.Hass):
67 | def initialize(self):
68 | self.listen_state_handle_list = []
69 |
70 | self.app_switch = self.args["app_switch"]
71 | try:
72 | self.entities_on = self.args["entities_on"].split(",")
73 | except KeyError:
74 | self.entities_on = []
75 | try:
76 | self.entities_off = self.args["entities_off"].split(",")
77 | except KeyError:
78 | self.entities_off = []
79 | self.after_sundown = self.args.get("after_sundown")
80 | self.trigger_entity = self.args["trigger_entity"]
81 | self.trigger_state = self.args["trigger_state"]
82 | self.message = self.args.get("message")
83 | self.message_off = self.args.get("message_off")
84 | self.message_reed = self.args.get("message_reed")
85 | self.message_reed_off = self.args.get("message_reed_off")
86 | self.notify_name = self.args.get("notify_name")
87 | self.use_alexa = self.args.get("use_alexa")
88 |
89 | self.notifier = self.get_app("Notifier")
90 |
91 | self.listen_state_handle_list.append(
92 | self.listen_state(self.state_change, self.trigger_entity)
93 | )
94 |
95 | def state_change(self, entity, attribute, old, new, kwargs):
96 | if self.get_state(self.app_switch) == "on":
97 | if new != "" and new == self.trigger_state:
98 | if self.after_sundown is None or (
99 | (self.after_sundown and self.sun_down())
100 | or self.after_sundown is not False
101 | ):
102 | self.check_entities_should_be_off()
103 | self.check_entities_should_be_on()
104 |
105 | def check_entities_should_be_off(self):
106 | off_states = ["off", "unavailable", "paused", "standby"]
107 | for entity in self.entities_off:
108 | state = self.get_state(entity)
109 | self.log(f"entity: {entity}")
110 | if state is not None and state not in off_states:
111 | if self.is_entity_reed_contact(entity):
112 | message = self.message_reed
113 | else:
114 | self.turn_off(entity)
115 | message = self.message
116 | self.send_notification(message, entity)
117 |
118 | def check_entities_should_be_on(self):
119 | for entity in self.entities_on:
120 | state = self.get_state(entity)
121 | if state == "off":
122 | if self.is_entity_reed_contact(entity):
123 | message = self.message_reed_off
124 | else:
125 | self.turn_on(entity)
126 | message = self.message_on
127 | self.send_notification(message, entity)
128 |
129 | def is_entity_reed_contact(self, entity):
130 | reed_types = ["window", "door", "garage_door"]
131 | full_state = self.get_state(entity, attribute="all")
132 | if full_state is not None:
133 | attributes = full_state["attributes"]
134 | self.log("full_state: {}".format(full_state), level="DEBUG")
135 | if attributes.get("device_class") in reed_types:
136 | return True
137 | return False
138 |
139 | def send_notification(self, message, entity):
140 | if message is not None:
141 | formatted_message = message.format(self.friendly_name(entity))
142 | self.log(formatted_message)
143 | if self.notify_name is not None:
144 | self.notifier.notify(
145 | self.notify_name, formatted_message, useAlexa=self.use_alexa,
146 | )
147 |
148 | def terminate(self):
149 | for listen_state_handle in self.listen_state_handle_list:
150 | self.cancel_listen_state(listen_state_handle)
151 |
--------------------------------------------------------------------------------
/detectWrongState/detectWrongState.yaml:
--------------------------------------------------------------------------------
1 | # detectWrongStateWhenLeaving:
2 | # module: detectWrongState
3 | # class: DetectWrongState
4 | # app_switch: input_boolean.detect_wrong_state_when_leaving
5 | # entities_off: "binary_sensor.contact_bedroom_door,\
6 | # binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\
7 | # binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\
8 | # binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\
9 | # binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\
10 | # media_player.denon_avr_x1300w,switch.large_lamp,switch.small_lamp,switch.snowboard,\
11 | # light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,light.reading_lamp_yeelight,\
12 | # light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\
13 | # switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,light.lower_bathroom_yeelight,\
14 | # media_player.kevin_s_echo_dot_unten,media_player.kevins_echo,media_player.kevins_echo_dot,\
15 | # media_player.kevins_echo_dot_oben,binary_sensor.contact_upper_bathroom_window_tilted,\
16 | # binary_sensor.contact_badfenster"
17 | # trigger_entity: input_boolean.is_home
18 | # trigger_state: "off"
19 | # message: "Du hast {} angelassen. Ich habe es für dich ausgemacht."
20 | # #message: "You left on {}. I turned it off for you"
21 | # message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht."
22 | # #message_off: "You forgot to turn on {}. I turned it on for you"
23 | # message_reed: "Du hast {} offen gelassen."
24 | # #message_reed: "You left open {} Dummy."
25 | # message_reed_off: "Du hast {} zu gelassen."
26 | # #message_reed_off: "You left {} closed Dummy."
27 | # notify_name: group_notifications
28 | # use_alexa: False
29 | # log_level: DEBUG
30 | # dependencies:
31 | # - Notifier
32 |
33 | # detectWindowsOpenWhenGoingToBed:
34 | # module: detectWrongState
35 | # class: DetectWrongState
36 | # app_switch: input_boolean.detect_windows_open_when_going_to_bed
37 | # entities_off: "binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\
38 | # binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\
39 | # binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\
40 | # binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\
41 | # binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\
42 | # binary_sensor.contact_upper_bathroom_window_tilted,binary_sensor.contact_badfenster"
43 | # after_sundown: True
44 | # trigger_entity: input_boolean.sleepmode
45 | # trigger_state: "on"
46 | # message: "Du hast {} angelassen. Ich habe es für dich ausgemacht."
47 | # #message: "You left on {}. I turned it off for you"
48 | # message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht."
49 | # #message_off: "You forgot to turn on {}. I turned it on for you"
50 | # message_reed: "Du hast {} offen gelassen."
51 | # #message_reed: "You left open {} Dummy."
52 | # message_reed_off: "Du hast {} zu gelassen."
53 | # #message_reed_off: "You left {} closed Dummy."
54 | # notify_name: group_notifications
55 | # use_alexa: True
56 | # log_level: DEBUG
57 | # dependencies:
58 | # - Notifier
59 |
60 | # detectDevicesOnWhenGoingToBed:
61 | # module: detectWrongState
62 | # class: DetectWrongState
63 | # app_switch: input_boolean.detect_devices_on_when_going_to_bed
64 | # entities_off: "media_player.denon_avr_x1300w,switch.large_lamp,\
65 | # switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\
66 | # light.reading_lamp_yeelight,light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\
67 | # switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,switch.tv,light.bar_table,light.lower_bathroom_yeelight,\
68 | # switch.markise, switch.coffee_machine_plug_relay"
69 | # trigger_entity: input_boolean.sleepmode
70 | # trigger_state: "on"
71 | # log_level: DEBUG
72 | # dependencies:
73 | # - Notifier
--------------------------------------------------------------------------------
/ench.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | ench:
3 | module: ench
4 | class: EnCh
5 | notify: "notify.kevin"
6 | exclude:
7 | - device_tracker.venue_8*
8 | - person.kevin
9 | - device_tracker.sonoff_large_ventilator_7998
10 | - device_tracker.sonoff_ventilator_8133
11 | - device_tracker.android*
12 | - device_tracker.astrids_mbp
13 | - device_tracker.franzi_s_iphone
14 | - device_tracker.galaxy*
15 | - device_tracker.iphone*
16 | - device_tracker.oneplus*
17 | - device_tracker.unifi*
18 | - light.group_0
19 | - media_player.kevin_s*
20 | - sensor.192_168_1_39*
21 | - sensor.192_168_1_48*
22 | - sensor.large_ventilator*
23 | - sensor.ventilator*
24 | - switch.ventilator
25 | - switch.large_ventilator
26 | - sensor.travel_time_next*
27 | - sensor.glances*_temp
28 | - sensor.publish_ip_on_boot
29 | - sensor.*odroid*
30 | - media_player.55pus7304_12
31 | - media_player.fernseher
32 | - sensor.consumption_31
33 | - sensor.power_30
34 | - sensor.openweathermap*
35 | - light.bar_table
36 | - sensor.*_nachster_wecker
37 | - switch.xiaomi_plug
38 | - media_player.55pus7304_12_*
39 | # Alexa Media Player
40 | - sensor.*next_alarm
41 | - sensor.*next_reminder
42 | - sensor.*next_timer
43 | - sensor.this_device*
44 | - switch.*_repeat_switch*
45 | - switch.*_shuffle_switch*
46 | - switch.*do_not_disturb_switch
47 | battery:
48 | interval_min: 180
49 | min_level: 20
50 | unavailable:
51 | interval_min: 60
52 | max_unavailable_min: 15
53 |
--------------------------------------------------------------------------------
/eventMonitor/eventMonitor.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | """
4 | Monitor events and output changes to the verbose_log. Nice for debugging purposes.
5 | Arguments:
6 | - events: List of events to monitor
7 | """
8 |
9 |
10 | class Monitor(hass.Hass):
11 | def initialize(self):
12 | self.listen_event_handle_list = []
13 |
14 | events = self.args["events"]
15 |
16 | if events != None:
17 | for event in self.split_device_list(self.args["events"]):
18 | self.log('watching event "{}" for state changes'.format(event))
19 | self.listen_event_handle_list.append(
20 | self.listen_event(self.changed, event)
21 | )
22 | if len(self.listen_event_handle_list) == 0:
23 | self.log("watching all events for state changes")
24 | self.listen_event_handle_list.append(self.listen_event(self.changed))
25 |
26 | def changed(self, event_name, data, kwargs):
27 | self.log(event_name + ": " + str(data))
28 |
29 | def terminate(self):
30 | for listen_event_handle in self.listen_event_handle_list:
31 | self.cancel_listen_event(listen_event_handle)
32 |
--------------------------------------------------------------------------------
/eventMonitor/eventMonitor.yaml:
--------------------------------------------------------------------------------
1 | #eventMonitor:
2 | # module: eventMonitor
3 | # class: Monitor
4 | # events:
--------------------------------------------------------------------------------
/faceRecognitionBot/faceRecognitionBot.yaml:
--------------------------------------------------------------------------------
1 | # faceRecognitionBot:
2 | # module: faceRecognitionBot
3 | # class: FaceRecognitionBot
4 | # app_switch: input_boolean.facebox_notifier
5 | # sensor: binary_sensor.contact_door
6 | # button: binary_sensor.switch_158d000215aa28
7 | # camera: camera.android_ip_webcam_door
8 | # local_file_camera: camera.saved_image
9 | # filename: !secret facebox_notifier_filename
10 | # image_processing: image_processing.facebox
11 | # notify_name: group_notifications
12 | # wol_switch: switch.facebox_wol
13 | # user_id: !secret telegram_user_id
14 | # facebox_source_directory: !secret facebox_folderpath
15 | # facebox_unknown_directory: !secret facebox_unknown_directory
16 | # facebox_noface_directory: !secret facebox_noface_directory
17 | # facebox_known_faces_directory: !secret facebox_known_faces_directory
18 | # facebox_healthcheck_filename: !secret facebox_healthcheck_filename
19 | # healthcheck_face_name: Kevin
20 | # number_of_images: 10
21 | # waitBeforeSnapshot: 1
22 | # ip: !secret facebox_ip
23 | # port: !secret facebox_port
24 | # message_face_identified: "Ich habe {} erkannt"
25 | # #message_face_identified: "I have recognized {}."
26 | # message_unkown_face: "Ich habe dieses Gesicht nicht erkannt. Kennst du es?"
27 | # #message_unkown_face: "I have not recognized this face. Do you know it?"
28 | # message_unkown_face_with_known: "Ich habe auch ein unbekanntes Gesicht entdeckt."
29 | # #message_unkown_face_with_known: "I have also discovered an unknown face."
30 | # message_provide_name: "Wenn du das Gesicht kennst, kannst du mir einfach innerhalb der nächsten {} Minuten den Namen schreiben. Dann merke ich ihn mir!"
31 | # #message_provide_name: "If you know the face you can write the name to me within the next {} minutes. I will remember it!"
32 | # message_name_provided: "Okay. Ich merke mir, dass das {} ist"
33 | # #message_name_provided: "Okay. I will remember that this is {}"
34 | # message_name_provided_callback: "{} sagte, dass dies {} ist."
35 | # #message_name_provided_callback: "{} said that this is {}"
36 | # dependencies:
37 | # - Notifier
--------------------------------------------------------------------------------
/globals.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 |
4 | def random_arg(argList):
5 | ############################################
6 | # pick a random text from a list
7 | ############################################
8 | if isinstance(argList, list):
9 | text = random.choice(argList)
10 | else:
11 | text = argList
12 | return text
13 |
--------------------------------------------------------------------------------
/heartbeat/heartbeat.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | from requests.exceptions import HTTPError
3 |
4 | #
5 | # App which sets a homeassistant entity as a heartbeat to check for threadstarvation etc
6 | #
7 | # Args:
8 | # sensor: sensor.appdaemon_heartbeat
9 | #
10 | # Release Notes
11 | #
12 | # Version 1.1:
13 | # Set start to None run_minutely will run after 1 minute
14 | #
15 | # Version 1.0:
16 | # Initial Version
17 |
18 |
19 | class Heartbeat(hass.Hass):
20 | def initialize(self):
21 | self.timer_handle_list = []
22 |
23 | self.sensor = self.args["sensor"]
24 |
25 | self.heartbeat(None)
26 |
27 | self.timer_handle_list.append(self.run_minutely(self.heartbeat, start=None))
28 |
29 | def heartbeat(self, kwargs):
30 | try:
31 | self.set_state(self.sensor, state=str(self.time()))
32 | self.log("Heartbeat", level="DEBUG")
33 | except HTTPError as exception:
34 | self.log(
35 | "Error trying to set entity. Will try again in 5s. Error: {}".format(
36 | exception
37 | ),
38 | level="WARNING",
39 | )
40 | self.timer_handle_list.append(self.run_in(self.heartbeat, 5))
41 |
42 | def terminate(self):
43 | for timer_handle in self.timer_handle_list:
44 | self.cancel_timer(timer_handle)
45 |
--------------------------------------------------------------------------------
/heartbeat/heartbeat.yaml:
--------------------------------------------------------------------------------
1 | heartbeat:
2 | module: heartbeat
3 | class: Heartbeat
4 | sensor: sensor.appdaemon_heartbeat
--------------------------------------------------------------------------------
/homeArrivalNotifier/homeArrivalNotifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App to send a notification if someone arrives at home
5 | #
6 | # Args:
7 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
8 | # input_boolean: input boolean which holds the information of someone is home or not
9 | # notify_name: Who to notify
10 | # user_name: name to use in notification message
11 | # zone_name: Name of the zone
12 | # message: message to use in notification
13 | # Release Notes
14 | #
15 | # Version 1.4.1:
16 | # Use consistent message variable
17 | #
18 | # Version 1.4:
19 | # use Notify App
20 | #
21 | # Version 1.3:
22 | # message now directly in own yaml instead of message module
23 | #
24 | # Version 1.2:
25 | # Added app_switch
26 | #
27 | # Version 1.1:
28 | # Added user_name
29 | #
30 | # Version 1.0:
31 | # Initial Version
32 |
33 |
34 | class HomeArrivalNotifier(hass.Hass):
35 | def initialize(self):
36 | self.listen_state_handle_list = []
37 |
38 | self.app_switch = self.args["app_switch"]
39 | self.zone_name = self.args["zone_name"]
40 | self.input_boolean = self.args["input_boolean"]
41 | self.notify_name = self.args["notify_name"]
42 | self.user_name = self.args["user_name"]
43 | self.message = self.args["message"]
44 |
45 | self.notifier = self.get_app("Notifier")
46 |
47 | self.listen_state_handle_list.append(
48 | self.listen_state(self.state_change, self.input_boolean)
49 | )
50 |
51 | def state_change(self, entity, attribute, old, new, kwargs):
52 | if self.get_state(self.app_switch) == "on":
53 | if new != "" and new != old:
54 | self.log("{} changed from {} to {}".format(entity, old, new))
55 | if new == "on":
56 | self.log(
57 | "{} arrived at {}".format(self.notify_name, self.zone_name)
58 | )
59 | self.notifier.notify(
60 | self.notify_name, self.message.format(self.user_name)
61 | )
62 |
63 | def terminate(self):
64 | for listen_state_handle in self.listen_state_handle_list:
65 | self.cancel_listen_state(listen_state_handle)
66 |
--------------------------------------------------------------------------------
/homeArrivalNotifier/homeArrivalNotifier.yaml:
--------------------------------------------------------------------------------
1 | #Notification if user one arrives at home
2 | # homeArrivalNotifierUserOne:
3 | # module: homeArrivalNotifier
4 | # class: HomeArrivalNotifier
5 | # app_switch: input_boolean.home_arrival_notifier_user_one
6 | # input_boolean: input_boolean.user_one_home
7 | # notify_name: group_notifications
8 | # user_name: Kevin
9 | # zone_name: Home
10 | # message: "Willkommen zu Hause {}."
11 | # #message: "Welcome Home {}."
12 | # dependencies:
13 | # - Notifier
14 |
15 | # #Notification if user two arrives at home
16 | # homeArrivalNotifierUserTwo:
17 | # module: homeArrivalNotifier
18 | # class: HomeArrivalNotifier
19 | # app_switch: input_boolean.home_arrival_notifier_user_two
20 | # input_boolean: input_boolean.user_two_home
21 | # notify_name: group_notifications
22 | # user_name: Sina
23 | # zone_name: Home
24 | # message: "Willkommen zu Hause {}."
25 | # #message: "Welcome Home {}."
26 | # dependencies:
27 | # - Notifier
--------------------------------------------------------------------------------
/images/alarmClock.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/alarmClock.PNG
--------------------------------------------------------------------------------
/images/dishWasherNotify.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/dishWasherNotify.PNG
--------------------------------------------------------------------------------
/images/failedLogin.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/failedLogin.PNG
--------------------------------------------------------------------------------
/images/googleTravelTimes.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/googleTravelTimes.PNG
--------------------------------------------------------------------------------
/images/logo-pretty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/logo-pretty.png
--------------------------------------------------------------------------------
/images/logo-round-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/logo-round-192x192.png
--------------------------------------------------------------------------------
/images/next_appoint_leave_modifier.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/next_appoint_leave_modifier.PNG
--------------------------------------------------------------------------------
/images/next_appoint_leave_modifier_notification.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/next_appoint_leave_modifier_notification.PNG
--------------------------------------------------------------------------------
/images/notifyOfActionWhenAway.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/notifyOfActionWhenAway.PNG
--------------------------------------------------------------------------------
/images/plantWateringReminder.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/plantWateringReminder.PNG
--------------------------------------------------------------------------------
/images/plantWateringReminderAcknowledged.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/plantWateringReminderAcknowledged.PNG
--------------------------------------------------------------------------------
/images/roggenNotify.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/roggenNotify.PNG
--------------------------------------------------------------------------------
/images/ventilatorAutomation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/ventilatorAutomation.PNG
--------------------------------------------------------------------------------
/images/washingMachineStart.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eifinger/appdaemon-scripts/0a8e19f5616bb71049907f53fdd818aea68269c7/images/washingMachineStart.PNG
--------------------------------------------------------------------------------
/isHomeDeterminer/isHomeDeterminer.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import globals
3 |
4 | #
5 | # App to
6 | #
7 | # Args:
8 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
9 | # input_booleans: list of input boolean which determine if a user is home
10 | # ishome: input boolean which determins if someone is home
11 | # message: message to use in notification
12 | # Release Notes
13 | #
14 | # Version 1.3:
15 | # message now a list
16 | #
17 | # Version 1.2:
18 | # message now directly in own yaml instead of message module
19 | #
20 | # Version 1.1:
21 | # Added app_switch
22 | #
23 | # Version 1.0:
24 | # Initial Version
25 |
26 |
27 | class IsHomeDeterminer(hass.Hass):
28 | def initialize(self):
29 | self.listen_state_handle_list = []
30 |
31 | self.app_switch = self.args["app_switch"]
32 | self.ishome = self.args["ishome"]
33 | self.input_booleans = self.args["input_booleans"].split(",")
34 | self.message = self.args["message"]
35 |
36 | if self.get_state(self.app_switch) == "on":
37 | for input_boolean in self.input_booleans:
38 | self.log(
39 | "{} is {}".format(input_boolean, self.get_state(input_boolean))
40 | )
41 | self.listen_state_handle_list.append(
42 | self.listen_state(self.state_change, input_boolean)
43 | )
44 | if (
45 | self.get_state(input_boolean) == "on"
46 | and self.get_state(self.ishome) == "off"
47 | ):
48 | self.turn_on(self.ishome)
49 | self.log("Setting {} to on".format(self.ishome))
50 | if (
51 | self.get_state(input_boolean) == "off"
52 | and self.get_state(self.ishome) == "on"
53 | ):
54 | if self.are_others_away(input_boolean):
55 | self.turn_off(self.ishome)
56 | self.log("Setting {} to off".format(self.ishome))
57 | notify_message = globals.random_arg(self.message)
58 | self.log("notify_messsage: {}".format(notify_message))
59 | self.call_service(
60 | "notify/group_notifications", message=notify_message
61 | )
62 |
63 | def state_change(self, entity, attribute, old, new, kwargs):
64 | if self.get_state(self.app_switch) == "on":
65 | if new != "" and new != old:
66 | self.log("{} changed from {} to {}".format(entity, old, new))
67 | if new == "on":
68 | self.turn_on(self.ishome)
69 | self.log("Setting {} to on".format(self.ishome))
70 | if new == "off":
71 | if self.are_others_away(entity):
72 | self.turn_off(self.ishome)
73 | self.log("Setting {} to off".format(self.ishome))
74 | notify_message = globals.random_arg(self.message)
75 | self.log("notify_messsage: {}".format(notify_message))
76 | self.call_service(
77 | "notify/group_notifications", message=notify_message
78 | )
79 |
80 | def are_others_away(self, entity):
81 | self.log("Entity: {}".format(entity))
82 | for input_boolean in self.input_booleans:
83 | self.log("{} is {}".format(input_boolean, self.get_state(input_boolean)))
84 | if input_boolean == entity:
85 | pass
86 | elif self.get_state(input_boolean) == "on":
87 | self.log("{} is still at on".format(input_boolean))
88 | return False
89 | self.log("Everybody not home")
90 | return True
91 |
92 | def terminate(self):
93 | for listen_state_handle in self.listen_state_handle_list:
94 | self.cancel_listen_state(listen_state_handle)
95 |
--------------------------------------------------------------------------------
/isHomeDeterminer/isHomeDeterminer.yaml:
--------------------------------------------------------------------------------
1 | # #Control the isHome state. Determines if someone is home or all persons are away
2 | # isHomeDeterminer:
3 | # module: isHomeDeterminer
4 | # class: IsHomeDeterminer
5 | # app_switch: input_boolean.is_home_determiner
6 | # ishome: input_boolean.is_home
7 | # input_booleans: input_boolean.user_one_home,input_boolean.user_two_home
8 | # message:
9 | # - "Es ist keiner mehr zu Hause."
10 | # - "Keiner mehr da? Panda Party!"
11 | # - "Ich passe auf die Wohnung auf, einen schönen Tag"
12 | # - "Tschüss, bis nachher"
13 | # #message: "Everyone left home. Setting isHome to off"
14 | # global_dependencies:
15 | # - globals
16 | # - secrets
--------------------------------------------------------------------------------
/isUserHomeDeterminer/isUserHomeDeterminer.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 | from requests.exceptions import HTTPError
4 |
5 | #
6 | # App to toggle an input boolean when a person enters or leaves home.
7 | # This is determined based on a combination of a GPS device tracker and the door sensor.
8 | #
9 | # - If the door sensor opens and the device_tracker changed to "home" in the last self.delay minutes
10 | # this means someone got home
11 | # - If the door sensor opens and the device_tracker changes to "not_home" after that
12 | # this means someone left home
13 | #
14 | # Args:
15 | #
16 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
17 | # input_boolean: input_boolean which shows if someone is home e.g. input_boolean.isHome
18 | # device_tracker: device tracker or person of the user to track e.g. device_tracker.simon
19 | # door_sensor: Door sensor which indicated the front door opened e.g. binary_sensor.door_window_sensor_158d000126a57b
20 | #
21 | # Release Notes
22 | #
23 | # Version 1.5:
24 | # Wait to leave home until door is opened again
25 | #
26 | # Version 1.4.3:
27 | # check for listen_state_callback == None before triggering again
28 | #
29 | # Version 1.4.2:
30 | # cancel listen callback only when its not None
31 | #
32 | # Version 1.4.1:
33 | # fix for 503, fix for listen callback not being cancelled correctly
34 | #
35 | # Version 1.4:
36 | # message now directly in own yaml instead of message module
37 | #
38 | # Version 1.3:
39 | # Added app_switch
40 | #
41 | # Version 1.2:
42 | # Change checking after a delay to a event based system
43 | #
44 | # Version 1.1:
45 | # Set when initializing (also when HA restarts)
46 | #
47 | # Version 1.0:
48 | # Initial Version
49 |
50 |
51 | class IsUserHomeDeterminer(hass.Hass):
52 | def initialize(self):
53 | self.listen_state_handle_list = []
54 | self.timer_handle_list = []
55 |
56 | self.delay = 600
57 |
58 | self.app_switch = self.args["app_switch"]
59 | self.input_boolean = self.args["input_boolean"]
60 | self.device_tracker = self.args["device_tracker"]
61 | self.door_sensor = self.args["door_sensor"]
62 |
63 | device_tracker_state = self.get_state(self.device_tracker, attribute="all")
64 | if self.get_state(self.app_switch) == "on":
65 | if device_tracker_state["state"] == "home":
66 | self.log("User is home")
67 | self.timer_handle_list.append(
68 | self.run_in(
69 | self.turn_on_callback, 0, turn_on_entity=self.input_boolean
70 | )
71 | )
72 | else:
73 | self.log("User is not home")
74 | self.timer_handle_list.append(
75 | self.run_in(
76 | self.turn_off_callback, 0, turn_off_entity=self.input_boolean
77 | )
78 | )
79 |
80 | self.listen_state_handle_list.append(
81 | self.listen_state(self.state_change, self.door_sensor)
82 | )
83 |
84 | self.listen_state_handle = None
85 |
86 | def state_change(self, entity, attribute, old, new, kwargs):
87 | if self.get_state(self.app_switch) == "on":
88 | if new != "" and new != old:
89 | self.log("{} changed from {} to {}".format(entity, old, new))
90 | if new == "on" and old == "off":
91 | self.cancel_listen_state_callback(None)
92 | device_tracker_state = self.get_state(
93 | self.device_tracker, attribute="all"
94 | )
95 | self.log("device_tracker_state: {}".format(device_tracker_state),)
96 | last_changed = device_tracker_state["last_changed"]
97 | self.log("last_changed: {}".format(last_changed))
98 | # User got home: Device tracker changed to home before door sensor triggered
99 | if device_tracker_state["state"] == "home" and (
100 | (
101 | datetime.datetime.now(datetime.timezone.utc)
102 | - self.convert_utc(last_changed)
103 | )
104 | < datetime.timedelta(seconds=self.delay)
105 | ):
106 | self.log("User got home")
107 | self.turn_on(self.input_boolean)
108 | # User got home: Device tracker is still not home.
109 | # Wait if it changes to home in the next self.delay seconds
110 | elif device_tracker_state["state"] != "home":
111 | self.log("Wait for device tracker to change to 'home'")
112 | self.listen_state_handle = self.listen_state(
113 | self.check_if_user_got_home, self.device_tracker
114 | )
115 | self.listen_state_handle_list.append(self.listen_state_handle)
116 | self.timer_handle_list.append(
117 | self.run_in(self.cancel_listen_state_callback, self.delay)
118 | )
119 | # User left home: Device tracker is still home.
120 | # Wait if it changes to not_home
121 | elif device_tracker_state["state"] == "home":
122 | self.log("Wait for device tracker to change to 'not_home'")
123 | self.listen_state_handle = self.listen_state(
124 | self.check_if_user_left_home, self.device_tracker
125 | )
126 | self.listen_state_handle_list.append(self.listen_state_handle)
127 |
128 | def cancel_listen_state_callback(self, kwargs):
129 | if self.listen_state_handle is not None:
130 | self.log(
131 | "Timeout while waiting for user to get/leave home. Cancel listen_state"
132 | )
133 | if self.listen_state_handle in self.listen_state_handle_list:
134 | self.listen_state_handle_list.remove(self.listen_state_handle)
135 | self.cancel_listen_state(self.listen_state_handle)
136 | self.listen_state_handle = None
137 |
138 | def check_if_user_left_home(self, entity, attribute, old, new, kwargs):
139 | if new != "home":
140 | self.log("User left home")
141 | if self.listen_state_handle in self.listen_state_handle_list:
142 | self.listen_state_handle_list.remove(self.listen_state_handle)
143 | if self.listen_state_handle != None:
144 | self.cancel_listen_state(self.listen_state_handle)
145 | self.listen_state_handle = None
146 | self.timer_handle_list.append(
147 | self.run_in(
148 | self.turn_off_callback, 1, turn_off_entity=self.input_boolean
149 | )
150 | )
151 |
152 | def check_if_user_got_home(self, entity, attribute, old, new, kwargs):
153 | if new == "home":
154 | self.log("User got home")
155 | if self.listen_state_handle in self.listen_state_handle_list:
156 | self.listen_state_handle_list.remove(self.listen_state_handle)
157 | if self.listen_state_handle is not None:
158 | self.cancel_listen_state(self.listen_state_handle)
159 | self.listen_state_handle = None
160 | self.timer_handle_list.append(
161 | self.run_in(
162 | self.turn_on_callback, 1, turn_on_entity=self.input_boolean
163 | )
164 | )
165 |
166 | def turn_on_callback(self, kwargs):
167 | """This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting"""
168 | try:
169 | self.turn_on(kwargs["turn_on_entity"])
170 | except HTTPError as exception:
171 | self.log(
172 | "Error trying to turn on entity. Will try again in 1s. Error: {}".format(
173 | exception
174 | ),
175 | level="WARNING",
176 | )
177 | self.timer_handle_list.append(
178 | self.run_in(
179 | self.turn_on_callback, 1, turn_on_entity=kwargs["turn_on_entity"]
180 | )
181 | )
182 |
183 | def turn_off_callback(self, kwargs):
184 | """This is needed because the turn_off command can result in a HTTP 503 when homeassistant is restarting"""
185 | try:
186 | self.turn_off(kwargs["turn_off_entity"])
187 | except HTTPError as exception:
188 | self.log(
189 | "Error trying to turn off entity. Will try again in 1s. Error: {}".format(
190 | exception
191 | ),
192 | level="WARNING",
193 | )
194 | self.timer_handle_list.append(
195 | self.run_in(
196 | self.turn_off_callback, 1, turn_off_entity=kwargs["turn_off_entity"]
197 | )
198 | )
199 |
200 | def terminate(self):
201 | for listen_state_handle in self.listen_state_handle_list:
202 | self.cancel_listen_state(listen_state_handle)
203 |
204 | for timer_handle in self.timer_handle_list:
205 | self.cancel_timer(timer_handle)
206 |
--------------------------------------------------------------------------------
/isUserHomeDeterminer/isUserHomeDeterminer.yaml:
--------------------------------------------------------------------------------
1 | # #Determine if user one gets/leaves home
2 | # isUserHomeDeterminerUserOne:
3 | # module: isUserHomeDeterminer
4 | # class: IsUserHomeDeterminer
5 | # app_switch: input_boolean.is_user_home_determiner_user_one
6 | # input_boolean: input_boolean.user_one_home
7 | # device_tracker: person.kevin
8 | # door_sensor: binary_sensor.contact_door
9 |
10 | # #Determine if user two gets/leaves home
11 | # isUserHomeDeterminerUserTwo:
12 | # module: isUserHomeDeterminer
13 | # class: IsUserHomeDeterminer
14 | # app_switch: input_boolean.is_user_home_determiner_user_two
15 | # input_boolean: input_boolean.user_two_home
16 | # device_tracker: person.sina
17 | # door_sensor: binary_sensor.contact_door
--------------------------------------------------------------------------------
/leavingZoneNotifier/leavingZoneNotifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App to notify if user_one is leaving a zone.
6 | # User had to be in that zone 3 minutes before
7 | # in order for the notification to be triggered
8 | #
9 | # Args:
10 | # app_switch: on/off switch for this app.
11 | # example: input_boolean.turn_fan_on_when_hot
12 | # device: Device to track
13 | # user_name: Name of the user used in the notification message
14 | # delay: seconds to wait before notifying. Maybe user returns to zone.
15 | # This should be too small to avoid false positives from your tracker.
16 | # I am using GPS Logger on Android and sometimes my device switches
17 | # from work to home and 2 minutes later back. example: 120
18 | # lingering_time: time a user has to be in a zone to trigger this app.
19 | # example: 3600
20 | # zone: zone name from which the user is leaving
21 | # notify_name: Who to notify. example: group_notifications
22 | # message: localized message to use in notification
23 | # travel_time_sensor (optional): Sensor showing the travel time home.
24 | # example: sensor.travel_time_home_user_one
25 | # travel_time_sensor_message (optional): Additional notify message.
26 | #
27 | # Release Notes
28 | #
29 | # Version 1.11:
30 | # Catch new state might be None
31 | #
32 | # Version 1.10:
33 | # Catch old state might be None during startup
34 | #
35 | # Version 1.9:
36 | # PEP8 style and log message when updating travel_time_sensor
37 | #
38 | # Version 1.8:
39 | # Add travel time in notification message
40 | #
41 | # Version 1.7.1:
42 | # Fix delay in notify message. Only input the minutes not the tuple
43 | #
44 | # Version 1.7:
45 | # use Notify App
46 | #
47 | # Version 1.6:
48 | # notify message includes delay
49 | #
50 | # Version 1.5:
51 | # message now directly in own yaml instead of message module
52 | #
53 | # Version 1.4:
54 | # additional note for delay and better handling of zone_entered for
55 | # false positives
56 | #
57 | # Version 1.3:
58 | # delay and lingering_time now as args
59 | #
60 | # Version 1.2:
61 | # Added app_switch
62 | #
63 | # Version 1.1:
64 | # Rework without proximity
65 | #
66 | # Version 1.0:
67 | # Initial Version
68 |
69 |
70 | class LeavingZoneNotifier(hass.Hass):
71 | def initialize(self):
72 |
73 | self.listen_state_handle_list = []
74 | self.timer_handle_list = []
75 |
76 | self.app_switch = self.args["app_switch"]
77 | self.user_name = self.args["user_name"]
78 | self.zone = self.args["zone"]
79 | self.notify_name = self.args["notify_name"]
80 | self.device = self.args["device"]
81 | # 'lingering_time' the time a user has to stay in a zone
82 | # for this app to trigger
83 | self.lingering_time = self.args["lingering_time"]
84 | self.delay = self.args["delay"]
85 | self.message = self.args["message"]
86 | self.travel_time_sensor = self.args.get("travel_time_sensor")
87 | self.travel_time_sensor_message = self.args.get("travel_time_sensor_message")
88 |
89 | self.user_entered_zone = None
90 | self.false_positive = False
91 |
92 | self.notifier = self.get_app("Notifier")
93 |
94 | self.listen_state_handle_list.append(
95 | self.listen_state(self.zone_state_change, self.device, attribute="all")
96 | )
97 |
98 | def zone_state_change(self, entity, attributes, old, new, kwargs):
99 | """Check if user entered or left a zone."""
100 | if self.get_state(self.app_switch) == "on":
101 | if new is not None:
102 | last_changed = self.convert_utc(new["last_changed"])
103 | if old is not None:
104 | old_state = old["state"]
105 | self.log(
106 | "Zone of {} changed from {} to {}.".format(
107 | self.friendly_name(entity), old_state, new["state"]
108 | ),
109 | )
110 | if (
111 | new["state"] == self.zone
112 | and old_state != self.zone
113 | and self.false_positive is False
114 | ):
115 | self.log("Setting user_entered_zone to {}".format(last_changed))
116 | self.user_entered_zone = last_changed
117 | if old_state == self.zone and new["state"] != self.zone:
118 | if self.user_entered_zone is None or (
119 | last_changed - self.user_entered_zone
120 | >= datetime.timedelta(seconds=self.lingering_time)
121 | ):
122 | self.log(
123 | "Zone of {} changed from {} to {}. Wait {} seconds until notification.".format(
124 | self.friendly_name(entity),
125 | old_state,
126 | new["state"],
127 | self.delay,
128 | )
129 | )
130 | self.timer_handle_list.append(
131 | self.run_in(self.notify_user, self.delay, old_zone=old)
132 | )
133 | self.false_positive = True
134 | self.log("Setting false_positive to {}".format(self.false_positive))
135 |
136 | def notify_user(self, kwargs):
137 | # Check if user did not come back to the zone in the meantime
138 | if self.get_state(self.device) != kwargs["old_zone"]:
139 | if self.travel_time_sensor is not None:
140 | self.log(
141 | "Updating travel_time_sensor: {}".format(self.travel_time_sensor)
142 | )
143 |
144 | self.call_service(
145 | "homeassistant/update_entity", entity_id=self.travel_time_sensor
146 | )
147 |
148 | self.timer_handle_list.append(self.run_in(self.notify_user_callback, 2))
149 | else:
150 | self.log("self.travel_time_sensor is not None")
151 | self.log("Notify user")
152 | self.notifier.notify(
153 | self.notify_name,
154 | self.message.format(
155 | self.user_name, self.zone, divmod(self.delay, 60)[0]
156 | ),
157 | )
158 | self.false_positive = False
159 | self.log("Setting false_positive to {}".format(self.false_positive))
160 |
161 | def notify_user_callback(self, kwargs):
162 | self.log("Notify user")
163 | self.notifier.notify(
164 | self.notify_name,
165 | self.message.format(self.user_name, self.zone, divmod(self.delay, 60)[0])
166 | + self.travel_time_sensor_message.format(
167 | self.get_state(self.travel_time_sensor)
168 | ),
169 | )
170 | self.false_positive = False
171 | self.log("Setting false_positive to {}".format(self.false_positive))
172 |
173 | def terminate(self):
174 | for listen_state_handle in self.listen_state_handle_list:
175 | self.cancel_listen_state(listen_state_handle)
176 |
177 | for timer_handle in self.timer_handle_list:
178 | self.cancel_timer(timer_handle)
179 |
--------------------------------------------------------------------------------
/leavingZoneNotifier/leavingZoneNotifier.yaml:
--------------------------------------------------------------------------------
1 | # leavingWorkNotifierUserOne:
2 | # module: leavingZoneNotifier
3 | # class: LeavingZoneNotifier
4 | # app_switch: input_boolean.leaving_work_notifier_user_one
5 | # device: person.kevin
6 | # user_name: Kevin
7 | # lingering_time: 3600
8 | # delay: 120
9 | # zone: Arbeit
10 | # notify_name: group_notifications
11 | # message: "{} hat {} vor {} Minuten verlassen."
12 | # #message: "{} left {} {} minutes ago"
13 | # travel_time_sensor: sensor.travel_time_home_user_one
14 | # travel_time_sensor_message: "Es dauert circa {} Minuten bis nach Hause."
15 | # #travel_time_sensor_message: "The travel time is {}."
16 | # dependencies:
17 | # - Notifier
18 |
19 | # leavingWorkNotifierUserTwo:
20 | # module: leavingZoneNotifier
21 | # class: LeavingZoneNotifier
22 | # app_switch: input_boolean.leaving_work_notifier_user_two
23 | # device: person.sina
24 | # user_name: Sina
25 | # lingering_time: 3600
26 | # delay: 120
27 | # zone: !secret friendly_name_work_user_two
28 | # notify_name: group_notifications
29 | # message: "{} hat {} vor {} Minuten verlassen."
30 | # #message: "{} left {} {} minutes ago"
31 | # travel_time_sensor: sensor.travel_time_home_user_two
32 | # travel_time_sensor_message: "Es dauert circa {} Minuten bis nach Hause."
33 | # #travel_time_sensor_message: "The travel time is {}."
34 | # dependencies:
35 | # - Notifier
36 |
37 | # leavingElmoNotifierUserTwo:
38 | # module: leavingZoneNotifier
39 | # class: LeavingZoneNotifier
40 | # app_switch: input_boolean.leaving_elmo_notifier_user_two
41 | # device: person.sina
42 | # user_name: Sina
43 | # lingering_time: 3600
44 | # delay: 120
45 | # zone: Elmo
46 | # notify_name: group_notifications
47 | # message: "{} hat {} vor {} Minuten verlassen."
48 | # #message: "{} left {} {} minutes ago"
49 | # travel_time_sensor: sensor.travel_time_home_user_two
50 | # travel_time_sensor_message: "Es dauert circa {} Minuten bis nach Hause."
51 | # #travel_time_sensor_message: "The travel time is {}."
52 | # dependencies:
53 | # - Notifier
--------------------------------------------------------------------------------
/motionTrigger/motionTrigger.yaml:
--------------------------------------------------------------------------------
1 | # bedroomMotionTrigger:
2 | # module: motionTrigger
3 | # class: MotionTrigger
4 | # app_switch: input_boolean.bedroom_motion_trigger
5 | # sensor: binary_sensor.presence_bedroom
6 | # entity_on: light.bedroom_yeelight
7 | # sensor_type: deconz
8 | # after_sundown: True
9 | # turn_on_constraint_entities_off: input_boolean.sleepmode
10 |
11 | # studyroomMotionTrigger:
12 | # module: motionTrigger
13 | # class: MotionTrigger
14 | # app_switch: input_boolean.studyroom_motion_trigger
15 | # sensor: binary_sensor.presence_studyroom
16 | # entity_on: light.philips_miio_light_bulb
17 | # sensor_type: deconz
18 | # after_sundown: True
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/newWifiDeviceNotify/newWifiDeviceNotify.yaml:
--------------------------------------------------------------------------------
1 | newWifiDeviceNotify:
2 | module: newWifiDeviceNotify
3 | class: DeviceNotify
4 | notify_name: group_notifications
5 | user_id: !secret telegram_user_id
6 | message: "Unbekanntes Gerät entdeckt. Hostname: {}. MAC: {}."
7 | #message: "Unknown device connected. Hostname: {}. MAC: {}"
8 | #fritzbox_url: fritzbox_url
9 | #fritzbox_user: ''
10 | #fritzbox_password: fritzbox_password
11 | #fritzbox_profile_name: 'Unbeschränkt'
12 | #fritzbox_message_allow_access: "Soll ich das Gerät ins Internet lassen?"
13 | #fritzbox_message_allow_access: "Should I let the device access the Internet?"
14 | #fritzbox_message_access_allowed: "Großzügig wie ich bin, habe ich das Gerät ins Internet gelassen"
15 | #fritzbox_message_access_allowed: "I have let the device access the internet. How kind of me!"
16 | #fritzbox_message_access_blocked: "Ich habe das Gerät vor den Schrecken des Internets bewahrt"
17 | #fritzbox_message_access_blocked: "I have saved the device from the dangers of the Internet"
18 | dependencies:
19 | - Notifier
--------------------------------------------------------------------------------
/newWifiDeviceNotify/requirements.txt:
--------------------------------------------------------------------------------
1 | fritz_switch_profiles >= 1.0.0
--------------------------------------------------------------------------------
/nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App which notifies the user to start to the next appointment
6 | #
7 | #
8 | # Args:
9 | # sensor: sensor to watch. example: sensor.calc_leave_time
10 | # notify_input_boolean: input_boolean determining whether to notify. example: input_boolean.announce_time_to_leave
11 | # notify_name: Who to notify. example: group_notifications
12 | # destination_name_sensor: Sensor which holds the Destination name to use in notification. example: sensor.cal_next_appointment_location
13 | # travel_time_sensor: sensor which holds the travel time. example: sensor.travel_time_next_appointment_location
14 | # message: message to use in notification
15 | #
16 | # Release Notes
17 | #
18 | # Version 1.5:
19 | # Catch None when Home Assistant is still starting
20 | #
21 | # Version 1.4.2:
22 | # Fix notification for location_name "None"
23 | #
24 | # Version 1.4.1:
25 | # Fix google maps url message
26 | #
27 | # Version 1.4:
28 | # Don't include Google Maps Link in Notification for Alexa
29 | #
30 | # Version 1.3:
31 | # Also notify when Notification time is in the past
32 | #
33 | # Version 1.2:
34 | # use Notify App
35 | #
36 | # Version 1.1:
37 | # Using globals, message now directly in own yaml instead of message module
38 | #
39 | # Version 1.0:
40 | # Initial Version
41 |
42 |
43 | class NextAppointmentLeaveNotifier(hass.Hass):
44 | def initialize(self):
45 |
46 | self.listen_state_handle_list = []
47 |
48 | self.sensor = self.args["sensor"]
49 | self.notify_input_boolean = self.args["notify_input_boolean"]
50 | self.notify_name = self.args["notify_name"]
51 | self.destination_name_sensor = self.args["destination_name_sensor"]
52 | self.travel_time_sensor = self.args["travel_time_sensor"]
53 | self.message = self.args["message"]
54 | self.message_google_link = self.args["message_google_link"]
55 |
56 | self.timer_handle = None
57 |
58 | self.google_source_url = "http://maps.google.com/maps?q="
59 |
60 | self.notifier = self.get_app("Notifier")
61 |
62 | # Used to check of user got already notified for this event
63 | self.location_of_last_notified_event = ""
64 | self.set_timer_handle()
65 |
66 | self.listen_state_handle_list.append(
67 | self.listen_state(self.state_change, self.sensor)
68 | )
69 |
70 | def state_change(self, entity, attributes, old, new, kwargs):
71 | try:
72 | self.cancel_timer(self.timer_handle)
73 | self.timer_handle = None
74 | except AttributeError:
75 | # Timer was not set
76 | pass
77 | self.set_timer_handle()
78 |
79 | def set_timer_handle(self):
80 | destination_name = self.get_state(self.destination_name_sensor)
81 | self.log(f"destination_name_sensor: {destination_name}")
82 | if self.get_state(self.sensor) != None:
83 | if destination_name != "unknown" and destination_name != "None":
84 | notification_time = datetime.datetime.strptime(
85 | self.get_state(self.sensor), "%Y-%m-%d %H:%M"
86 | )
87 | if self.get_state(self.travel_time_sensor) != "unknown":
88 | try:
89 | self.timer_handle = self.run_at(
90 | self.notify_user, notification_time
91 | )
92 | self.log(f"Will notify at {notification_time}")
93 | except ValueError:
94 | self.log("Notification time is in the past")
95 | self.timer_handle = self.run_at(
96 | self.notify_user, datetime.datetime.now()
97 | )
98 |
99 | def notify_user(self, *kwargs):
100 | if self.get_state(self.notify_input_boolean) == "on":
101 | location_name = self.get_state(self.destination_name_sensor)
102 | if location_name != "None":
103 | if self.location_of_last_notified_event == location_name:
104 | self.log(f"User already got notified for {location_name}")
105 | else:
106 | google_maps_url = self.google_source_url + location_name.replace(
107 | " ", "+"
108 | )
109 | self.log("Notify user")
110 | self.notifier.notify(
111 | self.notify_name,
112 | self.message.format(
113 | location_name, self.get_state(self.travel_time_sensor)
114 | ),
115 | )
116 | self.notifier.notify(
117 | self.notify_name,
118 | self.message_google_link.format(google_maps_url),
119 | useAlexa=False,
120 | )
121 | self.location_of_last_notified_event = location_name
122 | else:
123 | self.log(f"location_name: {location_name}")
124 | else:
125 | self.log("Notification is turned off")
126 |
127 | def terminate(self):
128 | for listen_state_handle in self.listen_state_handle_list:
129 | self.cancel_listen_state(listen_state_handle)
130 | if self.timer_handle is not None:
131 | self.cancel_timer(self.timer_handle)
132 |
--------------------------------------------------------------------------------
/nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.yaml:
--------------------------------------------------------------------------------
1 | # nextAppointmentLeaveNotifier:
2 | # module: nextAppointmentLeaveNotifier
3 | # class: NextAppointmentLeaveNotifier
4 | # sensor: sensor.calc_leave_time
5 | # notify_input_boolean: input_boolean.announce_time_to_leave
6 | # notify_name: Kevin
7 | # input_number: input_number.leave_time_offset
8 | # destination_name_sensor: sensor.cal_next_appointment_location
9 | # travel_time_sensor: sensor.travel_time_next_appointment_location
10 | # message: "Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten."
11 | # #message: "It's time to leave to {}. It will take {} minutes."
12 | # message_google_link: " Hier ist ein Google Maps Link: {}"
13 | # #message_google_link: " Here is a Google Maps Link: {}"
14 | # dependencies:
15 | # - Notifier
16 |
17 | # nextAppointmentLeaveNotifierUserTwo:
18 | # module: nextAppointmentLeaveNotifier
19 | # class: NextAppointmentLeaveNotifier
20 | # sensor: sensor.calc_leave_time_user_two
21 | # notify_input_boolean: input_boolean.announce_time_to_leave_user_two
22 | # notify_name: Sina
23 | # input_number: input_number.leave_time_offset_user_two
24 | # destination_name_sensor: sensor.cal_next_appointment_location_user_two
25 | # travel_time_sensor: sensor.travel_time_next_appointment_location_user_two
26 | # message: "Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten."
27 | # #message: "It's time to leave to {}. It will take {} minutes."
28 | # message_google_link: " Hier ist ein Google Maps Link: {}"
29 | # #message_google_link: " Here is a Google Maps Link: {}"
30 | # dependencies:
31 | # - Notifier
--------------------------------------------------------------------------------
/notifier/notifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # Centralizes messaging. Among other things, it will determine whether a user is at home and if yes in which room.
6 | # Then Alexa in that room will be used additionally to Telegram
7 | #
8 | # Args:
9 | # app_switch_alexa: mutes alexa. example:
10 | # alexa_tts: name of the notification service. example: alexa_media
11 | # alexa_media_player: media player entity of alexa to use. example: media_player.kevins_echo_dot_oben
12 | # user_location_sensors: sensors showing the location of users
13 | # alexa_to_location_mapping: mapping of which alexa device is used for which room
14 | #
15 | #
16 | #
17 | # Release Notes
18 | #
19 | # Version 1.5:
20 | # Allow multiple alexa_media_player
21 | #
22 | # Version 1.4:
23 | # Use type announce
24 | #
25 | # Version 1.3:
26 | # Use Version 1.2.1 of alexa_media_player
27 | #
28 | # Version 1.2.1:
29 | # Fix: Enqueue alexa messages
30 | #
31 | # Version 1.2:
32 | # Enqueue alexa messages
33 | #
34 | # Version 1.1:
35 | # Remove media_player constraints. If connected via bluetooth alexa can always be heard
36 | #
37 | # Version 1.0:
38 | # Initial Version
39 |
40 | __GROUP_NOTIFICATIONS__ = "group_notifications"
41 | __NOTIFY__ = "notify/"
42 | __WAIT_TIME__ = 5 # seconds
43 |
44 |
45 | class Notifier(hass.Hass):
46 | def initialize(self):
47 | self.timer_handle_list = []
48 |
49 | self.alexa_tts = self.args["alexa_tts"]
50 | self.alexa_media_player = self.args["alexa_media_player"].split(",")
51 | self.app_switch_alexa = self.args["app_switch_alexa"]
52 |
53 | self.last_alexa_notification_time = None
54 |
55 | def notify(self, notify_name, message, useAlexa=True, useTelegram=True):
56 | if useTelegram:
57 | self.log("Notifying via Telegram")
58 | self.call_service(__NOTIFY__ + notify_name, message=message)
59 | if useAlexa and self.get_state(self.app_switch_alexa) == "on":
60 | self.log("Notifying via Alexa")
61 | # check last message
62 | if self.last_alexa_notification_time is not None and (
63 | datetime.datetime.now() - self.last_alexa_notification_time
64 | < datetime.timedelta(seconds=__WAIT_TIME__)
65 | ):
66 | self.timer_handle_list.append(
67 | self.run_in(self.notify_callback, __WAIT_TIME__, message=message)
68 | )
69 | else:
70 | self.run_in(self.notify_callback, 0, message=message)
71 |
72 | def notify_callback(self, kwargs):
73 | self.last_alexa_notification_time = datetime.datetime.now()
74 | self.call_service(
75 | __NOTIFY__ + self.alexa_tts,
76 | data={"type": "announce", "method": "speak"},
77 | target=self.alexa_media_player,
78 | message=kwargs["message"],
79 | )
80 |
81 | def getAlexaDeviceForUserLocation(self, notify_name):
82 | if notify_name == __GROUP_NOTIFICATIONS__:
83 | return self.args["alexa_to_location_mapping"]["Wohnzimmer"]
84 | elif notify_name.lower() in self.args["user_location_sensors"]:
85 | location = self.get_state(
86 | self.args["user_location_sensors"][notify_name.lower()]
87 | )
88 | if location in self.args["alexa_to_location_mapping"]:
89 | return self.args["alexa_to_location_mapping"][location]
90 | else:
91 | return None
92 | else:
93 | self.log("Unknown notify_name: {}".format(notify_name))
94 | return None
95 |
96 | def terminate(self):
97 | for timer_handle in self.timer_handle_list:
98 | self.cancel_timer(timer_handle)
99 |
--------------------------------------------------------------------------------
/notifier/notifier.yaml:
--------------------------------------------------------------------------------
1 | Notifier:
2 | module: notifier
3 | class: Notifier
4 | app_switch_alexa: input_boolean.notifier_alexa
5 | alexa_tts: alexa_media
6 | alexa_media_player: media_player.kevins_echo_dot_oben,media_player.kevin_s_echo_dot_unten,media_player.kevins_echo,media_player.kevins_echo_dot
7 | user_location_sensors:
8 | kevin: sensor.location_user_one
9 | sina: sensor.location_user_two
10 | alexa_to_location_mapping:
11 | Wohnzimmer: media_player.kevins_echo_dot_oben
12 | Küche: media_player.kevins_echo_dot_oben
13 | Balkon Oben: media_player.kevins_echo_dot_oben
14 | Arbeitszimmer: media_player.kevin_s_echo_dot_unten
15 | Schlafzimmer: media_player.kevin_s_echo_dot_unten
--------------------------------------------------------------------------------
/notifyOfActionWhenAway/notifyOfActionWhenAway.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App to send notification when a sensor changes state
5 | #
6 | # Args:
7 | #
8 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
9 | # sensor: sensor to monitor. example: sensor.upstairs_smoke
10 | # isHome: input_boolean which shows if someone is home. example: input_boolean.isHome
11 | # isHome_delay: delay to wait for user to come home before notifying. example: 10
12 | #
13 | # Release Notes
14 | #
15 | # Version 1.3.1:
16 | # Use consistent message variable
17 | #
18 | # Version 1.3:
19 | # use Notify App
20 | #
21 | # Version 1.2:
22 | # message now directly in own yaml instead of message module
23 | #
24 | # Version 1.1:
25 | # Added isHome_delay
26 | #
27 | # Version 1.0:
28 | # Initial Version
29 |
30 |
31 | class NotifyOfActionWhenAway(hass.Hass):
32 | def initialize(self):
33 |
34 | self.listen_state_handle_list = []
35 | self.timer_handle_list = []
36 |
37 | self.app_switch = self.args["app_switch"]
38 | self.notify_name = self.args["notify_name"]
39 | self.isHome_delay = self.args["isHome_delay"]
40 | self.isHome = self.args["isHome"]
41 | self.message = self.args["message"]
42 |
43 | self.notifier = self.get_app("Notifier")
44 |
45 | for sensor in self.args["sensor"].split(","):
46 | self.listen_state_handle_list.append(
47 | self.listen_state(self.state_change, sensor)
48 | )
49 |
50 | def state_change(self, entity, attribute, old, new, kwargs):
51 | if self.get_state(self.app_switch) == "on":
52 | if new != "" and new != old:
53 | if self.get_state(self.isHome) == "off":
54 | if (
55 | entity.startswith("binary_sensor.motion_sensor")
56 | and new == "off"
57 | ):
58 | pass
59 | else:
60 | self.log(
61 | "Waiting {} seconds for someone to come home".format(
62 | self.isHome_delay
63 | )
64 | )
65 | self.timer_handle_list.append(
66 | self.run_in(
67 | self.notify_if_no_one_home,
68 | self.isHome_delay,
69 | sensor=entity,
70 | new=new,
71 | )
72 | )
73 |
74 | def notify_if_no_one_home(self, kwargs):
75 | if self.get_state(self.isHome) == "off":
76 | self.log(
77 | "{} changed to {}".format(
78 | self.friendly_name(kwargs["sensor"]), kwargs["new"]
79 | )
80 | )
81 | self.notifier.notify(
82 | self.notify_name,
83 | self.message.format(
84 | self.friendly_name(kwargs["sensor"]), kwargs["new"]
85 | ),
86 | useAlexa=False,
87 | )
88 |
89 | def terminate(self):
90 | for listen_state_handle in self.listen_state_handle_list:
91 | self.cancel_listen_state(listen_state_handle)
92 |
93 | for timer_handle in self.timer_handle_list:
94 | self.cancel_timer(timer_handle)
95 |
--------------------------------------------------------------------------------
/notifyOfActionWhenAway/notifyOfActionWhenAway.yaml:
--------------------------------------------------------------------------------
1 | notifyOfActionWhenAway:
2 | module: notifyOfActionWhenAway
3 | class: NotifyOfActionWhenAway
4 | app_switch: input_boolean.notify_of_action_when_away
5 | sensor: "binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\
6 | binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\
7 | binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\
8 | binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\
9 | binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\
10 | binary_sensor.presence_stairs,binary_sensor.presence_bathroom,binary_sensor.presence_lobby,\
11 | binary_sensor.presence_bedroom,binary_sensor.presence_kitchen,binary_sensor.presence_upper_stairs,\
12 | binary_sensor.contact_badfenster,binary_sensor.contact_upper_bathroom_window_tilted"
13 | isHome: input_boolean.is_home
14 | notify_name: group_notifications
15 | isHome_delay: 20
16 | message: "Alarm: {} ist gewechselt auf {}"
17 | #message: "Alarm: {} changed to {}"
18 | dependencies:
19 | - Notifier
--------------------------------------------------------------------------------
/plantWateringNotifier/plantWateringNotifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App which reminds you daily to water your plants if it won't rain
6 | #
7 | #
8 | # Args:
9 | #
10 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
11 | # rain_precip_sensor: sensor which shows rain probability. example: sensor.dark_sky_precip_probability
12 | # rain_precip_intensity_sensor: sensor which shows rain probability. example: sensor.dark_sky_precip_intensity
13 | # precip_type_sensor: sensor which shows precip type. example: sensor.dark_sky_precip
14 | # notify_name: Who to notify. example: group_notifications
15 | # user_id: The user_id of the telegram user to ask whether he knows an unknown face. example: 812391
16 | # reminder_acknowledged_entity: Input Boolean to store the information whether the user acknowledged the notification.
17 | # This prevents new notifications upon HA/Appdaemon restart.
18 | # example: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged
19 | # message: localized message to use in notification
20 | #
21 | # Release Notes
22 | #
23 | # Version 1.5.1:
24 | # Use consistent message variable
25 | #
26 | # Version 1.5:
27 | # use Notify App
28 | #
29 | # Version 1.4:
30 | # message now directly in own yaml instead of message module
31 | #
32 | # Version 1.3:
33 | # Added app_switch
34 | #
35 | # Version 1.2:
36 | # Update original message with information when the reminder was acknowledged
37 | #
38 | # Version 1.1:
39 | # Store reminder acknowledged in an input_boolean to prevent notifications after HA/Appdaemon restarts
40 | #
41 | # Version 1.0:
42 | # Initial Version
43 |
44 |
45 | class PlantWateringNotifier(hass.Hass):
46 | def initialize(self):
47 |
48 | self.timer_handle_list = []
49 | self.listen_event_handle_list = []
50 | self.listen_state_handle_list = []
51 |
52 | self.app_switch = self.args["app_switch"]
53 | self.rain_precip_sensor = self.args["rain_precip_sensor"]
54 | self.rain_precip_intensity_sensor = self.args["rain_precip_intensity_sensor"]
55 | self.precip_type_sensor = self.args["precip_type_sensor"]
56 | self.notify_name = self.args["notify_name"]
57 | self.user_id = self.args["user_id"]
58 | self.reminder_acknowledged_entity = self.args["reminder_acknowledged_entity"]
59 | self.message = self.args["message"]
60 | self.message_not_needed = self.args["message_not_needed"]
61 | self.message_evening = self.args["message_evening"]
62 |
63 | self.intensity_minimum = 2 # mm/h
64 | self.propability_minimum = 90 # %
65 |
66 | self.keyboard_callback = "/plants_watered"
67 |
68 | self.notifier = self.get_app("Notifier")
69 |
70 | self.reminder_acknowledged = self.get_state(self.reminder_acknowledged_entity)
71 |
72 | self.listen_event_handle_list.append(
73 | self.listen_event(self.receive_telegram_callback, "telegram_callback")
74 | )
75 |
76 | # Remind daily at 08:00
77 | self.timer_handle_list.append(
78 | self.run_daily(self.run_morning_callback, datetime.time(8, 0, 0))
79 | )
80 | # Remind daily at 18:00
81 | self.timer_handle_list.append(
82 | self.run_daily(self.run_evening_callback, datetime.time(18, 0, 0))
83 | )
84 |
85 | def run_morning_callback(self, kwargs):
86 | """Check if it will rain and if not remind the user to water the plants"""
87 | if self.get_state(self.app_switch) == "on":
88 | precip_propability = self.get_state(self.rain_precip_sensor)
89 | self.log("Rain Propability: {}".format(float(precip_propability)))
90 | precip_intensity = self.get_state(self.rain_precip_intensity_sensor)
91 | self.log("Rain Intensity: {}".format(float(precip_intensity)))
92 | precip_type = self.get_state(self.precip_type_sensor)
93 | self.log("Precip Type: {}".format(precip_type))
94 |
95 | if (
96 | precip_propability != None
97 | and precip_propability != ""
98 | and float(precip_propability) < self.propability_minimum
99 | and precip_intensity != None
100 | and precip_intensity != ""
101 | and float(precip_intensity) < self.intensity_minimum
102 | ):
103 | self.turn_off(self.reminder_acknowledged_entity)
104 | self.log("Setting reminder_acknowledged to: {}".format("off"))
105 | self.log("Reminding user")
106 | keyboard = [[("Hab ich gemacht", self.keyboard_callback)]]
107 | self.call_service(
108 | "telegram_bot/send_message",
109 | target=self.user_id,
110 | message=self.message.format(precip_propability),
111 | inline_keyboard=keyboard,
112 | )
113 |
114 | else:
115 | self.turn_on(self.reminder_acknowledged_entity)
116 | self.log("Setting reminder_acknowledged to: {}".format("off"))
117 | self.log("Notifying user")
118 | self.notifier.notify(
119 | self.notify_name,
120 | self.message_not_needed.format(
121 | precip_propability, precip_intensity
122 | ),
123 | )
124 |
125 | def run_evening_callback(self, kwargs):
126 | """Remind user to water the plants he if didn't acknowledge it"""
127 | if self.get_state(self.app_switch) == "on":
128 | if self.get_state(self.reminder_acknowledged_entity) == "off":
129 | self.log("Reminding user")
130 | self.call_service(
131 | "notify/" + self.notify_name, message=self.message_evening
132 | )
133 |
134 | def receive_telegram_callback(self, event_name, data, kwargs):
135 | """Event listener for Telegram callback queries."""
136 | assert event_name == "telegram_callback"
137 | data_callback = data["data"]
138 | callback_id = data["id"]
139 | chat_id = data["chat_id"]
140 | message_id = data["message"]["message_id"]
141 | text = data["message"]["text"]
142 | self.log("callback data: {}".format(data), level="DEBUG")
143 |
144 | if data_callback == self.keyboard_callback: # Keyboard editor:
145 | # Answer callback query
146 | self.call_service(
147 | "telegram_bot/answer_callback_query",
148 | message="Super!",
149 | callback_query_id=callback_id,
150 | )
151 | self.turn_on(self.reminder_acknowledged_entity)
152 | self.log("Setting reminder_acknowledged to: {}".format("on"))
153 |
154 | self.call_service(
155 | "telegram_bot/edit_message",
156 | chat_id=chat_id,
157 | message_id=message_id,
158 | message=text
159 | + " Hast du um {}:{} erledigt.".format(
160 | datetime.datetime.now().hour, datetime.datetime.now().minute
161 | ),
162 | inline_keyboard=[],
163 | )
164 |
165 | def terminate(self):
166 | for timer_handle in self.timer_handle_list:
167 | self.cancel_timer(timer_handle)
168 |
169 | for listen_event_handle in self.listen_event_handle_list:
170 | self.cancel_listen_event(listen_event_handle)
171 |
172 | for listen_state_handle in self.listen_state_handle_list:
173 | self.cancel_listen_state(listen_state_handle)
174 |
--------------------------------------------------------------------------------
/plantWateringNotifier/plantWateringNotifier.yaml:
--------------------------------------------------------------------------------
1 | # plantWateringNotifier:
2 | # module: plantWateringNotifier
3 | # class: PlantWateringNotifier
4 | # app_switch: input_boolean.plant_watering_notifier
5 | # rain_precip_sensor: sensor.dark_sky_precip_probability
6 | # rain_precip_intensity_sensor: sensor.dark_sky_precip_intensity
7 | # precip_type_sensor: sensor.dark_sky_precip
8 | # notify_name: group_notifications
9 | # user_id: !secret telegram_user_id
10 | # reminder_acknowledged_entity: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged
11 | # message: "Die Regenwahrscheinlichkeit beträgt heute nur {}. Vergiss nicht die Pflanzen zu gießen!"
12 | # #message: "The Rain Propability is only {}. Don't forget to water the plants!"
13 | # message_not_needed: "Es wird heute mit einer Wahrscheinlichkeit von {} Prozent ungefähr {} Millimeter pro Stunde regnen. Du brauchst nicht selbst gießen."
14 | # #message_not_needed: "It will rain today {} millimeter per hour with a propability of {}. You don't have to water your plants"
15 | # message_evening: "Ich bin mir nicht sicher ob du vergessen hast die Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran."
16 | # #message_evening: "I'm not sure whether you waterd your plants, so I thought I better remind you again"
17 | # dependencies:
18 | # - Notifier
--------------------------------------------------------------------------------
/pollenNotifier/pollenNotifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App which notifies you when there is a pollen forecast for today
6 | # Used with sensors getting data from https://opendata.dwd.de/climate_environment/health/alerts/s31fg.json
7 | #
8 | #
9 | # Args:
10 | #
11 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
12 | # pollen_sensor: sensor which shows pollen for today. example: sensor.pollen_101_roggen_today
13 | # pollen_name: Name of the allergen. example: Roggen
14 | # notify_name: Who to notify. example: group_notifications
15 | # notify_time: When to notify. example: 08:00
16 | # notify_threshold: Minimum level of pollen needed to notify. example: 1.0
17 | # message: localized message to use in notification
18 | #
19 | # Release Notes
20 | #
21 | # Version 1.3.1:
22 | # Use consistent message variable
23 | #
24 | # Version 1.3:
25 | # use Notify App
26 | #
27 | # Version 1.2:
28 | # message now directly in own yaml instead of message module
29 | #
30 | # Version 1.1:
31 | # Added notify_threshold
32 | #
33 | # Version 1.0:
34 | # Initial Version
35 |
36 |
37 | class PollenNotifier(hass.Hass):
38 | def initialize(self):
39 |
40 | self.timer_handle_list = []
41 | self.listen_event_handle_list = []
42 | self.listen_state_handle_list = []
43 |
44 | self.app_switch = self.args["app_switch"]
45 | self.pollen_sensor = self.args["pollen_sensor"]
46 | self.pollen_name = self.args["pollen_name"]
47 | self.notify_name = self.args["notify_name"]
48 | self.notify_time = self.args["notify_time"]
49 | self.notify_threshold = self.args["notify_threshold"]
50 | self.message = self.args["message"]
51 | self.message_no_data = self.args["message_no_data"]
52 |
53 | self.mappingsdict = {}
54 | self.mappingsdict["-1"] = "keine Daten"
55 | self.mappingsdict["0"] = "Keine"
56 | self.mappingsdict["0-1"] = "Keine bis Geringe"
57 | self.mappingsdict["1"] = "Geringe"
58 | self.mappingsdict["1-2"] = "Geringe bis Mittlere"
59 | self.mappingsdict["2"] = "Mittlere"
60 | self.mappingsdict["2-3"] = "Mittlere bis Hohe"
61 | self.mappingsdict["3"] = "Hohe"
62 |
63 | self.level_mapping_dict = {}
64 | self.level_mapping_dict["-1"] = -1.0
65 | self.level_mapping_dict["0"] = 0.0
66 | self.level_mapping_dict["0-1"] = 0.5
67 | self.level_mapping_dict["1"] = 1.0
68 | self.level_mapping_dict["1-2"] = 1.5
69 | self.level_mapping_dict["2"] = 2.0
70 | self.level_mapping_dict["2-3"] = 2.5
71 | self.level_mapping_dict["3"] = 3
72 |
73 | self.notifier = self.get_app("Notifier")
74 |
75 | hours = self.notify_time.split(":", 1)[0]
76 | minutes = self.notify_time.split(":", 1)[1]
77 | self.timer_handle_list.append(
78 | self.run_daily(
79 | self.run_daily_callback, datetime.time(int(hours), int(minutes), 0)
80 | )
81 | )
82 |
83 | def run_daily_callback(self, kwargs):
84 | """Check if there is an pollen forcast and notify the user about it"""
85 | if self.get_state(self.app_switch) == "on":
86 | pollen_sensor_state = self.get_state(self.pollen_sensor)
87 | self.log(
88 | "{} Belastung Heute: {}".format(self.pollen_name, pollen_sensor_state)
89 | )
90 |
91 | if pollen_sensor_state == "-1":
92 | message = self.message_no_data.format("Heute", self.pollen_name)
93 | elif pollen_sensor_state == "0":
94 | message = (
95 | self.message.format(
96 | "Heute",
97 | self.mappingsdict[pollen_sensor_state],
98 | self.pollen_name,
99 | )
100 | + " Genieß den Tag!"
101 | )
102 | else:
103 | message = self.message.format(
104 | "Heute", self.mappingsdict[pollen_sensor_state], self.pollen_name
105 | )
106 |
107 | if self.level_mapping_dict[pollen_sensor_state] >= float(
108 | self.notify_threshold
109 | ):
110 | self.log("Notifying user")
111 | self.notifier.notify(self.notify_name, message)
112 | else:
113 | self.log("Threshold not met. Not notifying user")
114 |
115 | def terminate(self):
116 | for timer_handle in self.timer_handle_list:
117 | self.cancel_timer(timer_handle)
118 |
119 | for listen_event_handle in self.listen_event_handle_list:
120 | self.cancel_listen_event(listen_event_handle)
121 |
122 | for listen_state_handle in self.listen_state_handle_list:
123 | self.cancel_listen_state(listen_state_handle)
124 |
--------------------------------------------------------------------------------
/pollenNotifier/pollenNotifier.yaml:
--------------------------------------------------------------------------------
1 | roggenNotifier:
2 | module: pollenNotifier
3 | class: PollenNotifier
4 | app_switch: input_boolean.roggen_notifier
5 | pollen_sensor: sensor.pollen_101_roggen_today
6 | pollen_name: Roggen
7 | notify_name: group_notifications
8 | notify_time: 08:00
9 | notify_threshold: 1.0
10 | message: "{} ist {} {} Belastung."
11 | #message: "The {} intensity {} is {}."
12 | message_no_data: "Ich habe {} leider keine Daten für {}."
13 | #message_no_data: "{} I have no pollen data for {}."
14 | dependencies:
15 | - Notifier
16 |
17 | graeserNotifier:
18 | module: pollenNotifier
19 | class: PollenNotifier
20 | app_switch: input_boolean.graeser_notifier
21 | pollen_sensor: sensor.pollen_101_graeser_today
22 | pollen_name: Gräser
23 | notify_name: group_notifications
24 | notify_time: 08:00
25 | notify_threshold: 1.0
26 | message: "{} ist {} {} Belastung."
27 | #message: "The {} intensity {} is {}."
28 | message_no_data: "Ich habe {} leider keine Daten für {}."
29 | #message_no_data: "{} I have no pollen data for {}."
30 | dependencies:
31 | - Notifier
32 |
33 | birkeNotifier:
34 | module: pollenNotifier
35 | class: PollenNotifier
36 | app_switch: input_boolean.birke_notifier
37 | pollen_sensor: sensor.pollen_101_birke_today
38 | pollen_name: Birke
39 | notify_name: group_notifications
40 | notify_time: 08:00
41 | notify_threshold: 1.0
42 | message: "{} ist {} {} Belastung."
43 | #message: "The {} intensity {} is {}."
44 | message_no_data: "Ich habe {} leider keine Daten für {}."
45 | #message_no_data: "{} I have no pollen data for {}."
46 | dependencies:
47 | - Notifier
--------------------------------------------------------------------------------
/powerUsageNotification/powerUsageNotification.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App which notifies you when a power usage sensor indicated a device is on/off
5 | #
6 | #
7 | # Args:
8 | #
9 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
10 | # sensor: power sensor. example: sensor.dishwasher_power_usage
11 | # input_boolean (optional): input_boolean to set to on/off
12 | # notify_name: Who to notify. example: group_notifications
13 | # notify_start (optional): Notify if start was detected: example True (default: True)
14 | # notify_start_use_alexa (optional): Notify with alexa if start was detected: example True (default: True)
15 | # notify_end (optional): Notify if end was detected: example True (default: True)
16 | # notify_end_use_alexa (optional): Notify with alexa if end was detected: example True (default: True)
17 | # delay: seconds to wait until a the device is considered "off". example: 60
18 | # threshold: amount of "usage" which indicated the device is on. example: 2
19 | # alternative_name: Name to use in notification. example: Waschmaschine
20 | # message: Message to use when notifying device is on
21 | # message_off: Message to use when notifying device is off
22 | #
23 | # Release Notes
24 | #
25 | # Version 1.5:
26 | # Catch unknown
27 | #
28 | # Version 1.4:
29 | # Added notify_start, notify_start_use_alexa, notify_end, notify_end_use_alexa, input_boolean
30 | #
31 | # Version 1.3:
32 | # use Notify App
33 | #
34 | # Version 1.2:
35 | # message now directly in own yaml instead of message module
36 | #
37 | # Version 1.1:
38 | # Added app_switch
39 | #
40 | # Version 1.0:
41 | # Initial Version
42 |
43 |
44 | class PowerUsageNotification(hass.Hass):
45 | def initialize(self):
46 |
47 | self.timer_handle_list = []
48 | self.listen_event_handle_list = []
49 | self.listen_state_handle_list = []
50 |
51 | self.app_switch = self.args["app_switch"]
52 | self.input_boolean = self.args.get("input_boolean")
53 | self.sensor = self.args["sensor"]
54 | self.alternative_name = self.args["alternative_name"]
55 | self.notify_name = self.args["notify_name"]
56 | try:
57 | self.notify_start = self.args["notify_start"]
58 | except KeyError:
59 | self.notify_start = True
60 | try:
61 | self.notify_start_use_alexa = self.args["notify_start_use_alexa"]
62 | except KeyError:
63 | self.notify_start_use_alexa = True
64 | try:
65 | self.notify_end = self.args["notify_end"]
66 | except KeyError:
67 | self.notify_end = True
68 | try:
69 | self.notify_end_use_alexa = self.args["notify_end_use_alexa"]
70 | except KeyError:
71 | self.notify_end_use_alexa = True
72 | self.delay = self.args["delay"]
73 | self.threshold = self.args["threshold"]
74 | self.message = self.args["message"]
75 | self.message_off = self.args["message_off"]
76 |
77 | self.triggered = False
78 | self.isWaitingHandle = None
79 |
80 | self.notifier = self.get_app("Notifier")
81 |
82 | # Subscribe to sensors
83 | self.listen_state_handle_list.append(
84 | self.listen_state(self.state_change, self.sensor)
85 | )
86 |
87 | def state_change(self, entity, attribute, old, new, kwargs):
88 | if self.get_state(self.app_switch) == "on":
89 | # Initial: power usage goes up
90 | if (
91 | new != None
92 | and new != ""
93 | and new != "unknown"
94 | and not self.triggered
95 | and float(new) > self.threshold
96 | ):
97 | self.triggered = True
98 | self.log("Power Usage is: {}".format(float(new)))
99 | self.log("Setting triggered to: {}".format(self.triggered))
100 | if self.input_boolean is not None:
101 | self.turn_on(self.input_boolean)
102 | if self.notify_start:
103 | self.notifier.notify(
104 | self.notify_name,
105 | self.message.format(self.alternative_name),
106 | useAlexa=self.notify_start_use_alexa,
107 | )
108 | else:
109 | self.log("Not notifying user")
110 | # Power usage goes down below threshold
111 | elif (
112 | new != None
113 | and new != ""
114 | and self.triggered
115 | and self.isWaitingHandle == None
116 | and float(new) <= self.threshold
117 | ):
118 | self.log("Waiting: {} seconds to notify.".format(self.delay))
119 | self.isWaitingHandle = self.run_in(self.notify_device_off, self.delay)
120 | self.log("Setting isWaitingHandle to: {}".format(self.isWaitingHandle))
121 | self.timer_handle_list.append(self.isWaitingHandle)
122 | # Power usage goes up before delay
123 | elif (
124 | new != None
125 | and new != ""
126 | and self.triggered
127 | and self.isWaitingHandle != None
128 | and float(new) > self.threshold
129 | ):
130 | self.log("Cancelling timer")
131 | self.cancel_timer(self.isWaitingHandle)
132 | self.isWaitingHandle = None
133 | self.log("Setting isWaitingHandle to: {}".format(self.isWaitingHandle))
134 |
135 | def notify_device_off(self, kwargs):
136 | """Notify User that device is off. This may get cancelled if it turns on again in the meantime"""
137 | self.triggered = False
138 | self.log("Setting triggered to: {}".format(self.triggered))
139 | self.isWaitingHandle = None
140 | self.log("Setting isWaitingHandle to: {}".format(self.isWaitingHandle))
141 | if self.input_boolean is not None:
142 | self.turn_off(self.input_boolean)
143 | if self.notify_end:
144 | self.notifier.notify(
145 | self.notify_name,
146 | self.message_off.format(self.alternative_name),
147 | useAlexa=self.notify_end_use_alexa,
148 | )
149 | else:
150 | self.log("Not notifying user")
151 |
152 | def terminate(self):
153 | for timer_handle in self.timer_handle_list:
154 | self.cancel_timer(timer_handle)
155 |
156 | for listen_event_handle in self.listen_event_handle_list:
157 | self.cancel_listen_event(listen_event_handle)
158 |
159 | for listen_state_handle in self.listen_state_handle_list:
160 | self.cancel_listen_state(listen_state_handle)
161 |
--------------------------------------------------------------------------------
/powerUsageNotification/powerUsageNotification.yaml:
--------------------------------------------------------------------------------
1 | powerUsageNotification_Dishwasher:
2 | module: powerUsageNotification
3 | class: PowerUsageNotification
4 | app_switch: input_boolean.power_usage_notification_dishwasher
5 | input_boolean: input_boolean.dishwasher
6 | sensor: sensor.dishwasher_power_usage
7 | notify_name: group_notifications
8 | notify_start: False
9 | delay: 1260 #21 minutes
10 | threshold: 2
11 | alternative_name: Die Spülmaschine
12 | message: "{} ist gestartet."
13 | #message: "{} just started."
14 | message_off: "{} ist fertig."
15 | #message_off: "{} just finished."
16 | dependencies:
17 | - Notifier
18 |
19 | powerUsageNotification_Washingmachine:
20 | module: powerUsageNotification
21 | class: PowerUsageNotification
22 | app_switch: input_boolean.power_usage_notification_washingmachine
23 | input_boolean: input_boolean.washingmachine
24 | sensor: sensor.washingmachine_power_usage
25 | notify_name: group_notifications
26 | notify_start: False
27 | delay: 60
28 | threshold: 2
29 | alternative_name: Die Waschmaschine
30 | message: "{} ist gestartet."
31 | #message: "{} just started."
32 | message_off: "{} ist fertig."
33 | #message_off: "{} just finished."
34 | dependencies:
35 | - Notifier
--------------------------------------------------------------------------------
/reminder/reminder.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 | import uuid
4 |
5 | #
6 | # App which reminds you daily and again in the evening if not acknowledged
7 | #
8 | #
9 | # Args:
10 | #
11 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
12 | # notify_name: Who to notify. example: group_notifications
13 | # user_id: The user_id of the telegram user to ask whether he knows an unknown face. example: 812391
14 | # reminder_acknowledged_entity: Input Boolean to store the information whether the user acknowledged the notification.
15 | # This prevents new notifications upon HA/Appdaemon restart.
16 | # example: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged
17 | # message: localized message to use in notification
18 | #
19 | # Release Notes
20 | #
21 | # Version 1.0:
22 | # Initial Version
23 |
24 | KEYBOARD_CALLBACK_BASE = "/reminder_acknowledged"
25 |
26 |
27 | class Reminder(hass.Hass):
28 | def initialize(self):
29 |
30 | self.timer_handle_list = []
31 | self.listen_event_handle_list = []
32 | self.listen_state_handle_list = []
33 |
34 | self.app_switch = self.args["app_switch"]
35 | self.notify_name = self.args["notify_name"]
36 | self.user_id = self.args["user_id"]
37 | self.reminder_acknowledged_entity = self.args["reminder_acknowledged_entity"]
38 | self.message = self.args["message"]
39 | self.message_evening = self.args["message_evening"]
40 |
41 | self.keyboard_callback = (
42 | KEYBOARD_CALLBACK_BASE + uuid.uuid4().hex
43 | ) # Unique callback for each instance
44 |
45 | self.notifier = self.get_app("Notifier")
46 |
47 | self.reminder_acknowledged = self.get_state(self.reminder_acknowledged_entity)
48 |
49 | self.listen_event_handle_list.append(
50 | self.listen_event(self.receive_telegram_callback, "telegram_callback")
51 | )
52 |
53 | # Remind daily at 08:00
54 | self.timer_handle_list.append(
55 | self.run_daily(self.run_morning_callback, datetime.time(8, 0, 0))
56 | )
57 | # Remind daily at 18:00
58 | self.timer_handle_list.append(
59 | self.run_daily(self.run_evening_callback, datetime.time(18, 0, 0))
60 | )
61 |
62 | def run_morning_callback(self, kwargs):
63 | """Remind the user of {self.message}"""
64 | if self.get_state(self.app_switch) == "on":
65 |
66 | self.turn_off(self.reminder_acknowledged_entity)
67 | self.log("Setting reminder_acknowledged to: off")
68 | self.log("Reminding user")
69 | keyboard = [[("Hab ich gemacht", self.keyboard_callback)]]
70 | self.call_service(
71 | "telegram_bot/send_message",
72 | target=self.user_id,
73 | message=self.message,
74 | inline_keyboard=keyboard,
75 | )
76 |
77 | def run_evening_callback(self, kwargs):
78 | """Remind the user again if he didn't acknowledge it"""
79 | if self.get_state(self.app_switch) == "on":
80 | if self.get_state(self.reminder_acknowledged_entity) == "off":
81 | self.log("Reminding user")
82 | self.call_service(
83 | "notify/" + self.notify_name, message=self.message_evening
84 | )
85 |
86 | def receive_telegram_callback(self, event_name, data, kwargs):
87 | """Event listener for Telegram callback queries."""
88 | assert event_name == "telegram_callback"
89 | data_callback = data["data"]
90 | callback_id = data["id"]
91 | chat_id = data["chat_id"]
92 | message_id = data["message"]["message_id"]
93 | text = data["message"]["text"]
94 | self.log(f"callback data: {data}", level="DEBUG")
95 |
96 | if data_callback == self.keyboard_callback: # Keyboard editor:
97 | # Answer callback query
98 | self.call_service(
99 | "telegram_bot/answer_callback_query",
100 | message="Super!",
101 | callback_query_id=callback_id,
102 | )
103 | self.turn_on(self.reminder_acknowledged_entity)
104 | self.log("Setting reminder_acknowledged to: on")
105 |
106 | self.call_service(
107 | "telegram_bot/edit_message",
108 | chat_id=chat_id,
109 | message_id=message_id,
110 | message=text
111 | + f" Hast du um {datetime.datetime.now().hour}:{datetime.datetime.now().minute} erledigt.",
112 | inline_keyboard=[],
113 | )
114 |
115 | def terminate(self):
116 | for timer_handle in self.timer_handle_list:
117 | self.cancel_timer(timer_handle)
118 |
119 | for listen_event_handle in self.listen_event_handle_list:
120 | self.cancel_listen_event(listen_event_handle)
121 |
122 | for listen_state_handle in self.listen_state_handle_list:
123 | self.cancel_listen_state(listen_state_handle)
124 |
--------------------------------------------------------------------------------
/reminder/reminder.yaml:
--------------------------------------------------------------------------------
1 | # reminderPlantwateringNew:
2 | # module: reminder
3 | # class: Reminder
4 | # app_switch: input_boolean.plant_watering_reminder_new
5 | # notify_name: group_notifications
6 | # user_id: !secret telegram_user_id
7 | # reminder_acknowledged_entity: input_boolean.persistence_reminder_plantwatering_new_reminder_acknowledged
8 | # message: "Vergiss nicht die neuen Pflanzen zu gießen!"
9 | # #message: "Don't forget to water the plants!"
10 | # message_evening: "Ich bin mir nicht sicher ob du vergessen hast die neuen Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran."
11 | # #message_evening: "I'm not sure whether you watered your plants, so I thought I better remind you again"
12 | # dependencies:
13 | # - Notifier
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | wheel
--------------------------------------------------------------------------------
/seqSink/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.24.0
--------------------------------------------------------------------------------
/seqSink/seqSink.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import socket
3 | import os
4 | import json
5 | import requests
6 |
7 | #
8 | # App which forwards all logs to seq.
9 | #
10 | # Args:
11 | #
12 | # server_url: Server url of the seq instance to log to. example: "http://seq:5341/"
13 | # api_key (optional): Api key to use.
14 | #
15 | # Release Notes
16 | #
17 | # Version 1.0:
18 | # Initial Version
19 |
20 |
21 | class SeqSink(hass.Hass):
22 | def initialize(self):
23 | self.server_url = self.args["server_url"]
24 | if not self.server_url.endswith("/"):
25 | self.server_url += "/"
26 | self.server_url += "api/events/raw"
27 |
28 | self.session = requests.Session()
29 | self.session.headers["Content-Type"] = "application/json"
30 |
31 | api_key = self.args.get("api_key")
32 | if api_key:
33 | self.session.headers["X-Seq-ApiKey"] = api_key
34 |
35 | self.handle = self.listen_log(self.log_message_callback)
36 |
37 | def log_message_callback(self, app_name, ts, level, log_type, message, kwargs):
38 | if app_name != "seqSink":
39 | event_data = {
40 | "Timestamp": str(ts),
41 | "Level": str(level),
42 | "MessageTemplate": str(message),
43 | "Properties": {
44 | "Type": "Appdaemon",
45 | "AppName": str(app_name)
46 | },
47 | }
48 | request_body = {"Events": [event_data]}
49 |
50 | try:
51 | request_body_json = json.dumps(request_body)
52 | except TypeError:
53 | self.log(f"Could not serialize {request_body}")
54 | return
55 |
56 | try:
57 | response = self.session.post(
58 | self.server_url,
59 | data=request_body_json,
60 | stream=True, # prevent '362'
61 | )
62 | response.raise_for_status()
63 | except requests.RequestException as requestFailed:
64 | self.log(f"Could not serialize {message}")
65 |
66 | if not requestFailed.response:
67 | self.log("Response from Seq was unavailable.")
68 | elif not requestFailed.response.text:
69 | self.log("Response body from Seq was empty.")
70 | else:
71 | self.log(f"Response body from Seq:{requestFailed.response.text}")
72 |
73 | def terminate(self):
74 | self.cancel_listen_log(self.handle)
75 |
--------------------------------------------------------------------------------
/seqSink/seqSink.yaml:
--------------------------------------------------------------------------------
1 | # seqSink:
2 | # module: seqSink
3 | # class: SeqSink
4 | # server_url: "http://seq:5341/"
5 | # api_key: !secret seq_api_key
--------------------------------------------------------------------------------
/setThermostat/setThermostat.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App which sets a thermostat to a target temperature based on a time from an entity
6 | #
7 | # Args:
8 | #
9 | # app_switch: on/off switch for this app. example: input_boolean.warm_bath_before_wakeup
10 | # isHome: entity which shows if someone is home. example: input_boolean.is_home
11 | # sleepMode: entity which shows if users are sleeping. example: input_boolean.sleepmode
12 | # time_entity: sensor which determines when to run in the format 14:30. example: sensor.alarm_time
13 | # upfront_time: how many minutes before the time_sensor to run. example: 60
14 | # duration: After how many minutes should the thermostat be set back to its previous value. example: 60
15 | # climat_entity: climate entity to set. example: climate.bad_thermostat
16 | # target_entity: the entity holding the target temp. example: warm_bath_before_wakeup
17 | # message: message to use in notification
18 | # notify_name: who to notify. example: group_notifications
19 | # use_alexa: use alexa for notification. example: False
20 | #
21 | # Release Notes
22 | #
23 | # Version 1.5:
24 | # Catch new is unknown
25 | #
26 | # Version 1.4:
27 | # Use sleepmode
28 | #
29 | # Version 1.3:
30 | # Use new formatted alarm_time
31 | #
32 | # Version 1.2.1:
33 | # Reschedule timer after first run
34 | #
35 | # Version 1.2:
36 | # Added isHome. Only run when someone is home
37 | #
38 | # Version 1.1:
39 | # Actually set the previous temp
40 | #
41 | # Version 1.0:
42 | # Initial Version
43 |
44 |
45 | class SetThermostat(hass.Hass):
46 | def initialize(self):
47 | self.timer_handle_list = []
48 | self.listen_state_handle_list = []
49 |
50 | self.app_switch = self.args["app_switch"]
51 | self.time_entity = self.args["time_entity"]
52 | self.upfront_time = self.args["upfront_time"]
53 | self.duration = self.args["duration"]
54 | self.climat_entity = self.args["climat_entity"]
55 | self.target_entity = self.args["target_entity"]
56 | self.message = self.args["message"]
57 | self.notify_name = self.args["notify_name"]
58 | self.use_alexa = self.args["use_alexa"]
59 | self.isHome = self.args["isHome"]
60 | self.sleepMode = self.args["sleepMode"]
61 |
62 | self.notifier = self.get_app("Notifier")
63 |
64 | self.run_timer = None
65 |
66 | self.cached_alarm_time = None
67 |
68 | self.listen_state_handle_list.append(
69 | self.listen_state(self.schedule_trigger, self.time_entity)
70 | )
71 | self.schedule_trigger(
72 | self.time_entity, None, None, self.get_state(self.time_entity), None
73 | )
74 |
75 | def schedule_trigger(self, entity, attribute, old, new, kwargs):
76 | if (
77 | new is not None
78 | and new != old
79 | and new != ""
80 | and new != "unknown"
81 | and new != self.cached_alarm_time
82 | ):
83 | if self.run_timer is not None:
84 | self.cancel_timer(self.run_timer)
85 | self.log("Cancelled scheduled trigger")
86 | self.run_timer = None
87 | self.cached_alarm_time = new
88 | event_time = datetime.datetime.strptime(new, "%Y-%m-%d %H:%M:%S")
89 | event_time = event_time - datetime.timedelta(minutes=self.upfront_time)
90 | try:
91 | self.run_timer = self.run_at(self.trigger_thermostat, event_time)
92 | self.timer_handle_list.append(self.run_timer)
93 | self.log("Thermostat will trigger at {}".format(event_time))
94 | except ValueError:
95 | self.log("New trigger time would be in the past: {}".format(event_time))
96 |
97 | def trigger_thermostat(self, kwargs):
98 | if (
99 | self.get_state(self.app_switch) == "on"
100 | and self.get_state(self.isHome) == "on"
101 | and self.get_state(self.sleepMode) == "on"
102 | ):
103 | self.log(
104 | self.message.format(
105 | self.friendly_name(self.climat_entity),
106 | self.get_state(self.target_entity),
107 | )
108 | )
109 | self.notifier.notify(
110 | self.notify_name,
111 | self.message.format(
112 | self.friendly_name(self.climat_entity),
113 | self.get_state(self.target_entity),
114 | ),
115 | useAlexa=self.use_alexa,
116 | )
117 | self.log("Turning {} on".format(self.climat_entity))
118 | self.call_service("climate/turn_on", entity_id=self.climat_entity)
119 | self.previous_temp = self.get_state(self.climat_entity, attribute="all")[
120 | "attributes"
121 | ]["temperature"]
122 | self.call_service(
123 | "climate/set_temperature",
124 | entity_id=self.climat_entity,
125 | temperature=self.get_state(self.target_entity),
126 | )
127 | self.log("Resetting Thermostat in {} minutes.".format(self.duration))
128 | self.timer_handle_list.append(
129 | self.run_in(self.reset_thermostat, float(self.duration) * 60)
130 | )
131 | if self.run_timer is not None:
132 | self.cancel_timer(self.run_timer)
133 |
134 | def reset_thermostat(self, kwargs):
135 | if self.previous_temp is not None:
136 | self.log(
137 | self.message.format(
138 | self.friendly_name(self.climat_entity), self.previous_temp
139 | )
140 | )
141 | self.notifier.notify(
142 | self.notify_name,
143 | self.message.format(
144 | self.friendly_name(self.climat_entity), self.previous_temp
145 | ),
146 | useAlexa=self.use_alexa,
147 | )
148 | self.call_service(
149 | "climate/set_temperature",
150 | entity_id=self.climat_entity,
151 | temperature=self.previous_temp,
152 | )
153 | self.schedule_trigger(
154 | self.time_entity, None, None, self.get_state(self.time_entity), None
155 | )
156 |
157 | def terminate(self):
158 | for timer_handle in self.timer_handle_list:
159 | self.cancel_timer(timer_handle)
160 |
161 | for listen_state_handle in self.listen_state_handle_list:
162 | self.cancel_listen_state(listen_state_handle)
163 |
--------------------------------------------------------------------------------
/setThermostat/setThermostat.yaml:
--------------------------------------------------------------------------------
1 | # warm_bath_before_wakeup:
2 | # module: setThermostat
3 | # class: SetThermostat
4 | # app_switch: input_boolean.warm_bath_before_wakeup
5 | # isHome: input_boolean.is_home
6 | # sleepMode: input_boolean.sleepmode
7 | # time_entity: sensor.alarm_time
8 | # upfront_time: 120
9 | # duration: 120
10 | # climat_entity: climate.bad_thermostat
11 | # target_entity: input_number.warm_bath_before_wakeup
12 | # message: "Ich habe {} auf {} °C gestellt"
13 | # #message: "I have set {} to {}"
14 | # notify_name: group_notifications
15 | # use_alexa: False
16 | # dependencies:
17 | # - Notifier
18 |
19 | # warm_upper_bath_before_wakeup:
20 | # module: setThermostat
21 | # class: SetThermostat
22 | # app_switch: input_boolean.warm_upper_bath_before_wakeup
23 | # isHome: input_boolean.is_home
24 | # sleepMode: input_boolean.sleepmode
25 | # time_entity: sensor.alarm_time
26 | # upfront_time: 60
27 | # duration: 60
28 | # climat_entity: climate.bad_oben_thermostat
29 | # target_entity: input_number.warm_upper_bath_before_wakeup
30 | # message: "Ich habe {} auf {} °C gestellt"
31 | # #message: "I have set {} to {}"
32 | # notify_name: group_notifications
33 | # use_alexa: False
34 | # dependencies:
35 | # - Notifier
36 |
37 |
--------------------------------------------------------------------------------
/setThermostatOnStateChange/setThermostatOnStateChange.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 | #
4 | # App which sets a thermostat to a target temperature on state change
5 | #
6 | # Args:
7 | #
8 | # app_switch: on/off switch for this app. example: input_boolean.warm_bath_before_wakeup
9 | # trigger_entity: entity which triggers this app. example: input_boolean.is_home
10 | # trigger_state: new state of trigger_entity which triggers this app. example: "off"
11 | # climate_entity: climate entity to set. example: climate.bad_thermostat
12 | # target_entity: the entity holding the target temp. example: warm_bath_before_wakeup
13 | # message (optional): message to use in notification
14 | # notify_name (optional): who to notify. example: group_notifications
15 | # use_alexa (optional): use alexa for notification. example: False
16 | #
17 | # Release Notes
18 | #
19 | # Version 1.2:
20 | # Rename of SetThermostatOnStateChange
21 | #
22 | # Version 1.1:
23 | # Use isHome as trigger
24 | #
25 | # Version 1.0:
26 | # Initial Version
27 |
28 |
29 | class SetThermostatOnStateChange(hass.Hass):
30 | def initialize(self):
31 | self.timer_handle_list = []
32 | self.listen_state_handle_list = []
33 |
34 | self.app_switch = self.args["app_switch"]
35 | self.trigger_entity = self.args["trigger_entity"]
36 | self.trigger_state = self.args["trigger_state"]
37 | self.climate_entity = self.args["climate_entity"]
38 | self.target_entity = self.args["target_entity"]
39 | self.message = self.args.get("message")
40 | self.notify_name = self.args.get("notify_name")
41 | try:
42 | self.use_alexa = self.args["use_alexa"]
43 | except KeyError:
44 | self.use_alexa = False
45 |
46 | self.notifier = self.get_app("Notifier")
47 |
48 | self.listen_state_handle_list.append(
49 | self.listen_state(self.state_change, self.trigger_entity)
50 | )
51 |
52 | def state_change(self, entity, attribute, old, new, kwargs):
53 | if self.get_state(self.app_switch) == "on":
54 | if new != "" and new == self.trigger_state and old != new:
55 | if self.message is not None:
56 | self.log(
57 | self.message.format(
58 | self.friendly_name(self.climate_entity),
59 | self.get_state(self.target_entity),
60 | )
61 | )
62 | self.call_service("climate/turn_on", entity_id=self.climate_entity)
63 | self.call_service(
64 | "climate/set_temperature",
65 | entity_id=self.climate_entity,
66 | temperature=self.get_state(self.target_entity),
67 | )
68 | if self.notify_name is not None:
69 | self.notifier.notify(
70 | self.notify_name,
71 | self.message.format(
72 | self.friendly_name(self.climate_entity),
73 | self.get_state(self.target_entity),
74 | ),
75 | useAlexa=self.use_alexa,
76 | )
77 |
78 | def terminate(self):
79 | for timer_handle in self.timer_handle_list:
80 | self.cancel_timer(timer_handle)
81 |
82 | for listen_state_handle in self.listen_state_handle_list:
83 | self.cancel_listen_state(listen_state_handle)
84 |
--------------------------------------------------------------------------------
/setThermostatOnStateChange/setThermostatOnStateChange.yaml:
--------------------------------------------------------------------------------
1 | # setWohnzimmerThermostatWhenLeaving:
2 | # module: setThermostatOnStateChange
3 | # class: SetThermostatOnStateChange
4 | # app_switch: input_boolean.set_livingroom_thermostat_when_leaving
5 | # trigger_entity: input_boolean.is_home
6 | # trigger_state: "off"
7 | # climate_entity: climate.wohnzimmer_thermostat
8 | # target_entity: input_number.set_livingroom_thermostat_when_leaving
9 | # message: "Ich habe {} auf {} °C gestellt"
10 | # #message: "I have set {} to {}"
11 | # dependencies:
12 | # - Notifier
13 |
14 | # setBadObenThermostatWhenLeaving:
15 | # module: setThermostatOnStateChange
16 | # class: SetThermostatOnStateChange
17 | # app_switch: input_boolean.set_upper_bath_thermostat_when_leaving
18 | # trigger_entity: input_boolean.is_home
19 | # trigger_state: "off"
20 | # climate_entity: climate.bad_oben_thermostat
21 | # target_entity: input_number.set_upper_bath_thermostat_when_leaving
22 | # message: "Ich habe {} auf {} °C gestellt"
23 | # #message: "I have set {} to {}"
24 | # dependencies:
25 | # - Notifier
26 |
27 | # setBadThermostatWhenLeaving:
28 | # module: setThermostatOnStateChange
29 | # class: SetThermostatOnStateChange
30 | # app_switch: input_boolean.set_bath_thermostat_when_leaving
31 | # trigger_entity: input_boolean.is_home
32 | # trigger_state: "off"
33 | # climate_entity: climate.bad_thermostat
34 | # target_entity: input_number.set_bath_thermostat_when_leaving
35 | # message: "Ich habe {} auf {} °C gestellt"
36 | # #message: "I have set {} to {}"
37 | # dependencies:
38 | # - Notifier
39 |
40 | # setKitchenThermostatWhenLeaving:
41 | # module: setThermostatOnStateChange
42 | # class: SetThermostatOnStateChange
43 | # app_switch: input_boolean.set_kitchen_thermostat_when_leaving
44 | # trigger_entity: input_boolean.is_home
45 | # trigger_state: "off"
46 | # climate_entity: climate.kuche_thermostat
47 | # target_entity: input_number.set_kitchen_thermostat_when_leaving
48 | # message: "Ich habe {} auf {} °C gestellt"
49 | # #message: "I have set {} to {}"
50 | # dependencies:
51 | # - Notifier
52 |
53 | # setWohnzimmerThermostatWhenSleeping:
54 | # module: setThermostatOnStateChange
55 | # class: SetThermostatOnStateChange
56 | # app_switch: input_boolean.set_livingroom_thermostat_when_sleeping
57 | # trigger_entity: input_boolean.sleepmode
58 | # trigger_state: "on"
59 | # climate_entity: climate.wohnzimmer_thermostat
60 | # target_entity: input_number.set_livingroom_thermostat_when_sleeping
61 | # message: "Ich habe {} auf {} °C gestellt"
62 | # #message: "I have set {} to {}"
63 | # dependencies:
64 | # - Notifier
65 |
66 | # setBadObenThermostatWhenSleeping:
67 | # module: setThermostatOnStateChange
68 | # class: SetThermostatOnStateChange
69 | # app_switch: input_boolean.set_upper_bath_thermostat_when_sleeping
70 | # trigger_entity: input_boolean.sleepmode
71 | # trigger_state: "on"
72 | # climate_entity: climate.bad_oben_thermostat
73 | # target_entity: input_number.set_upper_bath_thermostat_when_sleeping
74 | # message: "Ich habe {} auf {} °C gestellt"
75 | # #message: "I have set {} to {}"
76 | # dependencies:
77 | # - Notifier
78 |
79 | # setBadThermostatWhenSleeping:
80 | # module: setThermostatOnStateChange
81 | # class: SetThermostatOnStateChange
82 | # app_switch: input_boolean.set_bath_thermostat_when_sleeping
83 | # trigger_entity: input_boolean.sleepmode
84 | # trigger_state: "on"
85 | # climate_entity: climate.bad_thermostat
86 | # target_entity: input_number.set_bath_thermostat_when_sleeping
87 | # message: "Ich habe {} auf {} °C gestellt"
88 | # #message: "I have set {} to {}"
89 | # dependencies:
90 | # - Notifier
91 |
92 | # setKitchenThermostatWhenSleeping:
93 | # module: setThermostatOnStateChange
94 | # class: SetThermostatOnStateChange
95 | # app_switch: input_boolean.set_kitchen_thermostat_when_sleeping
96 | # trigger_entity: input_boolean.sleepmode
97 | # trigger_state: "on"
98 | # climate_entity: climate.kuche_thermostat
99 | # target_entity: input_number.set_kitchen_thermostat_when_sleeping
100 | # message: "Ich habe {} auf {} °C gestellt"
101 | # #message: "I have set {} to {}"
102 | # dependencies:
103 | # - Notifier
104 |
105 | # setLivingroomThermostatWhenWakingUp:
106 | # module: setThermostatOnStateChange
107 | # class: SetThermostatOnStateChange
108 | # app_switch: input_boolean.set_livingroom_thermostat_when_waking_up
109 | # trigger_entity: input_boolean.sleepmode
110 | # trigger_state: "off"
111 | # climate_entity: climate.wohnzimmer_thermostat
112 | # target_entity: input_number.set_livingroom_thermostat_when_waking_up
113 | # message: "Ich habe {} auf {} °C gestellt"
114 | # #message: "I have set {} to {}"
115 | # notify_name: group_notifications
116 | # use_alexa: False
117 | # dependencies:
118 | # - Notifier
119 |
120 | # setKitchenThermostatWhenWakingUp:
121 | # module: setThermostatOnStateChange
122 | # class: SetThermostatOnStateChange
123 | # app_switch: input_boolean.set_kitchen_thermostat_when_waking_up
124 | # trigger_entity: input_boolean.sleepmode
125 | # trigger_state: "off"
126 | # climate_entity: climate.kuche_thermostat
127 | # target_entity: input_number.set_kitchen_thermostat_when_waking_up
128 | # message: "Ich habe {} auf {} °C gestellt"
129 | # #message: "I have set {} to {}"
130 | # notify_name: group_notifications
131 | # use_alexa: False
132 | # dependencies:
133 | # - Notifier
134 |
135 | # setBadThermostatWhenWakingUp:
136 | # module: setThermostatOnStateChange
137 | # class: SetThermostatOnStateChange
138 | # app_switch: input_boolean.set_bath_thermostat_when_waking_up
139 | # trigger_entity: input_boolean.sleepmode
140 | # trigger_state: "off"
141 | # climate_entity: climate.bad_thermostat
142 | # target_entity: input_number.set_bath_thermostat_when_waking_up
143 | # message: "Ich habe {} auf {} °C gestellt"
144 | # #message: "I have set {} to {}"
145 | # notify_name: group_notifications
146 | # use_alexa: False
147 | # dependencies:
148 | # - Notifier
149 |
150 | # setBadObenThermostatWhenWakingUp:
151 | # module: setThermostatOnStateChange
152 | # class: SetThermostatOnStateChange
153 | # app_switch: input_boolean.set_upper_bath_thermostat_when_waking_up
154 | # trigger_entity: input_boolean.sleepmode
155 | # trigger_state: "off"
156 | # climate_entity: climate.bad_oben_thermostat
157 | # target_entity: input_number.set_upper_bath_thermostat_when_waking_up
158 | # message: "Ich habe {} auf {} °C gestellt"
159 | # #message: "I have set {} to {}"
160 | # notify_name: group_notifications
161 | # use_alexa: False
162 | # dependencies:
163 | # - Notifier
164 |
165 | # setLivingroomThermostatWhenComingHome:
166 | # module: setThermostatOnStateChange
167 | # class: SetThermostatOnStateChange
168 | # app_switch: input_boolean.set_livingroom_thermostat_when_coming_home
169 | # trigger_entity: input_boolean.is_home
170 | # trigger_state: "on"
171 | # climate_entity: climate.wohnzimmer_thermostat
172 | # target_entity: input_number.set_livingroom_thermostat_when_coming_home
173 | # message: "Ich habe {} auf {} °C gestellt"
174 | # #message: "I have set {} to {}"
175 | # notify_name: group_notifications
176 | # use_alexa: False
177 | # dependencies:
178 | # - Notifier
179 |
180 | # setKitchenThermostatWhenComingHome:
181 | # module: setThermostatOnStateChange
182 | # class: SetThermostatOnStateChange
183 | # app_switch: input_boolean.set_kitchen_thermostat_when_coming_home
184 | # trigger_entity: input_boolean.is_home
185 | # trigger_state: "on"
186 | # climate_entity: climate.kuche_thermostat
187 | # target_entity: input_number.set_kitchen_thermostat_when_coming_home
188 | # message: "Ich habe {} auf {} °C gestellt"
189 | # #message: "I have set {} to {}"
190 | # notify_name: group_notifications
191 | # use_alexa: False
192 | # dependencies:
193 | # - Notifier
194 |
195 | # setBadThermostatWhenComingHome:
196 | # module: setThermostatOnStateChange
197 | # class: SetThermostatOnStateChange
198 | # app_switch: input_boolean.set_bath_thermostat_when_coming_home
199 | # trigger_entity: input_boolean.is_home
200 | # trigger_state: "on"
201 | # climate_entity: climate.bad_thermostat
202 | # target_entity: input_number.set_bath_thermostat_when_coming_home
203 | # message: "Ich habe {} auf {} °C gestellt"
204 | # #message: "I have set {} to {}"
205 | # notify_name: group_notifications
206 | # use_alexa: False
207 | # dependencies:
208 | # - Notifier
209 |
210 | # setBadObenThermostatWhenComingHome:
211 | # module: setThermostatOnStateChange
212 | # class: SetThermostatOnStateChange
213 | # app_switch: input_boolean.set_upper_bath_thermostat_when_coming_home
214 | # trigger_entity: input_boolean.is_home
215 | # trigger_state: "on"
216 | # climate_entity: climate.bad_oben_thermostat
217 | # target_entity: input_number.set_upper_bath_thermostat_when_coming_home
218 | # message: "Ich habe {} auf {} °C gestellt"
219 | # #message: "I have set {} to {}"
220 | # notify_name: group_notifications
221 | # use_alexa: False
222 | # dependencies:
223 | # - Notifier
--------------------------------------------------------------------------------
/sleepModeHandler/sleepModeHandler.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 |
3 |
4 | #
5 | # App which sets the sleep mode on/off
6 | #
7 | # Args:
8 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
9 | # sleepmode: input_boolean holding the sleepmode. example: input_boolean.sleepmode
10 | # users: configuration for users
11 | #
12 | # Release Notes
13 | #
14 | # Version 1.2:
15 | # Add use_alexa
16 | #
17 | # Version 1.1:
18 | # Only send notification if sleepmode is actually changed
19 | #
20 | # Version 1.0:
21 | # Initial Version
22 |
23 |
24 | class SleepModeHandler(hass.Hass):
25 | def initialize(self):
26 | self.listen_state_handle_list = []
27 |
28 | self.app_switch = self.args["app_switch"]
29 | self.sleepmode = self.args["sleepmode"]
30 | self.users = self.args["users"]
31 | self.notify_name = self.args["notify_name"]
32 | self.message_sleeping = self.args["message_sleeping"]
33 | self.message_awake = self.args["message_awake"]
34 |
35 | try:
36 | self.use_alexa = self.args["use_alexa"]
37 | except KeyError:
38 | self.use_alexa = False
39 |
40 | self.notifier = self.get_app("Notifier")
41 |
42 | for user in self.users:
43 | self.listen_state_handle_list.append(
44 | self.listen_state(self.state_change, user["sleep_mode"])
45 | )
46 |
47 | def state_change(self, entity, attribute, old, new, kwargs):
48 | if self.get_state(self.app_switch) == "on":
49 | if new != "" and new != old:
50 | if new == "on":
51 | if self.are_all_that_are_home_sleeping():
52 | if self.get_state(self.sleepmode) == "off":
53 | self.log("All at home are sleeping")
54 | self.turn_on(self.sleepmode)
55 | self.notifier.notify(
56 | self.notify_name,
57 | self.message_sleeping,
58 | useAlexa=self.use_alexa,
59 | )
60 | elif new == "off":
61 | if self.are_all_that_are_home_awake():
62 | if self.get_state(self.sleepmode) == "on":
63 | self.log("All at home are awake")
64 | self.turn_off(self.sleepmode)
65 | self.notifier.notify(
66 | self.notify_name,
67 | self.message_awake,
68 | useAlexa=self.use_alexa,
69 | )
70 |
71 | def are_all_that_are_home_sleeping(self):
72 | for user in self.users:
73 | if self.get_state(user["isHome"]) == "on":
74 | if self.get_state(user["sleep_mode"]) != "on":
75 | return False
76 | return True
77 |
78 | def are_all_that_are_home_awake(self):
79 | for user in self.users:
80 | if self.get_state(user["isHome"]) == "on":
81 | if self.get_state(user["sleep_mode"]) == "on":
82 | return False
83 | return True
84 |
85 | def terminate(self):
86 | for listen_state_handle in self.listen_state_handle_list:
87 | self.cancel_listen_state(listen_state_handle)
88 |
--------------------------------------------------------------------------------
/sleepModeHandler/sleepModeHandler.yaml:
--------------------------------------------------------------------------------
1 | # sleepModeHandler:
2 | # module: sleepModeHandler
3 | # class: SleepModeHandler
4 | # app_switch: input_boolean.sleep_mode_handler
5 | # sleepmode: input_boolean.sleepmode
6 | # notify_name: group_notifications
7 | # message_sleeping: "Alle zu Hause sind im Bett"
8 | # #message_sleeping: "All home are in bed"
9 | # message_awake: "Alle zu Hause sind wach"
10 | # #message_awake: "All home are awake"
11 | # use_alexa: False
12 | # users:
13 | # - sleep_mode: input_boolean.user_one_sleep
14 | # isHome: input_boolean.user_one_home
15 | # - sleep_mode: input_boolean.user_two_sleep
16 | # isHome: input_boolean.user_two_home
17 | # dependencies:
18 | # - Notifier
--------------------------------------------------------------------------------
/sleepModeHandler/userSleepModeHandler.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | from queue import Queue
3 |
4 | #
5 | # App which sets the sleep mode on/off
6 | #
7 | # Args:
8 | # app_switch:
9 | # on/off switch for this app.
10 | # example: input_boolean.turn_fan_on_when_hot
11 | # input_boolean:
12 | # input_boolean holding the sleepmode. example: input_boolean.sleepmode
13 | # location_sensor:
14 | # location sensor of user. example: sensor.location_user_one
15 | # room:
16 | # Room name in which user must be. example: Wohnzimmer
17 | # asleep_duration:
18 | # seconds to wait before turning sleepmode on. example: 120
19 | # awake_duration:
20 | # seconds to wait before turning sleepmode off. example: 120
21 | # is_home_input_boolean:
22 | # input_boolean for the is home state of the user
23 | # example: input_boolean.is_user_home_determiner_user_one
24 | #
25 | # Release Notes
26 | #
27 | # Version 1.4:
28 | # Also watch is_home
29 | #
30 | # Version 1.3:
31 | # Reimplementation
32 | #
33 | # Version 1.2:
34 | # Only trigger on an actual change
35 | #
36 | # Version 1.1:
37 | # Added asleep_duration and awake_duration instead of duration
38 | #
39 | # Version 1.0:
40 | # Initial Version
41 |
42 |
43 | class UserSleepModeHandler(hass.Hass):
44 | def initialize(self):
45 | self.listen_state_handle_list = []
46 |
47 | self.app_switch = self.args["app_switch"]
48 | self.location_sensor = self.args["location_sensor"]
49 | self.room = self.args["room"]
50 | self.asleep_duration = self.args["asleep_duration"]
51 | self.awake_duration = self.args["awake_duration"]
52 | self.input_boolean = self.args["input_boolean"]
53 | self.is_home_input_boolean = self.args["is_home_input_boolean"]
54 |
55 | self.timer_handle = None
56 |
57 | self.queue = Queue()
58 |
59 | self.listen_state_handle_list.append(
60 | self.listen_state(self.state_change, self.location_sensor)
61 | )
62 |
63 | self.listen_state_handle_list.append(
64 | self.listen_state(self.state_change, self.is_home_input_boolean)
65 | )
66 |
67 | def home_state_change(self, entity, attribute, old, new, kwargs):
68 | """is_home state changed."""
69 | if new != old:
70 | if self.get_state(self.app_switch) == "on":
71 | if new == "off":
72 | self.log(
73 | f"User left home. Turning {self.input_boolean} off")
74 | self.turn_off(self.input_boolean)
75 |
76 | def state_change(self, entity, attribute, old, new, kwargs):
77 | """Handle state changes of the location sensor."""
78 | if new != old:
79 | if self.get_state(self.app_switch) == "on":
80 | # User left room
81 | if old == self.room:
82 | self.log(
83 | f"User left {self.room}. Resetting timer."
84 | f"Will trigger awake in {self.awake_duration}s"
85 | )
86 | if self.timer_handle is not None:
87 | self.cancel_timer(self.timer_handle)
88 | self.timer_handle = self.run_in(
89 | self.awake,
90 | self.awake_duration)
91 | elif new == self.room:
92 | self.log(
93 | f"User entered {self.room}. "
94 | f"Resetting timer. "
95 | f"Will trigger asleep in {self.asleep_duration}s"
96 | )
97 | if self.timer_handle is not None:
98 | self.cancel_timer(self.timer_handle)
99 | self.timer_handle = self.run_in(
100 | self.asleep,
101 | self.asleep_duration)
102 |
103 | def awake(self, kwargs):
104 | """User left room for more than self.awake_duration.
105 | Turn off sleep mode."""
106 | current_location = self.get_state(self.location_sensor)
107 | if current_location != self.room:
108 | if self.get_state(self.input_boolean) == "on":
109 | self.log(
110 | f"{self.friendly_name(self.location_sensor)} "
111 | f"is outside {self.room} "
112 | f"for more than {self.asleep_duration}s. "
113 | f"Turning {self.input_boolean} off"
114 | )
115 | self.turn_off(self.input_boolean)
116 | else:
117 | self.log(f"Timer ran out but user is in {current_location}")
118 |
119 | def asleep(self, kwargs):
120 | """User stayed in room for more than self.asleep_duration.
121 | Turn on sleep mode."""
122 | current_location = self.get_state(self.location_sensor)
123 | if current_location == self.room:
124 | if self.get_state(self.input_boolean) == "off":
125 | self.log(
126 | f"{self.friendly_name(self.location_sensor)} "
127 | f"is in {self.room} "
128 | f"for more than {self.asleep_duration}s. "
129 | f"Turning {self.input_boolean} on"
130 | )
131 | self.turn_on(self.input_boolean)
132 | else:
133 | self.log(f"Timer ran out but user is in {current_location}")
134 |
135 | def insert_room_state_change(self, entity, attribute, old, new, kwargs):
136 | """Insert a new room state change into the queue."""
137 | self.queue.put(new)
138 |
139 | def calculate_room_presence(self, kwargs):
140 | """Calculate the percentage the person was in the target room
141 | since the last invocation."""
142 | state_changes = []
143 | while not self.queue.empty():
144 | state_changes.append(self.queue.get())
145 | for state_change in state_changes:
146 | pass
147 |
148 | def terminate(self):
149 | if self.timer_handle is not None:
150 | self.cancel_timer(self.timer_handle)
151 |
152 | for listen_state_handle in self.listen_state_handle_list:
153 | self.cancel_listen_state(listen_state_handle)
154 |
--------------------------------------------------------------------------------
/sleepModeHandler/userSleepModeHandler.yaml:
--------------------------------------------------------------------------------
1 | # userSleepModeHandlerUserOne:
2 | # module: userSleepModeHandler
3 | # class: UserSleepModeHandler
4 | # app_switch: input_boolean.user_sleep_mode_handler_user_one
5 | # input_boolean: input_boolean.user_one_sleep
6 | # location_sensor: sensor.location_user_one
7 | # is_home_input_boolean: input_boolean.is_user_home_determiner_user_one
8 | # room: Schlafzimmer
9 | # awake_duration: 600
10 | # asleep_duration: 1800
11 |
12 | # userSleepModeHandlerUserTwo:
13 | # module: userSleepModeHandler
14 | # class: UserSleepModeHandler
15 | # app_switch: input_boolean.user_sleep_mode_handler_user_two
16 | # input_boolean: input_boolean.user_two_sleep
17 | # location_sensor: sensor.location_user_two
18 | # is_home_input_boolean: input_boolean.is_user_home_determiner_user_two
19 | # room: Schlafzimmer
20 | # awake_duration: 600
21 | # asleep_duration: 1800
--------------------------------------------------------------------------------
/travelTimeNotifier/travelTimeNotifier.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 | from typing import Optional
4 |
5 | #
6 | # App which notifies the user if the travel time is within a normal amount
7 | #
8 | #
9 | # Args:
10 | # sensor: google_travel_time or here_travel_time sensor to watch. example: sensor.travel_time_home_from_work
11 | # notify_input_boolean: input_boolean determining whether to notify. example: input_boolean.travel_time_home_from_work
12 | # notify_name: Who to notify. example: group_notifications
13 | # acceptable_range (optional): Multiplier of the normal travel time that is still acceptable. example: 1.2
14 | # message_: message to use in notification
15 | # notify_use_Alexa: use Alexa as TTS. Defaults to True. example: False
16 | #
17 | # Release Notes
18 | #
19 | # Version 1.7:
20 | # Catch NoneType when Homeassistant is still starting
21 | #
22 | # Version 1.6:
23 | # Introduce methods to deal with minor differences between google and here
24 | #
25 | # Version 1.5:
26 | # Rename to TravelTimeNotifier as this can be used with here_travel_time also
27 | #
28 | # Version 1.4:
29 | # use Notify App
30 | #
31 | # Version 1.3:
32 | # message now directly in own yaml instead of message module
33 | #
34 | # Version 1.2:
35 | # Moved to standard google travel sensors. Now only notification
36 | #
37 | # Version 1.1:
38 | # Add notification feature
39 | #
40 | # Version 1.0:
41 | # Initial Version
42 |
43 |
44 | class TravelTimeNotifier(hass.Hass):
45 | def initialize(self):
46 |
47 | self.listen_state_handle_list = []
48 | self.timer_handle_list = []
49 |
50 | self.sensor = self.args["sensor"]
51 | self.notify_input_boolean = self.args["notify_input_boolean"]
52 | self.notify_name = self.args["notify_name"]
53 | self.message = self.args["message"]
54 | try:
55 | self.acceptable_range = self.args["acceptable_range"]
56 | except KeyError:
57 | self.acceptable_range = 1.2
58 | try:
59 | self.notify_use_Alexa = self.args["notify_use_Alexa"]
60 | except KeyError:
61 | self.notify_use_Alexa = True
62 |
63 | self.notifier = self.get_app("Notifier")
64 |
65 | self.listen_state_handle_list.append(
66 | self.listen_state(self.state_change, self.sensor, attribute="all")
67 | )
68 |
69 | def state_change(self, entity, attributes, old, new, kwargs) -> None:
70 | self.log("entity: {}".format(entity))
71 | self.log("old: {}".format(old))
72 | self.log("new: {}".format(new))
73 |
74 | duration_in_traffic_minutes = self.parse_duration_in_traffic_minutes(new)
75 | self.log("duration_in_traffic_minutes: {}".format(duration_in_traffic_minutes),)
76 |
77 | duration_minutes = self.parse_duration_minutes(new)
78 | self.log("duration_minutes: {}".format(duration_minutes))
79 |
80 | if duration_minutes is None or duration_in_traffic_minutes is None:
81 | self.log("Sensor is None. Homeassistant might not be fully started.")
82 | else:
83 | if duration_in_traffic_minutes <= duration_minutes * self.acceptable_range:
84 | if self.get_state(self.notify_input_boolean) == "on":
85 | destination_address = self.parse_destination_address(new)
86 | self.notify_user(destination_address)
87 | self.turn_off(self.notify_input_boolean)
88 |
89 | def notify_user(self, address: str) -> None:
90 | """Notify the user it is time to leave for the given address."""
91 | message = self.message.format(address)
92 | self.log("Notify user")
93 | self.notifier.notify(self.notify_name, message, useAlexa=self.notify_use_Alexa)
94 |
95 | def parse_duration_in_traffic_minutes(self, state) -> Optional[int]:
96 | """Get duration_in_traffic from the states attributes."""
97 | duration_in_traffic = state["attributes"].get("duration_in_traffic")
98 | duration_in_traffic_minutes = None
99 | if duration_in_traffic is not None:
100 | if isinstance(duration_in_traffic, float):
101 | duration_in_traffic_minutes = int(duration_in_traffic)
102 | else:
103 | duration_in_traffic_minutes = int(
104 | duration_in_traffic[: duration_in_traffic.find(" ")]
105 | )
106 | else:
107 | self.log(
108 | "Could not find duration_in_traffic in state attributes.",
109 | level="WARNING",
110 | )
111 | return duration_in_traffic_minutes
112 |
113 | def parse_destination_address(self, state) -> Optional[str]:
114 | """Get a destination address from the states attributes."""
115 | attributes = state["attributes"]
116 | destination_address = None
117 | if "destination_name" in attributes:
118 | destination_address = attributes["destination_name"]
119 | elif "destination_addresses" in attributes:
120 | destination_address = attributes["destination_addresses"][0]
121 | else:
122 | self.log(
123 | "Could not find destination_name or destination_addresses in state attributes.",
124 | level="WARNING",
125 | )
126 | return destination_address
127 |
128 | def parse_duration_minutes(self, state) -> Optional[int]:
129 | """Get duration from the states attributes."""
130 | duration_minutes = None
131 | duration = state["attributes"].get("duration")
132 | if duration is not None:
133 | if isinstance(duration, float):
134 | duration_minutes = int(duration)
135 | else:
136 | duration_minutes = int(duration[: duration.find(" ")])
137 | else:
138 | self.log("Could not find duration in state attributes.", level="WARNING")
139 | return duration_minutes
140 |
141 | def terminate(self) -> None:
142 | for listen_state_handle in self.listen_state_handle_list:
143 | self.cancel_listen_state(listen_state_handle)
144 |
--------------------------------------------------------------------------------
/travelTimeNotifier/travelTimeNotifier.yaml:
--------------------------------------------------------------------------------
1 | # travelTime_home_from_work:
2 | # module: travelTimeNotifier
3 | # class: TravelTimeNotifier
4 | # sensor: sensor.travel_time_home_from_work_here
5 | # notify_input_boolean: input_boolean.travel_time_home_from_work
6 | # notify_name: group_notifications
7 | # message: "Du kannst losfahren nach {}"
8 | # #message: "You can start your journey to {}"
9 | # notify_use_Alexa: False
10 | # dependencies:
11 | # - Notifier
12 |
13 | # travelTime_work_from_home:
14 | # module: travelTimeNotifier
15 | # class: TravelTimeNotifier
16 | # sensor: sensor.travel_time_work_from_home_here
17 | # notify_input_boolean: input_boolean.travel_time_work_from_home
18 | # notify_name: group_notifications
19 | # message: "Du kannst losfahren nach {}"
20 | # #message: "You can start your journey to {}"
21 | # dependencies:
22 | # - Notifier
23 |
24 | # travelTime_elmo_from_home:
25 | # module: travelTimeNotifier
26 | # class: TravelTimeNotifier
27 | # sensor: sensor.travel_time_elmo_from_home_here
28 | # notify_input_boolean: input_boolean.travel_time_elmo_from_home
29 | # notify_name: group_notifications
30 | # message: "Du kannst losfahren nach {}"
31 | # #message: "You can start your journey to {}"
32 | # dependencies:
33 | # - Notifier
34 |
35 | # travelTime_home_from_elmo:
36 | # module: travelTimeNotifier
37 | # class: TravelTimeNotifier
38 | # sensor: sensor.travel_time_home_from_elmo_here
39 | # notify_input_boolean: input_boolean.travel_time_home_from_elmo
40 | # notify_name: group_notifications
41 | # message: "Du kannst losfahren nach {}"
42 | # #message: "You can start your journey to {}"
43 | # dependencies:
44 | # - Notifier
45 |
46 | # travelTime_work_user_two_from_home:
47 | # module: travelTimeNotifier
48 | # class: TravelTimeNotifier
49 | # sensor: sensor.travel_time_work_user_two_from_home_here
50 | # notify_input_boolean: input_boolean.travel_time_work_user_two_from_home
51 | # notify_name: group_notifications
52 | # message: "Du kannst losfahren nach {}"
53 | # #message: "You can start your journey to {}"
54 | # dependencies:
55 | # - Notifier
56 |
57 | # travelTime_home_from_work_user_two_here:
58 | # module: travelTimeNotifier
59 | # class: TravelTimeNotifier
60 | # sensor: sensor.travel_time_home_from_work_user_two_here
61 | # notify_input_boolean: input_boolean.travel_time_home_from_work_user_two
62 | # notify_name: group_notifications
63 | # message: "Du kannst losfahren nach {}"
64 | # #message: "You can start your journey to {}"
65 | # dependencies:
66 | # - Notifier
67 |
--------------------------------------------------------------------------------
/turnFanOnWhenHot/turnFanOnWhenHot.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | import datetime
3 |
4 | #
5 | # App to Turn on fan when temp is above a threshold and someone is in the room
6 | #
7 | # Args:
8 | #
9 | # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
10 | # temp_sensor: temp sensor to monitor. example: sensor.large_lamp_temperature
11 | # threshold_entity: entity which holds the temp threshold which must be reached. example: input_number.turn_fan_on_when_hot_threshold
12 | # location_sensors: location sensors of users. example: sensor.location_user_one,sensor.location_user_two
13 | # room: Room name in which one of the users must be. example: Wohnzimmer
14 | # actor: actor to turn on
15 | # delay: seconds to wait before turning off. example: 120
16 | # Release Notes
17 | #
18 | # Version 1.3:
19 | # Added delay
20 | #
21 | # Version 1.2:
22 | # Using entities from HA now. Added turned_on_by_me
23 | #
24 | # Version 1.1:
25 | # Only turn on when someone is in the room. Turn off otherwise
26 | #
27 | # Version 1.0:
28 | # Initial Version
29 |
30 |
31 | class TurnFanOnWhenHot(hass.Hass):
32 | def initialize(self):
33 | self.listen_state_handle_list = []
34 | self.timer_handle_list = []
35 |
36 | self.app_switch = self.args["app_switch"]
37 | self.temp_sensor = self.args["temp_sensor"]
38 | self.threshold_entity = self.args["threshold_entity"]
39 | self.location_sensors = self.args["location_sensors"].split(",")
40 | self.room = self.args["room"]
41 | self.actor = self.args["actor"]
42 | self.delay = self.args["delay"]
43 |
44 | self.turned_on_by_me = False # Giggedi
45 |
46 | self.turn_off_timer_handle = None
47 |
48 | self.listen_state_handle_list.append(
49 | self.listen_state(self.state_change, self.temp_sensor)
50 | )
51 | for sensor in self.location_sensors:
52 | self.listen_state_handle_list.append(
53 | self.listen_state(self.state_change, sensor)
54 | )
55 |
56 | def state_change(self, entity, attribute, old, new, kwargs):
57 | if self.get_state(self.app_switch) == "on":
58 | turn_on = False
59 | if (
60 | self.get_state(self.temp_sensor) != None
61 | and self.get_state(self.temp_sensor) != "unkown"
62 | and self.get_state(self.threshold_entity) != None
63 | and float(self.get_state(self.temp_sensor))
64 | > float(self.get_state(self.threshold_entity))
65 | ):
66 | for sensor in self.location_sensors:
67 | if self.get_state(sensor) == self.room:
68 | if self.get_state(self.actor) != "on":
69 | self.log(
70 | "{} is {}. This is above theshold of {}".format(
71 | self.friendly_name(self.temp_sensor),
72 | self.get_state(self.temp_sensor),
73 | self.get_state(self.threshold_entity),
74 | )
75 | )
76 | self.log("{} is in {}".format(sensor, self.room))
77 | self.log(
78 | "Turning on {}".format(self.friendly_name(self.actor))
79 | )
80 | self.turn_on(self.actor)
81 | self.turned_on_by_me = True
82 | turn_on = True
83 | if self.turn_off_timer_handle != None:
84 | self.timer_handle_list.remove(self.turn_off_timer_handle)
85 | self.cancel_timer(self.turn_off_timer_handle)
86 | self.turn_off_timer_handle = None
87 | if not turn_on and self.turned_on_by_me:
88 | if self.get_state(self.actor) != "off":
89 | self.turn_off_timer_handle = self.run_in(
90 | self.turn_off_callback, self.delay
91 | )
92 | self.timer_handle_list.append(self.turn_off_timer_handle)
93 |
94 | def turn_off_callback(self, kwargs):
95 | """Turn off the actor again if the timer was not cancelled in the meantime"""
96 | self.log("Turning off {}".format(self.friendly_name(self.actor)))
97 | self.turn_off(self.actor)
98 | self.turned_on_by_me = False
99 | self.turn_off_timer_handle = None
100 |
101 | def terminate(self):
102 | for listen_state_handle in self.listen_state_handle_list:
103 | self.cancel_listen_state(listen_state_handle)
104 |
105 | for timer_handle in self.timer_handle_list:
106 | self.cancel_timer(timer_handle)
107 |
--------------------------------------------------------------------------------
/turnFanOnWhenHot/turnFanOnWhenHot.yaml:
--------------------------------------------------------------------------------
1 | # turnLargeFanOnWhenHot:
2 | # module: turnFanOnWhenHot
3 | # class: TurnFanOnWhenHot
4 | # app_switch: input_boolean.turn_large_fan_on_when_hot
5 | # temp_sensor: sensor.large_ventilator_temperature
6 | # threshold_entity: input_number.turn_large_fan_on_when_hot_threshold
7 | # location_sensors: sensor.location_user_one,sensor.location_user_two
8 | # room: Wohnzimmer
9 | # actor: switch.large_ventilator
10 | # delay: 120
11 |
12 | # turnSmallFanOnWhenHot:
13 | # module: turnFanOnWhenHot
14 | # class: TurnFanOnWhenHot
15 | # app_switch: input_boolean.turn_small_fan_on_when_hot
16 | # temp_sensor: sensor.small_ventilator_temperature
17 | # threshold_entity: input_number.turn_small_fan_on_when_hot_threshold
18 | # location_sensors: sensor.location_user_one,sensor.location_user_two
19 | # room: Wohnzimmer
20 | # actor: switch.small_ventilator
21 | # delay: 120
--------------------------------------------------------------------------------
/turnOffBarAfterRestart/turnOffBarAfterRestart.py:
--------------------------------------------------------------------------------
1 | import appdaemon.plugins.hass.hassapi as hass
2 | from requests.exceptions import HTTPError
3 |
4 | #
5 | # Will turn the bar table green and then off when homeassistant restarts to indicate the restart went well
6 | #
7 | #
8 | # Args:
9 | #
10 | # light: light. example: light.bar_table
11 | #
12 | # Release Notes
13 | #
14 | # Version 1.0:
15 | # Initial Version
16 |
17 |
18 | class TurnOffBarAfterRestart(hass.Hass):
19 | def initialize(self):
20 |
21 | self.timer_handle_list = []
22 | self.listen_event_handle_list = []
23 | self.listen_state_handle_list = []
24 |
25 | self.light = self.args["light"]
26 |
27 | self.timer_handle_list.append(self.run_in(self.turn_green_callback, 1))
28 |
29 | def turn_off_callback(self, kwargs):
30 | """Turn off light"""
31 | try:
32 | self.log("Turning {} off".format(self.friendly_name(self.light)))
33 | self.turn_off(self.light)
34 | except HTTPError as exception:
35 | self.log(
36 | "Error trying to turn off entity. Will try again in 1s. Error: {}".format(
37 | exception
38 | ),
39 | level="WARNING",
40 | )
41 | self.timer_handle_list.append(self.run_in(self.turn_off_callback, 1))
42 |
43 | def turn_green_callback(self, kwargs):
44 | """This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting"""
45 | try:
46 | self.call_service(
47 | "light/turn_on",
48 | entity_id=self.light,
49 | rgb_color=[0, 255, 0],
50 | white_value=0,
51 | )
52 | self.log("Turning {} green".format(self.friendly_name(self.light)))
53 | self.timer_handle_list.append(self.run_in(self.turn_off_callback, 5))
54 | except HTTPError as exception:
55 | self.log(
56 | "Error trying to turn on entity. Will try again in 1s. Error: {}".format(
57 | exception
58 | ),
59 | level="WARNING",
60 | )
61 | self.timer_handle_list.append(self.run_in(self.turn_green_callback, 1))
62 |
63 | def terminate(self):
64 | for timer_handle in self.timer_handle_list:
65 | self.cancel_timer(timer_handle)
66 |
67 | for listen_event_handle in self.listen_event_handle_list:
68 | self.cancel_listen_event(listen_event_handle)
69 |
70 | for listen_state_handle in self.listen_state_handle_list:
71 | self.cancel_listen_state(listen_state_handle)
72 |
--------------------------------------------------------------------------------
/turnOffBarAfterRestart/turnOffBarAfterRestart.yaml:
--------------------------------------------------------------------------------
1 | # turnOffBarAfterRestart:
2 | # module: turnOffBarAfterRestart
3 | # class: TurnOffBarAfterRestart
4 | # light: light.bar_table
--------------------------------------------------------------------------------