mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Notifications for Android TV: Add fontsize and sending images (#16565)
* Add fontsize and image functionality * woof
This commit is contained in:
parent
34d369ba26
commit
601f2df5a7
@ -9,6 +9,8 @@ import io
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
from requests.auth import HTTPDigestAuth
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
@ -21,12 +23,14 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONF_IP = 'host'
|
CONF_IP = 'host'
|
||||||
CONF_DURATION = 'duration'
|
CONF_DURATION = 'duration'
|
||||||
|
CONF_FONTSIZE = 'fontsize'
|
||||||
CONF_POSITION = 'position'
|
CONF_POSITION = 'position'
|
||||||
CONF_TRANSPARENCY = 'transparency'
|
CONF_TRANSPARENCY = 'transparency'
|
||||||
CONF_COLOR = 'color'
|
CONF_COLOR = 'color'
|
||||||
CONF_INTERRUPT = 'interrupt'
|
CONF_INTERRUPT = 'interrupt'
|
||||||
|
|
||||||
DEFAULT_DURATION = 5
|
DEFAULT_DURATION = 5
|
||||||
|
DEFAULT_FONTSIZE = 'medium'
|
||||||
DEFAULT_POSITION = 'bottom-right'
|
DEFAULT_POSITION = 'bottom-right'
|
||||||
DEFAULT_TRANSPARENCY = 'default'
|
DEFAULT_TRANSPARENCY = 'default'
|
||||||
DEFAULT_COLOR = 'grey'
|
DEFAULT_COLOR = 'grey'
|
||||||
@ -37,11 +41,29 @@ DEFAULT_ICON = (
|
|||||||
'cMXEAAAAASUVORK5CYII=')
|
'cMXEAAAAASUVORK5CYII=')
|
||||||
|
|
||||||
ATTR_DURATION = 'duration'
|
ATTR_DURATION = 'duration'
|
||||||
|
ATTR_FONTSIZE = 'fontsize'
|
||||||
ATTR_POSITION = 'position'
|
ATTR_POSITION = 'position'
|
||||||
ATTR_TRANSPARENCY = 'transparency'
|
ATTR_TRANSPARENCY = 'transparency'
|
||||||
ATTR_COLOR = 'color'
|
ATTR_COLOR = 'color'
|
||||||
ATTR_BKGCOLOR = 'bkgcolor'
|
ATTR_BKGCOLOR = 'bkgcolor'
|
||||||
ATTR_INTERRUPT = 'interrupt'
|
ATTR_INTERRUPT = 'interrupt'
|
||||||
|
ATTR_IMAGE = 'filename2'
|
||||||
|
ATTR_FILE = 'file'
|
||||||
|
# Attributes contained in file
|
||||||
|
ATTR_FILE_URL = 'url'
|
||||||
|
ATTR_FILE_PATH = 'path'
|
||||||
|
ATTR_FILE_USERNAME = 'username'
|
||||||
|
ATTR_FILE_PASSWORD = 'password'
|
||||||
|
ATTR_FILE_AUTH = 'auth'
|
||||||
|
# Any other value or absence of 'auth' lead to basic authentication being used
|
||||||
|
ATTR_FILE_AUTH_DIGEST = 'digest'
|
||||||
|
|
||||||
|
FONTSIZES = {
|
||||||
|
'small': 1,
|
||||||
|
'medium': 0,
|
||||||
|
'large': 2,
|
||||||
|
'max': 3
|
||||||
|
}
|
||||||
|
|
||||||
POSITIONS = {
|
POSITIONS = {
|
||||||
'bottom-right': 0,
|
'bottom-right': 0,
|
||||||
@ -75,6 +97,8 @@ COLORS = {
|
|||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_IP): cv.string,
|
vol.Required(CONF_IP): cv.string,
|
||||||
vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.Coerce(int),
|
vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.Coerce(int),
|
||||||
|
vol.Optional(CONF_FONTSIZE, default=DEFAULT_FONTSIZE):
|
||||||
|
vol.In(FONTSIZES.keys()),
|
||||||
vol.Optional(CONF_POSITION, default=DEFAULT_POSITION):
|
vol.Optional(CONF_POSITION, default=DEFAULT_POSITION):
|
||||||
vol.In(POSITIONS.keys()),
|
vol.In(POSITIONS.keys()),
|
||||||
vol.Optional(CONF_TRANSPARENCY, default=DEFAULT_TRANSPARENCY):
|
vol.Optional(CONF_TRANSPARENCY, default=DEFAULT_TRANSPARENCY):
|
||||||
@ -90,6 +114,7 @@ def get_service(hass, config, discovery_info=None):
|
|||||||
"""Get the Notifications for Android TV notification service."""
|
"""Get the Notifications for Android TV notification service."""
|
||||||
remoteip = config.get(CONF_IP)
|
remoteip = config.get(CONF_IP)
|
||||||
duration = config.get(CONF_DURATION)
|
duration = config.get(CONF_DURATION)
|
||||||
|
fontsize = config.get(CONF_FONTSIZE)
|
||||||
position = config.get(CONF_POSITION)
|
position = config.get(CONF_POSITION)
|
||||||
transparency = config.get(CONF_TRANSPARENCY)
|
transparency = config.get(CONF_TRANSPARENCY)
|
||||||
color = config.get(CONF_COLOR)
|
color = config.get(CONF_COLOR)
|
||||||
@ -97,23 +122,26 @@ def get_service(hass, config, discovery_info=None):
|
|||||||
timeout = config.get(CONF_TIMEOUT)
|
timeout = config.get(CONF_TIMEOUT)
|
||||||
|
|
||||||
return NFAndroidTVNotificationService(
|
return NFAndroidTVNotificationService(
|
||||||
remoteip, duration, position, transparency, color, interrupt, timeout)
|
remoteip, duration, fontsize, position,
|
||||||
|
transparency, color, interrupt, timeout, hass.config.is_allowed_path)
|
||||||
|
|
||||||
|
|
||||||
class NFAndroidTVNotificationService(BaseNotificationService):
|
class NFAndroidTVNotificationService(BaseNotificationService):
|
||||||
"""Notification service for Notifications for Android TV."""
|
"""Notification service for Notifications for Android TV."""
|
||||||
|
|
||||||
def __init__(self, remoteip, duration, position, transparency, color,
|
def __init__(self, remoteip, duration, fontsize, position, transparency,
|
||||||
interrupt, timeout):
|
color, interrupt, timeout, is_allowed_path):
|
||||||
"""Initialize the service."""
|
"""Initialize the service."""
|
||||||
self._target = 'http://{}:7676'.format(remoteip)
|
self._target = 'http://{}:7676'.format(remoteip)
|
||||||
self._default_duration = duration
|
self._default_duration = duration
|
||||||
|
self._default_fontsize = fontsize
|
||||||
self._default_position = position
|
self._default_position = position
|
||||||
self._default_transparency = transparency
|
self._default_transparency = transparency
|
||||||
self._default_color = color
|
self._default_color = color
|
||||||
self._default_interrupt = interrupt
|
self._default_interrupt = interrupt
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._icon_file = io.BytesIO(base64.b64decode(DEFAULT_ICON))
|
self._icon_file = io.BytesIO(base64.b64decode(DEFAULT_ICON))
|
||||||
|
self.is_allowed_path = is_allowed_path
|
||||||
|
|
||||||
def send_message(self, message="", **kwargs):
|
def send_message(self, message="", **kwargs):
|
||||||
"""Send a message to a Android TV device."""
|
"""Send a message to a Android TV device."""
|
||||||
@ -123,7 +151,8 @@ class NFAndroidTVNotificationService(BaseNotificationService):
|
|||||||
'application/octet-stream',
|
'application/octet-stream',
|
||||||
{'Expires': '0'}), type='0',
|
{'Expires': '0'}), type='0',
|
||||||
title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT),
|
title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT),
|
||||||
msg=message, duration="%i" % self._default_duration,
|
msg=message, duration='%i' % self._default_duration,
|
||||||
|
fontsize='%i' % FONTSIZES.get(self._default_fontsize),
|
||||||
position='%i' % POSITIONS.get(self._default_position),
|
position='%i' % POSITIONS.get(self._default_position),
|
||||||
bkgcolor='%s' % COLORS.get(self._default_color),
|
bkgcolor='%s' % COLORS.get(self._default_color),
|
||||||
transparency='%i' % TRANSPARENCIES.get(
|
transparency='%i' % TRANSPARENCIES.get(
|
||||||
@ -140,6 +169,13 @@ class NFAndroidTVNotificationService(BaseNotificationService):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.warning("Invalid duration-value: %s",
|
_LOGGER.warning("Invalid duration-value: %s",
|
||||||
str(duration))
|
str(duration))
|
||||||
|
if ATTR_FONTSIZE in data:
|
||||||
|
fontsize = data.get(ATTR_FONTSIZE)
|
||||||
|
if fontsize in FONTSIZES:
|
||||||
|
payload[ATTR_FONTSIZE] = '%i' % FONTSIZES.get(fontsize)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Invalid fontsize-value: %s",
|
||||||
|
str(fontsize))
|
||||||
if ATTR_POSITION in data:
|
if ATTR_POSITION in data:
|
||||||
position = data.get(ATTR_POSITION)
|
position = data.get(ATTR_POSITION)
|
||||||
if position in POSITIONS:
|
if position in POSITIONS:
|
||||||
@ -168,6 +204,19 @@ class NFAndroidTVNotificationService(BaseNotificationService):
|
|||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
_LOGGER.warning("Invalid interrupt-value: %s",
|
_LOGGER.warning("Invalid interrupt-value: %s",
|
||||||
str(interrupt))
|
str(interrupt))
|
||||||
|
filedata = data.get(ATTR_FILE) if data else None
|
||||||
|
if filedata is not None:
|
||||||
|
# Load from file or URL
|
||||||
|
file_as_bytes = self.load_file(
|
||||||
|
url=filedata.get(ATTR_FILE_URL),
|
||||||
|
local_path=filedata.get(ATTR_FILE_PATH),
|
||||||
|
username=filedata.get(ATTR_FILE_USERNAME),
|
||||||
|
password=filedata.get(ATTR_FILE_PASSWORD),
|
||||||
|
auth=filedata.get(ATTR_FILE_AUTH))
|
||||||
|
if file_as_bytes:
|
||||||
|
payload[ATTR_IMAGE] = (
|
||||||
|
'image', file_as_bytes,
|
||||||
|
'application/octet-stream', {'Expires': '0'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("Payload: %s", str(payload))
|
_LOGGER.debug("Payload: %s", str(payload))
|
||||||
@ -178,3 +227,37 @@ class NFAndroidTVNotificationService(BaseNotificationService):
|
|||||||
except requests.exceptions.ConnectionError as err:
|
except requests.exceptions.ConnectionError as err:
|
||||||
_LOGGER.error("Error communicating with %s: %s",
|
_LOGGER.error("Error communicating with %s: %s",
|
||||||
self._target, str(err))
|
self._target, str(err))
|
||||||
|
|
||||||
|
def load_file(self, url=None, local_path=None, username=None,
|
||||||
|
password=None, auth=None):
|
||||||
|
"""Load image/document/etc from a local path or URL."""
|
||||||
|
try:
|
||||||
|
if url is not None:
|
||||||
|
# Check whether authentication parameters are provided
|
||||||
|
if username is not None and password is not None:
|
||||||
|
# Use digest or basic authentication
|
||||||
|
if ATTR_FILE_AUTH_DIGEST == auth:
|
||||||
|
auth_ = HTTPDigestAuth(username, password)
|
||||||
|
else:
|
||||||
|
auth_ = HTTPBasicAuth(username, password)
|
||||||
|
# Load file from URL with authentication
|
||||||
|
req = requests.get(
|
||||||
|
url, auth=auth_, timeout=DEFAULT_TIMEOUT)
|
||||||
|
else:
|
||||||
|
# Load file from URL without authentication
|
||||||
|
req = requests.get(url, timeout=DEFAULT_TIMEOUT)
|
||||||
|
return req.content
|
||||||
|
|
||||||
|
elif local_path is not None:
|
||||||
|
# Check whether path is whitelisted in configuration.yaml
|
||||||
|
if self.is_allowed_path(local_path):
|
||||||
|
return open(local_path, "rb")
|
||||||
|
_LOGGER.warning("'%s' is not secure to load data from!",
|
||||||
|
local_path)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Neither URL nor local path found in params!")
|
||||||
|
|
||||||
|
except OSError as error:
|
||||||
|
_LOGGER.error("Can't load from url or local path: %s", error)
|
||||||
|
|
||||||
|
return None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user