Full Code of eifinger/appdaemon-scripts for AI

master 0a8e19f5616b cached
100 files
298.5 KB
69.5k tokens
276 symbols
1 requests
Download .txt
Showing preview only (325K chars total). Download the full file or copy to clipboard to get everything.
Repository: eifinger/appdaemon-scripts
Branch: master
Commit: 0a8e19f5616b
Files: 100
Total size: 298.5 KB

Directory structure:
gitextract_92xa7u3p/

├── .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/
│   └── deconz_xiaomi_button.py
├── deconz_xiaomi_button.yaml
├── 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
├── 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

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

#appdaemon
secrets.py

#VS Code
.vscode

#PyCharm
.idea

#AppDaemon
secrets.yaml


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Kevin Eifinger

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">
  <a name="logo" href=""><img src="images/logo-round-192x192.png" alt="Home Assistant Logo" width="192"></a>
  <br>
  eifinger's Appdaemon Scripts
</h1>

## About

This is the repository containing all my Appdaemon apps.

Used together with my Homeassistant config which you can find here:

[https://github.com/eifinger/homeassistant-config](https://github.com/eifinger/homeassistant-config)

I use Appdaemon for all my automations since I am a programmer myself and it provides me with all the possibilities
of the Python world and a far better debugging experience than
[HA Automations](https://www.home-assistant.io/getting-started/automation/) or [nodered](https://nodered.org/).

### No longer actively maintained

I no longer use appdaemon for my automations and switched over to automations completely:

* Traces allow easy debugging of what happened and why
* I can see related entities from automations and vise versa
* I can quickly adjust automations via the UI editor on my smartphone
* There is no disconnect/lag between Appdaemon and Homeassistant.

## How to contribute

Just open an Issue or a Pull Request for any Comments, Questions, etc.

**Or you can message me on twitter :** [@eifinger](https://twitter.com/eifinger)

## How to use

If you have never used Appdaemon before I suggest you start with the
[tutorial](https://appdaemon.readthedocs.io/en/latest/TUTORIAL.html) and the
[guide](https://appdaemon.readthedocs.io/en/latest/APPGUIDE.html).
Both contain more links to great tutorials and examples.

I tried to write each App in this repository with reusability in mind.
This means that every app in here has a short documentation
and is (if possible) written to be easily adjusted to your environment and your needs.

### app_switch

Every App has an input_boolean inside HA which turns it on/off.
This is useful if I don't want any notifications right now or an App is misbehaving.

## App list

* [Alexa Intents](#alexaintents)
* [AlexaSpeakerConnector](#alexaspeakerconnector)
* [appWatcher](#appWatcher)
* [alarmClock](#alarmclock)
* [buttonClicked](#buttonclicked)
* [comingHome](#cominghome)
* [deconzXiaomiButton](#deconzxiaomibutton)
* [detectWrongState](#detectwrongstate)
* [eventMonitor](#eventmonitor)
* [faceRecognitionBot](#facerecognitionbot)
* [google_travel_time](#google_travel_time)
* [heartbeat](#heartbeat)
* [homeArrivalNotifier](#homearrivalnotifier)
* [isHomeDeterminer](#ishomedeterminer)
* [isUserHomeDeterminer](#isuserhomedeterminer)
* [leavingZoneNotifier](#leavingzonenotifier)
* [motionTrigger](#motiontrigger)
* [newWifiDeviceNotify](#newwifidevicenotify)
* [nextAppointmentLeaveNotifier](#nextappointmentleavenotifier)
* [notifyOfActionWhenAway](#notifyofactionwhenaway)
* [plantWateringNotifier](#plantwateringnotifier)
* [powerUsageNotification](#powerusagenotification)
* [seqSink](#seqsink)
* [setThermostat](#setthermostat)
* [setThermostatOnStateChange](#setthermostatonstatechange)
* [sleepModeHandler](#sleepmodehandler)
* [turnFanOnWhenHot](#turnfanonwhenhot)
* [turnOffBarAfterRestart](#turnoffbarafterrestart)
* [updateEntityService](#updateentityservice)
* [notify](#notify)

### AlexaIntents

Are explained [here](alexa/README.md)

### AlexaSpeakerConnector

App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers.
Uses a [custom_component](https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639)
for control alexa as a media player.

```yaml
alexaSpeakerConnector:
  module: alexaSpeakerConnector
  class: AlexaSpeakerConnector
  app_switch: input_boolean.alexaSpeakerConnector
  alexa_entity: media_player.kevins_echo_dot_oben
  alexa_entity_source: Denon AVR-X1300W
  receiver: media_player.denon_avr_x1300w
  receiver_source: Bluetooth
```

### appWatcher

Sends a notification if a WARNING or ERROR is logged in Appdaemon

```yaml
heartbeat:
  module: appWatcher
  class: AppWatcher
  notify_name: kevin
  include_log_message_in_notification: True
  notify_message: "Es ist ein Fehler aufgetreten in App: {}"
  #notify_message: "An Error occurred in App: {}"
  dependencies:
    - Notifier
```

### alarmClock

Alarm Clock App inspired by [this](https://community.home-assistant.io/t/creating-a-alarm-clock/410)
forum post.
It fades in my bedroom light and sends a notifcation.
The fade in and alarm time is defined by input_number sliders in HA

```yaml
alarmClock:
  module: alarmClock
  class: AlarmClock
  alarm_time: sensor.alarm_time
  wakemeup: input_boolean.wakemeup
  naturalwakeup: input_number.alarm_natural_wakeup_fade_in
  alarmweekday: input_boolean.alarmweekday
  radiowakeup: input_boolean.radiowakeup
  #TODO radioplayer: input_select.wakeup_radioplayer
  wakeup_light: light.bedroom_yeelight
  isweekday: binary_sensor.workday_today
  notify_name: group_notifications
  message: "Guten Morgen!"
  #message: "Good Morning!"
```

![alarmClock](images/alarmClock.PNG)

### buttonClicked

My multipurpose App to link any switch/light to a Xiaomi Button.

You can map different entities to the click types ``single`` and ``double``.

For the 1st Generation Button you can hold the button to use it as a light dimmer.

```yaml
xiaomiroundButtonBedroomClicked:
  module: buttonClicked
  class: ButtonClicked
  sensor: binary_sensor.switch_158d0001b12a12
  actor_single: light.bedroom_yeelight
  actor_double: group.all
  actor_hold: light.bedroom_yeelight
  dependencies:
    - Notifier
```

### comingHome

When the front door openes and no one was home before this will turn on something.
I am using it to turn on the light (if the sun is down) and turn on the receiver so I can hear Alexa

```yaml
comingHomeYeelight:
  module: comingHome
  class: ComingHome
  app_switch: input_boolean.coming_home_yeelight
  sensor: binary_sensor.door_window_sensor_158d000126a57b
  isHome: input_boolean.is_home
  actor: switch.large_lamp
  after_sundown: True
```

### deconzXiaomiButton

App which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz.

This app is installed via [HACS](https://github.com/custom-components/hacs).

The repository itself with the full documentation can be found on github:
[appdaemon-deconz-xiaomi-button](https://github.com/eifinger/appdaemon-deconz-xiaomi-button)

```yaml
DeconzXiaomiButtonBedroom:
  module: deconz_xiaomi_button
  class: DeconzXiaomiButton
  id: round_button_schlafzimmer
  actor_single: light.bedroom_yeelight
  actor_double: group.all
  actor_hold: light.bedroom_yeelight
```

### detectWrongState

Checks a list of entities which should be on/off when everybody left the house.
If something isn't right it will try to turn it off (e.g. a light) and send a notification.

```yaml
detectWrongStateWhenLeaving:
  module: detectWrongStateWhenLeaving
  class: DetectWrongStateWhenLeaving
  app_switch: input_boolean.detect_wrong_state_when_leaving
  entities_off: "binary_sensor.door_window_sensor_158d000205b808,binary_sensor.door_window_sensor_158d00020499ad,\
  binary_sensor.door_window_sensor_158d0002059ddf,media_player.denon_avr_x1300w,switch.large_lamp,\
  switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\
  light.reading_lamp_yeelight,light.treppe_oben,light.stairs_lower_yeelight,switch.ventilator"
  message: "Du hast {} angelassen. Ich habe es für dich ausgemacht."
  #message: "You left on {}. I turned it off for you"
  message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht."
  #message_off: "You forgot to turn on {}. I turned it on for you"
  message_reed: "Du hast {} offen gelassen."
  #message_reed: "You left open {} Dummy."
  message_reed_off: "Du hast {} zu gelassen."
  #message_reed_off: "You left {} closed Dummy."
  isHome: input_boolean.is_home
```

### eventMonitor

Monitor all events. Useful for debugging and developing

```yaml
eventMonitor:
  module: eventMonitor
  class: Monitor
  events:
```

### faceRecognitionBot

COMING SOON

### travel_time

Monitors my Travel Time Sensors e.g. between home and work.
I can enable an input_boolean in HA which causes this App to send me a notication
as soon as the traffic is in an acceptable range. I use this drive to/from work when there is the least traffic.

```yaml
travelTime_home_from_work:
  module: travelTimeNotifier
  class: TravelTimeNotifier
  sensor: sensor.travel_time_home_from_work
  notify_input_boolean: input_boolean.travel_time_home_from_work
  notify_name: group_notifications
  message: "Du kannst losfahren nach {}"
  #message: "You can start your journey to {}"
```

![travelTimeNotifier](images/googleTravelTimes.PNG)

### heartbeat

Sets a sensor in Homeassistant which is checked by an automation.
The [automation](https://github.com/eifinger/homeassistant-config/blob/master/automation.yaml)
sends out a notification if appdaemon does not respond.

### homeArrivalNotifier

Greet the person coming home with a notification

```yaml
homeArrivalNotifierUserOne:
  module: homeArrivalNotifier
  class: HomeArrivalNotifier
  app_switch: input_boolean.home_arrival_notifier_user_one
  input_boolean: input_boolean.user_one_home
  notify_name: group_notifications
  user_name: Kevin
  zone_name: Home
  message: "Willkommen zu Hause {}."
  #message: "Welcome Home {}."
```

### isHomeDeterminer

Controls an input_boolean "isHome" which is used as a trigger for other Apps.
The state depends on other input_booleans controlled by the
[isUserHomeDeterminer](isUserHomeDeterminer/isUserHomeDeterminer.py)

```yaml
isHomeDeterminer:
  module: isHomeDeterminer
  class: IsHomeDeterminer
  app_switch: input_boolean.is_home_determiner
  ishome: input_boolean.is_home
  input_booleans: input_boolean.user_one_home,input_boolean.user_two_home
  message: "Es ist keiner mehr zu Hause. Setze isHome auf off"
  #message: "Everyone left home. Setting isHome to off"
```

### isUserHomeDeterminer

The GPS Logger tells me where someone is. But I want to know for sure who just came in the door.
App to toggle an input boolean when a person enters or leaves home.
This is determined based on a combination of a GPS device tracker and the door sensor.

* If the door sensor opens and the device_tracker changed to "home" in the last self.delay minutes this means someone got home
* If the door sensor opens and the device_tracker changes to "not_home" in the next self.delay minutes this means someone left home

```yaml
isUserHomeDeterminerUserOne:
  module: isUserHomeDeterminer
  class: IsUserHomeDeterminer
  app_switch: input_boolean.is_user_home_determiner_user_one
  input_boolean: input_boolean.user_one_home
  device_tracker: person.kevin
  door_sensor: binary_sensor.door_window_sensor_158d000126a57b
```

### leavingZoneNotifier

Notify if a user is leaving a zone after being there for a certain amount of time.
I use this to notify my SO that I am leaving work and driving home

```yaml
leavingWorkNotifierUserOne:
  module: leavingZoneNotifier
  class: LeavingZoneNotifier
  app_switch: input_boolean.leaving_work_notifier_user_one
  device: person.kevin
  user_name: Kevin
  lingering_time: 3600
  delay: 120
  zone: Arbeit
  notify_name: group_notifications
  message: "{} hat {} vor {} Minuten verlassen."
  travel_time_sensor: sensor.travel_time_home_user_one
  travel_time_sensor_message: "Die momentane Reisezeit beträgt {}."
  dependencies:
    - Notifier
```

### motionTrigger

Turn something on/off when a motion sensor turns on. Automatically turn it off again after a delay.

```yaml
bathMotionTrigger:
  module: motionTrigger
  class: MotionTrigger
  app_switch: input_boolean.bath_motion_trigger
  sensor: binary_sensor.0x00158d000236b801_occupancy
  entity_on: light.lower_bathroom_yeelight
  entity_off: light.lower_bathroom_yeelight
  sensor_type: zigbee2mqtt
  after_sundown: True
  turn_off_constraint_entities_on: binary_sensor.0x00158d0001fa464b_occupancy
  delay: 300
```

### newWifiDeviceNotify

Actually a wrong name. This will send me a notification when any device_tracker component detects a new device.
I initally thought to use this as a security feature but found it quite useful when adding new Sonoff switches and such.
I get a notification if the setup was successfull.

**Version 1.2:**
Displays two buttons which let me control the internet access for the new device.
The configured fritzbox standard profile denies internet access.
With this I can easily allow my guests access to the internet without logging in to my fritzbox manually.

```yaml
newWifiDeviceNotify:
  module: newWifiDeviceNotify
  class: DeviceNotify
  notify_name: group_notifications
  message: "Unbekanntes Gerät entdeckt. Hostname: {}. MAC: {}."
  #message: "Unknown device connected. Hostname: {}. MAC: {}"
```

### nextAppointmentLeaveNotifier

Send me a notification when it is time to leave for my next appointment based on my current location.
Inspired by [this](https://community.home-assistant.io/t/text-to-speech-notification-to-leave-for-appointment/8689)
blog post.

* Selectable travel mode (car/bus/walk/bike)
* Only for google calendar events which have a location
* Adjustable offset when to notify
* Includes a direct Google Maps Navigation Link in Notification Message

Saved my ass quite a few times

```yaml
nextAppointmentLeaveNotifier:
  module: nextAppointmentLeaveNotifier
  class: NextAppointmentLeaveNotifier
  sensor: sensor.calc_leave_time
  notify_input_boolean: input_boolean.announce_time_to_leave
  notify_name: group_notifications
  input_number: input_number.leave_time_offset
  destination_name_sensor: sensor.cal_next_appointment_location
  travel_time_sensor: sensor.travel_time_next_appointment_location
  message: "Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten. Hier ist ein Google Maps Link: {}"
  #message: "It's time to leave to {}. It will take {} minutes. Here is a Google Maps Link: {}"
```

![nextAppointmentLeaveNotifier](images/next_appoint_leave_modifier.PNG)
![next_appoint_leave_modifier_notification](images/next_appoint_leave_modifier_notification.PNG)

### notifyOfActionWhenAway

Notify me of any event for a list of entities when no one is at home.
For example a door being openend or a motion sensor triggered

```yaml
notifyOfActionWhenAway:
  module: notifyOfActionWhenAway
  class: NotifyOfActionWhenAway
  app_switch: input_boolean.notify_of_action_when_away
  sensor: "binary_sensor.door_window_sensor_158d000126a57b,binary_sensor.door_window_sensor_158d0001bb4d94,\
  binary_sensor.door_window_sensor_158d0001bb4dc0,binary_sensor.door_window_sensor_158d000205b808,\
  binary_sensor.door_window_sensor_158d000205b82e,binary_sensor.door_window_sensor_158d00020498b6,\
  binary_sensor.door_window_sensor_158d000204ba26,binary_sensor.door_window_sensor_158d0002059ddf,\
  binary_sensor.door_window_sensor_158d00020499ad,binary_sensor.door_window_sensor_158d0002048951,\
  binary_sensor.door_window_sensor_158d00020455bf,binary_sensor.motion_sensor_158d00012aab97,\
  binary_sensor.motion_sensor_158d0001fa464b,binary_sensor.motion_sensor_158d0002006cfa"
  isHome: input_boolean.is_home
  user_name: group_notifications
  isHome_delay: 20
  message: "Alarm: {} ist gewechselt auf {}"
  #message: "Alarm: {} changed to {}"
```

![notifyOfActionWhenAway](images/notifyOfActionWhenAway.PNG)

### plantWateringNotifier

Remind us to water the plants in the morning when the precipiation propability is too low.
This uses a Telegram Chatbot. We can press a button in the notification to tell the App that we watered the plants.
If we don't do that we get reminded again in the evening.

```yaml
plantWateringNotifier:
  module: plantWateringNotifier
  class: PlantWateringNotifier
  app_switch: input_boolean.plant_watering_notifier
  rain_precip_sensor: sensor.dark_sky_precip_probability
  rain_precip_intensity_sensor: sensor.dark_sky_precip_intensity
  precip_type_sensor: sensor.dark_sky_precip
  notify_name: group_notifications
  user_id: secret! telegram_user_id
  reminder_acknowledged_entity: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged
  message: "Die Regenwahrscheinlichkeit beträgt heute nur {}. Vergiss nicht die Pflanzen zu gießen!"
  #message: "The Rain Propability is only {}. Don't forget to water the plants!"
  message_not_needed: "Es wird heute mit einer Wahrscheinlichkeit von {} Prozent ungefähr {} Millimeter pro Stunde regnen. Du brauchst nicht selbst gießen."
  #message_not_needed: "It will rain today {} millimeter per hour with a propability of {}. You don't have to water your plants"
  message_evening: "Ich bin mir nicht sicher ob du vergessen hast die Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran."
  #message_evening: "I'm not sure whether you waterd your plants, so I thought I better remind you again"
```

![plantWateringReminder](images/plantWateringReminder.PNG)
![plantWateringReminderAcknowledged](images/plantWateringReminderAcknowledged.PNG)

### pollenNotifier

Notify in the morning if any monitored pollen level is above a threshold.

```yaml
roggenNotifier:
  module: pollenNotifier
  class: PollenNotifier
  app_switch: input_boolean.roggen_notifier
  pollen_sensor: sensor.pollen_101_roggen_today
  pollen_name: Roggen
  notify_name: group_notifications
  notify_time: 08:00
  notify_threshold: 1.0
  message: "{} ist {} {} Belastung."
  #message: "The {} intensity {} is {}."
  message_no_data: "Ich habe {} leider keine Daten für {}."
  #message_no_data: "{} I have no pollen data for {}."
```

![roggenNotify](images/roggenNotify.PNG)

### powerUsageNotification

Notify when the Washingmachine or Dishwasher started/finished. Using power measured by TP HS110 Plugs like[![](https://static.tp-link.com/res/images/products/HS110_us_V1_1133_normal_0_20151017154946.jpg)](https://www.amazon.de/dp/B017X72IES/ref=twister_B07CQBCZ5G)

```yaml
powerUsageNotification_Dishwasher:
  module: powerUsageNotification
  class: PowerUsageNotification
  app_switch: input_boolean.power_usage_notification_dishwasher
  sensor: sensor.dishwasher_power_usage
  notify_name: group_notifications
  delay: 1260 #21 minutes
  threshold: 2
  alternative_name: Die Spülmaschine
  message: "{} ist gestartet."
  #message: "{} just started."
  message_off: "{} ist fertig."
  #message_off: "{} just finished."
```

![dishWasherNotify](images/dishWasherNotify.PNG)

### seqSink

App which forwards all logs to seq.
Blogged about this app in [this](https://blog.kevineifinger.de/archive/2020/07/06/Log-Management-For-My-AppDaemon-Apps.html) post.

```yaml
seqSink:
  module: seqSink
  class: SeqSink
  server_url: "http://seq:5341/"
```

### setThermostat

App which sets a thermostat to a target temperature for a specific duration

```yaml
warm_bath_before_wakeup:
  module: setThermostat
  class: SetThermostat
  app_switch: input_boolean.warm_bath_before_wakeup
  isHome: input_boolean.is_home
  time_entity: sensor.alarm_time
  upfront_time: 60
  duration: 60
  climat_entity: climate.bad_thermostat
  target_entity: input_number.warm_bath_before_wakeup
  message: "Ich habe {} auf {} gestellt"
  #message: "I have set {} to {}"
  notify_name: group_notifications
  use_alexa: False
  dependencies:
    - Notifier
```

### setThermostatOnStateChange

App which sets a thermostat to a target temperature on state change.

```yaml
setBadObenThermostatWhenComingHome:
  module: setThermostatOnStateChange
  class: SetThermostatOnStateChange
  app_switch: input_boolean.set_upper_bath_thermostat_when_coming_home
  trigger_entity: input_boolean.is_home
  trigger_state: "on"
  climate_entity: climate.bad_oben_thermostat
  target_entity: input_number.set_upper_bath_thermostat_when_coming_home
  message: "Ich habe {} auf {} °C gestellt"
  #message: "I have set {} to {}"
  notify_name: group_notifications
  use_alexa: False
  dependencies:
    - Notifier
```

### sleepModeHandler

Set an input_boolean on/off. Used as a trigger for other Apps.
Also controlled by ``Alexa, guten Morgen`` ``Alexa, gute Nacht``

Will watch room sensor of users

````yaml
sleepModeHandler:
  module: sleepModeHandler
  class: SleepModeHandler
  app_switch: input_boolean.sleep_mode_handler
  sleepmode: input_boolean.sleepmode
  notify_name: group_notifications
  message_sleeping: "Alle zu Hause sind im Bett"
  #message_sleeping: "All home are in bed"
  message_awake: "Alle zu Hause sind wach"
  #message_awake: "All home are awake"
  users:
    - sleep_mode: input_boolean.user_one_sleep
      isHome: input_boolean.user_one_home
    - sleep_mode: input_boolean.user_two_sleep
      isHome: input_boolean.user_two_home
  dependencies:
    - Notifier
````

````yaml
userSleepModeHandlerUserOne:
  module: userSleepModeHandler
  class: UserSleepModeHandler
  app_switch: input_boolean.user_sleep_mode_handler_user_one
  input_boolean: input_boolean.user_one_sleep
  location_sensor: person.kevin
  room: bedroom
  duration: 600

userSleepModeHandlerUserTwo:
  module: userSleepModeHandler
  class: UserSleepModeHandler
  app_switch: input_boolean.user_sleep_mode_handler_user_two
  input_boolean: input_boolean.user_two_sleep
  location_sensor: sensor.mqtt_room_user_two
  room: bedroom
  duration: 600
````

### turnFanOnWhenHot

Turns the Fan on when the temperature is above a configurable threshold and someone is in the room
([find3](https://github.com/schollz/find3))

```yaml
turnFanOnWhenHot:
  module: turnFanOnWhenHot
  class: TurnFanOnWhenHot
  app_switch: input_boolean.turn_fan_on_when_hot
  temp_sensor: sensor.large_lamp_temperature
  threshold_entity: input_number.turn_fan_on_when_hot_threshold
  location_sensors: sensor.location_user_one,sensor.location_user_two
  room: Wohnzimmer
  actor: switch.large_ventilator
  delay: 120
```

![ventilatorAutomation](images/ventilatorAutomation.PNG)

### turnOffBarAfterRestart

As I sometimes restart HA when working on it from remote I turn the Bar lights to red with
[this script](https://github.com/eifinger/homeassistant-config/blob/master/updateHomeassistant.sh).
This way everyone can see HA is currently unavailable.
If it comes back up again this app will turn the light green and then off.

### notify

IN DEVELOPMENT
Centralizes messaging. Among other things, it will determine whether a user is at home and if yes in which room.

Then Alexa in that room will be used additionally to Telegram

```yaml
Notify:
  module: notify
  class: Notify
  media_player: media_player.denon_avr_x1300w
  source: CBL/SAT
  alexa_media_player: media_player.kevins_echo_dot_oben
  global_dependencies:
    - globals
```

# Thanks

First of all thanks to the Homeassistant Team and [Andrew Cockburn](https://github.com/acockburn) for making Appdaemon

Some of the Apps are taken from the official examples and many based on or at least inspired by
[Rene Tode](https://github.com/ReneTode).
For example his absolutely fantastic [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App).


================================================
FILE: ad-ench/ench.py
================================================
"""EnCh.
   Entity Checker

  @benleb / https://github.com/benleb/ad-ench
"""

__version__ = "0.9.0"

from datetime import datetime, timedelta
from fnmatch import fnmatch
from pprint import pformat
from sys import version_info
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

import hassapi as hass


APP_NAME = "EnCh"
APP_ICON = "👩‍⚕️"

BATTERY_MIN_LEVEL = 20
INTERVAL_BATTERY_MIN = 180
INTERVAL_BATTERY = INTERVAL_BATTERY_MIN / 60

INTERVAL_UNAVAILABLE_MIN = 60
INTERVAL_UNAVAILABLE = INTERVAL_UNAVAILABLE_MIN / 60
MAX_UNAVAILABLE_MIN = 0

INTERVAL_STALE_MIN = 15
MAX_STALE_MIN = 60

INITIAL_DELAY = 60

RANDOMIZE_SEC: int = 15
SECONDS_PER_MIN: int = 60

EXCLUDE = ["binary_sensor.updater", "persistent_notification.config_entry_discovery"]
BAD_STATES = ["unavailable", "unknown"]
LEVEL_ATTRIBUTES = ["battery_level", "Battery Level"]
CHECKS = ["battery", "stale", "unavailable"]

ICONS: Dict[str, str] = dict(battery="🔋", unavailable="⁉️ ", unknown="❓", stale="⏰")


# version checks
py3_or_higher = version_info.major >= 3
py37_or_higher = py3_or_higher and version_info.minor >= 7
py38_or_higher = py3_or_higher and version_info.minor >= 8


def hl(text: Union[int, float, str]) -> str:
    return f"\033[1m{text}\033[0m"


def hl_entity(entity: str) -> str:
    if len(splitted := entity.split(".")) > 1:
        return f"{splitted[0]}.{hl(splitted[1])}"
    else:
        return f"{hl(entity)}"


class EnCh(hass.Hass):  # type: ignore
    """EnCh."""

    def lg(self, msg: str, *args: Any, icon: Optional[str] = None, repeat: int = 1, **kwargs: Any) -> None:
        kwargs.setdefault("ascii_encode", False)
        message = f"{f'{icon} ' if icon else ' '}{msg}"
        _ = [self.log(message, *args, **kwargs) for _ in range(repeat)]

    async def initialize(self) -> None:
        """Register API endpoint."""
        self.icon = APP_ICON

        # python version check
        if not py38_or_higher:
            icon_alert = "⚠️"
            self.lg("", icon=icon_alert)
            self.lg("")
            self.lg(f"please update to {hl('Python >= 3.8')}! 🤪", icon=icon_alert)
            self.lg("")
            self.lg("", icon=icon_alert)
        if not py37_or_higher:
            raise ValueError

        self.cfg: Dict[str, Any] = dict()
        self.cfg["show_friendly_name"] = bool(self.args.get("show_friendly_name", True))
        self.cfg["init_delay_secs"] = int(self.args.get("initial_delay_secs", INITIAL_DELAY))

        # home assistant sensor
        hass_sensor: str
        if hass_sensor := self.args.get("hass_sensor", "sensor.ench_entities"):
            self.cfg["hass_sensor"] = hass_sensor if hass_sensor.startswith("sensor.") else f"sensor.{hass_sensor}"
            self.sensor_state: int = 0
            self.sensor_attrs: Dict[str, Any] = {check: [] for check in CHECKS}
            self.sensor_attrs.update({"unit_of_measurement": "Entities", "should_poll": False})

        # global notification
        if "notify" in self.args:
            self.cfg["notify"] = self.args.get("notify")

        # initial wait to give all devices a chance to become available
        init_delay = await self.datetime() + timedelta(seconds=self.cfg["init_delay_secs"])

        # battery check
        if "battery" in self.args:

            config: Dict[str, Union[str, int]] = self.args.get("battery")

            # store configuration
            self.cfg["battery"] = dict(
                interval_min=int(config.get("interval_min", INTERVAL_BATTERY_MIN)),
                min_level=int(config.get("min_level", BATTERY_MIN_LEVEL)),
            )

            # no, per check or global notification
            self.choose_notify_recipient("battery", config)

            # schedule check
            await self.run_every(
                self.check_battery,
                init_delay,
                self.cfg["battery"]["interval_min"] * 60,
                random_start=-RANDOMIZE_SEC,
                random_end=RANDOMIZE_SEC,
            )

        # unavailable check
        if "unavailable" in self.args:

            config = self.args.get("unavailable")

            # store configuration
            self.cfg["unavailable"] = dict(
                interval_min=int(config.get("interval_min", INTERVAL_UNAVAILABLE_MIN)),
                max_unavailable_min=int(config.get("max_unavailable_min", MAX_UNAVAILABLE_MIN)),
            )

            # no, per check or global notification
            self.choose_notify_recipient("unavailable", config)

            # schedule check
            self.run_every(
                self.check_unavailable,
                await self.datetime() + timedelta(seconds=self.cfg["init_delay_secs"]),
                self.cfg["unavailable"]["interval_min"] * 60,
                random_start=-RANDOMIZE_SEC,
                random_end=RANDOMIZE_SEC,
            )

        # stale entities check
        if "stale" in self.args:

            config = self.args.get("stale", {})
            interval_min = config.get("interval_min", INTERVAL_STALE_MIN)
            max_stale_min = config.get("max_stale_min", MAX_STALE_MIN)

            # store configuration
            self.cfg["stale"] = dict(
                interval_min=int(min([interval_min, max_stale_min])),
                max_stale_min=int(max_stale_min),
            )

            self.cfg["stale"]["entities"] = config.get("entities", [])

            # no, per check or global notification
            self.choose_notify_recipient("stale", config)

            # schedule check
            self.run_every(
                self.check_stale,
                await self.datetime() + timedelta(seconds=self.cfg["init_delay_secs"]),
                self.cfg["stale"]["interval_min"] * 60,
                random_start=-RANDOMIZE_SEC,
                random_end=RANDOMIZE_SEC,
            )

        # merge excluded entities
        exclude = set(EXCLUDE)
        exclude.update([e.lower() for e in self.args.get("exclude", set())])
        self.cfg["exclude"] = sorted(list(exclude))

        # set units
        self.cfg.setdefault(
            "_units",
            dict(interval_min="min", max_stale_min="min", min_level="%"),
        )

        self.show_info(self.args)

    async def check_battery(self, _: Any) -> None:
        """Handle scheduled checks."""
        check_config = self.cfg["battery"]
        results: List[Tuple[str, int]] = []

        self.lg("Checking entities for low battery levels...", icon=APP_ICON, level="DEBUG")

        states = await self.get_state()

        entities = filter(
            lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg["exclude"]),
            states,
        )

        for entity in sorted(entities):
            battery_level = None

            try:
                # check entities which may be battery level sensors
                if "battery_level" in entity or "battery" in entity:
                    # battery_level = int(await self.get_state(entity))
                    battery_level = int(states[entity]["state"])

                # check entity attributes for battery levels
                if not battery_level:
                    for attr in LEVEL_ATTRIBUTES:
                        # battery_level = int(await self.get_state(entity, attribute=attr))
                        battery_level = int(states[entity]["attributes"].get(attr))
                        break
            except (TypeError, ValueError):
                pass

            if battery_level and battery_level <= check_config["min_level"]:
                # results.append(entity)
                results.append((entity, battery_level))
                last_updated = (await self.last_update(entity)).time().isoformat(timespec="seconds")
                self.lg(
                    f"{await self._name(entity)} has low "
                    # f"{hl(f'battery → {hl(int(battery_level))}')}%",
                    f"{hl(f'battery → {hl(int(battery_level))}')}% | " f"last update: {last_updated}",
                    # f"last update: {self.adu.last_update(entity)}",
                    icon=ICONS["battery"],
                )

        # send notification
        notify = self.cfg.get("notify") or check_config.get("notify")
        if notify and results:
            await self.call_service(
                str(notify).replace(".", "/"),
                message=f"{ICONS['battery']} Battery low ({len(results)}): "
                f"{', '.join([f'{str(await self._name(entity[0], notification=True))} {entity[1]}%' for entity in results])}",  # noqa
            )

        # update hass sensor
        if "hass_sensor" in self.cfg and self.cfg["hass_sensor"]:
            await self.update_sensor("battery", [entity[0] for entity in results])

        self._print_result("battery", [entity[0] for entity in results], "low battery levels")

    async def check_unavailable(self, _: Any) -> None:
        """Handle scheduled checks."""
        check_config = self.cfg["unavailable"]
        results: List[str] = []

        self.lg("Checking entities for unavailable/unknown state...", icon=APP_ICON, level="DEBUG")

        entities = filter(
            lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg["exclude"]),
            await self.get_state(),
        )

        for entity in sorted(entities):
            state = await self.get_state(entity_id=entity)

            if state in BAD_STATES and entity not in results:

                last_update = await self.last_update(entity)
                now: datetime = await self.datetime(aware=True)
                unavailable_time: timedelta = now - last_update
                max_unavailable_min = timedelta(minutes=self.cfg["unavailable"]["max_unavailable_min"])

                if unavailable_time >= max_unavailable_min:
                    results.append(entity)
                    last_updated = (await self.last_update(entity)).time().isoformat(timespec="seconds")
                    self.lg(
                        f"{await self._name(entity)} is "
                        f"{hl(state)} since {hl(int(unavailable_time.seconds / 60))}min | "
                        f"last update: {last_updated}",
                        icon=ICONS[state],
                    )
                    # self.lg(
                    #     f"{await self._name(entity)} is {hl(state)} | " f"last update: {last_updated}",
                    #     icon=ICONS[state],
                    # )

        # send notification
        notify = self.cfg.get("notify") or check_config.get("notify")
        if notify and results:
            self.call_service(
                str(notify).replace(".", "/"),
                message=f"{APP_ICON} Unavailable entities ({len(results)}): "
                f"{', '.join([str(await self._name(entity, notification=True)) for entity in results])}",
            )

        # update hass sensor
        if "hass_sensor" in self.cfg and self.cfg["hass_sensor"]:
            await self.update_sensor("unavailable", results)

        self._print_result("unavailable", results, "unavailable/unknown state")

    async def check_stale(self, _: Any) -> None:
        check_config = self.cfg["stale"]
        """Handle scheduled checks."""
        results: List[str] = []

        self.lg("Checking for stale entities...", icon=APP_ICON, level="DEBUG")

        if self.cfg["stale"]["entities"]:
            all_entities = self.cfg["stale"]["entities"]
        else:
            all_entities = await self.get_state()

        entities = filter(
            lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg["exclude"]),
            all_entities,
        )

        for entity in sorted(entities):

            attr_last_updated = await self.get_state(entity_id=entity, attribute="last_updated")

            if not attr_last_updated:
                self.lg(f"{await self._name(entity)} has no 'last_updated' attribute ¯\\_(ツ)_/¯", icon=ICONS["stale"])
                continue

            last_update = self.convert_utc(attr_last_updated)
            last_updated = (await self.last_update(entity)).time().isoformat(timespec="seconds")
            now: datetime = await self.datetime(aware=True)

            stale_time: timedelta = now - last_update
            max_stale_min = timedelta(minutes=self.cfg["stale"]["max_stale_min"])

            if stale_time and stale_time >= max_stale_min:
                results.append(entity)
                self.lg(
                    f"{await self._name(entity)} is "
                    f"{hl(f'stale since {hl(int(stale_time.seconds / 60))}')}min | "
                    f"last update: {last_updated}",
                    icon=ICONS["stale"],
                )

        # send notification
        notify = self.cfg.get("notify") or check_config.get("notify")
        if notify and results:
            self.call_service(
                str(notify).replace(".", "/"),
                message=f"{APP_ICON} Stalled entities ({len(results)}): "
                f"{', '.join([str(await self._name(entity, notification=True)) for entity in results])}",
            )

        # update hass sensor
        if "hass_sensor" in self.cfg and self.cfg["hass_sensor"]:
            await self.update_sensor("stale", results)

        self._print_result("stale", results, "stalled updates")

    def choose_notify_recipient(self, check: str, config: Dict[str, Any]) -> None:
        if "notify" in config and "notify" not in self.cfg:
            self.cfg[check]["notify"] = config["notify"]

    async def last_update(self, entity_id: str) -> Any:
        return self.convert_utc(await self.get_state(entity_id=entity_id, attribute="last_updated"))

    async def _name(self, entity: str, friendly_name: bool = False, notification: bool = False) -> Optional[str]:

        name: Optional[str] = None
        if self.cfg["show_friendly_name"]:
            name = await self.friendly_name(entity)
        else:
            name = entity

        if notification is False and name:
            name = hl_entity(name)

        return name

    def _print_result(self, check: str, entities: List[str], reason: str) -> None:
        if entities:
            self.lg(f"{hl(f'{len(entities)} entities')} with {hl(reason)}!", icon=APP_ICON, level="DEBUG")
        else:
            self.lg(f"{hl(f'no entities')} with {hl(reason)}!", icon=APP_ICON)

    async def update_sensor(self, check_name: str, entities: List[str]) -> None:

        if check_name not in CHECKS:
            self.lg(
                f"Unknown check: {hl(f'no entities')} - {self.cfg['hass_sensor']} not updated!",
                icon=APP_ICON,
                level="ERROR",
            )

        if len(self.sensor_attrs[check_name]) != len(entities):

            self.sensor_attrs[check_name] = entities
            self.sensor_state = sum([len(self.sensor_attrs[check]) for check in CHECKS])
            self.set_state(self.cfg["hass_sensor"], state=self.sensor_state, attributes=self.sensor_attrs)

            self.lg(
                f"{hl_entity(self.cfg['hass_sensor'])} -> {hl(self.sensor_state)} "
                f"| {', '.join([f'{hl(k) if k == check_name else k}: {hl(len(v))}' for k, v in self.sensor_attrs.items() if type(v) == list])}",
                icon=APP_ICON,
                level="INFO",
            )

    def show_info(self, config: Optional[Dict[str, Any]] = None) -> None:
        # check if a room is given
        if config:
            self.config = config

        if not self.config:
            self.lg("no configuration available", icon="‼️", level="ERROR")
            return

        room = ""
        if "room" in self.config:
            room = f" - {hl(self.config['room'].capitalize())}"

        self.lg("")
        self.lg(f"{hl(APP_NAME)} v{hl(__version__)}{room}", icon=self.icon)
        self.lg("")

        listeners = self.config.pop("listeners", None)

        for key, value in self.config.items():

            # hide "internal keys" when displaying config
            if key in ["module", "class"] or key.startswith("_"):
                continue

            if isinstance(value, list):
                self.print_collection(key, value, 2)
            elif isinstance(value, dict):
                self.print_collection(key, value, 2)
            else:
                self._print_cfg_setting(key, value, 2)

        if listeners:
            self.lg("  event listeners:")
            for listener in sorted(listeners):
                self.lg(f"    - {hl(listener)}")

        self.lg("")

    def print_collection(self, key: str, collection: Iterable[Any], indentation: int = 2) -> None:

        self.log(f"{indentation * ' '}{key}:")
        indentation = indentation + 2

        for item in collection:
            indent = indentation * " "

            if isinstance(item, dict):
                if "name" in item:
                    self.print_collection(item.pop("name", ""), item, indentation)
                else:
                    self.log(f"{indent}{hl(pformat(item, compact=True))}")

            elif isinstance(collection, dict):
                self._print_cfg_setting(item, collection[item], indentation)

            else:
                self.log(f"{indent}- {hl(item)}")

    def _print_cfg_setting(self, key: str, value: Union[int, str], indentation: int) -> None:
        unit = prefix = ""
        indent = indentation * " "

        # legacy way
        if key == "delay" and isinstance(value, int):
            unit = "min"
            min_value = f"{int(value / 60)}:{int(value % 60):02d}"
            self.log(f"{indent}{key}: {prefix}{hl(min_value)}{unit} ~≈ " f"{hl(value)}sec")

        else:
            if "_units" in self.config and key in self.config["_units"]:
                unit = self.config["_units"][key]
            if "_prefixes" in self.config and key in self.config["_prefixes"]:
                prefix = self.config["_prefixes"][key]

            self.log(f"{indent}{key}: {prefix}{hl(value)}{unit}")


================================================
FILE: alarmClock/alarmClock.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime
import math


#
# Alarm Clock App
#
#
# Args:
#  alarm_time: entity which holds the alarm time. example: sensor.alarm_time
#  wakemeup: entity which enables the alarm. example: input_boolean.wakemeup
#  naturalwakeup: entity which enables the natural wake up fade in. example: input_number.alarm_natural_wakeup_fade_in
#  alarmweekday: entity which enables alarm only on weekdays. example: input_boolean.alarmweekday
#  radiowakeup: entity which enables radio wake up. example: input_boolean.radiowakeup
#  TODO radioplayer: entity which holds the information which radio player to select. example: input_select.wakeup_radioplayer
#  wakeup_light: light to fade in. example: light.bedroom_yeelight
#  isweekday: entity which holds the information whether today is a week day. example: binary_sensor.workday_today
#  notify_name: Who to notify. example: group_notifications
#  message: localized message to use in notification. e.g. "You left open {} Dummy."
#
# Release Notes
#
# Version 1.4:
#   Catch unknown
#
# Version 1.3.1:
#   Use consistent message variable
#
# Version 1.3:
#   Use new formatted alarm_time
#
# Version 1.2:
#   use Notify App
#
# Version 1.1:
#   message now directly in own yaml instead of message module
#
# Version 1.0:
#   Initial Version


class AlarmClock(hass.Hass):
    def initialize(self):

        self.timer_handle_list = []
        self.listen_event_handle_list = []
        self.listen_state_handle_list = []

        self.alarm_time = self.args["alarm_time"]
        self.wakemeup = self.args["wakemeup"]
        self.naturalwakeup = self.args["naturalwakeup"]
        self.alarmweekday = self.args["alarmweekday"]
        self.radiowakeup = self.args["radiowakeup"]
        self.isweekday = self.args["isweekday"]
        self.notify_name = self.args["notify_name"]
        self.wakeup_light = self.args["wakeup_light"]
        self.fade_in_time_multiplicator = self.args["fade_in_time_multiplicator"]
        self.message = self.args["message"]
        self.button = self.args["button"]

        self.notifier = self.get_app("Notifier")

        self.brightness = 100
        self.rgb_color = [255, 120, 0]
        self.alarm_timer = None

        self.cached_alarm_time = self.get_state(self.alarm_time)
        self.cached_fade_in_time = self.get_state(self.naturalwakeup)
        self.add_timer()

        self.listen_state_handle_list.append(
            self.listen_state(self.alarm_change, self.alarm_time)
        )
        self.listen_state_handle_list.append(
            self.listen_state(self.naturalwakeup_change, self.naturalwakeup)
        )

        self.listen_event_handle_list.append(
            self.listen_event(self.button_clicked, "xiaomi_aqara.click")
        )

    def alarm_change(self, entity, attributes, old, new, kwargs):
        if new is not None and new != old and new != self.cached_alarm_time:
            if self.alarm_timer is not None:
                if self.alarm_timer in self.timer_handle_list:
                    self.timer_handle_list.remove(self.alarm_timer)
                self.cancel_timer(self.alarm_timer)
            self.log("Alarm time change: {}".format(new))
            self.cached_alarm_time = new
            self.add_timer()

    def naturalwakeup_change(self, entity, attributes, old, new, kwargs):
        if new is not None and new != old and new != self.cached_fade_in_time:
            if self.alarm_timer is not None:
                if self.alarm_timer in self.timer_handle_list:
                    self.timer_handle_list.remove(self.alarm_timer)
                self.cancel_timer(self.alarm_timer)
            self.log("Fade-In time change: {}".format(new))
            self.cached_fade_in_time = new
            self.add_timer()

    def add_timer(self):
        self.log("cached_alarm_time: {}".format(self.cached_alarm_time))
        self.log("cached_fade_in_time: {}".format(self.cached_fade_in_time))
        offset = self.cached_fade_in_time.split(".", 1)[0]

        if (
            self.cached_alarm_time is not None
            and self.cached_alarm_time != ""
            and self.cached_alarm_time != "unknown"
        ):
            run_datetime = datetime.datetime.strptime(
                self.cached_alarm_time, "%Y-%m-%d %H:%M:%S"
            )
            event_time = run_datetime - datetime.timedelta(minutes=int(offset))
            try:
                self.alarm_timer = self.run_at(self.trigger_alarm, event_time)
                self.timer_handle_list.append(self.alarm_timer)
                self.log("Alarm will trigger at {}".format(event_time))
            except ValueError:
                self.log("New trigger time would be in the past: {}".format(event_time))

    def trigger_alarm(self, kwargs):
        if self.get_state(self.wakemeup) == "on":
            if self.get_state(self.alarmweekday) == "off" or (
                self.get_state(self.alarmweekday) == "on"
                and self.get_state(self.isweekday) == "on"
            ):
                if float(self.cached_fade_in_time) > 0:
                    self.log(
                        "Turning on {}".format(self.friendly_name(self.wakeup_light))
                    )
                    self.call_service(
                        "light/turn_on", entity_id=self.wakeup_light, brightness_pct=1
                    )
                    transition = int(
                        float(self.cached_fade_in_time)
                        * int(self.fade_in_time_multiplicator)
                    )
                    self.log(
                        "Transitioning light in over {} seconds".format(transition)
                    )
                    self.timer_handle_list.append(
                        self.run_in(
                            self.run_fade_in, 1, transition=transition, brightness_pct=1
                        )
                    )
                self.timer_handle_list.append(
                    self.run_in(self.run_alarm, float(self.cached_fade_in_time))
                )

    def button_clicked(self, event_name, data, kwargs):
        """Extra callback method to trigger the wakeup light on demand by pressing a Xiaomi Button"""
        if data["entity_id"] == self.button:
            if data["click_type"] == "single":
                if float(self.cached_fade_in_time) > 0:
                    self.log(
                        "Turning on {}".format(self.friendly_name(self.wakeup_light))
                    )
                    self.call_service(
                        "light/turn_on", entity_id=self.wakeup_light, brightness_pct=1
                    )
                    transition = int(
                        float(self.cached_fade_in_time)
                        * int(self.fade_in_time_multiplicator)
                    )
                    self.log(
                        "Transitioning light in over {} seconds".format(transition)
                    )
                    self.timer_handle_list.append(
                        self.run_in(
                            self.run_fade_in, 1, transition=transition, brightness_pct=1
                        )
                    )

    def run_fade_in(self, kwargs):
        """
        Callback / recursion style because the transition feature does not seem to work well with Yeelight for
        transition values greater than 10s.
        :param kwargs:
        :return:
        """
        wait_factor = 1
        transition = kwargs["transition"]
        brightness_pct = kwargs["brightness_pct"]
        pct_increase = 1 / transition
        self.log("pct_increase: {}".format(pct_increase), level="DEBUG")
        if pct_increase < 0.01:
            wait_factor = math.ceil(0.01 / pct_increase)
            pct_increase = 0.01
            self.log(
                "pct_increase smaller than 1% next run_in in {} seconds".format(
                    wait_factor
                ),
                level="DEBUG",
            )
        brightness_pct_old = brightness_pct
        self.log("brightness_pct_old: {}".format(brightness_pct_old), level="DEBUG")
        brightness_pct_new = int((brightness_pct_old + pct_increase * 100))
        self.log("brightness_pct_new: {}".format(brightness_pct_new), level="DEBUG")
        if brightness_pct_new < 100:
            self.call_service(
                "light/turn_on",
                entity_id=self.wakeup_light,
                rgb_color=self.rgb_color,
                brightness_pct=brightness_pct_new,
            )
            self.timer_handle_list.append(
                self.run_in(
                    self.run_fade_in,
                    wait_factor,
                    transition=transition,
                    brightness_pct=brightness_pct_new,
                )
            )

    def run_alarm(self, kwargs):
        self.notifier.notify(self.notify_name, self.message)
        # TODO radio

    def terminate(self):
        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)

        for listen_event_handle in self.listen_event_handle_list:
            self.cancel_listen_event(listen_event_handle)

        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: alarmClock/alarmClock.yaml
================================================
# Alarm Clock App
# alarmClock:
#   module: alarmClock
#   class: AlarmClock
#   alarm_time: sensor.alarm_time
#   wakemeup: input_boolean.wakemeup
#   naturalwakeup: input_number.alarm_natural_wakeup_fade_in
#   alarmweekday: input_boolean.alarmweekday
#   radiowakeup: input_boolean.radiowakeup
#   #TODO radioplayer: input_select.wakeup_radioplayer
#   wakeup_light: light.bedroom_yeelight
#   fade_in_time_multiplicator: 60
#   isweekday: binary_sensor.workday_today
#   notify_name: group_notifications
#   message: "Guten Morgen!"
#   #message: "Good Morning!"
#   button: binary_sensor.switch_158d000215aa28
#   dependencies:
#     - Notifier


================================================
FILE: alexa/README.md
================================================
# Alexa Intents

Intents for [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App) from [Rene Tode](https://github.com/ReneTode).

To set it up for yourself follow [this](https://github.com/ReneTode/Alexa-Appdaemon-App/blob/master/alexa%20skill%20tutorial.md) tutorial

## listService

Supply friendly names and known entities for other alexa skills.
This is needed as this is a custom Alexa App and has nothing to do with HA Cloud / Alexa integration

## turnEntityOffInX

Ask Alexa to turn something off in a set amount of minutes.

Only works with entities defined under *switchable* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml)

``Alexa tell Home Assistant to turn off Ventilator in 10 Minutes``

```yaml
turnEntityOffInXIntent:
  module: turnEntityOffInXIntent
  class: TurnEntityOffInXIntent
  language: DE
  textLine: "Okay Homeassistant schaltet {{device}} in"
  Error: <p>Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?</p>
  unreadableState: "unlesbar fuer mich"
  dependencies:
   - listService
```

## windowsOpen

Will tell you if any windows / doors are open and/or tilted

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)

``Alexa ask Home Assistant whether all windows are closed``

```yaml
windowsOpenIntent:
  module: windowsOpenIntent
  class: WindowsOpenIntent
  language: DE
  textLineClosed: "Alle Fenster und Türen sind zu"
  #textLineClosed: "All windows and doors are closed"
  textLineWindowOpen: "Folgende Fenster sind noch offen"
  #textLineWindowOpen: "The following windows are stil open..."
  textLineDoorOpen: "Folgende Türen sind noch offen"
  #textLineDoorOpen: "The following doors are still open"
  textLineDoorTilted: "Die folgenden Türen sind noch gekippt"
  #textLineDoorTilted: "The following doors are tilted"
  Error: <p>Ich habe dich nicht richtig verstanden</p>
  unreadableState: "unlesbar fuer mich"
  dependencies:
   - listService
```

## nextBusIntent

Will tell you the next departure of a bus/train of a [RMV](https://www.home-assistant.io/components/sensor.rmvtransport/) sensor

``Alexa ask Home Assistant when the next bus departs``

```yaml
nextBusIntent:
  module: nextBusIntent
  class: nextBusIntent
  textLine: "Linie {} fährt in {} Minuten"
  #textLine: "Line {} departs in {} minutes"
  Error: <p>Ich habe nicht richtig verstanden was du meinst</p>
  sensor: sensor.nach_bruckenkopf
  global_dependencies:
    - globals
```

## remindMeOfXWhenZoneIntent

CURRENTLY DOES NOT WORK BECAUSE ALEXA DOES NOT ALLOW INTENTS CONTAINING REMINDERS

Will send you a reminder over the notification service when you leave/enter a zone.

``Alexa tell Home Assistant to remind me of <> when entering work``

```yaml
remindMeOfXWhenZoneIntent:
  module: remindMeOfXWhenZoneIntent
  class: RemindMeOfXWhenZoneIntent
  device_tracker: person.kevin
  notify_name: group_notifications
  Error: <p>Es ist etwas schief gegangen</p>
  textLine: "Okay ich erinnere dich an {{reminder}} wenn du {{zone}} "
  textEnter: "erreichst"
  textLeave: "verlässt"
  remindMessageSkeleton: "Ich sollte dich erinnern an "
  zoneMapping:
    arbeit: work
    hause: home
    elmo: elmo
  dependencies:
    - Notifier
  global_dependencies:
    - globals
```


================================================
FILE: alexa/alexa.yaml
================================================
alexa_api: # appdaemon skill
  module: alexa_api
  class: alexa_api
  cardTitle: Your Card Title
  devices:
    unknownDevice: ein unbekannte platze
  logfile: /conf/logs/alexaAD.log
  responselogfile: /conf/logs/alexaResponse.log
  language: DE
  temperatureUnit: "Grad"
  logging: "True"
  launchRequest:
    - <p><say-as interpret-as='interjection'>bonjour</say-as>, bist du wieder in {{device}}?</p> <p>wie kann ich dir helfen?</p>
    - <p><say-as interpret-as='interjection'>aloha</say-as>, wie gehts es in {{device}}?</p> <p>kann ich etwas fuer dich tun?</p>
    - <p><say-as interpret-as='interjection'>hey</say-as>, du bist wieder in {{device}}.</p> <p>was kan ich machen?</p>
    - <p><say-as interpret-as='interjection'>moin</say-as>. Schoen das du wieder im {{device}} bist,</p> <p>was ist dein wunsch?</p>
    - <say-as interpret-as='interjection'>mazzel tov</say-as>. <p>Ich sage hallo zu alle in {{device}}</p><p>Kann ich etwas fuer dich tun?</p>
  nextConversationQuestion:
    - <p>Was kann ich als naechstes fuer dich tun?</p>
    - <p>Mit was willst du das ich dir helfe? </p>
  intentEnd:
    - <p>Kann ich noch etwas fuer dich machen?</p>
    - <p>Willst du das ich noch etwas anderes mache?</p>
    - <p>Kann ich dir mit noch etwas helfen?</p>
    - <p>Gibt es noch etwas was ich fuer dich tun kann?</p>
  conversationEnd:
    - Bis zum naechsten mal
    - <say-as interpret-as='interjection'>arrivederci</say-as>
    - <say-as interpret-as='interjection'>bis bald</say-as>
    - <say-as interpret-as='interjection'>bis dann</say-as>
    - <say-as interpret-as='interjection'>mach's gut</say-as>
    - <say-as interpret-as='interjection'>tschoe</say-as>
    - <say-as interpret-as='interjection'>viel glueck</say-as>
    - Schoen mit dir gesprochen zu haben
  responseError:
    - Es tut mir leit aber etwas ist falsch gegangen
    - Leider meldet home assistant einen Fehler 

================================================
FILE: alexa/alexa_api.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import random
import datetime


class alexa_api(hass.Hass):
    def initialize(self):
        self.register_endpoint(self.api_call)
        self.tempCardContent = ""

    def api_call(self, data):
        self.my_alexa_interpret_data(data)
        response = ""
        dialogDelegate = False
        endsession = False
        speech = None
        card = None
        logtext = ""
        if self.dialog == None:
            if self.requesttype == "LaunchRequest":
                speech = self.random_arg(self.args["launchRequest"])
                endSession = False
                dialogDelegate = False
                card = None
                self.alexalog("Launch request (activate command)", 100, "*")
            elif self.requesttype == "IntentRequest":
                speech = None
                endSession = False
                dialogDelegate = True
                card = None
                self.alexalog("start dialog", 100, "*")
            elif self.requesttype == "SessionEndedRequest":
                speech = None
                endSession = False
                dialogDelegate = False
                card = None
                self.alexalog("The session has ended", 100, "*")
            else:
                self.alexalog("Strange state", 100, "*")
        elif self.dialog == "STARTED":
            ############################################
            # conversation started, just response dialogDelegate
            ############################################
            speech = None
            endSession = False
            dialogDelegate = True
            card = None
            self.alexalog("dialog has been started", 100, "*")
        elif self.dialog == "IN_PROGRESS":
            speech = None
            endSession = False
            dialogDelegate = True
            card = None
            self.alexalog("dialog is in progress", 100, "*")
        elif self.dialog == "COMPLETED":
            try:
                intentResponse = self.getIntentResponse()
            except:
                intentResponse = self.random_arg(self.args["responseError"])
            if intentResponse == "stop":
                ############################################
                # user used stop intent, stop conversation
                ############################################
                speech = self.random_arg(self.args["conversationEnd"])
                endSession = True
                dialogDelegate = False
                card = True
                cardContent = self.tempCardContent
                self.tempCardContent = ""
                self.alexalog(
                    "dialog has been completed, Dialog stopped by user", 100, "*"
                )
            elif intentResponse == "next":
                ############################################
                # user just responded yes, so just a question
                ############################################
                speech = self.random_arg(self.args["nextConversationQuestion"])
                endSession = False
                dialogDelegate = False
                card = None
                self.tempCardContent = self.tempCardContent + self.intentname + "\n"
                self.alexalog(
                    "dialog has been completed, User just responded yes", 100, "*"
                )
            else:
                ############################################
                # Send the response from the Intent + question for next action
                ############################################
                speech = intentResponse
                endSession = True
                dialogDelegate = False
                card = None
                self.tempCardContent = self.tempCardContent + self.intentname + "\n"
                self.alexalog("dialog has been completed, Response " + speech, 100, "*")
        if speech != None:
            speech = self.cleanup_text(speech)
        if card:
            response = self.my_alexa_response(
                EndSession=endSession,
                DialogDelegate=dialogDelegate,
                speech=speech,
                card=True,
                title=self.args["cardTitle"],
                content=cardContent,
            )
            self.alexalog(" ", 100, "X")
            self.alexalog(" ", 100, "X")
            self.alexalog(" ", 100, "X")
        else:
            response = self.my_alexa_response(
                EndSession=endSession,
                DialogDelegate=dialogDelegate,
                speech=speech,
                card=None,
            )
        if speech != None:
            if self.intentname == None:
                self.alexaresponselog("No Intent : " + speech)
            else:
                self.alexaresponselog(self.intentname + " : " + speech)
        return response, 200

    def my_alexa_interpret_data(self, data):
        ############################################
        # create vars from the data
        ############################################
        self.allslots = {}
        self.slots = {}
        self.dialog = self.my_alexa_dialog_state(data)
        self.requesttype = self.my_alexa_request_type(data)
        self.alexa_error = self.my_alexa_error(data)
        self.intentname = self.my_alexa_intent_name(data)
        if self.intentname != None:
            if "." in self.intentname:
                splitintent = self.intentname.split(".")
                self.intentname = splitintent[1]
        device = data["context"]["System"]["device"]["deviceId"]
        self.devicename = self.args["devices"]["unknownDevice"]
        for devicename, deviceid in self.args["devices"].items():
            if deviceid == device:
                self.devicename = devicename
        if self.devicename == self.args["devices"]["unknownDevice"]:
            self.log(device)
        ############################################
        # get slots out of the data
        ############################################
        if (
            "request" in data
            and "intent" in data["request"]
            and "slots" in data["request"]["intent"]
        ):
            self.allslots = data["request"]["intent"]["slots"]
        slottext = "slots: "
        for slot, slotvalue in self.allslots.items():
            if "value" in slotvalue:
                self.slots[slot] = slotvalue["value"].lower()
            else:
                self.slots[slot] = ""
        if self.intentname == "searchYoutubeIntent":
            self.slots["search"] = (
                self.slots["searchfielda"]
                + self.slots["searchfieldb"]
                + self.slots["searchfieldc"]
                + self.slots["searchfieldd"]
            )
        ############################################
        # log that data came in
        ############################################
        self.alexalog("data came in.", 100, "#")
        self.alexalog(
            "dialogstate = "
            + str(self.dialog)
            + " and requesttype = "
            + str(self.requesttype)
        )
        self.alexalog("intent = " + str(self.intentname))
        slottext = "slots: "
        for slot, slotvalue in self.slots.items():
            slottext = slottext + slot + "= " + str(slotvalue) + ", "
        self.alexalog(slottext)
        self.alexalog("error = " + str(self.alexa_error))
        self.alexalog(" ", 100, "#")

    def my_alexa_intent_name(self, data):
        ############################################
        # find the intent name in the data
        ############################################
        if (
            "request" in data
            and "intent" in data["request"]
            and "name" in data["request"]["intent"]
        ):
            return data["request"]["intent"]["name"]
        else:
            return None

    def my_alexa_dialog_state(self, data):
        ############################################
        # find the dialog state in the data
        ############################################
        if "request" in data and "dialogState" in data["request"]:
            return data["request"]["dialogState"]
        else:
            return None

    def my_alexa_intent(self, data):
        ############################################
        # find the requesttype in the data
        ############################################
        if "request" in data and "intent" in data["request"]:
            return data["request"]["intent"]
        else:
            return None

    def my_alexa_request_type(self, data):
        ############################################
        # find the requesttype in the data
        ############################################
        if "request" in data and "type" in data["request"]:
            return data["request"]["type"]
        else:
            return None

    def my_alexa_error(self, data):
        ############################################
        # get an error out of the data
        ############################################
        if (
            "request" in data
            and "error" in data["request"]
            and "message" in data["request"]["error"]
        ):
            return data["request"]["error"]["message"]
        else:
            return None

    def my_alexa_slot_value(self, data, slot):
        ############################################
        # get a slot value from the data
        ############################################
        if (
            "request" in data
            and "intent" in data["request"]
            and "slots" in data["request"]["intent"]
            and slot in data["request"]["intent"]["slots"]
            and "value" in data["request"]["intent"]["slots"][slot]
        ):
            return data["request"]["intent"]["slots"][slot]["value"]
        else:
            return ""

    def my_alexa_response(
        self,
        EndSession=False,
        DialogDelegate=False,
        speech=None,
        card=None,
        title=None,
        content=None,
    ):
        ############################################
        # put the speechfield from the response toghether
        ############################################
        response = {"shouldEndSession": EndSession}
        if DialogDelegate:
            response["directives"] = [
                {"type": "Dialog.Delegate", "updatedIntent": None}
            ]
        if speech is not None:
            response["outputSpeech"] = {
                "type": "SSML",
                "ssml": "<speak>" + speech + "</speak>",
            }
        if card is not None:
            response["card"] = {"type": "Simple", "title": title, "content": content}

        speech = {"version": "1.0", "response": response, "sessionAttributes": {}}
        return speech

    def random_arg(self, argName):
        ############################################
        # pick a random text from a list
        ############################################
        if isinstance(argName, list):
            text = random.choice(argName)
        else:
            text = argname
        return text

    def floatToStr(self, myfloat):
        ############################################
        # replace . with , for better speech
        ############################################
        floatstr = str(myfloat)
        floatstr = floatstr.replace(".", ",")
        return floatstr

    def cleanup_text(self, text):
        ############################################
        # replace some text like temperary slots with its value
        ############################################
        # self.log(text)
        for slotname, slotvalue in self.slots.items():
            text = text.replace("{{" + slotname + "}}", slotvalue)
        text = text.replace("{{device}}", self.devicename)

        text = text.replace("_", " ")
        text = text.replace("...", "<break time='2s'/>")
        return text

    def alexalog(self, logtext, repeat=0, surrounding=""):
        ############################################
        # put an entry in the alexa log
        ############################################
        if self.args["logging"] == "true" or self.args["logging"] == "True":
            runtime = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S.%f")
            if repeat > 0:
                surrounding = surrounding * repeat
            try:
                log = open(self.args["logfile"], "a")
                if logtext == " ":
                    log.write(runtime + ";  " + surrounding + "\n")
                else:
                    if surrounding != "":
                        log.write(runtime + ";  " + surrounding + "\n")
                    log.write(runtime + ";  " + logtext + "\n")
                    if surrounding != "":
                        log.write(runtime + ";  " + surrounding + "\n")
                log.close()
            except:
                self.log("ALEXA LOGFILE CANT BE WRITTEN!!")

    def alexaresponselog(self, logtext):
        ############################################
        # put an entry in the alexa responses log
        ############################################
        if self.args["logging"] == "true" or self.args["logging"] == "True":
            runtime = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S.%f")
            try:
                log = open(self.args["responselogfile"], "a")
                log.write(runtime + ";  " + logtext + "\n")
                log.close()
            except:
                self.log("ALEXA RESPONSELOGFILE CANT BE WRITTEN!!")

    def getIntentResponse(self):
        ############################################
        # perform the intent end get the response back
        # from the intent apps
        ############################################
        if self.intentname == "StopIntent":
            return "stop"
        if self.intentname == "yesIntent":
            return "next"
        alexa = self.get_app(self.intentname)
        return alexa.getIntentResponse(self.slots, self.devicename)


================================================
FILE: alexa/custom_skill.json
================================================
{
    "interactionModel": {
        "languageModel": {
            "invocationName": "home assistant",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "temperatureStateIntent",
                    "slots": [
                        {
                            "name": "location",
                            "type": "room",
                            "samples": [
                                "Vom Schlafzimmer",
                                "Vom Wohnzimmer",
                                "Wohnzimmer"
                            ]
                        }
                    ],
                    "samples": [
                        "wie viel grad ist es in {location}",
                        "was ist die temperatur in {location}"
                    ]
                },
                {
                    "name": "lightStateIntent",
                    "slots": [
                        {
                            "name": "device",
                            "type": "device",
                            "samples": [
                                "Von {device}",
                                "{device}"
                            ]
                        }
                    ],
                    "samples": [
                        "Ist {device} aus",
                        "Ist {device} an",
                        "Was ist der Status von {device}"
                    ]
                },
                {
                    "name": "turnEntityOffInXIntent",
                    "slots": [
                        {
                            "name": "device",
                            "type": "device",
                            "samples": [
                                "die {device}",
                                "der {device}",
                                "Das {device}",
                                "{device}"
                            ]
                        },
                        {
                            "name": "duration",
                            "type": "AMAZON.DURATION",
                            "samples": [
                                "In {duration}"
                            ]
                        }
                    ],
                    "samples": [
                        "In {duration} {device} ausschalten",
                        "{device} in {duration} ausschalten",
                        "er soll {device} in {duration} ausschalten",
                        "es soll {device} in {duration} ausschalten",
                        "Schalte {device} in {duration} aus"
                    ]
                },
                {
                    "name": "windowsOpenIntent",
                    "slots": [],
                    "samples": [
                        "ob alles zu ist",
                        "ob noch etwas offen ist",
                        "ist noch etwas offen",
                        "ist alles zu",
                        "sind alle türen zu",
                        "ob noch türen offen sind",
                        "ob alle türen zu sind",
                        "ob alle fenster zu sind",
                        "ob noch Fenster offen sind",
                        "ob die Fenster zu sind",
                        "Sind die Fenster zu",
                        "Sind alle Fenster zu",
                        "Welche Fenster sind offen"
                    ]
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                }
            ],
            "types": [
                {
                    "name": "room",
                    "values": [
                        {
                            "name": {
                                "value": "oben"
                            }
                        },
                        {
                            "name": {
                                "value": "küche"
                            }
                        },
                        {
                            "name": {
                                "value": "schlafzimmer"
                            }
                        },
                        {
                            "name": {
                                "value": "wohnzimmer"
                            }
                        }
                    ]
                },
                {
                    "name": "device",
                    "values": [
                        {
                            "name": {
                                "value": "Deckenlampe"
                            }
                        },
                        {
                            "name": {
                                "value": "Ventilator"
                            }
                        },
                        {
                            "name": {
                                "value": "Wohnzimmer Temperatur"
                            }
                        }
                    ]
                }
            ]
        },
        "dialog": {
            "intents": [
                {
                    "name": "temperatureStateIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "location",
                            "type": "room",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.748362018084.1289245209680"
                            }
                        }
                    ]
                },
                {
                    "name": "lightStateIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "device",
                            "type": "device",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.314395877784.987687730608"
                            }
                        }
                    ]
                },
                {
                    "name": "turnEntityOffInXIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "device",
                            "type": "device",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.208682348144.1260609041167"
                            }
                        },
                        {
                            "name": "duration",
                            "type": "AMAZON.DURATION",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.208682348144.1375070683830"
                            }
                        }
                    ]
                }
            ]
        },
        "prompts": [
            {
                "id": "Elicit.Slot.748362018084.1289245209680",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Von welchem Raum willst du die Temperatur wissen?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.314395877784.987687730608",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Von was willst du den Status wissen?"
                    },
                    {
                        "type": "PlainText",
                        "value": "Entschuldigung. Welches Gerät meintest du?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.208682348144.1260609041167",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Entschuldigung. Welches Gerät soll ich ausschalten?"
                    },
                    {
                        "type": "PlainText",
                        "value": "Welches Gerät meinst du?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.208682348144.1375070683830",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "Entschuldigung. Wann soll ich {device} ausschalten?"
                    },
                    {
                        "type": "PlainText",
                        "value": "Wann soll ich {device} ausschalten?"
                    }
                ]
            }
        ]
    }
}

================================================
FILE: alexa/lightState/lightStateIntent-utterances_DE.csv
================================================
Ist {device} aus
Ist {device} an
Was ist der Status von {device}

================================================
FILE: alexa/lightState/lightStateIntent-utterances_EN.csv
================================================
Is {device} off
Is {device} on
What is the state of {device}


================================================
FILE: alexa/lightState/lightStateIntent.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import random


class lightStateIntent(hass.Hass):
    def initialize(self):
        return

    def getIntentResponse(self, slots, devicename):
        ############################################
        # an Intent to give back the state from a light.
        # but it also can be any other kind of entity
        ############################################
        try:
            entityname = self.args["entities"][slots["device"]]
            state = self.get_state(entityname)
            if isinstance(state, float):
                if self.args["language"] == "DE":
                    state = self.floatToStr(state)
                else:
                    state = str(state)
            elif isinstance(state, str):
                state = state
            else:
                state = self.args["unreadableState"]
            text = self.random_arg(self.args["textLine"]) + state
        except:
            text = self.random_arg(self.args["Error"])
        return text

    def floatToStr(self, myfloat):
        ############################################
        # replace . with , for better speech
        ############################################
        floatstr = str(myfloat)
        floatstr = floatstr.replace(".", ",")
        return floatstr


================================================
FILE: alexa/lightState/lightStateIntent.yaml
================================================
lightStateIntent:
  module: lightStateIntent
  class: lightStateIntent
  language: DE
  temperatureUnit: "Grad"
  textLine:
    - "Der Status von {{device}} ist "
    - "{{device}} ist in diesem Moment "
    - "{{device}} ist "
  Error: <p>Ich habe nicht richtig verstanden welches geraet oder sensor du wissen wolltest</p>
  unreadableState: "unlesbar fuer mich"
  entities:
    wohnzimmer temperatur: sensor.large_lamp_temperature
    deckenlampe: light.livingroom_yeelight
    ventilator: switch.ventilator

================================================
FILE: alexa/listService/listService.py
================================================
import appdaemon.plugins.hass.hassapi as hass

#
# Provide the list of HA entities for Alexa Apps
#
#
# Args:
#
# switchable: dict of switchable entities
# temperature: dict of temperature sensors
# door: dict of reed sensors showing if the door is completely open
# door_tilted: dict of reed sensors showing if a door is partially/leaning open
# window: dict of reed sensors showing if a window is open
#
#
# Release Notes
#
# Version 1.0:
#   Initial Version


class ListService(hass.Hass):
    def initialize(self):
        return

    def getSwitchable(self):
        return self.args["switchable"]

    def getTemperature(self):
        return self.args["temperature"]

    def getDoor(self):
        return self.args["door"]

    def getWindow(self):
        return self.args["window"]

    def getDoorTilted(self):
        return self.args["door_tilted"]


================================================
FILE: alexa/listService/listService.yaml
================================================
listService:
  module: listService
  class: ListService
  switchable:
    große lampe: switch.large_lamp
    kleine lampe: switch.small_lamp
    deckenlampe: light.livingroom_yeelight
    ventilator: switch.ventilator
    großer ventilator: switch.large_ventilator
    markise: switch.markise
    fernseher: switch.tv
    schlafzimmer receiver: switch.bedroom_receiver
    snowboard: switch.snowboard
    receiver: media_player.denon_avr_x1300w
    wohnzimmer: group.livingroom
    wohnung: group.all
    schlafzimmer lampe: light.bedroom_yeelight
    schlafzimmer: light.bedroom_yeelight
    bar: light.bar_table
    bar licht: light.bar_table
    treppe unten: light.stairs_lower_yeelight
    treppe oben: light.treppe_oben
    leselampe: light.reading_lamp_yeelight
    lese lampe: light.reading_lamp_yeelight
    runde lampe: light.reading_lamp_yeelight
    garderobe: light.lobby_yeelight
    garderoben licht: light.lobby_yeelight
    garderobenlicht: light.lobby_yeelight
    flur: switch.lobby
    flur licht: switch.lobby
    bad: light.lower_bathroom_yeelight
    bad licht: light.lower_bathroom_yeelight
    treppe: group.stair_lights
    treppen licht: group.stair_lights
    treppenlicht: group.stair_lights

  temperature:
    große lampe thermostat: sensor.large_lamp_temperature
    kleine lampe thermostat: sensor.small_lamp_temperature
    ventilator thermostat: sensor.ventilator_temperature
    großer ventilator thermostat: sensor.large_ventilator_temperature
  door:
    wohnungstuer: binary_sensor.contact_door
    terassentuer schlafzimmer: binary_sensor.contact_bedroom_door
    terassentuer:  binary_sensor.contact_terrace_door
    terassentuer arbeitszimmer: binary_sensor.contact_studyroom_door
  window:
    kuechenfenster:  binary_sensor.contact_kitchen_window
    badfenster oben: binary_sensor.contact_upper_bathroom_window
    badfenster unten: binary_sensor.contact_lower_bathroom_window
    gaestezimmerfenster: binary_sensor.contact_guestroom_window
  door_tilted:
    kuechenfenster gekippt: binary_sensor.contact_kitchen_window_tilted
    terassentuer arbeitszimmer gekippt: binary_sensor.contact_studyroom_door_tilted
    terassentuer schlafzimmer gekippt: binary_sensor.contact_bedroom_door_tilted
    terrassentuer gekippt: binary_sensor.contact_terrace_door_tilted
    badfenster unten gekippt: binary_sensor.contact_lower_bathroom_window_tilted
    badfenster oben gekippt: binary_sensor.contact_upper_bathroom_window_tilted
    gaestezimmerfenster gekippt: binary_sensor.contact_guestroom_window_tilted



================================================
FILE: alexa/nextBus/nextBusIntent.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import random


class nextBusIntent(hass.Hass):
    def initialize(self):
        self.sensor = self.args["sensor"]
        self.textLine = self.args["textLine"]
        self.error = self.args["error"]

    def getIntentResponse(self, slots, devicename):
        ############################################
        # give next bus departure
        ############################################
        try:
            state = self.get_state(self.sensor, attribute="all")
            line = state["attributes"]["line"]
            minutes = state["attributes"]["minutes"]
            text = self.textLine.format(line, minutes)
        except:
            text = self.error
        return text


================================================
FILE: alexa/nextBus/nextBusIntent.yaml
================================================
nextBusIntent:
  module: nextBusIntent
  class: nextBusIntent
  textLine: "Linie {} fährt in {} Minuten"
  #textLine: "Line {} departs in {} minutes"
  error: <p>Ich habe nicht richtig verstanden was du meinst</p>
  sensor: sensor.nach_bruckenkopf

================================================
FILE: alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime

__ZONE_ACTION_ENTER__ = "kommen"
__ZONE_ACTION_LEAVE__ = "verlassen"


class RemindMeOfXWhenZoneIntent(hass.Hass):
    def initialize(self):
        self.timer_handle_list = []
        self.listen_state_handle_list = []

        self.device_tracker = self.args["device_tracker"]
        self.notify_name = self.args["notify_name"]
        self.remindMessageSkeleton = self.args["remindMessageSkeleton"]

        self.notifier = self.get_app("Notifier")
        return

    def getIntentResponse(self, slots, devicename):
        ############################################
        # an Intent to give back the state from a light.
        # but it also can be any other kind of entity
        ############################################
        try:
            # get zone_name for friendly name used when talking to alexa
            zone_name = None
            for key, value in self.args["zoneMapping"].items():
                if key == slots["zone"].lower():
                    zone_name = value
            # listen to a state change of the zone
            if zone_name == None:
                raise Exception(
                    "Could not find zonemapping for: {}".format(slots["zone"].lower())
                )
            else:
                self.listen_state_handle_list.append(
                    self.listen_state(
                        self.remind_callback,
                        self.device_tracker,
                        zone=slots["zone"],
                        zoneAction=slots["zoneAction"],
                        reminder=slots["reminder"],
                    )
                )
                # set correct zoneAction response
                if slots["zoneAction"] == __ZONE_ACTION_ENTER__:
                    text = self.args["textLine"] + self.args["textEnter"]
                else:
                    text = self.args["textLine"] + self.args["textLeave"]
        except Exception as e:
            self.log("Exception: {}".format(e))
            self.log("slots: {}".format(slots))
            text = self.random_arg(self.args["Error"])
        return text

    def remind_callback(self, entity, attribute, old, new, kwargs):
        if kwargs["zoneAction"] == __ZONE_ACTION_ENTER__:
            if new != old and new == kwargs["zone"]:
                self.log("Notifying")
                self.notifier.notify(
                    self.notify_name,
                    self.remindMessageSkeleton + kwargs["reminder"],
                    useAlexa=False,
                )
        elif kwargs["zoneAction"] == __ZONE_ACTION_LEAVE__:
            if new != old and old == kwargs["zone"]:
                self.log("Notifying")
                self.notifier.notify(
                    self.notify_name,
                    self.remindMessageSkeleton + kwargs["reminder"],
                    useAlexa=False,
                )

    def terminate(self):
        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)

        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.yaml
================================================
remindMeOfXWhenZoneIntent:
  module: remindMeOfXWhenZoneIntent
  class: RemindMeOfXWhenZoneIntent
  device_tracker: person.kevin
  notify_name: group_notifications
  Error: <p>Es ist etwas schief gegangen</p>
  textLine: "Okay ich erinnere dich an {{reminder}} wenn du {{zone}} "
  textEnter: "erreichst"
  textLeave: "verlässt"
  remindMessageSkeleton: "Ich sollte dich erinnern an "
  zoneMapping:
    arbeit: work
    hause: home
    elmo: elmo
  dependencies: 
    - Notifier

================================================
FILE: alexa/temperatureState/temperatureStateIntent-utterances_DE.csv
================================================
wie viel grad ist es in {location}
was ist die temperatur in {location}

================================================
FILE: alexa/temperatureState/temperatureStateIntent-utterances_EN.csv
================================================
how many degree is it in {location}
what is the temperture in {location}


================================================
FILE: alexa/temperatureState/temperatureStateIntent.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import random


class temperatureStateIntent(hass.Hass):
    def initialize(self):
        return

    def getIntentResponse(self, slots, devicename):
        ############################################
        # give temperature for a list of temperature sensors
        ############################################
        try:
            if self.args["language"] == "DE":
                temp = (
                    self.floatToStr(
                        self.get_state(self.args["sensors"][slots["location"]])
                    )
                    + self.args["temperatureUnit"]
                )
            else:
                temp = (
                    str(self.get_state(self.args["sensors"][slots["location"]]))
                    + self.args["temperatureUnit"]
                )
            text = self.random_arg(self.args["textLine"]) + temp
        except:
            text = self.random_arg(self.args["Error"])
        return text

    def floatToStr(self, myfloat):
        ############################################
        # replace . with , for better speech
        ############################################
        floatstr = str(myfloat)
        floatstr = floatstr.replace(".", ",")
        return floatstr

    def random_arg(self, argName):
        ############################################
        # pick a random text from a list
        ############################################
        if isinstance(argName, list):
            text = random.choice(argName)
        else:
            text = argName
        return text


================================================
FILE: alexa/temperatureState/temperatureStateIntent.yaml
================================================
temperatureStateIntent:
  module: temperatureStateIntent
  class: temperatureStateIntent
  language: DE
  temperatureUnit: "Grad"
  textLine:
    - "Die Temperatur im {{location}} ist "
    - "In diesem Moment ist es im {{location}} "
    - "Im Moment ist die Temperatur "
  Error: <p>Ich habe nicht richtig verstanden welche Temperatur du wissen wolltest</p>
  sensors:
    wohnzimmer: sensor.large_lamp_temperature

================================================
FILE: alexa/turnEntityOffInX/requirements.txt
================================================
isodate

================================================
FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_DE.csv
================================================
In {duration} {device} ausschalten
{device} in {duration} ausschalten
er soll {device} in {duration} ausschalten
es soll {device} in {duration} ausschalten
Schalte {device} in {duration} aus

================================================
FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_EN.csv
================================================
In {duration} turn off {device}
turn off {device} in {duration}
it should turn off {device} in {duration}


================================================
FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import random
import isodate
import datetime


class TurnEntityOffInXIntent(hass.Hass):
    def initialize(self):
        self.timer_handle_list = []
        self.listService = self.get_app("listService")
        return

    def getIntentResponse(self, slots, devicename):
        ############################################
        # an Intent to give back the state from a light.
        # but it also can be any other kind of entity
        ############################################
        try:

            entityname = self.listService.getSwitchable()[slots["device"]]
            # to upper because of https://github.com/gweis/isodate/issues/52
            duration = isodate.parse_duration(slots["duration"].upper())
            self.timer_handle_list.append(
                self.run_in(
                    self.turn_off_callback,
                    duration.total_seconds(),
                    entityname=entityname,
                )
            )
            minutes, seconds = divmod(duration.total_seconds(), 60)
            minutes = int(minutes)
            seconds = int(seconds)
            if minutes == 0:
                if seconds == 1:
                    timeText = " einer Sekunde"
                else:
                    timeText = " {} Sekunden".format(seconds)
            elif minutes == 1:
                if seconds == 1:
                    timeText = " einer Minute und einer Sekunde"
                elif seconds == 0:
                    timeText = " einer Minute"
                else:
                    timeText = " einer Minute und {} Sekunden".format(seconds)
            else:
                if seconds == 1:
                    timeText = " {} Minuten und einer Sekunde".format(minutes)
                elif seconds == 0:
                    timeText = " {} Minuten".format(minutes)
                else:
                    timeText = " {} Minuten und {} Sekunden".format(minutes, seconds)
            text = self.args["textLine"] + timeText + " ab."
        except Exception as e:
            self.log("Exception: {}".format(e))
            self.log("slots: {}".format(slots))
            text = self.random_arg(self.args["Error"])
        return text

    def turn_off_callback(self, kwargs):
        entityname = kwargs["entityname"]
        self.log("Turning off {}".format(entityname))
        self.turn_off(entityname)

    def random_arg(self, argName):
        ############################################
        # pick a random text from a list
        ############################################
        if isinstance(argName, list):
            text = random.choice(argName)
        else:
            text = argName
        return text

    def terminate(self):
        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)


================================================
FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent.yaml
================================================
turnEntityOffInXIntent:
  module: turnEntityOffInXIntent
  class: TurnEntityOffInXIntent
  language: DE
  textLine: "Okay Homeassistant schaltet {{device}} in"
  Error: <p>Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?</p>
  unreadableState: "unlesbar fuer mich"
  dependencies:
   - listService

================================================
FILE: alexa/windowsOpen/windowsOpenIntent-utterances_DE.csv
================================================
ob alles zu ist
ob noch etwas offen ist
ist noch etwas offen
ist alles zu
sind alle türen zu
ob noch türen offen sind
ob alle türen zu sind
ob alle fenster zu sind
ob noch Fenster offen sind
ob die Fenster zu sind
Sind die Fenster zu
Sind alle Fenster zu
Welche Fenster sind offen

================================================
FILE: alexa/windowsOpen/windowsOpenIntent-utterances_EN.csv
================================================
whether everything is closed
whether something is still open
is something still open
is everthing closed
are all doors closed
whether some doors are still open
whether all doors are closed
whether all windows are closed
whether some windows are open
whether the windows are closed
Are the Windows closed
Are all windows closed
Which Windows are open


================================================
FILE: alexa/windowsOpen/windowsOpenIntent.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import random
import isodate
import datetime


class WindowsOpenIntent(hass.Hass):
    def initialize(self):
        self.listService = self.get_app("listService")
        return

    def getIntentResponse(self, slots, devicename):
        ############################################
        # an Intent to give back the state of windows
        ############################################
        try:
            windows_dict = self.listService.getWindow()
            doors_dict = self.listService.getDoor()
            doors_tilted_dict = self.listService.getDoorTilted()
            window_open_list = []
            door_open_list = []
            door_tilted_list = []
            # iterate over all window entities
            for key, value in windows_dict.items():
                # if a window is open ("on") add it to the window_open_list
                if self.get_state(value) == "on":
                    window_open_list.append(value)
            # iterate over all door entities
            for key, value in doors_dict.items():
                # if a door is open ("on") add it to the door_open_list
                if self.get_state(value) == "on":
                    door_open_list.append(value)
            # iterate over all door_tilted entities
            for key, value in doors_tilted_dict.items():
                # if a door is tilted ("on") add it to the door_tilted_list
                if self.get_state(value) == "on":
                    door_tilted_list.append(value)

            text = ""
            # add open windows to response
            if len(window_open_list) > 0:
                if text != "":
                    text = text + ' <break strength="weak"/>'
                text = text + self.args["textLineWindowOpen"]
                for entity in window_open_list:
                    text = (
                        text + ' <break strength="weak"/>' + self.friendly_name(entity)
                    )
            # add open doors to response
            if len(door_open_list) > 0:
                if text != "":
                    text = text + ' <break strength="weak"/>'
                text = text + self.args["textLineDoorOpen"]
                for entity in door_open_list:
                    text = (
                        text + ' <break strength="weak"/>' + self.friendly_name(entity)
                    )
            # add tilted doors to reponse
            if len(door_tilted_list) > 0:
                if text != "":
                    text = text + ' <break strength="weak"/>'
                text = text + self.args["textLineDoorTilted"]
                for entity in door_tilted_list:
                    friendly_name = self.friendly_name(entity)
                    # remove "gekippt" (german for tilted) from the friendly name
                    friendly_name = friendly_name.replace(" gekippt", "")
                    friendly_name = friendly_name.replace(" Gekippt", "")
                    text = text + ' <break strength="weak"/>' + friendly_name
            # if all closed response
            if text == "":
                text = self.args["textLineClosed"]
        except Exception as e:
            self.log("Exception: {}".format(e))
            self.log("slots: {}".format(slots))
            text = self.random_arg(self.args["Error"])
        return text

    def random_arg(self, argName):
        ############################################
        # pick a random text from a list
        ############################################
        if isinstance(argName, list):
            text = random.choice(argName)
        else:
            text = argName
        return text


================================================
FILE: alexa/windowsOpen/windowsOpenIntent.yaml
================================================
windowsOpenIntent:
  module: windowsOpenIntent
  class: WindowsOpenIntent
  language: DE
  textLineClosed: "Alle Fenster und Türen sind zu"
  #textLineClosed: "All windows and doors are closed"
  textLineWindowOpen: "Folgende Fenster sind noch offen"
  #textLineWindowOpen: "The following windows are stil open..."
  textLineDoorOpen: "Folgende Türen sind noch offen"
  #textLineDoorOpen: "The following doors are still open"
  textLineDoorTilted: "Die folgenden Türen sind noch gekippt"
  #textLineDoorTilted: "The following doors are tilted"
  Error: <p>Ich habe dich nicht richtig verstanden</p>
  unreadableState: "unlesbar fuer mich"
  dependencies:
   - listService

================================================
FILE: alexaSpeakerConnector/alexaSpeakerConnector.py
================================================
import appdaemon.plugins.hass.hassapi as hass

#
# App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers
#
# Args:
#
# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
# alexa_entity: the alexa media player entity. example: media_player.kevins_echo_dot_oben
# alexa_entity_source: source to set alexa to. example: Denon AVR-X1300W
# receiver: Receiver to turn on. example: media_player.denon_avr_x1300w
# receiver_source: source to set receiver to. example: Bluetooth
#
# Release Notes
#
# Version 1.2.0:
#   Introduce INITIAL_VOLUME
#
# Version 1.1.1:
#   Fix WAITING_TIME
#
# Version 1.1:
#   Introduce WAITING_TIME
#
# Version 1.0:
#   Initial Version

WAITING_TIME = 10
INITIAL_VOLUME = 30


class AlexaSpeakerConnector(hass.Hass):
    def initialize(self):
        self.listen_state_handle_list = []
        self.timer_handle_list = []

        self.app_switch = self.args["app_switch"]
        self.alexa_entity = self.args["alexa_entity"]
        self.alexa_entity_source = self.args["alexa_entity_source"]
        self.receiver = self.args["receiver"]
        self.receiver_source = self.args["receiver_source"]

        self.listen_state_handle_list.append(
            self.listen_state(self.state_change, self.alexa_entity)
        )

    def state_change(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.app_switch) == "on":
            if new.lower() == "playing" and old.lower() != "playing":
                self.log("{} changed to {}".format(self.alexa_entity, new))
                # Only trigger when the receiver is off. Otherwise its probably playing something
                if self.get_state(self.receiver) == "off":
                    self.log(
                        "Setting source of {} to: {}".format(
                            self.receiver, self.receiver_source
                        )
                    )
                    self.call_service(
                        "media_player/select_source",
                        entity_id=self.receiver,
                        source=self.receiver_source,
                    )
                    self.log(f"Setting volume of {self.receiver} to: {INITIAL_VOLUME}")
                    self.call_service(
                        "media_player/volume_set",
                        entity_id=self.receiver,
                        volume_level=INITIAL_VOLUME,
                    )
                    self.timer_handle_list.append(
                        self.run_in(self.run_in_callback, WAITING_TIME)
                    )

    def run_in_callback(self, kwargs):
        """
        Callback method to introduce a waiting time for the receiver to come 'online'
        :return:
        """
        self.log(
            "Setting source of {} to: {}".format(
                self.alexa_entity, self.alexa_entity_source
            )
        )
        self.call_service(
            "media_player/select_source",
            entity_id=self.alexa_entity,
            source=self.alexa_entity_source,
        )

    def terminate(self):
        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)

        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)


================================================
FILE: alexaSpeakerConnector/alexaSpeakerConnector.yaml
================================================
#App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers
# alexaSpeakerConnector:
#   module: alexaSpeakerConnector
#   class: AlexaSpeakerConnector
#   app_switch: input_boolean.alexa_speaker_connector
#   alexa_entity: media_player.kevins_echo_dot_oben
#   alexa_entity_source: Denon AVR-X1300W
#   receiver: media_player.denon_avr_x1300w
#   receiver_source: Bluetooth

================================================
FILE: appWatcher/appWatcher.py
================================================
import appdaemon.plugins.hass.hassapi as hass

#
# App which listens on the log for App crashes and notifies via telegram
#
# Args:
#
# Release Notes
#
# Version 2.0:
#   Updates for Appdaemon Version 4.0.3
#
# Version 1.0:
#   Initial Version


class AppWatcher(hass.Hass):
    def initialize(self):
        self.notify_name = self.args["notify_name"]
        self.notify_message = self.args["notify_message"]
        try:
            self.exclude_apps = self.args["exclude_apps"].split(",")
        except KeyError:
            self.exclude_apps = None

        # App dependencies
        self.notifier = self.get_app("Notifier")

        self.handle = self.listen_log(self.log_message_callback)

    def log_message_callback(self, app_name, ts, level, log_type, message, kwargs):
        if level == "WARNING" or level == "ERROR" or level == "CRITICAL":
            if app_name == "AppDaemon":
                if "Unexpected error" in message:
                    self.notifier.notify(
                        self.notify_name,
                        self.notify_message.format(message),
                        useAlexa=False,
                    )

    def terminate(self):
        self.cancel_listen_log(self.handle)


================================================
FILE: appWatcher/appWatcher.yaml
================================================
appWatcher:
  module: appWatcher
  class: AppWatcher
  notify_name: kevin
  notify_message: "AppDaemon error: {}"
  #notify_message: "Appdaemon reported an error: {}"
  dependencies:
    - Notifier

================================================
FILE: apps.yaml
================================================
#################################################################
## Global
#################################################################
global_modules:
  - globals

================================================
FILE: buttonClicked/buttonClicked.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime

#
# App which toggles entities for single/double presses of xiaomi buttons
#
# Args:
#
# sensor: sensor to monitor e.g. sensor.upstairs_smoke
# actor_single: actor to toggle on single click
# actor_double: actor to toggle on double click
# actor_hold: actor to dim on hold
# Release Notes
#
# Version 1.2:
#   All actors optional
#
# Version 1.1:
#   added actor_hold
#
# Version 1.0:
#   Initial Version


class ButtonClicked(hass.Hass):
    def initialize(self):
        self.listen_event_handle_list = []
        self.timer_handle_list = []

        self.actor_single = self.args.get("actor_single")
        self.actor_double = self.args.get("actor_double")
        self.actor_hold = self.args.get("actor_hold")

        self.dimmer_timer_handle = None

        self.listen_event_handle_list.append(
            self.listen_event(self.event_detected, "xiaomi_aqara.click")
        )

    def event_detected(self, event_name, data, kwargs):
        if data["entity_id"] == self.args["sensor"]:
            if data["click_type"] == "single" and self.actor_single != None:
                self.log("ButtonClicked: {}".format(data["entity_id"]))
                # Is on
                if self.get_state(self.actor_single) == "on":
                    self.log("Turning {} off".format(self.actor_single))
                    # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4
                    if self.actor_single.startswith("light"):
                        self.call_service(
                            "light/turn_on",
                            entity_id=self.actor_single,
                            transition=1,
                            brightness_pct=1,
                        )
                        self.timer_handle_list.append(
                            self.run_in(self.turn_off_workaround, 2)
                        )
                    else:
                        self.turn_off(self.actor_single)
                # Is off
                if self.get_state(self.actor_single) == "off":
                    self.log("Turning {} on".format(self.actor_single))
                    if self.actor_single.startswith("light"):
                        self.call_service(
                            "light/turn_on",
                            entity_id=self.actor_single,
                            transition=1,
                            brightness_pct=100,
                        )
                    else:
                        self.turn_on(self.actor_single)

            if data["click_type"] == "double" and self.actor_double != None:
                self.log("Double Button Click: {}".format(data["entity_id"]))
                self.log("Toggling {}".format(self.actor_double))
                # Is on
                if self.get_state(self.actor_double) == "on":
                    # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4
                    if self.actor_single.startswith("light"):
                        self.call_service(
                            "light/turn_on",
                            entity_id=self.actor_single,
                            transition=1,
                            brightness_pct=1,
                        )
                        self.timer_handle_list.append(
                            self.run_in(self.turn_off_workaround, 2)
                        )
                    else:
                        self.turn_off(self.actor_single)
                # Is off
                if self.get_state(self.actor_double) == "off":
                    self.log("Turning {} on".format(self.actor_single))
                    if self.actor_single.startswith("light"):
                        self.call_service(
                            "light/turn_on",
                            entity_id=self.actor_single,
                            transition=1,
                            brightness_pct=100,
                        )
                    else:
                        self.turn_on(self.actor_single)

            if data["click_type"] == "long_click_press" and self.actor_hold != None:
                self.log("Long Button Click: {}".format(data["entity_id"]))
                self.log("Starting Dimmer")
                self.dimmer_timer_handle = self.run_every(
                    self.dimmer_callback,
                    datetime.datetime.now(),
                    0.5,
                    entity_id=self.actor_hold,
                )
                self.timer_handle_list.append(self.dimmer_timer_handle)

            if data["click_type"] == "hold" and self.actor_hold != None:
                self.log("Button Release: {}".format(data["entity_id"]))
                self.log("Stopping Dimmer")
                if self.dimmer_timer_handle != None:
                    self.cancel_timer(self.dimmer_timer_handle)

    def dimmer_callback(self, kwargs):
        """Dimm the by 10% light. If it would dim above 100% start again at 10%"""
        brightness_pct_old = (
            int(
                self.get_state(self.actor_hold, attribute="all")["attributes"][
                    "brightness"
                ]
            )
            / 255
        )
        brightness_pct_new = brightness_pct_old + 0.1
        if brightness_pct_new > 1:
            brightness_pct_new = 0.1
        self.call_service(
            "light/turn_on",
            entity_id=kwargs["entity_id"],
            brightness_pct=brightness_pct_new * 100,
        )

    def turn_off_workaround(self, *kwargs):
        self.call_service("light/turn_off", entity_id=self.actor_single)

    def terminate(self):
        for listen_event_handle in self.listen_event_handle_list:
            self.cancel_listen_event(listen_event_handle)

        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)


================================================
FILE: buttonClicked/buttonClicked.yaml
================================================
# App which toggles entities for single/double presses of xiaomi buttons
# xiaomiroundButtonBedroomClicked:
#   module: buttonClicked
#   class: ButtonClicked
#   sensor: binary_sensor.switch_158d0001b12a12
#   actor_single: light.bedroom_yeelight
#   actor_double: group.all
#   actor_hold: light.bedroom_yeelight
#   dependencies: 
#     - Notifier

# xiaomisquareButtonLobbyClicked:
#   module: buttonClicked
#   class: ButtonClicked
#   sensor: binary_sensor.switch_158d00021329bc
#   actor_single: switch.lobby
#   actor_double: switch.lobby
#   dependencies: 
#     - Notifier

# xiaomiroundButtonBathroomClicked:
#   module: buttonClicked
#   class: ButtonClicked
#   sensor: sensor.0x00158d00012db9e5_click
#   actor_single: light.lower_bathroom_yeelight
#   actor_double: light.lower_bathroom_yeelight
#   actor_hold: light.lower_bathroom_yeelight
#   dependencies: 
#     - Notifier
    

================================================
FILE: comingHome/comingHome.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime

#
# App to Turn on Lobby Lamp when Door openes and no one is Home
#
# Args:
#
# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
# sensor: door sensor
# isHome: input_boolean which shows if someone is home eg input_boolean.isHome
# actor (optional): actor to turn on. example: script.receiver_set_source_bluetooth
# service (optional): service to call. example: media_player.volume_set
# service_data (optional): dictionary of attributes for the service call.
# after_sundown (optional): whether to only trigger after sundown. example: True
# Release Notes
#
# Version 1.4.2:
#   unwrap service_data
#
# Version 1.4.1:
#   fix duplicate line for self.actor
#
# Version 1.4:
#   Add service and service_data and make actor optional
#
# Version 1.3.2:
#   Check for new != old
#
# Version 1.3.1:
#   Actually implement isHome
#
# Version 1.3:
#   Added app_switch
#
# Version 1.2:
#   Added after_sundown
#
# Version 1.1:
#   Using globals
#
# Version 1.0:
#   Initial Version


class ComingHome(hass.Hass):
    def initialize(self):
        self.listen_state_handle_list = []

        self.app_switch = self.args["app_switch"]
        self.sensor = self.args["sensor"]
        self.isHome = self.args["isHome"]
        self.actor = self.args.get("actor")
        self.service = self.args.get("service")
        self.service_data = self.args.get("service_data")
        self.after_sundown = self.args.get("after_sundown")

        self.delay = 2

        self.listen_state_handle_list.append(
            self.listen_state(self.state_change, self.sensor)
        )

    def state_change(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.app_switch) == "on":
            if new != "" and new != old:
                isHome_attributes = self.get_state(self.isHome, attribute="all")
                isHome_state = isHome_attributes["state"]
                last_changed = self.convert_utc(isHome_attributes["last_changed"])
                if isHome_state == "off" or (
                    datetime.datetime.now(datetime.timezone.utc) - last_changed
                    <= datetime.timedelta(seconds=self.delay)
                ):
                    if self.after_sundown is not None and self.after_sundown:
                        if self.sun_down():
                            self.turn_on_actor(self.actor, entity, new)
                            self.my_call_service(
                                self.service, self.service_data, entity, new
                            )
                    else:
                        self.turn_on_actor(self.actor, entity, new)
                        self.my_call_service(
                            self.service, self.service_data, entity, new
                        )

    def turn_on_actor(self, actor, entity, new):
        if self.actor is not None:
            self.log("{} changed to {}".format(self.friendly_name(entity), new))
            self.turn_on(actor)

    def my_call_service(self, service, service_data, entity, new):
        if self.service is not None:
            if self.service_data is not None:
                self.log("{} changed to {}".format(self.friendly_name(entity), new))
                self.call_service(service, **service_data)

    def terminate(self):
        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: comingHome/comingHome.yaml
================================================
#Switch on Lobby lamp when the first person is coming home and the sun is down
# comingHomeYeelight:
#   module: comingHome
#   class: ComingHome
#   app_switch: input_boolean.coming_home_yeelight
#   sensor: binary_sensor.contact_door
#   isHome: input_boolean.is_home
#   actor: switch.large_lamp
#   after_sundown: True

# comingHomeSetVolume:
#   module: comingHome
#   class: ComingHome
#   app_switch: input_boolean.coming_home_set_volume
#   sensor: binary_sensor.contact_door
#   isHome: input_boolean.is_home
#   service: media_player/volume_set
#   service_data:
#     entity_id: media_player.denon_avr_x1300w
#     volume_level: 0.3

================================================
FILE: deconz_xiaomi_button/deconz_xiaomi_button.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime

#
# App which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz
#
# Args:
#
# id: id of the xiaomi button
# actor_single: actor to toggle on single click
# actor_double: actor to toggle on double click
# actor_hold: actor to dim on hold
#
# Release Notes
#
# Version 2.0.2
#   use else when toggling
#
# Version 2.0.1
#   use elif when toggling
#
# Version 2.0:
#   Removed unneeded workaround for yeelight
#
# Version 1.0:
#   Initial Version


class DeconzXiaomiButton(hass.Hass):
    def initialize(self):
        self.listen_event_handle_list = []
        self.timer_handle_list = []

        self.actor_single = self.args.get("actor_single")
        self.actor_double = self.args.get("actor_double")
        self.actor_hold = self.args.get("actor_hold")
        self.id = self.args["id"]

        self.dimmer_timer_handle = None

        self.listen_event_handle_list.append(
            self.listen_event(self.event_detected, "deconz_event")
        )

    def event_detected(self, event_name, data, kwargs):
        if data["id"] == self.id:
            if data["event"] == 1002 and self.actor_single is not None:
                self.log("ButtonClicked: {}".format(data["id"]))
                self.log("Toggling {}".format(self.actor_double))
                # Is on
                if self.get_state(self.actor_single) == "on":
                    self.log("Turning {} off".format(self.actor_single))
                    self.turn_off(self.actor_single)
                # Is off
                else:
                    self.log("Turning {} on".format(self.actor_single))
                    self.turn_on(self.actor_single)

            if data["event"] == 1004 and self.actor_double is not None:
                self.log("Double Button Click: {}".format(data["id"]))
                self.log("Toggling {}".format(self.actor_double))
                # Is on
                if self.get_state(self.actor_double) == "on":
                    self.turn_off(self.actor_double)
                # Is off
                else:
                    self.log("Turning {} on".format(self.actor_double))
                    self.turn_on(self.actor_double)

            if data["event"] == 1001 and self.actor_hold is not None:
                self.log("Long Button Click: {}".format(data["id"]))
                self.log("Starting Dimmer")
                self.dimmer_timer_handle = self.run_every(
                    self.dimmer_callback,
                    datetime.datetime.now(),
                    0.5,
                    entity_id=self.actor_hold,
                )
                self.timer_handle_list.append(self.dimmer_timer_handle)

            if data["event"] == 1003 and self.actor_hold is not None:
                self.log("Button Release: {}".format(data["id"]))
                self.log("Stopping Dimmer")
                if self.dimmer_timer_handle is not None:
                    self.cancel_timer(self.dimmer_timer_handle)

    def dimmer_callback(self, kwargs):
        """Dimm the by 10% light. If it would dim above 100% start again at 10%"""
        brightness_pct_old = (
            int(
                self.get_state(self.actor_hold, attribute="all")["attributes"][
                    "brightness"
                ]
            )
            / 255
        )
        brightness_pct_new = brightness_pct_old + 0.1
        if brightness_pct_new > 1:
            brightness_pct_new = 0.1
        self.call_service(
            "light/turn_on",
            entity_id=kwargs["entity_id"],
            brightness_pct=brightness_pct_new * 100,
        )

    def terminate(self):
        for listen_event_handle in self.listen_event_handle_list:
            self.cancel_listen_event(listen_event_handle)

        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)


================================================
FILE: deconz_xiaomi_button.yaml
================================================
DeconzXiaomiButtonBedroom:
  module: deconz_xiaomi_button
  class: DeconzXiaomiButton
  id: round_button_schlafzimmer
  actor_single: light.bedroom_yeelight
  actor_double: group.all
  actor_hold: light.bedroom_yeelight

DeconzXiaomiButtonLobby:
  module: deconz_xiaomi_button
  class: DeconzXiaomiButton
  id: flur_switch
  actor_single: switch.lobby
  actor_double: switch.lobby

DeconzXiaomiButtonLobby:
  module: deconz_xiaomi_button
  class: DeconzXiaomiButton
  id: flur_switch
  actor_single: switch.lobby
  actor_double: switch.lobby

DeconzXiaomiButtonBathroom:
  module: deconz_xiaomi_button
  class: DeconzXiaomiButton
  id: round_button_bad
  actor_single: light.lower_bathroom_yeelight
  actor_double: light.lower_bathroom_yeelight
  actor_hold: light.lower_bathroom_yeelight

================================================
FILE: detectWrongState/detectWrongState.py
================================================
import appdaemon.plugins.hass.hassapi as hass

#
# App which notifies of wrong states based on a state change
#
# Args:
#
# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
# entities_on (optional): list of entities which should be on
# entities_off (optional): list of entities which should off
# trigger_entity: entity which triggers this app. example: input_boolean.is_home
# trigger_state: new state of trigger_entity which triggers this app. example: "off"
# after_sundown (optional): Only trigger after sundown. example: True
# message_<LANG>: message to use in notification
# message_off_<LANG>: message to use in notification
# message_reed_<LANG>: message to use in notification
# message_reed_off_<LANG>: message to use in notification
# notify_name: who to notify. example: group_notifications
# use_alexa: use alexa for notification. example: False
#
# Release Notes
#
# Version 2.1:
#   More off_states to support alexa_media
#
# Version 2.0:
#   Renamed to detectWrongState, notification optional
#
# Version 1.9:
#   check unavailable when using get_state
#
# Version 1.8:
#   check None when using get_state
#
# Version 1.7:
#   check for != off instead of == on
#
# Version 1.6.1:
#   fix wrong key access for attributes
#
# Version 1.6:
#   garage_door to device_classes of reed sensors
#
# Version 1.5:
#   distinguish normal and reed switches by device_class
#
# Version 1.4.1:
#   fix wrong assignment of app_switch
#
# Version 1.4:
#   Generalize to detectWrongState
#
# Version 1.3:
#   use Notify App
#
# Version 1.2:
#   message now directly in own yaml instead of message module
#
# Version 1.1:
#   Using globals and app_switch
#
# Version 1.0:
#   Initial Version


class DetectWrongState(hass.Hass):
    def initialize(self):
        self.listen_state_handle_list = []

        self.app_switch = self.args["app_switch"]
        try:
            self.entities_on = self.args["entities_on"].split(",")
        except KeyError:
            self.entities_on = []
        try:
            self.entities_off = self.args["entities_off"].split(",")
        except KeyError:
            self.entities_off = []
        self.after_sundown = self.args.get("after_sundown")
        self.trigger_entity = self.args["trigger_entity"]
        self.trigger_state = self.args["trigger_state"]
        self.message = self.args.get("message")
        self.message_off = self.args.get("message_off")
        self.message_reed = self.args.get("message_reed")
        self.message_reed_off = self.args.get("message_reed_off")
        self.notify_name = self.args.get("notify_name")
        self.use_alexa = self.args.get("use_alexa")

        self.notifier = self.get_app("Notifier")

        self.listen_state_handle_list.append(
            self.listen_state(self.state_change, self.trigger_entity)
        )

    def state_change(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.app_switch) == "on":
            if new != "" and new == self.trigger_state:
                if self.after_sundown is None or (
                    (self.after_sundown and self.sun_down())
                    or self.after_sundown is not False
                ):
                    self.check_entities_should_be_off()
                    self.check_entities_should_be_on()

    def check_entities_should_be_off(self):
        off_states = ["off", "unavailable", "paused", "standby"]
        for entity in self.entities_off:
            state = self.get_state(entity)
            self.log(f"entity: {entity}")
            if state is not None and state not in off_states:
                if self.is_entity_reed_contact(entity):
                    message = self.message_reed
                else:
                    self.turn_off(entity)
                    message = self.message
                self.send_notification(message, entity)

    def check_entities_should_be_on(self):
        for entity in self.entities_on:
            state = self.get_state(entity)
            if state == "off":
                if self.is_entity_reed_contact(entity):
                    message = self.message_reed_off
                else:
                    self.turn_on(entity)
                    message = self.message_on
                self.send_notification(message, entity)

    def is_entity_reed_contact(self, entity):
        reed_types = ["window", "door", "garage_door"]
        full_state = self.get_state(entity, attribute="all")
        if full_state is not None:
            attributes = full_state["attributes"]
            self.log("full_state: {}".format(full_state), level="DEBUG")
            if attributes.get("device_class") in reed_types:
                return True
        return False

    def send_notification(self, message, entity):
        if message is not None:
            formatted_message = message.format(self.friendly_name(entity))
            self.log(formatted_message)
            if self.notify_name is not None:
                self.notifier.notify(
                    self.notify_name, formatted_message, useAlexa=self.use_alexa,
                )

    def terminate(self):
        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: detectWrongState/detectWrongState.yaml
================================================
# detectWrongStateWhenLeaving:
#   module: detectWrongState
#   class: DetectWrongState
#   app_switch: input_boolean.detect_wrong_state_when_leaving
#   entities_off: "binary_sensor.contact_bedroom_door,\
#   binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\
#   binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\
#   binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\
#   binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\
#   media_player.denon_avr_x1300w,switch.large_lamp,switch.small_lamp,switch.snowboard,\
#   light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,light.reading_lamp_yeelight,\
#   light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\
#   switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,light.lower_bathroom_yeelight,\
#   media_player.kevin_s_echo_dot_unten,media_player.kevins_echo,media_player.kevins_echo_dot,\
#   media_player.kevins_echo_dot_oben,binary_sensor.contact_upper_bathroom_window_tilted,\
#   binary_sensor.contact_badfenster"
#   trigger_entity: input_boolean.is_home
#   trigger_state: "off"
#   message: "Du hast {} angelassen. Ich habe es für dich ausgemacht."
#   #message: "You left on {}. I turned it off for you"
#   message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht."
#   #message_off: "You forgot to turn on {}. I turned it on for you"
#   message_reed: "Du hast {} offen gelassen."
#   #message_reed: "You left open {} Dummy."
#   message_reed_off: "Du hast {} zu gelassen."
#   #message_reed_off: "You left {} closed Dummy."
#   notify_name: group_notifications
#   use_alexa: False
#   log_level: DEBUG
#   dependencies: 
#     - Notifier

# detectWindowsOpenWhenGoingToBed:
#   module: detectWrongState
#   class: DetectWrongState
#   app_switch: input_boolean.detect_windows_open_when_going_to_bed
#   entities_off: "binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\
#   binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\
#   binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\
#   binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\
#   binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\
#   binary_sensor.contact_upper_bathroom_window_tilted,binary_sensor.contact_badfenster"
#   after_sundown: True
#   trigger_entity: input_boolean.sleepmode
#   trigger_state: "on"
#   message: "Du hast {} angelassen. Ich habe es für dich ausgemacht."
#   #message: "You left on {}. I turned it off for you"
#   message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht."
#   #message_off: "You forgot to turn on {}. I turned it on for you"
#   message_reed: "Du hast {} offen gelassen."
#   #message_reed: "You left open {} Dummy."
#   message_reed_off: "Du hast {} zu gelassen."
#   #message_reed_off: "You left {} closed Dummy."
#   notify_name: group_notifications
#   use_alexa: True
#   log_level: DEBUG
#   dependencies: 
#     - Notifier

# detectDevicesOnWhenGoingToBed:
#   module: detectWrongState
#   class: DetectWrongState
#   app_switch: input_boolean.detect_devices_on_when_going_to_bed
#   entities_off: "media_player.denon_avr_x1300w,switch.large_lamp,\
#   switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\
#   light.reading_lamp_yeelight,light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\
#   switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,switch.tv,light.bar_table,light.lower_bathroom_yeelight,\
#   switch.markise, switch.coffee_machine_plug_relay"
#   trigger_entity: input_boolean.sleepmode
#   trigger_state: "on"
#   log_level: DEBUG
#   dependencies:
#     - Notifier

================================================
FILE: ench.yaml
================================================
---
ench:
  module: ench
  class: EnCh
  notify: "notify.kevin"
  exclude:
    - device_tracker.venue_8*
    - person.kevin
    - device_tracker.sonoff_large_ventilator_7998
    - device_tracker.sonoff_ventilator_8133
    - device_tracker.android*
    - device_tracker.astrids_mbp
    - device_tracker.franzi_s_iphone
    - device_tracker.galaxy*
    - device_tracker.iphone*
    - device_tracker.oneplus*
    - device_tracker.unifi*
    - light.group_0
    - media_player.kevin_s*
    - sensor.192_168_1_39*
    - sensor.192_168_1_48*
    - sensor.large_ventilator*
    - sensor.ventilator*
    - switch.ventilator
    - switch.large_ventilator
    - sensor.travel_time_next*
    - sensor.glances*_temp
    - sensor.publish_ip_on_boot
    - sensor.*odroid*
    - media_player.55pus7304_12
    - media_player.fernseher
    - sensor.consumption_31
    - sensor.power_30
    - sensor.openweathermap*
    - light.bar_table
    - sensor.*_nachster_wecker
    - switch.xiaomi_plug
    - media_player.55pus7304_12_*
    # Alexa Media Player
    - sensor.*next_alarm
    - sensor.*next_reminder
    - sensor.*next_timer
    - sensor.this_device*
    - switch.*_repeat_switch*
    - switch.*_shuffle_switch*
    - switch.*do_not_disturb_switch
  battery:
    interval_min: 180
    min_level: 20
  unavailable:
    interval_min: 60
    max_unavailable_min: 15


================================================
FILE: eventMonitor/eventMonitor.py
================================================
import appdaemon.plugins.hass.hassapi as hass

"""
Monitor events and output changes to the verbose_log. Nice for debugging purposes.
Arguments:
 - events: List of events to monitor
"""


class Monitor(hass.Hass):
    def initialize(self):
        self.listen_event_handle_list = []

        events = self.args["events"]

        if events != None:
            for event in self.split_device_list(self.args["events"]):
                self.log('watching event "{}" for state changes'.format(event))
                self.listen_event_handle_list.append(
                    self.listen_event(self.changed, event)
                )
        if len(self.listen_event_handle_list) == 0:
            self.log("watching all events for state changes")
            self.listen_event_handle_list.append(self.listen_event(self.changed))

    def changed(self, event_name, data, kwargs):
        self.log(event_name + ": " + str(data))

    def terminate(self):
        for listen_event_handle in self.listen_event_handle_list:
            self.cancel_listen_event(listen_event_handle)


================================================
FILE: eventMonitor/eventMonitor.yaml
================================================
#eventMonitor:
#  module: eventMonitor
#  class: Monitor
#  events: 

================================================
FILE: faceRecognitionBot/faceRecognitionBot.py
================================================
import json
from json import JSONDecodeError

import appdaemon.plugins.hass.hassapi as hass  # pylint: disable=import-error
import shutil
import os
import time
import datetime
import requests

#
# App which runs face detection and notifies the user with the result
#
#
# Args:
#
# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
# sensor: binary sensor to use as trigger
# button: xiaomi button to use as a trigger
# camera : camera entity. example: camera.ip_webcam
# local_file_camera: local file camera entity. example: camera.saved_image
# notify_name: Who to notify. example: group_notifications
# wol_switch: Wake on Lan switch which turns on the facebox server. example: switch.facebox_wol
# user_id: The user_id of the telegram user to ask whether he knows an unknown face
# number_of_images: Number of images to take. example: 10
# waitBeforeSnapshot: How many seconds to wait before triggering the inital snapshot. example: 2.5
# message_face_identified: Message to send if a face got identified.
# message_unkown_face: Message to send if a face is unknown
# message_provide_name
# message_name_provided
# message_name_provided_callback
# ip: Ip of facerec_service. example: 192.168.0.1
# port: port of facerec_service. example: 8080
#
# Release Notes
#
# Version 1.3.beta:
#   Fully working
#
# Version 1.2:
#   Rework to FaceRecognitionBot
#
# Version 1.1:
#   Take Snapshot before sending WoL
#
# Version 1.0:
#   Initial Version
CLASSIFIER = "faces"
TIMEOUT = 20
PROVIDE_NAME_TIMEOUT = 5
IDENTIFIER_DELIMITER = "_"
FILENAME_DELIMITER = "-"
MAXIMUM_DISTANCE = 0.40
UNKNOWN_FACE_NAME = "unkown"


class FaceRecognitionBot(hass.Hass):
    def initialize(self):

        # handle lists
        self.timer_handle_list = []
        self.listen_event_handle_list = []
        self.listen_state_handle_list = []

        # args
        self.app_switch = self.args["app_switch"]
        self.sensor = self.args["sensor"]
        self.button = self.args["button"]
        self.camera = self.args["camera"]
        self.local_file_camera = self.args["local_file_camera"]
        self.filename = self.args["filename"]
        self.notify_name = self.args["notify_name"]
        self.wol_switch = self.args["wol_switch"]
        self.user_id = self.args["user_id"]
        self.number_of_images = self.args["number_of_images"]
        self.waitBeforeSnapshot = self.args["waitBeforeSnapshot"]
        self.message_face_identified = self.args["message_face_identified"]
        self.message_unkown_face = self.args["message_unkown_face"]
        self.message_unkown_face_with_known = self.args[
            "message_unkown_face_with_known"
        ]
        self.message_provide_name = self.args["message_provide_name"]
        self.message_name_provided = self.args["message_name_provided"]
        self.message_name_provided_callback = self.args[
            "message_name_provided_callback"
        ]
        self.facebox_healthcheck_filename = self.args["facebox_healthcheck_filename"]
        self.healthcheck_face_name = self.args["healthcheck_face_name"]
        self.ip = self.args["ip"]
        self.port = self.args["port"]

        # optional args
        self.facebox_source_directory = self.args["facebox_source_directory"]
        if not self.facebox_source_directory.endswith("/"):
            self.facebox_source_directory = self.facebox_source_directory + "/"
        self.facebox_unknown_directory = self.args["facebox_unknown_directory"]
        if not self.facebox_unknown_directory.endswith("/"):
            self.facebox_unknown_directory = self.facebox_unknown_directory + "/"
        self.facebox_noface_directory = self.args["facebox_noface_directory"]
        if not self.facebox_noface_directory.endswith("/"):
            self.facebox_noface_directory = self.facebox_noface_directory + "/"
        self.facebox_known_faces_directory = self.args["facebox_known_faces_directory"]
        if not self.facebox_known_faces_directory.endswith("/"):
            self.facebox_known_faces_directory = (
                self.facebox_known_faces_directory + "/"
            )

        # App dependencies
        self.notifier = self.get_app("Notifier")

        # Subscribe to sensors
        self.listen_state_handle_list.append(
            self.listen_state(self.triggered, self.sensor)
        )
        # Subscribe to custom triggers
        self.listen_event_handle_list.append(
            self.listen_event(self.button_clicked, "xiaomi_aqara.click")
        )
        self.listen_event_handle_list.append(
            self.listen_event(self.learn_faces_event_callback, "eifinger_learn_faces")
        )
        # subscribe to telegram events
        self.listen_event_handle_list.append(
            self.listen_event(self.receive_telegram_callback, "telegram_callback")
        )
        self.listen_event_handle_list.append(
            self.listen_event(self.receive_telegram_text, "telegram_text")
        )
        # Teach periodic run
        self.timer_handle_list.append(self.run_in(self.check_health_callback, 5))

        # custom variables

        self.valid_filetypes = (".jpg", ".png", ".jpeg")

        self.teach_url = "http://{}:{}/faces".format(self.ip, self.port)
        self.health_url = "http://{}:{}/faces".format(self.ip, self.port)
        self.check_url = "http://{}:{}".format(self.ip, self.port)

        self.run_in_initial_delay = 43200
        self.run_in_delay = self.run_in_initial_delay
        self.run_in_error_delay = 60
        self.check_health_timeout = 10

        self.exclude_folders = (
            "healthcheck",
            "multiple",
            "noface",
            "tmp",
            "unknown",
            "new",
        )

        self.provide_name_timeout_start = None
        self.last_identifier = None
        self.last_message_id = None
        self.last_from_first = None

    ###############################################################
    # Teacher
    ###############################################################
    def check_health_callback(self, kwargs):
        """Check health.
        
        Runs repeatedly until it is veryfied that the classifier is healthy and faces are trained.
        
        If it is healthy and trained it will check again after run_in_initial_delay"""
        try:
            if self.check_classifier_health():
                self.check_if_trained(None)
                self.timer_handle_list.append(
                    self.run_in(self.check_health_callback, self.run_in_delay)
                )
        except requests.exceptions.HTTPError as exception:
            self.log(
                "Error trying to turn on entity. Will try again in 1s. Error: {}".format(
                    exception
                ),
                level="WARNING",
            )
            self.timer_handle_list.append(self.run_in(self.check_health_callback, 1))

    def learn_faces_event_callback(self, event_name, data, kwargs):
        """Callback function for manual trigger of face learning"""
        self.log("Event received. Triggering Face Learning")
        self.teach_faces(self.facebox_known_faces_directory, self.exclude_folders)

    def teach_name_by_file(self, teach_url, name, file_path):
        """Teach the classifier a single name using a single file."""
        file_name = file_path.split("/")[-1]
        file = {"file": open(file_path, "rb")}

        teach_url = teach_url + "?id=" + name
        response = requests.post(teach_url, files=file)

        if response.status_code == 200:
            self.log("File: {} taught with name: {}".format(file_name, name))
            return True

        elif response.status_code == 400:
            self.log(
                "Teaching of file: {} failed with message: {}".format(
                    file_name, response.text
                )
            )
            return False

    def check_classifier_health(self):
        """Check if classifier is reachable under health_url and returns HTTP 200"""
        try:
            response = requests.get(self.health_url, timeout=self.check_health_timeout)
            if response.status_code == 200:
                self.log("Health-check passed")
                self.run_in_delay = self.run_in_initial_delay
                self.log("Setting run_in_delay to {}".format(self.run_in_delay))
                return True
            else:
                self.log("Health-check failed")
                self.log(response.status_code)
                # check for recurring error
                if self.run_in_delay < self.run_in_initial_delay:
                    self.run_in_delay = self.run_in_delay * 2
                else:
                    self.run_in_delay = self.run_in_error_delay
                return False
        except requests.exceptions.Timeout as timeout_exception:
            self.log("Health-Check timed out")
            self.check_health_timeout = self.check_health_timeout * 1.5
            return self.check_classifier_health()
        except requests.exceptions.RequestException as exception:
            self.log("Server is unreachable", level="WARNING")
            self.log(exception, level="WARNING")
            # check for recurring error
            if self.run_in_delay < self.run_in_initial_delay:
                self.run_in_delay = self.run_in_delay * 2
            else:
                self.run_in_delay = self.run_in_error_delay
            self.log("Setting run_in_delay to {}".format(self.run_in_delay))

    def check_if_trained(self, kwargs):
        """Check if faces are trained. If not train them.
        
        Checks for a picture with a known result if the classifier returns the correct result
        """
        response = self.post_image(self.check_url, self.facebox_healthcheck_filename)
        if (
            response
            and response.status_code == 200
            and len(response.json()["faces"]) > 0
            and response.json()["faces"][0]["id"] == self.healthcheck_face_name
        ):
            self.log("Faces are still taught")
        else:
            self.log("Faces are not taught")
            self.teach_faces(self.facebox_known_faces_directory, self.exclude_folders)

    def teach_faces(self, folderpath, exclude_folders=[]):
        """Teach faces.

        Will iterate over all subdirectories of 'folderpath' and teach the name within
        that subdirectory with the name of the subdirectory"""
        self.log("Teaching faces")
        for folder_name in self.list_folders(folderpath):
            if not folder_name in exclude_folders:
                folder_path = os.path.join(folderpath, folder_name)
                for file in os.listdir(folder_path):
                    if file.endswith(self.valid_filetypes):
                        file_path = os.path.join(folder_path, file)
                        self.teach_name_by_file(self.teach_url, folder_name, file_path)

    def teach_name_by_directory(self, name, folderpath):
        """Teach faces in a directory for a given anme"""
        self.log("Teaching faces in dir: {}".format(folderpath))
        for file in os.listdir(folderpath):
            if file.endswith(self.valid_filetypes):
                file_path = os.path.join(folderpath, file)
                self.teach_name_by_file(self.teach_url, name, file_path)

    ###############################################################
    # Classifier
    ###############################################################
    def button_clicked(self, event_name, data, kwargs):
        """Extra callback method to trigger the face detection on demand by pressing a Xiaomi Button"""
        if data["entity_id"] == self.button:
            if data["click_type"] == "single":
                self.timer_handle_list.append(
                    self.run_in(self.takeSnapshots, self.waitBeforeSnapshot)
                )

    def triggered(self, entity, attribute, old, new, kwargs):
        """State Callback to start the face detection process"""
        if self.get_state(self.app_switch) == "on":
            if new == "on":
                self.timer_handle_list.append(
                    self.run_in(self.takeSnapshots, self.waitBeforeSnapshot)
                )

    def takeSnapshots(self, kwargs):
        """Take a snapshot. Save to a file."""
        file_locations = []
        timestamp = time.strftime("%Y%m%d%H%M%S")
        directory = self.facebox_source_directory + "new/" + timestamp
        if not os.path.exists(directory):
            os.makedirs(directory)
        for i in range(0, self.number_of_images):
            filename = (
                directory + "/" + timestamp + FILENAME_DELIMITER + str(i) + ".jpg"
            )
            self.log("Calling camera/snapshot and saving it to: {}".format(filename))
            self.call_service(
                "camera/snapshot", entity_id=self.camera, filename=filename
            )
            file_locations.append(filename)
        self.timer_handle_list.append(
            self.run_in(self.processImages, 0, file_locations=file_locations)
        )

    def processImages(self, kwargs):
        """Trigger image processing for all images and process the results
        
         Get the classifier result for each image
         store it in a dictionary in the following format
         {filename:
                  {"count":int,
                   "faces":{
                             [
                               {"dist":float,
                                "id":name}
                             ]
                            },
                   "matchedFacesCount":int}
         }
        """
        result_dict_dict = {}
        for filename in kwargs["file_locations"]:
            response = self.post_image(self.check_url, filename)
            if response is not None:
                result_dict = {}
                self.log("response is: {}".format(response.text))
                try:
                    response_json = response.json()
                    result_dict["count"] = response_json["count"]
                    result_dict["faces"] = response_json["faces"]
                    result_dict["matchedFacesCount"] = len(response_json["faces"])
                    result_dict_dict[filename] = result_dict
                except JSONDecodeError:
                    self.log("JSONDecodeError. Skipping response")
        # get the maximum number of faces detected in one image
        maxCount = self._getMaxCountFromResult(result_dict_dict)
        # get a list of distinct recognized face names
        faceNames = self._getFaceNamesFromResult(result_dict_dict)
        self.log("Number of distinct faces: {}".format(len(faceNames)))
        if maxCount > 1:
            self.log("At least one time detected more than one face")
            # check if it contains an unknown face
            if UNKNOWN_FACE_NAME in faceNames:
                self._notifyUnkownFaceFound(result_dict_dict)
            else:
                for faceName in faceNames:
                    if faceName in self._getKnownFaces():
                        self.log(self.message_face_identified.format(faceName))
                        self.notifier.notify(
                            self.notify_name,
                            self.message_face_identified.format(faceName),
                        )
                        # copy file to saved image to display in HA
                        shutil.copy(filename, self.filename)
        elif maxCount == 1:
            self.log("Always detected one face")
            # check if always the same face
            if len(faceNames) > 1:
                self.log("Not always the same face")
                # TODO test!
                # at least one time detected a known face
                # notify of who was detected
                for faceName in faceNames:
                    if faceName in self._getKnownFaces():
                        self.log(self.message_face_identified.format(faceName))
                        self.notifier.notify(
                            self.notify_name,
                            self.message_face_identified.format(faceName),
                        )
                        # copy file to saved image to display in HA
                        shutil.copy(filename, self.filename)
                # process the unknown faces
                if UNKNOWN_FACE_NAME in faceNames:
                    self._processUnkownFaceFound(result_dict_dict)
            else:
                self.log("Always the same face")
                # Is it a known face?
                if len(faceNames) > 0 and faceNames[0] in self._getKnownFaces():
                    # always identified the same known person
                    self.log(self.message_face_identified.format(faceNames[0]))
                    self.notifier.notify(
                        self.notify_name,
                        self.message_face_identified.format(faceNames[0]),
                    )
                    # Move files to known face subdirectory
                    for filename in result_dict_dict:
                        # at this time we know it is at most 1 and it is always the same known face
                        if result_dict_dict[filename]["count"] == 1:
                            directory = (
                                self.facebox_known_faces_directory
                                + result_dict_dict[filename]["faces"][0]["id"]
                            )
                            new_filename = os.path.join(
                                directory, os.path.split(filename)[1]
                            )
                            # copy file to saved image to display in HA
                            shutil.copy(filename, self.filename)
                            self.log(
                                "Move file from {} to {}".format(filename, new_filename)
                            )
                            shutil.move(filename, new_filename)
                            # trigger teaching
                            self.teach_name_by_file(
                                self.teach_url,
                                result_dict_dict[filename]["faces"][0]["id"],
                                new_filename,
                            )
                else:
                    # unknown face
                    self._processUnkownFaceFound(result_dict_dict)
        else:
            self.log("Detected no faces")
            # get directory of images and post that in telegram

    def _processUnkownFaceFound(self, result_dict_dict):
        """Store the faces for later use and ask the user if he knows the unkown face"""
        # TODO check if the faces are similar
        # create a temp identifier, compare and delete identifier again
        # self._determineIfSameUnkownFace(result_dict_dict)
        # get a file where the unknown face was detected and send it
        filename = self._getFileWithUnknownFaceFromResult(result_dict_dict)
        # copy file to saved image to display in HA
        shutil.copy(filename, self.filename)
        # move all files where a face was detected to the unkown folder
        identifier = self._moveFilesToUnknown(result_dict_dict)

        filename_without_path = os.path.split(filename)[1]
        # send photo
        unknown_filename = self.facebox_unknown_directory + filename_without_path
        self.log(f"Sending photo with filename: {unknown_filename}")
        self.call_service("telegram_bot/send_photo", file=unknown_filename, target=self.user_id)
        
        if identifier == "":
            self.log("Identifier is empty", level="ERROR")
        else:
            self.ask_for_name(identifier)

    def _notifyUnkownFaceFound(self, result_dict_dict):
        """Notify of an unkown face in a image where a known face was detected"""
        # get a file where the unknown face was detected and send it
        filename = self._getFileWithUnknownFaceFromResult(result_dict_dict)
        # copy file to saved image to display in HA
        shutil.copy(filename, self.filename)
        # send photo
        self.call_service("telegram_bot/send_photo", file=filename, target=self.user_id)
        self.log(self.message_unkown_face_with_known)
        self.notifier.notify(self.notify_name, self.message_unkown_face_with_known)

    def _getMaxCountFromResult(self, result_dict_dict):
        """Get the maximum number of faces found in the pictures"""
        count_list = [d["count"] for d in result_dict_dict.values()]
        return max(count_list)

    def _getFaceNamesFromResult(self, result_dict_dict):
        """Return a list of names for the identified faces"""
        try:
            id_list = []
            for d in result_dict_dict.values():
                # check for unknown face
                if len(d["faces"]) == 0 and d["count"] == 1:
                    id_list.append(UNKNOWN_FACE_NAME)
                else:
                    for face in d["faces"]:
                        if face["dist"] < MAXIMUM_DISTANCE:
                            id_list.append(face["id"])
                        # if distance(similarity) too large, mark as unknown
                        else:
                            self.log(
                                "Similary distance of {} is larger than maximum threshold of {}".format(
                                    face["dist"], MAXIMUM_DISTANCE
                                )
                            )
                            id_list.append(UNKNOWN_FACE_NAME)
                            face["id"] = UNKNOWN_FACE_NAME
            self.log("FacesNames: {}".format(list(set(id_list))))
            return list(set(id_list))
        except TypeError:
            return []

    def _getFileWithUnknownFaceFromResult(self, result_dict_dict):
        """Get the first file from the result which has an unmatched face"""
        for filename in result_dict_dict.keys():
            if (
                result_dict_dict[filename]["count"] > 0
                and result_dict_dict[filename]["faces"][0]["id"] == UNKNOWN_FACE_NAME
            ):
                return filename

    def _determineIfSameUnkownFace(self, result_dict_dict):
        """Determine if the unkown face which was detected several times is the same unknown face"""
        # get all files with unknown faces
        unkown_faces = []
        for filename in result_dict_dict.keys():
            if (
                result_dict_dict[filename]["count"] == 1
                and result_dict_dict[filename]["faces"][0]["id"] == UNKNOWN_FACE_NAME
            ):
                unkown_faces.append(filename)
        # iterate over all files
        for k, filename in enumerate(unkown_faces):
            for i, filename in enumerate(unkown_faces):
                if i < k:
                    pass
                elif i == k:
                    # teach the first face
                    filename_without_path = os.path.split(filename)[1]
                    self.teach_name_by_file(
                        self.teach_url, filename_without_path, filename
                    )
                else:
                    response = self.post_image(self.check_url, filename)
                    response_json = response.json()
                    if response_json["count"] > 0:
                        if (
                            response_json["faces"][0]["id"] == filename_without_path
                            and response_json["faces"][0]["dist"] < MAXIMUM_DISTANCE
                        ):
                            # same face remove it from the list
                            unkown_faces.remove(filename)

    def _moveFilesToUnknown(self, result_dict_dict):
        """Copy all files where the unknown face was detected to the unknown folder.
        Returns the timestamp under which all files can be identified"""
        identifier = ""
        for filename in result_dict_dict.keys():
            if result_dict_dict[filename]["count"] > 0 and (
                len(result_dict_dict[filename]["faces"]) == 0
                or result_dict_dict[filename]["faces"][0]["id"] == UNKNOWN_FACE_NAME
            ):
                filename_without_path = os.path.split(filename)[1]
                # get the timestamp as identifier, strip everything after "-""
                identifier = filename_without_path.split(FILENAME_DELIMITER)[0]
                self.log("Identifier is: {}".format(identifier), level="DEBUG")
                new_filename = self.facebox_unknown_directory + filename_without_path
                self.log("Move file from {} to {}".format(filename, new_filename))
                shutil.move(filename, new_filename)
        return identifier

    def _moveFilesFromUnkownToDirectoryByIdentifier(self, directory, identifier):
        """Copy all files in the unknown folder which belong to an identifier (a timestamp) to a new directory"""
        if not os.path.exists(directory):
            os.makedirs(directory)
        for file in os.listdir(self.facebox_unknown_directory):
            if identifier in file:
                filename = os.path.join(self.facebox_unknown_directory, file)
                new_filename = os.path.join(directory, file)
                self.log("Move file from {} to {}".format(filename, new_filename))
                shutil.move(filename, new_filename)

    def _getKnownFaces(self):
        """Return a list of known face names.

        Iterates over the subdirectory names of facebox_known_faces_directory"""
        return self.list_folders(self.facebox_known_faces_directory)

    def list_folders(self, directory):
        """Returns a list of folders
        These are not full paths, just the folder."""
        folders = [
            dir
            for dir in os.listdir(directory)
            if os.path.isdir(os.path.join(directory, dir))
            and not dir.startswith(directory)
            and not dir.startswith(".")
        ]
        folders.sort(key=str.lower)
        return folders

    def post_image(self, url, image):
        """Post an image to the classifier."""
        try:
            response = requests.post(
                url, files={"file": open(image, "rb")}, timeout=self.check_health_timeout
            )
            return response
        except requests.exceptions.ConnectionError:
            self.log("ConnectionError")
        except requests.exceptions.ReadTimeout:
            self.log("ReadTimeout")
            self.check_health_timeout = self.check_health_timeout * 1.5
            self.log(f"Setting Health Check Timeout to {self.check_health_timeout}")

    ###############################################################
    # Telegram Bot
    ###############################################################

    def ask_for_name(self, identifier):
        """Asks the user if he knows the face in the photo.
        The identifier is needed to link the user reply back to this message"""
        self.log("Asking for name")
        keyboard = [[("Unbekannt", "/unkown" + IDENTIFIER_DELIMITER + identifier)]]
        for face in self._getKnownFaces():
            keyboard.append([(face, "/" + face + IDENTIFIER_DELIMITER + identifier)])
        self.log("keyboard is: {}".format(keyboard), level="DEBUG")
        self.call_service(
            "telegram_bot/send_message",
            target=self.user_id,
            message=self.message_unkown_face,
            inline_keyboard=keyboard,
        )

    def receive_telegram_callback(self, event_name, data, kwargs):
        """Event listener for Telegram callback queries."""
        self.log("callback data: {}".format(data))
        data_callback = data["data"]
        callback_id = data["id"]
        chat_id = data["chat_id"]
        message_id = data["message"]["message_id"]
        text = data["message"]["text"]
        from_first = data["from_first"]

        for face in self._getKnownFaces():
            if data_callback.startswith("/" + face + IDENTIFIER_DELIMITER):
                self.log("Received Telegram Callback for {}".format(face))
                self.call_service(
                    "telegram_bot/answer_callback_query",
                    message="Dankeschön!",
                    callback_query_id=callback_id,
                )
                self.call_service(
                    "telegram_bot/edit_message",
                    chat_id=chat_id,
                    message_id=message_id,
                    message=self.message_name_provided_callback.format(
                        from_first, face
                    ),
                    inline_keyboard=[],
                )
                identifier = data_callback.split(IDENTIFIER_DELIMITER)[1]
                directory = self.facebox_known_faces_directory + face
                self._moveFilesFromUnkownToDirectoryByIdentifier(directory, identifier)
                self.teach_name_by_directory(face, directory)

        if data_callback.startswith("/unkown"):
            # Answer callback query
            self.call_service(
                "telegram_bot/answer_callback_query",
                message="Dankeschön!",
                callback_query_id=callback_id,
            )
            self.call_service(
                "telegram_bot/edit_message",
                chat_id=chat_id,
                message_id=message_id,
                message=text,
                inline_keyboard=[],
            )
            self.notifier.notify(
                self.notify_name,
                self.message_provide_name.format(PROVIDE_NAME_TIMEOUT),
                useAlexa=False,
            )
            self.provide_name_timeout_start = datetime.datetime.now()
            self.last_identifier = data_callback.split(IDENTIFIER_DELIMITER)[1]
            self.last_message_id = message_id
            self.last_from_first = from_first

    def receive_telegram_text(self, event_name, data, kwargs):
        """Telegram text listener"""
        self.log("callback data: {}".format(data), level="DEBUG")
        chat_id = data["chat_id"]
        text = data["text"]

        if self.provide_name_timeout_start != None and (
            datetime.datetime.now() - self.provide_name_timeout_start
            < datetime.timedelta(minutes=PROVIDE_NAME_TIMEOUT)
        ):
            # Edit the last ask_for_name message
            self.call_service(
                "telegram_bot/edit_message",
                chat_id=chat_id,
                message_id=self.last_message_id,
                message=self.message_name_provided_callback.format(
                    self.last_from_first, text
                ),
                inline_keyboard=[],
            )
            # Say thanks
            self.notifier.notify(
                self.notify_name,
                self.message_name_provided.format(text),
                useAlexa=False,
            )
            # Copy files to new directory
            directory = self.facebox_known_faces_directory + text
            self._moveFilesFromUnkownToDirectoryByIdentifier(
                directory, self.last_identifier
            )
            self.teach_name_by_directory(text, directory)
        else:
            self.log("PROVIDE_NAME_TIMEOUT exceeded")

    def terminate(self):
        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)

        for listen_event_handle in self.listen_event_handle_list:
            self.cancel_listen_event(listen_event_handle)

        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: faceRecognitionBot/faceRecognitionBot.yaml
================================================
# faceRecognitionBot:
#   module: faceRecognitionBot
#   class: FaceRecognitionBot
#   app_switch: input_boolean.facebox_notifier
#   sensor: binary_sensor.contact_door
#   button: binary_sensor.switch_158d000215aa28
#   camera: camera.android_ip_webcam_door
#   local_file_camera: camera.saved_image
#   filename: !secret facebox_notifier_filename
#   image_processing: image_processing.facebox
#   notify_name: group_notifications
#   wol_switch: switch.facebox_wol
#   user_id: !secret telegram_user_id
#   facebox_source_directory: !secret facebox_folderpath
#   facebox_unknown_directory: !secret facebox_unknown_directory
#   facebox_noface_directory: !secret facebox_noface_directory
#   facebox_known_faces_directory: !secret facebox_known_faces_directory
#   facebox_healthcheck_filename: !secret facebox_healthcheck_filename
#   healthcheck_face_name: Kevin 
#   number_of_images: 10
#   waitBeforeSnapshot: 1
#   ip: !secret facebox_ip
#   port: !secret facebox_port
#   message_face_identified: "Ich habe {} erkannt"
#   #message_face_identified: "I have recognized {}."
#   message_unkown_face: "Ich habe dieses Gesicht nicht erkannt. Kennst du es?"
#   #message_unkown_face: "I have not recognized this face. Do you know it?"
#   message_unkown_face_with_known: "Ich habe auch ein unbekanntes Gesicht entdeckt."
#   #message_unkown_face_with_known: "I have also discovered an unknown face."
#   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!"
#   #message_provide_name: "If you know the face you can write the name to me within the next {} minutes. I will remember it!"
#   message_name_provided: "Okay. Ich merke mir, dass das {} ist"
#   #message_name_provided: "Okay. I will remember that this is {}"
#   message_name_provided_callback: "{} sagte, dass dies {} ist."
#   #message_name_provided_callback: "{} said that this is {}"
#   dependencies:
#     - Notifier

================================================
FILE: globals.py
================================================
import random


def random_arg(argList):
    ############################################
    # pick a random text from a list
    ############################################
    if isinstance(argList, list):
        text = random.choice(argList)
    else:
        text = argList
    return text


================================================
FILE: heartbeat/heartbeat.py
================================================
import appdaemon.plugins.hass.hassapi as hass
from requests.exceptions import HTTPError

#
# App which sets a homeassistant entity as a heartbeat to check for threadstarvation etc
#
# Args:
# sensor: sensor.appdaemon_heartbeat
#
# Release Notes
#
# Version 1.1:
#   Set start to None run_minutely will run after 1 minute
#
# Version 1.0:
#   Initial Version


class Heartbeat(hass.Hass):
    def initialize(self):
        self.timer_handle_list = []

        self.sensor = self.args["sensor"]

        self.heartbeat(None)

        self.timer_handle_list.append(self.run_minutely(self.heartbeat, start=None))

    def heartbeat(self, kwargs):
        try:
            self.set_state(self.sensor, state=str(self.time()))
            self.log("Heartbeat", level="DEBUG")
        except HTTPError as exception:
            self.log(
                "Error trying to set entity. Will try again in 5s. Error: {}".format(
                    exception
                ),
                level="WARNING",
            )
            self.timer_handle_list.append(self.run_in(self.heartbeat, 5))

    def terminate(self):
        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)


================================================
FILE: heartbeat/heartbeat.yaml
================================================
heartbeat:
  module: heartbeat
  class: Heartbeat
  sensor: sensor.appdaemon_heartbeat

================================================
FILE: homeArrivalNotifier/homeArrivalNotifier.py
================================================
import appdaemon.plugins.hass.hassapi as hass

#
# App to send a notification if someone arrives at home
#
# Args:
#  app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
#  input_boolean: input boolean which holds the information of someone is home or not
#  notify_name: Who to notify
#  user_name: name to use in notification message
#  zone_name: Name of the zone
#  message: message to use in notification
# Release Notes
#
# Version 1.4.1:
#   Use consistent message variable
#
# Version 1.4:
#   use Notify App
#
# Version 1.3:
#   message now directly in own yaml instead of message module
#
# Version 1.2:
#   Added app_switch
#
# Version 1.1:
#   Added user_name
#
# Version 1.0:
#   Initial Version


class HomeArrivalNotifier(hass.Hass):
    def initialize(self):
        self.listen_state_handle_list = []

        self.app_switch = self.args["app_switch"]
        self.zone_name = self.args["zone_name"]
        self.input_boolean = self.args["input_boolean"]
        self.notify_name = self.args["notify_name"]
        self.user_name = self.args["user_name"]
        self.message = self.args["message"]

        self.notifier = self.get_app("Notifier")

        self.listen_state_handle_list.append(
            self.listen_state(self.state_change, self.input_boolean)
        )

    def state_change(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.app_switch) == "on":
            if new != "" and new != old:
                self.log("{} changed from {} to {}".format(entity, old, new))
                if new == "on":
                    self.log(
                        "{} arrived at {}".format(self.notify_name, self.zone_name)
                    )
                    self.notifier.notify(
                        self.notify_name, self.message.format(self.user_name)
                    )

    def terminate(self):
        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: homeArrivalNotifier/homeArrivalNotifier.yaml
================================================
#Notification if user one arrives at home
# homeArrivalNotifierUserOne:
#   module: homeArrivalNotifier
#   class: HomeArrivalNotifier
#   app_switch: input_boolean.home_arrival_notifier_user_one
#   input_boolean: input_boolean.user_one_home
#   notify_name: group_notifications
#   user_name: Kevin
#   zone_name: Home
#   message: "Willkommen zu Hause {}."
#   #message: "Welcome Home {}."
#   dependencies: 
#     - Notifier

# #Notification if user two arrives at home
# homeArrivalNotifierUserTwo:
#   module: homeArrivalNotifier
#   class: HomeArrivalNotifier
#   app_switch: input_boolean.home_arrival_notifier_user_two
#   input_boolean: input_boolean.user_two_home
#   notify_name: group_notifications
#   user_name: Sina
#   zone_name: Home
#   message: "Willkommen zu Hause {}."
#   #message: "Welcome Home {}."
#   dependencies: 
#     - Notifier

================================================
FILE: isHomeDeterminer/isHomeDeterminer.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import globals

#
# App to
#
# Args:
#   app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
#   input_booleans: list of input boolean which determine if a user is home
#   ishome: input boolean which determins if someone is home
#   message: message to use in notification
# Release Notes
#
# Version 1.3:
#   message now a list
#
# Version 1.2:
#   message now directly in own yaml instead of message module
#
# Version 1.1:
#   Added app_switch
#
# Version 1.0:
#   Initial Version


class IsHomeDeterminer(hass.Hass):
    def initialize(self):
        self.listen_state_handle_list = []

        self.app_switch = self.args["app_switch"]
        self.ishome = self.args["ishome"]
        self.input_booleans = self.args["input_booleans"].split(",")
        self.message = self.args["message"]

        if self.get_state(self.app_switch) == "on":
            for input_boolean in self.input_booleans:
                self.log(
                    "{} is {}".format(input_boolean, self.get_state(input_boolean))
                )
                self.listen_state_handle_list.append(
                    self.listen_state(self.state_change, input_boolean)
                )
                if (
                    self.get_state(input_boolean) == "on"
                    and self.get_state(self.ishome) == "off"
                ):
                    self.turn_on(self.ishome)
                    self.log("Setting {} to on".format(self.ishome))
                if (
                    self.get_state(input_boolean) == "off"
                    and self.get_state(self.ishome) == "on"
                ):
                    if self.are_others_away(input_boolean):
                        self.turn_off(self.ishome)
                        self.log("Setting {} to off".format(self.ishome))
                        notify_message = globals.random_arg(self.message)
                        self.log("notify_messsage: {}".format(notify_message))
                        self.call_service(
                            "notify/group_notifications", message=notify_message
                        )

    def state_change(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.app_switch) == "on":
            if new != "" and new != old:
                self.log("{} changed from {} to {}".format(entity, old, new))
                if new == "on":
                    self.turn_on(self.ishome)
                    self.log("Setting {} to on".format(self.ishome))
                if new == "off":
                    if self.are_others_away(entity):
                        self.turn_off(self.ishome)
                        self.log("Setting {} to off".format(self.ishome))
                        notify_message = globals.random_arg(self.message)
                        self.log("notify_messsage: {}".format(notify_message))
                        self.call_service(
                            "notify/group_notifications", message=notify_message
                        )

    def are_others_away(self, entity):
        self.log("Entity: {}".format(entity))
        for input_boolean in self.input_booleans:
            self.log("{} is {}".format(input_boolean, self.get_state(input_boolean)))
            if input_boolean == entity:
                pass
            elif self.get_state(input_boolean) == "on":
                self.log("{} is still at on".format(input_boolean))
                return False
        self.log("Everybody not home")
        return True

    def terminate(self):
        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)


================================================
FILE: isHomeDeterminer/isHomeDeterminer.yaml
================================================
# #Control the isHome state. Determines if someone is home or all persons are away
# isHomeDeterminer:
#   module: isHomeDeterminer
#   class: IsHomeDeterminer
#   app_switch: input_boolean.is_home_determiner
#   ishome: input_boolean.is_home
#   input_booleans: input_boolean.user_one_home,input_boolean.user_two_home
#   message:
#     - "Es ist keiner mehr zu Hause."
#     - "Keiner mehr da? Panda Party!"
#     - "Ich passe auf die Wohnung auf, einen schönen Tag"
#     - "Tschüss, bis nachher"
#   #message: "Everyone left home. Setting isHome to off"
#   global_dependencies:
#     - globals
#     - secrets

================================================
FILE: isUserHomeDeterminer/isUserHomeDeterminer.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime
from requests.exceptions import HTTPError

#
# App to toggle an input boolean when a person enters or leaves home.
# This is determined based on a combination of a GPS device tracker and the door sensor.
#
# - If the door sensor opens and the device_tracker changed to "home" in the last self.delay minutes
#   this means someone got home
# - If the door sensor opens and the device_tracker changes to "not_home" after that
#   this means someone left home
#
# Args:
#
# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot
# input_boolean: input_boolean which shows if someone is home e.g. input_boolean.isHome
# device_tracker: device tracker or person of the user to track e.g. device_tracker.simon
# door_sensor: Door sensor which indicated the front door opened e.g. binary_sensor.door_window_sensor_158d000126a57b
#
# Release Notes
#
# Version 1.5:
#   Wait to leave home until door is opened again
#
# Version 1.4.3:
#   check for listen_state_callback == None before triggering again
#
# Version 1.4.2:
#   cancel listen callback only when its not None
#
# Version 1.4.1:
#   fix for 503, fix for listen callback not being cancelled correctly
#
# Version 1.4:
#   message now directly in own yaml instead of message module
#
# Version 1.3:
#   Added app_switch
#
# Version 1.2:
#   Change checking after a delay to a event based system
#
# Version 1.1:
#   Set when initializing (also when HA restarts)
#
# Version 1.0:
#   Initial Version


class IsUserHomeDeterminer(hass.Hass):
    def initialize(self):
        self.listen_state_handle_list = []
        self.timer_handle_list = []

        self.delay = 600

        self.app_switch = self.args["app_switch"]
        self.input_boolean = self.args["input_boolean"]
        self.device_tracker = self.args["device_tracker"]
        self.door_sensor = self.args["door_sensor"]

        device_tracker_state = self.get_state(self.device_tracker, attribute="all")
        if self.get_state(self.app_switch) == "on":
            if device_tracker_state["state"] == "home":
                self.log("User is home")
                self.timer_handle_list.append(
                    self.run_in(
                        self.turn_on_callback, 0, turn_on_entity=self.input_boolean
                    )
                )
            else:
                self.log("User is not home")
                self.timer_handle_list.append(
                    self.run_in(
                        self.turn_off_callback, 0, turn_off_entity=self.input_boolean
                    )
                )

        self.listen_state_handle_list.append(
            self.listen_state(self.state_change, self.door_sensor)
        )

        self.listen_state_handle = None

    def state_change(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.app_switch) == "on":
            if new != "" and new != old:
                self.log("{} changed from {} to {}".format(entity, old, new))
                if new == "on" and old == "off":
                    self.cancel_listen_state_callback(None)
                    device_tracker_state = self.get_state(
                        self.device_tracker, attribute="all"
                    )
                    self.log("device_tracker_state: {}".format(device_tracker_state),)
                    last_changed = device_tracker_state["last_changed"]
                    self.log("last_changed: {}".format(last_changed))
                    # User got home: Device tracker changed to home before door sensor triggered
                    if device_tracker_state["state"] == "home" and (
                        (
                            datetime.datetime.now(datetime.timezone.utc)
                            - self.convert_utc(last_changed)
                        )
                        < datetime.timedelta(seconds=self.delay)
                    ):
                        self.log("User got home")
                        self.turn_on(self.input_boolean)
                    # User got home: Device tracker is still not home.
                    # Wait if it changes to home in the next self.delay seconds
                    elif device_tracker_state["state"] != "home":
                        self.log("Wait for device tracker to change to 'home'")
                        self.listen_state_handle = self.listen_state(
                            self.check_if_user_got_home, self.device_tracker
                        )
                        self.listen_state_handle_list.append(self.listen_state_handle)
                        self.timer_handle_list.append(
                            self.run_in(self.cancel_listen_state_callback, self.delay)
                        )
                    # User left home: Device tracker is still home.
                    # Wait if it changes to not_home
                    elif device_tracker_state["state"] == "home":
                        self.log("Wait for device tracker to change to 'not_home'")
                        self.listen_state_handle = self.listen_state(
                            self.check_if_user_left_home, self.device_tracker
                        )
                        self.listen_state_handle_list.append(self.listen_state_handle)

    def cancel_listen_state_callback(self, kwargs):
        if self.listen_state_handle is not None:
            self.log(
                "Timeout while waiting for user to get/leave home. Cancel listen_state"
            )
            if self.listen_state_handle in self.listen_state_handle_list:
                self.listen_state_handle_list.remove(self.listen_state_handle)
            self.cancel_listen_state(self.listen_state_handle)
            self.listen_state_handle = None

    def check_if_user_left_home(self, entity, attribute, old, new, kwargs):
        if new != "home":
            self.log("User left home")
            if self.listen_state_handle in self.listen_state_handle_list:
                self.listen_state_handle_list.remove(self.listen_state_handle)
            if self.listen_state_handle != None:
                self.cancel_listen_state(self.listen_state_handle)
                self.listen_state_handle = None
                self.timer_handle_list.append(
                    self.run_in(
                        self.turn_off_callback, 1, turn_off_entity=self.input_boolean
                    )
                )

    def check_if_user_got_home(self, entity, attribute, old, new, kwargs):
        if new == "home":
            self.log("User got home")
            if self.listen_state_handle in self.listen_state_handle_list:
                self.listen_state_handle_list.remove(self.listen_state_handle)
            if self.listen_state_handle is not None:
                self.cancel_listen_state(self.listen_state_handle)
                self.listen_state_handle = None
                self.timer_handle_list.append(
                    self.run_in(
                        self.turn_on_callback, 1, turn_on_entity=self.input_boolean
                    )
                )

    def turn_on_callback(self, kwargs):
        """This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting"""
        try:
            self.turn_on(kwargs["turn_on_entity"])
        except HTTPError as exception:
            self.log(
                "Error trying to turn on entity. Will try again in 1s. Error: {}".format(
                    exception
                ),
                level="WARNING",
            )
            self.timer_handle_list.append(
                self.run_in(
                    self.turn_on_callback, 1, turn_on_entity=kwargs["turn_on_entity"]
                )
            )

    def turn_off_callback(self, kwargs):
        """This is needed because the turn_off command can result in a HTTP 503 when homeassistant is restarting"""
        try:
            self.turn_off(kwargs["turn_off_entity"])
        except HTTPError as exception:
            self.log(
                "Error trying to turn off entity. Will try again in 1s. Error: {}".format(
                    exception
                ),
                level="WARNING",
            )
            self.timer_handle_list.append(
                self.run_in(
                    self.turn_off_callback, 1, turn_off_entity=kwargs["turn_off_entity"]
                )
            )

    def terminate(self):
        for listen_state_handle in self.listen_state_handle_list:
            self.cancel_listen_state(listen_state_handle)

        for timer_handle in self.timer_handle_list:
            self.cancel_timer(timer_handle)


================================================
FILE: isUserHomeDeterminer/isUserHomeDeterminer.yaml
================================================
# #Determine if user one gets/leaves home
# isUserHomeDeterminerUserOne:
#   module: isUserHomeDeterminer
#   class: IsUserHomeDeterminer
#   app_switch: input_boolean.is_user_home_determiner_user_one
#   input_boolean: input_boolean.user_one_home
#   device_tracker: person.kevin
#   door_sensor: binary_sensor.contact_door

# #Determine if user two gets/leaves home
# isUserHomeDeterminerUserTwo:
#   module: isUserHomeDeterminer
#   class: IsUserHomeDeterminer
#   app_switch: input_boolean.is_user_home_determiner_user_two
#   input_boolean: input_boolean.user_two_home
#   device_tracker: person.sina
#   door_sensor: binary_sensor.contact_door

================================================
FILE: leavingZoneNotifier/leavingZoneNotifier.py
================================================
import appdaemon.plugins.hass.hassapi as hass
import datetime

#
# App to notify if user_one is leaving a zone.
# User had to be in that zone 3 minutes before
# in order for the notification to be triggered
#
# Args:
#   app_switch: on/off switch for this app.
#               example: input_boolean.turn_fan_on_when_hot
#   device: Device to track
#   user_name: Name of the user used in the notification message
#   delay: seconds to wait before notifying. Maybe user returns to zone.
#          This should be too small to avoid false positives from your tracker.
#          I am using GPS Logger on Android and sometimes my device switches
#          from work to home and 2 minutes later back. example: 120
#   lingering_time: time a user has to be in a zone to trigger this app.
#                   example: 3600
#   zone: zone name from which the user is leaving
#   notify_name: Who to notify. example: group_notifications
#   message: localized message to use in notification
#   travel_time_sensor (optional): Sensor showing the travel time home.
#                                  example: sensor.travel_time_home_user_one
#   travel_time_sensor_message (optional): Additional notify message.
#
# Release Notes
#
# Version 1.11:
#   Catch new state might be None
#
# Version 1.10:
#   Catch old state might be None during startup
#
# Version 1.9:
#   PEP8 style and log message when updating travel_time_sensor
#
# Version 1.8:
#   Add travel time in notification message
#
# Version 1.7.1:
#   Fix delay in notify message. Only input the minutes not the tuple
#
# Version 1.7:
#   use Notify App
#
# Version 1.6:
#   notify message includes delay
#
# Version 1.5:
#   message now directly in own yaml instead of message module
#
# Version 1.4:
#   additional note for delay and better handling of zone_entered for
#   false positives
#
# Version 1.3:
#   delay and lingering_time now as args
#
# Version 1.2:
#   Added app_switch
#
# Version 1.1:
#   Rework without proximity
#
# Version 1.0:
#   Initial Version


class LeavingZoneNotifier(hass.Hass):
    def initialize(self):

        self.listen_state_handle_list = []
        self.timer_handle_list = []

        self.app_switch = self.args["app_switch"]
        self.user_name = self.args["user_name"]
        self.zone = self.args["zone"]
        self.notify_name = self.args["notify_name"]
        self.device = self.args["device"]
        # 'lingering_time' the time a user has to stay in a zone
        # for this app to trigger
        self.lingering_time = self.args["lingering_time"]
        self.delay = self.args["delay"]
        self.message = self.args["message"]
        self.travel_time_sensor = self.args.get("travel_time_sensor")
        self.travel_time_sensor_message = self.args.get("travel_time_sensor_message")

        self.user_entered_zone = None
        self.false_positive = False

        self.notifier = self.get_app("Notifier")

        self.listen_state_handle_list.append(
            self.listen_state(self.zone_state_change, self.device, attribute="all")
        )

    def zone_state_change(self, entity, attributes, old, new, kwargs):
        """Check if user entered or left a zone."""
        if self.get_state(self.app_switch) == "on":
            if new is not None:
                last_changed = self.convert_utc(new["last_changed"])
      
Download .txt
gitextract_92xa7u3p/

├── .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/
│   └── deconz_xiaomi_button.py
├── deconz_xiaomi_button.yaml
├── 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
├── 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
Download .txt
SYMBOL INDEX (276 symbols across 41 files)

FILE: ad-ench/ench.py
  function hl (line 51) | def hl(text: Union[int, float, str]) -> str:
  function hl_entity (line 55) | def hl_entity(entity: str) -> str:
  class EnCh (line 62) | class EnCh(hass.Hass):  # type: ignore
    method lg (line 65) | def lg(self, msg: str, *args: Any, icon: Optional[str] = None, repeat:...
    method initialize (line 70) | async def initialize(self) -> None:
    method check_battery (line 190) | async def check_battery(self, _: Any) -> None:
    method check_unavailable (line 249) | async def check_unavailable(self, _: Any) -> None:
    method check_stale (line 300) | async def check_stale(self, _: Any) -> None:
    method choose_notify_recipient (line 356) | def choose_notify_recipient(self, check: str, config: Dict[str, Any]) ...
    method last_update (line 360) | async def last_update(self, entity_id: str) -> Any:
    method _name (line 363) | async def _name(self, entity: str, friendly_name: bool = False, notifi...
    method _print_result (line 376) | def _print_result(self, check: str, entities: List[str], reason: str) ...
    method update_sensor (line 382) | async def update_sensor(self, check_name: str, entities: List[str]) ->...
    method show_info (line 404) | def show_info(self, config: Optional[Dict[str, Any]] = None) -> None:
    method print_collection (line 443) | def print_collection(self, key: str, collection: Iterable[Any], indent...
    method _print_cfg_setting (line 463) | def _print_cfg_setting(self, key: str, value: Union[int, str], indenta...

FILE: alarmClock/alarmClock.py
  class AlarmClock (line 43) | class AlarmClock(hass.Hass):
    method initialize (line 44) | def initialize(self):
    method alarm_change (line 83) | def alarm_change(self, entity, attributes, old, new, kwargs):
    method naturalwakeup_change (line 93) | def naturalwakeup_change(self, entity, attributes, old, new, kwargs):
    method add_timer (line 103) | def add_timer(self):
    method trigger_alarm (line 124) | def trigger_alarm(self, kwargs):
    method button_clicked (line 153) | def button_clicked(self, event_name, data, kwargs):
    method run_fade_in (line 177) | def run_fade_in(self, kwargs):
    method run_alarm (line 218) | def run_alarm(self, kwargs):
    method terminate (line 222) | def terminate(self):

FILE: alexa/alexa_api.py
  class alexa_api (line 6) | class alexa_api(hass.Hass):
    method initialize (line 7) | def initialize(self):
    method api_call (line 11) | def api_call(self, data):
    method my_alexa_interpret_data (line 123) | def my_alexa_interpret_data(self, data):
    method my_alexa_intent_name (line 184) | def my_alexa_intent_name(self, data):
    method my_alexa_dialog_state (line 197) | def my_alexa_dialog_state(self, data):
    method my_alexa_intent (line 206) | def my_alexa_intent(self, data):
    method my_alexa_request_type (line 215) | def my_alexa_request_type(self, data):
    method my_alexa_error (line 224) | def my_alexa_error(self, data):
    method my_alexa_slot_value (line 237) | def my_alexa_slot_value(self, data, slot):
    method my_alexa_response (line 252) | def my_alexa_response(
    method random_arg (line 280) | def random_arg(self, argName):
    method floatToStr (line 290) | def floatToStr(self, myfloat):
    method cleanup_text (line 298) | def cleanup_text(self, text):
    method alexalog (line 311) | def alexalog(self, logtext, repeat=0, surrounding=""):
    method alexaresponselog (line 333) | def alexaresponselog(self, logtext):
    method getIntentResponse (line 346) | def getIntentResponse(self):

FILE: alexa/lightState/lightStateIntent.py
  class lightStateIntent (line 5) | class lightStateIntent(hass.Hass):
    method initialize (line 6) | def initialize(self):
    method getIntentResponse (line 9) | def getIntentResponse(self, slots, devicename):
    method floatToStr (line 31) | def floatToStr(self, myfloat):

FILE: alexa/listService/listService.py
  class ListService (line 22) | class ListService(hass.Hass):
    method initialize (line 23) | def initialize(self):
    method getSwitchable (line 26) | def getSwitchable(self):
    method getTemperature (line 29) | def getTemperature(self):
    method getDoor (line 32) | def getDoor(self):
    method getWindow (line 35) | def getWindow(self):
    method getDoorTilted (line 38) | def getDoorTilted(self):

FILE: alexa/nextBus/nextBusIntent.py
  class nextBusIntent (line 5) | class nextBusIntent(hass.Hass):
    method initialize (line 6) | def initialize(self):
    method getIntentResponse (line 11) | def getIntentResponse(self, slots, devicename):

FILE: alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.py
  class RemindMeOfXWhenZoneIntent (line 8) | class RemindMeOfXWhenZoneIntent(hass.Hass):
    method initialize (line 9) | def initialize(self):
    method getIntentResponse (line 20) | def getIntentResponse(self, slots, devicename):
    method remind_callback (line 57) | def remind_callback(self, entity, attribute, old, new, kwargs):
    method terminate (line 75) | def terminate(self):

FILE: alexa/temperatureState/temperatureStateIntent.py
  class temperatureStateIntent (line 5) | class temperatureStateIntent(hass.Hass):
    method initialize (line 6) | def initialize(self):
    method getIntentResponse (line 9) | def getIntentResponse(self, slots, devicename):
    method floatToStr (line 31) | def floatToStr(self, myfloat):
    method random_arg (line 39) | def random_arg(self, argName):

FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent.py
  class TurnEntityOffInXIntent (line 7) | class TurnEntityOffInXIntent(hass.Hass):
    method initialize (line 8) | def initialize(self):
    method getIntentResponse (line 13) | def getIntentResponse(self, slots, devicename):
    method turn_off_callback (line 59) | def turn_off_callback(self, kwargs):
    method random_arg (line 64) | def random_arg(self, argName):
    method terminate (line 74) | def terminate(self):

FILE: alexa/windowsOpen/windowsOpenIntent.py
  class WindowsOpenIntent (line 7) | class WindowsOpenIntent(hass.Hass):
    method initialize (line 8) | def initialize(self):
    method getIntentResponse (line 12) | def getIntentResponse(self, slots, devicename):
    method random_arg (line 78) | def random_arg(self, argName):

FILE: alexaSpeakerConnector/alexaSpeakerConnector.py
  class AlexaSpeakerConnector (line 32) | class AlexaSpeakerConnector(hass.Hass):
    method initialize (line 33) | def initialize(self):
    method state_change (line 47) | def state_change(self, entity, attribute, old, new, kwargs):
    method run_in_callback (line 73) | def run_in_callback(self, kwargs):
    method terminate (line 89) | def terminate(self):

FILE: appWatcher/appWatcher.py
  class AppWatcher (line 17) | class AppWatcher(hass.Hass):
    method initialize (line 18) | def initialize(self):
    method log_message_callback (line 31) | def log_message_callback(self, app_name, ts, level, log_type, message,...
    method terminate (line 41) | def terminate(self):

FILE: buttonClicked/buttonClicked.py
  class ButtonClicked (line 25) | class ButtonClicked(hass.Hass):
    method initialize (line 26) | def initialize(self):
    method event_detected (line 40) | def event_detected(self, event_name, data, kwargs):
    method dimmer_callback (line 121) | def dimmer_callback(self, kwargs):
    method turn_off_workaround (line 140) | def turn_off_workaround(self, *kwargs):
    method terminate (line 143) | def terminate(self):

FILE: comingHome/comingHome.py
  class ComingHome (line 46) | class ComingHome(hass.Hass):
    method initialize (line 47) | def initialize(self):
    method state_change (line 64) | def state_change(self, entity, attribute, old, new, kwargs):
    method turn_on_actor (line 86) | def turn_on_actor(self, actor, entity, new):
    method my_call_service (line 91) | def my_call_service(self, service, service_data, entity, new):
    method terminate (line 97) | def terminate(self):

FILE: deconz_xiaomi_button/deconz_xiaomi_button.py
  class DeconzXiaomiButton (line 29) | class DeconzXiaomiButton(hass.Hass):
    method initialize (line 30) | def initialize(self):
    method event_detected (line 45) | def event_detected(self, event_name, data, kwargs):
    method dimmer_callback (line 87) | def dimmer_callback(self, kwargs):
    method terminate (line 106) | def terminate(self):

FILE: detectWrongState/detectWrongState.py
  class DetectWrongState (line 66) | class DetectWrongState(hass.Hass):
    method initialize (line 67) | def initialize(self):
    method state_change (line 95) | def state_change(self, entity, attribute, old, new, kwargs):
    method check_entities_should_be_off (line 105) | def check_entities_should_be_off(self):
    method check_entities_should_be_on (line 118) | def check_entities_should_be_on(self):
    method is_entity_reed_contact (line 129) | def is_entity_reed_contact(self, entity):
    method send_notification (line 139) | def send_notification(self, message, entity):
    method terminate (line 148) | def terminate(self):

FILE: eventMonitor/eventMonitor.py
  class Monitor (line 10) | class Monitor(hass.Hass):
    method initialize (line 11) | def initialize(self):
    method changed (line 26) | def changed(self, event_name, data, kwargs):
    method terminate (line 29) | def terminate(self):

FILE: faceRecognitionBot/faceRecognitionBot.py
  class FaceRecognitionBot (line 57) | class FaceRecognitionBot(hass.Hass):
    method initialize (line 58) | def initialize(self):
    method check_health_callback (line 162) | def check_health_callback(self, kwargs):
    method learn_faces_event_callback (line 183) | def learn_faces_event_callback(self, event_name, data, kwargs):
    method teach_name_by_file (line 188) | def teach_name_by_file(self, teach_url, name, file_path):
    method check_classifier_health (line 208) | def check_classifier_health(self):
    method check_if_trained (line 240) | def check_if_trained(self, kwargs):
    method teach_faces (line 257) | def teach_faces(self, folderpath, exclude_folders=[]):
    method teach_name_by_directory (line 271) | def teach_name_by_directory(self, name, folderpath):
    method button_clicked (line 282) | def button_clicked(self, event_name, data, kwargs):
    method triggered (line 290) | def triggered(self, entity, attribute, old, new, kwargs):
    method takeSnapshots (line 298) | def takeSnapshots(self, kwargs):
    method processImages (line 318) | def processImages(self, kwargs):
    method _processUnkownFaceFound (line 428) | def _processUnkownFaceFound(self, result_dict_dict):
    method _notifyUnkownFaceFound (line 451) | def _notifyUnkownFaceFound(self, result_dict_dict):
    method _getMaxCountFromResult (line 462) | def _getMaxCountFromResult(self, result_dict_dict):
    method _getFaceNamesFromResult (line 467) | def _getFaceNamesFromResult(self, result_dict_dict):
    method _getFileWithUnknownFaceFromResult (line 493) | def _getFileWithUnknownFaceFromResult(self, result_dict_dict):
    method _determineIfSameUnkownFace (line 502) | def _determineIfSameUnkownFace(self, result_dict_dict):
    method _moveFilesToUnknown (line 534) | def _moveFilesToUnknown(self, result_dict_dict):
    method _moveFilesFromUnkownToDirectoryByIdentifier (line 552) | def _moveFilesFromUnkownToDirectoryByIdentifier(self, directory, ident...
    method _getKnownFaces (line 563) | def _getKnownFaces(self):
    method list_folders (line 569) | def list_folders(self, directory):
    method post_image (line 582) | def post_image(self, url, image):
    method ask_for_name (line 600) | def ask_for_name(self, identifier):
    method receive_telegram_callback (line 615) | def receive_telegram_callback(self, event_name, data, kwargs):
    method receive_telegram_text (line 671) | def receive_telegram_text(self, event_name, data, kwargs):
    method terminate (line 706) | def terminate(self):

FILE: globals.py
  function random_arg (line 4) | def random_arg(argList):

FILE: heartbeat/heartbeat.py
  class Heartbeat (line 19) | class Heartbeat(hass.Hass):
    method initialize (line 20) | def initialize(self):
    method heartbeat (line 29) | def heartbeat(self, kwargs):
    method terminate (line 42) | def terminate(self):

FILE: homeArrivalNotifier/homeArrivalNotifier.py
  class HomeArrivalNotifier (line 34) | class HomeArrivalNotifier(hass.Hass):
    method initialize (line 35) | def initialize(self):
    method state_change (line 51) | def state_change(self, entity, attribute, old, new, kwargs):
    method terminate (line 63) | def terminate(self):

FILE: isHomeDeterminer/isHomeDeterminer.py
  class IsHomeDeterminer (line 27) | class IsHomeDeterminer(hass.Hass):
    method initialize (line 28) | def initialize(self):
    method state_change (line 63) | def state_change(self, entity, attribute, old, new, kwargs):
    method are_others_away (line 80) | def are_others_away(self, entity):
    method terminate (line 92) | def terminate(self):

FILE: isUserHomeDeterminer/isUserHomeDeterminer.py
  class IsUserHomeDeterminer (line 51) | class IsUserHomeDeterminer(hass.Hass):
    method initialize (line 52) | def initialize(self):
    method state_change (line 86) | def state_change(self, entity, attribute, old, new, kwargs):
    method cancel_listen_state_callback (line 128) | def cancel_listen_state_callback(self, kwargs):
    method check_if_user_left_home (line 138) | def check_if_user_left_home(self, entity, attribute, old, new, kwargs):
    method check_if_user_got_home (line 152) | def check_if_user_got_home(self, entity, attribute, old, new, kwargs):
    method turn_on_callback (line 166) | def turn_on_callback(self, kwargs):
    method turn_off_callback (line 183) | def turn_off_callback(self, kwargs):
    method terminate (line 200) | def terminate(self):

FILE: leavingZoneNotifier/leavingZoneNotifier.py
  class LeavingZoneNotifier (line 70) | class LeavingZoneNotifier(hass.Hass):
    method initialize (line 71) | def initialize(self):
    method zone_state_change (line 98) | def zone_state_change(self, entity, attributes, old, new, kwargs):
    method notify_user (line 136) | def notify_user(self, kwargs):
    method notify_user_callback (line 161) | def notify_user_callback(self, kwargs):
    method terminate (line 173) | def terminate(self):

FILE: motionTrigger/motionTrigger.py
  class MotionTrigger (line 67) | class MotionTrigger(hass.Hass):
    method initialize (line 68) | def initialize(self):
    method delay_changed (line 138) | def delay_changed(self, entity, attribute, old, new, kwargs):
    method motion_event_detected (line 142) | def motion_event_detected(self, event_name, data, kwargs):
    method state_changed (line 147) | def state_changed(self, entity, attribute, old, new, kwargs):
    method turn_on_callback (line 152) | def turn_on_callback(self, kwargs):
    method turn_off_callback (line 188) | def turn_off_callback(self, kwargs):
    method reset_timer (line 214) | def reset_timer(self):
    method terminate (line 223) | def terminate(self):

FILE: newWifiDeviceNotify/newWifiDeviceNotify.py
  class DeviceNotify (line 52) | class DeviceNotify(hass.Hass):
    method initialize (line 53) | def initialize(self):
    method entityRegistryUpdatedCallback (line 87) | def entityRegistryUpdatedCallback(self, event_name, data, kwargs):
    method handleNewRegistryEntity (line 97) | def handleNewRegistryEntity(self, kwargs):
    method newDeviceCallback (line 113) | def newDeviceCallback(self, event_name, data, kwargs):
    method notifyNewDeviceAdded (line 121) | def notifyNewDeviceAdded(self, host_name, mac):
    method askForProfileChange (line 126) | def askForProfileChange(self, host_name):
    method receiveTelegramCallback (line 148) | def receiveTelegramCallback(self, event_name, data, kwargs):
    method allowDevice (line 198) | def allowDevice(self, host_name):
    method terminate (line 235) | def terminate(self):

FILE: nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.py
  class NextAppointmentLeaveNotifier (line 43) | class NextAppointmentLeaveNotifier(hass.Hass):
    method initialize (line 44) | def initialize(self):
    method state_change (line 70) | def state_change(self, entity, attributes, old, new, kwargs):
    method set_timer_handle (line 79) | def set_timer_handle(self):
    method notify_user (line 99) | def notify_user(self, *kwargs):
    method terminate (line 127) | def terminate(self):

FILE: notifier/notifier.py
  class Notifier (line 45) | class Notifier(hass.Hass):
    method initialize (line 46) | def initialize(self):
    method notify (line 55) | def notify(self, notify_name, message, useAlexa=True, useTelegram=True):
    method notify_callback (line 72) | def notify_callback(self, kwargs):
    method getAlexaDeviceForUserLocation (line 81) | def getAlexaDeviceForUserLocation(self, notify_name):
    method terminate (line 96) | def terminate(self):

FILE: notifyOfActionWhenAway/notifyOfActionWhenAway.py
  class NotifyOfActionWhenAway (line 31) | class NotifyOfActionWhenAway(hass.Hass):
    method initialize (line 32) | def initialize(self):
    method state_change (line 50) | def state_change(self, entity, attribute, old, new, kwargs):
    method notify_if_no_one_home (line 74) | def notify_if_no_one_home(self, kwargs):
    method terminate (line 89) | def terminate(self):

FILE: plantWateringNotifier/plantWateringNotifier.py
  class PlantWateringNotifier (line 45) | class PlantWateringNotifier(hass.Hass):
    method initialize (line 46) | def initialize(self):
    method run_morning_callback (line 85) | def run_morning_callback(self, kwargs):
    method run_evening_callback (line 125) | def run_evening_callback(self, kwargs):
    method receive_telegram_callback (line 134) | def receive_telegram_callback(self, event_name, data, kwargs):
    method terminate (line 165) | def terminate(self):

FILE: pollenNotifier/pollenNotifier.py
  class PollenNotifier (line 37) | class PollenNotifier(hass.Hass):
    method initialize (line 38) | def initialize(self):
    method run_daily_callback (line 83) | def run_daily_callback(self, kwargs):
    method terminate (line 115) | def terminate(self):

FILE: powerUsageNotification/powerUsageNotification.py
  class PowerUsageNotification (line 44) | class PowerUsageNotification(hass.Hass):
    method initialize (line 45) | def initialize(self):
    method state_change (line 87) | def state_change(self, entity, attribute, old, new, kwargs):
    method notify_device_off (line 135) | def notify_device_off(self, kwargs):
    method terminate (line 152) | def terminate(self):

FILE: reminder/reminder.py
  class Reminder (line 27) | class Reminder(hass.Hass):
    method initialize (line 28) | def initialize(self):
    method run_morning_callback (line 62) | def run_morning_callback(self, kwargs):
    method run_evening_callback (line 77) | def run_evening_callback(self, kwargs):
    method receive_telegram_callback (line 86) | def receive_telegram_callback(self, event_name, data, kwargs):
    method terminate (line 115) | def terminate(self):

FILE: seqSink/seqSink.py
  class SeqSink (line 21) | class SeqSink(hass.Hass):
    method initialize (line 22) | def initialize(self):
    method log_message_callback (line 37) | def log_message_callback(self, app_name, ts, level, log_type, message,...
    method terminate (line 73) | def terminate(self):

FILE: setThermostat/setThermostat.py
  class SetThermostat (line 45) | class SetThermostat(hass.Hass):
    method initialize (line 46) | def initialize(self):
    method schedule_trigger (line 75) | def schedule_trigger(self, entity, attribute, old, new, kwargs):
    method trigger_thermostat (line 97) | def trigger_thermostat(self, kwargs):
    method reset_thermostat (line 134) | def reset_thermostat(self, kwargs):
    method terminate (line 157) | def terminate(self):

FILE: setThermostatOnStateChange/setThermostatOnStateChange.py
  class SetThermostatOnStateChange (line 29) | class SetThermostatOnStateChange(hass.Hass):
    method initialize (line 30) | def initialize(self):
    method state_change (line 52) | def state_change(self, entity, attribute, old, new, kwargs):
    method terminate (line 78) | def terminate(self):

FILE: sleepModeHandler/sleepModeHandler.py
  class SleepModeHandler (line 24) | class SleepModeHandler(hass.Hass):
    method initialize (line 25) | def initialize(self):
    method state_change (line 47) | def state_change(self, entity, attribute, old, new, kwargs):
    method are_all_that_are_home_sleeping (line 71) | def are_all_that_are_home_sleeping(self):
    method are_all_that_are_home_awake (line 78) | def are_all_that_are_home_awake(self):
    method terminate (line 85) | def terminate(self):

FILE: sleepModeHandler/userSleepModeHandler.py
  class UserSleepModeHandler (line 43) | class UserSleepModeHandler(hass.Hass):
    method initialize (line 44) | def initialize(self):
    method home_state_change (line 67) | def home_state_change(self, entity, attribute, old, new, kwargs):
    method state_change (line 76) | def state_change(self, entity, attribute, old, new, kwargs):
    method awake (line 103) | def awake(self, kwargs):
    method asleep (line 119) | def asleep(self, kwargs):
    method insert_room_state_change (line 135) | def insert_room_state_change(self, entity, attribute, old, new, kwargs):
    method calculate_room_presence (line 139) | def calculate_room_presence(self, kwargs):
    method terminate (line 148) | def terminate(self):

FILE: travelTimeNotifier/travelTimeNotifier.py
  class TravelTimeNotifier (line 44) | class TravelTimeNotifier(hass.Hass):
    method initialize (line 45) | def initialize(self):
    method state_change (line 69) | def state_change(self, entity, attributes, old, new, kwargs) -> None:
    method notify_user (line 89) | def notify_user(self, address: str) -> None:
    method parse_duration_in_traffic_minutes (line 95) | def parse_duration_in_traffic_minutes(self, state) -> Optional[int]:
    method parse_destination_address (line 113) | def parse_destination_address(self, state) -> Optional[str]:
    method parse_duration_minutes (line 128) | def parse_duration_minutes(self, state) -> Optional[int]:
    method terminate (line 141) | def terminate(self) -> None:

FILE: turnFanOnWhenHot/turnFanOnWhenHot.py
  class TurnFanOnWhenHot (line 31) | class TurnFanOnWhenHot(hass.Hass):
    method initialize (line 32) | def initialize(self):
    method state_change (line 56) | def state_change(self, entity, attribute, old, new, kwargs):
    method turn_off_callback (line 94) | def turn_off_callback(self, kwargs):
    method terminate (line 101) | def terminate(self):

FILE: turnOffBarAfterRestart/turnOffBarAfterRestart.py
  class TurnOffBarAfterRestart (line 18) | class TurnOffBarAfterRestart(hass.Hass):
    method initialize (line 19) | def initialize(self):
    method turn_off_callback (line 29) | def turn_off_callback(self, kwargs):
    method turn_green_callback (line 43) | def turn_green_callback(self, kwargs):
    method terminate (line 63) | def terminate(self):
Condensed preview — 100 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (325K chars).
[
  {
    "path": ".gitignore",
    "chars": 1285,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2018 Kevin Eifinger\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 22925,
    "preview": "<h1 align=\"center\">\n  <a name=\"logo\" href=\"\"><img src=\"images/logo-round-192x192.png\" alt=\"Home Assistant Logo\" width=\"1"
  },
  {
    "path": "ad-ench/ench.py",
    "chars": 18058,
    "preview": "\"\"\"EnCh.\n   Entity Checker\n\n  @benleb / https://github.com/benleb/ad-ench\n\"\"\"\n\n__version__ = \"0.9.0\"\n\nfrom datetime impo"
  },
  {
    "path": "alarmClock/alarmClock.py",
    "chars": 9320,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nimport math\n\n\n#\n# Alarm Clock App\n#\n#\n# Args:\n#  alarm_tim"
  },
  {
    "path": "alarmClock/alarmClock.yaml",
    "chars": 650,
    "preview": "# Alarm Clock App\n# alarmClock:\n#   module: alarmClock\n#   class: AlarmClock\n#   alarm_time: sensor.alarm_time\n#   wakem"
  },
  {
    "path": "alexa/README.md",
    "chars": 3428,
    "preview": "# Alexa Intents\n\nIntents for [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App) from [Rene Tode](htt"
  },
  {
    "path": "alexa/alexa.yaml",
    "chars": 1898,
    "preview": "alexa_api: # appdaemon skill\n  module: alexa_api\n  class: alexa_api\n  cardTitle: Your Card Title\n  devices:\n    unknownD"
  },
  {
    "path": "alexa/alexa_api.py",
    "chars": 14154,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport random\nimport datetime\n\n\nclass alexa_api(hass.Hass):\n    def initia"
  },
  {
    "path": "alexa/custom_skill.json",
    "chars": 9714,
    "preview": "{\n    \"interactionModel\": {\n        \"languageModel\": {\n            \"invocationName\": \"home assistant\",\n            \"inte"
  },
  {
    "path": "alexa/lightState/lightStateIntent-utterances_DE.csv",
    "chars": 65,
    "preview": "Ist {device} aus\nIst {device} an\nWas ist der Status von {device}"
  },
  {
    "path": "alexa/lightState/lightStateIntent-utterances_EN.csv",
    "chars": 61,
    "preview": "Is {device} off\nIs {device} on\nWhat is the state of {device}\n"
  },
  {
    "path": "alexa/lightState/lightStateIntent.py",
    "chars": 1323,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport random\n\n\nclass lightStateIntent(hass.Hass):\n    def initialize(self"
  },
  {
    "path": "alexa/lightState/lightStateIntent.yaml",
    "chars": 509,
    "preview": "lightStateIntent:\n  module: lightStateIntent\n  class: lightStateIntent\n  language: DE\n  temperatureUnit: \"Grad\"\n  textLi"
  },
  {
    "path": "alexa/listService/listService.py",
    "chars": 862,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# Provide the list of HA entities for Alexa Apps\n#\n#\n# Args:\n#\n# switch"
  },
  {
    "path": "alexa/listService/listService.yaml",
    "chars": 2548,
    "preview": "listService:\n  module: listService\n  class: ListService\n  switchable:\n    große lampe: switch.large_lamp\n    kleine lamp"
  },
  {
    "path": "alexa/nextBus/nextBusIntent.py",
    "chars": 740,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport random\n\n\nclass nextBusIntent(hass.Hass):\n    def initialize(self):\n"
  },
  {
    "path": "alexa/nextBus/nextBusIntent.yaml",
    "chars": 247,
    "preview": "nextBusIntent:\n  module: nextBusIntent\n  class: nextBusIntent\n  textLine: \"Linie {} fährt in {} Minuten\"\n  #textLine: \"L"
  },
  {
    "path": "alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.py",
    "chars": 3185,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n__ZONE_ACTION_ENTER__ = \"kommen\"\n__ZONE_ACTION_LEAVE__ = "
  },
  {
    "path": "alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.yaml",
    "chars": 479,
    "preview": "remindMeOfXWhenZoneIntent:\n  module: remindMeOfXWhenZoneIntent\n  class: RemindMeOfXWhenZoneIntent\n  device_tracker: pers"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent-utterances_DE.csv",
    "chars": 72,
    "preview": "wie viel grad ist es in {location}\nwas ist die temperatur in {location}"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent-utterances_EN.csv",
    "chars": 73,
    "preview": "how many degree is it in {location}\nwhat is the temperture in {location}\n"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent.py",
    "chars": 1618,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport random\n\n\nclass temperatureStateIntent(hass.Hass):\n    def initializ"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent.yaml",
    "chars": 416,
    "preview": "temperatureStateIntent:\n  module: temperatureStateIntent\n  class: temperatureStateIntent\n  language: DE\n  temperatureUni"
  },
  {
    "path": "alexa/turnEntityOffInX/requirements.txt",
    "chars": 7,
    "preview": "isodate"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_DE.csv",
    "chars": 191,
    "preview": "In {duration} {device} ausschalten\n{device} in {duration} ausschalten\ner soll {device} in {duration} ausschalten\nes sol"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_EN.csv",
    "chars": 106,
    "preview": "In {duration} turn off {device}\nturn off {device} in {duration}\nit should turn off {device} in {duration}\n"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent.py",
    "chars": 2871,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport random\nimport isodate\nimport datetime\n\n\nclass TurnEntityOffInXInten"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent.yaml",
    "chars": 319,
    "preview": "turnEntityOffInXIntent:\n  module: turnEntityOffInXIntent\n  class: TurnEntityOffInXIntent\n  language: DE\n  textLine: \"Oka"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent-utterances_DE.csv",
    "chars": 281,
    "preview": "ob alles zu ist\nob noch etwas offen ist\nist noch etwas offen\nist alles zu\nsind alle türen zu\nob noch türen offen sind\no"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent-utterances_EN.csv",
    "chars": 350,
    "preview": "whether everything is closed\nwhether something is still open\nis something still open\nis everthing closed\nare all doors c"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent.py",
    "chars": 3720,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport random\nimport isodate\nimport datetime\n\n\nclass WindowsOpenIntent(has"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent.yaml",
    "chars": 671,
    "preview": "windowsOpenIntent:\n  module: windowsOpenIntent\n  class: WindowsOpenIntent\n  language: DE\n  textLineClosed: \"Alle Fenster"
  },
  {
    "path": "alexaSpeakerConnector/alexaSpeakerConnector.py",
    "chars": 3344,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App to Turn on Receiver Bluetooth when Alexa is playing something so "
  },
  {
    "path": "alexaSpeakerConnector/alexaSpeakerConnector.yaml",
    "chars": 413,
    "preview": "#App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers\n# alexaSpeakerConnect"
  },
  {
    "path": "appWatcher/appWatcher.py",
    "chars": 1224,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which listens on the log for App crashes and notifies via telegra"
  },
  {
    "path": "appWatcher/appWatcher.yaml",
    "chars": 197,
    "preview": "appWatcher:\n  module: appWatcher\n  class: AppWatcher\n  notify_name: kevin\n  notify_message: \"AppDaemon error: {}\"\n  #not"
  },
  {
    "path": "apps.yaml",
    "chars": 169,
    "preview": "#################################################################\n## Global\n############################################"
  },
  {
    "path": "buttonClicked/buttonClicked.py",
    "chars": 6060,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which toggles entities for single/double presses "
  },
  {
    "path": "buttonClicked/buttonClicked.yaml",
    "chars": 897,
    "preview": "# App which toggles entities for single/double presses of xiaomi buttons\n# xiaomiroundButtonBedroomClicked:\n#   module: "
  },
  {
    "path": "comingHome/comingHome.py",
    "chars": 3482,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App to Turn on Lobby Lamp when Door openes and no one"
  },
  {
    "path": "comingHome/comingHome.yaml",
    "chars": 643,
    "preview": "#Switch on Lobby lamp when the first person is coming home and the sun is down\n# comingHomeYeelight:\n#   module: comingH"
  },
  {
    "path": "deconz_xiaomi_button/deconz_xiaomi_button.py",
    "chars": 3928,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which toggles entities for single/double/hold pre"
  },
  {
    "path": "deconz_xiaomi_button.yaml",
    "chars": 788,
    "preview": "DeconzXiaomiButtonBedroom:\n  module: deconz_xiaomi_button\n  class: DeconzXiaomiButton\n  id: round_button_schlafzimmer\n  "
  },
  {
    "path": "detectWrongState/detectWrongState.py",
    "chars": 5281,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which notifies of wrong states based on a state change\n#\n# Args:\n"
  },
  {
    "path": "detectWrongState/detectWrongState.yaml",
    "chars": 3989,
    "preview": "# detectWrongStateWhenLeaving:\n#   module: detectWrongState\n#   class: DetectWrongState\n#   app_switch: input_boolean.de"
  },
  {
    "path": "ench.yaml",
    "chars": 1351,
    "preview": "---\nench:\n  module: ench\n  class: EnCh\n  notify: \"notify.kevin\"\n  exclude:\n    - device_tracker.venue_8*\n    - person.ke"
  },
  {
    "path": "eventMonitor/eventMonitor.py",
    "chars": 1074,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n\"\"\"\nMonitor events and output changes to the verbose_log. Nice for debugg"
  },
  {
    "path": "eventMonitor/eventMonitor.yaml",
    "chars": 68,
    "preview": "#eventMonitor:\n#  module: eventMonitor\n#  class: Monitor\n#  events: "
  },
  {
    "path": "faceRecognitionBot/faceRecognitionBot.py",
    "chars": 31702,
    "preview": "import json\nfrom json import JSONDecodeError\n\nimport appdaemon.plugins.hass.hassapi as hass  # pylint: disable=import-er"
  },
  {
    "path": "faceRecognitionBot/faceRecognitionBot.yaml",
    "chars": 1986,
    "preview": "# faceRecognitionBot:\n#   module: faceRecognitionBot\n#   class: FaceRecognitionBot\n#   app_switch: input_boolean.facebox"
  },
  {
    "path": "globals.py",
    "chars": 297,
    "preview": "import random\n\n\ndef random_arg(argList):\n    ############################################\n    # pick a random text from "
  },
  {
    "path": "heartbeat/heartbeat.py",
    "chars": 1208,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nfrom requests.exceptions import HTTPError\n\n#\n# App which sets a homeassist"
  },
  {
    "path": "heartbeat/heartbeat.yaml",
    "chars": 86,
    "preview": "heartbeat:\n  module: heartbeat\n  class: Heartbeat\n  sensor: sensor.appdaemon_heartbeat"
  },
  {
    "path": "homeArrivalNotifier/homeArrivalNotifier.py",
    "chars": 2022,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App to send a notification if someone arrives at home\n#\n# Args:\n#  ap"
  },
  {
    "path": "homeArrivalNotifier/homeArrivalNotifier.yaml",
    "chars": 859,
    "preview": "#Notification if user one arrives at home\n# homeArrivalNotifierUserOne:\n#   module: homeArrivalNotifier\n#   class: HomeA"
  },
  {
    "path": "isHomeDeterminer/isHomeDeterminer.py",
    "chars": 3718,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport globals\n\n#\n# App to\n#\n# Args:\n#   app_switch: on/off switch for thi"
  },
  {
    "path": "isHomeDeterminer/isHomeDeterminer.yaml",
    "chars": 614,
    "preview": "# #Control the isHome state. Determines if someone is home or all persons are away\n# isHomeDeterminer:\n#   module: isHom"
  },
  {
    "path": "isUserHomeDeterminer/isUserHomeDeterminer.py",
    "chars": 8708,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nfrom requests.exceptions import HTTPError\n\n#\n# App to togg"
  },
  {
    "path": "isUserHomeDeterminer/isUserHomeDeterminer.yaml",
    "chars": 649,
    "preview": "# #Determine if user one gets/leaves home\n# isUserHomeDeterminerUserOne:\n#   module: isUserHomeDeterminer\n#   class: IsU"
  },
  {
    "path": "leavingZoneNotifier/leavingZoneNotifier.py",
    "chars": 6734,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App to notify if user_one is leaving a zone.\n# User h"
  },
  {
    "path": "leavingZoneNotifier/leavingZoneNotifier.yaml",
    "chars": 1868,
    "preview": "# leavingWorkNotifierUserOne:\n#   module: leavingZoneNotifier\n#   class: LeavingZoneNotifier\n#   app_switch: input_boole"
  },
  {
    "path": "motionTrigger/motionTrigger.py",
    "chars": 9656,
    "preview": "import appdaemon.plugins.hass.hassapi as hass  # pylint: disable=import-error\nimport datetime\n\n#\n# Special version of Mo"
  },
  {
    "path": "motionTrigger/motionTrigger.yaml",
    "chars": 594,
    "preview": "# bedroomMotionTrigger:\n#   module: motionTrigger\n#   class: MotionTrigger\n#   app_switch: input_boolean.bedroom_motion_"
  },
  {
    "path": "newWifiDeviceNotify/newWifiDeviceNotify.py",
    "chars": 9536,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nfrom fritz_switch_profiles import FritzProfileSwitch\n\n#\n# App which sends "
  },
  {
    "path": "newWifiDeviceNotify/newWifiDeviceNotify.yaml",
    "chars": 981,
    "preview": "newWifiDeviceNotify:\n  module: newWifiDeviceNotify\n  class: DeviceNotify\n  notify_name: group_notifications\n  user_id: !"
  },
  {
    "path": "newWifiDeviceNotify/requirements.txt",
    "chars": 30,
    "preview": "fritz_switch_profiles >= 1.0.0"
  },
  {
    "path": "nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.py",
    "chars": 4953,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which notifies the user to start to the next appo"
  },
  {
    "path": "nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.yaml",
    "chars": 1479,
    "preview": "# nextAppointmentLeaveNotifier:\n#   module: nextAppointmentLeaveNotifier\n#   class: NextAppointmentLeaveNotifier\n#   sen"
  },
  {
    "path": "notifier/notifier.py",
    "chars": 3408,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# Centralizes messaging. Among other things, it will de"
  },
  {
    "path": "notifier/notifier.yaml",
    "chars": 646,
    "preview": "Notifier:\n  module: notifier\n  class: Notifier\n  app_switch_alexa: input_boolean.notifier_alexa\n  alexa_tts: alexa_media"
  },
  {
    "path": "notifyOfActionWhenAway/notifyOfActionWhenAway.py",
    "chars": 3081,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App to send notification when a sensor changes state\n#\n# Args:\n#\n#  a"
  },
  {
    "path": "notifyOfActionWhenAway/notifyOfActionWhenAway.yaml",
    "chars": 1080,
    "preview": "notifyOfActionWhenAway:\n  module: notifyOfActionWhenAway\n  class: NotifyOfActionWhenAway\n  app_switch: input_boolean.not"
  },
  {
    "path": "plantWateringNotifier/plantWateringNotifier.py",
    "chars": 7041,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which reminds you daily to water your plants if i"
  },
  {
    "path": "plantWateringNotifier/plantWateringNotifier.yaml",
    "chars": 1262,
    "preview": "# plantWateringNotifier:\n#   module: plantWateringNotifier\n#   class: PlantWateringNotifier\n#   app_switch: input_boolea"
  },
  {
    "path": "pollenNotifier/pollenNotifier.py",
    "chars": 4415,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which notifies you when there is a pollen forecas"
  },
  {
    "path": "pollenNotifier/pollenNotifier.yaml",
    "chars": 1452,
    "preview": "roggenNotifier:\n  module: pollenNotifier\n  class: PollenNotifier\n  app_switch: input_boolean.roggen_notifier\n  pollen_se"
  },
  {
    "path": "powerUsageNotification/powerUsageNotification.py",
    "chars": 6189,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which notifies you when a power usage sensor indicated a device i"
  },
  {
    "path": "powerUsageNotification/powerUsageNotification.yaml",
    "chars": 1089,
    "preview": "powerUsageNotification_Dishwasher:\n  module: powerUsageNotification\n  class: PowerUsageNotification\n  app_switch: input_"
  },
  {
    "path": "reminder/reminder.py",
    "chars": 4671,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nimport uuid\n\n#\n# App which reminds you daily and again in "
  },
  {
    "path": "reminder/reminder.yaml",
    "chars": 715,
    "preview": "# reminderPlantwateringNew:\n#   module: reminder\n#   class: Reminder\n#   app_switch: input_boolean.plant_watering_remind"
  },
  {
    "path": "requirements.txt",
    "chars": 5,
    "preview": "wheel"
  },
  {
    "path": "seqSink/requirements.txt",
    "chars": 16,
    "preview": "requests==2.24.0"
  },
  {
    "path": "seqSink/seqSink.py",
    "chars": 2316,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport socket\nimport os\nimport json\nimport requests\n\n#\n# App which forward"
  },
  {
    "path": "seqSink/seqSink.yaml",
    "chars": 117,
    "preview": "# seqSink:\n#   module: seqSink\n#   class: SeqSink\n#   server_url: \"http://seq:5341/\"\n#   api_key: !secret seq_api_key"
  },
  {
    "path": "setThermostat/setThermostat.py",
    "chars": 6027,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which sets a thermostat to a target temperature b"
  },
  {
    "path": "setThermostat/setThermostat.yaml",
    "chars": 1130,
    "preview": "# warm_bath_before_wakeup:\n#   module: setThermostat\n#   class: SetThermostat\n#   app_switch: input_boolean.warm_bath_be"
  },
  {
    "path": "setThermostatOnStateChange/setThermostatOnStateChange.py",
    "chars": 3177,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which sets a thermostat to a target temperature on state change\n#"
  },
  {
    "path": "setThermostatOnStateChange/setThermostatOnStateChange.yaml",
    "chars": 8207,
    "preview": "# setWohnzimmerThermostatWhenLeaving:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   a"
  },
  {
    "path": "sleepModeHandler/sleepModeHandler.py",
    "chars": 3087,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\n\n\n#\n# App which sets the sleep mode on/off\n#\n# Args:\n#   app_switch: on/of"
  },
  {
    "path": "sleepModeHandler/sleepModeHandler.yaml",
    "chars": 636,
    "preview": "# sleepModeHandler:\n#   module: sleepModeHandler\n#   class: SleepModeHandler\n#   app_switch: input_boolean.sleep_mode_ha"
  },
  {
    "path": "sleepModeHandler/userSleepModeHandler.py",
    "chars": 5658,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nfrom queue import Queue\n\n#\n# App which sets the sleep mode on/off\n#\n# Args"
  },
  {
    "path": "sleepModeHandler/userSleepModeHandler.yaml",
    "chars": 800,
    "preview": "# userSleepModeHandlerUserOne:\n#   module: userSleepModeHandler\n#   class: UserSleepModeHandler\n#   app_switch: input_bo"
  },
  {
    "path": "travelTimeNotifier/travelTimeNotifier.py",
    "chars": 5567,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nfrom typing import Optional\n\n#\n# App which notifies the us"
  },
  {
    "path": "travelTimeNotifier/travelTimeNotifier.yaml",
    "chars": 2275,
    "preview": "# travelTime_home_from_work:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_ho"
  },
  {
    "path": "turnFanOnWhenHot/turnFanOnWhenHot.py",
    "chars": 4465,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App to Turn on fan when temp is above a threshold and"
  },
  {
    "path": "turnFanOnWhenHot/turnFanOnWhenHot.yaml",
    "chars": 814,
    "preview": "# turnLargeFanOnWhenHot:\n#   module: turnFanOnWhenHot\n#   class: TurnFanOnWhenHot\n#   app_switch: input_boolean.turn_lar"
  },
  {
    "path": "turnOffBarAfterRestart/turnOffBarAfterRestart.py",
    "chars": 2375,
    "preview": "import appdaemon.plugins.hass.hassapi as hass\nfrom requests.exceptions import HTTPError\n\n#\n# Will turn the bar table gre"
  },
  {
    "path": "turnOffBarAfterRestart/turnOffBarAfterRestart.yaml",
    "chars": 121,
    "preview": "# turnOffBarAfterRestart:\n#   module: turnOffBarAfterRestart\n#   class: TurnOffBarAfterRestart\n#   light: light.bar_tabl"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the eifinger/appdaemon-scripts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 100 files (298.5 KB), approximately 69.5k tokens, and a symbol index with 276 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!