[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n#appdaemon\nsecrets.py\n\n#VS Code\n.vscode\n\n#PyCharm\n.idea\n\n#AppDaemon\nsecrets.yaml\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Kevin Eifinger\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <a name=\"logo\" href=\"\"><img src=\"images/logo-round-192x192.png\" alt=\"Home Assistant Logo\" width=\"192\"></a>\n  <br>\n  eifinger's Appdaemon Scripts\n</h1>\n\n## About\n\nThis is the repository containing all my Appdaemon apps.\n\nUsed together with my Homeassistant config which you can find here:\n\n[https://github.com/eifinger/homeassistant-config](https://github.com/eifinger/homeassistant-config)\n\nI use Appdaemon for all my automations since I am a programmer myself and it provides me with all the possibilities\nof the Python world and a far better debugging experience than\n[HA Automations](https://www.home-assistant.io/getting-started/automation/) or [nodered](https://nodered.org/).\n\n### No longer actively maintained\n\nI no longer use appdaemon for my automations and switched over to automations completely:\n\n* Traces allow easy debugging of what happened and why\n* I can see related entities from automations and vise versa\n* I can quickly adjust automations via the UI editor on my smartphone\n* There is no disconnect/lag between Appdaemon and Homeassistant.\n\n## How to contribute\n\nJust open an Issue or a Pull Request for any Comments, Questions, etc.\n\n**Or you can message me on twitter :** [@eifinger](https://twitter.com/eifinger)\n\n## How to use\n\nIf you have never used Appdaemon before I suggest you start with the\n[tutorial](https://appdaemon.readthedocs.io/en/latest/TUTORIAL.html) and the\n[guide](https://appdaemon.readthedocs.io/en/latest/APPGUIDE.html).\nBoth contain more links to great tutorials and examples.\n\nI tried to write each App in this repository with reusability in mind.\nThis means that every app in here has a short documentation\nand is (if possible) written to be easily adjusted to your environment and your needs.\n\n### app_switch\n\nEvery App has an input_boolean inside HA which turns it on/off.\nThis is useful if I don't want any notifications right now or an App is misbehaving.\n\n## App list\n\n* [Alexa Intents](#alexaintents)\n* [AlexaSpeakerConnector](#alexaspeakerconnector)\n* [appWatcher](#appWatcher)\n* [alarmClock](#alarmclock)\n* [buttonClicked](#buttonclicked)\n* [comingHome](#cominghome)\n* [deconzXiaomiButton](#deconzxiaomibutton)\n* [detectWrongState](#detectwrongstate)\n* [eventMonitor](#eventmonitor)\n* [faceRecognitionBot](#facerecognitionbot)\n* [google_travel_time](#google_travel_time)\n* [heartbeat](#heartbeat)\n* [homeArrivalNotifier](#homearrivalnotifier)\n* [isHomeDeterminer](#ishomedeterminer)\n* [isUserHomeDeterminer](#isuserhomedeterminer)\n* [leavingZoneNotifier](#leavingzonenotifier)\n* [motionTrigger](#motiontrigger)\n* [newWifiDeviceNotify](#newwifidevicenotify)\n* [nextAppointmentLeaveNotifier](#nextappointmentleavenotifier)\n* [notifyOfActionWhenAway](#notifyofactionwhenaway)\n* [plantWateringNotifier](#plantwateringnotifier)\n* [powerUsageNotification](#powerusagenotification)\n* [seqSink](#seqsink)\n* [setThermostat](#setthermostat)\n* [setThermostatOnStateChange](#setthermostatonstatechange)\n* [sleepModeHandler](#sleepmodehandler)\n* [turnFanOnWhenHot](#turnfanonwhenhot)\n* [turnOffBarAfterRestart](#turnoffbarafterrestart)\n* [updateEntityService](#updateentityservice)\n* [notify](#notify)\n\n### AlexaIntents\n\nAre explained [here](alexa/README.md)\n\n### AlexaSpeakerConnector\n\nApp to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers.\nUses a [custom_component](https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639)\nfor control alexa as a media player.\n\n```yaml\nalexaSpeakerConnector:\n  module: alexaSpeakerConnector\n  class: AlexaSpeakerConnector\n  app_switch: input_boolean.alexaSpeakerConnector\n  alexa_entity: media_player.kevins_echo_dot_oben\n  alexa_entity_source: Denon AVR-X1300W\n  receiver: media_player.denon_avr_x1300w\n  receiver_source: Bluetooth\n```\n\n### appWatcher\n\nSends a notification if a WARNING or ERROR is logged in Appdaemon\n\n```yaml\nheartbeat:\n  module: appWatcher\n  class: AppWatcher\n  notify_name: kevin\n  include_log_message_in_notification: True\n  notify_message: \"Es ist ein Fehler aufgetreten in App: {}\"\n  #notify_message: \"An Error occurred in App: {}\"\n  dependencies:\n    - Notifier\n```\n\n### alarmClock\n\nAlarm Clock App inspired by [this](https://community.home-assistant.io/t/creating-a-alarm-clock/410)\nforum post.\nIt fades in my bedroom light and sends a notifcation.\nThe fade in and alarm time is defined by input_number sliders in HA\n\n```yaml\nalarmClock:\n  module: alarmClock\n  class: AlarmClock\n  alarm_time: sensor.alarm_time\n  wakemeup: input_boolean.wakemeup\n  naturalwakeup: input_number.alarm_natural_wakeup_fade_in\n  alarmweekday: input_boolean.alarmweekday\n  radiowakeup: input_boolean.radiowakeup\n  #TODO radioplayer: input_select.wakeup_radioplayer\n  wakeup_light: light.bedroom_yeelight\n  isweekday: binary_sensor.workday_today\n  notify_name: group_notifications\n  message: \"Guten Morgen!\"\n  #message: \"Good Morning!\"\n```\n\n![alarmClock](images/alarmClock.PNG)\n\n### buttonClicked\n\nMy multipurpose App to link any switch/light to a Xiaomi Button.\n\nYou can map different entities to the click types ``single`` and ``double``.\n\nFor the 1st Generation Button you can hold the button to use it as a light dimmer.\n\n```yaml\nxiaomiroundButtonBedroomClicked:\n  module: buttonClicked\n  class: ButtonClicked\n  sensor: binary_sensor.switch_158d0001b12a12\n  actor_single: light.bedroom_yeelight\n  actor_double: group.all\n  actor_hold: light.bedroom_yeelight\n  dependencies:\n    - Notifier\n```\n\n### comingHome\n\nWhen the front door openes and no one was home before this will turn on something.\nI am using it to turn on the light (if the sun is down) and turn on the receiver so I can hear Alexa\n\n```yaml\ncomingHomeYeelight:\n  module: comingHome\n  class: ComingHome\n  app_switch: input_boolean.coming_home_yeelight\n  sensor: binary_sensor.door_window_sensor_158d000126a57b\n  isHome: input_boolean.is_home\n  actor: switch.large_lamp\n  after_sundown: True\n```\n\n### deconzXiaomiButton\n\nApp which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz.\n\nThis app is installed via [HACS](https://github.com/custom-components/hacs).\n\nThe repository itself with the full documentation can be found on github:\n[appdaemon-deconz-xiaomi-button](https://github.com/eifinger/appdaemon-deconz-xiaomi-button)\n\n```yaml\nDeconzXiaomiButtonBedroom:\n  module: deconz_xiaomi_button\n  class: DeconzXiaomiButton\n  id: round_button_schlafzimmer\n  actor_single: light.bedroom_yeelight\n  actor_double: group.all\n  actor_hold: light.bedroom_yeelight\n```\n\n### detectWrongState\n\nChecks a list of entities which should be on/off when everybody left the house.\nIf something isn't right it will try to turn it off (e.g. a light) and send a notification.\n\n```yaml\ndetectWrongStateWhenLeaving:\n  module: detectWrongStateWhenLeaving\n  class: DetectWrongStateWhenLeaving\n  app_switch: input_boolean.detect_wrong_state_when_leaving\n  entities_off: \"binary_sensor.door_window_sensor_158d000205b808,binary_sensor.door_window_sensor_158d00020499ad,\\\n  binary_sensor.door_window_sensor_158d0002059ddf,media_player.denon_avr_x1300w,switch.large_lamp,\\\n  switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\\\n  light.reading_lamp_yeelight,light.treppe_oben,light.stairs_lower_yeelight,switch.ventilator\"\n  message: \"Du hast {} angelassen. Ich habe es für dich ausgemacht.\"\n  #message: \"You left on {}. I turned it off for you\"\n  message_off: \"Du hast {} vergessen anzumachen. Ich habe es für dich angemacht.\"\n  #message_off: \"You forgot to turn on {}. I turned it on for you\"\n  message_reed: \"Du hast {} offen gelassen.\"\n  #message_reed: \"You left open {} Dummy.\"\n  message_reed_off: \"Du hast {} zu gelassen.\"\n  #message_reed_off: \"You left {} closed Dummy.\"\n  isHome: input_boolean.is_home\n```\n\n### eventMonitor\n\nMonitor all events. Useful for debugging and developing\n\n```yaml\neventMonitor:\n  module: eventMonitor\n  class: Monitor\n  events:\n```\n\n### faceRecognitionBot\n\nCOMING SOON\n\n### travel_time\n\nMonitors my Travel Time Sensors e.g. between home and work.\nI can enable an input_boolean in HA which causes this App to send me a notication\nas soon as the traffic is in an acceptable range. I use this drive to/from work when there is the least traffic.\n\n```yaml\ntravelTime_home_from_work:\n  module: travelTimeNotifier\n  class: TravelTimeNotifier\n  sensor: sensor.travel_time_home_from_work\n  notify_input_boolean: input_boolean.travel_time_home_from_work\n  notify_name: group_notifications\n  message: \"Du kannst losfahren nach {}\"\n  #message: \"You can start your journey to {}\"\n```\n\n![travelTimeNotifier](images/googleTravelTimes.PNG)\n\n### heartbeat\n\nSets a sensor in Homeassistant which is checked by an automation.\nThe [automation](https://github.com/eifinger/homeassistant-config/blob/master/automation.yaml)\nsends out a notification if appdaemon does not respond.\n\n### homeArrivalNotifier\n\nGreet the person coming home with a notification\n\n```yaml\nhomeArrivalNotifierUserOne:\n  module: homeArrivalNotifier\n  class: HomeArrivalNotifier\n  app_switch: input_boolean.home_arrival_notifier_user_one\n  input_boolean: input_boolean.user_one_home\n  notify_name: group_notifications\n  user_name: Kevin\n  zone_name: Home\n  message: \"Willkommen zu Hause {}.\"\n  #message: \"Welcome Home {}.\"\n```\n\n### isHomeDeterminer\n\nControls an input_boolean \"isHome\" which is used as a trigger for other Apps.\nThe state depends on other input_booleans controlled by the\n[isUserHomeDeterminer](isUserHomeDeterminer/isUserHomeDeterminer.py)\n\n```yaml\nisHomeDeterminer:\n  module: isHomeDeterminer\n  class: IsHomeDeterminer\n  app_switch: input_boolean.is_home_determiner\n  ishome: input_boolean.is_home\n  input_booleans: input_boolean.user_one_home,input_boolean.user_two_home\n  message: \"Es ist keiner mehr zu Hause. Setze isHome auf off\"\n  #message: \"Everyone left home. Setting isHome to off\"\n```\n\n### isUserHomeDeterminer\n\nThe GPS Logger tells me where someone is. But I want to know for sure who just came in the door.\nApp to toggle an input boolean when a person enters or leaves home.\nThis is determined based on a combination of a GPS device tracker and the door sensor.\n\n* If the door sensor opens and the device_tracker changed to \"home\" in the last self.delay minutes this means someone got home\n* If the door sensor opens and the device_tracker changes to \"not_home\" in the next self.delay minutes this means someone left home\n\n```yaml\nisUserHomeDeterminerUserOne:\n  module: isUserHomeDeterminer\n  class: IsUserHomeDeterminer\n  app_switch: input_boolean.is_user_home_determiner_user_one\n  input_boolean: input_boolean.user_one_home\n  device_tracker: person.kevin\n  door_sensor: binary_sensor.door_window_sensor_158d000126a57b\n```\n\n### leavingZoneNotifier\n\nNotify if a user is leaving a zone after being there for a certain amount of time.\nI use this to notify my SO that I am leaving work and driving home\n\n```yaml\nleavingWorkNotifierUserOne:\n  module: leavingZoneNotifier\n  class: LeavingZoneNotifier\n  app_switch: input_boolean.leaving_work_notifier_user_one\n  device: person.kevin\n  user_name: Kevin\n  lingering_time: 3600\n  delay: 120\n  zone: Arbeit\n  notify_name: group_notifications\n  message: \"{} hat {} vor {} Minuten verlassen.\"\n  travel_time_sensor: sensor.travel_time_home_user_one\n  travel_time_sensor_message: \"Die momentane Reisezeit beträgt {}.\"\n  dependencies:\n    - Notifier\n```\n\n### motionTrigger\n\nTurn something on/off when a motion sensor turns on. Automatically turn it off again after a delay.\n\n```yaml\nbathMotionTrigger:\n  module: motionTrigger\n  class: MotionTrigger\n  app_switch: input_boolean.bath_motion_trigger\n  sensor: binary_sensor.0x00158d000236b801_occupancy\n  entity_on: light.lower_bathroom_yeelight\n  entity_off: light.lower_bathroom_yeelight\n  sensor_type: zigbee2mqtt\n  after_sundown: True\n  turn_off_constraint_entities_on: binary_sensor.0x00158d0001fa464b_occupancy\n  delay: 300\n```\n\n### newWifiDeviceNotify\n\nActually a wrong name. This will send me a notification when any device_tracker component detects a new device.\nI initally thought to use this as a security feature but found it quite useful when adding new Sonoff switches and such.\nI get a notification if the setup was successfull.\n\n**Version 1.2:**\nDisplays two buttons which let me control the internet access for the new device.\nThe configured fritzbox standard profile denies internet access.\nWith this I can easily allow my guests access to the internet without logging in to my fritzbox manually.\n\n```yaml\nnewWifiDeviceNotify:\n  module: newWifiDeviceNotify\n  class: DeviceNotify\n  notify_name: group_notifications\n  message: \"Unbekanntes Gerät entdeckt. Hostname: {}. MAC: {}.\"\n  #message: \"Unknown device connected. Hostname: {}. MAC: {}\"\n```\n\n### nextAppointmentLeaveNotifier\n\nSend me a notification when it is time to leave for my next appointment based on my current location.\nInspired by [this](https://community.home-assistant.io/t/text-to-speech-notification-to-leave-for-appointment/8689)\nblog post.\n\n* Selectable travel mode (car/bus/walk/bike)\n* Only for google calendar events which have a location\n* Adjustable offset when to notify\n* Includes a direct Google Maps Navigation Link in Notification Message\n\nSaved my ass quite a few times\n\n```yaml\nnextAppointmentLeaveNotifier:\n  module: nextAppointmentLeaveNotifier\n  class: NextAppointmentLeaveNotifier\n  sensor: sensor.calc_leave_time\n  notify_input_boolean: input_boolean.announce_time_to_leave\n  notify_name: group_notifications\n  input_number: input_number.leave_time_offset\n  destination_name_sensor: sensor.cal_next_appointment_location\n  travel_time_sensor: sensor.travel_time_next_appointment_location\n  message: \"Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten. Hier ist ein Google Maps Link: {}\"\n  #message: \"It's time to leave to {}. It will take {} minutes. Here is a Google Maps Link: {}\"\n```\n\n![nextAppointmentLeaveNotifier](images/next_appoint_leave_modifier.PNG)\n![next_appoint_leave_modifier_notification](images/next_appoint_leave_modifier_notification.PNG)\n\n### notifyOfActionWhenAway\n\nNotify me of any event for a list of entities when no one is at home.\nFor example a door being openend or a motion sensor triggered\n\n```yaml\nnotifyOfActionWhenAway:\n  module: notifyOfActionWhenAway\n  class: NotifyOfActionWhenAway\n  app_switch: input_boolean.notify_of_action_when_away\n  sensor: \"binary_sensor.door_window_sensor_158d000126a57b,binary_sensor.door_window_sensor_158d0001bb4d94,\\\n  binary_sensor.door_window_sensor_158d0001bb4dc0,binary_sensor.door_window_sensor_158d000205b808,\\\n  binary_sensor.door_window_sensor_158d000205b82e,binary_sensor.door_window_sensor_158d00020498b6,\\\n  binary_sensor.door_window_sensor_158d000204ba26,binary_sensor.door_window_sensor_158d0002059ddf,\\\n  binary_sensor.door_window_sensor_158d00020499ad,binary_sensor.door_window_sensor_158d0002048951,\\\n  binary_sensor.door_window_sensor_158d00020455bf,binary_sensor.motion_sensor_158d00012aab97,\\\n  binary_sensor.motion_sensor_158d0001fa464b,binary_sensor.motion_sensor_158d0002006cfa\"\n  isHome: input_boolean.is_home\n  user_name: group_notifications\n  isHome_delay: 20\n  message: \"Alarm: {} ist gewechselt auf {}\"\n  #message: \"Alarm: {} changed to {}\"\n```\n\n![notifyOfActionWhenAway](images/notifyOfActionWhenAway.PNG)\n\n### plantWateringNotifier\n\nRemind us to water the plants in the morning when the precipiation propability is too low.\nThis uses a Telegram Chatbot. We can press a button in the notification to tell the App that we watered the plants.\nIf we don't do that we get reminded again in the evening.\n\n```yaml\nplantWateringNotifier:\n  module: plantWateringNotifier\n  class: PlantWateringNotifier\n  app_switch: input_boolean.plant_watering_notifier\n  rain_precip_sensor: sensor.dark_sky_precip_probability\n  rain_precip_intensity_sensor: sensor.dark_sky_precip_intensity\n  precip_type_sensor: sensor.dark_sky_precip\n  notify_name: group_notifications\n  user_id: secret! telegram_user_id\n  reminder_acknowledged_entity: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged\n  message: \"Die Regenwahrscheinlichkeit beträgt heute nur {}. Vergiss nicht die Pflanzen zu gießen!\"\n  #message: \"The Rain Propability is only {}. Don't forget to water the plants!\"\n  message_not_needed: \"Es wird heute mit einer Wahrscheinlichkeit von {} Prozent ungefähr {} Millimeter pro Stunde regnen. Du brauchst nicht selbst gießen.\"\n  #message_not_needed: \"It will rain today {} millimeter per hour with a propability of {}. You don't have to water your plants\"\n  message_evening: \"Ich bin mir nicht sicher ob du vergessen hast die Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran.\"\n  #message_evening: \"I'm not sure whether you waterd your plants, so I thought I better remind you again\"\n```\n\n![plantWateringReminder](images/plantWateringReminder.PNG)\n![plantWateringReminderAcknowledged](images/plantWateringReminderAcknowledged.PNG)\n\n### pollenNotifier\n\nNotify in the morning if any monitored pollen level is above a threshold.\n\n```yaml\nroggenNotifier:\n  module: pollenNotifier\n  class: PollenNotifier\n  app_switch: input_boolean.roggen_notifier\n  pollen_sensor: sensor.pollen_101_roggen_today\n  pollen_name: Roggen\n  notify_name: group_notifications\n  notify_time: 08:00\n  notify_threshold: 1.0\n  message: \"{} ist {} {} Belastung.\"\n  #message: \"The {} intensity {} is {}.\"\n  message_no_data: \"Ich habe {} leider keine Daten für {}.\"\n  #message_no_data: \"{} I have no pollen data for {}.\"\n```\n\n![roggenNotify](images/roggenNotify.PNG)\n\n### powerUsageNotification\n\nNotify 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)\n\n```yaml\npowerUsageNotification_Dishwasher:\n  module: powerUsageNotification\n  class: PowerUsageNotification\n  app_switch: input_boolean.power_usage_notification_dishwasher\n  sensor: sensor.dishwasher_power_usage\n  notify_name: group_notifications\n  delay: 1260 #21 minutes\n  threshold: 2\n  alternative_name: Die Spülmaschine\n  message: \"{} ist gestartet.\"\n  #message: \"{} just started.\"\n  message_off: \"{} ist fertig.\"\n  #message_off: \"{} just finished.\"\n```\n\n![dishWasherNotify](images/dishWasherNotify.PNG)\n\n### seqSink\n\nApp which forwards all logs to seq.\nBlogged about this app in [this](https://blog.kevineifinger.de/archive/2020/07/06/Log-Management-For-My-AppDaemon-Apps.html) post.\n\n```yaml\nseqSink:\n  module: seqSink\n  class: SeqSink\n  server_url: \"http://seq:5341/\"\n```\n\n### setThermostat\n\nApp which sets a thermostat to a target temperature for a specific duration\n\n```yaml\nwarm_bath_before_wakeup:\n  module: setThermostat\n  class: SetThermostat\n  app_switch: input_boolean.warm_bath_before_wakeup\n  isHome: input_boolean.is_home\n  time_entity: sensor.alarm_time\n  upfront_time: 60\n  duration: 60\n  climat_entity: climate.bad_thermostat\n  target_entity: input_number.warm_bath_before_wakeup\n  message: \"Ich habe {} auf {} gestellt\"\n  #message: \"I have set {} to {}\"\n  notify_name: group_notifications\n  use_alexa: False\n  dependencies:\n    - Notifier\n```\n\n### setThermostatOnStateChange\n\nApp which sets a thermostat to a target temperature on state change.\n\n```yaml\nsetBadObenThermostatWhenComingHome:\n  module: setThermostatOnStateChange\n  class: SetThermostatOnStateChange\n  app_switch: input_boolean.set_upper_bath_thermostat_when_coming_home\n  trigger_entity: input_boolean.is_home\n  trigger_state: \"on\"\n  climate_entity: climate.bad_oben_thermostat\n  target_entity: input_number.set_upper_bath_thermostat_when_coming_home\n  message: \"Ich habe {} auf {} °C gestellt\"\n  #message: \"I have set {} to {}\"\n  notify_name: group_notifications\n  use_alexa: False\n  dependencies:\n    - Notifier\n```\n\n### sleepModeHandler\n\nSet an input_boolean on/off. Used as a trigger for other Apps.\nAlso controlled by ``Alexa, guten Morgen`` ``Alexa, gute Nacht``\n\nWill watch room sensor of users\n\n````yaml\nsleepModeHandler:\n  module: sleepModeHandler\n  class: SleepModeHandler\n  app_switch: input_boolean.sleep_mode_handler\n  sleepmode: input_boolean.sleepmode\n  notify_name: group_notifications\n  message_sleeping: \"Alle zu Hause sind im Bett\"\n  #message_sleeping: \"All home are in bed\"\n  message_awake: \"Alle zu Hause sind wach\"\n  #message_awake: \"All home are awake\"\n  users:\n    - sleep_mode: input_boolean.user_one_sleep\n      isHome: input_boolean.user_one_home\n    - sleep_mode: input_boolean.user_two_sleep\n      isHome: input_boolean.user_two_home\n  dependencies:\n    - Notifier\n````\n\n````yaml\nuserSleepModeHandlerUserOne:\n  module: userSleepModeHandler\n  class: UserSleepModeHandler\n  app_switch: input_boolean.user_sleep_mode_handler_user_one\n  input_boolean: input_boolean.user_one_sleep\n  location_sensor: person.kevin\n  room: bedroom\n  duration: 600\n\nuserSleepModeHandlerUserTwo:\n  module: userSleepModeHandler\n  class: UserSleepModeHandler\n  app_switch: input_boolean.user_sleep_mode_handler_user_two\n  input_boolean: input_boolean.user_two_sleep\n  location_sensor: sensor.mqtt_room_user_two\n  room: bedroom\n  duration: 600\n````\n\n### turnFanOnWhenHot\n\nTurns the Fan on when the temperature is above a configurable threshold and someone is in the room\n([find3](https://github.com/schollz/find3))\n\n```yaml\nturnFanOnWhenHot:\n  module: turnFanOnWhenHot\n  class: TurnFanOnWhenHot\n  app_switch: input_boolean.turn_fan_on_when_hot\n  temp_sensor: sensor.large_lamp_temperature\n  threshold_entity: input_number.turn_fan_on_when_hot_threshold\n  location_sensors: sensor.location_user_one,sensor.location_user_two\n  room: Wohnzimmer\n  actor: switch.large_ventilator\n  delay: 120\n```\n\n![ventilatorAutomation](images/ventilatorAutomation.PNG)\n\n### turnOffBarAfterRestart\n\nAs I sometimes restart HA when working on it from remote I turn the Bar lights to red with\n[this script](https://github.com/eifinger/homeassistant-config/blob/master/updateHomeassistant.sh).\nThis way everyone can see HA is currently unavailable.\nIf it comes back up again this app will turn the light green and then off.\n\n### notify\n\nIN DEVELOPMENT\nCentralizes messaging. Among other things, it will determine whether a user is at home and if yes in which room.\n\nThen Alexa in that room will be used additionally to Telegram\n\n```yaml\nNotify:\n  module: notify\n  class: Notify\n  media_player: media_player.denon_avr_x1300w\n  source: CBL/SAT\n  alexa_media_player: media_player.kevins_echo_dot_oben\n  global_dependencies:\n    - globals\n```\n\n# Thanks\n\nFirst of all thanks to the Homeassistant Team and [Andrew Cockburn](https://github.com/acockburn) for making Appdaemon\n\nSome of the Apps are taken from the official examples and many based on or at least inspired by\n[Rene Tode](https://github.com/ReneTode).\nFor example his absolutely fantastic [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App).\n"
  },
  {
    "path": "ad-ench/ench.py",
    "content": "\"\"\"EnCh.\n   Entity Checker\n\n  @benleb / https://github.com/benleb/ad-ench\n\"\"\"\n\n__version__ = \"0.9.0\"\n\nfrom datetime import datetime, timedelta\nfrom fnmatch import fnmatch\nfrom pprint import pformat\nfrom sys import version_info\nfrom typing import Any, Dict, Iterable, List, Optional, Tuple, Union\n\nimport hassapi as hass\n\n\nAPP_NAME = \"EnCh\"\nAPP_ICON = \"👩‍⚕️\"\n\nBATTERY_MIN_LEVEL = 20\nINTERVAL_BATTERY_MIN = 180\nINTERVAL_BATTERY = INTERVAL_BATTERY_MIN / 60\n\nINTERVAL_UNAVAILABLE_MIN = 60\nINTERVAL_UNAVAILABLE = INTERVAL_UNAVAILABLE_MIN / 60\nMAX_UNAVAILABLE_MIN = 0\n\nINTERVAL_STALE_MIN = 15\nMAX_STALE_MIN = 60\n\nINITIAL_DELAY = 60\n\nRANDOMIZE_SEC: int = 15\nSECONDS_PER_MIN: int = 60\n\nEXCLUDE = [\"binary_sensor.updater\", \"persistent_notification.config_entry_discovery\"]\nBAD_STATES = [\"unavailable\", \"unknown\"]\nLEVEL_ATTRIBUTES = [\"battery_level\", \"Battery Level\"]\nCHECKS = [\"battery\", \"stale\", \"unavailable\"]\n\nICONS: Dict[str, str] = dict(battery=\"🔋\", unavailable=\"⁉️ \", unknown=\"❓\", stale=\"⏰\")\n\n\n# version checks\npy3_or_higher = version_info.major >= 3\npy37_or_higher = py3_or_higher and version_info.minor >= 7\npy38_or_higher = py3_or_higher and version_info.minor >= 8\n\n\ndef hl(text: Union[int, float, str]) -> str:\n    return f\"\\033[1m{text}\\033[0m\"\n\n\ndef hl_entity(entity: str) -> str:\n    if len(splitted := entity.split(\".\")) > 1:\n        return f\"{splitted[0]}.{hl(splitted[1])}\"\n    else:\n        return f\"{hl(entity)}\"\n\n\nclass EnCh(hass.Hass):  # type: ignore\n    \"\"\"EnCh.\"\"\"\n\n    def lg(self, msg: str, *args: Any, icon: Optional[str] = None, repeat: int = 1, **kwargs: Any) -> None:\n        kwargs.setdefault(\"ascii_encode\", False)\n        message = f\"{f'{icon} ' if icon else ' '}{msg}\"\n        _ = [self.log(message, *args, **kwargs) for _ in range(repeat)]\n\n    async def initialize(self) -> None:\n        \"\"\"Register API endpoint.\"\"\"\n        self.icon = APP_ICON\n\n        # python version check\n        if not py38_or_higher:\n            icon_alert = \"⚠️\"\n            self.lg(\"\", icon=icon_alert)\n            self.lg(\"\")\n            self.lg(f\"please update to {hl('Python >= 3.8')}! 🤪\", icon=icon_alert)\n            self.lg(\"\")\n            self.lg(\"\", icon=icon_alert)\n        if not py37_or_higher:\n            raise ValueError\n\n        self.cfg: Dict[str, Any] = dict()\n        self.cfg[\"show_friendly_name\"] = bool(self.args.get(\"show_friendly_name\", True))\n        self.cfg[\"init_delay_secs\"] = int(self.args.get(\"initial_delay_secs\", INITIAL_DELAY))\n\n        # home assistant sensor\n        hass_sensor: str\n        if hass_sensor := self.args.get(\"hass_sensor\", \"sensor.ench_entities\"):\n            self.cfg[\"hass_sensor\"] = hass_sensor if hass_sensor.startswith(\"sensor.\") else f\"sensor.{hass_sensor}\"\n            self.sensor_state: int = 0\n            self.sensor_attrs: Dict[str, Any] = {check: [] for check in CHECKS}\n            self.sensor_attrs.update({\"unit_of_measurement\": \"Entities\", \"should_poll\": False})\n\n        # global notification\n        if \"notify\" in self.args:\n            self.cfg[\"notify\"] = self.args.get(\"notify\")\n\n        # initial wait to give all devices a chance to become available\n        init_delay = await self.datetime() + timedelta(seconds=self.cfg[\"init_delay_secs\"])\n\n        # battery check\n        if \"battery\" in self.args:\n\n            config: Dict[str, Union[str, int]] = self.args.get(\"battery\")\n\n            # store configuration\n            self.cfg[\"battery\"] = dict(\n                interval_min=int(config.get(\"interval_min\", INTERVAL_BATTERY_MIN)),\n                min_level=int(config.get(\"min_level\", BATTERY_MIN_LEVEL)),\n            )\n\n            # no, per check or global notification\n            self.choose_notify_recipient(\"battery\", config)\n\n            # schedule check\n            await self.run_every(\n                self.check_battery,\n                init_delay,\n                self.cfg[\"battery\"][\"interval_min\"] * 60,\n                random_start=-RANDOMIZE_SEC,\n                random_end=RANDOMIZE_SEC,\n            )\n\n        # unavailable check\n        if \"unavailable\" in self.args:\n\n            config = self.args.get(\"unavailable\")\n\n            # store configuration\n            self.cfg[\"unavailable\"] = dict(\n                interval_min=int(config.get(\"interval_min\", INTERVAL_UNAVAILABLE_MIN)),\n                max_unavailable_min=int(config.get(\"max_unavailable_min\", MAX_UNAVAILABLE_MIN)),\n            )\n\n            # no, per check or global notification\n            self.choose_notify_recipient(\"unavailable\", config)\n\n            # schedule check\n            self.run_every(\n                self.check_unavailable,\n                await self.datetime() + timedelta(seconds=self.cfg[\"init_delay_secs\"]),\n                self.cfg[\"unavailable\"][\"interval_min\"] * 60,\n                random_start=-RANDOMIZE_SEC,\n                random_end=RANDOMIZE_SEC,\n            )\n\n        # stale entities check\n        if \"stale\" in self.args:\n\n            config = self.args.get(\"stale\", {})\n            interval_min = config.get(\"interval_min\", INTERVAL_STALE_MIN)\n            max_stale_min = config.get(\"max_stale_min\", MAX_STALE_MIN)\n\n            # store configuration\n            self.cfg[\"stale\"] = dict(\n                interval_min=int(min([interval_min, max_stale_min])),\n                max_stale_min=int(max_stale_min),\n            )\n\n            self.cfg[\"stale\"][\"entities\"] = config.get(\"entities\", [])\n\n            # no, per check or global notification\n            self.choose_notify_recipient(\"stale\", config)\n\n            # schedule check\n            self.run_every(\n                self.check_stale,\n                await self.datetime() + timedelta(seconds=self.cfg[\"init_delay_secs\"]),\n                self.cfg[\"stale\"][\"interval_min\"] * 60,\n                random_start=-RANDOMIZE_SEC,\n                random_end=RANDOMIZE_SEC,\n            )\n\n        # merge excluded entities\n        exclude = set(EXCLUDE)\n        exclude.update([e.lower() for e in self.args.get(\"exclude\", set())])\n        self.cfg[\"exclude\"] = sorted(list(exclude))\n\n        # set units\n        self.cfg.setdefault(\n            \"_units\",\n            dict(interval_min=\"min\", max_stale_min=\"min\", min_level=\"%\"),\n        )\n\n        self.show_info(self.args)\n\n    async def check_battery(self, _: Any) -> None:\n        \"\"\"Handle scheduled checks.\"\"\"\n        check_config = self.cfg[\"battery\"]\n        results: List[Tuple[str, int]] = []\n\n        self.lg(\"Checking entities for low battery levels...\", icon=APP_ICON, level=\"DEBUG\")\n\n        states = await self.get_state()\n\n        entities = filter(\n            lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg[\"exclude\"]),\n            states,\n        )\n\n        for entity in sorted(entities):\n            battery_level = None\n\n            try:\n                # check entities which may be battery level sensors\n                if \"battery_level\" in entity or \"battery\" in entity:\n                    # battery_level = int(await self.get_state(entity))\n                    battery_level = int(states[entity][\"state\"])\n\n                # check entity attributes for battery levels\n                if not battery_level:\n                    for attr in LEVEL_ATTRIBUTES:\n                        # battery_level = int(await self.get_state(entity, attribute=attr))\n                        battery_level = int(states[entity][\"attributes\"].get(attr))\n                        break\n            except (TypeError, ValueError):\n                pass\n\n            if battery_level and battery_level <= check_config[\"min_level\"]:\n                # results.append(entity)\n                results.append((entity, battery_level))\n                last_updated = (await self.last_update(entity)).time().isoformat(timespec=\"seconds\")\n                self.lg(\n                    f\"{await self._name(entity)} has low \"\n                    # f\"{hl(f'battery → {hl(int(battery_level))}')}%\",\n                    f\"{hl(f'battery → {hl(int(battery_level))}')}% | \" f\"last update: {last_updated}\",\n                    # f\"last update: {self.adu.last_update(entity)}\",\n                    icon=ICONS[\"battery\"],\n                )\n\n        # send notification\n        notify = self.cfg.get(\"notify\") or check_config.get(\"notify\")\n        if notify and results:\n            await self.call_service(\n                str(notify).replace(\".\", \"/\"),\n                message=f\"{ICONS['battery']} Battery low ({len(results)}): \"\n                f\"{', '.join([f'{str(await self._name(entity[0], notification=True))} {entity[1]}%' for entity in results])}\",  # noqa\n            )\n\n        # update hass sensor\n        if \"hass_sensor\" in self.cfg and self.cfg[\"hass_sensor\"]:\n            await self.update_sensor(\"battery\", [entity[0] for entity in results])\n\n        self._print_result(\"battery\", [entity[0] for entity in results], \"low battery levels\")\n\n    async def check_unavailable(self, _: Any) -> None:\n        \"\"\"Handle scheduled checks.\"\"\"\n        check_config = self.cfg[\"unavailable\"]\n        results: List[str] = []\n\n        self.lg(\"Checking entities for unavailable/unknown state...\", icon=APP_ICON, level=\"DEBUG\")\n\n        entities = filter(\n            lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg[\"exclude\"]),\n            await self.get_state(),\n        )\n\n        for entity in sorted(entities):\n            state = await self.get_state(entity_id=entity)\n\n            if state in BAD_STATES and entity not in results:\n\n                last_update = await self.last_update(entity)\n                now: datetime = await self.datetime(aware=True)\n                unavailable_time: timedelta = now - last_update\n                max_unavailable_min = timedelta(minutes=self.cfg[\"unavailable\"][\"max_unavailable_min\"])\n\n                if unavailable_time >= max_unavailable_min:\n                    results.append(entity)\n                    last_updated = (await self.last_update(entity)).time().isoformat(timespec=\"seconds\")\n                    self.lg(\n                        f\"{await self._name(entity)} is \"\n                        f\"{hl(state)} since {hl(int(unavailable_time.seconds / 60))}min | \"\n                        f\"last update: {last_updated}\",\n                        icon=ICONS[state],\n                    )\n                    # self.lg(\n                    #     f\"{await self._name(entity)} is {hl(state)} | \" f\"last update: {last_updated}\",\n                    #     icon=ICONS[state],\n                    # )\n\n        # send notification\n        notify = self.cfg.get(\"notify\") or check_config.get(\"notify\")\n        if notify and results:\n            self.call_service(\n                str(notify).replace(\".\", \"/\"),\n                message=f\"{APP_ICON} Unavailable entities ({len(results)}): \"\n                f\"{', '.join([str(await self._name(entity, notification=True)) for entity in results])}\",\n            )\n\n        # update hass sensor\n        if \"hass_sensor\" in self.cfg and self.cfg[\"hass_sensor\"]:\n            await self.update_sensor(\"unavailable\", results)\n\n        self._print_result(\"unavailable\", results, \"unavailable/unknown state\")\n\n    async def check_stale(self, _: Any) -> None:\n        check_config = self.cfg[\"stale\"]\n        \"\"\"Handle scheduled checks.\"\"\"\n        results: List[str] = []\n\n        self.lg(\"Checking for stale entities...\", icon=APP_ICON, level=\"DEBUG\")\n\n        if self.cfg[\"stale\"][\"entities\"]:\n            all_entities = self.cfg[\"stale\"][\"entities\"]\n        else:\n            all_entities = await self.get_state()\n\n        entities = filter(\n            lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg[\"exclude\"]),\n            all_entities,\n        )\n\n        for entity in sorted(entities):\n\n            attr_last_updated = await self.get_state(entity_id=entity, attribute=\"last_updated\")\n\n            if not attr_last_updated:\n                self.lg(f\"{await self._name(entity)} has no 'last_updated' attribute ¯\\\\_(ツ)_/¯\", icon=ICONS[\"stale\"])\n                continue\n\n            last_update = self.convert_utc(attr_last_updated)\n            last_updated = (await self.last_update(entity)).time().isoformat(timespec=\"seconds\")\n            now: datetime = await self.datetime(aware=True)\n\n            stale_time: timedelta = now - last_update\n            max_stale_min = timedelta(minutes=self.cfg[\"stale\"][\"max_stale_min\"])\n\n            if stale_time and stale_time >= max_stale_min:\n                results.append(entity)\n                self.lg(\n                    f\"{await self._name(entity)} is \"\n                    f\"{hl(f'stale since {hl(int(stale_time.seconds / 60))}')}min | \"\n                    f\"last update: {last_updated}\",\n                    icon=ICONS[\"stale\"],\n                )\n\n        # send notification\n        notify = self.cfg.get(\"notify\") or check_config.get(\"notify\")\n        if notify and results:\n            self.call_service(\n                str(notify).replace(\".\", \"/\"),\n                message=f\"{APP_ICON} Stalled entities ({len(results)}): \"\n                f\"{', '.join([str(await self._name(entity, notification=True)) for entity in results])}\",\n            )\n\n        # update hass sensor\n        if \"hass_sensor\" in self.cfg and self.cfg[\"hass_sensor\"]:\n            await self.update_sensor(\"stale\", results)\n\n        self._print_result(\"stale\", results, \"stalled updates\")\n\n    def choose_notify_recipient(self, check: str, config: Dict[str, Any]) -> None:\n        if \"notify\" in config and \"notify\" not in self.cfg:\n            self.cfg[check][\"notify\"] = config[\"notify\"]\n\n    async def last_update(self, entity_id: str) -> Any:\n        return self.convert_utc(await self.get_state(entity_id=entity_id, attribute=\"last_updated\"))\n\n    async def _name(self, entity: str, friendly_name: bool = False, notification: bool = False) -> Optional[str]:\n\n        name: Optional[str] = None\n        if self.cfg[\"show_friendly_name\"]:\n            name = await self.friendly_name(entity)\n        else:\n            name = entity\n\n        if notification is False and name:\n            name = hl_entity(name)\n\n        return name\n\n    def _print_result(self, check: str, entities: List[str], reason: str) -> None:\n        if entities:\n            self.lg(f\"{hl(f'{len(entities)} entities')} with {hl(reason)}!\", icon=APP_ICON, level=\"DEBUG\")\n        else:\n            self.lg(f\"{hl(f'no entities')} with {hl(reason)}!\", icon=APP_ICON)\n\n    async def update_sensor(self, check_name: str, entities: List[str]) -> None:\n\n        if check_name not in CHECKS:\n            self.lg(\n                f\"Unknown check: {hl(f'no entities')} - {self.cfg['hass_sensor']} not updated!\",\n                icon=APP_ICON,\n                level=\"ERROR\",\n            )\n\n        if len(self.sensor_attrs[check_name]) != len(entities):\n\n            self.sensor_attrs[check_name] = entities\n            self.sensor_state = sum([len(self.sensor_attrs[check]) for check in CHECKS])\n            self.set_state(self.cfg[\"hass_sensor\"], state=self.sensor_state, attributes=self.sensor_attrs)\n\n            self.lg(\n                f\"{hl_entity(self.cfg['hass_sensor'])} -> {hl(self.sensor_state)} \"\n                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])}\",\n                icon=APP_ICON,\n                level=\"INFO\",\n            )\n\n    def show_info(self, config: Optional[Dict[str, Any]] = None) -> None:\n        # check if a room is given\n        if config:\n            self.config = config\n\n        if not self.config:\n            self.lg(\"no configuration available\", icon=\"‼️\", level=\"ERROR\")\n            return\n\n        room = \"\"\n        if \"room\" in self.config:\n            room = f\" - {hl(self.config['room'].capitalize())}\"\n\n        self.lg(\"\")\n        self.lg(f\"{hl(APP_NAME)} v{hl(__version__)}{room}\", icon=self.icon)\n        self.lg(\"\")\n\n        listeners = self.config.pop(\"listeners\", None)\n\n        for key, value in self.config.items():\n\n            # hide \"internal keys\" when displaying config\n            if key in [\"module\", \"class\"] or key.startswith(\"_\"):\n                continue\n\n            if isinstance(value, list):\n                self.print_collection(key, value, 2)\n            elif isinstance(value, dict):\n                self.print_collection(key, value, 2)\n            else:\n                self._print_cfg_setting(key, value, 2)\n\n        if listeners:\n            self.lg(\"  event listeners:\")\n            for listener in sorted(listeners):\n                self.lg(f\"    - {hl(listener)}\")\n\n        self.lg(\"\")\n\n    def print_collection(self, key: str, collection: Iterable[Any], indentation: int = 2) -> None:\n\n        self.log(f\"{indentation * ' '}{key}:\")\n        indentation = indentation + 2\n\n        for item in collection:\n            indent = indentation * \" \"\n\n            if isinstance(item, dict):\n                if \"name\" in item:\n                    self.print_collection(item.pop(\"name\", \"\"), item, indentation)\n                else:\n                    self.log(f\"{indent}{hl(pformat(item, compact=True))}\")\n\n            elif isinstance(collection, dict):\n                self._print_cfg_setting(item, collection[item], indentation)\n\n            else:\n                self.log(f\"{indent}- {hl(item)}\")\n\n    def _print_cfg_setting(self, key: str, value: Union[int, str], indentation: int) -> None:\n        unit = prefix = \"\"\n        indent = indentation * \" \"\n\n        # legacy way\n        if key == \"delay\" and isinstance(value, int):\n            unit = \"min\"\n            min_value = f\"{int(value / 60)}:{int(value % 60):02d}\"\n            self.log(f\"{indent}{key}: {prefix}{hl(min_value)}{unit} ~≈ \" f\"{hl(value)}sec\")\n\n        else:\n            if \"_units\" in self.config and key in self.config[\"_units\"]:\n                unit = self.config[\"_units\"][key]\n            if \"_prefixes\" in self.config and key in self.config[\"_prefixes\"]:\n                prefix = self.config[\"_prefixes\"][key]\n\n            self.log(f\"{indent}{key}: {prefix}{hl(value)}{unit}\")\n"
  },
  {
    "path": "alarmClock/alarmClock.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nimport math\n\n\n#\n# Alarm Clock App\n#\n#\n# Args:\n#  alarm_time: entity which holds the alarm time. example: sensor.alarm_time\n#  wakemeup: entity which enables the alarm. example: input_boolean.wakemeup\n#  naturalwakeup: entity which enables the natural wake up fade in. example: input_number.alarm_natural_wakeup_fade_in\n#  alarmweekday: entity which enables alarm only on weekdays. example: input_boolean.alarmweekday\n#  radiowakeup: entity which enables radio wake up. example: input_boolean.radiowakeup\n#  TODO radioplayer: entity which holds the information which radio player to select. example: input_select.wakeup_radioplayer\n#  wakeup_light: light to fade in. example: light.bedroom_yeelight\n#  isweekday: entity which holds the information whether today is a week day. example: binary_sensor.workday_today\n#  notify_name: Who to notify. example: group_notifications\n#  message: localized message to use in notification. e.g. \"You left open {} Dummy.\"\n#\n# Release Notes\n#\n# Version 1.4:\n#   Catch unknown\n#\n# Version 1.3.1:\n#   Use consistent message variable\n#\n# Version 1.3:\n#   Use new formatted alarm_time\n#\n# Version 1.2:\n#   use Notify App\n#\n# Version 1.1:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass AlarmClock(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.alarm_time = self.args[\"alarm_time\"]\n        self.wakemeup = self.args[\"wakemeup\"]\n        self.naturalwakeup = self.args[\"naturalwakeup\"]\n        self.alarmweekday = self.args[\"alarmweekday\"]\n        self.radiowakeup = self.args[\"radiowakeup\"]\n        self.isweekday = self.args[\"isweekday\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.wakeup_light = self.args[\"wakeup_light\"]\n        self.fade_in_time_multiplicator = self.args[\"fade_in_time_multiplicator\"]\n        self.message = self.args[\"message\"]\n        self.button = self.args[\"button\"]\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.brightness = 100\n        self.rgb_color = [255, 120, 0]\n        self.alarm_timer = None\n\n        self.cached_alarm_time = self.get_state(self.alarm_time)\n        self.cached_fade_in_time = self.get_state(self.naturalwakeup)\n        self.add_timer()\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.alarm_change, self.alarm_time)\n        )\n        self.listen_state_handle_list.append(\n            self.listen_state(self.naturalwakeup_change, self.naturalwakeup)\n        )\n\n        self.listen_event_handle_list.append(\n            self.listen_event(self.button_clicked, \"xiaomi_aqara.click\")\n        )\n\n    def alarm_change(self, entity, attributes, old, new, kwargs):\n        if new is not None and new != old and new != self.cached_alarm_time:\n            if self.alarm_timer is not None:\n                if self.alarm_timer in self.timer_handle_list:\n                    self.timer_handle_list.remove(self.alarm_timer)\n                self.cancel_timer(self.alarm_timer)\n            self.log(\"Alarm time change: {}\".format(new))\n            self.cached_alarm_time = new\n            self.add_timer()\n\n    def naturalwakeup_change(self, entity, attributes, old, new, kwargs):\n        if new is not None and new != old and new != self.cached_fade_in_time:\n            if self.alarm_timer is not None:\n                if self.alarm_timer in self.timer_handle_list:\n                    self.timer_handle_list.remove(self.alarm_timer)\n                self.cancel_timer(self.alarm_timer)\n            self.log(\"Fade-In time change: {}\".format(new))\n            self.cached_fade_in_time = new\n            self.add_timer()\n\n    def add_timer(self):\n        self.log(\"cached_alarm_time: {}\".format(self.cached_alarm_time))\n        self.log(\"cached_fade_in_time: {}\".format(self.cached_fade_in_time))\n        offset = self.cached_fade_in_time.split(\".\", 1)[0]\n\n        if (\n            self.cached_alarm_time is not None\n            and self.cached_alarm_time != \"\"\n            and self.cached_alarm_time != \"unknown\"\n        ):\n            run_datetime = datetime.datetime.strptime(\n                self.cached_alarm_time, \"%Y-%m-%d %H:%M:%S\"\n            )\n            event_time = run_datetime - datetime.timedelta(minutes=int(offset))\n            try:\n                self.alarm_timer = self.run_at(self.trigger_alarm, event_time)\n                self.timer_handle_list.append(self.alarm_timer)\n                self.log(\"Alarm will trigger at {}\".format(event_time))\n            except ValueError:\n                self.log(\"New trigger time would be in the past: {}\".format(event_time))\n\n    def trigger_alarm(self, kwargs):\n        if self.get_state(self.wakemeup) == \"on\":\n            if self.get_state(self.alarmweekday) == \"off\" or (\n                self.get_state(self.alarmweekday) == \"on\"\n                and self.get_state(self.isweekday) == \"on\"\n            ):\n                if float(self.cached_fade_in_time) > 0:\n                    self.log(\n                        \"Turning on {}\".format(self.friendly_name(self.wakeup_light))\n                    )\n                    self.call_service(\n                        \"light/turn_on\", entity_id=self.wakeup_light, brightness_pct=1\n                    )\n                    transition = int(\n                        float(self.cached_fade_in_time)\n                        * int(self.fade_in_time_multiplicator)\n                    )\n                    self.log(\n                        \"Transitioning light in over {} seconds\".format(transition)\n                    )\n                    self.timer_handle_list.append(\n                        self.run_in(\n                            self.run_fade_in, 1, transition=transition, brightness_pct=1\n                        )\n                    )\n                self.timer_handle_list.append(\n                    self.run_in(self.run_alarm, float(self.cached_fade_in_time))\n                )\n\n    def button_clicked(self, event_name, data, kwargs):\n        \"\"\"Extra callback method to trigger the wakeup light on demand by pressing a Xiaomi Button\"\"\"\n        if data[\"entity_id\"] == self.button:\n            if data[\"click_type\"] == \"single\":\n                if float(self.cached_fade_in_time) > 0:\n                    self.log(\n                        \"Turning on {}\".format(self.friendly_name(self.wakeup_light))\n                    )\n                    self.call_service(\n                        \"light/turn_on\", entity_id=self.wakeup_light, brightness_pct=1\n                    )\n                    transition = int(\n                        float(self.cached_fade_in_time)\n                        * int(self.fade_in_time_multiplicator)\n                    )\n                    self.log(\n                        \"Transitioning light in over {} seconds\".format(transition)\n                    )\n                    self.timer_handle_list.append(\n                        self.run_in(\n                            self.run_fade_in, 1, transition=transition, brightness_pct=1\n                        )\n                    )\n\n    def run_fade_in(self, kwargs):\n        \"\"\"\n        Callback / recursion style because the transition feature does not seem to work well with Yeelight for\n        transition values greater than 10s.\n        :param kwargs:\n        :return:\n        \"\"\"\n        wait_factor = 1\n        transition = kwargs[\"transition\"]\n        brightness_pct = kwargs[\"brightness_pct\"]\n        pct_increase = 1 / transition\n        self.log(\"pct_increase: {}\".format(pct_increase), level=\"DEBUG\")\n        if pct_increase < 0.01:\n            wait_factor = math.ceil(0.01 / pct_increase)\n            pct_increase = 0.01\n            self.log(\n                \"pct_increase smaller than 1% next run_in in {} seconds\".format(\n                    wait_factor\n                ),\n                level=\"DEBUG\",\n            )\n        brightness_pct_old = brightness_pct\n        self.log(\"brightness_pct_old: {}\".format(brightness_pct_old), level=\"DEBUG\")\n        brightness_pct_new = int((brightness_pct_old + pct_increase * 100))\n        self.log(\"brightness_pct_new: {}\".format(brightness_pct_new), level=\"DEBUG\")\n        if brightness_pct_new < 100:\n            self.call_service(\n                \"light/turn_on\",\n                entity_id=self.wakeup_light,\n                rgb_color=self.rgb_color,\n                brightness_pct=brightness_pct_new,\n            )\n            self.timer_handle_list.append(\n                self.run_in(\n                    self.run_fade_in,\n                    wait_factor,\n                    transition=transition,\n                    brightness_pct=brightness_pct_new,\n                )\n            )\n\n    def run_alarm(self, kwargs):\n        self.notifier.notify(self.notify_name, self.message)\n        # TODO radio\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "alarmClock/alarmClock.yaml",
    "content": "# Alarm Clock App\n# alarmClock:\n#   module: alarmClock\n#   class: AlarmClock\n#   alarm_time: sensor.alarm_time\n#   wakemeup: input_boolean.wakemeup\n#   naturalwakeup: input_number.alarm_natural_wakeup_fade_in\n#   alarmweekday: input_boolean.alarmweekday\n#   radiowakeup: input_boolean.radiowakeup\n#   #TODO radioplayer: input_select.wakeup_radioplayer\n#   wakeup_light: light.bedroom_yeelight\n#   fade_in_time_multiplicator: 60\n#   isweekday: binary_sensor.workday_today\n#   notify_name: group_notifications\n#   message: \"Guten Morgen!\"\n#   #message: \"Good Morning!\"\n#   button: binary_sensor.switch_158d000215aa28\n#   dependencies:\n#     - Notifier\n"
  },
  {
    "path": "alexa/README.md",
    "content": "# Alexa Intents\n\nIntents for [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App) from [Rene Tode](https://github.com/ReneTode).\n\nTo set it up for yourself follow [this](https://github.com/ReneTode/Alexa-Appdaemon-App/blob/master/alexa%20skill%20tutorial.md) tutorial\n\n## listService\n\nSupply friendly names and known entities for other alexa skills.\nThis is needed as this is a custom Alexa App and has nothing to do with HA Cloud / Alexa integration\n\n## turnEntityOffInX\n\nAsk Alexa to turn something off in a set amount of minutes.\n\nOnly works with entities defined under *switchable* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml)\n\n``Alexa tell Home Assistant to turn off Ventilator in 10 Minutes``\n\n```yaml\nturnEntityOffInXIntent:\n  module: turnEntityOffInXIntent\n  class: TurnEntityOffInXIntent\n  language: DE\n  textLine: \"Okay Homeassistant schaltet {{device}} in\"\n  Error: <p>Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?</p>\n  unreadableState: \"unlesbar fuer mich\"\n  dependencies:\n   - listService\n```\n\n## windowsOpen\n\nWill tell you if any windows / doors are open and/or tilted\n\nOnly works with entities defined under *window*/*door*/*door_tilted* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml)\n\n``Alexa ask Home Assistant whether all windows are closed``\n\n```yaml\nwindowsOpenIntent:\n  module: windowsOpenIntent\n  class: WindowsOpenIntent\n  language: DE\n  textLineClosed: \"Alle Fenster und Türen sind zu\"\n  #textLineClosed: \"All windows and doors are closed\"\n  textLineWindowOpen: \"Folgende Fenster sind noch offen\"\n  #textLineWindowOpen: \"The following windows are stil open...\"\n  textLineDoorOpen: \"Folgende Türen sind noch offen\"\n  #textLineDoorOpen: \"The following doors are still open\"\n  textLineDoorTilted: \"Die folgenden Türen sind noch gekippt\"\n  #textLineDoorTilted: \"The following doors are tilted\"\n  Error: <p>Ich habe dich nicht richtig verstanden</p>\n  unreadableState: \"unlesbar fuer mich\"\n  dependencies:\n   - listService\n```\n\n## nextBusIntent\n\nWill tell you the next departure of a bus/train of a [RMV](https://www.home-assistant.io/components/sensor.rmvtransport/) sensor\n\n``Alexa ask Home Assistant when the next bus departs``\n\n```yaml\nnextBusIntent:\n  module: nextBusIntent\n  class: nextBusIntent\n  textLine: \"Linie {} fährt in {} Minuten\"\n  #textLine: \"Line {} departs in {} minutes\"\n  Error: <p>Ich habe nicht richtig verstanden was du meinst</p>\n  sensor: sensor.nach_bruckenkopf\n  global_dependencies:\n    - globals\n```\n\n## remindMeOfXWhenZoneIntent\n\nCURRENTLY DOES NOT WORK BECAUSE ALEXA DOES NOT ALLOW INTENTS CONTAINING REMINDERS\n\nWill send you a reminder over the notification service when you leave/enter a zone.\n\n``Alexa tell Home Assistant to remind me of <> when entering work``\n\n```yaml\nremindMeOfXWhenZoneIntent:\n  module: remindMeOfXWhenZoneIntent\n  class: RemindMeOfXWhenZoneIntent\n  device_tracker: person.kevin\n  notify_name: group_notifications\n  Error: <p>Es ist etwas schief gegangen</p>\n  textLine: \"Okay ich erinnere dich an {{reminder}} wenn du {{zone}} \"\n  textEnter: \"erreichst\"\n  textLeave: \"verlässt\"\n  remindMessageSkeleton: \"Ich sollte dich erinnern an \"\n  zoneMapping:\n    arbeit: work\n    hause: home\n    elmo: elmo\n  dependencies:\n    - Notifier\n  global_dependencies:\n    - globals\n```\n"
  },
  {
    "path": "alexa/alexa.yaml",
    "content": "alexa_api: # appdaemon skill\n  module: alexa_api\n  class: alexa_api\n  cardTitle: Your Card Title\n  devices:\n    unknownDevice: ein unbekannte platze\n  logfile: /conf/logs/alexaAD.log\n  responselogfile: /conf/logs/alexaResponse.log\n  language: DE\n  temperatureUnit: \"Grad\"\n  logging: \"True\"\n  launchRequest:\n    - <p><say-as interpret-as='interjection'>bonjour</say-as>, bist du wieder in {{device}}?</p> <p>wie kann ich dir helfen?</p>\n    - <p><say-as interpret-as='interjection'>aloha</say-as>, wie gehts es in {{device}}?</p> <p>kann ich etwas fuer dich tun?</p>\n    - <p><say-as interpret-as='interjection'>hey</say-as>, du bist wieder in {{device}}.</p> <p>was kan ich machen?</p>\n    - <p><say-as interpret-as='interjection'>moin</say-as>. Schoen das du wieder im {{device}} bist,</p> <p>was ist dein wunsch?</p>\n    - <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>\n  nextConversationQuestion:\n    - <p>Was kann ich als naechstes fuer dich tun?</p>\n    - <p>Mit was willst du das ich dir helfe? </p>\n  intentEnd:\n    - <p>Kann ich noch etwas fuer dich machen?</p>\n    - <p>Willst du das ich noch etwas anderes mache?</p>\n    - <p>Kann ich dir mit noch etwas helfen?</p>\n    - <p>Gibt es noch etwas was ich fuer dich tun kann?</p>\n  conversationEnd:\n    - Bis zum naechsten mal\n    - <say-as interpret-as='interjection'>arrivederci</say-as>\n    - <say-as interpret-as='interjection'>bis bald</say-as>\n    - <say-as interpret-as='interjection'>bis dann</say-as>\n    - <say-as interpret-as='interjection'>mach's gut</say-as>\n    - <say-as interpret-as='interjection'>tschoe</say-as>\n    - <say-as interpret-as='interjection'>viel glueck</say-as>\n    - Schoen mit dir gesprochen zu haben\n  responseError:\n    - Es tut mir leit aber etwas ist falsch gegangen\n    - Leider meldet home assistant einen Fehler "
  },
  {
    "path": "alexa/alexa_api.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport random\nimport datetime\n\n\nclass alexa_api(hass.Hass):\n    def initialize(self):\n        self.register_endpoint(self.api_call)\n        self.tempCardContent = \"\"\n\n    def api_call(self, data):\n        self.my_alexa_interpret_data(data)\n        response = \"\"\n        dialogDelegate = False\n        endsession = False\n        speech = None\n        card = None\n        logtext = \"\"\n        if self.dialog == None:\n            if self.requesttype == \"LaunchRequest\":\n                speech = self.random_arg(self.args[\"launchRequest\"])\n                endSession = False\n                dialogDelegate = False\n                card = None\n                self.alexalog(\"Launch request (activate command)\", 100, \"*\")\n            elif self.requesttype == \"IntentRequest\":\n                speech = None\n                endSession = False\n                dialogDelegate = True\n                card = None\n                self.alexalog(\"start dialog\", 100, \"*\")\n            elif self.requesttype == \"SessionEndedRequest\":\n                speech = None\n                endSession = False\n                dialogDelegate = False\n                card = None\n                self.alexalog(\"The session has ended\", 100, \"*\")\n            else:\n                self.alexalog(\"Strange state\", 100, \"*\")\n        elif self.dialog == \"STARTED\":\n            ############################################\n            # conversation started, just response dialogDelegate\n            ############################################\n            speech = None\n            endSession = False\n            dialogDelegate = True\n            card = None\n            self.alexalog(\"dialog has been started\", 100, \"*\")\n        elif self.dialog == \"IN_PROGRESS\":\n            speech = None\n            endSession = False\n            dialogDelegate = True\n            card = None\n            self.alexalog(\"dialog is in progress\", 100, \"*\")\n        elif self.dialog == \"COMPLETED\":\n            try:\n                intentResponse = self.getIntentResponse()\n            except:\n                intentResponse = self.random_arg(self.args[\"responseError\"])\n            if intentResponse == \"stop\":\n                ############################################\n                # user used stop intent, stop conversation\n                ############################################\n                speech = self.random_arg(self.args[\"conversationEnd\"])\n                endSession = True\n                dialogDelegate = False\n                card = True\n                cardContent = self.tempCardContent\n                self.tempCardContent = \"\"\n                self.alexalog(\n                    \"dialog has been completed, Dialog stopped by user\", 100, \"*\"\n                )\n            elif intentResponse == \"next\":\n                ############################################\n                # user just responded yes, so just a question\n                ############################################\n                speech = self.random_arg(self.args[\"nextConversationQuestion\"])\n                endSession = False\n                dialogDelegate = False\n                card = None\n                self.tempCardContent = self.tempCardContent + self.intentname + \"\\n\"\n                self.alexalog(\n                    \"dialog has been completed, User just responded yes\", 100, \"*\"\n                )\n            else:\n                ############################################\n                # Send the response from the Intent + question for next action\n                ############################################\n                speech = intentResponse\n                endSession = True\n                dialogDelegate = False\n                card = None\n                self.tempCardContent = self.tempCardContent + self.intentname + \"\\n\"\n                self.alexalog(\"dialog has been completed, Response \" + speech, 100, \"*\")\n        if speech != None:\n            speech = self.cleanup_text(speech)\n        if card:\n            response = self.my_alexa_response(\n                EndSession=endSession,\n                DialogDelegate=dialogDelegate,\n                speech=speech,\n                card=True,\n                title=self.args[\"cardTitle\"],\n                content=cardContent,\n            )\n            self.alexalog(\" \", 100, \"X\")\n            self.alexalog(\" \", 100, \"X\")\n            self.alexalog(\" \", 100, \"X\")\n        else:\n            response = self.my_alexa_response(\n                EndSession=endSession,\n                DialogDelegate=dialogDelegate,\n                speech=speech,\n                card=None,\n            )\n        if speech != None:\n            if self.intentname == None:\n                self.alexaresponselog(\"No Intent : \" + speech)\n            else:\n                self.alexaresponselog(self.intentname + \" : \" + speech)\n        return response, 200\n\n    def my_alexa_interpret_data(self, data):\n        ############################################\n        # create vars from the data\n        ############################################\n        self.allslots = {}\n        self.slots = {}\n        self.dialog = self.my_alexa_dialog_state(data)\n        self.requesttype = self.my_alexa_request_type(data)\n        self.alexa_error = self.my_alexa_error(data)\n        self.intentname = self.my_alexa_intent_name(data)\n        if self.intentname != None:\n            if \".\" in self.intentname:\n                splitintent = self.intentname.split(\".\")\n                self.intentname = splitintent[1]\n        device = data[\"context\"][\"System\"][\"device\"][\"deviceId\"]\n        self.devicename = self.args[\"devices\"][\"unknownDevice\"]\n        for devicename, deviceid in self.args[\"devices\"].items():\n            if deviceid == device:\n                self.devicename = devicename\n        if self.devicename == self.args[\"devices\"][\"unknownDevice\"]:\n            self.log(device)\n        ############################################\n        # get slots out of the data\n        ############################################\n        if (\n            \"request\" in data\n            and \"intent\" in data[\"request\"]\n            and \"slots\" in data[\"request\"][\"intent\"]\n        ):\n            self.allslots = data[\"request\"][\"intent\"][\"slots\"]\n        slottext = \"slots: \"\n        for slot, slotvalue in self.allslots.items():\n            if \"value\" in slotvalue:\n                self.slots[slot] = slotvalue[\"value\"].lower()\n            else:\n                self.slots[slot] = \"\"\n        if self.intentname == \"searchYoutubeIntent\":\n            self.slots[\"search\"] = (\n                self.slots[\"searchfielda\"]\n                + self.slots[\"searchfieldb\"]\n                + self.slots[\"searchfieldc\"]\n                + self.slots[\"searchfieldd\"]\n            )\n        ############################################\n        # log that data came in\n        ############################################\n        self.alexalog(\"data came in.\", 100, \"#\")\n        self.alexalog(\n            \"dialogstate = \"\n            + str(self.dialog)\n            + \" and requesttype = \"\n            + str(self.requesttype)\n        )\n        self.alexalog(\"intent = \" + str(self.intentname))\n        slottext = \"slots: \"\n        for slot, slotvalue in self.slots.items():\n            slottext = slottext + slot + \"= \" + str(slotvalue) + \", \"\n        self.alexalog(slottext)\n        self.alexalog(\"error = \" + str(self.alexa_error))\n        self.alexalog(\" \", 100, \"#\")\n\n    def my_alexa_intent_name(self, data):\n        ############################################\n        # find the intent name in the data\n        ############################################\n        if (\n            \"request\" in data\n            and \"intent\" in data[\"request\"]\n            and \"name\" in data[\"request\"][\"intent\"]\n        ):\n            return data[\"request\"][\"intent\"][\"name\"]\n        else:\n            return None\n\n    def my_alexa_dialog_state(self, data):\n        ############################################\n        # find the dialog state in the data\n        ############################################\n        if \"request\" in data and \"dialogState\" in data[\"request\"]:\n            return data[\"request\"][\"dialogState\"]\n        else:\n            return None\n\n    def my_alexa_intent(self, data):\n        ############################################\n        # find the requesttype in the data\n        ############################################\n        if \"request\" in data and \"intent\" in data[\"request\"]:\n            return data[\"request\"][\"intent\"]\n        else:\n            return None\n\n    def my_alexa_request_type(self, data):\n        ############################################\n        # find the requesttype in the data\n        ############################################\n        if \"request\" in data and \"type\" in data[\"request\"]:\n            return data[\"request\"][\"type\"]\n        else:\n            return None\n\n    def my_alexa_error(self, data):\n        ############################################\n        # get an error out of the data\n        ############################################\n        if (\n            \"request\" in data\n            and \"error\" in data[\"request\"]\n            and \"message\" in data[\"request\"][\"error\"]\n        ):\n            return data[\"request\"][\"error\"][\"message\"]\n        else:\n            return None\n\n    def my_alexa_slot_value(self, data, slot):\n        ############################################\n        # get a slot value from the data\n        ############################################\n        if (\n            \"request\" in data\n            and \"intent\" in data[\"request\"]\n            and \"slots\" in data[\"request\"][\"intent\"]\n            and slot in data[\"request\"][\"intent\"][\"slots\"]\n            and \"value\" in data[\"request\"][\"intent\"][\"slots\"][slot]\n        ):\n            return data[\"request\"][\"intent\"][\"slots\"][slot][\"value\"]\n        else:\n            return \"\"\n\n    def my_alexa_response(\n        self,\n        EndSession=False,\n        DialogDelegate=False,\n        speech=None,\n        card=None,\n        title=None,\n        content=None,\n    ):\n        ############################################\n        # put the speechfield from the response toghether\n        ############################################\n        response = {\"shouldEndSession\": EndSession}\n        if DialogDelegate:\n            response[\"directives\"] = [\n                {\"type\": \"Dialog.Delegate\", \"updatedIntent\": None}\n            ]\n        if speech is not None:\n            response[\"outputSpeech\"] = {\n                \"type\": \"SSML\",\n                \"ssml\": \"<speak>\" + speech + \"</speak>\",\n            }\n        if card is not None:\n            response[\"card\"] = {\"type\": \"Simple\", \"title\": title, \"content\": content}\n\n        speech = {\"version\": \"1.0\", \"response\": response, \"sessionAttributes\": {}}\n        return speech\n\n    def random_arg(self, argName):\n        ############################################\n        # pick a random text from a list\n        ############################################\n        if isinstance(argName, list):\n            text = random.choice(argName)\n        else:\n            text = argname\n        return text\n\n    def floatToStr(self, myfloat):\n        ############################################\n        # replace . with , for better speech\n        ############################################\n        floatstr = str(myfloat)\n        floatstr = floatstr.replace(\".\", \",\")\n        return floatstr\n\n    def cleanup_text(self, text):\n        ############################################\n        # replace some text like temperary slots with its value\n        ############################################\n        # self.log(text)\n        for slotname, slotvalue in self.slots.items():\n            text = text.replace(\"{{\" + slotname + \"}}\", slotvalue)\n        text = text.replace(\"{{device}}\", self.devicename)\n\n        text = text.replace(\"_\", \" \")\n        text = text.replace(\"...\", \"<break time='2s'/>\")\n        return text\n\n    def alexalog(self, logtext, repeat=0, surrounding=\"\"):\n        ############################################\n        # put an entry in the alexa log\n        ############################################\n        if self.args[\"logging\"] == \"true\" or self.args[\"logging\"] == \"True\":\n            runtime = datetime.datetime.now().strftime(\"%d-%m-%Y %H:%M:%S.%f\")\n            if repeat > 0:\n                surrounding = surrounding * repeat\n            try:\n                log = open(self.args[\"logfile\"], \"a\")\n                if logtext == \" \":\n                    log.write(runtime + \";  \" + surrounding + \"\\n\")\n                else:\n                    if surrounding != \"\":\n                        log.write(runtime + \";  \" + surrounding + \"\\n\")\n                    log.write(runtime + \";  \" + logtext + \"\\n\")\n                    if surrounding != \"\":\n                        log.write(runtime + \";  \" + surrounding + \"\\n\")\n                log.close()\n            except:\n                self.log(\"ALEXA LOGFILE CANT BE WRITTEN!!\")\n\n    def alexaresponselog(self, logtext):\n        ############################################\n        # put an entry in the alexa responses log\n        ############################################\n        if self.args[\"logging\"] == \"true\" or self.args[\"logging\"] == \"True\":\n            runtime = datetime.datetime.now().strftime(\"%d-%m-%Y %H:%M:%S.%f\")\n            try:\n                log = open(self.args[\"responselogfile\"], \"a\")\n                log.write(runtime + \";  \" + logtext + \"\\n\")\n                log.close()\n            except:\n                self.log(\"ALEXA RESPONSELOGFILE CANT BE WRITTEN!!\")\n\n    def getIntentResponse(self):\n        ############################################\n        # perform the intent end get the response back\n        # from the intent apps\n        ############################################\n        if self.intentname == \"StopIntent\":\n            return \"stop\"\n        if self.intentname == \"yesIntent\":\n            return \"next\"\n        alexa = self.get_app(self.intentname)\n        return alexa.getIntentResponse(self.slots, self.devicename)\n"
  },
  {
    "path": "alexa/custom_skill.json",
    "content": "{\n    \"interactionModel\": {\n        \"languageModel\": {\n            \"invocationName\": \"home assistant\",\n            \"intents\": [\n                {\n                    \"name\": \"AMAZON.CancelIntent\",\n                    \"samples\": []\n                },\n                {\n                    \"name\": \"AMAZON.HelpIntent\",\n                    \"samples\": []\n                },\n                {\n                    \"name\": \"AMAZON.StopIntent\",\n                    \"samples\": []\n                },\n                {\n                    \"name\": \"temperatureStateIntent\",\n                    \"slots\": [\n                        {\n                            \"name\": \"location\",\n                            \"type\": \"room\",\n                            \"samples\": [\n                                \"Vom Schlafzimmer\",\n                                \"Vom Wohnzimmer\",\n                                \"Wohnzimmer\"\n                            ]\n                        }\n                    ],\n                    \"samples\": [\n                        \"wie viel grad ist es in {location}\",\n                        \"was ist die temperatur in {location}\"\n                    ]\n                },\n                {\n                    \"name\": \"lightStateIntent\",\n                    \"slots\": [\n                        {\n                            \"name\": \"device\",\n                            \"type\": \"device\",\n                            \"samples\": [\n                                \"Von {device}\",\n                                \"{device}\"\n                            ]\n                        }\n                    ],\n                    \"samples\": [\n                        \"Ist {device} aus\",\n                        \"Ist {device} an\",\n                        \"Was ist der Status von {device}\"\n                    ]\n                },\n                {\n                    \"name\": \"turnEntityOffInXIntent\",\n                    \"slots\": [\n                        {\n                            \"name\": \"device\",\n                            \"type\": \"device\",\n                            \"samples\": [\n                                \"die {device}\",\n                                \"der {device}\",\n                                \"Das {device}\",\n                                \"{device}\"\n                            ]\n                        },\n                        {\n                            \"name\": \"duration\",\n                            \"type\": \"AMAZON.DURATION\",\n                            \"samples\": [\n                                \"In {duration}\"\n                            ]\n                        }\n                    ],\n                    \"samples\": [\n                        \"In {duration} {device} ausschalten\",\n                        \"{device} in {duration} ausschalten\",\n                        \"er soll {device} in {duration} ausschalten\",\n                        \"es soll {device} in {duration} ausschalten\",\n                        \"Schalte {device} in {duration} aus\"\n                    ]\n                },\n                {\n                    \"name\": \"windowsOpenIntent\",\n                    \"slots\": [],\n                    \"samples\": [\n                        \"ob alles zu ist\",\n                        \"ob noch etwas offen ist\",\n                        \"ist noch etwas offen\",\n                        \"ist alles zu\",\n                        \"sind alle türen zu\",\n                        \"ob noch türen offen sind\",\n                        \"ob alle türen zu sind\",\n                        \"ob alle fenster zu sind\",\n                        \"ob noch Fenster offen sind\",\n                        \"ob die Fenster zu sind\",\n                        \"Sind die Fenster zu\",\n                        \"Sind alle Fenster zu\",\n                        \"Welche Fenster sind offen\"\n                    ]\n                },\n                {\n                    \"name\": \"AMAZON.NavigateHomeIntent\",\n                    \"samples\": []\n                }\n            ],\n            \"types\": [\n                {\n                    \"name\": \"room\",\n                    \"values\": [\n                        {\n                            \"name\": {\n                                \"value\": \"oben\"\n                            }\n                        },\n                        {\n                            \"name\": {\n                                \"value\": \"küche\"\n                            }\n                        },\n                        {\n                            \"name\": {\n                                \"value\": \"schlafzimmer\"\n                            }\n                        },\n                        {\n                            \"name\": {\n                                \"value\": \"wohnzimmer\"\n                            }\n                        }\n                    ]\n                },\n                {\n                    \"name\": \"device\",\n                    \"values\": [\n                        {\n                            \"name\": {\n                                \"value\": \"Deckenlampe\"\n                            }\n                        },\n                        {\n                            \"name\": {\n                                \"value\": \"Ventilator\"\n                            }\n                        },\n                        {\n                            \"name\": {\n                                \"value\": \"Wohnzimmer Temperatur\"\n                            }\n                        }\n                    ]\n                }\n            ]\n        },\n        \"dialog\": {\n            \"intents\": [\n                {\n                    \"name\": \"temperatureStateIntent\",\n                    \"confirmationRequired\": false,\n                    \"prompts\": {},\n                    \"slots\": [\n                        {\n                            \"name\": \"location\",\n                            \"type\": \"room\",\n                            \"confirmationRequired\": false,\n                            \"elicitationRequired\": true,\n                            \"prompts\": {\n                                \"elicitation\": \"Elicit.Slot.748362018084.1289245209680\"\n                            }\n                        }\n                    ]\n                },\n                {\n                    \"name\": \"lightStateIntent\",\n                    \"confirmationRequired\": false,\n                    \"prompts\": {},\n                    \"slots\": [\n                        {\n                            \"name\": \"device\",\n                            \"type\": \"device\",\n                            \"confirmationRequired\": false,\n                            \"elicitationRequired\": true,\n                            \"prompts\": {\n                                \"elicitation\": \"Elicit.Slot.314395877784.987687730608\"\n                            }\n                        }\n                    ]\n                },\n                {\n                    \"name\": \"turnEntityOffInXIntent\",\n                    \"confirmationRequired\": false,\n                    \"prompts\": {},\n                    \"slots\": [\n                        {\n                            \"name\": \"device\",\n                            \"type\": \"device\",\n                            \"confirmationRequired\": false,\n                            \"elicitationRequired\": true,\n                            \"prompts\": {\n                                \"elicitation\": \"Elicit.Slot.208682348144.1260609041167\"\n                            }\n                        },\n                        {\n                            \"name\": \"duration\",\n                            \"type\": \"AMAZON.DURATION\",\n                            \"confirmationRequired\": false,\n                            \"elicitationRequired\": true,\n                            \"prompts\": {\n                                \"elicitation\": \"Elicit.Slot.208682348144.1375070683830\"\n                            }\n                        }\n                    ]\n                }\n            ]\n        },\n        \"prompts\": [\n            {\n                \"id\": \"Elicit.Slot.748362018084.1289245209680\",\n                \"variations\": [\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Von welchem Raum willst du die Temperatur wissen?\"\n                    }\n                ]\n            },\n            {\n                \"id\": \"Elicit.Slot.314395877784.987687730608\",\n                \"variations\": [\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Von was willst du den Status wissen?\"\n                    },\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Entschuldigung. Welches Gerät meintest du?\"\n                    }\n                ]\n            },\n            {\n                \"id\": \"Elicit.Slot.208682348144.1260609041167\",\n                \"variations\": [\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Entschuldigung. Welches Gerät soll ich ausschalten?\"\n                    },\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Welches Gerät meinst du?\"\n                    }\n                ]\n            },\n            {\n                \"id\": \"Elicit.Slot.208682348144.1375070683830\",\n                \"variations\": [\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Entschuldigung. Wann soll ich {device} ausschalten?\"\n                    },\n                    {\n                        \"type\": \"PlainText\",\n                        \"value\": \"Wann soll ich {device} ausschalten?\"\n                    }\n                ]\n            }\n        ]\n    }\n}"
  },
  {
    "path": "alexa/lightState/lightStateIntent-utterances_DE.csv",
    "content": "﻿Ist {device} aus\nIst {device} an\nWas ist der Status von {device}"
  },
  {
    "path": "alexa/lightState/lightStateIntent-utterances_EN.csv",
    "content": "Is {device} off\nIs {device} on\nWhat is the state of {device}\n"
  },
  {
    "path": "alexa/lightState/lightStateIntent.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport random\n\n\nclass lightStateIntent(hass.Hass):\n    def initialize(self):\n        return\n\n    def getIntentResponse(self, slots, devicename):\n        ############################################\n        # an Intent to give back the state from a light.\n        # but it also can be any other kind of entity\n        ############################################\n        try:\n            entityname = self.args[\"entities\"][slots[\"device\"]]\n            state = self.get_state(entityname)\n            if isinstance(state, float):\n                if self.args[\"language\"] == \"DE\":\n                    state = self.floatToStr(state)\n                else:\n                    state = str(state)\n            elif isinstance(state, str):\n                state = state\n            else:\n                state = self.args[\"unreadableState\"]\n            text = self.random_arg(self.args[\"textLine\"]) + state\n        except:\n            text = self.random_arg(self.args[\"Error\"])\n        return text\n\n    def floatToStr(self, myfloat):\n        ############################################\n        # replace . with , for better speech\n        ############################################\n        floatstr = str(myfloat)\n        floatstr = floatstr.replace(\".\", \",\")\n        return floatstr\n"
  },
  {
    "path": "alexa/lightState/lightStateIntent.yaml",
    "content": "lightStateIntent:\n  module: lightStateIntent\n  class: lightStateIntent\n  language: DE\n  temperatureUnit: \"Grad\"\n  textLine:\n    - \"Der Status von {{device}} ist \"\n    - \"{{device}} ist in diesem Moment \"\n    - \"{{device}} ist \"\n  Error: <p>Ich habe nicht richtig verstanden welches geraet oder sensor du wissen wolltest</p>\n  unreadableState: \"unlesbar fuer mich\"\n  entities:\n    wohnzimmer temperatur: sensor.large_lamp_temperature\n    deckenlampe: light.livingroom_yeelight\n    ventilator: switch.ventilator"
  },
  {
    "path": "alexa/listService/listService.py",
    "content": "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# switchable: dict of switchable entities\n# temperature: dict of temperature sensors\n# door: dict of reed sensors showing if the door is completely open\n# door_tilted: dict of reed sensors showing if a door is partially/leaning open\n# window: dict of reed sensors showing if a window is open\n#\n#\n# Release Notes\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass ListService(hass.Hass):\n    def initialize(self):\n        return\n\n    def getSwitchable(self):\n        return self.args[\"switchable\"]\n\n    def getTemperature(self):\n        return self.args[\"temperature\"]\n\n    def getDoor(self):\n        return self.args[\"door\"]\n\n    def getWindow(self):\n        return self.args[\"window\"]\n\n    def getDoorTilted(self):\n        return self.args[\"door_tilted\"]\n"
  },
  {
    "path": "alexa/listService/listService.yaml",
    "content": "listService:\n  module: listService\n  class: ListService\n  switchable:\n    große lampe: switch.large_lamp\n    kleine lampe: switch.small_lamp\n    deckenlampe: light.livingroom_yeelight\n    ventilator: switch.ventilator\n    großer ventilator: switch.large_ventilator\n    markise: switch.markise\n    fernseher: switch.tv\n    schlafzimmer receiver: switch.bedroom_receiver\n    snowboard: switch.snowboard\n    receiver: media_player.denon_avr_x1300w\n    wohnzimmer: group.livingroom\n    wohnung: group.all\n    schlafzimmer lampe: light.bedroom_yeelight\n    schlafzimmer: light.bedroom_yeelight\n    bar: light.bar_table\n    bar licht: light.bar_table\n    treppe unten: light.stairs_lower_yeelight\n    treppe oben: light.treppe_oben\n    leselampe: light.reading_lamp_yeelight\n    lese lampe: light.reading_lamp_yeelight\n    runde lampe: light.reading_lamp_yeelight\n    garderobe: light.lobby_yeelight\n    garderoben licht: light.lobby_yeelight\n    garderobenlicht: light.lobby_yeelight\n    flur: switch.lobby\n    flur licht: switch.lobby\n    bad: light.lower_bathroom_yeelight\n    bad licht: light.lower_bathroom_yeelight\n    treppe: group.stair_lights\n    treppen licht: group.stair_lights\n    treppenlicht: group.stair_lights\n\n  temperature:\n    große lampe thermostat: sensor.large_lamp_temperature\n    kleine lampe thermostat: sensor.small_lamp_temperature\n    ventilator thermostat: sensor.ventilator_temperature\n    großer ventilator thermostat: sensor.large_ventilator_temperature\n  door:\n    wohnungstuer: binary_sensor.contact_door\n    terassentuer schlafzimmer: binary_sensor.contact_bedroom_door\n    terassentuer:  binary_sensor.contact_terrace_door\n    terassentuer arbeitszimmer: binary_sensor.contact_studyroom_door\n  window:\n    kuechenfenster:  binary_sensor.contact_kitchen_window\n    badfenster oben: binary_sensor.contact_upper_bathroom_window\n    badfenster unten: binary_sensor.contact_lower_bathroom_window\n    gaestezimmerfenster: binary_sensor.contact_guestroom_window\n  door_tilted:\n    kuechenfenster gekippt: binary_sensor.contact_kitchen_window_tilted\n    terassentuer arbeitszimmer gekippt: binary_sensor.contact_studyroom_door_tilted\n    terassentuer schlafzimmer gekippt: binary_sensor.contact_bedroom_door_tilted\n    terrassentuer gekippt: binary_sensor.contact_terrace_door_tilted\n    badfenster unten gekippt: binary_sensor.contact_lower_bathroom_window_tilted\n    badfenster oben gekippt: binary_sensor.contact_upper_bathroom_window_tilted\n    gaestezimmerfenster gekippt: binary_sensor.contact_guestroom_window_tilted\n\n"
  },
  {
    "path": "alexa/nextBus/nextBusIntent.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport random\n\n\nclass nextBusIntent(hass.Hass):\n    def initialize(self):\n        self.sensor = self.args[\"sensor\"]\n        self.textLine = self.args[\"textLine\"]\n        self.error = self.args[\"error\"]\n\n    def getIntentResponse(self, slots, devicename):\n        ############################################\n        # give next bus departure\n        ############################################\n        try:\n            state = self.get_state(self.sensor, attribute=\"all\")\n            line = state[\"attributes\"][\"line\"]\n            minutes = state[\"attributes\"][\"minutes\"]\n            text = self.textLine.format(line, minutes)\n        except:\n            text = self.error\n        return text\n"
  },
  {
    "path": "alexa/nextBus/nextBusIntent.yaml",
    "content": "nextBusIntent:\n  module: nextBusIntent\n  class: nextBusIntent\n  textLine: \"Linie {} fährt in {} Minuten\"\n  #textLine: \"Line {} departs in {} minutes\"\n  error: <p>Ich habe nicht richtig verstanden was du meinst</p>\n  sensor: sensor.nach_bruckenkopf"
  },
  {
    "path": "alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n__ZONE_ACTION_ENTER__ = \"kommen\"\n__ZONE_ACTION_LEAVE__ = \"verlassen\"\n\n\nclass RemindMeOfXWhenZoneIntent(hass.Hass):\n    def initialize(self):\n        self.timer_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.device_tracker = self.args[\"device_tracker\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.remindMessageSkeleton = self.args[\"remindMessageSkeleton\"]\n\n        self.notifier = self.get_app(\"Notifier\")\n        return\n\n    def getIntentResponse(self, slots, devicename):\n        ############################################\n        # an Intent to give back the state from a light.\n        # but it also can be any other kind of entity\n        ############################################\n        try:\n            # get zone_name for friendly name used when talking to alexa\n            zone_name = None\n            for key, value in self.args[\"zoneMapping\"].items():\n                if key == slots[\"zone\"].lower():\n                    zone_name = value\n            # listen to a state change of the zone\n            if zone_name == None:\n                raise Exception(\n                    \"Could not find zonemapping for: {}\".format(slots[\"zone\"].lower())\n                )\n            else:\n                self.listen_state_handle_list.append(\n                    self.listen_state(\n                        self.remind_callback,\n                        self.device_tracker,\n                        zone=slots[\"zone\"],\n                        zoneAction=slots[\"zoneAction\"],\n                        reminder=slots[\"reminder\"],\n                    )\n                )\n                # set correct zoneAction response\n                if slots[\"zoneAction\"] == __ZONE_ACTION_ENTER__:\n                    text = self.args[\"textLine\"] + self.args[\"textEnter\"]\n                else:\n                    text = self.args[\"textLine\"] + self.args[\"textLeave\"]\n        except Exception as e:\n            self.log(\"Exception: {}\".format(e))\n            self.log(\"slots: {}\".format(slots))\n            text = self.random_arg(self.args[\"Error\"])\n        return text\n\n    def remind_callback(self, entity, attribute, old, new, kwargs):\n        if kwargs[\"zoneAction\"] == __ZONE_ACTION_ENTER__:\n            if new != old and new == kwargs[\"zone\"]:\n                self.log(\"Notifying\")\n                self.notifier.notify(\n                    self.notify_name,\n                    self.remindMessageSkeleton + kwargs[\"reminder\"],\n                    useAlexa=False,\n                )\n        elif kwargs[\"zoneAction\"] == __ZONE_ACTION_LEAVE__:\n            if new != old and old == kwargs[\"zone\"]:\n                self.log(\"Notifying\")\n                self.notifier.notify(\n                    self.notify_name,\n                    self.remindMessageSkeleton + kwargs[\"reminder\"],\n                    useAlexa=False,\n                )\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.yaml",
    "content": "remindMeOfXWhenZoneIntent:\n  module: remindMeOfXWhenZoneIntent\n  class: RemindMeOfXWhenZoneIntent\n  device_tracker: person.kevin\n  notify_name: group_notifications\n  Error: <p>Es ist etwas schief gegangen</p>\n  textLine: \"Okay ich erinnere dich an {{reminder}} wenn du {{zone}} \"\n  textEnter: \"erreichst\"\n  textLeave: \"verlässt\"\n  remindMessageSkeleton: \"Ich sollte dich erinnern an \"\n  zoneMapping:\n    arbeit: work\n    hause: home\n    elmo: elmo\n  dependencies: \n    - Notifier"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent-utterances_DE.csv",
    "content": "﻿wie viel grad ist es in {location}\nwas ist die temperatur in {location}"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent-utterances_EN.csv",
    "content": "how many degree is it in {location}\nwhat is the temperture in {location}\n"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport random\n\n\nclass temperatureStateIntent(hass.Hass):\n    def initialize(self):\n        return\n\n    def getIntentResponse(self, slots, devicename):\n        ############################################\n        # give temperature for a list of temperature sensors\n        ############################################\n        try:\n            if self.args[\"language\"] == \"DE\":\n                temp = (\n                    self.floatToStr(\n                        self.get_state(self.args[\"sensors\"][slots[\"location\"]])\n                    )\n                    + self.args[\"temperatureUnit\"]\n                )\n            else:\n                temp = (\n                    str(self.get_state(self.args[\"sensors\"][slots[\"location\"]]))\n                    + self.args[\"temperatureUnit\"]\n                )\n            text = self.random_arg(self.args[\"textLine\"]) + temp\n        except:\n            text = self.random_arg(self.args[\"Error\"])\n        return text\n\n    def floatToStr(self, myfloat):\n        ############################################\n        # replace . with , for better speech\n        ############################################\n        floatstr = str(myfloat)\n        floatstr = floatstr.replace(\".\", \",\")\n        return floatstr\n\n    def random_arg(self, argName):\n        ############################################\n        # pick a random text from a list\n        ############################################\n        if isinstance(argName, list):\n            text = random.choice(argName)\n        else:\n            text = argName\n        return text\n"
  },
  {
    "path": "alexa/temperatureState/temperatureStateIntent.yaml",
    "content": "temperatureStateIntent:\n  module: temperatureStateIntent\n  class: temperatureStateIntent\n  language: DE\n  temperatureUnit: \"Grad\"\n  textLine:\n    - \"Die Temperatur im {{location}} ist \"\n    - \"In diesem Moment ist es im {{location}} \"\n    - \"Im Moment ist die Temperatur \"\n  Error: <p>Ich habe nicht richtig verstanden welche Temperatur du wissen wolltest</p>\n  sensors:\n    wohnzimmer: sensor.large_lamp_temperature"
  },
  {
    "path": "alexa/turnEntityOffInX/requirements.txt",
    "content": "isodate"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_DE.csv",
    "content": "﻿In {duration} {device} ausschalten\n{device} in {duration} ausschalten\ner soll {device} in {duration} ausschalten\nes soll {device} in {duration} ausschalten\nSchalte {device} in {duration} aus"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_EN.csv",
    "content": "In {duration} turn off {device}\nturn off {device} in {duration}\nit should turn off {device} in {duration}\n"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport random\nimport isodate\nimport datetime\n\n\nclass TurnEntityOffInXIntent(hass.Hass):\n    def initialize(self):\n        self.timer_handle_list = []\n        self.listService = self.get_app(\"listService\")\n        return\n\n    def getIntentResponse(self, slots, devicename):\n        ############################################\n        # an Intent to give back the state from a light.\n        # but it also can be any other kind of entity\n        ############################################\n        try:\n\n            entityname = self.listService.getSwitchable()[slots[\"device\"]]\n            # to upper because of https://github.com/gweis/isodate/issues/52\n            duration = isodate.parse_duration(slots[\"duration\"].upper())\n            self.timer_handle_list.append(\n                self.run_in(\n                    self.turn_off_callback,\n                    duration.total_seconds(),\n                    entityname=entityname,\n                )\n            )\n            minutes, seconds = divmod(duration.total_seconds(), 60)\n            minutes = int(minutes)\n            seconds = int(seconds)\n            if minutes == 0:\n                if seconds == 1:\n                    timeText = \" einer Sekunde\"\n                else:\n                    timeText = \" {} Sekunden\".format(seconds)\n            elif minutes == 1:\n                if seconds == 1:\n                    timeText = \" einer Minute und einer Sekunde\"\n                elif seconds == 0:\n                    timeText = \" einer Minute\"\n                else:\n                    timeText = \" einer Minute und {} Sekunden\".format(seconds)\n            else:\n                if seconds == 1:\n                    timeText = \" {} Minuten und einer Sekunde\".format(minutes)\n                elif seconds == 0:\n                    timeText = \" {} Minuten\".format(minutes)\n                else:\n                    timeText = \" {} Minuten und {} Sekunden\".format(minutes, seconds)\n            text = self.args[\"textLine\"] + timeText + \" ab.\"\n        except Exception as e:\n            self.log(\"Exception: {}\".format(e))\n            self.log(\"slots: {}\".format(slots))\n            text = self.random_arg(self.args[\"Error\"])\n        return text\n\n    def turn_off_callback(self, kwargs):\n        entityname = kwargs[\"entityname\"]\n        self.log(\"Turning off {}\".format(entityname))\n        self.turn_off(entityname)\n\n    def random_arg(self, argName):\n        ############################################\n        # pick a random text from a list\n        ############################################\n        if isinstance(argName, list):\n            text = random.choice(argName)\n        else:\n            text = argName\n        return text\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "alexa/turnEntityOffInX/turnEntityOffInXIntent.yaml",
    "content": "turnEntityOffInXIntent:\n  module: turnEntityOffInXIntent\n  class: TurnEntityOffInXIntent\n  language: DE\n  textLine: \"Okay Homeassistant schaltet {{device}} in\"\n  Error: <p>Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?</p>\n  unreadableState: \"unlesbar fuer mich\"\n  dependencies:\n   - listService"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent-utterances_DE.csv",
    "content": "﻿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\nob alle türen zu sind\nob alle fenster zu sind\nob noch Fenster offen sind\nob die Fenster zu sind\nSind die Fenster zu\nSind alle Fenster zu\nWelche Fenster sind offen"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent-utterances_EN.csv",
    "content": "whether everything is closed\nwhether something is still open\nis something still open\nis everthing closed\nare all doors closed\nwhether some doors are still open\nwhether all doors are closed\nwhether all windows are closed\nwhether some windows are open\nwhether the windows are closed\nAre the Windows closed\nAre all windows closed\nWhich Windows are open\n"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport random\nimport isodate\nimport datetime\n\n\nclass WindowsOpenIntent(hass.Hass):\n    def initialize(self):\n        self.listService = self.get_app(\"listService\")\n        return\n\n    def getIntentResponse(self, slots, devicename):\n        ############################################\n        # an Intent to give back the state of windows\n        ############################################\n        try:\n            windows_dict = self.listService.getWindow()\n            doors_dict = self.listService.getDoor()\n            doors_tilted_dict = self.listService.getDoorTilted()\n            window_open_list = []\n            door_open_list = []\n            door_tilted_list = []\n            # iterate over all window entities\n            for key, value in windows_dict.items():\n                # if a window is open (\"on\") add it to the window_open_list\n                if self.get_state(value) == \"on\":\n                    window_open_list.append(value)\n            # iterate over all door entities\n            for key, value in doors_dict.items():\n                # if a door is open (\"on\") add it to the door_open_list\n                if self.get_state(value) == \"on\":\n                    door_open_list.append(value)\n            # iterate over all door_tilted entities\n            for key, value in doors_tilted_dict.items():\n                # if a door is tilted (\"on\") add it to the door_tilted_list\n                if self.get_state(value) == \"on\":\n                    door_tilted_list.append(value)\n\n            text = \"\"\n            # add open windows to response\n            if len(window_open_list) > 0:\n                if text != \"\":\n                    text = text + ' <break strength=\"weak\"/>'\n                text = text + self.args[\"textLineWindowOpen\"]\n                for entity in window_open_list:\n                    text = (\n                        text + ' <break strength=\"weak\"/>' + self.friendly_name(entity)\n                    )\n            # add open doors to response\n            if len(door_open_list) > 0:\n                if text != \"\":\n                    text = text + ' <break strength=\"weak\"/>'\n                text = text + self.args[\"textLineDoorOpen\"]\n                for entity in door_open_list:\n                    text = (\n                        text + ' <break strength=\"weak\"/>' + self.friendly_name(entity)\n                    )\n            # add tilted doors to reponse\n            if len(door_tilted_list) > 0:\n                if text != \"\":\n                    text = text + ' <break strength=\"weak\"/>'\n                text = text + self.args[\"textLineDoorTilted\"]\n                for entity in door_tilted_list:\n                    friendly_name = self.friendly_name(entity)\n                    # remove \"gekippt\" (german for tilted) from the friendly name\n                    friendly_name = friendly_name.replace(\" gekippt\", \"\")\n                    friendly_name = friendly_name.replace(\" Gekippt\", \"\")\n                    text = text + ' <break strength=\"weak\"/>' + friendly_name\n            # if all closed response\n            if text == \"\":\n                text = self.args[\"textLineClosed\"]\n        except Exception as e:\n            self.log(\"Exception: {}\".format(e))\n            self.log(\"slots: {}\".format(slots))\n            text = self.random_arg(self.args[\"Error\"])\n        return text\n\n    def random_arg(self, argName):\n        ############################################\n        # pick a random text from a list\n        ############################################\n        if isinstance(argName, list):\n            text = random.choice(argName)\n        else:\n            text = argName\n        return text\n"
  },
  {
    "path": "alexa/windowsOpen/windowsOpenIntent.yaml",
    "content": "windowsOpenIntent:\n  module: windowsOpenIntent\n  class: WindowsOpenIntent\n  language: DE\n  textLineClosed: \"Alle Fenster und Türen sind zu\"\n  #textLineClosed: \"All windows and doors are closed\"\n  textLineWindowOpen: \"Folgende Fenster sind noch offen\"\n  #textLineWindowOpen: \"The following windows are stil open...\"\n  textLineDoorOpen: \"Folgende Türen sind noch offen\"\n  #textLineDoorOpen: \"The following doors are still open\"\n  textLineDoorTilted: \"Die folgenden Türen sind noch gekippt\"\n  #textLineDoorTilted: \"The following doors are tilted\"\n  Error: <p>Ich habe dich nicht richtig verstanden</p>\n  unreadableState: \"unlesbar fuer mich\"\n  dependencies:\n   - listService"
  },
  {
    "path": "alexaSpeakerConnector/alexaSpeakerConnector.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# alexa_entity: the alexa media player entity. example: media_player.kevins_echo_dot_oben\n# alexa_entity_source: source to set alexa to. example: Denon AVR-X1300W\n# receiver: Receiver to turn on. example: media_player.denon_avr_x1300w\n# receiver_source: source to set receiver to. example: Bluetooth\n#\n# Release Notes\n#\n# Version 1.2.0:\n#   Introduce INITIAL_VOLUME\n#\n# Version 1.1.1:\n#   Fix WAITING_TIME\n#\n# Version 1.1:\n#   Introduce WAITING_TIME\n#\n# Version 1.0:\n#   Initial Version\n\nWAITING_TIME = 10\nINITIAL_VOLUME = 30\n\n\nclass AlexaSpeakerConnector(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.alexa_entity = self.args[\"alexa_entity\"]\n        self.alexa_entity_source = self.args[\"alexa_entity_source\"]\n        self.receiver = self.args[\"receiver\"]\n        self.receiver_source = self.args[\"receiver_source\"]\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.alexa_entity)\n        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new.lower() == \"playing\" and old.lower() != \"playing\":\n                self.log(\"{} changed to {}\".format(self.alexa_entity, new))\n                # Only trigger when the receiver is off. Otherwise its probably playing something\n                if self.get_state(self.receiver) == \"off\":\n                    self.log(\n                        \"Setting source of {} to: {}\".format(\n                            self.receiver, self.receiver_source\n                        )\n                    )\n                    self.call_service(\n                        \"media_player/select_source\",\n                        entity_id=self.receiver,\n                        source=self.receiver_source,\n                    )\n                    self.log(f\"Setting volume of {self.receiver} to: {INITIAL_VOLUME}\")\n                    self.call_service(\n                        \"media_player/volume_set\",\n                        entity_id=self.receiver,\n                        volume_level=INITIAL_VOLUME,\n                    )\n                    self.timer_handle_list.append(\n                        self.run_in(self.run_in_callback, WAITING_TIME)\n                    )\n\n    def run_in_callback(self, kwargs):\n        \"\"\"\n        Callback method to introduce a waiting time for the receiver to come 'online'\n        :return:\n        \"\"\"\n        self.log(\n            \"Setting source of {} to: {}\".format(\n                self.alexa_entity, self.alexa_entity_source\n            )\n        )\n        self.call_service(\n            \"media_player/select_source\",\n            entity_id=self.alexa_entity,\n            source=self.alexa_entity_source,\n        )\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "alexaSpeakerConnector/alexaSpeakerConnector.yaml",
    "content": "#App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers\n# alexaSpeakerConnector:\n#   module: alexaSpeakerConnector\n#   class: AlexaSpeakerConnector\n#   app_switch: input_boolean.alexa_speaker_connector\n#   alexa_entity: media_player.kevins_echo_dot_oben\n#   alexa_entity_source: Denon AVR-X1300W\n#   receiver: media_player.denon_avr_x1300w\n#   receiver_source: Bluetooth"
  },
  {
    "path": "appWatcher/appWatcher.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which listens on the log for App crashes and notifies via telegram\n#\n# Args:\n#\n# Release Notes\n#\n# Version 2.0:\n#   Updates for Appdaemon Version 4.0.3\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass AppWatcher(hass.Hass):\n    def initialize(self):\n        self.notify_name = self.args[\"notify_name\"]\n        self.notify_message = self.args[\"notify_message\"]\n        try:\n            self.exclude_apps = self.args[\"exclude_apps\"].split(\",\")\n        except KeyError:\n            self.exclude_apps = None\n\n        # App dependencies\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.handle = self.listen_log(self.log_message_callback)\n\n    def log_message_callback(self, app_name, ts, level, log_type, message, kwargs):\n        if level == \"WARNING\" or level == \"ERROR\" or level == \"CRITICAL\":\n            if app_name == \"AppDaemon\":\n                if \"Unexpected error\" in message:\n                    self.notifier.notify(\n                        self.notify_name,\n                        self.notify_message.format(message),\n                        useAlexa=False,\n                    )\n\n    def terminate(self):\n        self.cancel_listen_log(self.handle)\n"
  },
  {
    "path": "appWatcher/appWatcher.yaml",
    "content": "appWatcher:\n  module: appWatcher\n  class: AppWatcher\n  notify_name: kevin\n  notify_message: \"AppDaemon error: {}\"\n  #notify_message: \"Appdaemon reported an error: {}\"\n  dependencies:\n    - Notifier"
  },
  {
    "path": "apps.yaml",
    "content": "#################################################################\n## Global\n#################################################################\nglobal_modules:\n  - globals"
  },
  {
    "path": "buttonClicked/buttonClicked.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which toggles entities for single/double presses of xiaomi buttons\n#\n# Args:\n#\n# sensor: sensor to monitor e.g. sensor.upstairs_smoke\n# actor_single: actor to toggle on single click\n# actor_double: actor to toggle on double click\n# actor_hold: actor to dim on hold\n# Release Notes\n#\n# Version 1.2:\n#   All actors optional\n#\n# Version 1.1:\n#   added actor_hold\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass ButtonClicked(hass.Hass):\n    def initialize(self):\n        self.listen_event_handle_list = []\n        self.timer_handle_list = []\n\n        self.actor_single = self.args.get(\"actor_single\")\n        self.actor_double = self.args.get(\"actor_double\")\n        self.actor_hold = self.args.get(\"actor_hold\")\n\n        self.dimmer_timer_handle = None\n\n        self.listen_event_handle_list.append(\n            self.listen_event(self.event_detected, \"xiaomi_aqara.click\")\n        )\n\n    def event_detected(self, event_name, data, kwargs):\n        if data[\"entity_id\"] == self.args[\"sensor\"]:\n            if data[\"click_type\"] == \"single\" and self.actor_single != None:\n                self.log(\"ButtonClicked: {}\".format(data[\"entity_id\"]))\n                # Is on\n                if self.get_state(self.actor_single) == \"on\":\n                    self.log(\"Turning {} off\".format(self.actor_single))\n                    # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4\n                    if self.actor_single.startswith(\"light\"):\n                        self.call_service(\n                            \"light/turn_on\",\n                            entity_id=self.actor_single,\n                            transition=1,\n                            brightness_pct=1,\n                        )\n                        self.timer_handle_list.append(\n                            self.run_in(self.turn_off_workaround, 2)\n                        )\n                    else:\n                        self.turn_off(self.actor_single)\n                # Is off\n                if self.get_state(self.actor_single) == \"off\":\n                    self.log(\"Turning {} on\".format(self.actor_single))\n                    if self.actor_single.startswith(\"light\"):\n                        self.call_service(\n                            \"light/turn_on\",\n                            entity_id=self.actor_single,\n                            transition=1,\n                            brightness_pct=100,\n                        )\n                    else:\n                        self.turn_on(self.actor_single)\n\n            if data[\"click_type\"] == \"double\" and self.actor_double != None:\n                self.log(\"Double Button Click: {}\".format(data[\"entity_id\"]))\n                self.log(\"Toggling {}\".format(self.actor_double))\n                # Is on\n                if self.get_state(self.actor_double) == \"on\":\n                    # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4\n                    if self.actor_single.startswith(\"light\"):\n                        self.call_service(\n                            \"light/turn_on\",\n                            entity_id=self.actor_single,\n                            transition=1,\n                            brightness_pct=1,\n                        )\n                        self.timer_handle_list.append(\n                            self.run_in(self.turn_off_workaround, 2)\n                        )\n                    else:\n                        self.turn_off(self.actor_single)\n                # Is off\n                if self.get_state(self.actor_double) == \"off\":\n                    self.log(\"Turning {} on\".format(self.actor_single))\n                    if self.actor_single.startswith(\"light\"):\n                        self.call_service(\n                            \"light/turn_on\",\n                            entity_id=self.actor_single,\n                            transition=1,\n                            brightness_pct=100,\n                        )\n                    else:\n                        self.turn_on(self.actor_single)\n\n            if data[\"click_type\"] == \"long_click_press\" and self.actor_hold != None:\n                self.log(\"Long Button Click: {}\".format(data[\"entity_id\"]))\n                self.log(\"Starting Dimmer\")\n                self.dimmer_timer_handle = self.run_every(\n                    self.dimmer_callback,\n                    datetime.datetime.now(),\n                    0.5,\n                    entity_id=self.actor_hold,\n                )\n                self.timer_handle_list.append(self.dimmer_timer_handle)\n\n            if data[\"click_type\"] == \"hold\" and self.actor_hold != None:\n                self.log(\"Button Release: {}\".format(data[\"entity_id\"]))\n                self.log(\"Stopping Dimmer\")\n                if self.dimmer_timer_handle != None:\n                    self.cancel_timer(self.dimmer_timer_handle)\n\n    def dimmer_callback(self, kwargs):\n        \"\"\"Dimm the by 10% light. If it would dim above 100% start again at 10%\"\"\"\n        brightness_pct_old = (\n            int(\n                self.get_state(self.actor_hold, attribute=\"all\")[\"attributes\"][\n                    \"brightness\"\n                ]\n            )\n            / 255\n        )\n        brightness_pct_new = brightness_pct_old + 0.1\n        if brightness_pct_new > 1:\n            brightness_pct_new = 0.1\n        self.call_service(\n            \"light/turn_on\",\n            entity_id=kwargs[\"entity_id\"],\n            brightness_pct=brightness_pct_new * 100,\n        )\n\n    def turn_off_workaround(self, *kwargs):\n        self.call_service(\"light/turn_off\", entity_id=self.actor_single)\n\n    def terminate(self):\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "buttonClicked/buttonClicked.yaml",
    "content": "# App which toggles entities for single/double presses of xiaomi buttons\n# xiaomiroundButtonBedroomClicked:\n#   module: buttonClicked\n#   class: ButtonClicked\n#   sensor: binary_sensor.switch_158d0001b12a12\n#   actor_single: light.bedroom_yeelight\n#   actor_double: group.all\n#   actor_hold: light.bedroom_yeelight\n#   dependencies: \n#     - Notifier\n\n# xiaomisquareButtonLobbyClicked:\n#   module: buttonClicked\n#   class: ButtonClicked\n#   sensor: binary_sensor.switch_158d00021329bc\n#   actor_single: switch.lobby\n#   actor_double: switch.lobby\n#   dependencies: \n#     - Notifier\n\n# xiaomiroundButtonBathroomClicked:\n#   module: buttonClicked\n#   class: ButtonClicked\n#   sensor: sensor.0x00158d00012db9e5_click\n#   actor_single: light.lower_bathroom_yeelight\n#   actor_double: light.lower_bathroom_yeelight\n#   actor_hold: light.lower_bathroom_yeelight\n#   dependencies: \n#     - Notifier\n    "
  },
  {
    "path": "comingHome/comingHome.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App to Turn on Lobby Lamp when Door openes and no one is Home\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# sensor: door sensor\n# isHome: input_boolean which shows if someone is home eg input_boolean.isHome\n# actor (optional): actor to turn on. example: script.receiver_set_source_bluetooth\n# service (optional): service to call. example: media_player.volume_set\n# service_data (optional): dictionary of attributes for the service call.\n# after_sundown (optional): whether to only trigger after sundown. example: True\n# Release Notes\n#\n# Version 1.4.2:\n#   unwrap service_data\n#\n# Version 1.4.1:\n#   fix duplicate line for self.actor\n#\n# Version 1.4:\n#   Add service and service_data and make actor optional\n#\n# Version 1.3.2:\n#   Check for new != old\n#\n# Version 1.3.1:\n#   Actually implement isHome\n#\n# Version 1.3:\n#   Added app_switch\n#\n# Version 1.2:\n#   Added after_sundown\n#\n# Version 1.1:\n#   Using globals\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass ComingHome(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.sensor = self.args[\"sensor\"]\n        self.isHome = self.args[\"isHome\"]\n        self.actor = self.args.get(\"actor\")\n        self.service = self.args.get(\"service\")\n        self.service_data = self.args.get(\"service_data\")\n        self.after_sundown = self.args.get(\"after_sundown\")\n\n        self.delay = 2\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.sensor)\n        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new != old:\n                isHome_attributes = self.get_state(self.isHome, attribute=\"all\")\n                isHome_state = isHome_attributes[\"state\"]\n                last_changed = self.convert_utc(isHome_attributes[\"last_changed\"])\n                if isHome_state == \"off\" or (\n                    datetime.datetime.now(datetime.timezone.utc) - last_changed\n                    <= datetime.timedelta(seconds=self.delay)\n                ):\n                    if self.after_sundown is not None and self.after_sundown:\n                        if self.sun_down():\n                            self.turn_on_actor(self.actor, entity, new)\n                            self.my_call_service(\n                                self.service, self.service_data, entity, new\n                            )\n                    else:\n                        self.turn_on_actor(self.actor, entity, new)\n                        self.my_call_service(\n                            self.service, self.service_data, entity, new\n                        )\n\n    def turn_on_actor(self, actor, entity, new):\n        if self.actor is not None:\n            self.log(\"{} changed to {}\".format(self.friendly_name(entity), new))\n            self.turn_on(actor)\n\n    def my_call_service(self, service, service_data, entity, new):\n        if self.service is not None:\n            if self.service_data is not None:\n                self.log(\"{} changed to {}\".format(self.friendly_name(entity), new))\n                self.call_service(service, **service_data)\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "comingHome/comingHome.yaml",
    "content": "#Switch on Lobby lamp when the first person is coming home and the sun is down\n# comingHomeYeelight:\n#   module: comingHome\n#   class: ComingHome\n#   app_switch: input_boolean.coming_home_yeelight\n#   sensor: binary_sensor.contact_door\n#   isHome: input_boolean.is_home\n#   actor: switch.large_lamp\n#   after_sundown: True\n\n# comingHomeSetVolume:\n#   module: comingHome\n#   class: ComingHome\n#   app_switch: input_boolean.coming_home_set_volume\n#   sensor: binary_sensor.contact_door\n#   isHome: input_boolean.is_home\n#   service: media_player/volume_set\n#   service_data:\n#     entity_id: media_player.denon_avr_x1300w\n#     volume_level: 0.3"
  },
  {
    "path": "deconz_xiaomi_button/deconz_xiaomi_button.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz\n#\n# Args:\n#\n# id: id of the xiaomi button\n# actor_single: actor to toggle on single click\n# actor_double: actor to toggle on double click\n# actor_hold: actor to dim on hold\n#\n# Release Notes\n#\n# Version 2.0.2\n#   use else when toggling\n#\n# Version 2.0.1\n#   use elif when toggling\n#\n# Version 2.0:\n#   Removed unneeded workaround for yeelight\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass DeconzXiaomiButton(hass.Hass):\n    def initialize(self):\n        self.listen_event_handle_list = []\n        self.timer_handle_list = []\n\n        self.actor_single = self.args.get(\"actor_single\")\n        self.actor_double = self.args.get(\"actor_double\")\n        self.actor_hold = self.args.get(\"actor_hold\")\n        self.id = self.args[\"id\"]\n\n        self.dimmer_timer_handle = None\n\n        self.listen_event_handle_list.append(\n            self.listen_event(self.event_detected, \"deconz_event\")\n        )\n\n    def event_detected(self, event_name, data, kwargs):\n        if data[\"id\"] == self.id:\n            if data[\"event\"] == 1002 and self.actor_single is not None:\n                self.log(\"ButtonClicked: {}\".format(data[\"id\"]))\n                self.log(\"Toggling {}\".format(self.actor_double))\n                # Is on\n                if self.get_state(self.actor_single) == \"on\":\n                    self.log(\"Turning {} off\".format(self.actor_single))\n                    self.turn_off(self.actor_single)\n                # Is off\n                else:\n                    self.log(\"Turning {} on\".format(self.actor_single))\n                    self.turn_on(self.actor_single)\n\n            if data[\"event\"] == 1004 and self.actor_double is not None:\n                self.log(\"Double Button Click: {}\".format(data[\"id\"]))\n                self.log(\"Toggling {}\".format(self.actor_double))\n                # Is on\n                if self.get_state(self.actor_double) == \"on\":\n                    self.turn_off(self.actor_double)\n                # Is off\n                else:\n                    self.log(\"Turning {} on\".format(self.actor_double))\n                    self.turn_on(self.actor_double)\n\n            if data[\"event\"] == 1001 and self.actor_hold is not None:\n                self.log(\"Long Button Click: {}\".format(data[\"id\"]))\n                self.log(\"Starting Dimmer\")\n                self.dimmer_timer_handle = self.run_every(\n                    self.dimmer_callback,\n                    datetime.datetime.now(),\n                    0.5,\n                    entity_id=self.actor_hold,\n                )\n                self.timer_handle_list.append(self.dimmer_timer_handle)\n\n            if data[\"event\"] == 1003 and self.actor_hold is not None:\n                self.log(\"Button Release: {}\".format(data[\"id\"]))\n                self.log(\"Stopping Dimmer\")\n                if self.dimmer_timer_handle is not None:\n                    self.cancel_timer(self.dimmer_timer_handle)\n\n    def dimmer_callback(self, kwargs):\n        \"\"\"Dimm the by 10% light. If it would dim above 100% start again at 10%\"\"\"\n        brightness_pct_old = (\n            int(\n                self.get_state(self.actor_hold, attribute=\"all\")[\"attributes\"][\n                    \"brightness\"\n                ]\n            )\n            / 255\n        )\n        brightness_pct_new = brightness_pct_old + 0.1\n        if brightness_pct_new > 1:\n            brightness_pct_new = 0.1\n        self.call_service(\n            \"light/turn_on\",\n            entity_id=kwargs[\"entity_id\"],\n            brightness_pct=brightness_pct_new * 100,\n        )\n\n    def terminate(self):\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "deconz_xiaomi_button.yaml",
    "content": "DeconzXiaomiButtonBedroom:\n  module: deconz_xiaomi_button\n  class: DeconzXiaomiButton\n  id: round_button_schlafzimmer\n  actor_single: light.bedroom_yeelight\n  actor_double: group.all\n  actor_hold: light.bedroom_yeelight\n\nDeconzXiaomiButtonLobby:\n  module: deconz_xiaomi_button\n  class: DeconzXiaomiButton\n  id: flur_switch\n  actor_single: switch.lobby\n  actor_double: switch.lobby\n\nDeconzXiaomiButtonLobby:\n  module: deconz_xiaomi_button\n  class: DeconzXiaomiButton\n  id: flur_switch\n  actor_single: switch.lobby\n  actor_double: switch.lobby\n\nDeconzXiaomiButtonBathroom:\n  module: deconz_xiaomi_button\n  class: DeconzXiaomiButton\n  id: round_button_bad\n  actor_single: light.lower_bathroom_yeelight\n  actor_double: light.lower_bathroom_yeelight\n  actor_hold: light.lower_bathroom_yeelight"
  },
  {
    "path": "detectWrongState/detectWrongState.py",
    "content": "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#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# entities_on (optional): list of entities which should be on\n# entities_off (optional): list of entities which should off\n# trigger_entity: entity which triggers this app. example: input_boolean.is_home\n# trigger_state: new state of trigger_entity which triggers this app. example: \"off\"\n# after_sundown (optional): Only trigger after sundown. example: True\n# message_<LANG>: message to use in notification\n# message_off_<LANG>: message to use in notification\n# message_reed_<LANG>: message to use in notification\n# message_reed_off_<LANG>: message to use in notification\n# notify_name: who to notify. example: group_notifications\n# use_alexa: use alexa for notification. example: False\n#\n# Release Notes\n#\n# Version 2.1:\n#   More off_states to support alexa_media\n#\n# Version 2.0:\n#   Renamed to detectWrongState, notification optional\n#\n# Version 1.9:\n#   check unavailable when using get_state\n#\n# Version 1.8:\n#   check None when using get_state\n#\n# Version 1.7:\n#   check for != off instead of == on\n#\n# Version 1.6.1:\n#   fix wrong key access for attributes\n#\n# Version 1.6:\n#   garage_door to device_classes of reed sensors\n#\n# Version 1.5:\n#   distinguish normal and reed switches by device_class\n#\n# Version 1.4.1:\n#   fix wrong assignment of app_switch\n#\n# Version 1.4:\n#   Generalize to detectWrongState\n#\n# Version 1.3:\n#   use Notify App\n#\n# Version 1.2:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.1:\n#   Using globals and app_switch\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass DetectWrongState(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        try:\n            self.entities_on = self.args[\"entities_on\"].split(\",\")\n        except KeyError:\n            self.entities_on = []\n        try:\n            self.entities_off = self.args[\"entities_off\"].split(\",\")\n        except KeyError:\n            self.entities_off = []\n        self.after_sundown = self.args.get(\"after_sundown\")\n        self.trigger_entity = self.args[\"trigger_entity\"]\n        self.trigger_state = self.args[\"trigger_state\"]\n        self.message = self.args.get(\"message\")\n        self.message_off = self.args.get(\"message_off\")\n        self.message_reed = self.args.get(\"message_reed\")\n        self.message_reed_off = self.args.get(\"message_reed_off\")\n        self.notify_name = self.args.get(\"notify_name\")\n        self.use_alexa = self.args.get(\"use_alexa\")\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.trigger_entity)\n        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new == self.trigger_state:\n                if self.after_sundown is None or (\n                    (self.after_sundown and self.sun_down())\n                    or self.after_sundown is not False\n                ):\n                    self.check_entities_should_be_off()\n                    self.check_entities_should_be_on()\n\n    def check_entities_should_be_off(self):\n        off_states = [\"off\", \"unavailable\", \"paused\", \"standby\"]\n        for entity in self.entities_off:\n            state = self.get_state(entity)\n            self.log(f\"entity: {entity}\")\n            if state is not None and state not in off_states:\n                if self.is_entity_reed_contact(entity):\n                    message = self.message_reed\n                else:\n                    self.turn_off(entity)\n                    message = self.message\n                self.send_notification(message, entity)\n\n    def check_entities_should_be_on(self):\n        for entity in self.entities_on:\n            state = self.get_state(entity)\n            if state == \"off\":\n                if self.is_entity_reed_contact(entity):\n                    message = self.message_reed_off\n                else:\n                    self.turn_on(entity)\n                    message = self.message_on\n                self.send_notification(message, entity)\n\n    def is_entity_reed_contact(self, entity):\n        reed_types = [\"window\", \"door\", \"garage_door\"]\n        full_state = self.get_state(entity, attribute=\"all\")\n        if full_state is not None:\n            attributes = full_state[\"attributes\"]\n            self.log(\"full_state: {}\".format(full_state), level=\"DEBUG\")\n            if attributes.get(\"device_class\") in reed_types:\n                return True\n        return False\n\n    def send_notification(self, message, entity):\n        if message is not None:\n            formatted_message = message.format(self.friendly_name(entity))\n            self.log(formatted_message)\n            if self.notify_name is not None:\n                self.notifier.notify(\n                    self.notify_name, formatted_message, useAlexa=self.use_alexa,\n                )\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "detectWrongState/detectWrongState.yaml",
    "content": "# detectWrongStateWhenLeaving:\n#   module: detectWrongState\n#   class: DetectWrongState\n#   app_switch: input_boolean.detect_wrong_state_when_leaving\n#   entities_off: \"binary_sensor.contact_bedroom_door,\\\n#   binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\\\n#   binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\\\n#   binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\\\n#   binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\\\n#   media_player.denon_avr_x1300w,switch.large_lamp,switch.small_lamp,switch.snowboard,\\\n#   light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,light.reading_lamp_yeelight,\\\n#   light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\\\n#   switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,light.lower_bathroom_yeelight,\\\n#   media_player.kevin_s_echo_dot_unten,media_player.kevins_echo,media_player.kevins_echo_dot,\\\n#   media_player.kevins_echo_dot_oben,binary_sensor.contact_upper_bathroom_window_tilted,\\\n#   binary_sensor.contact_badfenster\"\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"off\"\n#   message: \"Du hast {} angelassen. Ich habe es für dich ausgemacht.\"\n#   #message: \"You left on {}. I turned it off for you\"\n#   message_off: \"Du hast {} vergessen anzumachen. Ich habe es für dich angemacht.\"\n#   #message_off: \"You forgot to turn on {}. I turned it on for you\"\n#   message_reed: \"Du hast {} offen gelassen.\"\n#   #message_reed: \"You left open {} Dummy.\"\n#   message_reed_off: \"Du hast {} zu gelassen.\"\n#   #message_reed_off: \"You left {} closed Dummy.\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   log_level: DEBUG\n#   dependencies: \n#     - Notifier\n\n# detectWindowsOpenWhenGoingToBed:\n#   module: detectWrongState\n#   class: DetectWrongState\n#   app_switch: input_boolean.detect_windows_open_when_going_to_bed\n#   entities_off: \"binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\\\n#   binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\\\n#   binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\\\n#   binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\\\n#   binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\\\n#   binary_sensor.contact_upper_bathroom_window_tilted,binary_sensor.contact_badfenster\"\n#   after_sundown: True\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"on\"\n#   message: \"Du hast {} angelassen. Ich habe es für dich ausgemacht.\"\n#   #message: \"You left on {}. I turned it off for you\"\n#   message_off: \"Du hast {} vergessen anzumachen. Ich habe es für dich angemacht.\"\n#   #message_off: \"You forgot to turn on {}. I turned it on for you\"\n#   message_reed: \"Du hast {} offen gelassen.\"\n#   #message_reed: \"You left open {} Dummy.\"\n#   message_reed_off: \"Du hast {} zu gelassen.\"\n#   #message_reed_off: \"You left {} closed Dummy.\"\n#   notify_name: group_notifications\n#   use_alexa: True\n#   log_level: DEBUG\n#   dependencies: \n#     - Notifier\n\n# detectDevicesOnWhenGoingToBed:\n#   module: detectWrongState\n#   class: DetectWrongState\n#   app_switch: input_boolean.detect_devices_on_when_going_to_bed\n#   entities_off: \"media_player.denon_avr_x1300w,switch.large_lamp,\\\n#   switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\\\n#   light.reading_lamp_yeelight,light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\\\n#   switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,switch.tv,light.bar_table,light.lower_bathroom_yeelight,\\\n#   switch.markise, switch.coffee_machine_plug_relay\"\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"on\"\n#   log_level: DEBUG\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "ench.yaml",
    "content": "---\nench:\n  module: ench\n  class: EnCh\n  notify: \"notify.kevin\"\n  exclude:\n    - device_tracker.venue_8*\n    - person.kevin\n    - device_tracker.sonoff_large_ventilator_7998\n    - device_tracker.sonoff_ventilator_8133\n    - device_tracker.android*\n    - device_tracker.astrids_mbp\n    - device_tracker.franzi_s_iphone\n    - device_tracker.galaxy*\n    - device_tracker.iphone*\n    - device_tracker.oneplus*\n    - device_tracker.unifi*\n    - light.group_0\n    - media_player.kevin_s*\n    - sensor.192_168_1_39*\n    - sensor.192_168_1_48*\n    - sensor.large_ventilator*\n    - sensor.ventilator*\n    - switch.ventilator\n    - switch.large_ventilator\n    - sensor.travel_time_next*\n    - sensor.glances*_temp\n    - sensor.publish_ip_on_boot\n    - sensor.*odroid*\n    - media_player.55pus7304_12\n    - media_player.fernseher\n    - sensor.consumption_31\n    - sensor.power_30\n    - sensor.openweathermap*\n    - light.bar_table\n    - sensor.*_nachster_wecker\n    - switch.xiaomi_plug\n    - media_player.55pus7304_12_*\n    # Alexa Media Player\n    - sensor.*next_alarm\n    - sensor.*next_reminder\n    - sensor.*next_timer\n    - sensor.this_device*\n    - switch.*_repeat_switch*\n    - switch.*_shuffle_switch*\n    - switch.*do_not_disturb_switch\n  battery:\n    interval_min: 180\n    min_level: 20\n  unavailable:\n    interval_min: 60\n    max_unavailable_min: 15\n"
  },
  {
    "path": "eventMonitor/eventMonitor.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n\"\"\"\nMonitor events and output changes to the verbose_log. Nice for debugging purposes.\nArguments:\n - events: List of events to monitor\n\"\"\"\n\n\nclass Monitor(hass.Hass):\n    def initialize(self):\n        self.listen_event_handle_list = []\n\n        events = self.args[\"events\"]\n\n        if events != None:\n            for event in self.split_device_list(self.args[\"events\"]):\n                self.log('watching event \"{}\" for state changes'.format(event))\n                self.listen_event_handle_list.append(\n                    self.listen_event(self.changed, event)\n                )\n        if len(self.listen_event_handle_list) == 0:\n            self.log(\"watching all events for state changes\")\n            self.listen_event_handle_list.append(self.listen_event(self.changed))\n\n    def changed(self, event_name, data, kwargs):\n        self.log(event_name + \": \" + str(data))\n\n    def terminate(self):\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n"
  },
  {
    "path": "eventMonitor/eventMonitor.yaml",
    "content": "#eventMonitor:\n#  module: eventMonitor\n#  class: Monitor\n#  events: "
  },
  {
    "path": "faceRecognitionBot/faceRecognitionBot.py",
    "content": "import json\nfrom json import JSONDecodeError\n\nimport appdaemon.plugins.hass.hassapi as hass  # pylint: disable=import-error\nimport shutil\nimport os\nimport time\nimport datetime\nimport requests\n\n#\n# App which runs face detection and notifies the user with the result\n#\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# sensor: binary sensor to use as trigger\n# button: xiaomi button to use as a trigger\n# camera : camera entity. example: camera.ip_webcam\n# local_file_camera: local file camera entity. example: camera.saved_image\n# notify_name: Who to notify. example: group_notifications\n# wol_switch: Wake on Lan switch which turns on the facebox server. example: switch.facebox_wol\n# user_id: The user_id of the telegram user to ask whether he knows an unknown face\n# number_of_images: Number of images to take. example: 10\n# waitBeforeSnapshot: How many seconds to wait before triggering the inital snapshot. example: 2.5\n# message_face_identified: Message to send if a face got identified.\n# message_unkown_face: Message to send if a face is unknown\n# message_provide_name\n# message_name_provided\n# message_name_provided_callback\n# ip: Ip of facerec_service. example: 192.168.0.1\n# port: port of facerec_service. example: 8080\n#\n# Release Notes\n#\n# Version 1.3.beta:\n#   Fully working\n#\n# Version 1.2:\n#   Rework to FaceRecognitionBot\n#\n# Version 1.1:\n#   Take Snapshot before sending WoL\n#\n# Version 1.0:\n#   Initial Version\nCLASSIFIER = \"faces\"\nTIMEOUT = 20\nPROVIDE_NAME_TIMEOUT = 5\nIDENTIFIER_DELIMITER = \"_\"\nFILENAME_DELIMITER = \"-\"\nMAXIMUM_DISTANCE = 0.40\nUNKNOWN_FACE_NAME = \"unkown\"\n\n\nclass FaceRecognitionBot(hass.Hass):\n    def initialize(self):\n\n        # handle lists\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        # args\n        self.app_switch = self.args[\"app_switch\"]\n        self.sensor = self.args[\"sensor\"]\n        self.button = self.args[\"button\"]\n        self.camera = self.args[\"camera\"]\n        self.local_file_camera = self.args[\"local_file_camera\"]\n        self.filename = self.args[\"filename\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.wol_switch = self.args[\"wol_switch\"]\n        self.user_id = self.args[\"user_id\"]\n        self.number_of_images = self.args[\"number_of_images\"]\n        self.waitBeforeSnapshot = self.args[\"waitBeforeSnapshot\"]\n        self.message_face_identified = self.args[\"message_face_identified\"]\n        self.message_unkown_face = self.args[\"message_unkown_face\"]\n        self.message_unkown_face_with_known = self.args[\n            \"message_unkown_face_with_known\"\n        ]\n        self.message_provide_name = self.args[\"message_provide_name\"]\n        self.message_name_provided = self.args[\"message_name_provided\"]\n        self.message_name_provided_callback = self.args[\n            \"message_name_provided_callback\"\n        ]\n        self.facebox_healthcheck_filename = self.args[\"facebox_healthcheck_filename\"]\n        self.healthcheck_face_name = self.args[\"healthcheck_face_name\"]\n        self.ip = self.args[\"ip\"]\n        self.port = self.args[\"port\"]\n\n        # optional args\n        self.facebox_source_directory = self.args[\"facebox_source_directory\"]\n        if not self.facebox_source_directory.endswith(\"/\"):\n            self.facebox_source_directory = self.facebox_source_directory + \"/\"\n        self.facebox_unknown_directory = self.args[\"facebox_unknown_directory\"]\n        if not self.facebox_unknown_directory.endswith(\"/\"):\n            self.facebox_unknown_directory = self.facebox_unknown_directory + \"/\"\n        self.facebox_noface_directory = self.args[\"facebox_noface_directory\"]\n        if not self.facebox_noface_directory.endswith(\"/\"):\n            self.facebox_noface_directory = self.facebox_noface_directory + \"/\"\n        self.facebox_known_faces_directory = self.args[\"facebox_known_faces_directory\"]\n        if not self.facebox_known_faces_directory.endswith(\"/\"):\n            self.facebox_known_faces_directory = (\n                self.facebox_known_faces_directory + \"/\"\n            )\n\n        # App dependencies\n        self.notifier = self.get_app(\"Notifier\")\n\n        # Subscribe to sensors\n        self.listen_state_handle_list.append(\n            self.listen_state(self.triggered, self.sensor)\n        )\n        # Subscribe to custom triggers\n        self.listen_event_handle_list.append(\n            self.listen_event(self.button_clicked, \"xiaomi_aqara.click\")\n        )\n        self.listen_event_handle_list.append(\n            self.listen_event(self.learn_faces_event_callback, \"eifinger_learn_faces\")\n        )\n        # subscribe to telegram events\n        self.listen_event_handle_list.append(\n            self.listen_event(self.receive_telegram_callback, \"telegram_callback\")\n        )\n        self.listen_event_handle_list.append(\n            self.listen_event(self.receive_telegram_text, \"telegram_text\")\n        )\n        # Teach periodic run\n        self.timer_handle_list.append(self.run_in(self.check_health_callback, 5))\n\n        # custom variables\n\n        self.valid_filetypes = (\".jpg\", \".png\", \".jpeg\")\n\n        self.teach_url = \"http://{}:{}/faces\".format(self.ip, self.port)\n        self.health_url = \"http://{}:{}/faces\".format(self.ip, self.port)\n        self.check_url = \"http://{}:{}\".format(self.ip, self.port)\n\n        self.run_in_initial_delay = 43200\n        self.run_in_delay = self.run_in_initial_delay\n        self.run_in_error_delay = 60\n        self.check_health_timeout = 10\n\n        self.exclude_folders = (\n            \"healthcheck\",\n            \"multiple\",\n            \"noface\",\n            \"tmp\",\n            \"unknown\",\n            \"new\",\n        )\n\n        self.provide_name_timeout_start = None\n        self.last_identifier = None\n        self.last_message_id = None\n        self.last_from_first = None\n\n    ###############################################################\n    # Teacher\n    ###############################################################\n    def check_health_callback(self, kwargs):\n        \"\"\"Check health.\n        \n        Runs repeatedly until it is veryfied that the classifier is healthy and faces are trained.\n        \n        If it is healthy and trained it will check again after run_in_initial_delay\"\"\"\n        try:\n            if self.check_classifier_health():\n                self.check_if_trained(None)\n                self.timer_handle_list.append(\n                    self.run_in(self.check_health_callback, self.run_in_delay)\n                )\n        except requests.exceptions.HTTPError as exception:\n            self.log(\n                \"Error trying to turn on entity. Will try again in 1s. Error: {}\".format(\n                    exception\n                ),\n                level=\"WARNING\",\n            )\n            self.timer_handle_list.append(self.run_in(self.check_health_callback, 1))\n\n    def learn_faces_event_callback(self, event_name, data, kwargs):\n        \"\"\"Callback function for manual trigger of face learning\"\"\"\n        self.log(\"Event received. Triggering Face Learning\")\n        self.teach_faces(self.facebox_known_faces_directory, self.exclude_folders)\n\n    def teach_name_by_file(self, teach_url, name, file_path):\n        \"\"\"Teach the classifier a single name using a single file.\"\"\"\n        file_name = file_path.split(\"/\")[-1]\n        file = {\"file\": open(file_path, \"rb\")}\n\n        teach_url = teach_url + \"?id=\" + name\n        response = requests.post(teach_url, files=file)\n\n        if response.status_code == 200:\n            self.log(\"File: {} taught with name: {}\".format(file_name, name))\n            return True\n\n        elif response.status_code == 400:\n            self.log(\n                \"Teaching of file: {} failed with message: {}\".format(\n                    file_name, response.text\n                )\n            )\n            return False\n\n    def check_classifier_health(self):\n        \"\"\"Check if classifier is reachable under health_url and returns HTTP 200\"\"\"\n        try:\n            response = requests.get(self.health_url, timeout=self.check_health_timeout)\n            if response.status_code == 200:\n                self.log(\"Health-check passed\")\n                self.run_in_delay = self.run_in_initial_delay\n                self.log(\"Setting run_in_delay to {}\".format(self.run_in_delay))\n                return True\n            else:\n                self.log(\"Health-check failed\")\n                self.log(response.status_code)\n                # check for recurring error\n                if self.run_in_delay < self.run_in_initial_delay:\n                    self.run_in_delay = self.run_in_delay * 2\n                else:\n                    self.run_in_delay = self.run_in_error_delay\n                return False\n        except requests.exceptions.Timeout as timeout_exception:\n            self.log(\"Health-Check timed out\")\n            self.check_health_timeout = self.check_health_timeout * 1.5\n            return self.check_classifier_health()\n        except requests.exceptions.RequestException as exception:\n            self.log(\"Server is unreachable\", level=\"WARNING\")\n            self.log(exception, level=\"WARNING\")\n            # check for recurring error\n            if self.run_in_delay < self.run_in_initial_delay:\n                self.run_in_delay = self.run_in_delay * 2\n            else:\n                self.run_in_delay = self.run_in_error_delay\n            self.log(\"Setting run_in_delay to {}\".format(self.run_in_delay))\n\n    def check_if_trained(self, kwargs):\n        \"\"\"Check if faces are trained. If not train them.\n        \n        Checks for a picture with a known result if the classifier returns the correct result\n        \"\"\"\n        response = self.post_image(self.check_url, self.facebox_healthcheck_filename)\n        if (\n            response\n            and response.status_code == 200\n            and len(response.json()[\"faces\"]) > 0\n            and response.json()[\"faces\"][0][\"id\"] == self.healthcheck_face_name\n        ):\n            self.log(\"Faces are still taught\")\n        else:\n            self.log(\"Faces are not taught\")\n            self.teach_faces(self.facebox_known_faces_directory, self.exclude_folders)\n\n    def teach_faces(self, folderpath, exclude_folders=[]):\n        \"\"\"Teach faces.\n\n        Will iterate over all subdirectories of 'folderpath' and teach the name within\n        that subdirectory with the name of the subdirectory\"\"\"\n        self.log(\"Teaching faces\")\n        for folder_name in self.list_folders(folderpath):\n            if not folder_name in exclude_folders:\n                folder_path = os.path.join(folderpath, folder_name)\n                for file in os.listdir(folder_path):\n                    if file.endswith(self.valid_filetypes):\n                        file_path = os.path.join(folder_path, file)\n                        self.teach_name_by_file(self.teach_url, folder_name, file_path)\n\n    def teach_name_by_directory(self, name, folderpath):\n        \"\"\"Teach faces in a directory for a given anme\"\"\"\n        self.log(\"Teaching faces in dir: {}\".format(folderpath))\n        for file in os.listdir(folderpath):\n            if file.endswith(self.valid_filetypes):\n                file_path = os.path.join(folderpath, file)\n                self.teach_name_by_file(self.teach_url, name, file_path)\n\n    ###############################################################\n    # Classifier\n    ###############################################################\n    def button_clicked(self, event_name, data, kwargs):\n        \"\"\"Extra callback method to trigger the face detection on demand by pressing a Xiaomi Button\"\"\"\n        if data[\"entity_id\"] == self.button:\n            if data[\"click_type\"] == \"single\":\n                self.timer_handle_list.append(\n                    self.run_in(self.takeSnapshots, self.waitBeforeSnapshot)\n                )\n\n    def triggered(self, entity, attribute, old, new, kwargs):\n        \"\"\"State Callback to start the face detection process\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n            if new == \"on\":\n                self.timer_handle_list.append(\n                    self.run_in(self.takeSnapshots, self.waitBeforeSnapshot)\n                )\n\n    def takeSnapshots(self, kwargs):\n        \"\"\"Take a snapshot. Save to a file.\"\"\"\n        file_locations = []\n        timestamp = time.strftime(\"%Y%m%d%H%M%S\")\n        directory = self.facebox_source_directory + \"new/\" + timestamp\n        if not os.path.exists(directory):\n            os.makedirs(directory)\n        for i in range(0, self.number_of_images):\n            filename = (\n                directory + \"/\" + timestamp + FILENAME_DELIMITER + str(i) + \".jpg\"\n            )\n            self.log(\"Calling camera/snapshot and saving it to: {}\".format(filename))\n            self.call_service(\n                \"camera/snapshot\", entity_id=self.camera, filename=filename\n            )\n            file_locations.append(filename)\n        self.timer_handle_list.append(\n            self.run_in(self.processImages, 0, file_locations=file_locations)\n        )\n\n    def processImages(self, kwargs):\n        \"\"\"Trigger image processing for all images and process the results\n        \n         Get the classifier result for each image\n         store it in a dictionary in the following format\n         {filename:\n                  {\"count\":int,\n                   \"faces\":{\n                             [\n                               {\"dist\":float,\n                                \"id\":name}\n                             ]\n                            },\n                   \"matchedFacesCount\":int}\n         }\n        \"\"\"\n        result_dict_dict = {}\n        for filename in kwargs[\"file_locations\"]:\n            response = self.post_image(self.check_url, filename)\n            if response is not None:\n                result_dict = {}\n                self.log(\"response is: {}\".format(response.text))\n                try:\n                    response_json = response.json()\n                    result_dict[\"count\"] = response_json[\"count\"]\n                    result_dict[\"faces\"] = response_json[\"faces\"]\n                    result_dict[\"matchedFacesCount\"] = len(response_json[\"faces\"])\n                    result_dict_dict[filename] = result_dict\n                except JSONDecodeError:\n                    self.log(\"JSONDecodeError. Skipping response\")\n        # get the maximum number of faces detected in one image\n        maxCount = self._getMaxCountFromResult(result_dict_dict)\n        # get a list of distinct recognized face names\n        faceNames = self._getFaceNamesFromResult(result_dict_dict)\n        self.log(\"Number of distinct faces: {}\".format(len(faceNames)))\n        if maxCount > 1:\n            self.log(\"At least one time detected more than one face\")\n            # check if it contains an unknown face\n            if UNKNOWN_FACE_NAME in faceNames:\n                self._notifyUnkownFaceFound(result_dict_dict)\n            else:\n                for faceName in faceNames:\n                    if faceName in self._getKnownFaces():\n                        self.log(self.message_face_identified.format(faceName))\n                        self.notifier.notify(\n                            self.notify_name,\n                            self.message_face_identified.format(faceName),\n                        )\n                        # copy file to saved image to display in HA\n                        shutil.copy(filename, self.filename)\n        elif maxCount == 1:\n            self.log(\"Always detected one face\")\n            # check if always the same face\n            if len(faceNames) > 1:\n                self.log(\"Not always the same face\")\n                # TODO test!\n                # at least one time detected a known face\n                # notify of who was detected\n                for faceName in faceNames:\n                    if faceName in self._getKnownFaces():\n                        self.log(self.message_face_identified.format(faceName))\n                        self.notifier.notify(\n                            self.notify_name,\n                            self.message_face_identified.format(faceName),\n                        )\n                        # copy file to saved image to display in HA\n                        shutil.copy(filename, self.filename)\n                # process the unknown faces\n                if UNKNOWN_FACE_NAME in faceNames:\n                    self._processUnkownFaceFound(result_dict_dict)\n            else:\n                self.log(\"Always the same face\")\n                # Is it a known face?\n                if len(faceNames) > 0 and faceNames[0] in self._getKnownFaces():\n                    # always identified the same known person\n                    self.log(self.message_face_identified.format(faceNames[0]))\n                    self.notifier.notify(\n                        self.notify_name,\n                        self.message_face_identified.format(faceNames[0]),\n                    )\n                    # Move files to known face subdirectory\n                    for filename in result_dict_dict:\n                        # at this time we know it is at most 1 and it is always the same known face\n                        if result_dict_dict[filename][\"count\"] == 1:\n                            directory = (\n                                self.facebox_known_faces_directory\n                                + result_dict_dict[filename][\"faces\"][0][\"id\"]\n                            )\n                            new_filename = os.path.join(\n                                directory, os.path.split(filename)[1]\n                            )\n                            # copy file to saved image to display in HA\n                            shutil.copy(filename, self.filename)\n                            self.log(\n                                \"Move file from {} to {}\".format(filename, new_filename)\n                            )\n                            shutil.move(filename, new_filename)\n                            # trigger teaching\n                            self.teach_name_by_file(\n                                self.teach_url,\n                                result_dict_dict[filename][\"faces\"][0][\"id\"],\n                                new_filename,\n                            )\n                else:\n                    # unknown face\n                    self._processUnkownFaceFound(result_dict_dict)\n        else:\n            self.log(\"Detected no faces\")\n            # get directory of images and post that in telegram\n\n    def _processUnkownFaceFound(self, result_dict_dict):\n        \"\"\"Store the faces for later use and ask the user if he knows the unkown face\"\"\"\n        # TODO check if the faces are similar\n        # create a temp identifier, compare and delete identifier again\n        # self._determineIfSameUnkownFace(result_dict_dict)\n        # get a file where the unknown face was detected and send it\n        filename = self._getFileWithUnknownFaceFromResult(result_dict_dict)\n        # copy file to saved image to display in HA\n        shutil.copy(filename, self.filename)\n        # move all files where a face was detected to the unkown folder\n        identifier = self._moveFilesToUnknown(result_dict_dict)\n\n        filename_without_path = os.path.split(filename)[1]\n        # send photo\n        unknown_filename = self.facebox_unknown_directory + filename_without_path\n        self.log(f\"Sending photo with filename: {unknown_filename}\")\n        self.call_service(\"telegram_bot/send_photo\", file=unknown_filename, target=self.user_id)\n        \n        if identifier == \"\":\n            self.log(\"Identifier is empty\", level=\"ERROR\")\n        else:\n            self.ask_for_name(identifier)\n\n    def _notifyUnkownFaceFound(self, result_dict_dict):\n        \"\"\"Notify of an unkown face in a image where a known face was detected\"\"\"\n        # get a file where the unknown face was detected and send it\n        filename = self._getFileWithUnknownFaceFromResult(result_dict_dict)\n        # copy file to saved image to display in HA\n        shutil.copy(filename, self.filename)\n        # send photo\n        self.call_service(\"telegram_bot/send_photo\", file=filename, target=self.user_id)\n        self.log(self.message_unkown_face_with_known)\n        self.notifier.notify(self.notify_name, self.message_unkown_face_with_known)\n\n    def _getMaxCountFromResult(self, result_dict_dict):\n        \"\"\"Get the maximum number of faces found in the pictures\"\"\"\n        count_list = [d[\"count\"] for d in result_dict_dict.values()]\n        return max(count_list)\n\n    def _getFaceNamesFromResult(self, result_dict_dict):\n        \"\"\"Return a list of names for the identified faces\"\"\"\n        try:\n            id_list = []\n            for d in result_dict_dict.values():\n                # check for unknown face\n                if len(d[\"faces\"]) == 0 and d[\"count\"] == 1:\n                    id_list.append(UNKNOWN_FACE_NAME)\n                else:\n                    for face in d[\"faces\"]:\n                        if face[\"dist\"] < MAXIMUM_DISTANCE:\n                            id_list.append(face[\"id\"])\n                        # if distance(similarity) too large, mark as unknown\n                        else:\n                            self.log(\n                                \"Similary distance of {} is larger than maximum threshold of {}\".format(\n                                    face[\"dist\"], MAXIMUM_DISTANCE\n                                )\n                            )\n                            id_list.append(UNKNOWN_FACE_NAME)\n                            face[\"id\"] = UNKNOWN_FACE_NAME\n            self.log(\"FacesNames: {}\".format(list(set(id_list))))\n            return list(set(id_list))\n        except TypeError:\n            return []\n\n    def _getFileWithUnknownFaceFromResult(self, result_dict_dict):\n        \"\"\"Get the first file from the result which has an unmatched face\"\"\"\n        for filename in result_dict_dict.keys():\n            if (\n                result_dict_dict[filename][\"count\"] > 0\n                and result_dict_dict[filename][\"faces\"][0][\"id\"] == UNKNOWN_FACE_NAME\n            ):\n                return filename\n\n    def _determineIfSameUnkownFace(self, result_dict_dict):\n        \"\"\"Determine if the unkown face which was detected several times is the same unknown face\"\"\"\n        # get all files with unknown faces\n        unkown_faces = []\n        for filename in result_dict_dict.keys():\n            if (\n                result_dict_dict[filename][\"count\"] == 1\n                and result_dict_dict[filename][\"faces\"][0][\"id\"] == UNKNOWN_FACE_NAME\n            ):\n                unkown_faces.append(filename)\n        # iterate over all files\n        for k, filename in enumerate(unkown_faces):\n            for i, filename in enumerate(unkown_faces):\n                if i < k:\n                    pass\n                elif i == k:\n                    # teach the first face\n                    filename_without_path = os.path.split(filename)[1]\n                    self.teach_name_by_file(\n                        self.teach_url, filename_without_path, filename\n                    )\n                else:\n                    response = self.post_image(self.check_url, filename)\n                    response_json = response.json()\n                    if response_json[\"count\"] > 0:\n                        if (\n                            response_json[\"faces\"][0][\"id\"] == filename_without_path\n                            and response_json[\"faces\"][0][\"dist\"] < MAXIMUM_DISTANCE\n                        ):\n                            # same face remove it from the list\n                            unkown_faces.remove(filename)\n\n    def _moveFilesToUnknown(self, result_dict_dict):\n        \"\"\"Copy all files where the unknown face was detected to the unknown folder.\n        Returns the timestamp under which all files can be identified\"\"\"\n        identifier = \"\"\n        for filename in result_dict_dict.keys():\n            if result_dict_dict[filename][\"count\"] > 0 and (\n                len(result_dict_dict[filename][\"faces\"]) == 0\n                or result_dict_dict[filename][\"faces\"][0][\"id\"] == UNKNOWN_FACE_NAME\n            ):\n                filename_without_path = os.path.split(filename)[1]\n                # get the timestamp as identifier, strip everything after \"-\"\"\n                identifier = filename_without_path.split(FILENAME_DELIMITER)[0]\n                self.log(\"Identifier is: {}\".format(identifier), level=\"DEBUG\")\n                new_filename = self.facebox_unknown_directory + filename_without_path\n                self.log(\"Move file from {} to {}\".format(filename, new_filename))\n                shutil.move(filename, new_filename)\n        return identifier\n\n    def _moveFilesFromUnkownToDirectoryByIdentifier(self, directory, identifier):\n        \"\"\"Copy all files in the unknown folder which belong to an identifier (a timestamp) to a new directory\"\"\"\n        if not os.path.exists(directory):\n            os.makedirs(directory)\n        for file in os.listdir(self.facebox_unknown_directory):\n            if identifier in file:\n                filename = os.path.join(self.facebox_unknown_directory, file)\n                new_filename = os.path.join(directory, file)\n                self.log(\"Move file from {} to {}\".format(filename, new_filename))\n                shutil.move(filename, new_filename)\n\n    def _getKnownFaces(self):\n        \"\"\"Return a list of known face names.\n\n        Iterates over the subdirectory names of facebox_known_faces_directory\"\"\"\n        return self.list_folders(self.facebox_known_faces_directory)\n\n    def list_folders(self, directory):\n        \"\"\"Returns a list of folders\n        These are not full paths, just the folder.\"\"\"\n        folders = [\n            dir\n            for dir in os.listdir(directory)\n            if os.path.isdir(os.path.join(directory, dir))\n            and not dir.startswith(directory)\n            and not dir.startswith(\".\")\n        ]\n        folders.sort(key=str.lower)\n        return folders\n\n    def post_image(self, url, image):\n        \"\"\"Post an image to the classifier.\"\"\"\n        try:\n            response = requests.post(\n                url, files={\"file\": open(image, \"rb\")}, timeout=self.check_health_timeout\n            )\n            return response\n        except requests.exceptions.ConnectionError:\n            self.log(\"ConnectionError\")\n        except requests.exceptions.ReadTimeout:\n            self.log(\"ReadTimeout\")\n            self.check_health_timeout = self.check_health_timeout * 1.5\n            self.log(f\"Setting Health Check Timeout to {self.check_health_timeout}\")\n\n    ###############################################################\n    # Telegram Bot\n    ###############################################################\n\n    def ask_for_name(self, identifier):\n        \"\"\"Asks the user if he knows the face in the photo.\n        The identifier is needed to link the user reply back to this message\"\"\"\n        self.log(\"Asking for name\")\n        keyboard = [[(\"Unbekannt\", \"/unkown\" + IDENTIFIER_DELIMITER + identifier)]]\n        for face in self._getKnownFaces():\n            keyboard.append([(face, \"/\" + face + IDENTIFIER_DELIMITER + identifier)])\n        self.log(\"keyboard is: {}\".format(keyboard), level=\"DEBUG\")\n        self.call_service(\n            \"telegram_bot/send_message\",\n            target=self.user_id,\n            message=self.message_unkown_face,\n            inline_keyboard=keyboard,\n        )\n\n    def receive_telegram_callback(self, event_name, data, kwargs):\n        \"\"\"Event listener for Telegram callback queries.\"\"\"\n        self.log(\"callback data: {}\".format(data))\n        data_callback = data[\"data\"]\n        callback_id = data[\"id\"]\n        chat_id = data[\"chat_id\"]\n        message_id = data[\"message\"][\"message_id\"]\n        text = data[\"message\"][\"text\"]\n        from_first = data[\"from_first\"]\n\n        for face in self._getKnownFaces():\n            if data_callback.startswith(\"/\" + face + IDENTIFIER_DELIMITER):\n                self.log(\"Received Telegram Callback for {}\".format(face))\n                self.call_service(\n                    \"telegram_bot/answer_callback_query\",\n                    message=\"Dankeschön!\",\n                    callback_query_id=callback_id,\n                )\n                self.call_service(\n                    \"telegram_bot/edit_message\",\n                    chat_id=chat_id,\n                    message_id=message_id,\n                    message=self.message_name_provided_callback.format(\n                        from_first, face\n                    ),\n                    inline_keyboard=[],\n                )\n                identifier = data_callback.split(IDENTIFIER_DELIMITER)[1]\n                directory = self.facebox_known_faces_directory + face\n                self._moveFilesFromUnkownToDirectoryByIdentifier(directory, identifier)\n                self.teach_name_by_directory(face, directory)\n\n        if data_callback.startswith(\"/unkown\"):\n            # Answer callback query\n            self.call_service(\n                \"telegram_bot/answer_callback_query\",\n                message=\"Dankeschön!\",\n                callback_query_id=callback_id,\n            )\n            self.call_service(\n                \"telegram_bot/edit_message\",\n                chat_id=chat_id,\n                message_id=message_id,\n                message=text,\n                inline_keyboard=[],\n            )\n            self.notifier.notify(\n                self.notify_name,\n                self.message_provide_name.format(PROVIDE_NAME_TIMEOUT),\n                useAlexa=False,\n            )\n            self.provide_name_timeout_start = datetime.datetime.now()\n            self.last_identifier = data_callback.split(IDENTIFIER_DELIMITER)[1]\n            self.last_message_id = message_id\n            self.last_from_first = from_first\n\n    def receive_telegram_text(self, event_name, data, kwargs):\n        \"\"\"Telegram text listener\"\"\"\n        self.log(\"callback data: {}\".format(data), level=\"DEBUG\")\n        chat_id = data[\"chat_id\"]\n        text = data[\"text\"]\n\n        if self.provide_name_timeout_start != None and (\n            datetime.datetime.now() - self.provide_name_timeout_start\n            < datetime.timedelta(minutes=PROVIDE_NAME_TIMEOUT)\n        ):\n            # Edit the last ask_for_name message\n            self.call_service(\n                \"telegram_bot/edit_message\",\n                chat_id=chat_id,\n                message_id=self.last_message_id,\n                message=self.message_name_provided_callback.format(\n                    self.last_from_first, text\n                ),\n                inline_keyboard=[],\n            )\n            # Say thanks\n            self.notifier.notify(\n                self.notify_name,\n                self.message_name_provided.format(text),\n                useAlexa=False,\n            )\n            # Copy files to new directory\n            directory = self.facebox_known_faces_directory + text\n            self._moveFilesFromUnkownToDirectoryByIdentifier(\n                directory, self.last_identifier\n            )\n            self.teach_name_by_directory(text, directory)\n        else:\n            self.log(\"PROVIDE_NAME_TIMEOUT exceeded\")\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "faceRecognitionBot/faceRecognitionBot.yaml",
    "content": "# faceRecognitionBot:\n#   module: faceRecognitionBot\n#   class: FaceRecognitionBot\n#   app_switch: input_boolean.facebox_notifier\n#   sensor: binary_sensor.contact_door\n#   button: binary_sensor.switch_158d000215aa28\n#   camera: camera.android_ip_webcam_door\n#   local_file_camera: camera.saved_image\n#   filename: !secret facebox_notifier_filename\n#   image_processing: image_processing.facebox\n#   notify_name: group_notifications\n#   wol_switch: switch.facebox_wol\n#   user_id: !secret telegram_user_id\n#   facebox_source_directory: !secret facebox_folderpath\n#   facebox_unknown_directory: !secret facebox_unknown_directory\n#   facebox_noface_directory: !secret facebox_noface_directory\n#   facebox_known_faces_directory: !secret facebox_known_faces_directory\n#   facebox_healthcheck_filename: !secret facebox_healthcheck_filename\n#   healthcheck_face_name: Kevin \n#   number_of_images: 10\n#   waitBeforeSnapshot: 1\n#   ip: !secret facebox_ip\n#   port: !secret facebox_port\n#   message_face_identified: \"Ich habe {} erkannt\"\n#   #message_face_identified: \"I have recognized {}.\"\n#   message_unkown_face: \"Ich habe dieses Gesicht nicht erkannt. Kennst du es?\"\n#   #message_unkown_face: \"I have not recognized this face. Do you know it?\"\n#   message_unkown_face_with_known: \"Ich habe auch ein unbekanntes Gesicht entdeckt.\"\n#   #message_unkown_face_with_known: \"I have also discovered an unknown face.\"\n#   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!\"\n#   #message_provide_name: \"If you know the face you can write the name to me within the next {} minutes. I will remember it!\"\n#   message_name_provided: \"Okay. Ich merke mir, dass das {} ist\"\n#   #message_name_provided: \"Okay. I will remember that this is {}\"\n#   message_name_provided_callback: \"{} sagte, dass dies {} ist.\"\n#   #message_name_provided_callback: \"{} said that this is {}\"\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "globals.py",
    "content": "import random\n\n\ndef random_arg(argList):\n    ############################################\n    # pick a random text from a list\n    ############################################\n    if isinstance(argList, list):\n        text = random.choice(argList)\n    else:\n        text = argList\n    return text\n"
  },
  {
    "path": "heartbeat/heartbeat.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nfrom requests.exceptions import HTTPError\n\n#\n# App which sets a homeassistant entity as a heartbeat to check for threadstarvation etc\n#\n# Args:\n# sensor: sensor.appdaemon_heartbeat\n#\n# Release Notes\n#\n# Version 1.1:\n#   Set start to None run_minutely will run after 1 minute\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass Heartbeat(hass.Hass):\n    def initialize(self):\n        self.timer_handle_list = []\n\n        self.sensor = self.args[\"sensor\"]\n\n        self.heartbeat(None)\n\n        self.timer_handle_list.append(self.run_minutely(self.heartbeat, start=None))\n\n    def heartbeat(self, kwargs):\n        try:\n            self.set_state(self.sensor, state=str(self.time()))\n            self.log(\"Heartbeat\", level=\"DEBUG\")\n        except HTTPError as exception:\n            self.log(\n                \"Error trying to set entity. Will try again in 5s. Error: {}\".format(\n                    exception\n                ),\n                level=\"WARNING\",\n            )\n            self.timer_handle_list.append(self.run_in(self.heartbeat, 5))\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "heartbeat/heartbeat.yaml",
    "content": "heartbeat:\n  module: heartbeat\n  class: Heartbeat\n  sensor: sensor.appdaemon_heartbeat"
  },
  {
    "path": "homeArrivalNotifier/homeArrivalNotifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App to send a notification if someone arrives at home\n#\n# Args:\n#  app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n#  input_boolean: input boolean which holds the information of someone is home or not\n#  notify_name: Who to notify\n#  user_name: name to use in notification message\n#  zone_name: Name of the zone\n#  message: message to use in notification\n# Release Notes\n#\n# Version 1.4.1:\n#   Use consistent message variable\n#\n# Version 1.4:\n#   use Notify App\n#\n# Version 1.3:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.2:\n#   Added app_switch\n#\n# Version 1.1:\n#   Added user_name\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass HomeArrivalNotifier(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.zone_name = self.args[\"zone_name\"]\n        self.input_boolean = self.args[\"input_boolean\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.user_name = self.args[\"user_name\"]\n        self.message = self.args[\"message\"]\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.input_boolean)\n        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new != old:\n                self.log(\"{} changed from {} to {}\".format(entity, old, new))\n                if new == \"on\":\n                    self.log(\n                        \"{} arrived at {}\".format(self.notify_name, self.zone_name)\n                    )\n                    self.notifier.notify(\n                        self.notify_name, self.message.format(self.user_name)\n                    )\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "homeArrivalNotifier/homeArrivalNotifier.yaml",
    "content": "#Notification if user one arrives at home\n# homeArrivalNotifierUserOne:\n#   module: homeArrivalNotifier\n#   class: HomeArrivalNotifier\n#   app_switch: input_boolean.home_arrival_notifier_user_one\n#   input_boolean: input_boolean.user_one_home\n#   notify_name: group_notifications\n#   user_name: Kevin\n#   zone_name: Home\n#   message: \"Willkommen zu Hause {}.\"\n#   #message: \"Welcome Home {}.\"\n#   dependencies: \n#     - Notifier\n\n# #Notification if user two arrives at home\n# homeArrivalNotifierUserTwo:\n#   module: homeArrivalNotifier\n#   class: HomeArrivalNotifier\n#   app_switch: input_boolean.home_arrival_notifier_user_two\n#   input_boolean: input_boolean.user_two_home\n#   notify_name: group_notifications\n#   user_name: Sina\n#   zone_name: Home\n#   message: \"Willkommen zu Hause {}.\"\n#   #message: \"Welcome Home {}.\"\n#   dependencies: \n#     - Notifier"
  },
  {
    "path": "isHomeDeterminer/isHomeDeterminer.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport globals\n\n#\n# App to\n#\n# Args:\n#   app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n#   input_booleans: list of input boolean which determine if a user is home\n#   ishome: input boolean which determins if someone is home\n#   message: message to use in notification\n# Release Notes\n#\n# Version 1.3:\n#   message now a list\n#\n# Version 1.2:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.1:\n#   Added app_switch\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass IsHomeDeterminer(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.ishome = self.args[\"ishome\"]\n        self.input_booleans = self.args[\"input_booleans\"].split(\",\")\n        self.message = self.args[\"message\"]\n\n        if self.get_state(self.app_switch) == \"on\":\n            for input_boolean in self.input_booleans:\n                self.log(\n                    \"{} is {}\".format(input_boolean, self.get_state(input_boolean))\n                )\n                self.listen_state_handle_list.append(\n                    self.listen_state(self.state_change, input_boolean)\n                )\n                if (\n                    self.get_state(input_boolean) == \"on\"\n                    and self.get_state(self.ishome) == \"off\"\n                ):\n                    self.turn_on(self.ishome)\n                    self.log(\"Setting {} to on\".format(self.ishome))\n                if (\n                    self.get_state(input_boolean) == \"off\"\n                    and self.get_state(self.ishome) == \"on\"\n                ):\n                    if self.are_others_away(input_boolean):\n                        self.turn_off(self.ishome)\n                        self.log(\"Setting {} to off\".format(self.ishome))\n                        notify_message = globals.random_arg(self.message)\n                        self.log(\"notify_messsage: {}\".format(notify_message))\n                        self.call_service(\n                            \"notify/group_notifications\", message=notify_message\n                        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new != old:\n                self.log(\"{} changed from {} to {}\".format(entity, old, new))\n                if new == \"on\":\n                    self.turn_on(self.ishome)\n                    self.log(\"Setting {} to on\".format(self.ishome))\n                if new == \"off\":\n                    if self.are_others_away(entity):\n                        self.turn_off(self.ishome)\n                        self.log(\"Setting {} to off\".format(self.ishome))\n                        notify_message = globals.random_arg(self.message)\n                        self.log(\"notify_messsage: {}\".format(notify_message))\n                        self.call_service(\n                            \"notify/group_notifications\", message=notify_message\n                        )\n\n    def are_others_away(self, entity):\n        self.log(\"Entity: {}\".format(entity))\n        for input_boolean in self.input_booleans:\n            self.log(\"{} is {}\".format(input_boolean, self.get_state(input_boolean)))\n            if input_boolean == entity:\n                pass\n            elif self.get_state(input_boolean) == \"on\":\n                self.log(\"{} is still at on\".format(input_boolean))\n                return False\n        self.log(\"Everybody not home\")\n        return True\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "isHomeDeterminer/isHomeDeterminer.yaml",
    "content": "# #Control the isHome state. Determines if someone is home or all persons are away\n# isHomeDeterminer:\n#   module: isHomeDeterminer\n#   class: IsHomeDeterminer\n#   app_switch: input_boolean.is_home_determiner\n#   ishome: input_boolean.is_home\n#   input_booleans: input_boolean.user_one_home,input_boolean.user_two_home\n#   message:\n#     - \"Es ist keiner mehr zu Hause.\"\n#     - \"Keiner mehr da? Panda Party!\"\n#     - \"Ich passe auf die Wohnung auf, einen schönen Tag\"\n#     - \"Tschüss, bis nachher\"\n#   #message: \"Everyone left home. Setting isHome to off\"\n#   global_dependencies:\n#     - globals\n#     - secrets"
  },
  {
    "path": "isUserHomeDeterminer/isUserHomeDeterminer.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nfrom requests.exceptions import HTTPError\n\n#\n# App to toggle an input boolean when a person enters or leaves home.\n# This is determined based on a combination of a GPS device tracker and the door sensor.\n#\n# - If the door sensor opens and the device_tracker changed to \"home\" in the last self.delay minutes\n#   this means someone got home\n# - If the door sensor opens and the device_tracker changes to \"not_home\" after that\n#   this means someone left home\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# input_boolean: input_boolean which shows if someone is home e.g. input_boolean.isHome\n# device_tracker: device tracker or person of the user to track e.g. device_tracker.simon\n# door_sensor: Door sensor which indicated the front door opened e.g. binary_sensor.door_window_sensor_158d000126a57b\n#\n# Release Notes\n#\n# Version 1.5:\n#   Wait to leave home until door is opened again\n#\n# Version 1.4.3:\n#   check for listen_state_callback == None before triggering again\n#\n# Version 1.4.2:\n#   cancel listen callback only when its not None\n#\n# Version 1.4.1:\n#   fix for 503, fix for listen callback not being cancelled correctly\n#\n# Version 1.4:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.3:\n#   Added app_switch\n#\n# Version 1.2:\n#   Change checking after a delay to a event based system\n#\n# Version 1.1:\n#   Set when initializing (also when HA restarts)\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass IsUserHomeDeterminer(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.delay = 600\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.input_boolean = self.args[\"input_boolean\"]\n        self.device_tracker = self.args[\"device_tracker\"]\n        self.door_sensor = self.args[\"door_sensor\"]\n\n        device_tracker_state = self.get_state(self.device_tracker, attribute=\"all\")\n        if self.get_state(self.app_switch) == \"on\":\n            if device_tracker_state[\"state\"] == \"home\":\n                self.log(\"User is home\")\n                self.timer_handle_list.append(\n                    self.run_in(\n                        self.turn_on_callback, 0, turn_on_entity=self.input_boolean\n                    )\n                )\n            else:\n                self.log(\"User is not home\")\n                self.timer_handle_list.append(\n                    self.run_in(\n                        self.turn_off_callback, 0, turn_off_entity=self.input_boolean\n                    )\n                )\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.door_sensor)\n        )\n\n        self.listen_state_handle = None\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new != old:\n                self.log(\"{} changed from {} to {}\".format(entity, old, new))\n                if new == \"on\" and old == \"off\":\n                    self.cancel_listen_state_callback(None)\n                    device_tracker_state = self.get_state(\n                        self.device_tracker, attribute=\"all\"\n                    )\n                    self.log(\"device_tracker_state: {}\".format(device_tracker_state),)\n                    last_changed = device_tracker_state[\"last_changed\"]\n                    self.log(\"last_changed: {}\".format(last_changed))\n                    # User got home: Device tracker changed to home before door sensor triggered\n                    if device_tracker_state[\"state\"] == \"home\" and (\n                        (\n                            datetime.datetime.now(datetime.timezone.utc)\n                            - self.convert_utc(last_changed)\n                        )\n                        < datetime.timedelta(seconds=self.delay)\n                    ):\n                        self.log(\"User got home\")\n                        self.turn_on(self.input_boolean)\n                    # User got home: Device tracker is still not home.\n                    # Wait if it changes to home in the next self.delay seconds\n                    elif device_tracker_state[\"state\"] != \"home\":\n                        self.log(\"Wait for device tracker to change to 'home'\")\n                        self.listen_state_handle = self.listen_state(\n                            self.check_if_user_got_home, self.device_tracker\n                        )\n                        self.listen_state_handle_list.append(self.listen_state_handle)\n                        self.timer_handle_list.append(\n                            self.run_in(self.cancel_listen_state_callback, self.delay)\n                        )\n                    # User left home: Device tracker is still home.\n                    # Wait if it changes to not_home\n                    elif device_tracker_state[\"state\"] == \"home\":\n                        self.log(\"Wait for device tracker to change to 'not_home'\")\n                        self.listen_state_handle = self.listen_state(\n                            self.check_if_user_left_home, self.device_tracker\n                        )\n                        self.listen_state_handle_list.append(self.listen_state_handle)\n\n    def cancel_listen_state_callback(self, kwargs):\n        if self.listen_state_handle is not None:\n            self.log(\n                \"Timeout while waiting for user to get/leave home. Cancel listen_state\"\n            )\n            if self.listen_state_handle in self.listen_state_handle_list:\n                self.listen_state_handle_list.remove(self.listen_state_handle)\n            self.cancel_listen_state(self.listen_state_handle)\n            self.listen_state_handle = None\n\n    def check_if_user_left_home(self, entity, attribute, old, new, kwargs):\n        if new != \"home\":\n            self.log(\"User left home\")\n            if self.listen_state_handle in self.listen_state_handle_list:\n                self.listen_state_handle_list.remove(self.listen_state_handle)\n            if self.listen_state_handle != None:\n                self.cancel_listen_state(self.listen_state_handle)\n                self.listen_state_handle = None\n                self.timer_handle_list.append(\n                    self.run_in(\n                        self.turn_off_callback, 1, turn_off_entity=self.input_boolean\n                    )\n                )\n\n    def check_if_user_got_home(self, entity, attribute, old, new, kwargs):\n        if new == \"home\":\n            self.log(\"User got home\")\n            if self.listen_state_handle in self.listen_state_handle_list:\n                self.listen_state_handle_list.remove(self.listen_state_handle)\n            if self.listen_state_handle is not None:\n                self.cancel_listen_state(self.listen_state_handle)\n                self.listen_state_handle = None\n                self.timer_handle_list.append(\n                    self.run_in(\n                        self.turn_on_callback, 1, turn_on_entity=self.input_boolean\n                    )\n                )\n\n    def turn_on_callback(self, kwargs):\n        \"\"\"This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting\"\"\"\n        try:\n            self.turn_on(kwargs[\"turn_on_entity\"])\n        except HTTPError as exception:\n            self.log(\n                \"Error trying to turn on entity. Will try again in 1s. Error: {}\".format(\n                    exception\n                ),\n                level=\"WARNING\",\n            )\n            self.timer_handle_list.append(\n                self.run_in(\n                    self.turn_on_callback, 1, turn_on_entity=kwargs[\"turn_on_entity\"]\n                )\n            )\n\n    def turn_off_callback(self, kwargs):\n        \"\"\"This is needed because the turn_off command can result in a HTTP 503 when homeassistant is restarting\"\"\"\n        try:\n            self.turn_off(kwargs[\"turn_off_entity\"])\n        except HTTPError as exception:\n            self.log(\n                \"Error trying to turn off entity. Will try again in 1s. Error: {}\".format(\n                    exception\n                ),\n                level=\"WARNING\",\n            )\n            self.timer_handle_list.append(\n                self.run_in(\n                    self.turn_off_callback, 1, turn_off_entity=kwargs[\"turn_off_entity\"]\n                )\n            )\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "isUserHomeDeterminer/isUserHomeDeterminer.yaml",
    "content": "# #Determine if user one gets/leaves home\n# isUserHomeDeterminerUserOne:\n#   module: isUserHomeDeterminer\n#   class: IsUserHomeDeterminer\n#   app_switch: input_boolean.is_user_home_determiner_user_one\n#   input_boolean: input_boolean.user_one_home\n#   device_tracker: person.kevin\n#   door_sensor: binary_sensor.contact_door\n\n# #Determine if user two gets/leaves home\n# isUserHomeDeterminerUserTwo:\n#   module: isUserHomeDeterminer\n#   class: IsUserHomeDeterminer\n#   app_switch: input_boolean.is_user_home_determiner_user_two\n#   input_boolean: input_boolean.user_two_home\n#   device_tracker: person.sina\n#   door_sensor: binary_sensor.contact_door"
  },
  {
    "path": "leavingZoneNotifier/leavingZoneNotifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App to notify if user_one is leaving a zone.\n# User had to be in that zone 3 minutes before\n# in order for the notification to be triggered\n#\n# Args:\n#   app_switch: on/off switch for this app.\n#               example: input_boolean.turn_fan_on_when_hot\n#   device: Device to track\n#   user_name: Name of the user used in the notification message\n#   delay: seconds to wait before notifying. Maybe user returns to zone.\n#          This should be too small to avoid false positives from your tracker.\n#          I am using GPS Logger on Android and sometimes my device switches\n#          from work to home and 2 minutes later back. example: 120\n#   lingering_time: time a user has to be in a zone to trigger this app.\n#                   example: 3600\n#   zone: zone name from which the user is leaving\n#   notify_name: Who to notify. example: group_notifications\n#   message: localized message to use in notification\n#   travel_time_sensor (optional): Sensor showing the travel time home.\n#                                  example: sensor.travel_time_home_user_one\n#   travel_time_sensor_message (optional): Additional notify message.\n#\n# Release Notes\n#\n# Version 1.11:\n#   Catch new state might be None\n#\n# Version 1.10:\n#   Catch old state might be None during startup\n#\n# Version 1.9:\n#   PEP8 style and log message when updating travel_time_sensor\n#\n# Version 1.8:\n#   Add travel time in notification message\n#\n# Version 1.7.1:\n#   Fix delay in notify message. Only input the minutes not the tuple\n#\n# Version 1.7:\n#   use Notify App\n#\n# Version 1.6:\n#   notify message includes delay\n#\n# Version 1.5:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.4:\n#   additional note for delay and better handling of zone_entered for\n#   false positives\n#\n# Version 1.3:\n#   delay and lingering_time now as args\n#\n# Version 1.2:\n#   Added app_switch\n#\n# Version 1.1:\n#   Rework without proximity\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass LeavingZoneNotifier(hass.Hass):\n    def initialize(self):\n\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.user_name = self.args[\"user_name\"]\n        self.zone = self.args[\"zone\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.device = self.args[\"device\"]\n        # 'lingering_time' the time a user has to stay in a zone\n        # for this app to trigger\n        self.lingering_time = self.args[\"lingering_time\"]\n        self.delay = self.args[\"delay\"]\n        self.message = self.args[\"message\"]\n        self.travel_time_sensor = self.args.get(\"travel_time_sensor\")\n        self.travel_time_sensor_message = self.args.get(\"travel_time_sensor_message\")\n\n        self.user_entered_zone = None\n        self.false_positive = False\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.zone_state_change, self.device, attribute=\"all\")\n        )\n\n    def zone_state_change(self, entity, attributes, old, new, kwargs):\n        \"\"\"Check if user entered or left a zone.\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n            if new is not None:\n                last_changed = self.convert_utc(new[\"last_changed\"])\n                if old is not None:\n                    old_state = old[\"state\"]\n                self.log(\n                    \"Zone of {} changed from {} to {}.\".format(\n                        self.friendly_name(entity), old_state, new[\"state\"]\n                    ),\n                )\n                if (\n                    new[\"state\"] == self.zone\n                    and old_state != self.zone\n                    and self.false_positive is False\n                ):\n                    self.log(\"Setting user_entered_zone to {}\".format(last_changed))\n                    self.user_entered_zone = last_changed\n                if old_state == self.zone and new[\"state\"] != self.zone:\n                    if self.user_entered_zone is None or (\n                        last_changed - self.user_entered_zone\n                        >= datetime.timedelta(seconds=self.lingering_time)\n                    ):\n                        self.log(\n                            \"Zone of {} changed from {} to {}. Wait {} seconds until notification.\".format(\n                                self.friendly_name(entity),\n                                old_state,\n                                new[\"state\"],\n                                self.delay,\n                            )\n                        )\n                        self.timer_handle_list.append(\n                            self.run_in(self.notify_user, self.delay, old_zone=old)\n                        )\n                        self.false_positive = True\n                        self.log(\"Setting false_positive to {}\".format(self.false_positive))\n\n    def notify_user(self, kwargs):\n        # Check if user did not come back to the zone in the meantime\n        if self.get_state(self.device) != kwargs[\"old_zone\"]:\n            if self.travel_time_sensor is not None:\n                self.log(\n                    \"Updating travel_time_sensor: {}\".format(self.travel_time_sensor)\n                )\n\n                self.call_service(\n                    \"homeassistant/update_entity\", entity_id=self.travel_time_sensor\n                )\n\n                self.timer_handle_list.append(self.run_in(self.notify_user_callback, 2))\n            else:\n                self.log(\"self.travel_time_sensor is not None\")\n                self.log(\"Notify user\")\n                self.notifier.notify(\n                    self.notify_name,\n                    self.message.format(\n                        self.user_name, self.zone, divmod(self.delay, 60)[0]\n                    ),\n                )\n                self.false_positive = False\n                self.log(\"Setting false_positive to {}\".format(self.false_positive))\n\n    def notify_user_callback(self, kwargs):\n        self.log(\"Notify user\")\n        self.notifier.notify(\n            self.notify_name,\n            self.message.format(self.user_name, self.zone, divmod(self.delay, 60)[0])\n            + self.travel_time_sensor_message.format(\n                self.get_state(self.travel_time_sensor)\n            ),\n        )\n        self.false_positive = False\n        self.log(\"Setting false_positive to {}\".format(self.false_positive))\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "leavingZoneNotifier/leavingZoneNotifier.yaml",
    "content": "# leavingWorkNotifierUserOne:\n#   module: leavingZoneNotifier\n#   class: LeavingZoneNotifier\n#   app_switch: input_boolean.leaving_work_notifier_user_one\n#   device: person.kevin\n#   user_name: Kevin\n#   lingering_time: 3600\n#   delay: 120\n#   zone: Arbeit\n#   notify_name: group_notifications\n#   message: \"{} hat {} vor {} Minuten verlassen.\"\n#   #message: \"{} left {} {} minutes ago\"\n#   travel_time_sensor: sensor.travel_time_home_user_one\n#   travel_time_sensor_message: \"Es dauert circa {} Minuten bis nach Hause.\"\n#   #travel_time_sensor_message: \"The travel time is {}.\"\n#   dependencies: \n#     - Notifier\n\n# leavingWorkNotifierUserTwo:\n#   module: leavingZoneNotifier\n#   class: LeavingZoneNotifier\n#   app_switch: input_boolean.leaving_work_notifier_user_two\n#   device: person.sina\n#   user_name: Sina\n#   lingering_time: 3600\n#   delay: 120\n#   zone: !secret friendly_name_work_user_two\n#   notify_name: group_notifications\n#   message: \"{} hat {} vor {} Minuten verlassen.\"\n#   #message: \"{} left {} {} minutes ago\"\n#   travel_time_sensor: sensor.travel_time_home_user_two\n#   travel_time_sensor_message: \"Es dauert circa {} Minuten bis nach Hause.\"\n#   #travel_time_sensor_message: \"The travel time is {}.\"\n#   dependencies:\n#     - Notifier\n\n# leavingElmoNotifierUserTwo:\n#   module: leavingZoneNotifier\n#   class: LeavingZoneNotifier\n#   app_switch: input_boolean.leaving_elmo_notifier_user_two\n#   device: person.sina\n#   user_name: Sina\n#   lingering_time: 3600\n#   delay: 120\n#   zone: Elmo\n#   notify_name: group_notifications\n#   message: \"{} hat {} vor {} Minuten verlassen.\"\n#   #message: \"{} left {} {} minutes ago\"\n#   travel_time_sensor: sensor.travel_time_home_user_two\n#   travel_time_sensor_message: \"Es dauert circa {} Minuten bis nach Hause.\"\n#   #travel_time_sensor_message: \"The travel time is {}.\"\n#   dependencies: \n#     - Notifier"
  },
  {
    "path": "motionTrigger/motionTrigger.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass  # pylint: disable=import-error\nimport datetime\n\n#\n# Special version of Motion Trigger. Only trigger when Door is not open (dont want any mosquittos) and only trigger when not both smartphones are in bedroom\n#\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# sensor: binary sensor to use as trigger\n# entity_on : entity to turn on when detecting motion, can be a light, script, scene or anything else that can be turned on\n# entity_off (optional): entity to turn off when detecting motion, can be a light, script or anything else that can be turned off. Can also be a scene which will be turned on\n# sensor_type: Possible values: xiaomi, zigbee2mqtt, deconz. Default: xiaomi\n# after (optional): Only trigger after a certain time. example: 22:00\n# after_sundown (optional): true\n# delay (optional): amount of time after turning on to turn off again. If not specified defaults to 70 seconds. example: 10\n#                     if an input_number is defined it will automatically take the delay from there. example: input_number.motionTrigger_delay\n# turn_on_constraint_entities_off (optional): list of entities which have to be off for entity to be turned on. example: light.bedroom_yeelight,light.bar_table\n# turn_on_constraint_entities_on (optional): list of entities which have to be on for entity to be turned on. example: light.bedroom_yeelight,light.bar_table\n# turn_off_constraint_entities_off (optional): list of entities which have to be off for entity to be turned off. example: light.bedroom_yeelight,light.bar_table\n# turn_off_constraint_entities_on (optional): list of entities which have to be on for entity to be turned off. example: light.bedroom_yeelight,light.bar_table\n#\n# Release Notes\n#\n# Version 1.10.1:\n#   always log No entity_off defined\n#\n# Version 1.10:\n#   wait with turn_off till sensor is really off\n#\n# Version 1.9:\n#   introduced turn_on_constraints and turn_off_constraints\n#\n# Version 1.8:\n#   support for input_number as delay and delay starts on last motion not when state changes to off\n#\n# Version 1.7:\n#   support for zigbee2mqtt and xiaomi motion sensors\n#\n# Version 1.6:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.5:\n#   Added app_switch\n#\n# Version 1.4:\n#   Added options \"after, turn_on_constraint_entities_off and turn_on_constraint_entities_on\"\n#\n# Version 1.3:\n#   Only turn off entity if it was turned on by this app\n#\n# Version 1.2:\n#   Add after_sundown argument\n#\n# Version 1.1:\n#   Add ability for other apps to cancel the timer\n#\n# Version 1.0:\n#   Initial Version\n\nSENSOR_TYPE_XIAOMI = \"xiaomi\"\nSENSOR_TYPE_ZIGBEE2MQTT = \"zigbee2mqtt\"\nSENSOR_TYPE_DECONZ = \"deconz\"\n\n\nclass MotionTrigger(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle = None\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.turned_on_by_me = False  # Giggedi\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.sensor = self.args[\"sensor\"]\n        self.entity_on = self.args[\"entity_on\"]\n        self.entity_off = self.args.get(\"entity_off\")\n        try:\n            self.sensor_type = self.args[\"sensor_type\"]\n        except KeyError:\n            self.sensor_type = SENSOR_TYPE_ZIGBEE2MQTT\n        self.after = self.args.get(\"after\")\n        self.after_sundown = self.args.get(\"after_sundown\")\n        try:\n            self.delay = self.args[\"delay\"]\n            try:\n                if self.delay.startswith(\"input_number\"):\n                    self.delay_entity = self.delay\n                    self.delay = int(self.get_state(self.delay_entity).split(\".\")[0])\n                    self.listen_state_handle_list.append(\n                        self.listen_state(self.delay_changed, self.delay_entity)\n                    )\n            except AttributeError:  # does not have attribute 'startswith' -> is not of type string\n                pass\n            self.log(\"Delay changed to : {}\".format(self.delay))\n        except KeyError:\n            self.delay = 90\n        try:\n            self.turn_on_constraint_entities_off = self.args[\n                \"turn_on_constraint_entities_off\"\n            ].split(\",\")\n        except KeyError:\n            self.turn_on_constraint_entities_off = []\n        try:\n            self.turn_on_constraint_entities_on = self.args[\n                \"turn_on_constraint_entities_on\"\n            ].split(\",\")\n        except KeyError:\n            self.turn_on_constraint_entities_on = []\n        try:\n            self.turn_off_constraint_entities_off = self.args[\n                \"turn_off_constraint_entities_off\"\n            ].split(\",\")\n        except KeyError:\n            self.turn_off_constraint_entities_off = []\n        try:\n            self.turn_off_constraint_entities_on = self.args[\n                \"turn_off_constraint_entities_on\"\n            ].split(\",\")\n        except KeyError:\n            self.turn_off_constraint_entities_on = []\n\n        # Subscribe to sensors\n        if self.sensor_type == SENSOR_TYPE_XIAOMI:\n            self.listen_event_handle_list.append(\n                self.listen_event(self.motion_event_detected, \"xiaomi_aqara.motion\")\n            )\n        elif self.sensor_type in [SENSOR_TYPE_ZIGBEE2MQTT, SENSOR_TYPE_DECONZ]:\n            self.listen_state_handle_list.append(\n                self.listen_state(self.state_changed, self.sensor)\n            )\n        else:\n            self.log(f\"Unknown sensor_type: {self.sensor_type}\", level=\"ERROR\")\n\n    def delay_changed(self, entity, attribute, old, new, kwargs):\n        self.delay = int(self.get_state(self.delay_entity).split(\".\")[0])\n        self.log(f\"Delay changed to : {self.delay}\")\n\n    def motion_event_detected(self, event_name, data, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if data[\"entity_id\"] == self.sensor:\n                self.turn_on_callback(None)\n\n    def state_changed(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new == \"on\":\n                self.turn_on_callback(None)\n\n    def turn_on_callback(self, kwargs):\n        self.log(f\"Motion detected on sensor: {self.friendly_name(self.sensor)}\",)\n        turn_on = True\n        if self.after_sundown is not None:\n            if self.after_sundown and not self.sun_down():\n                turn_on = False\n                self.log(\"Is not after sundown\")\n        if self.after is not None:\n            after_time = datetime.datetime.combine(\n                datetime.date.today(),\n                datetime.time(\n                    int(self.after.split(\":\")[0]), int(self.after.split(\":\")[1])\n                ),\n            )\n            if datetime.datetime.now() > after_time:\n                turn_on = False\n                self.log(f\"Now is before {self.after}\")\n        for entity in self.turn_on_constraint_entities_off:\n            entity_state = self.get_state(entity)\n            if entity_state != \"off\":\n                turn_on = False\n                self.log(f\"{entity} is still {entity_state}\")\n                break\n        for entity in self.turn_on_constraint_entities_on:\n            entity_state = self.get_state(entity)\n            if entity_state != \"on\":\n                turn_on = False\n                self.log(f\"{entity} is still {entity_state}\")\n                break\n        if turn_on and self.get_state(self.entity_on) == \"off\":\n            self.log(f\"Motion detected: turning {self.entity_on} on\")\n            self.turn_on(self.entity_on)\n            self.turned_on_by_me = True\n        if self.turned_on_by_me and turn_on:\n            self.reset_timer()\n\n    def turn_off_callback(self, kwargs):\n        if self.get_state(self.sensor) == \"on\":\n            self.log(f\"{self.sensor} is still on\")\n            self.reset_timer()\n        else:\n            turn_off = True\n            if self.entity_off is not None:\n                for entity in self.turn_off_constraint_entities_off:\n                    entity_state = self.get_state(entity)\n                    if entity_state != \"off\":\n                        turn_off = False\n                        self.log(f\"{entity} is still {entity_state}\")\n                        break\n                for entity in self.turn_off_constraint_entities_on:\n                    entity_state = self.get_state(entity)\n                    if entity_state != \"on\":\n                        turn_off = False\n                        self.log(f\"{entity} is still {entity_state}\")\n                        break\n                if turn_off:\n                    self.log(f\"Turning {self.entity_off} off\")\n                    self.turn_off(self.entity_off)\n                    self.turned_on_by_me = False\n            else:\n                self.log(\"No entity_off defined.\")\n\n    def reset_timer(self):\n        if self.timer_handle is not None:\n            self.log(\"Resetting timer\")\n            self.timer_handle_list.remove(self.timer_handle)\n            self.cancel_timer(self.timer_handle)\n        self.log(f\"Will turn off in {self.delay}s\")\n        self.timer_handle = self.run_in(self.turn_off_callback, self.delay)\n        self.timer_handle_list.append(self.timer_handle)\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "motionTrigger/motionTrigger.yaml",
    "content": "# bedroomMotionTrigger:\n#   module: motionTrigger\n#   class: MotionTrigger\n#   app_switch: input_boolean.bedroom_motion_trigger\n#   sensor: binary_sensor.presence_bedroom\n#   entity_on: light.bedroom_yeelight\n#   sensor_type: deconz\n#   after_sundown: True\n#   turn_on_constraint_entities_off: input_boolean.sleepmode\n\n# studyroomMotionTrigger:\n#   module: motionTrigger\n#   class: MotionTrigger\n#   app_switch: input_boolean.studyroom_motion_trigger\n#   sensor: binary_sensor.presence_studyroom\n#   entity_on: light.philips_miio_light_bulb\n#   sensor_type: deconz\n#   after_sundown: True\n\n\n\n\n\n"
  },
  {
    "path": "newWifiDeviceNotify/newWifiDeviceNotify.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nfrom fritz_switch_profiles import FritzProfileSwitch\n\n#\n# App which sends a notification if a new device is found\n#\n# Args:\n#\n# notify_name: Who to notify. example: group_notifications\n# user_id: Who to notify. example: -217831\n# message: Message to use in notification. e.g. \"Unknown device connected. Hostname: {}. MAC: {}\"\n# fritzbox_url (optional): The url of your fritzbox. example: http://fritz.box\n# fritzbox_user (optional): The user to login to your fritzbox. example: ''\n# fritzbox_password (optional): The password to login to your fritzbox. example: 'mysecurepassword'\n# fritzbox_profile_name (optional): Name of the profile with Internet Access. example: 'Unbeschränkt'\n# fritzbox_message_allow_access (optional): Message to use in telegram message. example: \"Should I let the device access the Internet?\"\n# fritzbox_message_access_allowed (optional): Message to use in telegram message. example: \"I have let the device access the internet. How kind of me!\"\n# fritzbox_message_access_blocked (optional): Message to use in telegram message. example: \"I have saved the device from the dangers of the Internet\"\n#\n# Release Notes\n#\n# Version 1.5.2:\n#   Handle hostname is None\n#\n# Version 1.5.1:\n#   Wait till entity is fully created\n#\n# Version 1.5:\n#   Add support for Unifi > 0.98 and other integrations that are not using known_devices / device_tracker_new_device.\n#   Fritzbox support is now optional\n#\n# Version 1.4:\n#   Don't use Alexa for Notifications\n#\n# Version 1.3:\n#   Fix for hostnames containing \"-\"\n#\n# Version 1.2:\n#   Make us of fritz_switch_profiles to control Internet access\n#\n# Version 1.1:\n#   use Notify App\n#\n# Version 1.0:\n#   Initial Version\n\nIDENTIFIER_DELIMITER = \"-\"\nALLOW_CALLBACK_IDENTIFIER = \"/NEWDEVICENOTIFYALLOW\"\nBLOCK_CALLBACK_IDENTIFIER = \"/NEWDEVICEBNOTIFYLOCK\"\n\n\nclass DeviceNotify(hass.Hass):\n    def initialize(self):\n        self.listen_event_handle_list = []\n\n        self.notify_name = self.args[\"notify_name\"]\n        self.user_id = self.args[\"user_id\"]\n        self.message = self.args[\"message\"]\n        self.fritzbox_url = self.args.get(\"fritzbox_url\")\n        self.fritzbox_user = self.args.get(\"fritzbox_user\")\n        self.fritzbox_password = self.args.get(\"fritzbox_password\")\n        self.fritzbox_profile_name = self.args.get(\"fritzbox_profile_name\")\n        self.fritzbox_message_allow_access = self.args.get(\n            \"fritzbox_message_allow_access\"\n        )\n        self.fritzbox_message_access_allowed = self.args.get(\n            \"fritzbox_message_access_allowed\"\n        )\n        self.fritzbox_message_access_blocked = self.args.get(\n            \"fritzbox_message_access_blocked\"\n        )\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.listen_event_handle_list.append(\n            self.listen_event(self.newDeviceCallback, \"device_tracker_new_device\")\n        )\n        self.listen_event_handle_list.append(\n            self.listen_event(\n                self.entityRegistryUpdatedCallback, \"entity_registry_updated\"\n            )\n        )\n        self.listen_event_handle_list.append(\n            self.listen_event(self.receiveTelegramCallback, \"telegram_callback\")\n        )\n\n    def entityRegistryUpdatedCallback(self, event_name, data, kwargs):\n        \"\"\"Callback method for entity_registry_updated event\"\"\"\n        self.log(\"event_name: {}\".format(event_name))\n        self.log(\"data: {}\".format(data))\n        if data[\"action\"] == \"create\":\n            # Wait recursively until the entity was fully created\n            self.run_in(\n                self.handleNewRegistryEntity, 1, new_entity_id=data[\"entity_id\"]\n            )\n\n    def handleNewRegistryEntity(self, kwargs):\n        \"\"\"Wait till Entity is available and notify if it was created by a router\"\"\"\n        full_state = self.get_state(kwargs[\"new_entity_id\"], attribute=\"all\")\n        if full_state is None:\n            self.run_in(\n                self.handleNewRegistryEntity, 1, new_entity_id=kwargs[\"new_entity_id\"]\n            )\n        else:\n            new_entity_attributes = full_state[\"attributes\"]\n            if new_entity_attributes.get(\"source_type\") == \"router\":\n                hostname = new_entity_attributes.get(\"hostname\")\n                mac = new_entity_attributes.get(\"mac\")\n                self.notifyNewDeviceAdded(hostname, mac)\n                if self.fritzbox_url is not None and hostname is not None:\n                    self.askForProfileChange(hostname)\n\n    def newDeviceCallback(self, event_name, data, kwargs):\n        \"\"\"Callback method for device_tracker_new_device event\"\"\"\n        self.log(\"event_name: {}\".format(event_name))\n        self.log(\"data: {}\".format(data))\n        self.notifyNewDeviceAdded(data[\"host_name\"], data[\"mac\"])\n        if self.fritzbox_url is not None:\n            self.askForProfileChange(data[\"host_name\"])\n\n    def notifyNewDeviceAdded(self, host_name, mac):\n        \"\"\"Send a notification message when a new device was added\"\"\"\n        message = self.message.format(host_name, mac)\n        self.notifier.notify(self.notify_name, message, useAlexa=False)\n\n    def askForProfileChange(self, host_name):\n        \"\"\"Asks the user if he wants to allow the new device to have internet access\"\"\"\n        self.log(\"Asking for profile change\")\n        if host_name is None:\n            host_name = \"\"\n        keyboard = [\n            [\n                (\n                    \"Zulassen\",\n                    ALLOW_CALLBACK_IDENTIFIER + IDENTIFIER_DELIMITER + host_name,\n                )\n            ],\n            [(\"Sperren\", BLOCK_CALLBACK_IDENTIFIER + IDENTIFIER_DELIMITER + host_name)],\n        ]\n        self.log(\"keyboard is: {}\".format(keyboard), level=\"DEBUG\")\n        self.call_service(\n            \"telegram_bot/send_message\",\n            target=self.user_id,\n            message=self.message_allow_access,\n            inline_keyboard=keyboard,\n        )\n\n    def receiveTelegramCallback(self, event_name, data, kwargs):\n        \"\"\"Event listener for Telegram callback queries.\"\"\"\n        self.log(\"callback data: {}\".format(data))\n        data_callback = data[\"data\"]\n        callback_id = data[\"id\"]\n        chat_id = data[\"chat_id\"]\n        message_id = data[\"message\"][\"message_id\"]\n        text = data[\"message\"][\"text\"]\n        from_first = data[\"from_first\"]\n\n        if data_callback.startswith(ALLOW_CALLBACK_IDENTIFIER):\n            host_name = data_callback.split(IDENTIFIER_DELIMITER, maxsplit=1)[1]\n            self.log(\n                \"Received Telegram Callback to allow internet access for: {}\".format(\n                    host_name\n                )\n            )\n            self.call_service(\n                \"telegram_bot/answer_callback_query\",\n                message=\"Dankeschön!\",\n                callback_query_id=callback_id,\n            )\n            self.call_service(\n                \"telegram_bot/edit_message\",\n                chat_id=chat_id,\n                message_id=message_id,\n                message=self.message_access_allowed,\n                inline_keyboard=[],\n            )\n            self.allowDevice(host_name)\n        elif data_callback.startswith(BLOCK_CALLBACK_IDENTIFIER):\n            host_name = data_callback.split(IDENTIFIER_DELIMITER, maxsplit=1)[1]\n            self.log(\n                \"Received Telegram Callback to block internet access for: {}\".format(\n                    host_name\n                )\n            )\n            self.call_service(\n                \"telegram_bot/answer_callback_query\",\n                message=\"Dankeschön!\",\n                callback_query_id=callback_id,\n            )\n            self.call_service(\n                \"telegram_bot/edit_message\",\n                chat_id=chat_id,\n                message_id=message_id,\n                message=self.message_access_blocked,\n                inline_keyboard=[],\n            )\n\n    def allowDevice(self, host_name):\n        \"\"\"Login to fritzbox and assign the 'Internet Access' profile to the device with the given host name\"\"\"\n        fps = FritzProfileSwitch(\n            self.fritzbox_url, self.fritzbox_user, self.fritzbox_password\n        )\n        devices = fps.get_devices()\n        profiles = fps.get_profiles()\n\n        # Get the device_id for the host name\n        device_id = None\n        for device in devices:\n            if device[\"name\"] == host_name:\n                device_id = device[\"id1\"]\n\n        if device_id:\n            # Get the profile id for the Internet access profile\n            profile_id = None\n            for profile in profiles:\n                if profile[\"name\"] == self.fritzbox_profile_name:\n                    profile_id = profile[\"id\"]\n\n            if profile_id:\n                # construct the array to set the profile for the device\n                profile_for_device = [(device_id, profile_id)]\n                # set device profile\n                fps.set_profiles(profile_for_device)\n            else:\n                message = \"Could not find profile with the name: {}\".format(\n                    self.fritzbox_profile_name\n                )\n                self.log(message)\n                self.notifier.notify(self.notify_name, message)\n        else:\n            message = \"Could not find device with the hostname: {}\".format(host_name)\n            self.log(message)\n            self.notifier.notify(self.notify_name, message)\n\n    def terminate(self):\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n"
  },
  {
    "path": "newWifiDeviceNotify/newWifiDeviceNotify.yaml",
    "content": "newWifiDeviceNotify:\n  module: newWifiDeviceNotify\n  class: DeviceNotify\n  notify_name: group_notifications\n  user_id: !secret telegram_user_id\n  message: \"Unbekanntes Gerät entdeckt. Hostname: {}. MAC: {}.\"\n  #message: \"Unknown device connected. Hostname: {}. MAC: {}\"\n  #fritzbox_url: fritzbox_url\n  #fritzbox_user: ''\n  #fritzbox_password: fritzbox_password\n  #fritzbox_profile_name: 'Unbeschränkt'\n  #fritzbox_message_allow_access: \"Soll ich das Gerät ins Internet lassen?\"\n  #fritzbox_message_allow_access: \"Should I let the device access the Internet?\"\n  #fritzbox_message_access_allowed: \"Großzügig wie ich bin, habe ich das Gerät ins Internet gelassen\"\n  #fritzbox_message_access_allowed: \"I have let the device access the internet. How kind of me!\"\n  #fritzbox_message_access_blocked: \"Ich habe das Gerät vor den Schrecken des Internets bewahrt\"\n  #fritzbox_message_access_blocked: \"I have saved the device from the dangers of the Internet\"\n  dependencies: \n    - Notifier"
  },
  {
    "path": "newWifiDeviceNotify/requirements.txt",
    "content": "fritz_switch_profiles >= 1.0.0"
  },
  {
    "path": "nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which notifies the user to start to the next appointment\n#\n#\n# Args:\n# sensor: sensor to watch. example: sensor.calc_leave_time\n# notify_input_boolean: input_boolean determining whether to notify. example: input_boolean.announce_time_to_leave\n# notify_name: Who to notify. example: group_notifications\n# destination_name_sensor: Sensor which holds the Destination name to use in notification. example: sensor.cal_next_appointment_location\n# travel_time_sensor: sensor which holds the travel time. example: sensor.travel_time_next_appointment_location\n# message: message to use in notification\n#\n# Release Notes\n#\n# Version 1.5:\n#   Catch None when Home Assistant is still starting\n#\n# Version 1.4.2:\n#   Fix notification for location_name \"None\"\n#\n# Version 1.4.1:\n#   Fix google maps url message\n#\n# Version 1.4:\n#   Don't include Google Maps Link in Notification for Alexa\n#\n# Version 1.3:\n#   Also notify when Notification time is in the past\n#\n# Version 1.2:\n#   use Notify App\n#\n# Version 1.1:\n#   Using globals, message now directly in own yaml instead of message module\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass NextAppointmentLeaveNotifier(hass.Hass):\n    def initialize(self):\n\n        self.listen_state_handle_list = []\n\n        self.sensor = self.args[\"sensor\"]\n        self.notify_input_boolean = self.args[\"notify_input_boolean\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.destination_name_sensor = self.args[\"destination_name_sensor\"]\n        self.travel_time_sensor = self.args[\"travel_time_sensor\"]\n        self.message = self.args[\"message\"]\n        self.message_google_link = self.args[\"message_google_link\"]\n\n        self.timer_handle = None\n\n        self.google_source_url = \"http://maps.google.com/maps?q=\"\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        # Used to check of user got already notified for this event\n        self.location_of_last_notified_event = \"\"\n        self.set_timer_handle()\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.sensor)\n        )\n\n    def state_change(self, entity, attributes, old, new, kwargs):\n        try:\n            self.cancel_timer(self.timer_handle)\n            self.timer_handle = None\n        except AttributeError:\n            # Timer was not set\n            pass\n        self.set_timer_handle()\n\n    def set_timer_handle(self):\n        destination_name = self.get_state(self.destination_name_sensor)\n        self.log(f\"destination_name_sensor: {destination_name}\")\n        if self.get_state(self.sensor) != None:\n            if destination_name != \"unknown\" and destination_name != \"None\":\n                notification_time = datetime.datetime.strptime(\n                    self.get_state(self.sensor), \"%Y-%m-%d %H:%M\"\n                )\n                if self.get_state(self.travel_time_sensor) != \"unknown\":\n                    try:\n                        self.timer_handle = self.run_at(\n                            self.notify_user, notification_time\n                        )\n                        self.log(f\"Will notify at {notification_time}\")\n                    except ValueError:\n                        self.log(\"Notification time is in the past\")\n                        self.timer_handle = self.run_at(\n                            self.notify_user, datetime.datetime.now()\n                        )\n\n    def notify_user(self, *kwargs):\n        if self.get_state(self.notify_input_boolean) == \"on\":\n            location_name = self.get_state(self.destination_name_sensor)\n            if location_name != \"None\":\n                if self.location_of_last_notified_event == location_name:\n                    self.log(f\"User already got notified for {location_name}\")\n                else:\n                    google_maps_url = self.google_source_url + location_name.replace(\n                        \" \", \"+\"\n                    )\n                    self.log(\"Notify user\")\n                    self.notifier.notify(\n                        self.notify_name,\n                        self.message.format(\n                            location_name, self.get_state(self.travel_time_sensor)\n                        ),\n                    )\n                    self.notifier.notify(\n                        self.notify_name,\n                        self.message_google_link.format(google_maps_url),\n                        useAlexa=False,\n                    )\n                    self.location_of_last_notified_event = location_name\n            else:\n                self.log(f\"location_name: {location_name}\")\n        else:\n            self.log(\"Notification is turned off\")\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n        if self.timer_handle is not None:\n            self.cancel_timer(self.timer_handle)\n"
  },
  {
    "path": "nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.yaml",
    "content": "# nextAppointmentLeaveNotifier:\n#   module: nextAppointmentLeaveNotifier\n#   class: NextAppointmentLeaveNotifier\n#   sensor: sensor.calc_leave_time\n#   notify_input_boolean: input_boolean.announce_time_to_leave\n#   notify_name: Kevin\n#   input_number: input_number.leave_time_offset\n#   destination_name_sensor: sensor.cal_next_appointment_location\n#   travel_time_sensor: sensor.travel_time_next_appointment_location\n#   message: \"Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten.\"\n#   #message: \"It's time to leave to {}. It will take {} minutes.\"\n#   message_google_link: \" Hier ist ein Google Maps Link: {}\"\n#   #message_google_link: \" Here is a Google Maps Link: {}\"\n#   dependencies:\n#     - Notifier\n\n# nextAppointmentLeaveNotifierUserTwo:\n#   module: nextAppointmentLeaveNotifier\n#   class: NextAppointmentLeaveNotifier\n#   sensor: sensor.calc_leave_time_user_two\n#   notify_input_boolean: input_boolean.announce_time_to_leave_user_two\n#   notify_name: Sina\n#   input_number: input_number.leave_time_offset_user_two\n#   destination_name_sensor: sensor.cal_next_appointment_location_user_two\n#   travel_time_sensor: sensor.travel_time_next_appointment_location_user_two\n#   message: \"Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten.\"\n#   #message: \"It's time to leave to {}. It will take {} minutes.\"\n#   message_google_link: \" Hier ist ein Google Maps Link: {}\"\n#   #message_google_link: \" Here is a Google Maps Link: {}\"\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "notifier/notifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# Centralizes messaging. Among other things, it will determine whether a user is at home and if yes in which room.\n# Then Alexa in that room will be used additionally to Telegram\n#\n# Args:\n#  app_switch_alexa: mutes alexa. example:\n#  alexa_tts: name of the notification service. example: alexa_media\n#  alexa_media_player: media player entity of alexa to use. example: media_player.kevins_echo_dot_oben\n#  user_location_sensors: sensors showing the location of users\n#  alexa_to_location_mapping: mapping of which alexa device is used for which room\n#\n#\n#\n# Release Notes\n#\n# Version 1.5:\n#   Allow multiple alexa_media_player\n#\n# Version 1.4:\n#   Use type announce\n#\n# Version 1.3:\n#   Use Version 1.2.1 of alexa_media_player\n#\n# Version 1.2.1:\n#   Fix: Enqueue alexa messages\n#\n# Version 1.2:\n#   Enqueue alexa messages\n#\n# Version 1.1:\n#   Remove media_player constraints. If connected via bluetooth alexa can always be heard\n#\n# Version 1.0:\n#   Initial Version\n\n__GROUP_NOTIFICATIONS__ = \"group_notifications\"\n__NOTIFY__ = \"notify/\"\n__WAIT_TIME__ = 5  # seconds\n\n\nclass Notifier(hass.Hass):\n    def initialize(self):\n        self.timer_handle_list = []\n\n        self.alexa_tts = self.args[\"alexa_tts\"]\n        self.alexa_media_player = self.args[\"alexa_media_player\"].split(\",\")\n        self.app_switch_alexa = self.args[\"app_switch_alexa\"]\n\n        self.last_alexa_notification_time = None\n\n    def notify(self, notify_name, message, useAlexa=True, useTelegram=True):\n        if useTelegram:\n            self.log(\"Notifying via Telegram\")\n            self.call_service(__NOTIFY__ + notify_name, message=message)\n        if useAlexa and self.get_state(self.app_switch_alexa) == \"on\":\n            self.log(\"Notifying via Alexa\")\n            # check last message\n            if self.last_alexa_notification_time is not None and (\n                datetime.datetime.now() - self.last_alexa_notification_time\n                < datetime.timedelta(seconds=__WAIT_TIME__)\n            ):\n                self.timer_handle_list.append(\n                    self.run_in(self.notify_callback, __WAIT_TIME__, message=message)\n                )\n            else:\n                self.run_in(self.notify_callback, 0, message=message)\n\n    def notify_callback(self, kwargs):\n        self.last_alexa_notification_time = datetime.datetime.now()\n        self.call_service(\n            __NOTIFY__ + self.alexa_tts,\n            data={\"type\": \"announce\", \"method\": \"speak\"},\n            target=self.alexa_media_player,\n            message=kwargs[\"message\"],\n        )\n\n    def getAlexaDeviceForUserLocation(self, notify_name):\n        if notify_name == __GROUP_NOTIFICATIONS__:\n            return self.args[\"alexa_to_location_mapping\"][\"Wohnzimmer\"]\n        elif notify_name.lower() in self.args[\"user_location_sensors\"]:\n            location = self.get_state(\n                self.args[\"user_location_sensors\"][notify_name.lower()]\n            )\n            if location in self.args[\"alexa_to_location_mapping\"]:\n                return self.args[\"alexa_to_location_mapping\"][location]\n            else:\n                return None\n        else:\n            self.log(\"Unknown notify_name: {}\".format(notify_name))\n            return None\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "notifier/notifier.yaml",
    "content": "Notifier:\n  module: notifier\n  class: Notifier\n  app_switch_alexa: input_boolean.notifier_alexa\n  alexa_tts: alexa_media\n  alexa_media_player: media_player.kevins_echo_dot_oben,media_player.kevin_s_echo_dot_unten,media_player.kevins_echo,media_player.kevins_echo_dot\n  user_location_sensors:\n    kevin: sensor.location_user_one\n    sina: sensor.location_user_two\n  alexa_to_location_mapping:\n    Wohnzimmer: media_player.kevins_echo_dot_oben\n    Küche: media_player.kevins_echo_dot_oben\n    Balkon Oben: media_player.kevins_echo_dot_oben\n    Arbeitszimmer: media_player.kevin_s_echo_dot_unten\n    Schlafzimmer: media_player.kevin_s_echo_dot_unten"
  },
  {
    "path": "notifyOfActionWhenAway/notifyOfActionWhenAway.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App to send notification when a sensor changes state\n#\n# Args:\n#\n#  app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n#  sensor: sensor to monitor. example: sensor.upstairs_smoke\n#  isHome: input_boolean which shows if someone is home. example: input_boolean.isHome\n#  isHome_delay: delay to wait for user to come home before notifying. example: 10\n#\n# Release Notes\n#\n# Version 1.3.1:\n#   Use consistent message variable\n#\n# Version 1.3:\n#   use Notify App\n#\n# Version 1.2:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.1:\n#   Added isHome_delay\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass NotifyOfActionWhenAway(hass.Hass):\n    def initialize(self):\n\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.isHome_delay = self.args[\"isHome_delay\"]\n        self.isHome = self.args[\"isHome\"]\n        self.message = self.args[\"message\"]\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        for sensor in self.args[\"sensor\"].split(\",\"):\n            self.listen_state_handle_list.append(\n                self.listen_state(self.state_change, sensor)\n            )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new != old:\n                if self.get_state(self.isHome) == \"off\":\n                    if (\n                        entity.startswith(\"binary_sensor.motion_sensor\")\n                        and new == \"off\"\n                    ):\n                        pass\n                    else:\n                        self.log(\n                            \"Waiting {} seconds for someone to come home\".format(\n                                self.isHome_delay\n                            )\n                        )\n                        self.timer_handle_list.append(\n                            self.run_in(\n                                self.notify_if_no_one_home,\n                                self.isHome_delay,\n                                sensor=entity,\n                                new=new,\n                            )\n                        )\n\n    def notify_if_no_one_home(self, kwargs):\n        if self.get_state(self.isHome) == \"off\":\n            self.log(\n                \"{} changed to {}\".format(\n                    self.friendly_name(kwargs[\"sensor\"]), kwargs[\"new\"]\n                )\n            )\n            self.notifier.notify(\n                self.notify_name,\n                self.message.format(\n                    self.friendly_name(kwargs[\"sensor\"]), kwargs[\"new\"]\n                ),\n                useAlexa=False,\n            )\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "notifyOfActionWhenAway/notifyOfActionWhenAway.yaml",
    "content": "notifyOfActionWhenAway:\n  module: notifyOfActionWhenAway\n  class: NotifyOfActionWhenAway\n  app_switch: input_boolean.notify_of_action_when_away\n  sensor: \"binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\\\n  binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\\\n  binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\\\n  binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\\\n  binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\\\n  binary_sensor.presence_stairs,binary_sensor.presence_bathroom,binary_sensor.presence_lobby,\\\n  binary_sensor.presence_bedroom,binary_sensor.presence_kitchen,binary_sensor.presence_upper_stairs,\\\n  binary_sensor.contact_badfenster,binary_sensor.contact_upper_bathroom_window_tilted\"\n  isHome: input_boolean.is_home\n  notify_name: group_notifications\n  isHome_delay: 20\n  message: \"Alarm: {} ist gewechselt auf {}\"\n  #message: \"Alarm: {} changed to {}\"\n  dependencies: \n    - Notifier"
  },
  {
    "path": "plantWateringNotifier/plantWateringNotifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which reminds you daily to water your plants if it won't rain\n#\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# rain_precip_sensor: sensor which shows rain probability. example: sensor.dark_sky_precip_probability\n# rain_precip_intensity_sensor: sensor which shows rain probability. example: sensor.dark_sky_precip_intensity\n# precip_type_sensor: sensor which shows precip type. example: sensor.dark_sky_precip\n# notify_name: Who to notify. example: group_notifications\n# user_id: The user_id of the telegram user to ask whether he knows an unknown face. example: 812391\n# reminder_acknowledged_entity: Input Boolean to store the information whether the user acknowledged the notification.\n#                        This prevents new notifications upon HA/Appdaemon restart.\n#                        example: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged\n# message: localized message to use in notification\n#\n# Release Notes\n#\n# Version 1.5.1:\n#   Use consistent message variable\n#\n# Version 1.5:\n#   use Notify App\n#\n# Version 1.4:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.3:\n#   Added app_switch\n#\n# Version 1.2:\n#   Update original message with information when the reminder was acknowledged\n#\n# Version 1.1:\n#   Store reminder acknowledged in an input_boolean to prevent notifications after HA/Appdaemon restarts\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass PlantWateringNotifier(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.rain_precip_sensor = self.args[\"rain_precip_sensor\"]\n        self.rain_precip_intensity_sensor = self.args[\"rain_precip_intensity_sensor\"]\n        self.precip_type_sensor = self.args[\"precip_type_sensor\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.user_id = self.args[\"user_id\"]\n        self.reminder_acknowledged_entity = self.args[\"reminder_acknowledged_entity\"]\n        self.message = self.args[\"message\"]\n        self.message_not_needed = self.args[\"message_not_needed\"]\n        self.message_evening = self.args[\"message_evening\"]\n\n        self.intensity_minimum = 2  # mm/h\n        self.propability_minimum = 90  # %\n\n        self.keyboard_callback = \"/plants_watered\"\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.reminder_acknowledged = self.get_state(self.reminder_acknowledged_entity)\n\n        self.listen_event_handle_list.append(\n            self.listen_event(self.receive_telegram_callback, \"telegram_callback\")\n        )\n\n        # Remind daily at 08:00\n        self.timer_handle_list.append(\n            self.run_daily(self.run_morning_callback, datetime.time(8, 0, 0))\n        )\n        # Remind daily at 18:00\n        self.timer_handle_list.append(\n            self.run_daily(self.run_evening_callback, datetime.time(18, 0, 0))\n        )\n\n    def run_morning_callback(self, kwargs):\n        \"\"\"Check if it will rain and if not remind the user to water the plants\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n            precip_propability = self.get_state(self.rain_precip_sensor)\n            self.log(\"Rain Propability: {}\".format(float(precip_propability)))\n            precip_intensity = self.get_state(self.rain_precip_intensity_sensor)\n            self.log(\"Rain Intensity: {}\".format(float(precip_intensity)))\n            precip_type = self.get_state(self.precip_type_sensor)\n            self.log(\"Precip Type: {}\".format(precip_type))\n\n            if (\n                precip_propability != None\n                and precip_propability != \"\"\n                and float(precip_propability) < self.propability_minimum\n                and precip_intensity != None\n                and precip_intensity != \"\"\n                and float(precip_intensity) < self.intensity_minimum\n            ):\n                self.turn_off(self.reminder_acknowledged_entity)\n                self.log(\"Setting reminder_acknowledged to: {}\".format(\"off\"))\n                self.log(\"Reminding user\")\n                keyboard = [[(\"Hab ich gemacht\", self.keyboard_callback)]]\n                self.call_service(\n                    \"telegram_bot/send_message\",\n                    target=self.user_id,\n                    message=self.message.format(precip_propability),\n                    inline_keyboard=keyboard,\n                )\n\n            else:\n                self.turn_on(self.reminder_acknowledged_entity)\n                self.log(\"Setting reminder_acknowledged to: {}\".format(\"off\"))\n                self.log(\"Notifying user\")\n                self.notifier.notify(\n                    self.notify_name,\n                    self.message_not_needed.format(\n                        precip_propability, precip_intensity\n                    ),\n                )\n\n    def run_evening_callback(self, kwargs):\n        \"\"\"Remind user to water the plants he if didn't acknowledge it\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n            if self.get_state(self.reminder_acknowledged_entity) == \"off\":\n                self.log(\"Reminding user\")\n                self.call_service(\n                    \"notify/\" + self.notify_name, message=self.message_evening\n                )\n\n    def receive_telegram_callback(self, event_name, data, kwargs):\n        \"\"\"Event listener for Telegram callback queries.\"\"\"\n        assert event_name == \"telegram_callback\"\n        data_callback = data[\"data\"]\n        callback_id = data[\"id\"]\n        chat_id = data[\"chat_id\"]\n        message_id = data[\"message\"][\"message_id\"]\n        text = data[\"message\"][\"text\"]\n        self.log(\"callback data: {}\".format(data), level=\"DEBUG\")\n\n        if data_callback == self.keyboard_callback:  # Keyboard editor:\n            # Answer callback query\n            self.call_service(\n                \"telegram_bot/answer_callback_query\",\n                message=\"Super!\",\n                callback_query_id=callback_id,\n            )\n            self.turn_on(self.reminder_acknowledged_entity)\n            self.log(\"Setting reminder_acknowledged to: {}\".format(\"on\"))\n\n            self.call_service(\n                \"telegram_bot/edit_message\",\n                chat_id=chat_id,\n                message_id=message_id,\n                message=text\n                + \" Hast du um {}:{} erledigt.\".format(\n                    datetime.datetime.now().hour, datetime.datetime.now().minute\n                ),\n                inline_keyboard=[],\n            )\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "plantWateringNotifier/plantWateringNotifier.yaml",
    "content": "# plantWateringNotifier:\n#   module: plantWateringNotifier\n#   class: PlantWateringNotifier\n#   app_switch: input_boolean.plant_watering_notifier\n#   rain_precip_sensor: sensor.dark_sky_precip_probability\n#   rain_precip_intensity_sensor: sensor.dark_sky_precip_intensity\n#   precip_type_sensor: sensor.dark_sky_precip\n#   notify_name: group_notifications\n#   user_id: !secret telegram_user_id\n#   reminder_acknowledged_entity: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged\n#   message: \"Die Regenwahrscheinlichkeit beträgt heute nur {}. Vergiss nicht die Pflanzen zu gießen!\"\n#   #message: \"The Rain Propability is only {}. Don't forget to water the plants!\"\n#   message_not_needed: \"Es wird heute mit einer Wahrscheinlichkeit von {} Prozent ungefähr {} Millimeter pro Stunde regnen. Du brauchst nicht selbst gießen.\"\n#   #message_not_needed: \"It will rain today {} millimeter per hour with a propability of {}. You don't have to water your plants\"\n#   message_evening: \"Ich bin mir nicht sicher ob du vergessen hast die Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran.\"\n#   #message_evening: \"I'm not sure whether you waterd your plants, so I thought I better remind you again\"\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "pollenNotifier/pollenNotifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which notifies you when there is a pollen forecast for today\n# Used with sensors getting data from https://opendata.dwd.de/climate_environment/health/alerts/s31fg.json\n#\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# pollen_sensor: sensor which shows pollen for today. example: sensor.pollen_101_roggen_today\n# pollen_name: Name of the allergen. example: Roggen\n# notify_name: Who to notify. example: group_notifications\n# notify_time: When to notify. example: 08:00\n# notify_threshold: Minimum level of pollen needed to notify. example: 1.0\n# message: localized message to use in notification\n#\n# Release Notes\n#\n# Version 1.3.1:\n#   Use consistent message variable\n#\n# Version 1.3:\n#   use Notify App\n#\n# Version 1.2:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.1:\n#   Added notify_threshold\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass PollenNotifier(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.pollen_sensor = self.args[\"pollen_sensor\"]\n        self.pollen_name = self.args[\"pollen_name\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.notify_time = self.args[\"notify_time\"]\n        self.notify_threshold = self.args[\"notify_threshold\"]\n        self.message = self.args[\"message\"]\n        self.message_no_data = self.args[\"message_no_data\"]\n\n        self.mappingsdict = {}\n        self.mappingsdict[\"-1\"] = \"keine Daten\"\n        self.mappingsdict[\"0\"] = \"Keine\"\n        self.mappingsdict[\"0-1\"] = \"Keine bis Geringe\"\n        self.mappingsdict[\"1\"] = \"Geringe\"\n        self.mappingsdict[\"1-2\"] = \"Geringe bis Mittlere\"\n        self.mappingsdict[\"2\"] = \"Mittlere\"\n        self.mappingsdict[\"2-3\"] = \"Mittlere bis Hohe\"\n        self.mappingsdict[\"3\"] = \"Hohe\"\n\n        self.level_mapping_dict = {}\n        self.level_mapping_dict[\"-1\"] = -1.0\n        self.level_mapping_dict[\"0\"] = 0.0\n        self.level_mapping_dict[\"0-1\"] = 0.5\n        self.level_mapping_dict[\"1\"] = 1.0\n        self.level_mapping_dict[\"1-2\"] = 1.5\n        self.level_mapping_dict[\"2\"] = 2.0\n        self.level_mapping_dict[\"2-3\"] = 2.5\n        self.level_mapping_dict[\"3\"] = 3\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        hours = self.notify_time.split(\":\", 1)[0]\n        minutes = self.notify_time.split(\":\", 1)[1]\n        self.timer_handle_list.append(\n            self.run_daily(\n                self.run_daily_callback, datetime.time(int(hours), int(minutes), 0)\n            )\n        )\n\n    def run_daily_callback(self, kwargs):\n        \"\"\"Check if there is an pollen forcast and notify the user about it\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n            pollen_sensor_state = self.get_state(self.pollen_sensor)\n            self.log(\n                \"{} Belastung Heute: {}\".format(self.pollen_name, pollen_sensor_state)\n            )\n\n            if pollen_sensor_state == \"-1\":\n                message = self.message_no_data.format(\"Heute\", self.pollen_name)\n            elif pollen_sensor_state == \"0\":\n                message = (\n                    self.message.format(\n                        \"Heute\",\n                        self.mappingsdict[pollen_sensor_state],\n                        self.pollen_name,\n                    )\n                    + \" Genieß den Tag!\"\n                )\n            else:\n                message = self.message.format(\n                    \"Heute\", self.mappingsdict[pollen_sensor_state], self.pollen_name\n                )\n\n            if self.level_mapping_dict[pollen_sensor_state] >= float(\n                self.notify_threshold\n            ):\n                self.log(\"Notifying user\")\n                self.notifier.notify(self.notify_name, message)\n            else:\n                self.log(\"Threshold not met. Not notifying user\")\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "pollenNotifier/pollenNotifier.yaml",
    "content": "roggenNotifier:\n  module: pollenNotifier\n  class: PollenNotifier\n  app_switch: input_boolean.roggen_notifier\n  pollen_sensor: sensor.pollen_101_roggen_today\n  pollen_name: Roggen\n  notify_name: group_notifications\n  notify_time: 08:00\n  notify_threshold: 1.0\n  message: \"{} ist {} {} Belastung.\"\n  #message: \"The {} intensity {} is {}.\"\n  message_no_data: \"Ich habe {} leider keine Daten für {}.\"\n  #message_no_data: \"{} I have no pollen data for {}.\"\n  dependencies: \n    - Notifier\n\ngraeserNotifier:\n  module: pollenNotifier\n  class: PollenNotifier\n  app_switch: input_boolean.graeser_notifier\n  pollen_sensor: sensor.pollen_101_graeser_today\n  pollen_name: Gräser\n  notify_name: group_notifications\n  notify_time: 08:00\n  notify_threshold: 1.0\n  message: \"{} ist {} {} Belastung.\"\n  #message: \"The {} intensity {} is {}.\"\n  message_no_data: \"Ich habe {} leider keine Daten für {}.\"\n  #message_no_data: \"{} I have no pollen data for {}.\"\n  dependencies: \n    - Notifier\n\nbirkeNotifier:\n  module: pollenNotifier\n  class: PollenNotifier\n  app_switch: input_boolean.birke_notifier\n  pollen_sensor: sensor.pollen_101_birke_today\n  pollen_name: Birke\n  notify_name: group_notifications\n  notify_time: 08:00\n  notify_threshold: 1.0\n  message: \"{} ist {} {} Belastung.\"\n  #message: \"The {} intensity {} is {}.\"\n  message_no_data: \"Ich habe {} leider keine Daten für {}.\"\n  #message_no_data: \"{} I have no pollen data for {}.\"\n  dependencies: \n    - Notifier"
  },
  {
    "path": "powerUsageNotification/powerUsageNotification.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which notifies you when a power usage sensor indicated a device is on/off\n#\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# sensor: power sensor. example: sensor.dishwasher_power_usage\n# input_boolean (optional): input_boolean to set to on/off\n# notify_name: Who to notify. example: group_notifications\n# notify_start (optional): Notify if start was detected: example True (default: True)\n# notify_start_use_alexa (optional): Notify with alexa if start was detected: example True (default: True)\n# notify_end (optional): Notify if end was detected: example True (default: True)\n# notify_end_use_alexa (optional): Notify with alexa if end was detected: example True (default: True)\n# delay: seconds to wait until a the device is considered \"off\". example: 60\n# threshold: amount of \"usage\" which indicated the device is on. example: 2\n# alternative_name: Name to use in notification. example: Waschmaschine\n# message: Message to use when notifying device is on\n# message_off: Message to use when notifying device is off\n#\n# Release Notes\n#\n# Version 1.5:\n#   Catch unknown\n#\n# Version 1.4:\n#   Added notify_start, notify_start_use_alexa, notify_end, notify_end_use_alexa, input_boolean\n#\n# Version 1.3:\n#   use Notify App\n#\n# Version 1.2:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.1:\n#   Added app_switch\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass PowerUsageNotification(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.input_boolean = self.args.get(\"input_boolean\")\n        self.sensor = self.args[\"sensor\"]\n        self.alternative_name = self.args[\"alternative_name\"]\n        self.notify_name = self.args[\"notify_name\"]\n        try:\n            self.notify_start = self.args[\"notify_start\"]\n        except KeyError:\n            self.notify_start = True\n        try:\n            self.notify_start_use_alexa = self.args[\"notify_start_use_alexa\"]\n        except KeyError:\n            self.notify_start_use_alexa = True\n        try:\n            self.notify_end = self.args[\"notify_end\"]\n        except KeyError:\n            self.notify_end = True\n        try:\n            self.notify_end_use_alexa = self.args[\"notify_end_use_alexa\"]\n        except KeyError:\n            self.notify_end_use_alexa = True\n        self.delay = self.args[\"delay\"]\n        self.threshold = self.args[\"threshold\"]\n        self.message = self.args[\"message\"]\n        self.message_off = self.args[\"message_off\"]\n\n        self.triggered = False\n        self.isWaitingHandle = None\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        # Subscribe to sensors\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.sensor)\n        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            # Initial: power usage goes up\n            if (\n                new != None\n                and new != \"\"\n                and new != \"unknown\"\n                and not self.triggered\n                and float(new) > self.threshold\n            ):\n                self.triggered = True\n                self.log(\"Power Usage is: {}\".format(float(new)))\n                self.log(\"Setting triggered to: {}\".format(self.triggered))\n                if self.input_boolean is not None:\n                    self.turn_on(self.input_boolean)\n                if self.notify_start:\n                    self.notifier.notify(\n                        self.notify_name,\n                        self.message.format(self.alternative_name),\n                        useAlexa=self.notify_start_use_alexa,\n                    )\n                else:\n                    self.log(\"Not notifying user\")\n            # Power usage goes down below threshold\n            elif (\n                new != None\n                and new != \"\"\n                and self.triggered\n                and self.isWaitingHandle == None\n                and float(new) <= self.threshold\n            ):\n                self.log(\"Waiting: {} seconds to notify.\".format(self.delay))\n                self.isWaitingHandle = self.run_in(self.notify_device_off, self.delay)\n                self.log(\"Setting isWaitingHandle to: {}\".format(self.isWaitingHandle))\n                self.timer_handle_list.append(self.isWaitingHandle)\n            # Power usage goes up before delay\n            elif (\n                new != None\n                and new != \"\"\n                and self.triggered\n                and self.isWaitingHandle != None\n                and float(new) > self.threshold\n            ):\n                self.log(\"Cancelling timer\")\n                self.cancel_timer(self.isWaitingHandle)\n                self.isWaitingHandle = None\n                self.log(\"Setting isWaitingHandle to: {}\".format(self.isWaitingHandle))\n\n    def notify_device_off(self, kwargs):\n        \"\"\"Notify User that device is off. This may get cancelled if it turns on again in the meantime\"\"\"\n        self.triggered = False\n        self.log(\"Setting triggered to: {}\".format(self.triggered))\n        self.isWaitingHandle = None\n        self.log(\"Setting isWaitingHandle to: {}\".format(self.isWaitingHandle))\n        if self.input_boolean is not None:\n            self.turn_off(self.input_boolean)\n        if self.notify_end:\n            self.notifier.notify(\n                self.notify_name,\n                self.message_off.format(self.alternative_name),\n                useAlexa=self.notify_end_use_alexa,\n            )\n        else:\n            self.log(\"Not notifying user\")\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "powerUsageNotification/powerUsageNotification.yaml",
    "content": "powerUsageNotification_Dishwasher:\n  module: powerUsageNotification\n  class: PowerUsageNotification\n  app_switch: input_boolean.power_usage_notification_dishwasher\n  input_boolean: input_boolean.dishwasher\n  sensor: sensor.dishwasher_power_usage\n  notify_name: group_notifications\n  notify_start: False\n  delay: 1260 #21 minutes\n  threshold: 2\n  alternative_name: Die Spülmaschine\n  message: \"{} ist gestartet.\"\n  #message: \"{} just started.\"\n  message_off: \"{} ist fertig.\"\n  #message_off: \"{} just finished.\"\n  dependencies: \n    - Notifier\n\npowerUsageNotification_Washingmachine:\n  module: powerUsageNotification\n  class: PowerUsageNotification\n  app_switch: input_boolean.power_usage_notification_washingmachine\n  input_boolean: input_boolean.washingmachine\n  sensor: sensor.washingmachine_power_usage\n  notify_name: group_notifications\n  notify_start: False\n  delay: 60\n  threshold: 2\n  alternative_name: Die Waschmaschine\n  message: \"{} ist gestartet.\"\n  #message: \"{} just started.\"\n  message_off: \"{} ist fertig.\"\n  #message_off: \"{} just finished.\"\n  dependencies: \n    - Notifier"
  },
  {
    "path": "reminder/reminder.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nimport uuid\n\n#\n# App which reminds you daily and again in the evening if not acknowledged\n#\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# notify_name: Who to notify. example: group_notifications\n# user_id: The user_id of the telegram user to ask whether he knows an unknown face. example: 812391\n# reminder_acknowledged_entity: Input Boolean to store the information whether the user acknowledged the notification.\n#                        This prevents new notifications upon HA/Appdaemon restart.\n#                        example: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged\n# message: localized message to use in notification\n#\n# Release Notes\n#\n# Version 1.0:\n#   Initial Version\n\nKEYBOARD_CALLBACK_BASE = \"/reminder_acknowledged\"\n\n\nclass Reminder(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.user_id = self.args[\"user_id\"]\n        self.reminder_acknowledged_entity = self.args[\"reminder_acknowledged_entity\"]\n        self.message = self.args[\"message\"]\n        self.message_evening = self.args[\"message_evening\"]\n\n        self.keyboard_callback = (\n            KEYBOARD_CALLBACK_BASE + uuid.uuid4().hex\n        )  # Unique callback for each instance\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.reminder_acknowledged = self.get_state(self.reminder_acknowledged_entity)\n\n        self.listen_event_handle_list.append(\n            self.listen_event(self.receive_telegram_callback, \"telegram_callback\")\n        )\n\n        # Remind daily at 08:00\n        self.timer_handle_list.append(\n            self.run_daily(self.run_morning_callback, datetime.time(8, 0, 0))\n        )\n        # Remind daily at 18:00\n        self.timer_handle_list.append(\n            self.run_daily(self.run_evening_callback, datetime.time(18, 0, 0))\n        )\n\n    def run_morning_callback(self, kwargs):\n        \"\"\"Remind the user of {self.message}\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n\n            self.turn_off(self.reminder_acknowledged_entity)\n            self.log(\"Setting reminder_acknowledged to: off\")\n            self.log(\"Reminding user\")\n            keyboard = [[(\"Hab ich gemacht\", self.keyboard_callback)]]\n            self.call_service(\n                \"telegram_bot/send_message\",\n                target=self.user_id,\n                message=self.message,\n                inline_keyboard=keyboard,\n            )\n\n    def run_evening_callback(self, kwargs):\n        \"\"\"Remind the user again if he didn't acknowledge it\"\"\"\n        if self.get_state(self.app_switch) == \"on\":\n            if self.get_state(self.reminder_acknowledged_entity) == \"off\":\n                self.log(\"Reminding user\")\n                self.call_service(\n                    \"notify/\" + self.notify_name, message=self.message_evening\n                )\n\n    def receive_telegram_callback(self, event_name, data, kwargs):\n        \"\"\"Event listener for Telegram callback queries.\"\"\"\n        assert event_name == \"telegram_callback\"\n        data_callback = data[\"data\"]\n        callback_id = data[\"id\"]\n        chat_id = data[\"chat_id\"]\n        message_id = data[\"message\"][\"message_id\"]\n        text = data[\"message\"][\"text\"]\n        self.log(f\"callback data: {data}\", level=\"DEBUG\")\n\n        if data_callback == self.keyboard_callback:  # Keyboard editor:\n            # Answer callback query\n            self.call_service(\n                \"telegram_bot/answer_callback_query\",\n                message=\"Super!\",\n                callback_query_id=callback_id,\n            )\n            self.turn_on(self.reminder_acknowledged_entity)\n            self.log(\"Setting reminder_acknowledged to: on\")\n\n            self.call_service(\n                \"telegram_bot/edit_message\",\n                chat_id=chat_id,\n                message_id=message_id,\n                message=text\n                + f\" Hast du um {datetime.datetime.now().hour}:{datetime.datetime.now().minute} erledigt.\",\n                inline_keyboard=[],\n            )\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "reminder/reminder.yaml",
    "content": "# reminderPlantwateringNew:\n#   module: reminder\n#   class: Reminder\n#   app_switch: input_boolean.plant_watering_reminder_new\n#   notify_name: group_notifications\n#   user_id: !secret telegram_user_id\n#   reminder_acknowledged_entity: input_boolean.persistence_reminder_plantwatering_new_reminder_acknowledged\n#   message: \"Vergiss nicht die neuen Pflanzen zu gießen!\"\n#   #message: \"Don't forget to water the plants!\"\n#   message_evening: \"Ich bin mir nicht sicher ob du vergessen hast die neuen Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran.\"\n#   #message_evening: \"I'm not sure whether you watered your plants, so I thought I better remind you again\"\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "requirements.txt",
    "content": "wheel"
  },
  {
    "path": "seqSink/requirements.txt",
    "content": "requests==2.24.0"
  },
  {
    "path": "seqSink/seqSink.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport socket\nimport os\nimport json\nimport requests\n\n#\n# App which forwards all logs to seq.\n#\n# Args:\n#\n# server_url: Server url of the seq instance to log to. example: \"http://seq:5341/\"\n# api_key (optional): Api key to use.\n#\n# Release Notes\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass SeqSink(hass.Hass):\n    def initialize(self):\n        self.server_url = self.args[\"server_url\"]\n        if not self.server_url.endswith(\"/\"):\n            self.server_url += \"/\"\n        self.server_url += \"api/events/raw\"\n\n        self.session = requests.Session()\n        self.session.headers[\"Content-Type\"] = \"application/json\"\n\n        api_key = self.args.get(\"api_key\")\n        if api_key:\n            self.session.headers[\"X-Seq-ApiKey\"] = api_key\n\n        self.handle = self.listen_log(self.log_message_callback)\n\n    def log_message_callback(self, app_name, ts, level, log_type, message, kwargs):\n        if app_name != \"seqSink\":\n            event_data = {\n                \"Timestamp\": str(ts),\n                \"Level\": str(level),\n                \"MessageTemplate\": str(message),\n                \"Properties\": {\n                    \"Type\": \"Appdaemon\",\n                    \"AppName\": str(app_name)\n                },\n            }\n            request_body = {\"Events\": [event_data]}\n\n            try:\n                request_body_json = json.dumps(request_body)\n            except TypeError:\n                self.log(f\"Could not serialize {request_body}\")\n                return\n\n            try:\n                response = self.session.post(\n                    self.server_url,\n                    data=request_body_json,\n                    stream=True,  # prevent '362'\n                )\n                response.raise_for_status()\n            except requests.RequestException as requestFailed:\n                self.log(f\"Could not serialize {message}\")\n\n                if not requestFailed.response:\n                    self.log(\"Response from Seq was unavailable.\")\n                elif not requestFailed.response.text:\n                    self.log(\"Response body from Seq was empty.\")\n                else:\n                    self.log(f\"Response body from Seq:{requestFailed.response.text}\")\n\n    def terminate(self):\n        self.cancel_listen_log(self.handle)\n"
  },
  {
    "path": "seqSink/seqSink.yaml",
    "content": "# seqSink:\n#   module: seqSink\n#   class: SeqSink\n#   server_url: \"http://seq:5341/\"\n#   api_key: !secret seq_api_key"
  },
  {
    "path": "setThermostat/setThermostat.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App which sets a thermostat to a target temperature based on a time from an entity\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.warm_bath_before_wakeup\n# isHome: entity which shows if someone is home. example: input_boolean.is_home\n# sleepMode: entity which shows if users are sleeping. example: input_boolean.sleepmode\n# time_entity: sensor which determines when to run in the format 14:30. example: sensor.alarm_time\n# upfront_time: how many minutes before the time_sensor to run. example: 60\n# duration: After how many minutes should the thermostat be set back to its previous value. example: 60\n# climat_entity: climate entity to set. example: climate.bad_thermostat\n# target_entity: the entity holding the target temp. example: warm_bath_before_wakeup\n# message: message to use in notification\n# notify_name: who to notify. example: group_notifications\n# use_alexa: use alexa for notification. example: False\n#\n# Release Notes\n#\n# Version 1.5:\n#   Catch new is unknown\n#\n# Version 1.4:\n#   Use sleepmode\n#\n# Version 1.3:\n#   Use new formatted alarm_time\n#\n# Version 1.2.1:\n#   Reschedule timer after first run\n#\n# Version 1.2:\n#   Added isHome. Only run when someone is home\n#\n# Version 1.1:\n#   Actually set the previous temp\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass SetThermostat(hass.Hass):\n    def initialize(self):\n        self.timer_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.time_entity = self.args[\"time_entity\"]\n        self.upfront_time = self.args[\"upfront_time\"]\n        self.duration = self.args[\"duration\"]\n        self.climat_entity = self.args[\"climat_entity\"]\n        self.target_entity = self.args[\"target_entity\"]\n        self.message = self.args[\"message\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.use_alexa = self.args[\"use_alexa\"]\n        self.isHome = self.args[\"isHome\"]\n        self.sleepMode = self.args[\"sleepMode\"]\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.run_timer = None\n\n        self.cached_alarm_time = None\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.schedule_trigger, self.time_entity)\n        )\n        self.schedule_trigger(\n            self.time_entity, None, None, self.get_state(self.time_entity), None\n        )\n\n    def schedule_trigger(self, entity, attribute, old, new, kwargs):\n        if (\n            new is not None\n            and new != old\n            and new != \"\"\n            and new != \"unknown\"\n            and new != self.cached_alarm_time\n        ):\n            if self.run_timer is not None:\n                self.cancel_timer(self.run_timer)\n                self.log(\"Cancelled scheduled trigger\")\n                self.run_timer = None\n            self.cached_alarm_time = new\n            event_time = datetime.datetime.strptime(new, \"%Y-%m-%d %H:%M:%S\")\n            event_time = event_time - datetime.timedelta(minutes=self.upfront_time)\n            try:\n                self.run_timer = self.run_at(self.trigger_thermostat, event_time)\n                self.timer_handle_list.append(self.run_timer)\n                self.log(\"Thermostat will trigger at {}\".format(event_time))\n            except ValueError:\n                self.log(\"New trigger time would be in the past: {}\".format(event_time))\n\n    def trigger_thermostat(self, kwargs):\n        if (\n            self.get_state(self.app_switch) == \"on\"\n            and self.get_state(self.isHome) == \"on\"\n            and self.get_state(self.sleepMode) == \"on\"\n        ):\n            self.log(\n                self.message.format(\n                    self.friendly_name(self.climat_entity),\n                    self.get_state(self.target_entity),\n                )\n            )\n            self.notifier.notify(\n                self.notify_name,\n                self.message.format(\n                    self.friendly_name(self.climat_entity),\n                    self.get_state(self.target_entity),\n                ),\n                useAlexa=self.use_alexa,\n            )\n            self.log(\"Turning {} on\".format(self.climat_entity))\n            self.call_service(\"climate/turn_on\", entity_id=self.climat_entity)\n            self.previous_temp = self.get_state(self.climat_entity, attribute=\"all\")[\n                \"attributes\"\n            ][\"temperature\"]\n            self.call_service(\n                \"climate/set_temperature\",\n                entity_id=self.climat_entity,\n                temperature=self.get_state(self.target_entity),\n            )\n            self.log(\"Resetting Thermostat in {} minutes.\".format(self.duration))\n            self.timer_handle_list.append(\n                self.run_in(self.reset_thermostat, float(self.duration) * 60)\n            )\n            if self.run_timer is not None:\n                self.cancel_timer(self.run_timer)\n\n    def reset_thermostat(self, kwargs):\n        if self.previous_temp is not None:\n            self.log(\n                self.message.format(\n                    self.friendly_name(self.climat_entity), self.previous_temp\n                )\n            )\n            self.notifier.notify(\n                self.notify_name,\n                self.message.format(\n                    self.friendly_name(self.climat_entity), self.previous_temp\n                ),\n                useAlexa=self.use_alexa,\n            )\n            self.call_service(\n                \"climate/set_temperature\",\n                entity_id=self.climat_entity,\n                temperature=self.previous_temp,\n            )\n            self.schedule_trigger(\n                self.time_entity, None, None, self.get_state(self.time_entity), None\n            )\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "setThermostat/setThermostat.yaml",
    "content": "# warm_bath_before_wakeup:\n#   module: setThermostat\n#   class: SetThermostat\n#   app_switch: input_boolean.warm_bath_before_wakeup\n#   isHome: input_boolean.is_home\n#   sleepMode: input_boolean.sleepmode\n#   time_entity: sensor.alarm_time\n#   upfront_time: 120\n#   duration: 120\n#   climat_entity: climate.bad_thermostat\n#   target_entity: input_number.warm_bath_before_wakeup\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies: \n#     - Notifier\n\n# warm_upper_bath_before_wakeup:\n#   module: setThermostat\n#   class: SetThermostat\n#   app_switch: input_boolean.warm_upper_bath_before_wakeup\n#   isHome: input_boolean.is_home\n#   sleepMode: input_boolean.sleepmode\n#   time_entity: sensor.alarm_time\n#   upfront_time: 60\n#   duration: 60\n#   climat_entity: climate.bad_oben_thermostat\n#   target_entity: input_number.warm_upper_bath_before_wakeup\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n"
  },
  {
    "path": "setThermostatOnStateChange/setThermostatOnStateChange.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\n\n#\n# App which sets a thermostat to a target temperature on state change\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.warm_bath_before_wakeup\n# trigger_entity: entity which triggers this app. example: input_boolean.is_home\n# trigger_state: new state of trigger_entity which triggers this app. example: \"off\"\n# climate_entity: climate entity to set. example: climate.bad_thermostat\n# target_entity: the entity holding the target temp. example: warm_bath_before_wakeup\n# message (optional): message to use in notification\n# notify_name (optional): who to notify. example: group_notifications\n# use_alexa (optional): use alexa for notification. example: False\n#\n# Release Notes\n#\n# Version 1.2:\n#   Rename of SetThermostatOnStateChange\n#\n# Version 1.1:\n#   Use isHome as trigger\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass SetThermostatOnStateChange(hass.Hass):\n    def initialize(self):\n        self.timer_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.trigger_entity = self.args[\"trigger_entity\"]\n        self.trigger_state = self.args[\"trigger_state\"]\n        self.climate_entity = self.args[\"climate_entity\"]\n        self.target_entity = self.args[\"target_entity\"]\n        self.message = self.args.get(\"message\")\n        self.notify_name = self.args.get(\"notify_name\")\n        try:\n            self.use_alexa = self.args[\"use_alexa\"]\n        except KeyError:\n            self.use_alexa = False\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.trigger_entity)\n        )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new == self.trigger_state and old != new:\n                if self.message is not None:\n                    self.log(\n                        self.message.format(\n                            self.friendly_name(self.climate_entity),\n                            self.get_state(self.target_entity),\n                        )\n                    )\n                self.call_service(\"climate/turn_on\", entity_id=self.climate_entity)\n                self.call_service(\n                    \"climate/set_temperature\",\n                    entity_id=self.climate_entity,\n                    temperature=self.get_state(self.target_entity),\n                )\n                if self.notify_name is not None:\n                    self.notifier.notify(\n                        self.notify_name,\n                        self.message.format(\n                            self.friendly_name(self.climate_entity),\n                            self.get_state(self.target_entity),\n                        ),\n                        useAlexa=self.use_alexa,\n                    )\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "setThermostatOnStateChange/setThermostatOnStateChange.yaml",
    "content": "# setWohnzimmerThermostatWhenLeaving:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_livingroom_thermostat_when_leaving\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"off\"\n#   climate_entity: climate.wohnzimmer_thermostat\n#   target_entity: input_number.set_livingroom_thermostat_when_leaving\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies: \n#     - Notifier\n\n# setBadObenThermostatWhenLeaving:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_upper_bath_thermostat_when_leaving\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"off\"\n#   climate_entity: climate.bad_oben_thermostat\n#   target_entity: input_number.set_upper_bath_thermostat_when_leaving\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setBadThermostatWhenLeaving:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_bath_thermostat_when_leaving\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"off\"\n#   climate_entity: climate.bad_thermostat\n#   target_entity: input_number.set_bath_thermostat_when_leaving\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setKitchenThermostatWhenLeaving:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_kitchen_thermostat_when_leaving\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"off\"\n#   climate_entity: climate.kuche_thermostat\n#   target_entity: input_number.set_kitchen_thermostat_when_leaving\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setWohnzimmerThermostatWhenSleeping:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_livingroom_thermostat_when_sleeping\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"on\"\n#   climate_entity: climate.wohnzimmer_thermostat\n#   target_entity: input_number.set_livingroom_thermostat_when_sleeping\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setBadObenThermostatWhenSleeping:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_upper_bath_thermostat_when_sleeping\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"on\"\n#   climate_entity: climate.bad_oben_thermostat\n#   target_entity: input_number.set_upper_bath_thermostat_when_sleeping\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setBadThermostatWhenSleeping:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_bath_thermostat_when_sleeping\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"on\"\n#   climate_entity: climate.bad_thermostat\n#   target_entity: input_number.set_bath_thermostat_when_sleeping\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setKitchenThermostatWhenSleeping:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_kitchen_thermostat_when_sleeping\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"on\"\n#   climate_entity: climate.kuche_thermostat\n#   target_entity: input_number.set_kitchen_thermostat_when_sleeping\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   dependencies:\n#     - Notifier\n\n# setLivingroomThermostatWhenWakingUp:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_livingroom_thermostat_when_waking_up\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"off\"\n#   climate_entity: climate.wohnzimmer_thermostat\n#   target_entity: input_number.set_livingroom_thermostat_when_waking_up\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setKitchenThermostatWhenWakingUp:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_kitchen_thermostat_when_waking_up\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"off\"\n#   climate_entity: climate.kuche_thermostat\n#   target_entity: input_number.set_kitchen_thermostat_when_waking_up\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setBadThermostatWhenWakingUp:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_bath_thermostat_when_waking_up\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"off\"\n#   climate_entity: climate.bad_thermostat\n#   target_entity: input_number.set_bath_thermostat_when_waking_up\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setBadObenThermostatWhenWakingUp:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_upper_bath_thermostat_when_waking_up\n#   trigger_entity: input_boolean.sleepmode\n#   trigger_state: \"off\"\n#   climate_entity: climate.bad_oben_thermostat\n#   target_entity: input_number.set_upper_bath_thermostat_when_waking_up\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setLivingroomThermostatWhenComingHome:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_livingroom_thermostat_when_coming_home\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"on\"\n#   climate_entity: climate.wohnzimmer_thermostat\n#   target_entity: input_number.set_livingroom_thermostat_when_coming_home\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setKitchenThermostatWhenComingHome:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_kitchen_thermostat_when_coming_home\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"on\"\n#   climate_entity: climate.kuche_thermostat\n#   target_entity: input_number.set_kitchen_thermostat_when_coming_home\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setBadThermostatWhenComingHome:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_bath_thermostat_when_coming_home\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"on\"\n#   climate_entity: climate.bad_thermostat\n#   target_entity: input_number.set_bath_thermostat_when_coming_home\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier\n\n# setBadObenThermostatWhenComingHome:\n#   module: setThermostatOnStateChange\n#   class: SetThermostatOnStateChange\n#   app_switch: input_boolean.set_upper_bath_thermostat_when_coming_home\n#   trigger_entity: input_boolean.is_home\n#   trigger_state: \"on\"\n#   climate_entity: climate.bad_oben_thermostat\n#   target_entity: input_number.set_upper_bath_thermostat_when_coming_home\n#   message: \"Ich habe {} auf {} °C gestellt\"\n#   #message: \"I have set {} to {}\"\n#   notify_name: group_notifications\n#   use_alexa: False\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "sleepModeHandler/sleepModeHandler.py",
    "content": "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/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n#   sleepmode: input_boolean holding the sleepmode. example: input_boolean.sleepmode\n#   users: configuration for users\n#\n# Release Notes\n#\n# Version 1.2:\n#   Add use_alexa\n#\n# Version 1.1:\n#   Only send notification if sleepmode is actually changed\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass SleepModeHandler(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.sleepmode = self.args[\"sleepmode\"]\n        self.users = self.args[\"users\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.message_sleeping = self.args[\"message_sleeping\"]\n        self.message_awake = self.args[\"message_awake\"]\n\n        try:\n            self.use_alexa = self.args[\"use_alexa\"]\n        except KeyError:\n            self.use_alexa = False\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        for user in self.users:\n            self.listen_state_handle_list.append(\n                self.listen_state(self.state_change, user[\"sleep_mode\"])\n            )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            if new != \"\" and new != old:\n                if new == \"on\":\n                    if self.are_all_that_are_home_sleeping():\n                        if self.get_state(self.sleepmode) == \"off\":\n                            self.log(\"All at home are sleeping\")\n                            self.turn_on(self.sleepmode)\n                            self.notifier.notify(\n                                self.notify_name,\n                                self.message_sleeping,\n                                useAlexa=self.use_alexa,\n                            )\n                elif new == \"off\":\n                    if self.are_all_that_are_home_awake():\n                        if self.get_state(self.sleepmode) == \"on\":\n                            self.log(\"All at home are awake\")\n                            self.turn_off(self.sleepmode)\n                            self.notifier.notify(\n                                self.notify_name,\n                                self.message_awake,\n                                useAlexa=self.use_alexa,\n                            )\n\n    def are_all_that_are_home_sleeping(self):\n        for user in self.users:\n            if self.get_state(user[\"isHome\"]) == \"on\":\n                if self.get_state(user[\"sleep_mode\"]) != \"on\":\n                    return False\n        return True\n\n    def are_all_that_are_home_awake(self):\n        for user in self.users:\n            if self.get_state(user[\"isHome\"]) == \"on\":\n                if self.get_state(user[\"sleep_mode\"]) == \"on\":\n                    return False\n        return True\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "sleepModeHandler/sleepModeHandler.yaml",
    "content": "# sleepModeHandler:\n#   module: sleepModeHandler\n#   class: SleepModeHandler\n#   app_switch: input_boolean.sleep_mode_handler\n#   sleepmode: input_boolean.sleepmode\n#   notify_name: group_notifications\n#   message_sleeping: \"Alle zu Hause sind im Bett\"\n#   #message_sleeping: \"All home are in bed\"\n#   message_awake: \"Alle zu Hause sind wach\"\n#   #message_awake: \"All home are awake\"\n#   use_alexa: False\n#   users:\n#     - sleep_mode: input_boolean.user_one_sleep\n#       isHome: input_boolean.user_one_home\n#     - sleep_mode: input_boolean.user_two_sleep\n#       isHome: input_boolean.user_two_home\n#   dependencies:\n#     - Notifier"
  },
  {
    "path": "sleepModeHandler/userSleepModeHandler.py",
    "content": "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:\n#   app_switch:\n#       on/off switch for this app.\n#       example: input_boolean.turn_fan_on_when_hot\n#   input_boolean:\n#       input_boolean holding the sleepmode. example: input_boolean.sleepmode\n#   location_sensor:\n#       location sensor of user. example: sensor.location_user_one\n#   room:\n#       Room name in which user must be. example: Wohnzimmer\n#   asleep_duration:\n#       seconds to wait before turning sleepmode on. example: 120\n#   awake_duration:\n#       seconds to wait before turning sleepmode off. example: 120\n#   is_home_input_boolean:\n#       input_boolean for the is home state of the user\n#       example: input_boolean.is_user_home_determiner_user_one\n#\n# Release Notes\n#\n# Version 1.4:\n#   Also watch is_home\n#\n# Version 1.3:\n#   Reimplementation\n#\n# Version 1.2:\n#   Only trigger on an actual change\n#\n# Version 1.1:\n#   Added asleep_duration and awake_duration instead of duration\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass UserSleepModeHandler(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.location_sensor = self.args[\"location_sensor\"]\n        self.room = self.args[\"room\"]\n        self.asleep_duration = self.args[\"asleep_duration\"]\n        self.awake_duration = self.args[\"awake_duration\"]\n        self.input_boolean = self.args[\"input_boolean\"]\n        self.is_home_input_boolean = self.args[\"is_home_input_boolean\"]\n\n        self.timer_handle = None\n\n        self.queue = Queue()\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.location_sensor)\n        )\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.is_home_input_boolean)\n        )\n\n    def home_state_change(self, entity, attribute, old, new, kwargs):\n        \"\"\"is_home state changed.\"\"\"\n        if new != old:\n            if self.get_state(self.app_switch) == \"on\":\n                if new == \"off\":\n                    self.log(\n                        f\"User left home. Turning {self.input_boolean} off\")\n                    self.turn_off(self.input_boolean)\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        \"\"\"Handle state changes of the location sensor.\"\"\"\n        if new != old:\n            if self.get_state(self.app_switch) == \"on\":\n                # User left room\n                if old == self.room:\n                    self.log(\n                        f\"User left {self.room}. Resetting timer.\"\n                        f\"Will trigger awake in {self.awake_duration}s\"\n                    )\n                    if self.timer_handle is not None:\n                        self.cancel_timer(self.timer_handle)\n                    self.timer_handle = self.run_in(\n                        self.awake,\n                        self.awake_duration)\n                elif new == self.room:\n                    self.log(\n                        f\"User entered {self.room}. \"\n                        f\"Resetting timer. \"\n                        f\"Will trigger asleep in {self.asleep_duration}s\"\n                    )\n                    if self.timer_handle is not None:\n                        self.cancel_timer(self.timer_handle)\n                    self.timer_handle = self.run_in(\n                        self.asleep,\n                        self.asleep_duration)\n\n    def awake(self, kwargs):\n        \"\"\"User left room for more than self.awake_duration.\n        Turn off sleep mode.\"\"\"\n        current_location = self.get_state(self.location_sensor)\n        if current_location != self.room:\n            if self.get_state(self.input_boolean) == \"on\":\n                self.log(\n                    f\"{self.friendly_name(self.location_sensor)} \"\n                    f\"is outside {self.room} \"\n                    f\"for more than {self.asleep_duration}s. \"\n                    f\"Turning {self.input_boolean} off\"\n                )\n                self.turn_off(self.input_boolean)\n        else:\n            self.log(f\"Timer ran out but user is in {current_location}\")\n\n    def asleep(self, kwargs):\n        \"\"\"User stayed in room for more than self.asleep_duration.\n        Turn on sleep mode.\"\"\"\n        current_location = self.get_state(self.location_sensor)\n        if current_location == self.room:\n            if self.get_state(self.input_boolean) == \"off\":\n                self.log(\n                    f\"{self.friendly_name(self.location_sensor)} \"\n                    f\"is in {self.room} \"\n                    f\"for more than {self.asleep_duration}s. \"\n                    f\"Turning {self.input_boolean} on\"\n                )\n                self.turn_on(self.input_boolean)\n        else:\n            self.log(f\"Timer ran out but user is in {current_location}\")\n\n    def insert_room_state_change(self, entity, attribute, old, new, kwargs):\n        \"\"\"Insert a new room state change into the queue.\"\"\"\n        self.queue.put(new)\n\n    def calculate_room_presence(self, kwargs):\n        \"\"\"Calculate the percentage the person was in the target room\n        since the last invocation.\"\"\"\n        state_changes = []\n        while not self.queue.empty():\n            state_changes.append(self.queue.get())\n        for state_change in state_changes:\n            pass\n\n    def terminate(self):\n        if self.timer_handle is not None:\n            self.cancel_timer(self.timer_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "sleepModeHandler/userSleepModeHandler.yaml",
    "content": "# userSleepModeHandlerUserOne:\n#   module: userSleepModeHandler\n#   class: UserSleepModeHandler\n#   app_switch: input_boolean.user_sleep_mode_handler_user_one\n#   input_boolean: input_boolean.user_one_sleep\n#   location_sensor: sensor.location_user_one\n#   is_home_input_boolean: input_boolean.is_user_home_determiner_user_one\n#   room: Schlafzimmer\n#   awake_duration: 600\n#   asleep_duration: 1800\n\n# userSleepModeHandlerUserTwo:\n#   module: userSleepModeHandler\n#   class: UserSleepModeHandler\n#   app_switch: input_boolean.user_sleep_mode_handler_user_two\n#   input_boolean: input_boolean.user_two_sleep\n#   location_sensor: sensor.location_user_two\n#   is_home_input_boolean: input_boolean.is_user_home_determiner_user_two\n#   room: Schlafzimmer\n#   awake_duration: 600\n#   asleep_duration: 1800"
  },
  {
    "path": "travelTimeNotifier/travelTimeNotifier.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\nfrom typing import Optional\n\n#\n# App which notifies the user if the travel time is within a normal amount\n#\n#\n# Args:\n# sensor: google_travel_time or here_travel_time sensor to watch. example: sensor.travel_time_home_from_work\n# notify_input_boolean: input_boolean determining whether to notify. example: input_boolean.travel_time_home_from_work\n# notify_name: Who to notify. example: group_notifications\n# acceptable_range (optional): Multiplier of the normal travel time that is still acceptable. example: 1.2\n# message_<LANG>: message to use in notification\n# notify_use_Alexa: use Alexa as TTS. Defaults to True. example: False\n#\n# Release Notes\n#\n# Version 1.7:\n#   Catch NoneType when Homeassistant is still starting\n#\n# Version 1.6:\n#   Introduce methods to deal with minor differences between google and here\n#\n# Version 1.5:\n#   Rename to TravelTimeNotifier as this can be used with here_travel_time also\n#\n# Version 1.4:\n#   use Notify App\n#\n# Version 1.3:\n#   message now directly in own yaml instead of message module\n#\n# Version 1.2:\n#   Moved to standard google travel sensors. Now only notification\n#\n# Version 1.1:\n#   Add notification feature\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass TravelTimeNotifier(hass.Hass):\n    def initialize(self):\n\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.sensor = self.args[\"sensor\"]\n        self.notify_input_boolean = self.args[\"notify_input_boolean\"]\n        self.notify_name = self.args[\"notify_name\"]\n        self.message = self.args[\"message\"]\n        try:\n            self.acceptable_range = self.args[\"acceptable_range\"]\n        except KeyError:\n            self.acceptable_range = 1.2\n        try:\n            self.notify_use_Alexa = self.args[\"notify_use_Alexa\"]\n        except KeyError:\n            self.notify_use_Alexa = True\n\n        self.notifier = self.get_app(\"Notifier\")\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.sensor, attribute=\"all\")\n        )\n\n    def state_change(self, entity, attributes, old, new, kwargs) -> None:\n        self.log(\"entity: {}\".format(entity))\n        self.log(\"old: {}\".format(old))\n        self.log(\"new: {}\".format(new))\n\n        duration_in_traffic_minutes = self.parse_duration_in_traffic_minutes(new)\n        self.log(\"duration_in_traffic_minutes: {}\".format(duration_in_traffic_minutes),)\n\n        duration_minutes = self.parse_duration_minutes(new)\n        self.log(\"duration_minutes: {}\".format(duration_minutes))\n\n        if duration_minutes is None or duration_in_traffic_minutes is None:\n            self.log(\"Sensor is None. Homeassistant might not be fully started.\")\n        else:\n            if duration_in_traffic_minutes <= duration_minutes * self.acceptable_range:\n                if self.get_state(self.notify_input_boolean) == \"on\":\n                    destination_address = self.parse_destination_address(new)\n                    self.notify_user(destination_address)\n                    self.turn_off(self.notify_input_boolean)\n\n    def notify_user(self, address: str) -> None:\n        \"\"\"Notify the user it is time to leave for the given address.\"\"\"\n        message = self.message.format(address)\n        self.log(\"Notify user\")\n        self.notifier.notify(self.notify_name, message, useAlexa=self.notify_use_Alexa)\n\n    def parse_duration_in_traffic_minutes(self, state) -> Optional[int]:\n        \"\"\"Get duration_in_traffic from the states attributes.\"\"\"\n        duration_in_traffic = state[\"attributes\"].get(\"duration_in_traffic\")\n        duration_in_traffic_minutes = None\n        if duration_in_traffic is not None:\n            if isinstance(duration_in_traffic, float):\n                duration_in_traffic_minutes = int(duration_in_traffic)\n            else:\n                duration_in_traffic_minutes = int(\n                    duration_in_traffic[: duration_in_traffic.find(\" \")]\n                )\n        else:\n            self.log(\n                \"Could not find duration_in_traffic in state attributes.\",\n                level=\"WARNING\",\n            )\n        return duration_in_traffic_minutes\n\n    def parse_destination_address(self, state) -> Optional[str]:\n        \"\"\"Get a destination address from the states attributes.\"\"\"\n        attributes = state[\"attributes\"]\n        destination_address = None\n        if \"destination_name\" in attributes:\n            destination_address = attributes[\"destination_name\"]\n        elif \"destination_addresses\" in attributes:\n            destination_address = attributes[\"destination_addresses\"][0]\n        else:\n            self.log(\n                \"Could not find destination_name or destination_addresses in state attributes.\",\n                level=\"WARNING\",\n            )\n        return destination_address\n\n    def parse_duration_minutes(self, state) -> Optional[int]:\n        \"\"\"Get duration from the states attributes.\"\"\"\n        duration_minutes = None\n        duration = state[\"attributes\"].get(\"duration\")\n        if duration is not None:\n            if isinstance(duration, float):\n                duration_minutes = int(duration)\n            else:\n                duration_minutes = int(duration[: duration.find(\" \")])\n        else:\n            self.log(\"Could not find duration in state attributes.\", level=\"WARNING\")\n        return duration_minutes\n\n    def terminate(self) -> None:\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "travelTimeNotifier/travelTimeNotifier.yaml",
    "content": "# travelTime_home_from_work:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_home_from_work_here\n#  notify_input_boolean: input_boolean.travel_time_home_from_work\n#  notify_name: group_notifications\n#  message: \"Du kannst losfahren nach {}\"\n#  #message: \"You can start your journey to {}\"\n#  notify_use_Alexa: False\n#  dependencies: \n#    - Notifier\n\n# travelTime_work_from_home:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_work_from_home_here\n#  notify_input_boolean: input_boolean.travel_time_work_from_home\n#  notify_name: group_notifications\n#  message: \"Du kannst losfahren nach {}\"\n#  #message: \"You can start your journey to {}\"\n#  dependencies: \n#    - Notifier\n\n# travelTime_elmo_from_home:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_elmo_from_home_here\n#  notify_input_boolean: input_boolean.travel_time_elmo_from_home\n#  notify_name: group_notifications\n#  message: \"Du kannst losfahren nach {}\"\n#  #message: \"You can start your journey to {}\"\n#  dependencies: \n#    - Notifier\n\n# travelTime_home_from_elmo:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_home_from_elmo_here\n#  notify_input_boolean: input_boolean.travel_time_home_from_elmo\n#  notify_name: group_notifications\n#  message: \"Du kannst losfahren nach {}\"\n#  #message: \"You can start your journey to {}\"\n#  dependencies: \n#    - Notifier\n\n# travelTime_work_user_two_from_home:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_work_user_two_from_home_here\n#  notify_input_boolean: input_boolean.travel_time_work_user_two_from_home\n#  notify_name: group_notifications\n#  message: \"Du kannst losfahren nach {}\"\n#  #message: \"You can start your journey to {}\"\n#  dependencies: \n#    - Notifier\n\n# travelTime_home_from_work_user_two_here:\n#  module: travelTimeNotifier\n#  class: TravelTimeNotifier\n#  sensor: sensor.travel_time_home_from_work_user_two_here\n#  notify_input_boolean: input_boolean.travel_time_home_from_work_user_two\n#  notify_name: group_notifications\n#  message: \"Du kannst losfahren nach {}\"\n#  #message: \"You can start your journey to {}\"\n#  dependencies: \n#    - Notifier\n"
  },
  {
    "path": "turnFanOnWhenHot/turnFanOnWhenHot.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nimport datetime\n\n#\n# App to Turn on fan when temp is above a threshold and someone is in the room\n#\n# Args:\n#\n# app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot\n# temp_sensor: temp sensor to monitor. example: sensor.large_lamp_temperature\n# threshold_entity: entity which holds the temp threshold which must be reached. example: input_number.turn_fan_on_when_hot_threshold\n# location_sensors: location sensors of users. example: sensor.location_user_one,sensor.location_user_two\n# room: Room name in which one of the users must be. example: Wohnzimmer\n# actor: actor to turn on\n# delay: seconds to wait before turning off. example: 120\n# Release Notes\n#\n# Version 1.3:\n#   Added delay\n#\n# Version 1.2:\n#   Using entities from HA now. Added turned_on_by_me\n#\n# Version 1.1:\n#   Only turn on when someone is in the room. Turn off otherwise\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass TurnFanOnWhenHot(hass.Hass):\n    def initialize(self):\n        self.listen_state_handle_list = []\n        self.timer_handle_list = []\n\n        self.app_switch = self.args[\"app_switch\"]\n        self.temp_sensor = self.args[\"temp_sensor\"]\n        self.threshold_entity = self.args[\"threshold_entity\"]\n        self.location_sensors = self.args[\"location_sensors\"].split(\",\")\n        self.room = self.args[\"room\"]\n        self.actor = self.args[\"actor\"]\n        self.delay = self.args[\"delay\"]\n\n        self.turned_on_by_me = False  # Giggedi\n\n        self.turn_off_timer_handle = None\n\n        self.listen_state_handle_list.append(\n            self.listen_state(self.state_change, self.temp_sensor)\n        )\n        for sensor in self.location_sensors:\n            self.listen_state_handle_list.append(\n                self.listen_state(self.state_change, sensor)\n            )\n\n    def state_change(self, entity, attribute, old, new, kwargs):\n        if self.get_state(self.app_switch) == \"on\":\n            turn_on = False\n            if (\n                self.get_state(self.temp_sensor) != None\n                and self.get_state(self.temp_sensor) != \"unkown\"\n                and self.get_state(self.threshold_entity) != None\n                and float(self.get_state(self.temp_sensor))\n                > float(self.get_state(self.threshold_entity))\n            ):\n                for sensor in self.location_sensors:\n                    if self.get_state(sensor) == self.room:\n                        if self.get_state(self.actor) != \"on\":\n                            self.log(\n                                \"{} is {}. This is above theshold of {}\".format(\n                                    self.friendly_name(self.temp_sensor),\n                                    self.get_state(self.temp_sensor),\n                                    self.get_state(self.threshold_entity),\n                                )\n                            )\n                            self.log(\"{} is in {}\".format(sensor, self.room))\n                            self.log(\n                                \"Turning on {}\".format(self.friendly_name(self.actor))\n                            )\n                            self.turn_on(self.actor)\n                            self.turned_on_by_me = True\n                        turn_on = True\n                        if self.turn_off_timer_handle != None:\n                            self.timer_handle_list.remove(self.turn_off_timer_handle)\n                            self.cancel_timer(self.turn_off_timer_handle)\n                            self.turn_off_timer_handle = None\n            if not turn_on and self.turned_on_by_me:\n                if self.get_state(self.actor) != \"off\":\n                    self.turn_off_timer_handle = self.run_in(\n                        self.turn_off_callback, self.delay\n                    )\n                    self.timer_handle_list.append(self.turn_off_timer_handle)\n\n    def turn_off_callback(self, kwargs):\n        \"\"\"Turn off the actor again if the timer was not cancelled in the meantime\"\"\"\n        self.log(\"Turning off {}\".format(self.friendly_name(self.actor)))\n        self.turn_off(self.actor)\n        self.turned_on_by_me = False\n        self.turn_off_timer_handle = None\n\n    def terminate(self):\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n"
  },
  {
    "path": "turnFanOnWhenHot/turnFanOnWhenHot.yaml",
    "content": "# turnLargeFanOnWhenHot:\n#   module: turnFanOnWhenHot\n#   class: TurnFanOnWhenHot\n#   app_switch: input_boolean.turn_large_fan_on_when_hot\n#   temp_sensor: sensor.large_ventilator_temperature\n#   threshold_entity: input_number.turn_large_fan_on_when_hot_threshold\n#   location_sensors: sensor.location_user_one,sensor.location_user_two\n#   room: Wohnzimmer\n#   actor: switch.large_ventilator\n#   delay: 120\n\n# turnSmallFanOnWhenHot:\n#   module: turnFanOnWhenHot\n#   class: TurnFanOnWhenHot\n#   app_switch: input_boolean.turn_small_fan_on_when_hot\n#   temp_sensor: sensor.small_ventilator_temperature\n#   threshold_entity: input_number.turn_small_fan_on_when_hot_threshold\n#   location_sensors: sensor.location_user_one,sensor.location_user_two\n#   room: Wohnzimmer\n#   actor: switch.small_ventilator\n#   delay: 120"
  },
  {
    "path": "turnOffBarAfterRestart/turnOffBarAfterRestart.py",
    "content": "import appdaemon.plugins.hass.hassapi as hass\nfrom requests.exceptions import HTTPError\n\n#\n# Will turn the bar table green and then off when homeassistant restarts to indicate the restart went well\n#\n#\n# Args:\n#\n# light: light. example: light.bar_table\n#\n# Release Notes\n#\n# Version 1.0:\n#   Initial Version\n\n\nclass TurnOffBarAfterRestart(hass.Hass):\n    def initialize(self):\n\n        self.timer_handle_list = []\n        self.listen_event_handle_list = []\n        self.listen_state_handle_list = []\n\n        self.light = self.args[\"light\"]\n\n        self.timer_handle_list.append(self.run_in(self.turn_green_callback, 1))\n\n    def turn_off_callback(self, kwargs):\n        \"\"\"Turn off light\"\"\"\n        try:\n            self.log(\"Turning {} off\".format(self.friendly_name(self.light)))\n            self.turn_off(self.light)\n        except HTTPError as exception:\n            self.log(\n                \"Error trying to turn off entity. Will try again in 1s. Error: {}\".format(\n                    exception\n                ),\n                level=\"WARNING\",\n            )\n            self.timer_handle_list.append(self.run_in(self.turn_off_callback, 1))\n\n    def turn_green_callback(self, kwargs):\n        \"\"\"This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting\"\"\"\n        try:\n            self.call_service(\n                \"light/turn_on\",\n                entity_id=self.light,\n                rgb_color=[0, 255, 0],\n                white_value=0,\n            )\n            self.log(\"Turning {} green\".format(self.friendly_name(self.light)))\n            self.timer_handle_list.append(self.run_in(self.turn_off_callback, 5))\n        except HTTPError as exception:\n            self.log(\n                \"Error trying to turn on entity. Will try again in 1s. Error: {}\".format(\n                    exception\n                ),\n                level=\"WARNING\",\n            )\n            self.timer_handle_list.append(self.run_in(self.turn_green_callback, 1))\n\n    def terminate(self):\n        for timer_handle in self.timer_handle_list:\n            self.cancel_timer(timer_handle)\n\n        for listen_event_handle in self.listen_event_handle_list:\n            self.cancel_listen_event(listen_event_handle)\n\n        for listen_state_handle in self.listen_state_handle_list:\n            self.cancel_listen_state(listen_state_handle)\n"
  },
  {
    "path": "turnOffBarAfterRestart/turnOffBarAfterRestart.yaml",
    "content": "# turnOffBarAfterRestart:\n#   module: turnOffBarAfterRestart\n#   class: TurnOffBarAfterRestart\n#   light: light.bar_table"
  }
]