Repository: eifinger/appdaemon-scripts Branch: master Commit: 0a8e19f5616b Files: 100 Total size: 298.5 KB Directory structure: gitextract_92xa7u3p/ ├── .gitignore ├── .pylintrc ├── LICENSE ├── README.md ├── ad-ench/ │ └── ench.py ├── alarmClock/ │ ├── alarmClock.py │ └── alarmClock.yaml ├── alexa/ │ ├── README.md │ ├── alexa.yaml │ ├── alexa_api.py │ ├── custom_skill.json │ ├── lightState/ │ │ ├── lightStateIntent-utterances_DE.csv │ │ ├── lightStateIntent-utterances_EN.csv │ │ ├── lightStateIntent.py │ │ └── lightStateIntent.yaml │ ├── listService/ │ │ ├── listService.py │ │ └── listService.yaml │ ├── nextBus/ │ │ ├── nextBusIntent.py │ │ └── nextBusIntent.yaml │ ├── remindMeOfXWhenZone/ │ │ ├── remindMeOfXWhenZoneIntent.py │ │ └── remindMeOfXWhenZoneIntent.yaml │ ├── temperatureState/ │ │ ├── temperatureStateIntent-utterances_DE.csv │ │ ├── temperatureStateIntent-utterances_EN.csv │ │ ├── temperatureStateIntent.py │ │ └── temperatureStateIntent.yaml │ ├── turnEntityOffInX/ │ │ ├── requirements.txt │ │ ├── turnEntityOffInXIntent-utterances_DE.csv │ │ ├── turnEntityOffInXIntent-utterances_EN.csv │ │ ├── turnEntityOffInXIntent.py │ │ └── turnEntityOffInXIntent.yaml │ └── windowsOpen/ │ ├── windowsOpenIntent-utterances_DE.csv │ ├── windowsOpenIntent-utterances_EN.csv │ ├── windowsOpenIntent.py │ └── windowsOpenIntent.yaml ├── alexaSpeakerConnector/ │ ├── alexaSpeakerConnector.py │ └── alexaSpeakerConnector.yaml ├── appWatcher/ │ ├── appWatcher.py │ └── appWatcher.yaml ├── apps.yaml ├── buttonClicked/ │ ├── buttonClicked.py │ └── buttonClicked.yaml ├── comingHome/ │ ├── comingHome.py │ └── comingHome.yaml ├── deconz_xiaomi_button/ │ └── deconz_xiaomi_button.py ├── deconz_xiaomi_button.yaml ├── detectWrongState/ │ ├── detectWrongState.py │ └── detectWrongState.yaml ├── ench.yaml ├── eventMonitor/ │ ├── eventMonitor.py │ └── eventMonitor.yaml ├── faceRecognitionBot/ │ ├── faceRecognitionBot.py │ └── faceRecognitionBot.yaml ├── globals.py ├── heartbeat/ │ ├── heartbeat.py │ └── heartbeat.yaml ├── homeArrivalNotifier/ │ ├── homeArrivalNotifier.py │ └── homeArrivalNotifier.yaml ├── isHomeDeterminer/ │ ├── isHomeDeterminer.py │ └── isHomeDeterminer.yaml ├── isUserHomeDeterminer/ │ ├── isUserHomeDeterminer.py │ └── isUserHomeDeterminer.yaml ├── leavingZoneNotifier/ │ ├── leavingZoneNotifier.py │ └── leavingZoneNotifier.yaml ├── motionTrigger/ │ ├── motionTrigger.py │ └── motionTrigger.yaml ├── newWifiDeviceNotify/ │ ├── newWifiDeviceNotify.py │ ├── newWifiDeviceNotify.yaml │ └── requirements.txt ├── nextAppointmentLeaveNotifier/ │ ├── nextAppointmentLeaveNotifier.py │ └── nextAppointmentLeaveNotifier.yaml ├── notifier/ │ ├── notifier.py │ └── notifier.yaml ├── notifyOfActionWhenAway/ │ ├── notifyOfActionWhenAway.py │ └── notifyOfActionWhenAway.yaml ├── plantWateringNotifier/ │ ├── plantWateringNotifier.py │ └── plantWateringNotifier.yaml ├── pollenNotifier/ │ ├── pollenNotifier.py │ └── pollenNotifier.yaml ├── powerUsageNotification/ │ ├── powerUsageNotification.py │ └── powerUsageNotification.yaml ├── reminder/ │ ├── reminder.py │ └── reminder.yaml ├── requirements.txt ├── seqSink/ │ ├── requirements.txt │ ├── seqSink.py │ └── seqSink.yaml ├── setThermostat/ │ ├── setThermostat.py │ └── setThermostat.yaml ├── setThermostatOnStateChange/ │ ├── setThermostatOnStateChange.py │ └── setThermostatOnStateChange.yaml ├── sleepModeHandler/ │ ├── sleepModeHandler.py │ ├── sleepModeHandler.yaml │ ├── userSleepModeHandler.py │ └── userSleepModeHandler.yaml ├── travelTimeNotifier/ │ ├── travelTimeNotifier.py │ └── travelTimeNotifier.yaml ├── turnFanOnWhenHot/ │ ├── turnFanOnWhenHot.py │ └── turnFanOnWhenHot.yaml └── turnOffBarAfterRestart/ ├── turnOffBarAfterRestart.py └── turnOffBarAfterRestart.yaml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ #appdaemon secrets.py #VS Code .vscode #PyCharm .idea #AppDaemon secrets.yaml ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Kevin Eifinger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Home Assistant Logo
eifinger's Appdaemon Scripts

## About This is the repository containing all my Appdaemon apps. Used together with my Homeassistant config which you can find here: [https://github.com/eifinger/homeassistant-config](https://github.com/eifinger/homeassistant-config) I use Appdaemon for all my automations since I am a programmer myself and it provides me with all the possibilities of the Python world and a far better debugging experience than [HA Automations](https://www.home-assistant.io/getting-started/automation/) or [nodered](https://nodered.org/). ### No longer actively maintained I no longer use appdaemon for my automations and switched over to automations completely: * Traces allow easy debugging of what happened and why * I can see related entities from automations and vise versa * I can quickly adjust automations via the UI editor on my smartphone * There is no disconnect/lag between Appdaemon and Homeassistant. ## How to contribute Just open an Issue or a Pull Request for any Comments, Questions, etc. **Or you can message me on twitter :** [@eifinger](https://twitter.com/eifinger) ## How to use If you have never used Appdaemon before I suggest you start with the [tutorial](https://appdaemon.readthedocs.io/en/latest/TUTORIAL.html) and the [guide](https://appdaemon.readthedocs.io/en/latest/APPGUIDE.html). Both contain more links to great tutorials and examples. I tried to write each App in this repository with reusability in mind. This means that every app in here has a short documentation and is (if possible) written to be easily adjusted to your environment and your needs. ### app_switch Every App has an input_boolean inside HA which turns it on/off. This is useful if I don't want any notifications right now or an App is misbehaving. ## App list * [Alexa Intents](#alexaintents) * [AlexaSpeakerConnector](#alexaspeakerconnector) * [appWatcher](#appWatcher) * [alarmClock](#alarmclock) * [buttonClicked](#buttonclicked) * [comingHome](#cominghome) * [deconzXiaomiButton](#deconzxiaomibutton) * [detectWrongState](#detectwrongstate) * [eventMonitor](#eventmonitor) * [faceRecognitionBot](#facerecognitionbot) * [google_travel_time](#google_travel_time) * [heartbeat](#heartbeat) * [homeArrivalNotifier](#homearrivalnotifier) * [isHomeDeterminer](#ishomedeterminer) * [isUserHomeDeterminer](#isuserhomedeterminer) * [leavingZoneNotifier](#leavingzonenotifier) * [motionTrigger](#motiontrigger) * [newWifiDeviceNotify](#newwifidevicenotify) * [nextAppointmentLeaveNotifier](#nextappointmentleavenotifier) * [notifyOfActionWhenAway](#notifyofactionwhenaway) * [plantWateringNotifier](#plantwateringnotifier) * [powerUsageNotification](#powerusagenotification) * [seqSink](#seqsink) * [setThermostat](#setthermostat) * [setThermostatOnStateChange](#setthermostatonstatechange) * [sleepModeHandler](#sleepmodehandler) * [turnFanOnWhenHot](#turnfanonwhenhot) * [turnOffBarAfterRestart](#turnoffbarafterrestart) * [updateEntityService](#updateentityservice) * [notify](#notify) ### AlexaIntents Are explained [here](alexa/README.md) ### AlexaSpeakerConnector App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers. Uses a [custom_component](https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639) for control alexa as a media player. ```yaml alexaSpeakerConnector: module: alexaSpeakerConnector class: AlexaSpeakerConnector app_switch: input_boolean.alexaSpeakerConnector alexa_entity: media_player.kevins_echo_dot_oben alexa_entity_source: Denon AVR-X1300W receiver: media_player.denon_avr_x1300w receiver_source: Bluetooth ``` ### appWatcher Sends a notification if a WARNING or ERROR is logged in Appdaemon ```yaml heartbeat: module: appWatcher class: AppWatcher notify_name: kevin include_log_message_in_notification: True notify_message: "Es ist ein Fehler aufgetreten in App: {}" #notify_message: "An Error occurred in App: {}" dependencies: - Notifier ``` ### alarmClock Alarm Clock App inspired by [this](https://community.home-assistant.io/t/creating-a-alarm-clock/410) forum post. It fades in my bedroom light and sends a notifcation. The fade in and alarm time is defined by input_number sliders in HA ```yaml alarmClock: module: alarmClock class: AlarmClock alarm_time: sensor.alarm_time wakemeup: input_boolean.wakemeup naturalwakeup: input_number.alarm_natural_wakeup_fade_in alarmweekday: input_boolean.alarmweekday radiowakeup: input_boolean.radiowakeup #TODO radioplayer: input_select.wakeup_radioplayer wakeup_light: light.bedroom_yeelight isweekday: binary_sensor.workday_today notify_name: group_notifications message: "Guten Morgen!" #message: "Good Morning!" ``` ![alarmClock](images/alarmClock.PNG) ### buttonClicked My multipurpose App to link any switch/light to a Xiaomi Button. You can map different entities to the click types ``single`` and ``double``. For the 1st Generation Button you can hold the button to use it as a light dimmer. ```yaml xiaomiroundButtonBedroomClicked: module: buttonClicked class: ButtonClicked sensor: binary_sensor.switch_158d0001b12a12 actor_single: light.bedroom_yeelight actor_double: group.all actor_hold: light.bedroom_yeelight dependencies: - Notifier ``` ### comingHome When the front door openes and no one was home before this will turn on something. I am using it to turn on the light (if the sun is down) and turn on the receiver so I can hear Alexa ```yaml comingHomeYeelight: module: comingHome class: ComingHome app_switch: input_boolean.coming_home_yeelight sensor: binary_sensor.door_window_sensor_158d000126a57b isHome: input_boolean.is_home actor: switch.large_lamp after_sundown: True ``` ### deconzXiaomiButton App which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz. This app is installed via [HACS](https://github.com/custom-components/hacs). The repository itself with the full documentation can be found on github: [appdaemon-deconz-xiaomi-button](https://github.com/eifinger/appdaemon-deconz-xiaomi-button) ```yaml DeconzXiaomiButtonBedroom: module: deconz_xiaomi_button class: DeconzXiaomiButton id: round_button_schlafzimmer actor_single: light.bedroom_yeelight actor_double: group.all actor_hold: light.bedroom_yeelight ``` ### detectWrongState Checks a list of entities which should be on/off when everybody left the house. If something isn't right it will try to turn it off (e.g. a light) and send a notification. ```yaml detectWrongStateWhenLeaving: module: detectWrongStateWhenLeaving class: DetectWrongStateWhenLeaving app_switch: input_boolean.detect_wrong_state_when_leaving entities_off: "binary_sensor.door_window_sensor_158d000205b808,binary_sensor.door_window_sensor_158d00020499ad,\ binary_sensor.door_window_sensor_158d0002059ddf,media_player.denon_avr_x1300w,switch.large_lamp,\ switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\ light.reading_lamp_yeelight,light.treppe_oben,light.stairs_lower_yeelight,switch.ventilator" message: "Du hast {} angelassen. Ich habe es für dich ausgemacht." #message: "You left on {}. I turned it off for you" message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht." #message_off: "You forgot to turn on {}. I turned it on for you" message_reed: "Du hast {} offen gelassen." #message_reed: "You left open {} Dummy." message_reed_off: "Du hast {} zu gelassen." #message_reed_off: "You left {} closed Dummy." isHome: input_boolean.is_home ``` ### eventMonitor Monitor all events. Useful for debugging and developing ```yaml eventMonitor: module: eventMonitor class: Monitor events: ``` ### faceRecognitionBot COMING SOON ### travel_time Monitors my Travel Time Sensors e.g. between home and work. I can enable an input_boolean in HA which causes this App to send me a notication as soon as the traffic is in an acceptable range. I use this drive to/from work when there is the least traffic. ```yaml travelTime_home_from_work: module: travelTimeNotifier class: TravelTimeNotifier sensor: sensor.travel_time_home_from_work notify_input_boolean: input_boolean.travel_time_home_from_work notify_name: group_notifications message: "Du kannst losfahren nach {}" #message: "You can start your journey to {}" ``` ![travelTimeNotifier](images/googleTravelTimes.PNG) ### heartbeat Sets a sensor in Homeassistant which is checked by an automation. The [automation](https://github.com/eifinger/homeassistant-config/blob/master/automation.yaml) sends out a notification if appdaemon does not respond. ### homeArrivalNotifier Greet the person coming home with a notification ```yaml homeArrivalNotifierUserOne: module: homeArrivalNotifier class: HomeArrivalNotifier app_switch: input_boolean.home_arrival_notifier_user_one input_boolean: input_boolean.user_one_home notify_name: group_notifications user_name: Kevin zone_name: Home message: "Willkommen zu Hause {}." #message: "Welcome Home {}." ``` ### isHomeDeterminer Controls an input_boolean "isHome" which is used as a trigger for other Apps. The state depends on other input_booleans controlled by the [isUserHomeDeterminer](isUserHomeDeterminer/isUserHomeDeterminer.py) ```yaml isHomeDeterminer: module: isHomeDeterminer class: IsHomeDeterminer app_switch: input_boolean.is_home_determiner ishome: input_boolean.is_home input_booleans: input_boolean.user_one_home,input_boolean.user_two_home message: "Es ist keiner mehr zu Hause. Setze isHome auf off" #message: "Everyone left home. Setting isHome to off" ``` ### isUserHomeDeterminer The GPS Logger tells me where someone is. But I want to know for sure who just came in the door. App to toggle an input boolean when a person enters or leaves home. This is determined based on a combination of a GPS device tracker and the door sensor. * If the door sensor opens and the device_tracker changed to "home" in the last self.delay minutes this means someone got home * If the door sensor opens and the device_tracker changes to "not_home" in the next self.delay minutes this means someone left home ```yaml isUserHomeDeterminerUserOne: module: isUserHomeDeterminer class: IsUserHomeDeterminer app_switch: input_boolean.is_user_home_determiner_user_one input_boolean: input_boolean.user_one_home device_tracker: person.kevin door_sensor: binary_sensor.door_window_sensor_158d000126a57b ``` ### leavingZoneNotifier Notify if a user is leaving a zone after being there for a certain amount of time. I use this to notify my SO that I am leaving work and driving home ```yaml leavingWorkNotifierUserOne: module: leavingZoneNotifier class: LeavingZoneNotifier app_switch: input_boolean.leaving_work_notifier_user_one device: person.kevin user_name: Kevin lingering_time: 3600 delay: 120 zone: Arbeit notify_name: group_notifications message: "{} hat {} vor {} Minuten verlassen." travel_time_sensor: sensor.travel_time_home_user_one travel_time_sensor_message: "Die momentane Reisezeit beträgt {}." dependencies: - Notifier ``` ### motionTrigger Turn something on/off when a motion sensor turns on. Automatically turn it off again after a delay. ```yaml bathMotionTrigger: module: motionTrigger class: MotionTrigger app_switch: input_boolean.bath_motion_trigger sensor: binary_sensor.0x00158d000236b801_occupancy entity_on: light.lower_bathroom_yeelight entity_off: light.lower_bathroom_yeelight sensor_type: zigbee2mqtt after_sundown: True turn_off_constraint_entities_on: binary_sensor.0x00158d0001fa464b_occupancy delay: 300 ``` ### newWifiDeviceNotify Actually a wrong name. This will send me a notification when any device_tracker component detects a new device. I initally thought to use this as a security feature but found it quite useful when adding new Sonoff switches and such. I get a notification if the setup was successfull. **Version 1.2:** Displays two buttons which let me control the internet access for the new device. The configured fritzbox standard profile denies internet access. With this I can easily allow my guests access to the internet without logging in to my fritzbox manually. ```yaml newWifiDeviceNotify: module: newWifiDeviceNotify class: DeviceNotify notify_name: group_notifications message: "Unbekanntes Gerät entdeckt. Hostname: {}. MAC: {}." #message: "Unknown device connected. Hostname: {}. MAC: {}" ``` ### nextAppointmentLeaveNotifier Send me a notification when it is time to leave for my next appointment based on my current location. Inspired by [this](https://community.home-assistant.io/t/text-to-speech-notification-to-leave-for-appointment/8689) blog post. * Selectable travel mode (car/bus/walk/bike) * Only for google calendar events which have a location * Adjustable offset when to notify * Includes a direct Google Maps Navigation Link in Notification Message Saved my ass quite a few times ```yaml nextAppointmentLeaveNotifier: module: nextAppointmentLeaveNotifier class: NextAppointmentLeaveNotifier sensor: sensor.calc_leave_time notify_input_boolean: input_boolean.announce_time_to_leave notify_name: group_notifications input_number: input_number.leave_time_offset destination_name_sensor: sensor.cal_next_appointment_location travel_time_sensor: sensor.travel_time_next_appointment_location message: "Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten. Hier ist ein Google Maps Link: {}" #message: "It's time to leave to {}. It will take {} minutes. Here is a Google Maps Link: {}" ``` ![nextAppointmentLeaveNotifier](images/next_appoint_leave_modifier.PNG) ![next_appoint_leave_modifier_notification](images/next_appoint_leave_modifier_notification.PNG) ### notifyOfActionWhenAway Notify me of any event for a list of entities when no one is at home. For example a door being openend or a motion sensor triggered ```yaml notifyOfActionWhenAway: module: notifyOfActionWhenAway class: NotifyOfActionWhenAway app_switch: input_boolean.notify_of_action_when_away sensor: "binary_sensor.door_window_sensor_158d000126a57b,binary_sensor.door_window_sensor_158d0001bb4d94,\ binary_sensor.door_window_sensor_158d0001bb4dc0,binary_sensor.door_window_sensor_158d000205b808,\ binary_sensor.door_window_sensor_158d000205b82e,binary_sensor.door_window_sensor_158d00020498b6,\ binary_sensor.door_window_sensor_158d000204ba26,binary_sensor.door_window_sensor_158d0002059ddf,\ binary_sensor.door_window_sensor_158d00020499ad,binary_sensor.door_window_sensor_158d0002048951,\ binary_sensor.door_window_sensor_158d00020455bf,binary_sensor.motion_sensor_158d00012aab97,\ binary_sensor.motion_sensor_158d0001fa464b,binary_sensor.motion_sensor_158d0002006cfa" isHome: input_boolean.is_home user_name: group_notifications isHome_delay: 20 message: "Alarm: {} ist gewechselt auf {}" #message: "Alarm: {} changed to {}" ``` ![notifyOfActionWhenAway](images/notifyOfActionWhenAway.PNG) ### plantWateringNotifier Remind us to water the plants in the morning when the precipiation propability is too low. This uses a Telegram Chatbot. We can press a button in the notification to tell the App that we watered the plants. If we don't do that we get reminded again in the evening. ```yaml plantWateringNotifier: module: plantWateringNotifier class: PlantWateringNotifier app_switch: input_boolean.plant_watering_notifier rain_precip_sensor: sensor.dark_sky_precip_probability rain_precip_intensity_sensor: sensor.dark_sky_precip_intensity precip_type_sensor: sensor.dark_sky_precip notify_name: group_notifications user_id: secret! telegram_user_id reminder_acknowledged_entity: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged message: "Die Regenwahrscheinlichkeit beträgt heute nur {}. Vergiss nicht die Pflanzen zu gießen!" #message: "The Rain Propability is only {}. Don't forget to water the plants!" message_not_needed: "Es wird heute mit einer Wahrscheinlichkeit von {} Prozent ungefähr {} Millimeter pro Stunde regnen. Du brauchst nicht selbst gießen." #message_not_needed: "It will rain today {} millimeter per hour with a propability of {}. You don't have to water your plants" message_evening: "Ich bin mir nicht sicher ob du vergessen hast die Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran." #message_evening: "I'm not sure whether you waterd your plants, so I thought I better remind you again" ``` ![plantWateringReminder](images/plantWateringReminder.PNG) ![plantWateringReminderAcknowledged](images/plantWateringReminderAcknowledged.PNG) ### pollenNotifier Notify in the morning if any monitored pollen level is above a threshold. ```yaml roggenNotifier: module: pollenNotifier class: PollenNotifier app_switch: input_boolean.roggen_notifier pollen_sensor: sensor.pollen_101_roggen_today pollen_name: Roggen notify_name: group_notifications notify_time: 08:00 notify_threshold: 1.0 message: "{} ist {} {} Belastung." #message: "The {} intensity {} is {}." message_no_data: "Ich habe {} leider keine Daten für {}." #message_no_data: "{} I have no pollen data for {}." ``` ![roggenNotify](images/roggenNotify.PNG) ### powerUsageNotification Notify when the Washingmachine or Dishwasher started/finished. Using power measured by TP HS110 Plugs like[![](https://static.tp-link.com/res/images/products/HS110_us_V1_1133_normal_0_20151017154946.jpg)](https://www.amazon.de/dp/B017X72IES/ref=twister_B07CQBCZ5G) ```yaml powerUsageNotification_Dishwasher: module: powerUsageNotification class: PowerUsageNotification app_switch: input_boolean.power_usage_notification_dishwasher sensor: sensor.dishwasher_power_usage notify_name: group_notifications delay: 1260 #21 minutes threshold: 2 alternative_name: Die Spülmaschine message: "{} ist gestartet." #message: "{} just started." message_off: "{} ist fertig." #message_off: "{} just finished." ``` ![dishWasherNotify](images/dishWasherNotify.PNG) ### seqSink App which forwards all logs to seq. Blogged about this app in [this](https://blog.kevineifinger.de/archive/2020/07/06/Log-Management-For-My-AppDaemon-Apps.html) post. ```yaml seqSink: module: seqSink class: SeqSink server_url: "http://seq:5341/" ``` ### setThermostat App which sets a thermostat to a target temperature for a specific duration ```yaml warm_bath_before_wakeup: module: setThermostat class: SetThermostat app_switch: input_boolean.warm_bath_before_wakeup isHome: input_boolean.is_home time_entity: sensor.alarm_time upfront_time: 60 duration: 60 climat_entity: climate.bad_thermostat target_entity: input_number.warm_bath_before_wakeup message: "Ich habe {} auf {} gestellt" #message: "I have set {} to {}" notify_name: group_notifications use_alexa: False dependencies: - Notifier ``` ### setThermostatOnStateChange App which sets a thermostat to a target temperature on state change. ```yaml setBadObenThermostatWhenComingHome: module: setThermostatOnStateChange class: SetThermostatOnStateChange app_switch: input_boolean.set_upper_bath_thermostat_when_coming_home trigger_entity: input_boolean.is_home trigger_state: "on" climate_entity: climate.bad_oben_thermostat target_entity: input_number.set_upper_bath_thermostat_when_coming_home message: "Ich habe {} auf {} °C gestellt" #message: "I have set {} to {}" notify_name: group_notifications use_alexa: False dependencies: - Notifier ``` ### sleepModeHandler Set an input_boolean on/off. Used as a trigger for other Apps. Also controlled by ``Alexa, guten Morgen`` ``Alexa, gute Nacht`` Will watch room sensor of users ````yaml sleepModeHandler: module: sleepModeHandler class: SleepModeHandler app_switch: input_boolean.sleep_mode_handler sleepmode: input_boolean.sleepmode notify_name: group_notifications message_sleeping: "Alle zu Hause sind im Bett" #message_sleeping: "All home are in bed" message_awake: "Alle zu Hause sind wach" #message_awake: "All home are awake" users: - sleep_mode: input_boolean.user_one_sleep isHome: input_boolean.user_one_home - sleep_mode: input_boolean.user_two_sleep isHome: input_boolean.user_two_home dependencies: - Notifier ```` ````yaml userSleepModeHandlerUserOne: module: userSleepModeHandler class: UserSleepModeHandler app_switch: input_boolean.user_sleep_mode_handler_user_one input_boolean: input_boolean.user_one_sleep location_sensor: person.kevin room: bedroom duration: 600 userSleepModeHandlerUserTwo: module: userSleepModeHandler class: UserSleepModeHandler app_switch: input_boolean.user_sleep_mode_handler_user_two input_boolean: input_boolean.user_two_sleep location_sensor: sensor.mqtt_room_user_two room: bedroom duration: 600 ```` ### turnFanOnWhenHot Turns the Fan on when the temperature is above a configurable threshold and someone is in the room ([find3](https://github.com/schollz/find3)) ```yaml turnFanOnWhenHot: module: turnFanOnWhenHot class: TurnFanOnWhenHot app_switch: input_boolean.turn_fan_on_when_hot temp_sensor: sensor.large_lamp_temperature threshold_entity: input_number.turn_fan_on_when_hot_threshold location_sensors: sensor.location_user_one,sensor.location_user_two room: Wohnzimmer actor: switch.large_ventilator delay: 120 ``` ![ventilatorAutomation](images/ventilatorAutomation.PNG) ### turnOffBarAfterRestart As I sometimes restart HA when working on it from remote I turn the Bar lights to red with [this script](https://github.com/eifinger/homeassistant-config/blob/master/updateHomeassistant.sh). This way everyone can see HA is currently unavailable. If it comes back up again this app will turn the light green and then off. ### notify IN DEVELOPMENT Centralizes messaging. Among other things, it will determine whether a user is at home and if yes in which room. Then Alexa in that room will be used additionally to Telegram ```yaml Notify: module: notify class: Notify media_player: media_player.denon_avr_x1300w source: CBL/SAT alexa_media_player: media_player.kevins_echo_dot_oben global_dependencies: - globals ``` # Thanks First of all thanks to the Homeassistant Team and [Andrew Cockburn](https://github.com/acockburn) for making Appdaemon Some of the Apps are taken from the official examples and many based on or at least inspired by [Rene Tode](https://github.com/ReneTode). For example his absolutely fantastic [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App). ================================================ FILE: ad-ench/ench.py ================================================ """EnCh. Entity Checker @benleb / https://github.com/benleb/ad-ench """ __version__ = "0.9.0" from datetime import datetime, timedelta from fnmatch import fnmatch from pprint import pformat from sys import version_info from typing import Any, Dict, Iterable, List, Optional, Tuple, Union import hassapi as hass APP_NAME = "EnCh" APP_ICON = "👩‍⚕️" BATTERY_MIN_LEVEL = 20 INTERVAL_BATTERY_MIN = 180 INTERVAL_BATTERY = INTERVAL_BATTERY_MIN / 60 INTERVAL_UNAVAILABLE_MIN = 60 INTERVAL_UNAVAILABLE = INTERVAL_UNAVAILABLE_MIN / 60 MAX_UNAVAILABLE_MIN = 0 INTERVAL_STALE_MIN = 15 MAX_STALE_MIN = 60 INITIAL_DELAY = 60 RANDOMIZE_SEC: int = 15 SECONDS_PER_MIN: int = 60 EXCLUDE = ["binary_sensor.updater", "persistent_notification.config_entry_discovery"] BAD_STATES = ["unavailable", "unknown"] LEVEL_ATTRIBUTES = ["battery_level", "Battery Level"] CHECKS = ["battery", "stale", "unavailable"] ICONS: Dict[str, str] = dict(battery="🔋", unavailable="⁉️ ", unknown="❓", stale="⏰") # version checks py3_or_higher = version_info.major >= 3 py37_or_higher = py3_or_higher and version_info.minor >= 7 py38_or_higher = py3_or_higher and version_info.minor >= 8 def hl(text: Union[int, float, str]) -> str: return f"\033[1m{text}\033[0m" def hl_entity(entity: str) -> str: if len(splitted := entity.split(".")) > 1: return f"{splitted[0]}.{hl(splitted[1])}" else: return f"{hl(entity)}" class EnCh(hass.Hass): # type: ignore """EnCh.""" def lg(self, msg: str, *args: Any, icon: Optional[str] = None, repeat: int = 1, **kwargs: Any) -> None: kwargs.setdefault("ascii_encode", False) message = f"{f'{icon} ' if icon else ' '}{msg}" _ = [self.log(message, *args, **kwargs) for _ in range(repeat)] async def initialize(self) -> None: """Register API endpoint.""" self.icon = APP_ICON # python version check if not py38_or_higher: icon_alert = "⚠️" self.lg("", icon=icon_alert) self.lg("") self.lg(f"please update to {hl('Python >= 3.8')}! 🤪", icon=icon_alert) self.lg("") self.lg("", icon=icon_alert) if not py37_or_higher: raise ValueError self.cfg: Dict[str, Any] = dict() self.cfg["show_friendly_name"] = bool(self.args.get("show_friendly_name", True)) self.cfg["init_delay_secs"] = int(self.args.get("initial_delay_secs", INITIAL_DELAY)) # home assistant sensor hass_sensor: str if hass_sensor := self.args.get("hass_sensor", "sensor.ench_entities"): self.cfg["hass_sensor"] = hass_sensor if hass_sensor.startswith("sensor.") else f"sensor.{hass_sensor}" self.sensor_state: int = 0 self.sensor_attrs: Dict[str, Any] = {check: [] for check in CHECKS} self.sensor_attrs.update({"unit_of_measurement": "Entities", "should_poll": False}) # global notification if "notify" in self.args: self.cfg["notify"] = self.args.get("notify") # initial wait to give all devices a chance to become available init_delay = await self.datetime() + timedelta(seconds=self.cfg["init_delay_secs"]) # battery check if "battery" in self.args: config: Dict[str, Union[str, int]] = self.args.get("battery") # store configuration self.cfg["battery"] = dict( interval_min=int(config.get("interval_min", INTERVAL_BATTERY_MIN)), min_level=int(config.get("min_level", BATTERY_MIN_LEVEL)), ) # no, per check or global notification self.choose_notify_recipient("battery", config) # schedule check await self.run_every( self.check_battery, init_delay, self.cfg["battery"]["interval_min"] * 60, random_start=-RANDOMIZE_SEC, random_end=RANDOMIZE_SEC, ) # unavailable check if "unavailable" in self.args: config = self.args.get("unavailable") # store configuration self.cfg["unavailable"] = dict( interval_min=int(config.get("interval_min", INTERVAL_UNAVAILABLE_MIN)), max_unavailable_min=int(config.get("max_unavailable_min", MAX_UNAVAILABLE_MIN)), ) # no, per check or global notification self.choose_notify_recipient("unavailable", config) # schedule check self.run_every( self.check_unavailable, await self.datetime() + timedelta(seconds=self.cfg["init_delay_secs"]), self.cfg["unavailable"]["interval_min"] * 60, random_start=-RANDOMIZE_SEC, random_end=RANDOMIZE_SEC, ) # stale entities check if "stale" in self.args: config = self.args.get("stale", {}) interval_min = config.get("interval_min", INTERVAL_STALE_MIN) max_stale_min = config.get("max_stale_min", MAX_STALE_MIN) # store configuration self.cfg["stale"] = dict( interval_min=int(min([interval_min, max_stale_min])), max_stale_min=int(max_stale_min), ) self.cfg["stale"]["entities"] = config.get("entities", []) # no, per check or global notification self.choose_notify_recipient("stale", config) # schedule check self.run_every( self.check_stale, await self.datetime() + timedelta(seconds=self.cfg["init_delay_secs"]), self.cfg["stale"]["interval_min"] * 60, random_start=-RANDOMIZE_SEC, random_end=RANDOMIZE_SEC, ) # merge excluded entities exclude = set(EXCLUDE) exclude.update([e.lower() for e in self.args.get("exclude", set())]) self.cfg["exclude"] = sorted(list(exclude)) # set units self.cfg.setdefault( "_units", dict(interval_min="min", max_stale_min="min", min_level="%"), ) self.show_info(self.args) async def check_battery(self, _: Any) -> None: """Handle scheduled checks.""" check_config = self.cfg["battery"] results: List[Tuple[str, int]] = [] self.lg("Checking entities for low battery levels...", icon=APP_ICON, level="DEBUG") states = await self.get_state() entities = filter( lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg["exclude"]), states, ) for entity in sorted(entities): battery_level = None try: # check entities which may be battery level sensors if "battery_level" in entity or "battery" in entity: # battery_level = int(await self.get_state(entity)) battery_level = int(states[entity]["state"]) # check entity attributes for battery levels if not battery_level: for attr in LEVEL_ATTRIBUTES: # battery_level = int(await self.get_state(entity, attribute=attr)) battery_level = int(states[entity]["attributes"].get(attr)) break except (TypeError, ValueError): pass if battery_level and battery_level <= check_config["min_level"]: # results.append(entity) results.append((entity, battery_level)) last_updated = (await self.last_update(entity)).time().isoformat(timespec="seconds") self.lg( f"{await self._name(entity)} has low " # f"{hl(f'battery → {hl(int(battery_level))}')}%", f"{hl(f'battery → {hl(int(battery_level))}')}% | " f"last update: {last_updated}", # f"last update: {self.adu.last_update(entity)}", icon=ICONS["battery"], ) # send notification notify = self.cfg.get("notify") or check_config.get("notify") if notify and results: await self.call_service( str(notify).replace(".", "/"), message=f"{ICONS['battery']} Battery low ({len(results)}): " f"{', '.join([f'{str(await self._name(entity[0], notification=True))} {entity[1]}%' for entity in results])}", # noqa ) # update hass sensor if "hass_sensor" in self.cfg and self.cfg["hass_sensor"]: await self.update_sensor("battery", [entity[0] for entity in results]) self._print_result("battery", [entity[0] for entity in results], "low battery levels") async def check_unavailable(self, _: Any) -> None: """Handle scheduled checks.""" check_config = self.cfg["unavailable"] results: List[str] = [] self.lg("Checking entities for unavailable/unknown state...", icon=APP_ICON, level="DEBUG") entities = filter( lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg["exclude"]), await self.get_state(), ) for entity in sorted(entities): state = await self.get_state(entity_id=entity) if state in BAD_STATES and entity not in results: last_update = await self.last_update(entity) now: datetime = await self.datetime(aware=True) unavailable_time: timedelta = now - last_update max_unavailable_min = timedelta(minutes=self.cfg["unavailable"]["max_unavailable_min"]) if unavailable_time >= max_unavailable_min: results.append(entity) last_updated = (await self.last_update(entity)).time().isoformat(timespec="seconds") self.lg( f"{await self._name(entity)} is " f"{hl(state)} since {hl(int(unavailable_time.seconds / 60))}min | " f"last update: {last_updated}", icon=ICONS[state], ) # self.lg( # f"{await self._name(entity)} is {hl(state)} | " f"last update: {last_updated}", # icon=ICONS[state], # ) # send notification notify = self.cfg.get("notify") or check_config.get("notify") if notify and results: self.call_service( str(notify).replace(".", "/"), message=f"{APP_ICON} Unavailable entities ({len(results)}): " f"{', '.join([str(await self._name(entity, notification=True)) for entity in results])}", ) # update hass sensor if "hass_sensor" in self.cfg and self.cfg["hass_sensor"]: await self.update_sensor("unavailable", results) self._print_result("unavailable", results, "unavailable/unknown state") async def check_stale(self, _: Any) -> None: check_config = self.cfg["stale"] """Handle scheduled checks.""" results: List[str] = [] self.lg("Checking for stale entities...", icon=APP_ICON, level="DEBUG") if self.cfg["stale"]["entities"]: all_entities = self.cfg["stale"]["entities"] else: all_entities = await self.get_state() entities = filter( lambda entity: not any(fnmatch(entity, pattern) for pattern in self.cfg["exclude"]), all_entities, ) for entity in sorted(entities): attr_last_updated = await self.get_state(entity_id=entity, attribute="last_updated") if not attr_last_updated: self.lg(f"{await self._name(entity)} has no 'last_updated' attribute ¯\\_(ツ)_/¯", icon=ICONS["stale"]) continue last_update = self.convert_utc(attr_last_updated) last_updated = (await self.last_update(entity)).time().isoformat(timespec="seconds") now: datetime = await self.datetime(aware=True) stale_time: timedelta = now - last_update max_stale_min = timedelta(minutes=self.cfg["stale"]["max_stale_min"]) if stale_time and stale_time >= max_stale_min: results.append(entity) self.lg( f"{await self._name(entity)} is " f"{hl(f'stale since {hl(int(stale_time.seconds / 60))}')}min | " f"last update: {last_updated}", icon=ICONS["stale"], ) # send notification notify = self.cfg.get("notify") or check_config.get("notify") if notify and results: self.call_service( str(notify).replace(".", "/"), message=f"{APP_ICON} Stalled entities ({len(results)}): " f"{', '.join([str(await self._name(entity, notification=True)) for entity in results])}", ) # update hass sensor if "hass_sensor" in self.cfg and self.cfg["hass_sensor"]: await self.update_sensor("stale", results) self._print_result("stale", results, "stalled updates") def choose_notify_recipient(self, check: str, config: Dict[str, Any]) -> None: if "notify" in config and "notify" not in self.cfg: self.cfg[check]["notify"] = config["notify"] async def last_update(self, entity_id: str) -> Any: return self.convert_utc(await self.get_state(entity_id=entity_id, attribute="last_updated")) async def _name(self, entity: str, friendly_name: bool = False, notification: bool = False) -> Optional[str]: name: Optional[str] = None if self.cfg["show_friendly_name"]: name = await self.friendly_name(entity) else: name = entity if notification is False and name: name = hl_entity(name) return name def _print_result(self, check: str, entities: List[str], reason: str) -> None: if entities: self.lg(f"{hl(f'{len(entities)} entities')} with {hl(reason)}!", icon=APP_ICON, level="DEBUG") else: self.lg(f"{hl(f'no entities')} with {hl(reason)}!", icon=APP_ICON) async def update_sensor(self, check_name: str, entities: List[str]) -> None: if check_name not in CHECKS: self.lg( f"Unknown check: {hl(f'no entities')} - {self.cfg['hass_sensor']} not updated!", icon=APP_ICON, level="ERROR", ) if len(self.sensor_attrs[check_name]) != len(entities): self.sensor_attrs[check_name] = entities self.sensor_state = sum([len(self.sensor_attrs[check]) for check in CHECKS]) self.set_state(self.cfg["hass_sensor"], state=self.sensor_state, attributes=self.sensor_attrs) self.lg( f"{hl_entity(self.cfg['hass_sensor'])} -> {hl(self.sensor_state)} " f"| {', '.join([f'{hl(k) if k == check_name else k}: {hl(len(v))}' for k, v in self.sensor_attrs.items() if type(v) == list])}", icon=APP_ICON, level="INFO", ) def show_info(self, config: Optional[Dict[str, Any]] = None) -> None: # check if a room is given if config: self.config = config if not self.config: self.lg("no configuration available", icon="‼️", level="ERROR") return room = "" if "room" in self.config: room = f" - {hl(self.config['room'].capitalize())}" self.lg("") self.lg(f"{hl(APP_NAME)} v{hl(__version__)}{room}", icon=self.icon) self.lg("") listeners = self.config.pop("listeners", None) for key, value in self.config.items(): # hide "internal keys" when displaying config if key in ["module", "class"] or key.startswith("_"): continue if isinstance(value, list): self.print_collection(key, value, 2) elif isinstance(value, dict): self.print_collection(key, value, 2) else: self._print_cfg_setting(key, value, 2) if listeners: self.lg(" event listeners:") for listener in sorted(listeners): self.lg(f" - {hl(listener)}") self.lg("") def print_collection(self, key: str, collection: Iterable[Any], indentation: int = 2) -> None: self.log(f"{indentation * ' '}{key}:") indentation = indentation + 2 for item in collection: indent = indentation * " " if isinstance(item, dict): if "name" in item: self.print_collection(item.pop("name", ""), item, indentation) else: self.log(f"{indent}{hl(pformat(item, compact=True))}") elif isinstance(collection, dict): self._print_cfg_setting(item, collection[item], indentation) else: self.log(f"{indent}- {hl(item)}") def _print_cfg_setting(self, key: str, value: Union[int, str], indentation: int) -> None: unit = prefix = "" indent = indentation * " " # legacy way if key == "delay" and isinstance(value, int): unit = "min" min_value = f"{int(value / 60)}:{int(value % 60):02d}" self.log(f"{indent}{key}: {prefix}{hl(min_value)}{unit} ~≈ " f"{hl(value)}sec") else: if "_units" in self.config and key in self.config["_units"]: unit = self.config["_units"][key] if "_prefixes" in self.config and key in self.config["_prefixes"]: prefix = self.config["_prefixes"][key] self.log(f"{indent}{key}: {prefix}{hl(value)}{unit}") ================================================ FILE: alarmClock/alarmClock.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime import math # # Alarm Clock App # # # Args: # alarm_time: entity which holds the alarm time. example: sensor.alarm_time # wakemeup: entity which enables the alarm. example: input_boolean.wakemeup # naturalwakeup: entity which enables the natural wake up fade in. example: input_number.alarm_natural_wakeup_fade_in # alarmweekday: entity which enables alarm only on weekdays. example: input_boolean.alarmweekday # radiowakeup: entity which enables radio wake up. example: input_boolean.radiowakeup # TODO radioplayer: entity which holds the information which radio player to select. example: input_select.wakeup_radioplayer # wakeup_light: light to fade in. example: light.bedroom_yeelight # isweekday: entity which holds the information whether today is a week day. example: binary_sensor.workday_today # notify_name: Who to notify. example: group_notifications # message: localized message to use in notification. e.g. "You left open {} Dummy." # # Release Notes # # Version 1.4: # Catch unknown # # Version 1.3.1: # Use consistent message variable # # Version 1.3: # Use new formatted alarm_time # # Version 1.2: # use Notify App # # Version 1.1: # message now directly in own yaml instead of message module # # Version 1.0: # Initial Version class AlarmClock(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.alarm_time = self.args["alarm_time"] self.wakemeup = self.args["wakemeup"] self.naturalwakeup = self.args["naturalwakeup"] self.alarmweekday = self.args["alarmweekday"] self.radiowakeup = self.args["radiowakeup"] self.isweekday = self.args["isweekday"] self.notify_name = self.args["notify_name"] self.wakeup_light = self.args["wakeup_light"] self.fade_in_time_multiplicator = self.args["fade_in_time_multiplicator"] self.message = self.args["message"] self.button = self.args["button"] self.notifier = self.get_app("Notifier") self.brightness = 100 self.rgb_color = [255, 120, 0] self.alarm_timer = None self.cached_alarm_time = self.get_state(self.alarm_time) self.cached_fade_in_time = self.get_state(self.naturalwakeup) self.add_timer() self.listen_state_handle_list.append( self.listen_state(self.alarm_change, self.alarm_time) ) self.listen_state_handle_list.append( self.listen_state(self.naturalwakeup_change, self.naturalwakeup) ) self.listen_event_handle_list.append( self.listen_event(self.button_clicked, "xiaomi_aqara.click") ) def alarm_change(self, entity, attributes, old, new, kwargs): if new is not None and new != old and new != self.cached_alarm_time: if self.alarm_timer is not None: if self.alarm_timer in self.timer_handle_list: self.timer_handle_list.remove(self.alarm_timer) self.cancel_timer(self.alarm_timer) self.log("Alarm time change: {}".format(new)) self.cached_alarm_time = new self.add_timer() def naturalwakeup_change(self, entity, attributes, old, new, kwargs): if new is not None and new != old and new != self.cached_fade_in_time: if self.alarm_timer is not None: if self.alarm_timer in self.timer_handle_list: self.timer_handle_list.remove(self.alarm_timer) self.cancel_timer(self.alarm_timer) self.log("Fade-In time change: {}".format(new)) self.cached_fade_in_time = new self.add_timer() def add_timer(self): self.log("cached_alarm_time: {}".format(self.cached_alarm_time)) self.log("cached_fade_in_time: {}".format(self.cached_fade_in_time)) offset = self.cached_fade_in_time.split(".", 1)[0] if ( self.cached_alarm_time is not None and self.cached_alarm_time != "" and self.cached_alarm_time != "unknown" ): run_datetime = datetime.datetime.strptime( self.cached_alarm_time, "%Y-%m-%d %H:%M:%S" ) event_time = run_datetime - datetime.timedelta(minutes=int(offset)) try: self.alarm_timer = self.run_at(self.trigger_alarm, event_time) self.timer_handle_list.append(self.alarm_timer) self.log("Alarm will trigger at {}".format(event_time)) except ValueError: self.log("New trigger time would be in the past: {}".format(event_time)) def trigger_alarm(self, kwargs): if self.get_state(self.wakemeup) == "on": if self.get_state(self.alarmweekday) == "off" or ( self.get_state(self.alarmweekday) == "on" and self.get_state(self.isweekday) == "on" ): if float(self.cached_fade_in_time) > 0: self.log( "Turning on {}".format(self.friendly_name(self.wakeup_light)) ) self.call_service( "light/turn_on", entity_id=self.wakeup_light, brightness_pct=1 ) transition = int( float(self.cached_fade_in_time) * int(self.fade_in_time_multiplicator) ) self.log( "Transitioning light in over {} seconds".format(transition) ) self.timer_handle_list.append( self.run_in( self.run_fade_in, 1, transition=transition, brightness_pct=1 ) ) self.timer_handle_list.append( self.run_in(self.run_alarm, float(self.cached_fade_in_time)) ) def button_clicked(self, event_name, data, kwargs): """Extra callback method to trigger the wakeup light on demand by pressing a Xiaomi Button""" if data["entity_id"] == self.button: if data["click_type"] == "single": if float(self.cached_fade_in_time) > 0: self.log( "Turning on {}".format(self.friendly_name(self.wakeup_light)) ) self.call_service( "light/turn_on", entity_id=self.wakeup_light, brightness_pct=1 ) transition = int( float(self.cached_fade_in_time) * int(self.fade_in_time_multiplicator) ) self.log( "Transitioning light in over {} seconds".format(transition) ) self.timer_handle_list.append( self.run_in( self.run_fade_in, 1, transition=transition, brightness_pct=1 ) ) def run_fade_in(self, kwargs): """ Callback / recursion style because the transition feature does not seem to work well with Yeelight for transition values greater than 10s. :param kwargs: :return: """ wait_factor = 1 transition = kwargs["transition"] brightness_pct = kwargs["brightness_pct"] pct_increase = 1 / transition self.log("pct_increase: {}".format(pct_increase), level="DEBUG") if pct_increase < 0.01: wait_factor = math.ceil(0.01 / pct_increase) pct_increase = 0.01 self.log( "pct_increase smaller than 1% next run_in in {} seconds".format( wait_factor ), level="DEBUG", ) brightness_pct_old = brightness_pct self.log("brightness_pct_old: {}".format(brightness_pct_old), level="DEBUG") brightness_pct_new = int((brightness_pct_old + pct_increase * 100)) self.log("brightness_pct_new: {}".format(brightness_pct_new), level="DEBUG") if brightness_pct_new < 100: self.call_service( "light/turn_on", entity_id=self.wakeup_light, rgb_color=self.rgb_color, brightness_pct=brightness_pct_new, ) self.timer_handle_list.append( self.run_in( self.run_fade_in, wait_factor, transition=transition, brightness_pct=brightness_pct_new, ) ) def run_alarm(self, kwargs): self.notifier.notify(self.notify_name, self.message) # TODO radio def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: alarmClock/alarmClock.yaml ================================================ # Alarm Clock App # alarmClock: # module: alarmClock # class: AlarmClock # alarm_time: sensor.alarm_time # wakemeup: input_boolean.wakemeup # naturalwakeup: input_number.alarm_natural_wakeup_fade_in # alarmweekday: input_boolean.alarmweekday # radiowakeup: input_boolean.radiowakeup # #TODO radioplayer: input_select.wakeup_radioplayer # wakeup_light: light.bedroom_yeelight # fade_in_time_multiplicator: 60 # isweekday: binary_sensor.workday_today # notify_name: group_notifications # message: "Guten Morgen!" # #message: "Good Morning!" # button: binary_sensor.switch_158d000215aa28 # dependencies: # - Notifier ================================================ FILE: alexa/README.md ================================================ # Alexa Intents Intents for [Alexa-Appdaemon-App](https://github.com/ReneTode/Alexa-Appdaemon-App) from [Rene Tode](https://github.com/ReneTode). To set it up for yourself follow [this](https://github.com/ReneTode/Alexa-Appdaemon-App/blob/master/alexa%20skill%20tutorial.md) tutorial ## listService Supply friendly names and known entities for other alexa skills. This is needed as this is a custom Alexa App and has nothing to do with HA Cloud / Alexa integration ## turnEntityOffInX Ask Alexa to turn something off in a set amount of minutes. Only works with entities defined under *switchable* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml) ``Alexa tell Home Assistant to turn off Ventilator in 10 Minutes`` ```yaml turnEntityOffInXIntent: module: turnEntityOffInXIntent class: TurnEntityOffInXIntent language: DE textLine: "Okay Homeassistant schaltet {{device}} in" Error:

Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?

unreadableState: "unlesbar fuer mich" dependencies: - listService ``` ## windowsOpen Will tell you if any windows / doors are open and/or tilted Only works with entities defined under *window*/*door*/*door_tilted* in [listService.yaml](https://github.com/eifinger/appdaemon-scripts/blob/master/alexa/listService/listService.yaml) ``Alexa ask Home Assistant whether all windows are closed`` ```yaml windowsOpenIntent: module: windowsOpenIntent class: WindowsOpenIntent language: DE textLineClosed: "Alle Fenster und Türen sind zu" #textLineClosed: "All windows and doors are closed" textLineWindowOpen: "Folgende Fenster sind noch offen" #textLineWindowOpen: "The following windows are stil open..." textLineDoorOpen: "Folgende Türen sind noch offen" #textLineDoorOpen: "The following doors are still open" textLineDoorTilted: "Die folgenden Türen sind noch gekippt" #textLineDoorTilted: "The following doors are tilted" Error:

Ich habe dich nicht richtig verstanden

unreadableState: "unlesbar fuer mich" dependencies: - listService ``` ## nextBusIntent Will tell you the next departure of a bus/train of a [RMV](https://www.home-assistant.io/components/sensor.rmvtransport/) sensor ``Alexa ask Home Assistant when the next bus departs`` ```yaml nextBusIntent: module: nextBusIntent class: nextBusIntent textLine: "Linie {} fährt in {} Minuten" #textLine: "Line {} departs in {} minutes" Error:

Ich habe nicht richtig verstanden was du meinst

sensor: sensor.nach_bruckenkopf global_dependencies: - globals ``` ## remindMeOfXWhenZoneIntent CURRENTLY DOES NOT WORK BECAUSE ALEXA DOES NOT ALLOW INTENTS CONTAINING REMINDERS Will send you a reminder over the notification service when you leave/enter a zone. ``Alexa tell Home Assistant to remind me of <> when entering work`` ```yaml remindMeOfXWhenZoneIntent: module: remindMeOfXWhenZoneIntent class: RemindMeOfXWhenZoneIntent device_tracker: person.kevin notify_name: group_notifications Error:

Es ist etwas schief gegangen

textLine: "Okay ich erinnere dich an {{reminder}} wenn du {{zone}} " textEnter: "erreichst" textLeave: "verlässt" remindMessageSkeleton: "Ich sollte dich erinnern an " zoneMapping: arbeit: work hause: home elmo: elmo dependencies: - Notifier global_dependencies: - globals ``` ================================================ FILE: alexa/alexa.yaml ================================================ alexa_api: # appdaemon skill module: alexa_api class: alexa_api cardTitle: Your Card Title devices: unknownDevice: ein unbekannte platze logfile: /conf/logs/alexaAD.log responselogfile: /conf/logs/alexaResponse.log language: DE temperatureUnit: "Grad" logging: "True" launchRequest: -

bonjour, bist du wieder in {{device}}?

wie kann ich dir helfen?

-

aloha, wie gehts es in {{device}}?

kann ich etwas fuer dich tun?

-

hey, du bist wieder in {{device}}.

was kan ich machen?

-

moin. Schoen das du wieder im {{device}} bist,

was ist dein wunsch?

- mazzel tov.

Ich sage hallo zu alle in {{device}}

Kann ich etwas fuer dich tun?

nextConversationQuestion: -

Was kann ich als naechstes fuer dich tun?

-

Mit was willst du das ich dir helfe?

intentEnd: -

Kann ich noch etwas fuer dich machen?

-

Willst du das ich noch etwas anderes mache?

-

Kann ich dir mit noch etwas helfen?

-

Gibt es noch etwas was ich fuer dich tun kann?

conversationEnd: - Bis zum naechsten mal - arrivederci - bis bald - bis dann - mach's gut - tschoe - viel glueck - Schoen mit dir gesprochen zu haben responseError: - Es tut mir leit aber etwas ist falsch gegangen - Leider meldet home assistant einen Fehler ================================================ FILE: alexa/alexa_api.py ================================================ import appdaemon.plugins.hass.hassapi as hass import random import datetime class alexa_api(hass.Hass): def initialize(self): self.register_endpoint(self.api_call) self.tempCardContent = "" def api_call(self, data): self.my_alexa_interpret_data(data) response = "" dialogDelegate = False endsession = False speech = None card = None logtext = "" if self.dialog == None: if self.requesttype == "LaunchRequest": speech = self.random_arg(self.args["launchRequest"]) endSession = False dialogDelegate = False card = None self.alexalog("Launch request (activate command)", 100, "*") elif self.requesttype == "IntentRequest": speech = None endSession = False dialogDelegate = True card = None self.alexalog("start dialog", 100, "*") elif self.requesttype == "SessionEndedRequest": speech = None endSession = False dialogDelegate = False card = None self.alexalog("The session has ended", 100, "*") else: self.alexalog("Strange state", 100, "*") elif self.dialog == "STARTED": ############################################ # conversation started, just response dialogDelegate ############################################ speech = None endSession = False dialogDelegate = True card = None self.alexalog("dialog has been started", 100, "*") elif self.dialog == "IN_PROGRESS": speech = None endSession = False dialogDelegate = True card = None self.alexalog("dialog is in progress", 100, "*") elif self.dialog == "COMPLETED": try: intentResponse = self.getIntentResponse() except: intentResponse = self.random_arg(self.args["responseError"]) if intentResponse == "stop": ############################################ # user used stop intent, stop conversation ############################################ speech = self.random_arg(self.args["conversationEnd"]) endSession = True dialogDelegate = False card = True cardContent = self.tempCardContent self.tempCardContent = "" self.alexalog( "dialog has been completed, Dialog stopped by user", 100, "*" ) elif intentResponse == "next": ############################################ # user just responded yes, so just a question ############################################ speech = self.random_arg(self.args["nextConversationQuestion"]) endSession = False dialogDelegate = False card = None self.tempCardContent = self.tempCardContent + self.intentname + "\n" self.alexalog( "dialog has been completed, User just responded yes", 100, "*" ) else: ############################################ # Send the response from the Intent + question for next action ############################################ speech = intentResponse endSession = True dialogDelegate = False card = None self.tempCardContent = self.tempCardContent + self.intentname + "\n" self.alexalog("dialog has been completed, Response " + speech, 100, "*") if speech != None: speech = self.cleanup_text(speech) if card: response = self.my_alexa_response( EndSession=endSession, DialogDelegate=dialogDelegate, speech=speech, card=True, title=self.args["cardTitle"], content=cardContent, ) self.alexalog(" ", 100, "X") self.alexalog(" ", 100, "X") self.alexalog(" ", 100, "X") else: response = self.my_alexa_response( EndSession=endSession, DialogDelegate=dialogDelegate, speech=speech, card=None, ) if speech != None: if self.intentname == None: self.alexaresponselog("No Intent : " + speech) else: self.alexaresponselog(self.intentname + " : " + speech) return response, 200 def my_alexa_interpret_data(self, data): ############################################ # create vars from the data ############################################ self.allslots = {} self.slots = {} self.dialog = self.my_alexa_dialog_state(data) self.requesttype = self.my_alexa_request_type(data) self.alexa_error = self.my_alexa_error(data) self.intentname = self.my_alexa_intent_name(data) if self.intentname != None: if "." in self.intentname: splitintent = self.intentname.split(".") self.intentname = splitintent[1] device = data["context"]["System"]["device"]["deviceId"] self.devicename = self.args["devices"]["unknownDevice"] for devicename, deviceid in self.args["devices"].items(): if deviceid == device: self.devicename = devicename if self.devicename == self.args["devices"]["unknownDevice"]: self.log(device) ############################################ # get slots out of the data ############################################ if ( "request" in data and "intent" in data["request"] and "slots" in data["request"]["intent"] ): self.allslots = data["request"]["intent"]["slots"] slottext = "slots: " for slot, slotvalue in self.allslots.items(): if "value" in slotvalue: self.slots[slot] = slotvalue["value"].lower() else: self.slots[slot] = "" if self.intentname == "searchYoutubeIntent": self.slots["search"] = ( self.slots["searchfielda"] + self.slots["searchfieldb"] + self.slots["searchfieldc"] + self.slots["searchfieldd"] ) ############################################ # log that data came in ############################################ self.alexalog("data came in.", 100, "#") self.alexalog( "dialogstate = " + str(self.dialog) + " and requesttype = " + str(self.requesttype) ) self.alexalog("intent = " + str(self.intentname)) slottext = "slots: " for slot, slotvalue in self.slots.items(): slottext = slottext + slot + "= " + str(slotvalue) + ", " self.alexalog(slottext) self.alexalog("error = " + str(self.alexa_error)) self.alexalog(" ", 100, "#") def my_alexa_intent_name(self, data): ############################################ # find the intent name in the data ############################################ if ( "request" in data and "intent" in data["request"] and "name" in data["request"]["intent"] ): return data["request"]["intent"]["name"] else: return None def my_alexa_dialog_state(self, data): ############################################ # find the dialog state in the data ############################################ if "request" in data and "dialogState" in data["request"]: return data["request"]["dialogState"] else: return None def my_alexa_intent(self, data): ############################################ # find the requesttype in the data ############################################ if "request" in data and "intent" in data["request"]: return data["request"]["intent"] else: return None def my_alexa_request_type(self, data): ############################################ # find the requesttype in the data ############################################ if "request" in data and "type" in data["request"]: return data["request"]["type"] else: return None def my_alexa_error(self, data): ############################################ # get an error out of the data ############################################ if ( "request" in data and "error" in data["request"] and "message" in data["request"]["error"] ): return data["request"]["error"]["message"] else: return None def my_alexa_slot_value(self, data, slot): ############################################ # get a slot value from the data ############################################ if ( "request" in data and "intent" in data["request"] and "slots" in data["request"]["intent"] and slot in data["request"]["intent"]["slots"] and "value" in data["request"]["intent"]["slots"][slot] ): return data["request"]["intent"]["slots"][slot]["value"] else: return "" def my_alexa_response( self, EndSession=False, DialogDelegate=False, speech=None, card=None, title=None, content=None, ): ############################################ # put the speechfield from the response toghether ############################################ response = {"shouldEndSession": EndSession} if DialogDelegate: response["directives"] = [ {"type": "Dialog.Delegate", "updatedIntent": None} ] if speech is not None: response["outputSpeech"] = { "type": "SSML", "ssml": "" + speech + "", } if card is not None: response["card"] = {"type": "Simple", "title": title, "content": content} speech = {"version": "1.0", "response": response, "sessionAttributes": {}} return speech def random_arg(self, argName): ############################################ # pick a random text from a list ############################################ if isinstance(argName, list): text = random.choice(argName) else: text = argname return text def floatToStr(self, myfloat): ############################################ # replace . with , for better speech ############################################ floatstr = str(myfloat) floatstr = floatstr.replace(".", ",") return floatstr def cleanup_text(self, text): ############################################ # replace some text like temperary slots with its value ############################################ # self.log(text) for slotname, slotvalue in self.slots.items(): text = text.replace("{{" + slotname + "}}", slotvalue) text = text.replace("{{device}}", self.devicename) text = text.replace("_", " ") text = text.replace("...", "") return text def alexalog(self, logtext, repeat=0, surrounding=""): ############################################ # put an entry in the alexa log ############################################ if self.args["logging"] == "true" or self.args["logging"] == "True": runtime = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S.%f") if repeat > 0: surrounding = surrounding * repeat try: log = open(self.args["logfile"], "a") if logtext == " ": log.write(runtime + "; " + surrounding + "\n") else: if surrounding != "": log.write(runtime + "; " + surrounding + "\n") log.write(runtime + "; " + logtext + "\n") if surrounding != "": log.write(runtime + "; " + surrounding + "\n") log.close() except: self.log("ALEXA LOGFILE CANT BE WRITTEN!!") def alexaresponselog(self, logtext): ############################################ # put an entry in the alexa responses log ############################################ if self.args["logging"] == "true" or self.args["logging"] == "True": runtime = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S.%f") try: log = open(self.args["responselogfile"], "a") log.write(runtime + "; " + logtext + "\n") log.close() except: self.log("ALEXA RESPONSELOGFILE CANT BE WRITTEN!!") def getIntentResponse(self): ############################################ # perform the intent end get the response back # from the intent apps ############################################ if self.intentname == "StopIntent": return "stop" if self.intentname == "yesIntent": return "next" alexa = self.get_app(self.intentname) return alexa.getIntentResponse(self.slots, self.devicename) ================================================ FILE: alexa/custom_skill.json ================================================ { "interactionModel": { "languageModel": { "invocationName": "home assistant", "intents": [ { "name": "AMAZON.CancelIntent", "samples": [] }, { "name": "AMAZON.HelpIntent", "samples": [] }, { "name": "AMAZON.StopIntent", "samples": [] }, { "name": "temperatureStateIntent", "slots": [ { "name": "location", "type": "room", "samples": [ "Vom Schlafzimmer", "Vom Wohnzimmer", "Wohnzimmer" ] } ], "samples": [ "wie viel grad ist es in {location}", "was ist die temperatur in {location}" ] }, { "name": "lightStateIntent", "slots": [ { "name": "device", "type": "device", "samples": [ "Von {device}", "{device}" ] } ], "samples": [ "Ist {device} aus", "Ist {device} an", "Was ist der Status von {device}" ] }, { "name": "turnEntityOffInXIntent", "slots": [ { "name": "device", "type": "device", "samples": [ "die {device}", "der {device}", "Das {device}", "{device}" ] }, { "name": "duration", "type": "AMAZON.DURATION", "samples": [ "In {duration}" ] } ], "samples": [ "In {duration} {device} ausschalten", "{device} in {duration} ausschalten", "er soll {device} in {duration} ausschalten", "es soll {device} in {duration} ausschalten", "Schalte {device} in {duration} aus" ] }, { "name": "windowsOpenIntent", "slots": [], "samples": [ "ob alles zu ist", "ob noch etwas offen ist", "ist noch etwas offen", "ist alles zu", "sind alle türen zu", "ob noch türen offen sind", "ob alle türen zu sind", "ob alle fenster zu sind", "ob noch Fenster offen sind", "ob die Fenster zu sind", "Sind die Fenster zu", "Sind alle Fenster zu", "Welche Fenster sind offen" ] }, { "name": "AMAZON.NavigateHomeIntent", "samples": [] } ], "types": [ { "name": "room", "values": [ { "name": { "value": "oben" } }, { "name": { "value": "küche" } }, { "name": { "value": "schlafzimmer" } }, { "name": { "value": "wohnzimmer" } } ] }, { "name": "device", "values": [ { "name": { "value": "Deckenlampe" } }, { "name": { "value": "Ventilator" } }, { "name": { "value": "Wohnzimmer Temperatur" } } ] } ] }, "dialog": { "intents": [ { "name": "temperatureStateIntent", "confirmationRequired": false, "prompts": {}, "slots": [ { "name": "location", "type": "room", "confirmationRequired": false, "elicitationRequired": true, "prompts": { "elicitation": "Elicit.Slot.748362018084.1289245209680" } } ] }, { "name": "lightStateIntent", "confirmationRequired": false, "prompts": {}, "slots": [ { "name": "device", "type": "device", "confirmationRequired": false, "elicitationRequired": true, "prompts": { "elicitation": "Elicit.Slot.314395877784.987687730608" } } ] }, { "name": "turnEntityOffInXIntent", "confirmationRequired": false, "prompts": {}, "slots": [ { "name": "device", "type": "device", "confirmationRequired": false, "elicitationRequired": true, "prompts": { "elicitation": "Elicit.Slot.208682348144.1260609041167" } }, { "name": "duration", "type": "AMAZON.DURATION", "confirmationRequired": false, "elicitationRequired": true, "prompts": { "elicitation": "Elicit.Slot.208682348144.1375070683830" } } ] } ] }, "prompts": [ { "id": "Elicit.Slot.748362018084.1289245209680", "variations": [ { "type": "PlainText", "value": "Von welchem Raum willst du die Temperatur wissen?" } ] }, { "id": "Elicit.Slot.314395877784.987687730608", "variations": [ { "type": "PlainText", "value": "Von was willst du den Status wissen?" }, { "type": "PlainText", "value": "Entschuldigung. Welches Gerät meintest du?" } ] }, { "id": "Elicit.Slot.208682348144.1260609041167", "variations": [ { "type": "PlainText", "value": "Entschuldigung. Welches Gerät soll ich ausschalten?" }, { "type": "PlainText", "value": "Welches Gerät meinst du?" } ] }, { "id": "Elicit.Slot.208682348144.1375070683830", "variations": [ { "type": "PlainText", "value": "Entschuldigung. Wann soll ich {device} ausschalten?" }, { "type": "PlainText", "value": "Wann soll ich {device} ausschalten?" } ] } ] } } ================================================ FILE: alexa/lightState/lightStateIntent-utterances_DE.csv ================================================ Ist {device} aus Ist {device} an Was ist der Status von {device} ================================================ FILE: alexa/lightState/lightStateIntent-utterances_EN.csv ================================================ Is {device} off Is {device} on What is the state of {device} ================================================ FILE: alexa/lightState/lightStateIntent.py ================================================ import appdaemon.plugins.hass.hassapi as hass import random class lightStateIntent(hass.Hass): def initialize(self): return def getIntentResponse(self, slots, devicename): ############################################ # an Intent to give back the state from a light. # but it also can be any other kind of entity ############################################ try: entityname = self.args["entities"][slots["device"]] state = self.get_state(entityname) if isinstance(state, float): if self.args["language"] == "DE": state = self.floatToStr(state) else: state = str(state) elif isinstance(state, str): state = state else: state = self.args["unreadableState"] text = self.random_arg(self.args["textLine"]) + state except: text = self.random_arg(self.args["Error"]) return text def floatToStr(self, myfloat): ############################################ # replace . with , for better speech ############################################ floatstr = str(myfloat) floatstr = floatstr.replace(".", ",") return floatstr ================================================ FILE: alexa/lightState/lightStateIntent.yaml ================================================ lightStateIntent: module: lightStateIntent class: lightStateIntent language: DE temperatureUnit: "Grad" textLine: - "Der Status von {{device}} ist " - "{{device}} ist in diesem Moment " - "{{device}} ist " Error:

Ich habe nicht richtig verstanden welches geraet oder sensor du wissen wolltest

unreadableState: "unlesbar fuer mich" entities: wohnzimmer temperatur: sensor.large_lamp_temperature deckenlampe: light.livingroom_yeelight ventilator: switch.ventilator ================================================ FILE: alexa/listService/listService.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # Provide the list of HA entities for Alexa Apps # # # Args: # # switchable: dict of switchable entities # temperature: dict of temperature sensors # door: dict of reed sensors showing if the door is completely open # door_tilted: dict of reed sensors showing if a door is partially/leaning open # window: dict of reed sensors showing if a window is open # # # Release Notes # # Version 1.0: # Initial Version class ListService(hass.Hass): def initialize(self): return def getSwitchable(self): return self.args["switchable"] def getTemperature(self): return self.args["temperature"] def getDoor(self): return self.args["door"] def getWindow(self): return self.args["window"] def getDoorTilted(self): return self.args["door_tilted"] ================================================ FILE: alexa/listService/listService.yaml ================================================ listService: module: listService class: ListService switchable: große lampe: switch.large_lamp kleine lampe: switch.small_lamp deckenlampe: light.livingroom_yeelight ventilator: switch.ventilator großer ventilator: switch.large_ventilator markise: switch.markise fernseher: switch.tv schlafzimmer receiver: switch.bedroom_receiver snowboard: switch.snowboard receiver: media_player.denon_avr_x1300w wohnzimmer: group.livingroom wohnung: group.all schlafzimmer lampe: light.bedroom_yeelight schlafzimmer: light.bedroom_yeelight bar: light.bar_table bar licht: light.bar_table treppe unten: light.stairs_lower_yeelight treppe oben: light.treppe_oben leselampe: light.reading_lamp_yeelight lese lampe: light.reading_lamp_yeelight runde lampe: light.reading_lamp_yeelight garderobe: light.lobby_yeelight garderoben licht: light.lobby_yeelight garderobenlicht: light.lobby_yeelight flur: switch.lobby flur licht: switch.lobby bad: light.lower_bathroom_yeelight bad licht: light.lower_bathroom_yeelight treppe: group.stair_lights treppen licht: group.stair_lights treppenlicht: group.stair_lights temperature: große lampe thermostat: sensor.large_lamp_temperature kleine lampe thermostat: sensor.small_lamp_temperature ventilator thermostat: sensor.ventilator_temperature großer ventilator thermostat: sensor.large_ventilator_temperature door: wohnungstuer: binary_sensor.contact_door terassentuer schlafzimmer: binary_sensor.contact_bedroom_door terassentuer: binary_sensor.contact_terrace_door terassentuer arbeitszimmer: binary_sensor.contact_studyroom_door window: kuechenfenster: binary_sensor.contact_kitchen_window badfenster oben: binary_sensor.contact_upper_bathroom_window badfenster unten: binary_sensor.contact_lower_bathroom_window gaestezimmerfenster: binary_sensor.contact_guestroom_window door_tilted: kuechenfenster gekippt: binary_sensor.contact_kitchen_window_tilted terassentuer arbeitszimmer gekippt: binary_sensor.contact_studyroom_door_tilted terassentuer schlafzimmer gekippt: binary_sensor.contact_bedroom_door_tilted terrassentuer gekippt: binary_sensor.contact_terrace_door_tilted badfenster unten gekippt: binary_sensor.contact_lower_bathroom_window_tilted badfenster oben gekippt: binary_sensor.contact_upper_bathroom_window_tilted gaestezimmerfenster gekippt: binary_sensor.contact_guestroom_window_tilted ================================================ FILE: alexa/nextBus/nextBusIntent.py ================================================ import appdaemon.plugins.hass.hassapi as hass import random class nextBusIntent(hass.Hass): def initialize(self): self.sensor = self.args["sensor"] self.textLine = self.args["textLine"] self.error = self.args["error"] def getIntentResponse(self, slots, devicename): ############################################ # give next bus departure ############################################ try: state = self.get_state(self.sensor, attribute="all") line = state["attributes"]["line"] minutes = state["attributes"]["minutes"] text = self.textLine.format(line, minutes) except: text = self.error return text ================================================ FILE: alexa/nextBus/nextBusIntent.yaml ================================================ nextBusIntent: module: nextBusIntent class: nextBusIntent textLine: "Linie {} fährt in {} Minuten" #textLine: "Line {} departs in {} minutes" error:

Ich habe nicht richtig verstanden was du meinst

sensor: sensor.nach_bruckenkopf ================================================ FILE: alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime __ZONE_ACTION_ENTER__ = "kommen" __ZONE_ACTION_LEAVE__ = "verlassen" class RemindMeOfXWhenZoneIntent(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_state_handle_list = [] self.device_tracker = self.args["device_tracker"] self.notify_name = self.args["notify_name"] self.remindMessageSkeleton = self.args["remindMessageSkeleton"] self.notifier = self.get_app("Notifier") return def getIntentResponse(self, slots, devicename): ############################################ # an Intent to give back the state from a light. # but it also can be any other kind of entity ############################################ try: # get zone_name for friendly name used when talking to alexa zone_name = None for key, value in self.args["zoneMapping"].items(): if key == slots["zone"].lower(): zone_name = value # listen to a state change of the zone if zone_name == None: raise Exception( "Could not find zonemapping for: {}".format(slots["zone"].lower()) ) else: self.listen_state_handle_list.append( self.listen_state( self.remind_callback, self.device_tracker, zone=slots["zone"], zoneAction=slots["zoneAction"], reminder=slots["reminder"], ) ) # set correct zoneAction response if slots["zoneAction"] == __ZONE_ACTION_ENTER__: text = self.args["textLine"] + self.args["textEnter"] else: text = self.args["textLine"] + self.args["textLeave"] except Exception as e: self.log("Exception: {}".format(e)) self.log("slots: {}".format(slots)) text = self.random_arg(self.args["Error"]) return text def remind_callback(self, entity, attribute, old, new, kwargs): if kwargs["zoneAction"] == __ZONE_ACTION_ENTER__: if new != old and new == kwargs["zone"]: self.log("Notifying") self.notifier.notify( self.notify_name, self.remindMessageSkeleton + kwargs["reminder"], useAlexa=False, ) elif kwargs["zoneAction"] == __ZONE_ACTION_LEAVE__: if new != old and old == kwargs["zone"]: self.log("Notifying") self.notifier.notify( self.notify_name, self.remindMessageSkeleton + kwargs["reminder"], useAlexa=False, ) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: alexa/remindMeOfXWhenZone/remindMeOfXWhenZoneIntent.yaml ================================================ remindMeOfXWhenZoneIntent: module: remindMeOfXWhenZoneIntent class: RemindMeOfXWhenZoneIntent device_tracker: person.kevin notify_name: group_notifications Error:

Es ist etwas schief gegangen

textLine: "Okay ich erinnere dich an {{reminder}} wenn du {{zone}} " textEnter: "erreichst" textLeave: "verlässt" remindMessageSkeleton: "Ich sollte dich erinnern an " zoneMapping: arbeit: work hause: home elmo: elmo dependencies: - Notifier ================================================ FILE: alexa/temperatureState/temperatureStateIntent-utterances_DE.csv ================================================ wie viel grad ist es in {location} was ist die temperatur in {location} ================================================ FILE: alexa/temperatureState/temperatureStateIntent-utterances_EN.csv ================================================ how many degree is it in {location} what is the temperture in {location} ================================================ FILE: alexa/temperatureState/temperatureStateIntent.py ================================================ import appdaemon.plugins.hass.hassapi as hass import random class temperatureStateIntent(hass.Hass): def initialize(self): return def getIntentResponse(self, slots, devicename): ############################################ # give temperature for a list of temperature sensors ############################################ try: if self.args["language"] == "DE": temp = ( self.floatToStr( self.get_state(self.args["sensors"][slots["location"]]) ) + self.args["temperatureUnit"] ) else: temp = ( str(self.get_state(self.args["sensors"][slots["location"]])) + self.args["temperatureUnit"] ) text = self.random_arg(self.args["textLine"]) + temp except: text = self.random_arg(self.args["Error"]) return text def floatToStr(self, myfloat): ############################################ # replace . with , for better speech ############################################ floatstr = str(myfloat) floatstr = floatstr.replace(".", ",") return floatstr def random_arg(self, argName): ############################################ # pick a random text from a list ############################################ if isinstance(argName, list): text = random.choice(argName) else: text = argName return text ================================================ FILE: alexa/temperatureState/temperatureStateIntent.yaml ================================================ temperatureStateIntent: module: temperatureStateIntent class: temperatureStateIntent language: DE temperatureUnit: "Grad" textLine: - "Die Temperatur im {{location}} ist " - "In diesem Moment ist es im {{location}} " - "Im Moment ist die Temperatur " Error:

Ich habe nicht richtig verstanden welche Temperatur du wissen wolltest

sensors: wohnzimmer: sensor.large_lamp_temperature ================================================ FILE: alexa/turnEntityOffInX/requirements.txt ================================================ isodate ================================================ FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_DE.csv ================================================ In {duration} {device} ausschalten {device} in {duration} ausschalten er soll {device} in {duration} ausschalten es soll {device} in {duration} ausschalten Schalte {device} in {duration} aus ================================================ FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent-utterances_EN.csv ================================================ In {duration} turn off {device} turn off {device} in {duration} it should turn off {device} in {duration} ================================================ FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent.py ================================================ import appdaemon.plugins.hass.hassapi as hass import random import isodate import datetime class TurnEntityOffInXIntent(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listService = self.get_app("listService") return def getIntentResponse(self, slots, devicename): ############################################ # an Intent to give back the state from a light. # but it also can be any other kind of entity ############################################ try: entityname = self.listService.getSwitchable()[slots["device"]] # to upper because of https://github.com/gweis/isodate/issues/52 duration = isodate.parse_duration(slots["duration"].upper()) self.timer_handle_list.append( self.run_in( self.turn_off_callback, duration.total_seconds(), entityname=entityname, ) ) minutes, seconds = divmod(duration.total_seconds(), 60) minutes = int(minutes) seconds = int(seconds) if minutes == 0: if seconds == 1: timeText = " einer Sekunde" else: timeText = " {} Sekunden".format(seconds) elif minutes == 1: if seconds == 1: timeText = " einer Minute und einer Sekunde" elif seconds == 0: timeText = " einer Minute" else: timeText = " einer Minute und {} Sekunden".format(seconds) else: if seconds == 1: timeText = " {} Minuten und einer Sekunde".format(minutes) elif seconds == 0: timeText = " {} Minuten".format(minutes) else: timeText = " {} Minuten und {} Sekunden".format(minutes, seconds) text = self.args["textLine"] + timeText + " ab." except Exception as e: self.log("Exception: {}".format(e)) self.log("slots: {}".format(slots)) text = self.random_arg(self.args["Error"]) return text def turn_off_callback(self, kwargs): entityname = kwargs["entityname"] self.log("Turning off {}".format(entityname)) self.turn_off(entityname) def random_arg(self, argName): ############################################ # pick a random text from a list ############################################ if isinstance(argName, list): text = random.choice(argName) else: text = argName return text def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: alexa/turnEntityOffInX/turnEntityOffInXIntent.yaml ================================================ turnEntityOffInXIntent: module: turnEntityOffInXIntent class: TurnEntityOffInXIntent language: DE textLine: "Okay Homeassistant schaltet {{device}} in" Error:

Ich habe nicht richtig verstanden welches geraet soll ich ausschalten?

unreadableState: "unlesbar fuer mich" dependencies: - listService ================================================ FILE: alexa/windowsOpen/windowsOpenIntent-utterances_DE.csv ================================================ ob alles zu ist ob noch etwas offen ist ist noch etwas offen ist alles zu sind alle türen zu ob noch türen offen sind ob alle türen zu sind ob alle fenster zu sind ob noch Fenster offen sind ob die Fenster zu sind Sind die Fenster zu Sind alle Fenster zu Welche Fenster sind offen ================================================ FILE: alexa/windowsOpen/windowsOpenIntent-utterances_EN.csv ================================================ whether everything is closed whether something is still open is something still open is everthing closed are all doors closed whether some doors are still open whether all doors are closed whether all windows are closed whether some windows are open whether the windows are closed Are the Windows closed Are all windows closed Which Windows are open ================================================ FILE: alexa/windowsOpen/windowsOpenIntent.py ================================================ import appdaemon.plugins.hass.hassapi as hass import random import isodate import datetime class WindowsOpenIntent(hass.Hass): def initialize(self): self.listService = self.get_app("listService") return def getIntentResponse(self, slots, devicename): ############################################ # an Intent to give back the state of windows ############################################ try: windows_dict = self.listService.getWindow() doors_dict = self.listService.getDoor() doors_tilted_dict = self.listService.getDoorTilted() window_open_list = [] door_open_list = [] door_tilted_list = [] # iterate over all window entities for key, value in windows_dict.items(): # if a window is open ("on") add it to the window_open_list if self.get_state(value) == "on": window_open_list.append(value) # iterate over all door entities for key, value in doors_dict.items(): # if a door is open ("on") add it to the door_open_list if self.get_state(value) == "on": door_open_list.append(value) # iterate over all door_tilted entities for key, value in doors_tilted_dict.items(): # if a door is tilted ("on") add it to the door_tilted_list if self.get_state(value) == "on": door_tilted_list.append(value) text = "" # add open windows to response if len(window_open_list) > 0: if text != "": text = text + ' ' text = text + self.args["textLineWindowOpen"] for entity in window_open_list: text = ( text + ' ' + self.friendly_name(entity) ) # add open doors to response if len(door_open_list) > 0: if text != "": text = text + ' ' text = text + self.args["textLineDoorOpen"] for entity in door_open_list: text = ( text + ' ' + self.friendly_name(entity) ) # add tilted doors to reponse if len(door_tilted_list) > 0: if text != "": text = text + ' ' text = text + self.args["textLineDoorTilted"] for entity in door_tilted_list: friendly_name = self.friendly_name(entity) # remove "gekippt" (german for tilted) from the friendly name friendly_name = friendly_name.replace(" gekippt", "") friendly_name = friendly_name.replace(" Gekippt", "") text = text + ' ' + friendly_name # if all closed response if text == "": text = self.args["textLineClosed"] except Exception as e: self.log("Exception: {}".format(e)) self.log("slots: {}".format(slots)) text = self.random_arg(self.args["Error"]) return text def random_arg(self, argName): ############################################ # pick a random text from a list ############################################ if isinstance(argName, list): text = random.choice(argName) else: text = argName return text ================================================ FILE: alexa/windowsOpen/windowsOpenIntent.yaml ================================================ windowsOpenIntent: module: windowsOpenIntent class: WindowsOpenIntent language: DE textLineClosed: "Alle Fenster und Türen sind zu" #textLineClosed: "All windows and doors are closed" textLineWindowOpen: "Folgende Fenster sind noch offen" #textLineWindowOpen: "The following windows are stil open..." textLineDoorOpen: "Folgende Türen sind noch offen" #textLineDoorOpen: "The following doors are still open" textLineDoorTilted: "Die folgenden Türen sind noch gekippt" #textLineDoorTilted: "The following doors are tilted" Error:

Ich habe dich nicht richtig verstanden

unreadableState: "unlesbar fuer mich" dependencies: - listService ================================================ FILE: alexaSpeakerConnector/alexaSpeakerConnector.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # alexa_entity: the alexa media player entity. example: media_player.kevins_echo_dot_oben # alexa_entity_source: source to set alexa to. example: Denon AVR-X1300W # receiver: Receiver to turn on. example: media_player.denon_avr_x1300w # receiver_source: source to set receiver to. example: Bluetooth # # Release Notes # # Version 1.2.0: # Introduce INITIAL_VOLUME # # Version 1.1.1: # Fix WAITING_TIME # # Version 1.1: # Introduce WAITING_TIME # # Version 1.0: # Initial Version WAITING_TIME = 10 INITIAL_VOLUME = 30 class AlexaSpeakerConnector(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.timer_handle_list = [] self.app_switch = self.args["app_switch"] self.alexa_entity = self.args["alexa_entity"] self.alexa_entity_source = self.args["alexa_entity_source"] self.receiver = self.args["receiver"] self.receiver_source = self.args["receiver_source"] self.listen_state_handle_list.append( self.listen_state(self.state_change, self.alexa_entity) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new.lower() == "playing" and old.lower() != "playing": self.log("{} changed to {}".format(self.alexa_entity, new)) # Only trigger when the receiver is off. Otherwise its probably playing something if self.get_state(self.receiver) == "off": self.log( "Setting source of {} to: {}".format( self.receiver, self.receiver_source ) ) self.call_service( "media_player/select_source", entity_id=self.receiver, source=self.receiver_source, ) self.log(f"Setting volume of {self.receiver} to: {INITIAL_VOLUME}") self.call_service( "media_player/volume_set", entity_id=self.receiver, volume_level=INITIAL_VOLUME, ) self.timer_handle_list.append( self.run_in(self.run_in_callback, WAITING_TIME) ) def run_in_callback(self, kwargs): """ Callback method to introduce a waiting time for the receiver to come 'online' :return: """ self.log( "Setting source of {} to: {}".format( self.alexa_entity, self.alexa_entity_source ) ) self.call_service( "media_player/select_source", entity_id=self.alexa_entity, source=self.alexa_entity_source, ) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: alexaSpeakerConnector/alexaSpeakerConnector.yaml ================================================ #App to Turn on Receiver Bluetooth when Alexa is playing something so it plays on the big speakers # alexaSpeakerConnector: # module: alexaSpeakerConnector # class: AlexaSpeakerConnector # app_switch: input_boolean.alexa_speaker_connector # alexa_entity: media_player.kevins_echo_dot_oben # alexa_entity_source: Denon AVR-X1300W # receiver: media_player.denon_avr_x1300w # receiver_source: Bluetooth ================================================ FILE: appWatcher/appWatcher.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App which listens on the log for App crashes and notifies via telegram # # Args: # # Release Notes # # Version 2.0: # Updates for Appdaemon Version 4.0.3 # # Version 1.0: # Initial Version class AppWatcher(hass.Hass): def initialize(self): self.notify_name = self.args["notify_name"] self.notify_message = self.args["notify_message"] try: self.exclude_apps = self.args["exclude_apps"].split(",") except KeyError: self.exclude_apps = None # App dependencies self.notifier = self.get_app("Notifier") self.handle = self.listen_log(self.log_message_callback) def log_message_callback(self, app_name, ts, level, log_type, message, kwargs): if level == "WARNING" or level == "ERROR" or level == "CRITICAL": if app_name == "AppDaemon": if "Unexpected error" in message: self.notifier.notify( self.notify_name, self.notify_message.format(message), useAlexa=False, ) def terminate(self): self.cancel_listen_log(self.handle) ================================================ FILE: appWatcher/appWatcher.yaml ================================================ appWatcher: module: appWatcher class: AppWatcher notify_name: kevin notify_message: "AppDaemon error: {}" #notify_message: "Appdaemon reported an error: {}" dependencies: - Notifier ================================================ FILE: apps.yaml ================================================ ################################################################# ## Global ################################################################# global_modules: - globals ================================================ FILE: buttonClicked/buttonClicked.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App which toggles entities for single/double presses of xiaomi buttons # # Args: # # sensor: sensor to monitor e.g. sensor.upstairs_smoke # actor_single: actor to toggle on single click # actor_double: actor to toggle on double click # actor_hold: actor to dim on hold # Release Notes # # Version 1.2: # All actors optional # # Version 1.1: # added actor_hold # # Version 1.0: # Initial Version class ButtonClicked(hass.Hass): def initialize(self): self.listen_event_handle_list = [] self.timer_handle_list = [] self.actor_single = self.args.get("actor_single") self.actor_double = self.args.get("actor_double") self.actor_hold = self.args.get("actor_hold") self.dimmer_timer_handle = None self.listen_event_handle_list.append( self.listen_event(self.event_detected, "xiaomi_aqara.click") ) def event_detected(self, event_name, data, kwargs): if data["entity_id"] == self.args["sensor"]: if data["click_type"] == "single" and self.actor_single != None: self.log("ButtonClicked: {}".format(data["entity_id"])) # Is on if self.get_state(self.actor_single) == "on": self.log("Turning {} off".format(self.actor_single)) # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4 if self.actor_single.startswith("light"): self.call_service( "light/turn_on", entity_id=self.actor_single, transition=1, brightness_pct=1, ) self.timer_handle_list.append( self.run_in(self.turn_off_workaround, 2) ) else: self.turn_off(self.actor_single) # Is off if self.get_state(self.actor_single) == "off": self.log("Turning {} on".format(self.actor_single)) if self.actor_single.startswith("light"): self.call_service( "light/turn_on", entity_id=self.actor_single, transition=1, brightness_pct=100, ) else: self.turn_on(self.actor_single) if data["click_type"] == "double" and self.actor_double != None: self.log("Double Button Click: {}".format(data["entity_id"])) self.log("Toggling {}".format(self.actor_double)) # Is on if self.get_state(self.actor_double) == "on": # Workaround for Yeelight see https://community.home-assistant.io/t/transition-for-turn-off-service-doesnt-work-for-yeelight-lightstrip/25333/4 if self.actor_single.startswith("light"): self.call_service( "light/turn_on", entity_id=self.actor_single, transition=1, brightness_pct=1, ) self.timer_handle_list.append( self.run_in(self.turn_off_workaround, 2) ) else: self.turn_off(self.actor_single) # Is off if self.get_state(self.actor_double) == "off": self.log("Turning {} on".format(self.actor_single)) if self.actor_single.startswith("light"): self.call_service( "light/turn_on", entity_id=self.actor_single, transition=1, brightness_pct=100, ) else: self.turn_on(self.actor_single) if data["click_type"] == "long_click_press" and self.actor_hold != None: self.log("Long Button Click: {}".format(data["entity_id"])) self.log("Starting Dimmer") self.dimmer_timer_handle = self.run_every( self.dimmer_callback, datetime.datetime.now(), 0.5, entity_id=self.actor_hold, ) self.timer_handle_list.append(self.dimmer_timer_handle) if data["click_type"] == "hold" and self.actor_hold != None: self.log("Button Release: {}".format(data["entity_id"])) self.log("Stopping Dimmer") if self.dimmer_timer_handle != None: self.cancel_timer(self.dimmer_timer_handle) def dimmer_callback(self, kwargs): """Dimm the by 10% light. If it would dim above 100% start again at 10%""" brightness_pct_old = ( int( self.get_state(self.actor_hold, attribute="all")["attributes"][ "brightness" ] ) / 255 ) brightness_pct_new = brightness_pct_old + 0.1 if brightness_pct_new > 1: brightness_pct_new = 0.1 self.call_service( "light/turn_on", entity_id=kwargs["entity_id"], brightness_pct=brightness_pct_new * 100, ) def turn_off_workaround(self, *kwargs): self.call_service("light/turn_off", entity_id=self.actor_single) def terminate(self): for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: buttonClicked/buttonClicked.yaml ================================================ # App which toggles entities for single/double presses of xiaomi buttons # xiaomiroundButtonBedroomClicked: # module: buttonClicked # class: ButtonClicked # sensor: binary_sensor.switch_158d0001b12a12 # actor_single: light.bedroom_yeelight # actor_double: group.all # actor_hold: light.bedroom_yeelight # dependencies: # - Notifier # xiaomisquareButtonLobbyClicked: # module: buttonClicked # class: ButtonClicked # sensor: binary_sensor.switch_158d00021329bc # actor_single: switch.lobby # actor_double: switch.lobby # dependencies: # - Notifier # xiaomiroundButtonBathroomClicked: # module: buttonClicked # class: ButtonClicked # sensor: sensor.0x00158d00012db9e5_click # actor_single: light.lower_bathroom_yeelight # actor_double: light.lower_bathroom_yeelight # actor_hold: light.lower_bathroom_yeelight # dependencies: # - Notifier ================================================ FILE: comingHome/comingHome.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App to Turn on Lobby Lamp when Door openes and no one is Home # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # sensor: door sensor # isHome: input_boolean which shows if someone is home eg input_boolean.isHome # actor (optional): actor to turn on. example: script.receiver_set_source_bluetooth # service (optional): service to call. example: media_player.volume_set # service_data (optional): dictionary of attributes for the service call. # after_sundown (optional): whether to only trigger after sundown. example: True # Release Notes # # Version 1.4.2: # unwrap service_data # # Version 1.4.1: # fix duplicate line for self.actor # # Version 1.4: # Add service and service_data and make actor optional # # Version 1.3.2: # Check for new != old # # Version 1.3.1: # Actually implement isHome # # Version 1.3: # Added app_switch # # Version 1.2: # Added after_sundown # # Version 1.1: # Using globals # # Version 1.0: # Initial Version class ComingHome(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.sensor = self.args["sensor"] self.isHome = self.args["isHome"] self.actor = self.args.get("actor") self.service = self.args.get("service") self.service_data = self.args.get("service_data") self.after_sundown = self.args.get("after_sundown") self.delay = 2 self.listen_state_handle_list.append( self.listen_state(self.state_change, self.sensor) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new != old: isHome_attributes = self.get_state(self.isHome, attribute="all") isHome_state = isHome_attributes["state"] last_changed = self.convert_utc(isHome_attributes["last_changed"]) if isHome_state == "off" or ( datetime.datetime.now(datetime.timezone.utc) - last_changed <= datetime.timedelta(seconds=self.delay) ): if self.after_sundown is not None and self.after_sundown: if self.sun_down(): self.turn_on_actor(self.actor, entity, new) self.my_call_service( self.service, self.service_data, entity, new ) else: self.turn_on_actor(self.actor, entity, new) self.my_call_service( self.service, self.service_data, entity, new ) def turn_on_actor(self, actor, entity, new): if self.actor is not None: self.log("{} changed to {}".format(self.friendly_name(entity), new)) self.turn_on(actor) def my_call_service(self, service, service_data, entity, new): if self.service is not None: if self.service_data is not None: self.log("{} changed to {}".format(self.friendly_name(entity), new)) self.call_service(service, **service_data) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: comingHome/comingHome.yaml ================================================ #Switch on Lobby lamp when the first person is coming home and the sun is down # comingHomeYeelight: # module: comingHome # class: ComingHome # app_switch: input_boolean.coming_home_yeelight # sensor: binary_sensor.contact_door # isHome: input_boolean.is_home # actor: switch.large_lamp # after_sundown: True # comingHomeSetVolume: # module: comingHome # class: ComingHome # app_switch: input_boolean.coming_home_set_volume # sensor: binary_sensor.contact_door # isHome: input_boolean.is_home # service: media_player/volume_set # service_data: # entity_id: media_player.denon_avr_x1300w # volume_level: 0.3 ================================================ FILE: deconz_xiaomi_button/deconz_xiaomi_button.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App which toggles entities for single/double/hold presses of Xiaomi buttons connected via deconz # # Args: # # id: id of the xiaomi button # actor_single: actor to toggle on single click # actor_double: actor to toggle on double click # actor_hold: actor to dim on hold # # Release Notes # # Version 2.0.2 # use else when toggling # # Version 2.0.1 # use elif when toggling # # Version 2.0: # Removed unneeded workaround for yeelight # # Version 1.0: # Initial Version class DeconzXiaomiButton(hass.Hass): def initialize(self): self.listen_event_handle_list = [] self.timer_handle_list = [] self.actor_single = self.args.get("actor_single") self.actor_double = self.args.get("actor_double") self.actor_hold = self.args.get("actor_hold") self.id = self.args["id"] self.dimmer_timer_handle = None self.listen_event_handle_list.append( self.listen_event(self.event_detected, "deconz_event") ) def event_detected(self, event_name, data, kwargs): if data["id"] == self.id: if data["event"] == 1002 and self.actor_single is not None: self.log("ButtonClicked: {}".format(data["id"])) self.log("Toggling {}".format(self.actor_double)) # Is on if self.get_state(self.actor_single) == "on": self.log("Turning {} off".format(self.actor_single)) self.turn_off(self.actor_single) # Is off else: self.log("Turning {} on".format(self.actor_single)) self.turn_on(self.actor_single) if data["event"] == 1004 and self.actor_double is not None: self.log("Double Button Click: {}".format(data["id"])) self.log("Toggling {}".format(self.actor_double)) # Is on if self.get_state(self.actor_double) == "on": self.turn_off(self.actor_double) # Is off else: self.log("Turning {} on".format(self.actor_double)) self.turn_on(self.actor_double) if data["event"] == 1001 and self.actor_hold is not None: self.log("Long Button Click: {}".format(data["id"])) self.log("Starting Dimmer") self.dimmer_timer_handle = self.run_every( self.dimmer_callback, datetime.datetime.now(), 0.5, entity_id=self.actor_hold, ) self.timer_handle_list.append(self.dimmer_timer_handle) if data["event"] == 1003 and self.actor_hold is not None: self.log("Button Release: {}".format(data["id"])) self.log("Stopping Dimmer") if self.dimmer_timer_handle is not None: self.cancel_timer(self.dimmer_timer_handle) def dimmer_callback(self, kwargs): """Dimm the by 10% light. If it would dim above 100% start again at 10%""" brightness_pct_old = ( int( self.get_state(self.actor_hold, attribute="all")["attributes"][ "brightness" ] ) / 255 ) brightness_pct_new = brightness_pct_old + 0.1 if brightness_pct_new > 1: brightness_pct_new = 0.1 self.call_service( "light/turn_on", entity_id=kwargs["entity_id"], brightness_pct=brightness_pct_new * 100, ) def terminate(self): for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: deconz_xiaomi_button.yaml ================================================ DeconzXiaomiButtonBedroom: module: deconz_xiaomi_button class: DeconzXiaomiButton id: round_button_schlafzimmer actor_single: light.bedroom_yeelight actor_double: group.all actor_hold: light.bedroom_yeelight DeconzXiaomiButtonLobby: module: deconz_xiaomi_button class: DeconzXiaomiButton id: flur_switch actor_single: switch.lobby actor_double: switch.lobby DeconzXiaomiButtonLobby: module: deconz_xiaomi_button class: DeconzXiaomiButton id: flur_switch actor_single: switch.lobby actor_double: switch.lobby DeconzXiaomiButtonBathroom: module: deconz_xiaomi_button class: DeconzXiaomiButton id: round_button_bad actor_single: light.lower_bathroom_yeelight actor_double: light.lower_bathroom_yeelight actor_hold: light.lower_bathroom_yeelight ================================================ FILE: detectWrongState/detectWrongState.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App which notifies of wrong states based on a state change # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # entities_on (optional): list of entities which should be on # entities_off (optional): list of entities which should off # trigger_entity: entity which triggers this app. example: input_boolean.is_home # trigger_state: new state of trigger_entity which triggers this app. example: "off" # after_sundown (optional): Only trigger after sundown. example: True # message_: message to use in notification # message_off_: message to use in notification # message_reed_: message to use in notification # message_reed_off_: message to use in notification # notify_name: who to notify. example: group_notifications # use_alexa: use alexa for notification. example: False # # Release Notes # # Version 2.1: # More off_states to support alexa_media # # Version 2.0: # Renamed to detectWrongState, notification optional # # Version 1.9: # check unavailable when using get_state # # Version 1.8: # check None when using get_state # # Version 1.7: # check for != off instead of == on # # Version 1.6.1: # fix wrong key access for attributes # # Version 1.6: # garage_door to device_classes of reed sensors # # Version 1.5: # distinguish normal and reed switches by device_class # # Version 1.4.1: # fix wrong assignment of app_switch # # Version 1.4: # Generalize to detectWrongState # # Version 1.3: # use Notify App # # Version 1.2: # message now directly in own yaml instead of message module # # Version 1.1: # Using globals and app_switch # # Version 1.0: # Initial Version class DetectWrongState(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] try: self.entities_on = self.args["entities_on"].split(",") except KeyError: self.entities_on = [] try: self.entities_off = self.args["entities_off"].split(",") except KeyError: self.entities_off = [] self.after_sundown = self.args.get("after_sundown") self.trigger_entity = self.args["trigger_entity"] self.trigger_state = self.args["trigger_state"] self.message = self.args.get("message") self.message_off = self.args.get("message_off") self.message_reed = self.args.get("message_reed") self.message_reed_off = self.args.get("message_reed_off") self.notify_name = self.args.get("notify_name") self.use_alexa = self.args.get("use_alexa") self.notifier = self.get_app("Notifier") self.listen_state_handle_list.append( self.listen_state(self.state_change, self.trigger_entity) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new == self.trigger_state: if self.after_sundown is None or ( (self.after_sundown and self.sun_down()) or self.after_sundown is not False ): self.check_entities_should_be_off() self.check_entities_should_be_on() def check_entities_should_be_off(self): off_states = ["off", "unavailable", "paused", "standby"] for entity in self.entities_off: state = self.get_state(entity) self.log(f"entity: {entity}") if state is not None and state not in off_states: if self.is_entity_reed_contact(entity): message = self.message_reed else: self.turn_off(entity) message = self.message self.send_notification(message, entity) def check_entities_should_be_on(self): for entity in self.entities_on: state = self.get_state(entity) if state == "off": if self.is_entity_reed_contact(entity): message = self.message_reed_off else: self.turn_on(entity) message = self.message_on self.send_notification(message, entity) def is_entity_reed_contact(self, entity): reed_types = ["window", "door", "garage_door"] full_state = self.get_state(entity, attribute="all") if full_state is not None: attributes = full_state["attributes"] self.log("full_state: {}".format(full_state), level="DEBUG") if attributes.get("device_class") in reed_types: return True return False def send_notification(self, message, entity): if message is not None: formatted_message = message.format(self.friendly_name(entity)) self.log(formatted_message) if self.notify_name is not None: self.notifier.notify( self.notify_name, formatted_message, useAlexa=self.use_alexa, ) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: detectWrongState/detectWrongState.yaml ================================================ # detectWrongStateWhenLeaving: # module: detectWrongState # class: DetectWrongState # app_switch: input_boolean.detect_wrong_state_when_leaving # entities_off: "binary_sensor.contact_bedroom_door,\ # binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\ # binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\ # binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\ # binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\ # media_player.denon_avr_x1300w,switch.large_lamp,switch.small_lamp,switch.snowboard,\ # light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,light.reading_lamp_yeelight,\ # light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\ # switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,light.lower_bathroom_yeelight,\ # media_player.kevin_s_echo_dot_unten,media_player.kevins_echo,media_player.kevins_echo_dot,\ # media_player.kevins_echo_dot_oben,binary_sensor.contact_upper_bathroom_window_tilted,\ # binary_sensor.contact_badfenster" # trigger_entity: input_boolean.is_home # trigger_state: "off" # message: "Du hast {} angelassen. Ich habe es für dich ausgemacht." # #message: "You left on {}. I turned it off for you" # message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht." # #message_off: "You forgot to turn on {}. I turned it on for you" # message_reed: "Du hast {} offen gelassen." # #message_reed: "You left open {} Dummy." # message_reed_off: "Du hast {} zu gelassen." # #message_reed_off: "You left {} closed Dummy." # notify_name: group_notifications # use_alexa: False # log_level: DEBUG # dependencies: # - Notifier # detectWindowsOpenWhenGoingToBed: # module: detectWrongState # class: DetectWrongState # app_switch: input_boolean.detect_windows_open_when_going_to_bed # entities_off: "binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\ # binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\ # binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\ # binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\ # binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\ # binary_sensor.contact_upper_bathroom_window_tilted,binary_sensor.contact_badfenster" # after_sundown: True # trigger_entity: input_boolean.sleepmode # trigger_state: "on" # message: "Du hast {} angelassen. Ich habe es für dich ausgemacht." # #message: "You left on {}. I turned it off for you" # message_off: "Du hast {} vergessen anzumachen. Ich habe es für dich angemacht." # #message_off: "You forgot to turn on {}. I turned it on for you" # message_reed: "Du hast {} offen gelassen." # #message_reed: "You left open {} Dummy." # message_reed_off: "Du hast {} zu gelassen." # #message_reed_off: "You left {} closed Dummy." # notify_name: group_notifications # use_alexa: True # log_level: DEBUG # dependencies: # - Notifier # detectDevicesOnWhenGoingToBed: # module: detectWrongState # class: DetectWrongState # app_switch: input_boolean.detect_devices_on_when_going_to_bed # entities_off: "media_player.denon_avr_x1300w,switch.large_lamp,\ # switch.small_lamp,switch.snowboard,light.bedroom_yeelight,light.bar_table,light.lobby_yeelight,\ # light.reading_lamp_yeelight,light.upper_stairs_yeelight,light.stairs_lower_yeelight,switch.ventilator,light.livingroom_yeelight,\ # switch.tv,switch.weihnachtslichter,switch.bedroom_receiver,switch.tv,light.bar_table,light.lower_bathroom_yeelight,\ # switch.markise, switch.coffee_machine_plug_relay" # trigger_entity: input_boolean.sleepmode # trigger_state: "on" # log_level: DEBUG # dependencies: # - Notifier ================================================ FILE: ench.yaml ================================================ --- ench: module: ench class: EnCh notify: "notify.kevin" exclude: - device_tracker.venue_8* - person.kevin - device_tracker.sonoff_large_ventilator_7998 - device_tracker.sonoff_ventilator_8133 - device_tracker.android* - device_tracker.astrids_mbp - device_tracker.franzi_s_iphone - device_tracker.galaxy* - device_tracker.iphone* - device_tracker.oneplus* - device_tracker.unifi* - light.group_0 - media_player.kevin_s* - sensor.192_168_1_39* - sensor.192_168_1_48* - sensor.large_ventilator* - sensor.ventilator* - switch.ventilator - switch.large_ventilator - sensor.travel_time_next* - sensor.glances*_temp - sensor.publish_ip_on_boot - sensor.*odroid* - media_player.55pus7304_12 - media_player.fernseher - sensor.consumption_31 - sensor.power_30 - sensor.openweathermap* - light.bar_table - sensor.*_nachster_wecker - switch.xiaomi_plug - media_player.55pus7304_12_* # Alexa Media Player - sensor.*next_alarm - sensor.*next_reminder - sensor.*next_timer - sensor.this_device* - switch.*_repeat_switch* - switch.*_shuffle_switch* - switch.*do_not_disturb_switch battery: interval_min: 180 min_level: 20 unavailable: interval_min: 60 max_unavailable_min: 15 ================================================ FILE: eventMonitor/eventMonitor.py ================================================ import appdaemon.plugins.hass.hassapi as hass """ Monitor events and output changes to the verbose_log. Nice for debugging purposes. Arguments: - events: List of events to monitor """ class Monitor(hass.Hass): def initialize(self): self.listen_event_handle_list = [] events = self.args["events"] if events != None: for event in self.split_device_list(self.args["events"]): self.log('watching event "{}" for state changes'.format(event)) self.listen_event_handle_list.append( self.listen_event(self.changed, event) ) if len(self.listen_event_handle_list) == 0: self.log("watching all events for state changes") self.listen_event_handle_list.append(self.listen_event(self.changed)) def changed(self, event_name, data, kwargs): self.log(event_name + ": " + str(data)) def terminate(self): for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) ================================================ FILE: eventMonitor/eventMonitor.yaml ================================================ #eventMonitor: # module: eventMonitor # class: Monitor # events: ================================================ FILE: faceRecognitionBot/faceRecognitionBot.py ================================================ import json from json import JSONDecodeError import appdaemon.plugins.hass.hassapi as hass # pylint: disable=import-error import shutil import os import time import datetime import requests # # App which runs face detection and notifies the user with the result # # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # sensor: binary sensor to use as trigger # button: xiaomi button to use as a trigger # camera : camera entity. example: camera.ip_webcam # local_file_camera: local file camera entity. example: camera.saved_image # notify_name: Who to notify. example: group_notifications # wol_switch: Wake on Lan switch which turns on the facebox server. example: switch.facebox_wol # user_id: The user_id of the telegram user to ask whether he knows an unknown face # number_of_images: Number of images to take. example: 10 # waitBeforeSnapshot: How many seconds to wait before triggering the inital snapshot. example: 2.5 # message_face_identified: Message to send if a face got identified. # message_unkown_face: Message to send if a face is unknown # message_provide_name # message_name_provided # message_name_provided_callback # ip: Ip of facerec_service. example: 192.168.0.1 # port: port of facerec_service. example: 8080 # # Release Notes # # Version 1.3.beta: # Fully working # # Version 1.2: # Rework to FaceRecognitionBot # # Version 1.1: # Take Snapshot before sending WoL # # Version 1.0: # Initial Version CLASSIFIER = "faces" TIMEOUT = 20 PROVIDE_NAME_TIMEOUT = 5 IDENTIFIER_DELIMITER = "_" FILENAME_DELIMITER = "-" MAXIMUM_DISTANCE = 0.40 UNKNOWN_FACE_NAME = "unkown" class FaceRecognitionBot(hass.Hass): def initialize(self): # handle lists self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] # args self.app_switch = self.args["app_switch"] self.sensor = self.args["sensor"] self.button = self.args["button"] self.camera = self.args["camera"] self.local_file_camera = self.args["local_file_camera"] self.filename = self.args["filename"] self.notify_name = self.args["notify_name"] self.wol_switch = self.args["wol_switch"] self.user_id = self.args["user_id"] self.number_of_images = self.args["number_of_images"] self.waitBeforeSnapshot = self.args["waitBeforeSnapshot"] self.message_face_identified = self.args["message_face_identified"] self.message_unkown_face = self.args["message_unkown_face"] self.message_unkown_face_with_known = self.args[ "message_unkown_face_with_known" ] self.message_provide_name = self.args["message_provide_name"] self.message_name_provided = self.args["message_name_provided"] self.message_name_provided_callback = self.args[ "message_name_provided_callback" ] self.facebox_healthcheck_filename = self.args["facebox_healthcheck_filename"] self.healthcheck_face_name = self.args["healthcheck_face_name"] self.ip = self.args["ip"] self.port = self.args["port"] # optional args self.facebox_source_directory = self.args["facebox_source_directory"] if not self.facebox_source_directory.endswith("/"): self.facebox_source_directory = self.facebox_source_directory + "/" self.facebox_unknown_directory = self.args["facebox_unknown_directory"] if not self.facebox_unknown_directory.endswith("/"): self.facebox_unknown_directory = self.facebox_unknown_directory + "/" self.facebox_noface_directory = self.args["facebox_noface_directory"] if not self.facebox_noface_directory.endswith("/"): self.facebox_noface_directory = self.facebox_noface_directory + "/" self.facebox_known_faces_directory = self.args["facebox_known_faces_directory"] if not self.facebox_known_faces_directory.endswith("/"): self.facebox_known_faces_directory = ( self.facebox_known_faces_directory + "/" ) # App dependencies self.notifier = self.get_app("Notifier") # Subscribe to sensors self.listen_state_handle_list.append( self.listen_state(self.triggered, self.sensor) ) # Subscribe to custom triggers self.listen_event_handle_list.append( self.listen_event(self.button_clicked, "xiaomi_aqara.click") ) self.listen_event_handle_list.append( self.listen_event(self.learn_faces_event_callback, "eifinger_learn_faces") ) # subscribe to telegram events self.listen_event_handle_list.append( self.listen_event(self.receive_telegram_callback, "telegram_callback") ) self.listen_event_handle_list.append( self.listen_event(self.receive_telegram_text, "telegram_text") ) # Teach periodic run self.timer_handle_list.append(self.run_in(self.check_health_callback, 5)) # custom variables self.valid_filetypes = (".jpg", ".png", ".jpeg") self.teach_url = "http://{}:{}/faces".format(self.ip, self.port) self.health_url = "http://{}:{}/faces".format(self.ip, self.port) self.check_url = "http://{}:{}".format(self.ip, self.port) self.run_in_initial_delay = 43200 self.run_in_delay = self.run_in_initial_delay self.run_in_error_delay = 60 self.check_health_timeout = 10 self.exclude_folders = ( "healthcheck", "multiple", "noface", "tmp", "unknown", "new", ) self.provide_name_timeout_start = None self.last_identifier = None self.last_message_id = None self.last_from_first = None ############################################################### # Teacher ############################################################### def check_health_callback(self, kwargs): """Check health. Runs repeatedly until it is veryfied that the classifier is healthy and faces are trained. If it is healthy and trained it will check again after run_in_initial_delay""" try: if self.check_classifier_health(): self.check_if_trained(None) self.timer_handle_list.append( self.run_in(self.check_health_callback, self.run_in_delay) ) except requests.exceptions.HTTPError as exception: self.log( "Error trying to turn on entity. Will try again in 1s. Error: {}".format( exception ), level="WARNING", ) self.timer_handle_list.append(self.run_in(self.check_health_callback, 1)) def learn_faces_event_callback(self, event_name, data, kwargs): """Callback function for manual trigger of face learning""" self.log("Event received. Triggering Face Learning") self.teach_faces(self.facebox_known_faces_directory, self.exclude_folders) def teach_name_by_file(self, teach_url, name, file_path): """Teach the classifier a single name using a single file.""" file_name = file_path.split("/")[-1] file = {"file": open(file_path, "rb")} teach_url = teach_url + "?id=" + name response = requests.post(teach_url, files=file) if response.status_code == 200: self.log("File: {} taught with name: {}".format(file_name, name)) return True elif response.status_code == 400: self.log( "Teaching of file: {} failed with message: {}".format( file_name, response.text ) ) return False def check_classifier_health(self): """Check if classifier is reachable under health_url and returns HTTP 200""" try: response = requests.get(self.health_url, timeout=self.check_health_timeout) if response.status_code == 200: self.log("Health-check passed") self.run_in_delay = self.run_in_initial_delay self.log("Setting run_in_delay to {}".format(self.run_in_delay)) return True else: self.log("Health-check failed") self.log(response.status_code) # check for recurring error if self.run_in_delay < self.run_in_initial_delay: self.run_in_delay = self.run_in_delay * 2 else: self.run_in_delay = self.run_in_error_delay return False except requests.exceptions.Timeout as timeout_exception: self.log("Health-Check timed out") self.check_health_timeout = self.check_health_timeout * 1.5 return self.check_classifier_health() except requests.exceptions.RequestException as exception: self.log("Server is unreachable", level="WARNING") self.log(exception, level="WARNING") # check for recurring error if self.run_in_delay < self.run_in_initial_delay: self.run_in_delay = self.run_in_delay * 2 else: self.run_in_delay = self.run_in_error_delay self.log("Setting run_in_delay to {}".format(self.run_in_delay)) def check_if_trained(self, kwargs): """Check if faces are trained. If not train them. Checks for a picture with a known result if the classifier returns the correct result """ response = self.post_image(self.check_url, self.facebox_healthcheck_filename) if ( response and response.status_code == 200 and len(response.json()["faces"]) > 0 and response.json()["faces"][0]["id"] == self.healthcheck_face_name ): self.log("Faces are still taught") else: self.log("Faces are not taught") self.teach_faces(self.facebox_known_faces_directory, self.exclude_folders) def teach_faces(self, folderpath, exclude_folders=[]): """Teach faces. Will iterate over all subdirectories of 'folderpath' and teach the name within that subdirectory with the name of the subdirectory""" self.log("Teaching faces") for folder_name in self.list_folders(folderpath): if not folder_name in exclude_folders: folder_path = os.path.join(folderpath, folder_name) for file in os.listdir(folder_path): if file.endswith(self.valid_filetypes): file_path = os.path.join(folder_path, file) self.teach_name_by_file(self.teach_url, folder_name, file_path) def teach_name_by_directory(self, name, folderpath): """Teach faces in a directory for a given anme""" self.log("Teaching faces in dir: {}".format(folderpath)) for file in os.listdir(folderpath): if file.endswith(self.valid_filetypes): file_path = os.path.join(folderpath, file) self.teach_name_by_file(self.teach_url, name, file_path) ############################################################### # Classifier ############################################################### def button_clicked(self, event_name, data, kwargs): """Extra callback method to trigger the face detection on demand by pressing a Xiaomi Button""" if data["entity_id"] == self.button: if data["click_type"] == "single": self.timer_handle_list.append( self.run_in(self.takeSnapshots, self.waitBeforeSnapshot) ) def triggered(self, entity, attribute, old, new, kwargs): """State Callback to start the face detection process""" if self.get_state(self.app_switch) == "on": if new == "on": self.timer_handle_list.append( self.run_in(self.takeSnapshots, self.waitBeforeSnapshot) ) def takeSnapshots(self, kwargs): """Take a snapshot. Save to a file.""" file_locations = [] timestamp = time.strftime("%Y%m%d%H%M%S") directory = self.facebox_source_directory + "new/" + timestamp if not os.path.exists(directory): os.makedirs(directory) for i in range(0, self.number_of_images): filename = ( directory + "/" + timestamp + FILENAME_DELIMITER + str(i) + ".jpg" ) self.log("Calling camera/snapshot and saving it to: {}".format(filename)) self.call_service( "camera/snapshot", entity_id=self.camera, filename=filename ) file_locations.append(filename) self.timer_handle_list.append( self.run_in(self.processImages, 0, file_locations=file_locations) ) def processImages(self, kwargs): """Trigger image processing for all images and process the results Get the classifier result for each image store it in a dictionary in the following format {filename: {"count":int, "faces":{ [ {"dist":float, "id":name} ] }, "matchedFacesCount":int} } """ result_dict_dict = {} for filename in kwargs["file_locations"]: response = self.post_image(self.check_url, filename) if response is not None: result_dict = {} self.log("response is: {}".format(response.text)) try: response_json = response.json() result_dict["count"] = response_json["count"] result_dict["faces"] = response_json["faces"] result_dict["matchedFacesCount"] = len(response_json["faces"]) result_dict_dict[filename] = result_dict except JSONDecodeError: self.log("JSONDecodeError. Skipping response") # get the maximum number of faces detected in one image maxCount = self._getMaxCountFromResult(result_dict_dict) # get a list of distinct recognized face names faceNames = self._getFaceNamesFromResult(result_dict_dict) self.log("Number of distinct faces: {}".format(len(faceNames))) if maxCount > 1: self.log("At least one time detected more than one face") # check if it contains an unknown face if UNKNOWN_FACE_NAME in faceNames: self._notifyUnkownFaceFound(result_dict_dict) else: for faceName in faceNames: if faceName in self._getKnownFaces(): self.log(self.message_face_identified.format(faceName)) self.notifier.notify( self.notify_name, self.message_face_identified.format(faceName), ) # copy file to saved image to display in HA shutil.copy(filename, self.filename) elif maxCount == 1: self.log("Always detected one face") # check if always the same face if len(faceNames) > 1: self.log("Not always the same face") # TODO test! # at least one time detected a known face # notify of who was detected for faceName in faceNames: if faceName in self._getKnownFaces(): self.log(self.message_face_identified.format(faceName)) self.notifier.notify( self.notify_name, self.message_face_identified.format(faceName), ) # copy file to saved image to display in HA shutil.copy(filename, self.filename) # process the unknown faces if UNKNOWN_FACE_NAME in faceNames: self._processUnkownFaceFound(result_dict_dict) else: self.log("Always the same face") # Is it a known face? if len(faceNames) > 0 and faceNames[0] in self._getKnownFaces(): # always identified the same known person self.log(self.message_face_identified.format(faceNames[0])) self.notifier.notify( self.notify_name, self.message_face_identified.format(faceNames[0]), ) # Move files to known face subdirectory for filename in result_dict_dict: # at this time we know it is at most 1 and it is always the same known face if result_dict_dict[filename]["count"] == 1: directory = ( self.facebox_known_faces_directory + result_dict_dict[filename]["faces"][0]["id"] ) new_filename = os.path.join( directory, os.path.split(filename)[1] ) # copy file to saved image to display in HA shutil.copy(filename, self.filename) self.log( "Move file from {} to {}".format(filename, new_filename) ) shutil.move(filename, new_filename) # trigger teaching self.teach_name_by_file( self.teach_url, result_dict_dict[filename]["faces"][0]["id"], new_filename, ) else: # unknown face self._processUnkownFaceFound(result_dict_dict) else: self.log("Detected no faces") # get directory of images and post that in telegram def _processUnkownFaceFound(self, result_dict_dict): """Store the faces for later use and ask the user if he knows the unkown face""" # TODO check if the faces are similar # create a temp identifier, compare and delete identifier again # self._determineIfSameUnkownFace(result_dict_dict) # get a file where the unknown face was detected and send it filename = self._getFileWithUnknownFaceFromResult(result_dict_dict) # copy file to saved image to display in HA shutil.copy(filename, self.filename) # move all files where a face was detected to the unkown folder identifier = self._moveFilesToUnknown(result_dict_dict) filename_without_path = os.path.split(filename)[1] # send photo unknown_filename = self.facebox_unknown_directory + filename_without_path self.log(f"Sending photo with filename: {unknown_filename}") self.call_service("telegram_bot/send_photo", file=unknown_filename, target=self.user_id) if identifier == "": self.log("Identifier is empty", level="ERROR") else: self.ask_for_name(identifier) def _notifyUnkownFaceFound(self, result_dict_dict): """Notify of an unkown face in a image where a known face was detected""" # get a file where the unknown face was detected and send it filename = self._getFileWithUnknownFaceFromResult(result_dict_dict) # copy file to saved image to display in HA shutil.copy(filename, self.filename) # send photo self.call_service("telegram_bot/send_photo", file=filename, target=self.user_id) self.log(self.message_unkown_face_with_known) self.notifier.notify(self.notify_name, self.message_unkown_face_with_known) def _getMaxCountFromResult(self, result_dict_dict): """Get the maximum number of faces found in the pictures""" count_list = [d["count"] for d in result_dict_dict.values()] return max(count_list) def _getFaceNamesFromResult(self, result_dict_dict): """Return a list of names for the identified faces""" try: id_list = [] for d in result_dict_dict.values(): # check for unknown face if len(d["faces"]) == 0 and d["count"] == 1: id_list.append(UNKNOWN_FACE_NAME) else: for face in d["faces"]: if face["dist"] < MAXIMUM_DISTANCE: id_list.append(face["id"]) # if distance(similarity) too large, mark as unknown else: self.log( "Similary distance of {} is larger than maximum threshold of {}".format( face["dist"], MAXIMUM_DISTANCE ) ) id_list.append(UNKNOWN_FACE_NAME) face["id"] = UNKNOWN_FACE_NAME self.log("FacesNames: {}".format(list(set(id_list)))) return list(set(id_list)) except TypeError: return [] def _getFileWithUnknownFaceFromResult(self, result_dict_dict): """Get the first file from the result which has an unmatched face""" for filename in result_dict_dict.keys(): if ( result_dict_dict[filename]["count"] > 0 and result_dict_dict[filename]["faces"][0]["id"] == UNKNOWN_FACE_NAME ): return filename def _determineIfSameUnkownFace(self, result_dict_dict): """Determine if the unkown face which was detected several times is the same unknown face""" # get all files with unknown faces unkown_faces = [] for filename in result_dict_dict.keys(): if ( result_dict_dict[filename]["count"] == 1 and result_dict_dict[filename]["faces"][0]["id"] == UNKNOWN_FACE_NAME ): unkown_faces.append(filename) # iterate over all files for k, filename in enumerate(unkown_faces): for i, filename in enumerate(unkown_faces): if i < k: pass elif i == k: # teach the first face filename_without_path = os.path.split(filename)[1] self.teach_name_by_file( self.teach_url, filename_without_path, filename ) else: response = self.post_image(self.check_url, filename) response_json = response.json() if response_json["count"] > 0: if ( response_json["faces"][0]["id"] == filename_without_path and response_json["faces"][0]["dist"] < MAXIMUM_DISTANCE ): # same face remove it from the list unkown_faces.remove(filename) def _moveFilesToUnknown(self, result_dict_dict): """Copy all files where the unknown face was detected to the unknown folder. Returns the timestamp under which all files can be identified""" identifier = "" for filename in result_dict_dict.keys(): if result_dict_dict[filename]["count"] > 0 and ( len(result_dict_dict[filename]["faces"]) == 0 or result_dict_dict[filename]["faces"][0]["id"] == UNKNOWN_FACE_NAME ): filename_without_path = os.path.split(filename)[1] # get the timestamp as identifier, strip everything after "-"" identifier = filename_without_path.split(FILENAME_DELIMITER)[0] self.log("Identifier is: {}".format(identifier), level="DEBUG") new_filename = self.facebox_unknown_directory + filename_without_path self.log("Move file from {} to {}".format(filename, new_filename)) shutil.move(filename, new_filename) return identifier def _moveFilesFromUnkownToDirectoryByIdentifier(self, directory, identifier): """Copy all files in the unknown folder which belong to an identifier (a timestamp) to a new directory""" if not os.path.exists(directory): os.makedirs(directory) for file in os.listdir(self.facebox_unknown_directory): if identifier in file: filename = os.path.join(self.facebox_unknown_directory, file) new_filename = os.path.join(directory, file) self.log("Move file from {} to {}".format(filename, new_filename)) shutil.move(filename, new_filename) def _getKnownFaces(self): """Return a list of known face names. Iterates over the subdirectory names of facebox_known_faces_directory""" return self.list_folders(self.facebox_known_faces_directory) def list_folders(self, directory): """Returns a list of folders These are not full paths, just the folder.""" folders = [ dir for dir in os.listdir(directory) if os.path.isdir(os.path.join(directory, dir)) and not dir.startswith(directory) and not dir.startswith(".") ] folders.sort(key=str.lower) return folders def post_image(self, url, image): """Post an image to the classifier.""" try: response = requests.post( url, files={"file": open(image, "rb")}, timeout=self.check_health_timeout ) return response except requests.exceptions.ConnectionError: self.log("ConnectionError") except requests.exceptions.ReadTimeout: self.log("ReadTimeout") self.check_health_timeout = self.check_health_timeout * 1.5 self.log(f"Setting Health Check Timeout to {self.check_health_timeout}") ############################################################### # Telegram Bot ############################################################### def ask_for_name(self, identifier): """Asks the user if he knows the face in the photo. The identifier is needed to link the user reply back to this message""" self.log("Asking for name") keyboard = [[("Unbekannt", "/unkown" + IDENTIFIER_DELIMITER + identifier)]] for face in self._getKnownFaces(): keyboard.append([(face, "/" + face + IDENTIFIER_DELIMITER + identifier)]) self.log("keyboard is: {}".format(keyboard), level="DEBUG") self.call_service( "telegram_bot/send_message", target=self.user_id, message=self.message_unkown_face, inline_keyboard=keyboard, ) def receive_telegram_callback(self, event_name, data, kwargs): """Event listener for Telegram callback queries.""" self.log("callback data: {}".format(data)) data_callback = data["data"] callback_id = data["id"] chat_id = data["chat_id"] message_id = data["message"]["message_id"] text = data["message"]["text"] from_first = data["from_first"] for face in self._getKnownFaces(): if data_callback.startswith("/" + face + IDENTIFIER_DELIMITER): self.log("Received Telegram Callback for {}".format(face)) self.call_service( "telegram_bot/answer_callback_query", message="Dankeschön!", callback_query_id=callback_id, ) self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=message_id, message=self.message_name_provided_callback.format( from_first, face ), inline_keyboard=[], ) identifier = data_callback.split(IDENTIFIER_DELIMITER)[1] directory = self.facebox_known_faces_directory + face self._moveFilesFromUnkownToDirectoryByIdentifier(directory, identifier) self.teach_name_by_directory(face, directory) if data_callback.startswith("/unkown"): # Answer callback query self.call_service( "telegram_bot/answer_callback_query", message="Dankeschön!", callback_query_id=callback_id, ) self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=message_id, message=text, inline_keyboard=[], ) self.notifier.notify( self.notify_name, self.message_provide_name.format(PROVIDE_NAME_TIMEOUT), useAlexa=False, ) self.provide_name_timeout_start = datetime.datetime.now() self.last_identifier = data_callback.split(IDENTIFIER_DELIMITER)[1] self.last_message_id = message_id self.last_from_first = from_first def receive_telegram_text(self, event_name, data, kwargs): """Telegram text listener""" self.log("callback data: {}".format(data), level="DEBUG") chat_id = data["chat_id"] text = data["text"] if self.provide_name_timeout_start != None and ( datetime.datetime.now() - self.provide_name_timeout_start < datetime.timedelta(minutes=PROVIDE_NAME_TIMEOUT) ): # Edit the last ask_for_name message self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=self.last_message_id, message=self.message_name_provided_callback.format( self.last_from_first, text ), inline_keyboard=[], ) # Say thanks self.notifier.notify( self.notify_name, self.message_name_provided.format(text), useAlexa=False, ) # Copy files to new directory directory = self.facebox_known_faces_directory + text self._moveFilesFromUnkownToDirectoryByIdentifier( directory, self.last_identifier ) self.teach_name_by_directory(text, directory) else: self.log("PROVIDE_NAME_TIMEOUT exceeded") def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: faceRecognitionBot/faceRecognitionBot.yaml ================================================ # faceRecognitionBot: # module: faceRecognitionBot # class: FaceRecognitionBot # app_switch: input_boolean.facebox_notifier # sensor: binary_sensor.contact_door # button: binary_sensor.switch_158d000215aa28 # camera: camera.android_ip_webcam_door # local_file_camera: camera.saved_image # filename: !secret facebox_notifier_filename # image_processing: image_processing.facebox # notify_name: group_notifications # wol_switch: switch.facebox_wol # user_id: !secret telegram_user_id # facebox_source_directory: !secret facebox_folderpath # facebox_unknown_directory: !secret facebox_unknown_directory # facebox_noface_directory: !secret facebox_noface_directory # facebox_known_faces_directory: !secret facebox_known_faces_directory # facebox_healthcheck_filename: !secret facebox_healthcheck_filename # healthcheck_face_name: Kevin # number_of_images: 10 # waitBeforeSnapshot: 1 # ip: !secret facebox_ip # port: !secret facebox_port # message_face_identified: "Ich habe {} erkannt" # #message_face_identified: "I have recognized {}." # message_unkown_face: "Ich habe dieses Gesicht nicht erkannt. Kennst du es?" # #message_unkown_face: "I have not recognized this face. Do you know it?" # message_unkown_face_with_known: "Ich habe auch ein unbekanntes Gesicht entdeckt." # #message_unkown_face_with_known: "I have also discovered an unknown face." # message_provide_name: "Wenn du das Gesicht kennst, kannst du mir einfach innerhalb der nächsten {} Minuten den Namen schreiben. Dann merke ich ihn mir!" # #message_provide_name: "If you know the face you can write the name to me within the next {} minutes. I will remember it!" # message_name_provided: "Okay. Ich merke mir, dass das {} ist" # #message_name_provided: "Okay. I will remember that this is {}" # message_name_provided_callback: "{} sagte, dass dies {} ist." # #message_name_provided_callback: "{} said that this is {}" # dependencies: # - Notifier ================================================ FILE: globals.py ================================================ import random def random_arg(argList): ############################################ # pick a random text from a list ############################################ if isinstance(argList, list): text = random.choice(argList) else: text = argList return text ================================================ FILE: heartbeat/heartbeat.py ================================================ import appdaemon.plugins.hass.hassapi as hass from requests.exceptions import HTTPError # # App which sets a homeassistant entity as a heartbeat to check for threadstarvation etc # # Args: # sensor: sensor.appdaemon_heartbeat # # Release Notes # # Version 1.1: # Set start to None run_minutely will run after 1 minute # # Version 1.0: # Initial Version class Heartbeat(hass.Hass): def initialize(self): self.timer_handle_list = [] self.sensor = self.args["sensor"] self.heartbeat(None) self.timer_handle_list.append(self.run_minutely(self.heartbeat, start=None)) def heartbeat(self, kwargs): try: self.set_state(self.sensor, state=str(self.time())) self.log("Heartbeat", level="DEBUG") except HTTPError as exception: self.log( "Error trying to set entity. Will try again in 5s. Error: {}".format( exception ), level="WARNING", ) self.timer_handle_list.append(self.run_in(self.heartbeat, 5)) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: heartbeat/heartbeat.yaml ================================================ heartbeat: module: heartbeat class: Heartbeat sensor: sensor.appdaemon_heartbeat ================================================ FILE: homeArrivalNotifier/homeArrivalNotifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App to send a notification if someone arrives at home # # Args: # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # input_boolean: input boolean which holds the information of someone is home or not # notify_name: Who to notify # user_name: name to use in notification message # zone_name: Name of the zone # message: message to use in notification # Release Notes # # Version 1.4.1: # Use consistent message variable # # Version 1.4: # use Notify App # # Version 1.3: # message now directly in own yaml instead of message module # # Version 1.2: # Added app_switch # # Version 1.1: # Added user_name # # Version 1.0: # Initial Version class HomeArrivalNotifier(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.zone_name = self.args["zone_name"] self.input_boolean = self.args["input_boolean"] self.notify_name = self.args["notify_name"] self.user_name = self.args["user_name"] self.message = self.args["message"] self.notifier = self.get_app("Notifier") self.listen_state_handle_list.append( self.listen_state(self.state_change, self.input_boolean) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new != old: self.log("{} changed from {} to {}".format(entity, old, new)) if new == "on": self.log( "{} arrived at {}".format(self.notify_name, self.zone_name) ) self.notifier.notify( self.notify_name, self.message.format(self.user_name) ) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: homeArrivalNotifier/homeArrivalNotifier.yaml ================================================ #Notification if user one arrives at home # homeArrivalNotifierUserOne: # module: homeArrivalNotifier # class: HomeArrivalNotifier # app_switch: input_boolean.home_arrival_notifier_user_one # input_boolean: input_boolean.user_one_home # notify_name: group_notifications # user_name: Kevin # zone_name: Home # message: "Willkommen zu Hause {}." # #message: "Welcome Home {}." # dependencies: # - Notifier # #Notification if user two arrives at home # homeArrivalNotifierUserTwo: # module: homeArrivalNotifier # class: HomeArrivalNotifier # app_switch: input_boolean.home_arrival_notifier_user_two # input_boolean: input_boolean.user_two_home # notify_name: group_notifications # user_name: Sina # zone_name: Home # message: "Willkommen zu Hause {}." # #message: "Welcome Home {}." # dependencies: # - Notifier ================================================ FILE: isHomeDeterminer/isHomeDeterminer.py ================================================ import appdaemon.plugins.hass.hassapi as hass import globals # # App to # # Args: # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # input_booleans: list of input boolean which determine if a user is home # ishome: input boolean which determins if someone is home # message: message to use in notification # Release Notes # # Version 1.3: # message now a list # # Version 1.2: # message now directly in own yaml instead of message module # # Version 1.1: # Added app_switch # # Version 1.0: # Initial Version class IsHomeDeterminer(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.ishome = self.args["ishome"] self.input_booleans = self.args["input_booleans"].split(",") self.message = self.args["message"] if self.get_state(self.app_switch) == "on": for input_boolean in self.input_booleans: self.log( "{} is {}".format(input_boolean, self.get_state(input_boolean)) ) self.listen_state_handle_list.append( self.listen_state(self.state_change, input_boolean) ) if ( self.get_state(input_boolean) == "on" and self.get_state(self.ishome) == "off" ): self.turn_on(self.ishome) self.log("Setting {} to on".format(self.ishome)) if ( self.get_state(input_boolean) == "off" and self.get_state(self.ishome) == "on" ): if self.are_others_away(input_boolean): self.turn_off(self.ishome) self.log("Setting {} to off".format(self.ishome)) notify_message = globals.random_arg(self.message) self.log("notify_messsage: {}".format(notify_message)) self.call_service( "notify/group_notifications", message=notify_message ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new != old: self.log("{} changed from {} to {}".format(entity, old, new)) if new == "on": self.turn_on(self.ishome) self.log("Setting {} to on".format(self.ishome)) if new == "off": if self.are_others_away(entity): self.turn_off(self.ishome) self.log("Setting {} to off".format(self.ishome)) notify_message = globals.random_arg(self.message) self.log("notify_messsage: {}".format(notify_message)) self.call_service( "notify/group_notifications", message=notify_message ) def are_others_away(self, entity): self.log("Entity: {}".format(entity)) for input_boolean in self.input_booleans: self.log("{} is {}".format(input_boolean, self.get_state(input_boolean))) if input_boolean == entity: pass elif self.get_state(input_boolean) == "on": self.log("{} is still at on".format(input_boolean)) return False self.log("Everybody not home") return True def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: isHomeDeterminer/isHomeDeterminer.yaml ================================================ # #Control the isHome state. Determines if someone is home or all persons are away # isHomeDeterminer: # module: isHomeDeterminer # class: IsHomeDeterminer # app_switch: input_boolean.is_home_determiner # ishome: input_boolean.is_home # input_booleans: input_boolean.user_one_home,input_boolean.user_two_home # message: # - "Es ist keiner mehr zu Hause." # - "Keiner mehr da? Panda Party!" # - "Ich passe auf die Wohnung auf, einen schönen Tag" # - "Tschüss, bis nachher" # #message: "Everyone left home. Setting isHome to off" # global_dependencies: # - globals # - secrets ================================================ FILE: isUserHomeDeterminer/isUserHomeDeterminer.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime from requests.exceptions import HTTPError # # App to toggle an input boolean when a person enters or leaves home. # This is determined based on a combination of a GPS device tracker and the door sensor. # # - If the door sensor opens and the device_tracker changed to "home" in the last self.delay minutes # this means someone got home # - If the door sensor opens and the device_tracker changes to "not_home" after that # this means someone left home # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # input_boolean: input_boolean which shows if someone is home e.g. input_boolean.isHome # device_tracker: device tracker or person of the user to track e.g. device_tracker.simon # door_sensor: Door sensor which indicated the front door opened e.g. binary_sensor.door_window_sensor_158d000126a57b # # Release Notes # # Version 1.5: # Wait to leave home until door is opened again # # Version 1.4.3: # check for listen_state_callback == None before triggering again # # Version 1.4.2: # cancel listen callback only when its not None # # Version 1.4.1: # fix for 503, fix for listen callback not being cancelled correctly # # Version 1.4: # message now directly in own yaml instead of message module # # Version 1.3: # Added app_switch # # Version 1.2: # Change checking after a delay to a event based system # # Version 1.1: # Set when initializing (also when HA restarts) # # Version 1.0: # Initial Version class IsUserHomeDeterminer(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.timer_handle_list = [] self.delay = 600 self.app_switch = self.args["app_switch"] self.input_boolean = self.args["input_boolean"] self.device_tracker = self.args["device_tracker"] self.door_sensor = self.args["door_sensor"] device_tracker_state = self.get_state(self.device_tracker, attribute="all") if self.get_state(self.app_switch) == "on": if device_tracker_state["state"] == "home": self.log("User is home") self.timer_handle_list.append( self.run_in( self.turn_on_callback, 0, turn_on_entity=self.input_boolean ) ) else: self.log("User is not home") self.timer_handle_list.append( self.run_in( self.turn_off_callback, 0, turn_off_entity=self.input_boolean ) ) self.listen_state_handle_list.append( self.listen_state(self.state_change, self.door_sensor) ) self.listen_state_handle = None def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new != old: self.log("{} changed from {} to {}".format(entity, old, new)) if new == "on" and old == "off": self.cancel_listen_state_callback(None) device_tracker_state = self.get_state( self.device_tracker, attribute="all" ) self.log("device_tracker_state: {}".format(device_tracker_state),) last_changed = device_tracker_state["last_changed"] self.log("last_changed: {}".format(last_changed)) # User got home: Device tracker changed to home before door sensor triggered if device_tracker_state["state"] == "home" and ( ( datetime.datetime.now(datetime.timezone.utc) - self.convert_utc(last_changed) ) < datetime.timedelta(seconds=self.delay) ): self.log("User got home") self.turn_on(self.input_boolean) # User got home: Device tracker is still not home. # Wait if it changes to home in the next self.delay seconds elif device_tracker_state["state"] != "home": self.log("Wait for device tracker to change to 'home'") self.listen_state_handle = self.listen_state( self.check_if_user_got_home, self.device_tracker ) self.listen_state_handle_list.append(self.listen_state_handle) self.timer_handle_list.append( self.run_in(self.cancel_listen_state_callback, self.delay) ) # User left home: Device tracker is still home. # Wait if it changes to not_home elif device_tracker_state["state"] == "home": self.log("Wait for device tracker to change to 'not_home'") self.listen_state_handle = self.listen_state( self.check_if_user_left_home, self.device_tracker ) self.listen_state_handle_list.append(self.listen_state_handle) def cancel_listen_state_callback(self, kwargs): if self.listen_state_handle is not None: self.log( "Timeout while waiting for user to get/leave home. Cancel listen_state" ) if self.listen_state_handle in self.listen_state_handle_list: self.listen_state_handle_list.remove(self.listen_state_handle) self.cancel_listen_state(self.listen_state_handle) self.listen_state_handle = None def check_if_user_left_home(self, entity, attribute, old, new, kwargs): if new != "home": self.log("User left home") if self.listen_state_handle in self.listen_state_handle_list: self.listen_state_handle_list.remove(self.listen_state_handle) if self.listen_state_handle != None: self.cancel_listen_state(self.listen_state_handle) self.listen_state_handle = None self.timer_handle_list.append( self.run_in( self.turn_off_callback, 1, turn_off_entity=self.input_boolean ) ) def check_if_user_got_home(self, entity, attribute, old, new, kwargs): if new == "home": self.log("User got home") if self.listen_state_handle in self.listen_state_handle_list: self.listen_state_handle_list.remove(self.listen_state_handle) if self.listen_state_handle is not None: self.cancel_listen_state(self.listen_state_handle) self.listen_state_handle = None self.timer_handle_list.append( self.run_in( self.turn_on_callback, 1, turn_on_entity=self.input_boolean ) ) def turn_on_callback(self, kwargs): """This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting""" try: self.turn_on(kwargs["turn_on_entity"]) except HTTPError as exception: self.log( "Error trying to turn on entity. Will try again in 1s. Error: {}".format( exception ), level="WARNING", ) self.timer_handle_list.append( self.run_in( self.turn_on_callback, 1, turn_on_entity=kwargs["turn_on_entity"] ) ) def turn_off_callback(self, kwargs): """This is needed because the turn_off command can result in a HTTP 503 when homeassistant is restarting""" try: self.turn_off(kwargs["turn_off_entity"]) except HTTPError as exception: self.log( "Error trying to turn off entity. Will try again in 1s. Error: {}".format( exception ), level="WARNING", ) self.timer_handle_list.append( self.run_in( self.turn_off_callback, 1, turn_off_entity=kwargs["turn_off_entity"] ) ) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: isUserHomeDeterminer/isUserHomeDeterminer.yaml ================================================ # #Determine if user one gets/leaves home # isUserHomeDeterminerUserOne: # module: isUserHomeDeterminer # class: IsUserHomeDeterminer # app_switch: input_boolean.is_user_home_determiner_user_one # input_boolean: input_boolean.user_one_home # device_tracker: person.kevin # door_sensor: binary_sensor.contact_door # #Determine if user two gets/leaves home # isUserHomeDeterminerUserTwo: # module: isUserHomeDeterminer # class: IsUserHomeDeterminer # app_switch: input_boolean.is_user_home_determiner_user_two # input_boolean: input_boolean.user_two_home # device_tracker: person.sina # door_sensor: binary_sensor.contact_door ================================================ FILE: leavingZoneNotifier/leavingZoneNotifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App to notify if user_one is leaving a zone. # User had to be in that zone 3 minutes before # in order for the notification to be triggered # # Args: # app_switch: on/off switch for this app. # example: input_boolean.turn_fan_on_when_hot # device: Device to track # user_name: Name of the user used in the notification message # delay: seconds to wait before notifying. Maybe user returns to zone. # This should be too small to avoid false positives from your tracker. # I am using GPS Logger on Android and sometimes my device switches # from work to home and 2 minutes later back. example: 120 # lingering_time: time a user has to be in a zone to trigger this app. # example: 3600 # zone: zone name from which the user is leaving # notify_name: Who to notify. example: group_notifications # message: localized message to use in notification # travel_time_sensor (optional): Sensor showing the travel time home. # example: sensor.travel_time_home_user_one # travel_time_sensor_message (optional): Additional notify message. # # Release Notes # # Version 1.11: # Catch new state might be None # # Version 1.10: # Catch old state might be None during startup # # Version 1.9: # PEP8 style and log message when updating travel_time_sensor # # Version 1.8: # Add travel time in notification message # # Version 1.7.1: # Fix delay in notify message. Only input the minutes not the tuple # # Version 1.7: # use Notify App # # Version 1.6: # notify message includes delay # # Version 1.5: # message now directly in own yaml instead of message module # # Version 1.4: # additional note for delay and better handling of zone_entered for # false positives # # Version 1.3: # delay and lingering_time now as args # # Version 1.2: # Added app_switch # # Version 1.1: # Rework without proximity # # Version 1.0: # Initial Version class LeavingZoneNotifier(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.timer_handle_list = [] self.app_switch = self.args["app_switch"] self.user_name = self.args["user_name"] self.zone = self.args["zone"] self.notify_name = self.args["notify_name"] self.device = self.args["device"] # 'lingering_time' the time a user has to stay in a zone # for this app to trigger self.lingering_time = self.args["lingering_time"] self.delay = self.args["delay"] self.message = self.args["message"] self.travel_time_sensor = self.args.get("travel_time_sensor") self.travel_time_sensor_message = self.args.get("travel_time_sensor_message") self.user_entered_zone = None self.false_positive = False self.notifier = self.get_app("Notifier") self.listen_state_handle_list.append( self.listen_state(self.zone_state_change, self.device, attribute="all") ) def zone_state_change(self, entity, attributes, old, new, kwargs): """Check if user entered or left a zone.""" if self.get_state(self.app_switch) == "on": if new is not None: last_changed = self.convert_utc(new["last_changed"]) if old is not None: old_state = old["state"] self.log( "Zone of {} changed from {} to {}.".format( self.friendly_name(entity), old_state, new["state"] ), ) if ( new["state"] == self.zone and old_state != self.zone and self.false_positive is False ): self.log("Setting user_entered_zone to {}".format(last_changed)) self.user_entered_zone = last_changed if old_state == self.zone and new["state"] != self.zone: if self.user_entered_zone is None or ( last_changed - self.user_entered_zone >= datetime.timedelta(seconds=self.lingering_time) ): self.log( "Zone of {} changed from {} to {}. Wait {} seconds until notification.".format( self.friendly_name(entity), old_state, new["state"], self.delay, ) ) self.timer_handle_list.append( self.run_in(self.notify_user, self.delay, old_zone=old) ) self.false_positive = True self.log("Setting false_positive to {}".format(self.false_positive)) def notify_user(self, kwargs): # Check if user did not come back to the zone in the meantime if self.get_state(self.device) != kwargs["old_zone"]: if self.travel_time_sensor is not None: self.log( "Updating travel_time_sensor: {}".format(self.travel_time_sensor) ) self.call_service( "homeassistant/update_entity", entity_id=self.travel_time_sensor ) self.timer_handle_list.append(self.run_in(self.notify_user_callback, 2)) else: self.log("self.travel_time_sensor is not None") self.log("Notify user") self.notifier.notify( self.notify_name, self.message.format( self.user_name, self.zone, divmod(self.delay, 60)[0] ), ) self.false_positive = False self.log("Setting false_positive to {}".format(self.false_positive)) def notify_user_callback(self, kwargs): self.log("Notify user") self.notifier.notify( self.notify_name, self.message.format(self.user_name, self.zone, divmod(self.delay, 60)[0]) + self.travel_time_sensor_message.format( self.get_state(self.travel_time_sensor) ), ) self.false_positive = False self.log("Setting false_positive to {}".format(self.false_positive)) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: leavingZoneNotifier/leavingZoneNotifier.yaml ================================================ # leavingWorkNotifierUserOne: # module: leavingZoneNotifier # class: LeavingZoneNotifier # app_switch: input_boolean.leaving_work_notifier_user_one # device: person.kevin # user_name: Kevin # lingering_time: 3600 # delay: 120 # zone: Arbeit # notify_name: group_notifications # message: "{} hat {} vor {} Minuten verlassen." # #message: "{} left {} {} minutes ago" # travel_time_sensor: sensor.travel_time_home_user_one # travel_time_sensor_message: "Es dauert circa {} Minuten bis nach Hause." # #travel_time_sensor_message: "The travel time is {}." # dependencies: # - Notifier # leavingWorkNotifierUserTwo: # module: leavingZoneNotifier # class: LeavingZoneNotifier # app_switch: input_boolean.leaving_work_notifier_user_two # device: person.sina # user_name: Sina # lingering_time: 3600 # delay: 120 # zone: !secret friendly_name_work_user_two # notify_name: group_notifications # message: "{} hat {} vor {} Minuten verlassen." # #message: "{} left {} {} minutes ago" # travel_time_sensor: sensor.travel_time_home_user_two # travel_time_sensor_message: "Es dauert circa {} Minuten bis nach Hause." # #travel_time_sensor_message: "The travel time is {}." # dependencies: # - Notifier # leavingElmoNotifierUserTwo: # module: leavingZoneNotifier # class: LeavingZoneNotifier # app_switch: input_boolean.leaving_elmo_notifier_user_two # device: person.sina # user_name: Sina # lingering_time: 3600 # delay: 120 # zone: Elmo # notify_name: group_notifications # message: "{} hat {} vor {} Minuten verlassen." # #message: "{} left {} {} minutes ago" # travel_time_sensor: sensor.travel_time_home_user_two # travel_time_sensor_message: "Es dauert circa {} Minuten bis nach Hause." # #travel_time_sensor_message: "The travel time is {}." # dependencies: # - Notifier ================================================ FILE: motionTrigger/motionTrigger.py ================================================ import appdaemon.plugins.hass.hassapi as hass # pylint: disable=import-error import datetime # # 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 # # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # sensor: binary sensor to use as trigger # entity_on : entity to turn on when detecting motion, can be a light, script, scene or anything else that can be turned on # 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 # sensor_type: Possible values: xiaomi, zigbee2mqtt, deconz. Default: xiaomi # after (optional): Only trigger after a certain time. example: 22:00 # after_sundown (optional): true # delay (optional): amount of time after turning on to turn off again. If not specified defaults to 70 seconds. example: 10 # if an input_number is defined it will automatically take the delay from there. example: input_number.motionTrigger_delay # 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 # 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 # 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 # 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 # # Release Notes # # Version 1.10.1: # always log No entity_off defined # # Version 1.10: # wait with turn_off till sensor is really off # # Version 1.9: # introduced turn_on_constraints and turn_off_constraints # # Version 1.8: # support for input_number as delay and delay starts on last motion not when state changes to off # # Version 1.7: # support for zigbee2mqtt and xiaomi motion sensors # # Version 1.6: # message now directly in own yaml instead of message module # # Version 1.5: # Added app_switch # # Version 1.4: # Added options "after, turn_on_constraint_entities_off and turn_on_constraint_entities_on" # # Version 1.3: # Only turn off entity if it was turned on by this app # # Version 1.2: # Add after_sundown argument # # Version 1.1: # Add ability for other apps to cancel the timer # # Version 1.0: # Initial Version SENSOR_TYPE_XIAOMI = "xiaomi" SENSOR_TYPE_ZIGBEE2MQTT = "zigbee2mqtt" SENSOR_TYPE_DECONZ = "deconz" class MotionTrigger(hass.Hass): def initialize(self): self.timer_handle = None self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.timer_handle_list = [] self.turned_on_by_me = False # Giggedi self.app_switch = self.args["app_switch"] self.sensor = self.args["sensor"] self.entity_on = self.args["entity_on"] self.entity_off = self.args.get("entity_off") try: self.sensor_type = self.args["sensor_type"] except KeyError: self.sensor_type = SENSOR_TYPE_ZIGBEE2MQTT self.after = self.args.get("after") self.after_sundown = self.args.get("after_sundown") try: self.delay = self.args["delay"] try: if self.delay.startswith("input_number"): self.delay_entity = self.delay self.delay = int(self.get_state(self.delay_entity).split(".")[0]) self.listen_state_handle_list.append( self.listen_state(self.delay_changed, self.delay_entity) ) except AttributeError: # does not have attribute 'startswith' -> is not of type string pass self.log("Delay changed to : {}".format(self.delay)) except KeyError: self.delay = 90 try: self.turn_on_constraint_entities_off = self.args[ "turn_on_constraint_entities_off" ].split(",") except KeyError: self.turn_on_constraint_entities_off = [] try: self.turn_on_constraint_entities_on = self.args[ "turn_on_constraint_entities_on" ].split(",") except KeyError: self.turn_on_constraint_entities_on = [] try: self.turn_off_constraint_entities_off = self.args[ "turn_off_constraint_entities_off" ].split(",") except KeyError: self.turn_off_constraint_entities_off = [] try: self.turn_off_constraint_entities_on = self.args[ "turn_off_constraint_entities_on" ].split(",") except KeyError: self.turn_off_constraint_entities_on = [] # Subscribe to sensors if self.sensor_type == SENSOR_TYPE_XIAOMI: self.listen_event_handle_list.append( self.listen_event(self.motion_event_detected, "xiaomi_aqara.motion") ) elif self.sensor_type in [SENSOR_TYPE_ZIGBEE2MQTT, SENSOR_TYPE_DECONZ]: self.listen_state_handle_list.append( self.listen_state(self.state_changed, self.sensor) ) else: self.log(f"Unknown sensor_type: {self.sensor_type}", level="ERROR") def delay_changed(self, entity, attribute, old, new, kwargs): self.delay = int(self.get_state(self.delay_entity).split(".")[0]) self.log(f"Delay changed to : {self.delay}") def motion_event_detected(self, event_name, data, kwargs): if self.get_state(self.app_switch) == "on": if data["entity_id"] == self.sensor: self.turn_on_callback(None) def state_changed(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new == "on": self.turn_on_callback(None) def turn_on_callback(self, kwargs): self.log(f"Motion detected on sensor: {self.friendly_name(self.sensor)}",) turn_on = True if self.after_sundown is not None: if self.after_sundown and not self.sun_down(): turn_on = False self.log("Is not after sundown") if self.after is not None: after_time = datetime.datetime.combine( datetime.date.today(), datetime.time( int(self.after.split(":")[0]), int(self.after.split(":")[1]) ), ) if datetime.datetime.now() > after_time: turn_on = False self.log(f"Now is before {self.after}") for entity in self.turn_on_constraint_entities_off: entity_state = self.get_state(entity) if entity_state != "off": turn_on = False self.log(f"{entity} is still {entity_state}") break for entity in self.turn_on_constraint_entities_on: entity_state = self.get_state(entity) if entity_state != "on": turn_on = False self.log(f"{entity} is still {entity_state}") break if turn_on and self.get_state(self.entity_on) == "off": self.log(f"Motion detected: turning {self.entity_on} on") self.turn_on(self.entity_on) self.turned_on_by_me = True if self.turned_on_by_me and turn_on: self.reset_timer() def turn_off_callback(self, kwargs): if self.get_state(self.sensor) == "on": self.log(f"{self.sensor} is still on") self.reset_timer() else: turn_off = True if self.entity_off is not None: for entity in self.turn_off_constraint_entities_off: entity_state = self.get_state(entity) if entity_state != "off": turn_off = False self.log(f"{entity} is still {entity_state}") break for entity in self.turn_off_constraint_entities_on: entity_state = self.get_state(entity) if entity_state != "on": turn_off = False self.log(f"{entity} is still {entity_state}") break if turn_off: self.log(f"Turning {self.entity_off} off") self.turn_off(self.entity_off) self.turned_on_by_me = False else: self.log("No entity_off defined.") def reset_timer(self): if self.timer_handle is not None: self.log("Resetting timer") self.timer_handle_list.remove(self.timer_handle) self.cancel_timer(self.timer_handle) self.log(f"Will turn off in {self.delay}s") self.timer_handle = self.run_in(self.turn_off_callback, self.delay) self.timer_handle_list.append(self.timer_handle) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: motionTrigger/motionTrigger.yaml ================================================ # bedroomMotionTrigger: # module: motionTrigger # class: MotionTrigger # app_switch: input_boolean.bedroom_motion_trigger # sensor: binary_sensor.presence_bedroom # entity_on: light.bedroom_yeelight # sensor_type: deconz # after_sundown: True # turn_on_constraint_entities_off: input_boolean.sleepmode # studyroomMotionTrigger: # module: motionTrigger # class: MotionTrigger # app_switch: input_boolean.studyroom_motion_trigger # sensor: binary_sensor.presence_studyroom # entity_on: light.philips_miio_light_bulb # sensor_type: deconz # after_sundown: True ================================================ FILE: newWifiDeviceNotify/newWifiDeviceNotify.py ================================================ import appdaemon.plugins.hass.hassapi as hass from fritz_switch_profiles import FritzProfileSwitch # # App which sends a notification if a new device is found # # Args: # # notify_name: Who to notify. example: group_notifications # user_id: Who to notify. example: -217831 # message: Message to use in notification. e.g. "Unknown device connected. Hostname: {}. MAC: {}" # fritzbox_url (optional): The url of your fritzbox. example: http://fritz.box # fritzbox_user (optional): The user to login to your fritzbox. example: '' # fritzbox_password (optional): The password to login to your fritzbox. example: 'mysecurepassword' # fritzbox_profile_name (optional): Name of the profile with Internet Access. example: 'Unbeschränkt' # fritzbox_message_allow_access (optional): Message to use in telegram message. example: "Should I let the device access the Internet?" # fritzbox_message_access_allowed (optional): Message to use in telegram message. example: "I have let the device access the internet. How kind of me!" # fritzbox_message_access_blocked (optional): Message to use in telegram message. example: "I have saved the device from the dangers of the Internet" # # Release Notes # # Version 1.5.2: # Handle hostname is None # # Version 1.5.1: # Wait till entity is fully created # # Version 1.5: # Add support for Unifi > 0.98 and other integrations that are not using known_devices / device_tracker_new_device. # Fritzbox support is now optional # # Version 1.4: # Don't use Alexa for Notifications # # Version 1.3: # Fix for hostnames containing "-" # # Version 1.2: # Make us of fritz_switch_profiles to control Internet access # # Version 1.1: # use Notify App # # Version 1.0: # Initial Version IDENTIFIER_DELIMITER = "-" ALLOW_CALLBACK_IDENTIFIER = "/NEWDEVICENOTIFYALLOW" BLOCK_CALLBACK_IDENTIFIER = "/NEWDEVICEBNOTIFYLOCK" class DeviceNotify(hass.Hass): def initialize(self): self.listen_event_handle_list = [] self.notify_name = self.args["notify_name"] self.user_id = self.args["user_id"] self.message = self.args["message"] self.fritzbox_url = self.args.get("fritzbox_url") self.fritzbox_user = self.args.get("fritzbox_user") self.fritzbox_password = self.args.get("fritzbox_password") self.fritzbox_profile_name = self.args.get("fritzbox_profile_name") self.fritzbox_message_allow_access = self.args.get( "fritzbox_message_allow_access" ) self.fritzbox_message_access_allowed = self.args.get( "fritzbox_message_access_allowed" ) self.fritzbox_message_access_blocked = self.args.get( "fritzbox_message_access_blocked" ) self.notifier = self.get_app("Notifier") self.listen_event_handle_list.append( self.listen_event(self.newDeviceCallback, "device_tracker_new_device") ) self.listen_event_handle_list.append( self.listen_event( self.entityRegistryUpdatedCallback, "entity_registry_updated" ) ) self.listen_event_handle_list.append( self.listen_event(self.receiveTelegramCallback, "telegram_callback") ) def entityRegistryUpdatedCallback(self, event_name, data, kwargs): """Callback method for entity_registry_updated event""" self.log("event_name: {}".format(event_name)) self.log("data: {}".format(data)) if data["action"] == "create": # Wait recursively until the entity was fully created self.run_in( self.handleNewRegistryEntity, 1, new_entity_id=data["entity_id"] ) def handleNewRegistryEntity(self, kwargs): """Wait till Entity is available and notify if it was created by a router""" full_state = self.get_state(kwargs["new_entity_id"], attribute="all") if full_state is None: self.run_in( self.handleNewRegistryEntity, 1, new_entity_id=kwargs["new_entity_id"] ) else: new_entity_attributes = full_state["attributes"] if new_entity_attributes.get("source_type") == "router": hostname = new_entity_attributes.get("hostname") mac = new_entity_attributes.get("mac") self.notifyNewDeviceAdded(hostname, mac) if self.fritzbox_url is not None and hostname is not None: self.askForProfileChange(hostname) def newDeviceCallback(self, event_name, data, kwargs): """Callback method for device_tracker_new_device event""" self.log("event_name: {}".format(event_name)) self.log("data: {}".format(data)) self.notifyNewDeviceAdded(data["host_name"], data["mac"]) if self.fritzbox_url is not None: self.askForProfileChange(data["host_name"]) def notifyNewDeviceAdded(self, host_name, mac): """Send a notification message when a new device was added""" message = self.message.format(host_name, mac) self.notifier.notify(self.notify_name, message, useAlexa=False) def askForProfileChange(self, host_name): """Asks the user if he wants to allow the new device to have internet access""" self.log("Asking for profile change") if host_name is None: host_name = "" keyboard = [ [ ( "Zulassen", ALLOW_CALLBACK_IDENTIFIER + IDENTIFIER_DELIMITER + host_name, ) ], [("Sperren", BLOCK_CALLBACK_IDENTIFIER + IDENTIFIER_DELIMITER + host_name)], ] self.log("keyboard is: {}".format(keyboard), level="DEBUG") self.call_service( "telegram_bot/send_message", target=self.user_id, message=self.message_allow_access, inline_keyboard=keyboard, ) def receiveTelegramCallback(self, event_name, data, kwargs): """Event listener for Telegram callback queries.""" self.log("callback data: {}".format(data)) data_callback = data["data"] callback_id = data["id"] chat_id = data["chat_id"] message_id = data["message"]["message_id"] text = data["message"]["text"] from_first = data["from_first"] if data_callback.startswith(ALLOW_CALLBACK_IDENTIFIER): host_name = data_callback.split(IDENTIFIER_DELIMITER, maxsplit=1)[1] self.log( "Received Telegram Callback to allow internet access for: {}".format( host_name ) ) self.call_service( "telegram_bot/answer_callback_query", message="Dankeschön!", callback_query_id=callback_id, ) self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=message_id, message=self.message_access_allowed, inline_keyboard=[], ) self.allowDevice(host_name) elif data_callback.startswith(BLOCK_CALLBACK_IDENTIFIER): host_name = data_callback.split(IDENTIFIER_DELIMITER, maxsplit=1)[1] self.log( "Received Telegram Callback to block internet access for: {}".format( host_name ) ) self.call_service( "telegram_bot/answer_callback_query", message="Dankeschön!", callback_query_id=callback_id, ) self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=message_id, message=self.message_access_blocked, inline_keyboard=[], ) def allowDevice(self, host_name): """Login to fritzbox and assign the 'Internet Access' profile to the device with the given host name""" fps = FritzProfileSwitch( self.fritzbox_url, self.fritzbox_user, self.fritzbox_password ) devices = fps.get_devices() profiles = fps.get_profiles() # Get the device_id for the host name device_id = None for device in devices: if device["name"] == host_name: device_id = device["id1"] if device_id: # Get the profile id for the Internet access profile profile_id = None for profile in profiles: if profile["name"] == self.fritzbox_profile_name: profile_id = profile["id"] if profile_id: # construct the array to set the profile for the device profile_for_device = [(device_id, profile_id)] # set device profile fps.set_profiles(profile_for_device) else: message = "Could not find profile with the name: {}".format( self.fritzbox_profile_name ) self.log(message) self.notifier.notify(self.notify_name, message) else: message = "Could not find device with the hostname: {}".format(host_name) self.log(message) self.notifier.notify(self.notify_name, message) def terminate(self): for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) ================================================ FILE: newWifiDeviceNotify/newWifiDeviceNotify.yaml ================================================ newWifiDeviceNotify: module: newWifiDeviceNotify class: DeviceNotify notify_name: group_notifications user_id: !secret telegram_user_id message: "Unbekanntes Gerät entdeckt. Hostname: {}. MAC: {}." #message: "Unknown device connected. Hostname: {}. MAC: {}" #fritzbox_url: fritzbox_url #fritzbox_user: '' #fritzbox_password: fritzbox_password #fritzbox_profile_name: 'Unbeschränkt' #fritzbox_message_allow_access: "Soll ich das Gerät ins Internet lassen?" #fritzbox_message_allow_access: "Should I let the device access the Internet?" #fritzbox_message_access_allowed: "Großzügig wie ich bin, habe ich das Gerät ins Internet gelassen" #fritzbox_message_access_allowed: "I have let the device access the internet. How kind of me!" #fritzbox_message_access_blocked: "Ich habe das Gerät vor den Schrecken des Internets bewahrt" #fritzbox_message_access_blocked: "I have saved the device from the dangers of the Internet" dependencies: - Notifier ================================================ FILE: newWifiDeviceNotify/requirements.txt ================================================ fritz_switch_profiles >= 1.0.0 ================================================ FILE: nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App which notifies the user to start to the next appointment # # # Args: # sensor: sensor to watch. example: sensor.calc_leave_time # notify_input_boolean: input_boolean determining whether to notify. example: input_boolean.announce_time_to_leave # notify_name: Who to notify. example: group_notifications # destination_name_sensor: Sensor which holds the Destination name to use in notification. example: sensor.cal_next_appointment_location # travel_time_sensor: sensor which holds the travel time. example: sensor.travel_time_next_appointment_location # message: message to use in notification # # Release Notes # # Version 1.5: # Catch None when Home Assistant is still starting # # Version 1.4.2: # Fix notification for location_name "None" # # Version 1.4.1: # Fix google maps url message # # Version 1.4: # Don't include Google Maps Link in Notification for Alexa # # Version 1.3: # Also notify when Notification time is in the past # # Version 1.2: # use Notify App # # Version 1.1: # Using globals, message now directly in own yaml instead of message module # # Version 1.0: # Initial Version class NextAppointmentLeaveNotifier(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.sensor = self.args["sensor"] self.notify_input_boolean = self.args["notify_input_boolean"] self.notify_name = self.args["notify_name"] self.destination_name_sensor = self.args["destination_name_sensor"] self.travel_time_sensor = self.args["travel_time_sensor"] self.message = self.args["message"] self.message_google_link = self.args["message_google_link"] self.timer_handle = None self.google_source_url = "http://maps.google.com/maps?q=" self.notifier = self.get_app("Notifier") # Used to check of user got already notified for this event self.location_of_last_notified_event = "" self.set_timer_handle() self.listen_state_handle_list.append( self.listen_state(self.state_change, self.sensor) ) def state_change(self, entity, attributes, old, new, kwargs): try: self.cancel_timer(self.timer_handle) self.timer_handle = None except AttributeError: # Timer was not set pass self.set_timer_handle() def set_timer_handle(self): destination_name = self.get_state(self.destination_name_sensor) self.log(f"destination_name_sensor: {destination_name}") if self.get_state(self.sensor) != None: if destination_name != "unknown" and destination_name != "None": notification_time = datetime.datetime.strptime( self.get_state(self.sensor), "%Y-%m-%d %H:%M" ) if self.get_state(self.travel_time_sensor) != "unknown": try: self.timer_handle = self.run_at( self.notify_user, notification_time ) self.log(f"Will notify at {notification_time}") except ValueError: self.log("Notification time is in the past") self.timer_handle = self.run_at( self.notify_user, datetime.datetime.now() ) def notify_user(self, *kwargs): if self.get_state(self.notify_input_boolean) == "on": location_name = self.get_state(self.destination_name_sensor) if location_name != "None": if self.location_of_last_notified_event == location_name: self.log(f"User already got notified for {location_name}") else: google_maps_url = self.google_source_url + location_name.replace( " ", "+" ) self.log("Notify user") self.notifier.notify( self.notify_name, self.message.format( location_name, self.get_state(self.travel_time_sensor) ), ) self.notifier.notify( self.notify_name, self.message_google_link.format(google_maps_url), useAlexa=False, ) self.location_of_last_notified_event = location_name else: self.log(f"location_name: {location_name}") else: self.log("Notification is turned off") def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) if self.timer_handle is not None: self.cancel_timer(self.timer_handle) ================================================ FILE: nextAppointmentLeaveNotifier/nextAppointmentLeaveNotifier.yaml ================================================ # nextAppointmentLeaveNotifier: # module: nextAppointmentLeaveNotifier # class: NextAppointmentLeaveNotifier # sensor: sensor.calc_leave_time # notify_input_boolean: input_boolean.announce_time_to_leave # notify_name: Kevin # input_number: input_number.leave_time_offset # destination_name_sensor: sensor.cal_next_appointment_location # travel_time_sensor: sensor.travel_time_next_appointment_location # message: "Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten." # #message: "It's time to leave to {}. It will take {} minutes." # message_google_link: " Hier ist ein Google Maps Link: {}" # #message_google_link: " Here is a Google Maps Link: {}" # dependencies: # - Notifier # nextAppointmentLeaveNotifierUserTwo: # module: nextAppointmentLeaveNotifier # class: NextAppointmentLeaveNotifier # sensor: sensor.calc_leave_time_user_two # notify_input_boolean: input_boolean.announce_time_to_leave_user_two # notify_name: Sina # input_number: input_number.leave_time_offset_user_two # destination_name_sensor: sensor.cal_next_appointment_location_user_two # travel_time_sensor: sensor.travel_time_next_appointment_location_user_two # message: "Es ist Zeit loszufahren nach {}. Du brauchst {} Minuten." # #message: "It's time to leave to {}. It will take {} minutes." # message_google_link: " Hier ist ein Google Maps Link: {}" # #message_google_link: " Here is a Google Maps Link: {}" # dependencies: # - Notifier ================================================ FILE: notifier/notifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # Centralizes messaging. Among other things, it will determine whether a user is at home and if yes in which room. # Then Alexa in that room will be used additionally to Telegram # # Args: # app_switch_alexa: mutes alexa. example: # alexa_tts: name of the notification service. example: alexa_media # alexa_media_player: media player entity of alexa to use. example: media_player.kevins_echo_dot_oben # user_location_sensors: sensors showing the location of users # alexa_to_location_mapping: mapping of which alexa device is used for which room # # # # Release Notes # # Version 1.5: # Allow multiple alexa_media_player # # Version 1.4: # Use type announce # # Version 1.3: # Use Version 1.2.1 of alexa_media_player # # Version 1.2.1: # Fix: Enqueue alexa messages # # Version 1.2: # Enqueue alexa messages # # Version 1.1: # Remove media_player constraints. If connected via bluetooth alexa can always be heard # # Version 1.0: # Initial Version __GROUP_NOTIFICATIONS__ = "group_notifications" __NOTIFY__ = "notify/" __WAIT_TIME__ = 5 # seconds class Notifier(hass.Hass): def initialize(self): self.timer_handle_list = [] self.alexa_tts = self.args["alexa_tts"] self.alexa_media_player = self.args["alexa_media_player"].split(",") self.app_switch_alexa = self.args["app_switch_alexa"] self.last_alexa_notification_time = None def notify(self, notify_name, message, useAlexa=True, useTelegram=True): if useTelegram: self.log("Notifying via Telegram") self.call_service(__NOTIFY__ + notify_name, message=message) if useAlexa and self.get_state(self.app_switch_alexa) == "on": self.log("Notifying via Alexa") # check last message if self.last_alexa_notification_time is not None and ( datetime.datetime.now() - self.last_alexa_notification_time < datetime.timedelta(seconds=__WAIT_TIME__) ): self.timer_handle_list.append( self.run_in(self.notify_callback, __WAIT_TIME__, message=message) ) else: self.run_in(self.notify_callback, 0, message=message) def notify_callback(self, kwargs): self.last_alexa_notification_time = datetime.datetime.now() self.call_service( __NOTIFY__ + self.alexa_tts, data={"type": "announce", "method": "speak"}, target=self.alexa_media_player, message=kwargs["message"], ) def getAlexaDeviceForUserLocation(self, notify_name): if notify_name == __GROUP_NOTIFICATIONS__: return self.args["alexa_to_location_mapping"]["Wohnzimmer"] elif notify_name.lower() in self.args["user_location_sensors"]: location = self.get_state( self.args["user_location_sensors"][notify_name.lower()] ) if location in self.args["alexa_to_location_mapping"]: return self.args["alexa_to_location_mapping"][location] else: return None else: self.log("Unknown notify_name: {}".format(notify_name)) return None def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: notifier/notifier.yaml ================================================ Notifier: module: notifier class: Notifier app_switch_alexa: input_boolean.notifier_alexa alexa_tts: alexa_media 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 user_location_sensors: kevin: sensor.location_user_one sina: sensor.location_user_two alexa_to_location_mapping: Wohnzimmer: media_player.kevins_echo_dot_oben Küche: media_player.kevins_echo_dot_oben Balkon Oben: media_player.kevins_echo_dot_oben Arbeitszimmer: media_player.kevin_s_echo_dot_unten Schlafzimmer: media_player.kevin_s_echo_dot_unten ================================================ FILE: notifyOfActionWhenAway/notifyOfActionWhenAway.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App to send notification when a sensor changes state # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # sensor: sensor to monitor. example: sensor.upstairs_smoke # isHome: input_boolean which shows if someone is home. example: input_boolean.isHome # isHome_delay: delay to wait for user to come home before notifying. example: 10 # # Release Notes # # Version 1.3.1: # Use consistent message variable # # Version 1.3: # use Notify App # # Version 1.2: # message now directly in own yaml instead of message module # # Version 1.1: # Added isHome_delay # # Version 1.0: # Initial Version class NotifyOfActionWhenAway(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.timer_handle_list = [] self.app_switch = self.args["app_switch"] self.notify_name = self.args["notify_name"] self.isHome_delay = self.args["isHome_delay"] self.isHome = self.args["isHome"] self.message = self.args["message"] self.notifier = self.get_app("Notifier") for sensor in self.args["sensor"].split(","): self.listen_state_handle_list.append( self.listen_state(self.state_change, sensor) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new != old: if self.get_state(self.isHome) == "off": if ( entity.startswith("binary_sensor.motion_sensor") and new == "off" ): pass else: self.log( "Waiting {} seconds for someone to come home".format( self.isHome_delay ) ) self.timer_handle_list.append( self.run_in( self.notify_if_no_one_home, self.isHome_delay, sensor=entity, new=new, ) ) def notify_if_no_one_home(self, kwargs): if self.get_state(self.isHome) == "off": self.log( "{} changed to {}".format( self.friendly_name(kwargs["sensor"]), kwargs["new"] ) ) self.notifier.notify( self.notify_name, self.message.format( self.friendly_name(kwargs["sensor"]), kwargs["new"] ), useAlexa=False, ) def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: notifyOfActionWhenAway/notifyOfActionWhenAway.yaml ================================================ notifyOfActionWhenAway: module: notifyOfActionWhenAway class: NotifyOfActionWhenAway app_switch: input_boolean.notify_of_action_when_away sensor: "binary_sensor.contact_bathroom_window_tilted,binary_sensor.contact_bedroom_door,\ binary_sensor.contact_bedroom_door_tilted,binary_sensor.contact_door,binary_sensor.contact_guest_window,\ binary_sensor.contact_kitchen_window,binary_sensor.contact_studyroom_door,\ binary_sensor.contact_studyroom_door_tilted,binary_sensor.contact_terrace_door,\ binary_sensor.contact_terrace_door_tilted,binary_sensor.contact_upper_bathroom_window,\ binary_sensor.presence_stairs,binary_sensor.presence_bathroom,binary_sensor.presence_lobby,\ binary_sensor.presence_bedroom,binary_sensor.presence_kitchen,binary_sensor.presence_upper_stairs,\ binary_sensor.contact_badfenster,binary_sensor.contact_upper_bathroom_window_tilted" isHome: input_boolean.is_home notify_name: group_notifications isHome_delay: 20 message: "Alarm: {} ist gewechselt auf {}" #message: "Alarm: {} changed to {}" dependencies: - Notifier ================================================ FILE: plantWateringNotifier/plantWateringNotifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App which reminds you daily to water your plants if it won't rain # # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # rain_precip_sensor: sensor which shows rain probability. example: sensor.dark_sky_precip_probability # rain_precip_intensity_sensor: sensor which shows rain probability. example: sensor.dark_sky_precip_intensity # precip_type_sensor: sensor which shows precip type. example: sensor.dark_sky_precip # notify_name: Who to notify. example: group_notifications # user_id: The user_id of the telegram user to ask whether he knows an unknown face. example: 812391 # reminder_acknowledged_entity: Input Boolean to store the information whether the user acknowledged the notification. # This prevents new notifications upon HA/Appdaemon restart. # example: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged # message: localized message to use in notification # # Release Notes # # Version 1.5.1: # Use consistent message variable # # Version 1.5: # use Notify App # # Version 1.4: # message now directly in own yaml instead of message module # # Version 1.3: # Added app_switch # # Version 1.2: # Update original message with information when the reminder was acknowledged # # Version 1.1: # Store reminder acknowledged in an input_boolean to prevent notifications after HA/Appdaemon restarts # # Version 1.0: # Initial Version class PlantWateringNotifier(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.rain_precip_sensor = self.args["rain_precip_sensor"] self.rain_precip_intensity_sensor = self.args["rain_precip_intensity_sensor"] self.precip_type_sensor = self.args["precip_type_sensor"] self.notify_name = self.args["notify_name"] self.user_id = self.args["user_id"] self.reminder_acknowledged_entity = self.args["reminder_acknowledged_entity"] self.message = self.args["message"] self.message_not_needed = self.args["message_not_needed"] self.message_evening = self.args["message_evening"] self.intensity_minimum = 2 # mm/h self.propability_minimum = 90 # % self.keyboard_callback = "/plants_watered" self.notifier = self.get_app("Notifier") self.reminder_acknowledged = self.get_state(self.reminder_acknowledged_entity) self.listen_event_handle_list.append( self.listen_event(self.receive_telegram_callback, "telegram_callback") ) # Remind daily at 08:00 self.timer_handle_list.append( self.run_daily(self.run_morning_callback, datetime.time(8, 0, 0)) ) # Remind daily at 18:00 self.timer_handle_list.append( self.run_daily(self.run_evening_callback, datetime.time(18, 0, 0)) ) def run_morning_callback(self, kwargs): """Check if it will rain and if not remind the user to water the plants""" if self.get_state(self.app_switch) == "on": precip_propability = self.get_state(self.rain_precip_sensor) self.log("Rain Propability: {}".format(float(precip_propability))) precip_intensity = self.get_state(self.rain_precip_intensity_sensor) self.log("Rain Intensity: {}".format(float(precip_intensity))) precip_type = self.get_state(self.precip_type_sensor) self.log("Precip Type: {}".format(precip_type)) if ( precip_propability != None and precip_propability != "" and float(precip_propability) < self.propability_minimum and precip_intensity != None and precip_intensity != "" and float(precip_intensity) < self.intensity_minimum ): self.turn_off(self.reminder_acknowledged_entity) self.log("Setting reminder_acknowledged to: {}".format("off")) self.log("Reminding user") keyboard = [[("Hab ich gemacht", self.keyboard_callback)]] self.call_service( "telegram_bot/send_message", target=self.user_id, message=self.message.format(precip_propability), inline_keyboard=keyboard, ) else: self.turn_on(self.reminder_acknowledged_entity) self.log("Setting reminder_acknowledged to: {}".format("off")) self.log("Notifying user") self.notifier.notify( self.notify_name, self.message_not_needed.format( precip_propability, precip_intensity ), ) def run_evening_callback(self, kwargs): """Remind user to water the plants he if didn't acknowledge it""" if self.get_state(self.app_switch) == "on": if self.get_state(self.reminder_acknowledged_entity) == "off": self.log("Reminding user") self.call_service( "notify/" + self.notify_name, message=self.message_evening ) def receive_telegram_callback(self, event_name, data, kwargs): """Event listener for Telegram callback queries.""" assert event_name == "telegram_callback" data_callback = data["data"] callback_id = data["id"] chat_id = data["chat_id"] message_id = data["message"]["message_id"] text = data["message"]["text"] self.log("callback data: {}".format(data), level="DEBUG") if data_callback == self.keyboard_callback: # Keyboard editor: # Answer callback query self.call_service( "telegram_bot/answer_callback_query", message="Super!", callback_query_id=callback_id, ) self.turn_on(self.reminder_acknowledged_entity) self.log("Setting reminder_acknowledged to: {}".format("on")) self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=message_id, message=text + " Hast du um {}:{} erledigt.".format( datetime.datetime.now().hour, datetime.datetime.now().minute ), inline_keyboard=[], ) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: plantWateringNotifier/plantWateringNotifier.yaml ================================================ # plantWateringNotifier: # module: plantWateringNotifier # class: PlantWateringNotifier # app_switch: input_boolean.plant_watering_notifier # rain_precip_sensor: sensor.dark_sky_precip_probability # rain_precip_intensity_sensor: sensor.dark_sky_precip_intensity # precip_type_sensor: sensor.dark_sky_precip # notify_name: group_notifications # user_id: !secret telegram_user_id # reminder_acknowledged_entity: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged # message: "Die Regenwahrscheinlichkeit beträgt heute nur {}. Vergiss nicht die Pflanzen zu gießen!" # #message: "The Rain Propability is only {}. Don't forget to water the plants!" # message_not_needed: "Es wird heute mit einer Wahrscheinlichkeit von {} Prozent ungefähr {} Millimeter pro Stunde regnen. Du brauchst nicht selbst gießen." # #message_not_needed: "It will rain today {} millimeter per hour with a propability of {}. You don't have to water your plants" # message_evening: "Ich bin mir nicht sicher ob du vergessen hast die Pflanzen zu gießen, deswegen erinnere ich dich lieber noch einmal daran." # #message_evening: "I'm not sure whether you waterd your plants, so I thought I better remind you again" # dependencies: # - Notifier ================================================ FILE: pollenNotifier/pollenNotifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App which notifies you when there is a pollen forecast for today # Used with sensors getting data from https://opendata.dwd.de/climate_environment/health/alerts/s31fg.json # # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # pollen_sensor: sensor which shows pollen for today. example: sensor.pollen_101_roggen_today # pollen_name: Name of the allergen. example: Roggen # notify_name: Who to notify. example: group_notifications # notify_time: When to notify. example: 08:00 # notify_threshold: Minimum level of pollen needed to notify. example: 1.0 # message: localized message to use in notification # # Release Notes # # Version 1.3.1: # Use consistent message variable # # Version 1.3: # use Notify App # # Version 1.2: # message now directly in own yaml instead of message module # # Version 1.1: # Added notify_threshold # # Version 1.0: # Initial Version class PollenNotifier(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.pollen_sensor = self.args["pollen_sensor"] self.pollen_name = self.args["pollen_name"] self.notify_name = self.args["notify_name"] self.notify_time = self.args["notify_time"] self.notify_threshold = self.args["notify_threshold"] self.message = self.args["message"] self.message_no_data = self.args["message_no_data"] self.mappingsdict = {} self.mappingsdict["-1"] = "keine Daten" self.mappingsdict["0"] = "Keine" self.mappingsdict["0-1"] = "Keine bis Geringe" self.mappingsdict["1"] = "Geringe" self.mappingsdict["1-2"] = "Geringe bis Mittlere" self.mappingsdict["2"] = "Mittlere" self.mappingsdict["2-3"] = "Mittlere bis Hohe" self.mappingsdict["3"] = "Hohe" self.level_mapping_dict = {} self.level_mapping_dict["-1"] = -1.0 self.level_mapping_dict["0"] = 0.0 self.level_mapping_dict["0-1"] = 0.5 self.level_mapping_dict["1"] = 1.0 self.level_mapping_dict["1-2"] = 1.5 self.level_mapping_dict["2"] = 2.0 self.level_mapping_dict["2-3"] = 2.5 self.level_mapping_dict["3"] = 3 self.notifier = self.get_app("Notifier") hours = self.notify_time.split(":", 1)[0] minutes = self.notify_time.split(":", 1)[1] self.timer_handle_list.append( self.run_daily( self.run_daily_callback, datetime.time(int(hours), int(minutes), 0) ) ) def run_daily_callback(self, kwargs): """Check if there is an pollen forcast and notify the user about it""" if self.get_state(self.app_switch) == "on": pollen_sensor_state = self.get_state(self.pollen_sensor) self.log( "{} Belastung Heute: {}".format(self.pollen_name, pollen_sensor_state) ) if pollen_sensor_state == "-1": message = self.message_no_data.format("Heute", self.pollen_name) elif pollen_sensor_state == "0": message = ( self.message.format( "Heute", self.mappingsdict[pollen_sensor_state], self.pollen_name, ) + " Genieß den Tag!" ) else: message = self.message.format( "Heute", self.mappingsdict[pollen_sensor_state], self.pollen_name ) if self.level_mapping_dict[pollen_sensor_state] >= float( self.notify_threshold ): self.log("Notifying user") self.notifier.notify(self.notify_name, message) else: self.log("Threshold not met. Not notifying user") def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: pollenNotifier/pollenNotifier.yaml ================================================ roggenNotifier: module: pollenNotifier class: PollenNotifier app_switch: input_boolean.roggen_notifier pollen_sensor: sensor.pollen_101_roggen_today pollen_name: Roggen notify_name: group_notifications notify_time: 08:00 notify_threshold: 1.0 message: "{} ist {} {} Belastung." #message: "The {} intensity {} is {}." message_no_data: "Ich habe {} leider keine Daten für {}." #message_no_data: "{} I have no pollen data for {}." dependencies: - Notifier graeserNotifier: module: pollenNotifier class: PollenNotifier app_switch: input_boolean.graeser_notifier pollen_sensor: sensor.pollen_101_graeser_today pollen_name: Gräser notify_name: group_notifications notify_time: 08:00 notify_threshold: 1.0 message: "{} ist {} {} Belastung." #message: "The {} intensity {} is {}." message_no_data: "Ich habe {} leider keine Daten für {}." #message_no_data: "{} I have no pollen data for {}." dependencies: - Notifier birkeNotifier: module: pollenNotifier class: PollenNotifier app_switch: input_boolean.birke_notifier pollen_sensor: sensor.pollen_101_birke_today pollen_name: Birke notify_name: group_notifications notify_time: 08:00 notify_threshold: 1.0 message: "{} ist {} {} Belastung." #message: "The {} intensity {} is {}." message_no_data: "Ich habe {} leider keine Daten für {}." #message_no_data: "{} I have no pollen data for {}." dependencies: - Notifier ================================================ FILE: powerUsageNotification/powerUsageNotification.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App which notifies you when a power usage sensor indicated a device is on/off # # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # sensor: power sensor. example: sensor.dishwasher_power_usage # input_boolean (optional): input_boolean to set to on/off # notify_name: Who to notify. example: group_notifications # notify_start (optional): Notify if start was detected: example True (default: True) # notify_start_use_alexa (optional): Notify with alexa if start was detected: example True (default: True) # notify_end (optional): Notify if end was detected: example True (default: True) # notify_end_use_alexa (optional): Notify with alexa if end was detected: example True (default: True) # delay: seconds to wait until a the device is considered "off". example: 60 # threshold: amount of "usage" which indicated the device is on. example: 2 # alternative_name: Name to use in notification. example: Waschmaschine # message: Message to use when notifying device is on # message_off: Message to use when notifying device is off # # Release Notes # # Version 1.5: # Catch unknown # # Version 1.4: # Added notify_start, notify_start_use_alexa, notify_end, notify_end_use_alexa, input_boolean # # Version 1.3: # use Notify App # # Version 1.2: # message now directly in own yaml instead of message module # # Version 1.1: # Added app_switch # # Version 1.0: # Initial Version class PowerUsageNotification(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.input_boolean = self.args.get("input_boolean") self.sensor = self.args["sensor"] self.alternative_name = self.args["alternative_name"] self.notify_name = self.args["notify_name"] try: self.notify_start = self.args["notify_start"] except KeyError: self.notify_start = True try: self.notify_start_use_alexa = self.args["notify_start_use_alexa"] except KeyError: self.notify_start_use_alexa = True try: self.notify_end = self.args["notify_end"] except KeyError: self.notify_end = True try: self.notify_end_use_alexa = self.args["notify_end_use_alexa"] except KeyError: self.notify_end_use_alexa = True self.delay = self.args["delay"] self.threshold = self.args["threshold"] self.message = self.args["message"] self.message_off = self.args["message_off"] self.triggered = False self.isWaitingHandle = None self.notifier = self.get_app("Notifier") # Subscribe to sensors self.listen_state_handle_list.append( self.listen_state(self.state_change, self.sensor) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": # Initial: power usage goes up if ( new != None and new != "" and new != "unknown" and not self.triggered and float(new) > self.threshold ): self.triggered = True self.log("Power Usage is: {}".format(float(new))) self.log("Setting triggered to: {}".format(self.triggered)) if self.input_boolean is not None: self.turn_on(self.input_boolean) if self.notify_start: self.notifier.notify( self.notify_name, self.message.format(self.alternative_name), useAlexa=self.notify_start_use_alexa, ) else: self.log("Not notifying user") # Power usage goes down below threshold elif ( new != None and new != "" and self.triggered and self.isWaitingHandle == None and float(new) <= self.threshold ): self.log("Waiting: {} seconds to notify.".format(self.delay)) self.isWaitingHandle = self.run_in(self.notify_device_off, self.delay) self.log("Setting isWaitingHandle to: {}".format(self.isWaitingHandle)) self.timer_handle_list.append(self.isWaitingHandle) # Power usage goes up before delay elif ( new != None and new != "" and self.triggered and self.isWaitingHandle != None and float(new) > self.threshold ): self.log("Cancelling timer") self.cancel_timer(self.isWaitingHandle) self.isWaitingHandle = None self.log("Setting isWaitingHandle to: {}".format(self.isWaitingHandle)) def notify_device_off(self, kwargs): """Notify User that device is off. This may get cancelled if it turns on again in the meantime""" self.triggered = False self.log("Setting triggered to: {}".format(self.triggered)) self.isWaitingHandle = None self.log("Setting isWaitingHandle to: {}".format(self.isWaitingHandle)) if self.input_boolean is not None: self.turn_off(self.input_boolean) if self.notify_end: self.notifier.notify( self.notify_name, self.message_off.format(self.alternative_name), useAlexa=self.notify_end_use_alexa, ) else: self.log("Not notifying user") def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: powerUsageNotification/powerUsageNotification.yaml ================================================ powerUsageNotification_Dishwasher: module: powerUsageNotification class: PowerUsageNotification app_switch: input_boolean.power_usage_notification_dishwasher input_boolean: input_boolean.dishwasher sensor: sensor.dishwasher_power_usage notify_name: group_notifications notify_start: False delay: 1260 #21 minutes threshold: 2 alternative_name: Die Spülmaschine message: "{} ist gestartet." #message: "{} just started." message_off: "{} ist fertig." #message_off: "{} just finished." dependencies: - Notifier powerUsageNotification_Washingmachine: module: powerUsageNotification class: PowerUsageNotification app_switch: input_boolean.power_usage_notification_washingmachine input_boolean: input_boolean.washingmachine sensor: sensor.washingmachine_power_usage notify_name: group_notifications notify_start: False delay: 60 threshold: 2 alternative_name: Die Waschmaschine message: "{} ist gestartet." #message: "{} just started." message_off: "{} ist fertig." #message_off: "{} just finished." dependencies: - Notifier ================================================ FILE: reminder/reminder.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime import uuid # # App which reminds you daily and again in the evening if not acknowledged # # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # notify_name: Who to notify. example: group_notifications # user_id: The user_id of the telegram user to ask whether he knows an unknown face. example: 812391 # reminder_acknowledged_entity: Input Boolean to store the information whether the user acknowledged the notification. # This prevents new notifications upon HA/Appdaemon restart. # example: input_boolean.persistence_plantwateringnotifier_reminder_acknowledged # message: localized message to use in notification # # Release Notes # # Version 1.0: # Initial Version KEYBOARD_CALLBACK_BASE = "/reminder_acknowledged" class Reminder(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.notify_name = self.args["notify_name"] self.user_id = self.args["user_id"] self.reminder_acknowledged_entity = self.args["reminder_acknowledged_entity"] self.message = self.args["message"] self.message_evening = self.args["message_evening"] self.keyboard_callback = ( KEYBOARD_CALLBACK_BASE + uuid.uuid4().hex ) # Unique callback for each instance self.notifier = self.get_app("Notifier") self.reminder_acknowledged = self.get_state(self.reminder_acknowledged_entity) self.listen_event_handle_list.append( self.listen_event(self.receive_telegram_callback, "telegram_callback") ) # Remind daily at 08:00 self.timer_handle_list.append( self.run_daily(self.run_morning_callback, datetime.time(8, 0, 0)) ) # Remind daily at 18:00 self.timer_handle_list.append( self.run_daily(self.run_evening_callback, datetime.time(18, 0, 0)) ) def run_morning_callback(self, kwargs): """Remind the user of {self.message}""" if self.get_state(self.app_switch) == "on": self.turn_off(self.reminder_acknowledged_entity) self.log("Setting reminder_acknowledged to: off") self.log("Reminding user") keyboard = [[("Hab ich gemacht", self.keyboard_callback)]] self.call_service( "telegram_bot/send_message", target=self.user_id, message=self.message, inline_keyboard=keyboard, ) def run_evening_callback(self, kwargs): """Remind the user again if he didn't acknowledge it""" if self.get_state(self.app_switch) == "on": if self.get_state(self.reminder_acknowledged_entity) == "off": self.log("Reminding user") self.call_service( "notify/" + self.notify_name, message=self.message_evening ) def receive_telegram_callback(self, event_name, data, kwargs): """Event listener for Telegram callback queries.""" assert event_name == "telegram_callback" data_callback = data["data"] callback_id = data["id"] chat_id = data["chat_id"] message_id = data["message"]["message_id"] text = data["message"]["text"] self.log(f"callback data: {data}", level="DEBUG") if data_callback == self.keyboard_callback: # Keyboard editor: # Answer callback query self.call_service( "telegram_bot/answer_callback_query", message="Super!", callback_query_id=callback_id, ) self.turn_on(self.reminder_acknowledged_entity) self.log("Setting reminder_acknowledged to: on") self.call_service( "telegram_bot/edit_message", chat_id=chat_id, message_id=message_id, message=text + f" Hast du um {datetime.datetime.now().hour}:{datetime.datetime.now().minute} erledigt.", inline_keyboard=[], ) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: reminder/reminder.yaml ================================================ # reminderPlantwateringNew: # module: reminder # class: Reminder # app_switch: input_boolean.plant_watering_reminder_new # notify_name: group_notifications # user_id: !secret telegram_user_id # reminder_acknowledged_entity: input_boolean.persistence_reminder_plantwatering_new_reminder_acknowledged # message: "Vergiss nicht die neuen Pflanzen zu gießen!" # #message: "Don't forget to water the plants!" # 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." # #message_evening: "I'm not sure whether you watered your plants, so I thought I better remind you again" # dependencies: # - Notifier ================================================ FILE: requirements.txt ================================================ wheel ================================================ FILE: seqSink/requirements.txt ================================================ requests==2.24.0 ================================================ FILE: seqSink/seqSink.py ================================================ import appdaemon.plugins.hass.hassapi as hass import socket import os import json import requests # # App which forwards all logs to seq. # # Args: # # server_url: Server url of the seq instance to log to. example: "http://seq:5341/" # api_key (optional): Api key to use. # # Release Notes # # Version 1.0: # Initial Version class SeqSink(hass.Hass): def initialize(self): self.server_url = self.args["server_url"] if not self.server_url.endswith("/"): self.server_url += "/" self.server_url += "api/events/raw" self.session = requests.Session() self.session.headers["Content-Type"] = "application/json" api_key = self.args.get("api_key") if api_key: self.session.headers["X-Seq-ApiKey"] = api_key self.handle = self.listen_log(self.log_message_callback) def log_message_callback(self, app_name, ts, level, log_type, message, kwargs): if app_name != "seqSink": event_data = { "Timestamp": str(ts), "Level": str(level), "MessageTemplate": str(message), "Properties": { "Type": "Appdaemon", "AppName": str(app_name) }, } request_body = {"Events": [event_data]} try: request_body_json = json.dumps(request_body) except TypeError: self.log(f"Could not serialize {request_body}") return try: response = self.session.post( self.server_url, data=request_body_json, stream=True, # prevent '362' ) response.raise_for_status() except requests.RequestException as requestFailed: self.log(f"Could not serialize {message}") if not requestFailed.response: self.log("Response from Seq was unavailable.") elif not requestFailed.response.text: self.log("Response body from Seq was empty.") else: self.log(f"Response body from Seq:{requestFailed.response.text}") def terminate(self): self.cancel_listen_log(self.handle) ================================================ FILE: seqSink/seqSink.yaml ================================================ # seqSink: # module: seqSink # class: SeqSink # server_url: "http://seq:5341/" # api_key: !secret seq_api_key ================================================ FILE: setThermostat/setThermostat.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App which sets a thermostat to a target temperature based on a time from an entity # # Args: # # app_switch: on/off switch for this app. example: input_boolean.warm_bath_before_wakeup # isHome: entity which shows if someone is home. example: input_boolean.is_home # sleepMode: entity which shows if users are sleeping. example: input_boolean.sleepmode # time_entity: sensor which determines when to run in the format 14:30. example: sensor.alarm_time # upfront_time: how many minutes before the time_sensor to run. example: 60 # duration: After how many minutes should the thermostat be set back to its previous value. example: 60 # climat_entity: climate entity to set. example: climate.bad_thermostat # target_entity: the entity holding the target temp. example: warm_bath_before_wakeup # message: message to use in notification # notify_name: who to notify. example: group_notifications # use_alexa: use alexa for notification. example: False # # Release Notes # # Version 1.5: # Catch new is unknown # # Version 1.4: # Use sleepmode # # Version 1.3: # Use new formatted alarm_time # # Version 1.2.1: # Reschedule timer after first run # # Version 1.2: # Added isHome. Only run when someone is home # # Version 1.1: # Actually set the previous temp # # Version 1.0: # Initial Version class SetThermostat(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.time_entity = self.args["time_entity"] self.upfront_time = self.args["upfront_time"] self.duration = self.args["duration"] self.climat_entity = self.args["climat_entity"] self.target_entity = self.args["target_entity"] self.message = self.args["message"] self.notify_name = self.args["notify_name"] self.use_alexa = self.args["use_alexa"] self.isHome = self.args["isHome"] self.sleepMode = self.args["sleepMode"] self.notifier = self.get_app("Notifier") self.run_timer = None self.cached_alarm_time = None self.listen_state_handle_list.append( self.listen_state(self.schedule_trigger, self.time_entity) ) self.schedule_trigger( self.time_entity, None, None, self.get_state(self.time_entity), None ) def schedule_trigger(self, entity, attribute, old, new, kwargs): if ( new is not None and new != old and new != "" and new != "unknown" and new != self.cached_alarm_time ): if self.run_timer is not None: self.cancel_timer(self.run_timer) self.log("Cancelled scheduled trigger") self.run_timer = None self.cached_alarm_time = new event_time = datetime.datetime.strptime(new, "%Y-%m-%d %H:%M:%S") event_time = event_time - datetime.timedelta(minutes=self.upfront_time) try: self.run_timer = self.run_at(self.trigger_thermostat, event_time) self.timer_handle_list.append(self.run_timer) self.log("Thermostat will trigger at {}".format(event_time)) except ValueError: self.log("New trigger time would be in the past: {}".format(event_time)) def trigger_thermostat(self, kwargs): if ( self.get_state(self.app_switch) == "on" and self.get_state(self.isHome) == "on" and self.get_state(self.sleepMode) == "on" ): self.log( self.message.format( self.friendly_name(self.climat_entity), self.get_state(self.target_entity), ) ) self.notifier.notify( self.notify_name, self.message.format( self.friendly_name(self.climat_entity), self.get_state(self.target_entity), ), useAlexa=self.use_alexa, ) self.log("Turning {} on".format(self.climat_entity)) self.call_service("climate/turn_on", entity_id=self.climat_entity) self.previous_temp = self.get_state(self.climat_entity, attribute="all")[ "attributes" ]["temperature"] self.call_service( "climate/set_temperature", entity_id=self.climat_entity, temperature=self.get_state(self.target_entity), ) self.log("Resetting Thermostat in {} minutes.".format(self.duration)) self.timer_handle_list.append( self.run_in(self.reset_thermostat, float(self.duration) * 60) ) if self.run_timer is not None: self.cancel_timer(self.run_timer) def reset_thermostat(self, kwargs): if self.previous_temp is not None: self.log( self.message.format( self.friendly_name(self.climat_entity), self.previous_temp ) ) self.notifier.notify( self.notify_name, self.message.format( self.friendly_name(self.climat_entity), self.previous_temp ), useAlexa=self.use_alexa, ) self.call_service( "climate/set_temperature", entity_id=self.climat_entity, temperature=self.previous_temp, ) self.schedule_trigger( self.time_entity, None, None, self.get_state(self.time_entity), None ) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: setThermostat/setThermostat.yaml ================================================ # warm_bath_before_wakeup: # module: setThermostat # class: SetThermostat # app_switch: input_boolean.warm_bath_before_wakeup # isHome: input_boolean.is_home # sleepMode: input_boolean.sleepmode # time_entity: sensor.alarm_time # upfront_time: 120 # duration: 120 # climat_entity: climate.bad_thermostat # target_entity: input_number.warm_bath_before_wakeup # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # warm_upper_bath_before_wakeup: # module: setThermostat # class: SetThermostat # app_switch: input_boolean.warm_upper_bath_before_wakeup # isHome: input_boolean.is_home # sleepMode: input_boolean.sleepmode # time_entity: sensor.alarm_time # upfront_time: 60 # duration: 60 # climat_entity: climate.bad_oben_thermostat # target_entity: input_number.warm_upper_bath_before_wakeup # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier ================================================ FILE: setThermostatOnStateChange/setThermostatOnStateChange.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App which sets a thermostat to a target temperature on state change # # Args: # # app_switch: on/off switch for this app. example: input_boolean.warm_bath_before_wakeup # trigger_entity: entity which triggers this app. example: input_boolean.is_home # trigger_state: new state of trigger_entity which triggers this app. example: "off" # climate_entity: climate entity to set. example: climate.bad_thermostat # target_entity: the entity holding the target temp. example: warm_bath_before_wakeup # message (optional): message to use in notification # notify_name (optional): who to notify. example: group_notifications # use_alexa (optional): use alexa for notification. example: False # # Release Notes # # Version 1.2: # Rename of SetThermostatOnStateChange # # Version 1.1: # Use isHome as trigger # # Version 1.0: # Initial Version class SetThermostatOnStateChange(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.trigger_entity = self.args["trigger_entity"] self.trigger_state = self.args["trigger_state"] self.climate_entity = self.args["climate_entity"] self.target_entity = self.args["target_entity"] self.message = self.args.get("message") self.notify_name = self.args.get("notify_name") try: self.use_alexa = self.args["use_alexa"] except KeyError: self.use_alexa = False self.notifier = self.get_app("Notifier") self.listen_state_handle_list.append( self.listen_state(self.state_change, self.trigger_entity) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new == self.trigger_state and old != new: if self.message is not None: self.log( self.message.format( self.friendly_name(self.climate_entity), self.get_state(self.target_entity), ) ) self.call_service("climate/turn_on", entity_id=self.climate_entity) self.call_service( "climate/set_temperature", entity_id=self.climate_entity, temperature=self.get_state(self.target_entity), ) if self.notify_name is not None: self.notifier.notify( self.notify_name, self.message.format( self.friendly_name(self.climate_entity), self.get_state(self.target_entity), ), useAlexa=self.use_alexa, ) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: setThermostatOnStateChange/setThermostatOnStateChange.yaml ================================================ # setWohnzimmerThermostatWhenLeaving: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_livingroom_thermostat_when_leaving # trigger_entity: input_boolean.is_home # trigger_state: "off" # climate_entity: climate.wohnzimmer_thermostat # target_entity: input_number.set_livingroom_thermostat_when_leaving # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setBadObenThermostatWhenLeaving: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_upper_bath_thermostat_when_leaving # trigger_entity: input_boolean.is_home # trigger_state: "off" # climate_entity: climate.bad_oben_thermostat # target_entity: input_number.set_upper_bath_thermostat_when_leaving # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setBadThermostatWhenLeaving: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_bath_thermostat_when_leaving # trigger_entity: input_boolean.is_home # trigger_state: "off" # climate_entity: climate.bad_thermostat # target_entity: input_number.set_bath_thermostat_when_leaving # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setKitchenThermostatWhenLeaving: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_kitchen_thermostat_when_leaving # trigger_entity: input_boolean.is_home # trigger_state: "off" # climate_entity: climate.kuche_thermostat # target_entity: input_number.set_kitchen_thermostat_when_leaving # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setWohnzimmerThermostatWhenSleeping: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_livingroom_thermostat_when_sleeping # trigger_entity: input_boolean.sleepmode # trigger_state: "on" # climate_entity: climate.wohnzimmer_thermostat # target_entity: input_number.set_livingroom_thermostat_when_sleeping # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setBadObenThermostatWhenSleeping: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_upper_bath_thermostat_when_sleeping # trigger_entity: input_boolean.sleepmode # trigger_state: "on" # climate_entity: climate.bad_oben_thermostat # target_entity: input_number.set_upper_bath_thermostat_when_sleeping # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setBadThermostatWhenSleeping: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_bath_thermostat_when_sleeping # trigger_entity: input_boolean.sleepmode # trigger_state: "on" # climate_entity: climate.bad_thermostat # target_entity: input_number.set_bath_thermostat_when_sleeping # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setKitchenThermostatWhenSleeping: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_kitchen_thermostat_when_sleeping # trigger_entity: input_boolean.sleepmode # trigger_state: "on" # climate_entity: climate.kuche_thermostat # target_entity: input_number.set_kitchen_thermostat_when_sleeping # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # dependencies: # - Notifier # setLivingroomThermostatWhenWakingUp: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_livingroom_thermostat_when_waking_up # trigger_entity: input_boolean.sleepmode # trigger_state: "off" # climate_entity: climate.wohnzimmer_thermostat # target_entity: input_number.set_livingroom_thermostat_when_waking_up # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setKitchenThermostatWhenWakingUp: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_kitchen_thermostat_when_waking_up # trigger_entity: input_boolean.sleepmode # trigger_state: "off" # climate_entity: climate.kuche_thermostat # target_entity: input_number.set_kitchen_thermostat_when_waking_up # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setBadThermostatWhenWakingUp: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_bath_thermostat_when_waking_up # trigger_entity: input_boolean.sleepmode # trigger_state: "off" # climate_entity: climate.bad_thermostat # target_entity: input_number.set_bath_thermostat_when_waking_up # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setBadObenThermostatWhenWakingUp: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_upper_bath_thermostat_when_waking_up # trigger_entity: input_boolean.sleepmode # trigger_state: "off" # climate_entity: climate.bad_oben_thermostat # target_entity: input_number.set_upper_bath_thermostat_when_waking_up # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setLivingroomThermostatWhenComingHome: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_livingroom_thermostat_when_coming_home # trigger_entity: input_boolean.is_home # trigger_state: "on" # climate_entity: climate.wohnzimmer_thermostat # target_entity: input_number.set_livingroom_thermostat_when_coming_home # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setKitchenThermostatWhenComingHome: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_kitchen_thermostat_when_coming_home # trigger_entity: input_boolean.is_home # trigger_state: "on" # climate_entity: climate.kuche_thermostat # target_entity: input_number.set_kitchen_thermostat_when_coming_home # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setBadThermostatWhenComingHome: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_bath_thermostat_when_coming_home # trigger_entity: input_boolean.is_home # trigger_state: "on" # climate_entity: climate.bad_thermostat # target_entity: input_number.set_bath_thermostat_when_coming_home # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier # setBadObenThermostatWhenComingHome: # module: setThermostatOnStateChange # class: SetThermostatOnStateChange # app_switch: input_boolean.set_upper_bath_thermostat_when_coming_home # trigger_entity: input_boolean.is_home # trigger_state: "on" # climate_entity: climate.bad_oben_thermostat # target_entity: input_number.set_upper_bath_thermostat_when_coming_home # message: "Ich habe {} auf {} °C gestellt" # #message: "I have set {} to {}" # notify_name: group_notifications # use_alexa: False # dependencies: # - Notifier ================================================ FILE: sleepModeHandler/sleepModeHandler.py ================================================ import appdaemon.plugins.hass.hassapi as hass # # App which sets the sleep mode on/off # # Args: # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # sleepmode: input_boolean holding the sleepmode. example: input_boolean.sleepmode # users: configuration for users # # Release Notes # # Version 1.2: # Add use_alexa # # Version 1.1: # Only send notification if sleepmode is actually changed # # Version 1.0: # Initial Version class SleepModeHandler(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.sleepmode = self.args["sleepmode"] self.users = self.args["users"] self.notify_name = self.args["notify_name"] self.message_sleeping = self.args["message_sleeping"] self.message_awake = self.args["message_awake"] try: self.use_alexa = self.args["use_alexa"] except KeyError: self.use_alexa = False self.notifier = self.get_app("Notifier") for user in self.users: self.listen_state_handle_list.append( self.listen_state(self.state_change, user["sleep_mode"]) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": if new != "" and new != old: if new == "on": if self.are_all_that_are_home_sleeping(): if self.get_state(self.sleepmode) == "off": self.log("All at home are sleeping") self.turn_on(self.sleepmode) self.notifier.notify( self.notify_name, self.message_sleeping, useAlexa=self.use_alexa, ) elif new == "off": if self.are_all_that_are_home_awake(): if self.get_state(self.sleepmode) == "on": self.log("All at home are awake") self.turn_off(self.sleepmode) self.notifier.notify( self.notify_name, self.message_awake, useAlexa=self.use_alexa, ) def are_all_that_are_home_sleeping(self): for user in self.users: if self.get_state(user["isHome"]) == "on": if self.get_state(user["sleep_mode"]) != "on": return False return True def are_all_that_are_home_awake(self): for user in self.users: if self.get_state(user["isHome"]) == "on": if self.get_state(user["sleep_mode"]) == "on": return False return True def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: sleepModeHandler/sleepModeHandler.yaml ================================================ # sleepModeHandler: # module: sleepModeHandler # class: SleepModeHandler # app_switch: input_boolean.sleep_mode_handler # sleepmode: input_boolean.sleepmode # notify_name: group_notifications # message_sleeping: "Alle zu Hause sind im Bett" # #message_sleeping: "All home are in bed" # message_awake: "Alle zu Hause sind wach" # #message_awake: "All home are awake" # use_alexa: False # users: # - sleep_mode: input_boolean.user_one_sleep # isHome: input_boolean.user_one_home # - sleep_mode: input_boolean.user_two_sleep # isHome: input_boolean.user_two_home # dependencies: # - Notifier ================================================ FILE: sleepModeHandler/userSleepModeHandler.py ================================================ import appdaemon.plugins.hass.hassapi as hass from queue import Queue # # App which sets the sleep mode on/off # # Args: # app_switch: # on/off switch for this app. # example: input_boolean.turn_fan_on_when_hot # input_boolean: # input_boolean holding the sleepmode. example: input_boolean.sleepmode # location_sensor: # location sensor of user. example: sensor.location_user_one # room: # Room name in which user must be. example: Wohnzimmer # asleep_duration: # seconds to wait before turning sleepmode on. example: 120 # awake_duration: # seconds to wait before turning sleepmode off. example: 120 # is_home_input_boolean: # input_boolean for the is home state of the user # example: input_boolean.is_user_home_determiner_user_one # # Release Notes # # Version 1.4: # Also watch is_home # # Version 1.3: # Reimplementation # # Version 1.2: # Only trigger on an actual change # # Version 1.1: # Added asleep_duration and awake_duration instead of duration # # Version 1.0: # Initial Version class UserSleepModeHandler(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.app_switch = self.args["app_switch"] self.location_sensor = self.args["location_sensor"] self.room = self.args["room"] self.asleep_duration = self.args["asleep_duration"] self.awake_duration = self.args["awake_duration"] self.input_boolean = self.args["input_boolean"] self.is_home_input_boolean = self.args["is_home_input_boolean"] self.timer_handle = None self.queue = Queue() self.listen_state_handle_list.append( self.listen_state(self.state_change, self.location_sensor) ) self.listen_state_handle_list.append( self.listen_state(self.state_change, self.is_home_input_boolean) ) def home_state_change(self, entity, attribute, old, new, kwargs): """is_home state changed.""" if new != old: if self.get_state(self.app_switch) == "on": if new == "off": self.log( f"User left home. Turning {self.input_boolean} off") self.turn_off(self.input_boolean) def state_change(self, entity, attribute, old, new, kwargs): """Handle state changes of the location sensor.""" if new != old: if self.get_state(self.app_switch) == "on": # User left room if old == self.room: self.log( f"User left {self.room}. Resetting timer." f"Will trigger awake in {self.awake_duration}s" ) if self.timer_handle is not None: self.cancel_timer(self.timer_handle) self.timer_handle = self.run_in( self.awake, self.awake_duration) elif new == self.room: self.log( f"User entered {self.room}. " f"Resetting timer. " f"Will trigger asleep in {self.asleep_duration}s" ) if self.timer_handle is not None: self.cancel_timer(self.timer_handle) self.timer_handle = self.run_in( self.asleep, self.asleep_duration) def awake(self, kwargs): """User left room for more than self.awake_duration. Turn off sleep mode.""" current_location = self.get_state(self.location_sensor) if current_location != self.room: if self.get_state(self.input_boolean) == "on": self.log( f"{self.friendly_name(self.location_sensor)} " f"is outside {self.room} " f"for more than {self.asleep_duration}s. " f"Turning {self.input_boolean} off" ) self.turn_off(self.input_boolean) else: self.log(f"Timer ran out but user is in {current_location}") def asleep(self, kwargs): """User stayed in room for more than self.asleep_duration. Turn on sleep mode.""" current_location = self.get_state(self.location_sensor) if current_location == self.room: if self.get_state(self.input_boolean) == "off": self.log( f"{self.friendly_name(self.location_sensor)} " f"is in {self.room} " f"for more than {self.asleep_duration}s. " f"Turning {self.input_boolean} on" ) self.turn_on(self.input_boolean) else: self.log(f"Timer ran out but user is in {current_location}") def insert_room_state_change(self, entity, attribute, old, new, kwargs): """Insert a new room state change into the queue.""" self.queue.put(new) def calculate_room_presence(self, kwargs): """Calculate the percentage the person was in the target room since the last invocation.""" state_changes = [] while not self.queue.empty(): state_changes.append(self.queue.get()) for state_change in state_changes: pass def terminate(self): if self.timer_handle is not None: self.cancel_timer(self.timer_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: sleepModeHandler/userSleepModeHandler.yaml ================================================ # userSleepModeHandlerUserOne: # module: userSleepModeHandler # class: UserSleepModeHandler # app_switch: input_boolean.user_sleep_mode_handler_user_one # input_boolean: input_boolean.user_one_sleep # location_sensor: sensor.location_user_one # is_home_input_boolean: input_boolean.is_user_home_determiner_user_one # room: Schlafzimmer # awake_duration: 600 # asleep_duration: 1800 # userSleepModeHandlerUserTwo: # module: userSleepModeHandler # class: UserSleepModeHandler # app_switch: input_boolean.user_sleep_mode_handler_user_two # input_boolean: input_boolean.user_two_sleep # location_sensor: sensor.location_user_two # is_home_input_boolean: input_boolean.is_user_home_determiner_user_two # room: Schlafzimmer # awake_duration: 600 # asleep_duration: 1800 ================================================ FILE: travelTimeNotifier/travelTimeNotifier.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime from typing import Optional # # App which notifies the user if the travel time is within a normal amount # # # Args: # sensor: google_travel_time or here_travel_time sensor to watch. example: sensor.travel_time_home_from_work # notify_input_boolean: input_boolean determining whether to notify. example: input_boolean.travel_time_home_from_work # notify_name: Who to notify. example: group_notifications # acceptable_range (optional): Multiplier of the normal travel time that is still acceptable. example: 1.2 # message_: message to use in notification # notify_use_Alexa: use Alexa as TTS. Defaults to True. example: False # # Release Notes # # Version 1.7: # Catch NoneType when Homeassistant is still starting # # Version 1.6: # Introduce methods to deal with minor differences between google and here # # Version 1.5: # Rename to TravelTimeNotifier as this can be used with here_travel_time also # # Version 1.4: # use Notify App # # Version 1.3: # message now directly in own yaml instead of message module # # Version 1.2: # Moved to standard google travel sensors. Now only notification # # Version 1.1: # Add notification feature # # Version 1.0: # Initial Version class TravelTimeNotifier(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.timer_handle_list = [] self.sensor = self.args["sensor"] self.notify_input_boolean = self.args["notify_input_boolean"] self.notify_name = self.args["notify_name"] self.message = self.args["message"] try: self.acceptable_range = self.args["acceptable_range"] except KeyError: self.acceptable_range = 1.2 try: self.notify_use_Alexa = self.args["notify_use_Alexa"] except KeyError: self.notify_use_Alexa = True self.notifier = self.get_app("Notifier") self.listen_state_handle_list.append( self.listen_state(self.state_change, self.sensor, attribute="all") ) def state_change(self, entity, attributes, old, new, kwargs) -> None: self.log("entity: {}".format(entity)) self.log("old: {}".format(old)) self.log("new: {}".format(new)) duration_in_traffic_minutes = self.parse_duration_in_traffic_minutes(new) self.log("duration_in_traffic_minutes: {}".format(duration_in_traffic_minutes),) duration_minutes = self.parse_duration_minutes(new) self.log("duration_minutes: {}".format(duration_minutes)) if duration_minutes is None or duration_in_traffic_minutes is None: self.log("Sensor is None. Homeassistant might not be fully started.") else: if duration_in_traffic_minutes <= duration_minutes * self.acceptable_range: if self.get_state(self.notify_input_boolean) == "on": destination_address = self.parse_destination_address(new) self.notify_user(destination_address) self.turn_off(self.notify_input_boolean) def notify_user(self, address: str) -> None: """Notify the user it is time to leave for the given address.""" message = self.message.format(address) self.log("Notify user") self.notifier.notify(self.notify_name, message, useAlexa=self.notify_use_Alexa) def parse_duration_in_traffic_minutes(self, state) -> Optional[int]: """Get duration_in_traffic from the states attributes.""" duration_in_traffic = state["attributes"].get("duration_in_traffic") duration_in_traffic_minutes = None if duration_in_traffic is not None: if isinstance(duration_in_traffic, float): duration_in_traffic_minutes = int(duration_in_traffic) else: duration_in_traffic_minutes = int( duration_in_traffic[: duration_in_traffic.find(" ")] ) else: self.log( "Could not find duration_in_traffic in state attributes.", level="WARNING", ) return duration_in_traffic_minutes def parse_destination_address(self, state) -> Optional[str]: """Get a destination address from the states attributes.""" attributes = state["attributes"] destination_address = None if "destination_name" in attributes: destination_address = attributes["destination_name"] elif "destination_addresses" in attributes: destination_address = attributes["destination_addresses"][0] else: self.log( "Could not find destination_name or destination_addresses in state attributes.", level="WARNING", ) return destination_address def parse_duration_minutes(self, state) -> Optional[int]: """Get duration from the states attributes.""" duration_minutes = None duration = state["attributes"].get("duration") if duration is not None: if isinstance(duration, float): duration_minutes = int(duration) else: duration_minutes = int(duration[: duration.find(" ")]) else: self.log("Could not find duration in state attributes.", level="WARNING") return duration_minutes def terminate(self) -> None: for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: travelTimeNotifier/travelTimeNotifier.yaml ================================================ # travelTime_home_from_work: # module: travelTimeNotifier # class: TravelTimeNotifier # sensor: sensor.travel_time_home_from_work_here # notify_input_boolean: input_boolean.travel_time_home_from_work # notify_name: group_notifications # message: "Du kannst losfahren nach {}" # #message: "You can start your journey to {}" # notify_use_Alexa: False # dependencies: # - Notifier # travelTime_work_from_home: # module: travelTimeNotifier # class: TravelTimeNotifier # sensor: sensor.travel_time_work_from_home_here # notify_input_boolean: input_boolean.travel_time_work_from_home # notify_name: group_notifications # message: "Du kannst losfahren nach {}" # #message: "You can start your journey to {}" # dependencies: # - Notifier # travelTime_elmo_from_home: # module: travelTimeNotifier # class: TravelTimeNotifier # sensor: sensor.travel_time_elmo_from_home_here # notify_input_boolean: input_boolean.travel_time_elmo_from_home # notify_name: group_notifications # message: "Du kannst losfahren nach {}" # #message: "You can start your journey to {}" # dependencies: # - Notifier # travelTime_home_from_elmo: # module: travelTimeNotifier # class: TravelTimeNotifier # sensor: sensor.travel_time_home_from_elmo_here # notify_input_boolean: input_boolean.travel_time_home_from_elmo # notify_name: group_notifications # message: "Du kannst losfahren nach {}" # #message: "You can start your journey to {}" # dependencies: # - Notifier # travelTime_work_user_two_from_home: # module: travelTimeNotifier # class: TravelTimeNotifier # sensor: sensor.travel_time_work_user_two_from_home_here # notify_input_boolean: input_boolean.travel_time_work_user_two_from_home # notify_name: group_notifications # message: "Du kannst losfahren nach {}" # #message: "You can start your journey to {}" # dependencies: # - Notifier # travelTime_home_from_work_user_two_here: # module: travelTimeNotifier # class: TravelTimeNotifier # sensor: sensor.travel_time_home_from_work_user_two_here # notify_input_boolean: input_boolean.travel_time_home_from_work_user_two # notify_name: group_notifications # message: "Du kannst losfahren nach {}" # #message: "You can start your journey to {}" # dependencies: # - Notifier ================================================ FILE: turnFanOnWhenHot/turnFanOnWhenHot.py ================================================ import appdaemon.plugins.hass.hassapi as hass import datetime # # App to Turn on fan when temp is above a threshold and someone is in the room # # Args: # # app_switch: on/off switch for this app. example: input_boolean.turn_fan_on_when_hot # temp_sensor: temp sensor to monitor. example: sensor.large_lamp_temperature # threshold_entity: entity which holds the temp threshold which must be reached. example: input_number.turn_fan_on_when_hot_threshold # location_sensors: location sensors of users. example: sensor.location_user_one,sensor.location_user_two # room: Room name in which one of the users must be. example: Wohnzimmer # actor: actor to turn on # delay: seconds to wait before turning off. example: 120 # Release Notes # # Version 1.3: # Added delay # # Version 1.2: # Using entities from HA now. Added turned_on_by_me # # Version 1.1: # Only turn on when someone is in the room. Turn off otherwise # # Version 1.0: # Initial Version class TurnFanOnWhenHot(hass.Hass): def initialize(self): self.listen_state_handle_list = [] self.timer_handle_list = [] self.app_switch = self.args["app_switch"] self.temp_sensor = self.args["temp_sensor"] self.threshold_entity = self.args["threshold_entity"] self.location_sensors = self.args["location_sensors"].split(",") self.room = self.args["room"] self.actor = self.args["actor"] self.delay = self.args["delay"] self.turned_on_by_me = False # Giggedi self.turn_off_timer_handle = None self.listen_state_handle_list.append( self.listen_state(self.state_change, self.temp_sensor) ) for sensor in self.location_sensors: self.listen_state_handle_list.append( self.listen_state(self.state_change, sensor) ) def state_change(self, entity, attribute, old, new, kwargs): if self.get_state(self.app_switch) == "on": turn_on = False if ( self.get_state(self.temp_sensor) != None and self.get_state(self.temp_sensor) != "unkown" and self.get_state(self.threshold_entity) != None and float(self.get_state(self.temp_sensor)) > float(self.get_state(self.threshold_entity)) ): for sensor in self.location_sensors: if self.get_state(sensor) == self.room: if self.get_state(self.actor) != "on": self.log( "{} is {}. This is above theshold of {}".format( self.friendly_name(self.temp_sensor), self.get_state(self.temp_sensor), self.get_state(self.threshold_entity), ) ) self.log("{} is in {}".format(sensor, self.room)) self.log( "Turning on {}".format(self.friendly_name(self.actor)) ) self.turn_on(self.actor) self.turned_on_by_me = True turn_on = True if self.turn_off_timer_handle != None: self.timer_handle_list.remove(self.turn_off_timer_handle) self.cancel_timer(self.turn_off_timer_handle) self.turn_off_timer_handle = None if not turn_on and self.turned_on_by_me: if self.get_state(self.actor) != "off": self.turn_off_timer_handle = self.run_in( self.turn_off_callback, self.delay ) self.timer_handle_list.append(self.turn_off_timer_handle) def turn_off_callback(self, kwargs): """Turn off the actor again if the timer was not cancelled in the meantime""" self.log("Turning off {}".format(self.friendly_name(self.actor))) self.turn_off(self.actor) self.turned_on_by_me = False self.turn_off_timer_handle = None def terminate(self): for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) ================================================ FILE: turnFanOnWhenHot/turnFanOnWhenHot.yaml ================================================ # turnLargeFanOnWhenHot: # module: turnFanOnWhenHot # class: TurnFanOnWhenHot # app_switch: input_boolean.turn_large_fan_on_when_hot # temp_sensor: sensor.large_ventilator_temperature # threshold_entity: input_number.turn_large_fan_on_when_hot_threshold # location_sensors: sensor.location_user_one,sensor.location_user_two # room: Wohnzimmer # actor: switch.large_ventilator # delay: 120 # turnSmallFanOnWhenHot: # module: turnFanOnWhenHot # class: TurnFanOnWhenHot # app_switch: input_boolean.turn_small_fan_on_when_hot # temp_sensor: sensor.small_ventilator_temperature # threshold_entity: input_number.turn_small_fan_on_when_hot_threshold # location_sensors: sensor.location_user_one,sensor.location_user_two # room: Wohnzimmer # actor: switch.small_ventilator # delay: 120 ================================================ FILE: turnOffBarAfterRestart/turnOffBarAfterRestart.py ================================================ import appdaemon.plugins.hass.hassapi as hass from requests.exceptions import HTTPError # # Will turn the bar table green and then off when homeassistant restarts to indicate the restart went well # # # Args: # # light: light. example: light.bar_table # # Release Notes # # Version 1.0: # Initial Version class TurnOffBarAfterRestart(hass.Hass): def initialize(self): self.timer_handle_list = [] self.listen_event_handle_list = [] self.listen_state_handle_list = [] self.light = self.args["light"] self.timer_handle_list.append(self.run_in(self.turn_green_callback, 1)) def turn_off_callback(self, kwargs): """Turn off light""" try: self.log("Turning {} off".format(self.friendly_name(self.light))) self.turn_off(self.light) except HTTPError as exception: self.log( "Error trying to turn off entity. Will try again in 1s. Error: {}".format( exception ), level="WARNING", ) self.timer_handle_list.append(self.run_in(self.turn_off_callback, 1)) def turn_green_callback(self, kwargs): """This is needed because the turn_on command can result in a HTTP 503 when homeassistant is restarting""" try: self.call_service( "light/turn_on", entity_id=self.light, rgb_color=[0, 255, 0], white_value=0, ) self.log("Turning {} green".format(self.friendly_name(self.light))) self.timer_handle_list.append(self.run_in(self.turn_off_callback, 5)) except HTTPError as exception: self.log( "Error trying to turn on entity. Will try again in 1s. Error: {}".format( exception ), level="WARNING", ) self.timer_handle_list.append(self.run_in(self.turn_green_callback, 1)) def terminate(self): for timer_handle in self.timer_handle_list: self.cancel_timer(timer_handle) for listen_event_handle in self.listen_event_handle_list: self.cancel_listen_event(listen_event_handle) for listen_state_handle in self.listen_state_handle_list: self.cancel_listen_state(listen_state_handle) ================================================ FILE: turnOffBarAfterRestart/turnOffBarAfterRestart.yaml ================================================ # turnOffBarAfterRestart: # module: turnOffBarAfterRestart # class: TurnOffBarAfterRestart # light: light.bar_table