Skip to content

Instantly share code, notes, and snippets.

@robbiet480
Last active October 2, 2021 00:17
Show Gist options
  • Save robbiet480/a12be2ab6401f182952f0f95f6b229ff to your computer and use it in GitHub Desktop.
Save robbiet480/a12be2ab6401f182952f0f95f6b229ff to your computer and use it in GitHub Desktop.
Home Assistant iOS app component. Supports actionable push notifications.
ios:
push_categories: # Defines actionable notifications. Categories are groups of actions. You specify the category in the notification.
- name: Alarm
identifier: 'ALARM' # Must be unique and ALL CAPS
actions:
- identifier: 'SOUND_ALARM' # Must be unique and ALL CAPS
title: 'Sound Alarm' # Needs to be short!
activationMode: 'background' # or background. The mode in which to run the app when the action is performed.
authenticationRequired: yes # A Boolean value indicating whether the user must unlock the device before the action is performed.
destructive: yes # A Boolean value indicating whether the action is destructive. When the value of this property is true, the system displays the corresponding button differently to indicate that the action is destructive.
behavior: 'default' # or TextInput. When TextInput the system provides a way for the user to enter a text response to be included with the notification.
context: 'default' # or Minimal. Default = In this context, the full UI is displayed for the notification’s alert. You may specify up to four custom actions in this context. Minimal = In this context, a minimal UI is displayed for the notification’s alert. You may specify up to two custom actions in this context.
- identifier: 'SILENCE_ALARM' # Must be unique and ALL CAPS
title: 'Silence Alarm' # Needs to be short!
activationMode: 'background' # or background. The mode in which to run the app when the action is performed.
authenticationRequired: yes # A Boolean value indicating whether the user must unlock the device before the action is performed.
destructive: no # A Boolean value indicating whether the action is destructive. When the value of this property is true, the system displays the corresponding button differently to indicate that the action is destructive.
behavior: 'TextInput' # Default or TextInput. When TextInput the system provides a way for the user to enter a text response to be included with the notification.
context: 'default' # or Minimal. Default = In this context, the full UI is displayed for the notification’s alert. You may specify up to four custom actions in this context. Minimal = In this context, a minimal UI is displayed for the notification’s alert. You may specify up to two custom actions in this context.
textInputButtonTitle: 'Silencio!' # Only for iOS 10 when behavior is TextInput.
textInputPlaceholder: '...' # Only for iOS 10 when behavior is TextInput.
parameters: # iOS 9 only. Use this dictionary to specify any behavior-specific data for the action. For example, if you set behavior to TextInput you can use the UIUserNotificationTextInputActionButtonTitleKey key, which lets you customize the title of the button displayed by the text input interface.
UIUserNotificationTextInputActionButtonTitleKey: "Silencio!"
notify:
- name: iOSApp
platform: ios
target: '<TOKEN FROM SETTINGS>'
automation:
- alias: Notify iOS app
trigger:
...
action:
service: notify.iOSApp
data:
message: “Something happened at home!”
data:
push:
badge: 5
sound: <SOUND FILE HERE>
category: "ALARM" # Needs to match the top level identifier you used in the ios configuration
action_data: # Anything passed in action_data will get echoed back to Home Assistant.
entity_id: light.test
my_custom_data: foo_bar
- alias: iOS app notification action pressed
trigger:
platform: event
event_type: ios.notification_action_fired
event_data:
actionName: SOUND_ALARM
action:
...
  1. Pre-define notification categories which contain 1-4 actions.
  2. Notify iOS app with data.push.category set to ALARM (which contains actions named SOUND_ALARM and SILENCE_ALARM).
  3. Push notification delivered to device
  4. SOUND_ALARM button on notification pressed
  5. Identifier of button (SOUND_ALARM) sent back to HA as the actionName property of the event ios.notification_action_fired.
"""
Native Home Assistant iOS app component. Should be put into ~/.homeassistant/custom_components/ios.py.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/ios/
"""
import os
import json
import logging
import requests
import homeassistant.loader as loader
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.notify import (ATTR_TARGET, ATTR_TITLE,
ATTR_MESSAGE, ATTR_DATA)
from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
PUSH_URL = "https://mj28d17jwj.execute-api.us-west-2.amazonaws.com/prod/push"
DOMAIN = "ios"
DEPENDENCIES = ["http"]
DEVICES_FILE = "ios_devices.conf"
DEVICE_IDENTIFIED = "device_identified"
DEVICES = None
PUSH_CONFIGURATION = None
def config_from_file(filename, config=None):
"""Small configuration file management function."""
if config:
# We"re writing configuration
try:
with open(filename, "w") as fdesc:
fdesc.write(json.dumps(config))
except IOError as error:
_LOGGER.error("Saving config file failed: %s", error)
return False
return True
else:
# We"re reading config
if os.path.isfile(filename):
try:
with open(filename, "r") as fdesc:
return json.loads(fdesc.read())
except IOError as error:
_LOGGER.error("Reading config file failed: %s", error)
# This won"t work yet
return False
else:
return {}
def setup(hass, config):
DEVICES = config_from_file(hass.config.path(DEVICES_FILE))
app_config = config[DOMAIN]
push_categories = app_config.get("push_categories", [])
discovery = loader.get_component("discovery")
device_tracker = loader.get_component("device_tracker")
zeroconf = loader.get_component("zeroconf")
zeroconf.setup(hass, config)
hass.wsgi.register_view(iOSAppPushConfigView(hass, push_categories))
hass.wsgi.register_view(iOSAppIdentifyDeviceView(hass))
def device_identified(event):
print("DEVICE IDENTIFIED EVENT FIRED", event)
permanentID = event.data.get("device").get("permanentID")
DEVICES[permanentID] = event.data
if not config_from_file(hass.config.path(DEVICES_FILE), DEVICES):
_LOGGER.error("failed to save config file")
hass.bus.listen(DEVICE_IDENTIFIED, device_identified)
return True
def notify(title="", message="", target=""):
data = {ATTR_MESSAGE: message, ATTR_TITLE: title, ATTR_TARGET: target}
response = requests.put(PUSH_URL, json=data, timeout=10)
if response.status_code != 200:
_LOGGER.exception("Error sending message. Response %d: %s:",
response.status_code, response.reason)
class iOSAppPushConfigView(HomeAssistantView):
requires_auth = False
url = "/api/ios/push"
name = "api:ios:push"
def __init__(self, hass, push_categories):
super().__init__(hass)
self.push_categories = push_categories
def get(self, request):
return self.json(self.push_categories)
class iOSAppIdentifyDeviceView(HomeAssistantView):
requires_auth = False
url = "/api/ios/identify"
name = "api:ios:identify"
def __init__(self, hass):
super().__init__(hass)
def post(self, request):
print("IDENTIFY DEVICE DATA", request.json)
self.hass.bus.fire(DEVICE_IDENTIFIED, request.json)
return self.json({"status": "ok"})
# You can now use iTunes to upload your own sounds.
US-EN-Daisy-Back-Door-Motion.wav
US-EN-Daisy-Back-Door-Open.wav
US-EN-Daisy-Front-Door-Motion.wav
US-EN-Daisy-Front-Door-Open.wav
US-EN-Daisy-Front-Window-Open.wav
US-EN-Daisy-Garage-Door-Open.wav
US-EN-Daisy-Guest-Bath-Leak.wav
US-EN-Daisy-Kitchen-Sink-Leak.wav
US-EN-Daisy-Kitchen-Window-Open.wav
US-EN-Daisy-Laundry-Room-Leak.wav
US-EN-Daisy-Master-Bath-Leak.wav
US-EN-Daisy-Master-Bedroom-Window-Open.wav
US-EN-Daisy-Office-Window-Open.wav
US-EN-Daisy-Refrigerator-Leak.wav
US-EN-Daisy-Water-Heater-Leak.wav
# Added in build 27
US-EN-Alexa-Back-Door-Opened.wav
US-EN-Alexa-Back-Door-Unlocked.wav
US-EN-Alexa-Basement-Door-Opened.wav
US-EN-Alexa-Basement-Door-Unlocked.wav
US-EN-Alexa-Boyfriend-Is-Arriving.wav
US-EN-Alexa-Daughter-Is-Arriving.wav
US-EN-Alexa-Front-Door-Opened.wav
US-EN-Alexa-Front-Door-Unlocked.wav
US-EN-Alexa-Garage-Door-Opened.wav
US-EN-Alexa-Girlfriend-Is-Arriving.wav
US-EN-Alexa-Good-Morning.wav
US-EN-Alexa-Good-Night.wav
US-EN-Alexa-Husband-Is-Arriving.wav
US-EN-Alexa-Mail-Has-Arrived.wav
US-EN-Alexa-Motion-At-Back-Door.wav
US-EN-Alexa-Motion-At-Front-Door.wav
US-EN-Alexa-Motion-Detected-Generic.wav
US-EN-Alexa-Motion-In-Back-Yard.wav
US-EN-Alexa-Motion-In-Basement.wav
US-EN-Alexa-Motion-In-Front-Yard.wav
US-EN-Alexa-Motion-In-Garage.wav
US-EN-Alexa-Patio-Door-Opened.wav
US-EN-Alexa-Patio-Door-Unlocked.wav
US-EN-Alexa-Smoke-Detected-Generic.wav
US-EN-Alexa-Smoke-Detected-In-Basement.wav
US-EN-Alexa-Smoke-Detected-In-Garage.wav
US-EN-Alexa-Smoke-Detected-In-Kitchen.wav
US-EN-Alexa-Son-Is-Arriving.wav
US-EN-Alexa-Water-Detected-Generic.wav
US-EN-Alexa-Water-Detected-In-Basement.wav
US-EN-Alexa-Water-Detected-In-Garage.wav
US-EN-Alexa-Water-Detected-In-Kitchen.wav
US-EN-Alexa-Welcome-Home.wav
US-EN-Alexa-Wife-Is-Arriving.wav
US-EN-Morgan-Freeman-Back-Door-Closed.wav
US-EN-Morgan-Freeman-Back-Door-Locked.wav
US-EN-Morgan-Freeman-Back-Door-Opened.wav
US-EN-Morgan-Freeman-Back-Door-Unlocked.wav
US-EN-Morgan-Freeman-Basement-Door-Closed.wav
US-EN-Morgan-Freeman-Basement-Door-Locked.wav
US-EN-Morgan-Freeman-Basement-Door-Opened.wav
US-EN-Morgan-Freeman-Basement-Door-Unlocked.wav
US-EN-Morgan-Freeman-Boss-Is-Arriving.wav
US-EN-Morgan-Freeman-Boyfriend-Is-Arriving.wav
US-EN-Morgan-Freeman-Cleaning-Supplies-Closet-Opened.wav
US-EN-Morgan-Freeman-Coworker-Is-Arriving.wav
US-EN-Morgan-Freeman-Daughter-Is-Arriving.wav
US-EN-Morgan-Freeman-Friend-Is-Arriving.wav
US-EN-Morgan-Freeman-Front-Door-Closed.wav
US-EN-Morgan-Freeman-Front-Door-Locked.wav
US-EN-Morgan-Freeman-Front-Door-Opened.wav
US-EN-Morgan-Freeman-Front-Door-Unlocked.wav
US-EN-Morgan-Freeman-Garage-Door-Closed.wav
US-EN-Morgan-Freeman-Garage-Door-Opened.wav
US-EN-Morgan-Freeman-Girlfriend-Is-Arriving.wav
US-EN-Morgan-Freeman-Good-Morning.wav
US-EN-Morgan-Freeman-Good-Night.wav
US-EN-Morgan-Freeman-Liquor-Cabinet-Opened.wav
US-EN-Morgan-Freeman-Motion-Detected.wav
US-EN-Morgan-Freeman-Motion-In-Basement.wav
US-EN-Morgan-Freeman-Motion-In-Bedroom.wav
US-EN-Morgan-Freeman-Motion-In-Game-Room.wav
US-EN-Morgan-Freeman-Motion-In-Garage.wav
US-EN-Morgan-Freeman-Motion-In-Kitchen.wav
US-EN-Morgan-Freeman-Motion-In-Living-Room.wav
US-EN-Morgan-Freeman-Motion-In-Theater.wav
US-EN-Morgan-Freeman-Motion-In-Wine-Cellar.wav
US-EN-Morgan-Freeman-Patio-Door-Closed.wav
US-EN-Morgan-Freeman-Patio-Door-Locked.wav
US-EN-Morgan-Freeman-Patio-Door-Opened.wav
US-EN-Morgan-Freeman-Patio-Door-Unlocked.wav
US-EN-Morgan-Freeman-Roommate-Is-Arriving.wav
US-EN-Morgan-Freeman-Searching-For-Car-Keys.wav
US-EN-Morgan-Freeman-Setting-The-Mood.wav
US-EN-Morgan-Freeman-Smartthings-Detected-A-Flood.wav
US-EN-Morgan-Freeman-Smartthings-Detected-Carbon-Monoxide.wav
US-EN-Morgan-Freeman-Smartthings-Detected-Smoke.wav
US-EN-Morgan-Freeman-Smoke-Detected-In-Basement.wav
US-EN-Morgan-Freeman-Smoke-Detected-In-Garage.wav
US-EN-Morgan-Freeman-Smoke-Detected-In-Kitchen.wav
US-EN-Morgan-Freeman-Someone-Is-Arriving.wav
US-EN-Morgan-Freeman-Son-Is-Arriving.wav
US-EN-Morgan-Freeman-Starting-Movie-Mode.wav
US-EN-Morgan-Freeman-Starting-Party-Mode.wav
US-EN-Morgan-Freeman-Starting-Romance-Mode.wav
US-EN-Morgan-Freeman-Turning-Off-All-The-Lights.wav
US-EN-Morgan-Freeman-Turning-Off-The-Air-Conditioner.wav
US-EN-Morgan-Freeman-Turning-Off-The-Bar-Lights.wav
US-EN-Morgan-Freeman-Turning-Off-The-Chandelier.wav
US-EN-Morgan-Freeman-Turning-Off-The-Family-Room-Lights.wav
US-EN-Morgan-Freeman-Turning-Off-The-Hallway-Lights.wav
US-EN-Morgan-Freeman-Turning-Off-The-Kitchen-Light.wav
US-EN-Morgan-Freeman-Turning-Off-The-Light.wav
US-EN-Morgan-Freeman-Turning-Off-The-Lights.wav
US-EN-Morgan-Freeman-Turning-Off-The-Mood-Lights.wav
US-EN-Morgan-Freeman-Turning-Off-The-TV.wav
US-EN-Morgan-Freeman-Turning-On-The-Air-Conditioner.wav
US-EN-Morgan-Freeman-Turning-On-The-Bar-Lights.wav
US-EN-Morgan-Freeman-Turning-On-The-Chandelier.wav
US-EN-Morgan-Freeman-Turning-On-The-Family-Room-Lights.wav
US-EN-Morgan-Freeman-Turning-On-The-Hallway-Lights.wav
US-EN-Morgan-Freeman-Turning-On-The-Kitchen-Light.wav
US-EN-Morgan-Freeman-Turning-On-The-Light.wav
US-EN-Morgan-Freeman-Turning-On-The-Lights.wav
US-EN-Morgan-Freeman-Turning-On-The-Mood-Lights.wav
US-EN-Morgan-Freeman-Turning-On-The-TV.wav
US-EN-Morgan-Freeman-Vacate-The-Premises.wav
US-EN-Morgan-Freeman-Water-Detected-In-Basement.wav
US-EN-Morgan-Freeman-Water-Detected-In-Garage.wav
US-EN-Morgan-Freeman-Water-Detected-In-Kitchen.wav
US-EN-Morgan-Freeman-Welcome-Home.wav
US-EN-Morgan-Freeman-Wife-Is-Arriving.wav
"""
iOS platform for notify component. Should be put into ~/.homeassistant/custom_components/notify/ios.py.
(Yes, actually notify/ios.py, even though the filename on Gist is notify_ios.py).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.ios/
"""
import logging
import requests
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, ATTR_MESSAGE,
ATTR_DATA, DOMAIN, BaseNotificationService)
_LOGGER = logging.getLogger(__name__)
PUSH_URL = "https://mj28d17jwj.execute-api.us-west-2.amazonaws.com/prod/push"
# DEPENDENCIES = ["ios"]
def get_service(hass, config):
"""Get the iOS notification service."""
message = config.get(ATTR_MESSAGE)
target = config.get(ATTR_TARGET)
title = config.get(ATTR_TITLE)
return iOSNotificationService(message, title, target)
# pylint: disable=too-few-public-methods, too-many-arguments
class iOSNotificationService(BaseNotificationService):
"""Implement the notification service for iOS."""
def __init__(self, message, title, target):
"""Initialize the service."""
self._message = message
self._title = title
self._target = target
def send_message(self, message="", title=None, target=None, **kwargs):
"""Send a message to the Lambda APNS gateway."""
data = {
ATTR_MESSAGE: message,
ATTR_TARGET: target
}
"""Don't send the default title because
it makes Apple Watch notifications look bad"""
if title != ATTR_TITLE_DEFAULT:
data[ATTR_TITLE] = title
elif title == ATTR_TITLE_DEFAULT and self._title is not None:
data[ATTR_TITLE] = self._title
if target is None:
data[ATTR_TARGET] = self._target
if kwargs.get(ATTR_DATA) is not None:
data[ATTR_DATA] = kwargs.get(ATTR_DATA)
response = requests.put(PUSH_URL, json=data, timeout=10)
if response.status_code not in (200, 201):
_LOGGER.exception(
"Error sending message. Response %d: %s:",
response.status_code, response.reason)

Map

Will show a map with a red tipped pin at the coordinates given. The map will be centered at the coordinates given.

service: notify.iOSApp
data:
  message: Something happened at home!
  data:
    push:
      category: map
    action_data:
      latitude: 40.785091
      longitude: -73.968285

Camera Stream

The notification thumbnail will be a still image from the camera. The notification content is a real time stream of a camera.

You can use the attachment parameters content-type and hide-thumbnail with camera.

You can view an example here.

service: notify.iOSApp
data:
  message: Motion detected in the Living Room
  data:
    push:
      category: camera
    entity_id: camera.demo_camera

Attachment

The thumbnail of the notification will be the media at the URL. The notification content is the media at the URL.

Attachment can be used with custom push notification categories/actions.

service: notify.iOSApp
data:
  message: Something happened at home!
  data:
    attachment:
      url: https://67.media.tumblr.com/ab04c028a5244377a0ab96e73915e584/tumblr_nfn3ztLjxk1tq4of6o1_400.gif
      content-type: gif
      hide-thumbnail: false

Supported media types

If the attachment does not appear please ensure it is in one of the following formats:

Audio

Maximum file size: 5 MB

Formats:

  • AIFF
  • WAV
  • MP3
  • MPEG4 Audio

Image

Maximum file size: 10 MB

Formats:

  • JPEG
  • GIF
  • PNG

Video

Maximum file size: 50 MB

Formats:

  • MPEG
  • MPEG2
  • MPEG4
  • AVI

Configuration

You can pass the following keys to change the behavior of the attachment:

url

The URL of content to use as the attachment. This URL must be accessible from the Internet, or the receiving device must be on the same network as the hosted content.

content-type

By default, the extension of the URL will be checked to determine the filetype. If there is no extension/it can't be determined you can manually provide a file extension.

hide-thumbnail

If set to true the thumbnail will not show on the notification. The content will only be viewable by expanding.

@mowfqahmed
Copy link

Ha@633475

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment