Skip to content
3 changes: 2 additions & 1 deletion homeassistant/components/automation/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def call_action():
return

# If only state attributes changed, ignore this event
if from_s.last_changed == to_s.last_changed:
if (from_s is not None and to_s is not None and
from_s.last_changed == to_s.last_changed):
return

@callback
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/device_tracker/ubus.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
response = res.json()

if rpcmethod == "call":
return response["result"][1]
try:
return response["result"][1]
except IndexError:
return
else:
return response["result"]

Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/hassio.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import async_timeout

from homeassistant.const import CONTENT_TYPE_TEXT_PLAIN
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.components.frontend import register_built_in_panel

Expand Down Expand Up @@ -139,7 +139,7 @@ class HassIOView(HomeAssistantView):

name = "api:hassio"
url = "/api/hassio/{path:.+}"
requires_auth = True
requires_auth = False

def __init__(self, hassio):
"""Initialize a hassio base view."""
Expand All @@ -148,6 +148,9 @@ def __init__(self, hassio):
@asyncio.coroutine
def _handle(self, request, path):
"""Route data to hassio."""
if path != 'panel' and not request[KEY_AUTHENTICATED]:
return web.Response(status=401)

client = yield from self.hassio.command_proxy(path, request)

data = yield from client.read()
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/media_player/volumio.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ def async_media_play(self):

def async_media_pause(self):
"""Send media_pause command to media player."""
if self._state['trackType'] == 'webradio':
return self.send_volumio_msg('commands', params={'cmd': 'stop'})
return self.send_volumio_msg('commands', params={'cmd': 'pause'})

def async_set_volume_level(self, volume):
Expand Down
19 changes: 15 additions & 4 deletions homeassistant/components/notify/html5.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def get_service(hass, config, discovery_info=None):
add_manifest_json_key(
ATTR_GCM_SENDER_ID, config.get(ATTR_GCM_SENDER_ID))

return HTML5NotificationService(gcm_api_key, registrations)
return HTML5NotificationService(gcm_api_key, registrations, json_path)


def _load_config(filename):
Expand Down Expand Up @@ -327,10 +327,11 @@ def post(self, request):
class HTML5NotificationService(BaseNotificationService):
"""Implement the notification service for HTML5."""

def __init__(self, gcm_key, registrations):
def __init__(self, gcm_key, registrations, json_path):
"""Initialize the service."""
self._gcm_key = gcm_key
self.registrations = registrations
self.registrations_json_path = json_path

@property
def targets(self):
Expand Down Expand Up @@ -383,7 +384,7 @@ def send_message(self, message="", **kwargs):
if not targets:
targets = self.registrations.keys()

for target in targets:
for target in list(targets):
info = self.registrations.get(target)
if info is None:
_LOGGER.error("%s is not a valid HTML5 push notification"
Expand All @@ -399,5 +400,15 @@ def send_message(self, message="", **kwargs):
jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8')
payload[ATTR_DATA][ATTR_JWT] = jwt_token

WebPusher(info[ATTR_SUBSCRIPTION]).send(
response = WebPusher(info[ATTR_SUBSCRIPTION]).send(
json.dumps(payload), gcm_key=self._gcm_key, ttl='86400')

if (response.status_code == 410):
_LOGGER.info("Notification channel has expired")
reg = self.registrations.pop(target)
if not _save_config(self.registrations_json_path,
self.registrations):
self.registrations[target] = reg
_LOGGER.error("Error saving registration.")
else:
_LOGGER.info("Configuration saved")
3 changes: 1 addition & 2 deletions homeassistant/components/notify/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.notify import (
ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, ATTR_TARGET,
PLATFORM_SCHEMA, BaseNotificationService)
Expand All @@ -27,7 +26,7 @@
CONF_CHAT_ID = 'chat_id'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CHAT_ID): cv.positive_int,
vol.Required(CONF_CHAT_ID): vol.Coerce(int),
})


Expand Down
97 changes: 58 additions & 39 deletions homeassistant/components/telegram_bot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
ATTR_USER_ID = 'user_id'
ATTR_ARGS = 'args'
ATTR_MSG = 'message'
ATTR_EDITED_MSG = 'edited_message'
ATTR_CHAT_INSTANCE = 'chat_instance'
ATTR_CHAT_ID = 'chat_id'
ATTR_MSGID = 'id'
Expand Down Expand Up @@ -76,15 +77,15 @@
vol.Required(CONF_PLATFORM): cv.string,
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_ALLOWED_CHAT_IDS):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(ATTR_PARSER, default=PARSER_MD): cv.string,
vol.Optional(CONF_TRUSTED_NETWORKS, default=DEFAULT_TRUSTED_NETWORKS):
vol.All(cv.ensure_list, [ip_network])
})
}, extra=vol.ALLOW_EXTRA)

BASE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [vol.Coerce(int)]),
vol.Optional(ATTR_PARSER): cv.string,
vol.Optional(ATTR_DISABLE_NOTIF): cv.boolean,
vol.Optional(ATTR_DISABLE_WEB_PREV): cv.boolean,
Expand Down Expand Up @@ -113,19 +114,19 @@
SERVICE_EDIT_MESSAGE = 'edit_message'
SERVICE_SCHEMA_EDIT_MESSAGE = SERVICE_SCHEMA_SEND_MESSAGE.extend({
vol.Required(ATTR_MESSAGEID): vol.Any(cv.positive_int, cv.string),
vol.Required(ATTR_CHAT_ID): cv.positive_int,
vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
})
SERVICE_EDIT_CAPTION = 'edit_caption'
SERVICE_SCHEMA_EDIT_CAPTION = vol.Schema({
vol.Required(ATTR_MESSAGEID): vol.Any(cv.positive_int, cv.string),
vol.Required(ATTR_CHAT_ID): cv.positive_int,
vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
vol.Required(ATTR_CAPTION): cv.string,
vol.Optional(ATTR_KEYBOARD_INLINE): cv.ensure_list,
}, extra=vol.ALLOW_EXTRA)
SERVICE_EDIT_REPLYMARKUP = 'edit_replymarkup'
SERVICE_SCHEMA_EDIT_REPLYMARKUP = vol.Schema({
vol.Required(ATTR_MESSAGEID): vol.Any(cv.positive_int, cv.string),
vol.Required(ATTR_CHAT_ID): cv.positive_int,
vol.Required(ATTR_CHAT_ID): vol.Coerce(int),
vol.Required(ATTR_KEYBOARD_INLINE): cv.ensure_list,
}, extra=vol.ALLOW_EXTRA)
SERVICE_ANSWER_CALLBACK_QUERY = 'answer_callback_query'
Expand Down Expand Up @@ -198,7 +199,7 @@ def async_setup_platform(p_type, p_config=None, discovery_info=None):
return

except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up platform %s', p_type)
_LOGGER.exception("Error setting up platform %s", p_type)
return

notify_service = TelegramNotificationService(
Expand All @@ -221,7 +222,7 @@ def _render_template_attr(data, attribute):
kwargs = dict(service.data)
_render_template_attr(kwargs, ATTR_MESSAGE)
_render_template_attr(kwargs, ATTR_TITLE)
_LOGGER.debug('NEW telegram_message "%s": %s', msgtype, kwargs)
_LOGGER.debug("NEW telegram_message %s: %s", msgtype, kwargs)

if msgtype == SERVICE_SEND_MESSAGE:
yield from hass.async_add_job(
Expand Down Expand Up @@ -300,17 +301,17 @@ def _get_target_chat_ids(self, target):
if isinstance(target, int):
if target in self.allowed_chat_ids:
return [target]
_LOGGER.warning('BAD TARGET "%s", using default: %s',
_LOGGER.warning("BAD TARGET %s, using default: %s",
target, self._default_user)
else:
try:
chat_ids = [int(t) for t in target
if int(t) in self.allowed_chat_ids]
if len(chat_ids) > 0:
return chat_ids
_LOGGER.warning('ALL BAD TARGETS: "%s"', target)
_LOGGER.warning("ALL BAD TARGETS: %s", target)
except (ValueError, TypeError):
_LOGGER.warning('BAD TARGET DATA "%s", using default: %s',
_LOGGER.warning("BAD TARGET DATA %s, using default: %s",
target, self._default_user)
return [self._default_user]

Expand Down Expand Up @@ -378,10 +379,10 @@ def _send_msg(self, func_send, msg_error, *args_rep, **kwargs_rep):
if not isinstance(out, bool) and hasattr(out, ATTR_MESSAGEID):
chat_id = out.chat_id
self._last_message_id[chat_id] = out[ATTR_MESSAGEID]
_LOGGER.debug('LAST MSG ID: %s (from chat_id %s)',
_LOGGER.debug("LAST MSG ID: %s (from chat_id %s)",
self._last_message_id, chat_id)
elif not isinstance(out, bool):
_LOGGER.warning('UPDATE LAST MSG??: out_type:%s, out=%s',
_LOGGER.warning("UPDATE LAST MSG??: out_type:%s, out=%s",
type(out), out)
return out
except TelegramError:
Expand All @@ -393,7 +394,7 @@ def send_message(self, message="", target=None, **kwargs):
text = '{}\n{}'.format(title, message) if title else message
params = self._get_msg_kwargs(kwargs)
for chat_id in self._get_target_chat_ids(target):
_LOGGER.debug('send_message in chat_id %s with params: %s',
_LOGGER.debug("send_message in chat_id %s with params: %s",
chat_id, params)
self._send_msg(self.bot.sendMessage,
"Error sending message",
Expand All @@ -404,13 +405,13 @@ def edit_message(self, type_edit, chat_id=None, **kwargs):
chat_id = self._get_target_chat_ids(chat_id)[0]
message_id, inline_message_id = self._get_msg_ids(kwargs, chat_id)
params = self._get_msg_kwargs(kwargs)
_LOGGER.debug('edit_message %s in chat_id %s with params: %s',
_LOGGER.debug("edit_message %s in chat_id %s with params: %s",
message_id or inline_message_id, chat_id, params)
if type_edit == SERVICE_EDIT_MESSAGE:
message = kwargs.get(ATTR_MESSAGE)
title = kwargs.get(ATTR_TITLE)
text = '{}\n{}'.format(title, message) if title else message
_LOGGER.debug('editing message w/id %s.',
_LOGGER.debug("editing message w/id %s.",
message_id or inline_message_id)
return self._send_msg(self.bot.editMessageText,
"Error editing text message",
Expand All @@ -432,7 +433,7 @@ def answer_callback_query(self, message, callback_query_id,
show_alert=False, **kwargs):
"""Answer a callback originated with a press in an inline keyboard."""
params = self._get_msg_kwargs(kwargs)
_LOGGER.debug('answer_callback_query w/callback_id %s: %s, alert: %s.',
_LOGGER.debug("answer_callback_query w/callback_id %s: %s, alert: %s.",
callback_query_id, message, show_alert)
self._send_msg(self.bot.answerCallbackQuery,
"Error sending answer callback query",
Expand All @@ -451,7 +452,7 @@ def send_file(self, is_photo=True, target=None, **kwargs):
caption = kwargs.get(ATTR_CAPTION)
func_send = self.bot.sendPhoto if is_photo else self.bot.sendDocument
for chat_id in self._get_target_chat_ids(target):
_LOGGER.debug('send file %s to chat_id %s. Caption: %s.',
_LOGGER.debug("send file %s to chat_id %s. Caption: %s.",
file, chat_id, caption)
self._send_msg(func_send, "Error sending file",
chat_id, file, caption=caption, **params)
Expand All @@ -462,7 +463,7 @@ def send_location(self, latitude, longitude, target=None, **kwargs):
longitude = float(longitude)
params = self._get_msg_kwargs(kwargs)
for chat_id in self._get_target_chat_ids(target):
_LOGGER.debug('send location %s/%s to chat_id %s.',
_LOGGER.debug("send location %s/%s to chat_id %s.",
latitude, longitude, chat_id)
self._send_msg(self.bot.sendLocation,
"Error sending location",
Expand All @@ -479,46 +480,64 @@ def __init__(self, hass, allowed_chat_ids):
self.hass = hass

def _get_message_data(self, msg_data):
if (not msg_data or
('text' not in msg_data and 'data' not in msg_data) or
'from' not in msg_data or
msg_data['from'].get('id') not in self.allowed_chat_ids):
"""Return boolean msg_data_is_ok and dict msg_data."""
if not msg_data:
return False, None
bad_fields = ('text' not in msg_data and
'data' not in msg_data and
'chat' not in msg_data)
if bad_fields or 'from' not in msg_data:
# Message is not correct.
_LOGGER.error("Incoming message does not have required data (%s)",
msg_data)
return None

return {
return False, None
if msg_data['from'].get('id') not in self.allowed_chat_ids \
or msg_data['chat'].get('id') not in self.allowed_chat_ids:
# Origin is not allowed.
_LOGGER.error("Incoming message is not allowed (%s)", msg_data)
return True, None

return True, {
ATTR_USER_ID: msg_data['from']['id'],
ATTR_CHAT_ID: msg_data['chat']['id'],
ATTR_FROM_FIRST: msg_data['from']['first_name'],
ATTR_FROM_LAST: msg_data['from']['last_name']
}

def process_message(self, data):
"""Check for basic message rules and fire an event if message is ok."""
if ATTR_MSG in data:
if ATTR_MSG in data or ATTR_EDITED_MSG in data:
event = EVENT_TELEGRAM_COMMAND
data = data.get(ATTR_MSG)
event_data = self._get_message_data(data)
if ATTR_MSG in data:
data = data.get(ATTR_MSG)
else:
data = data.get(ATTR_EDITED_MSG)
message_ok, event_data = self._get_message_data(data)
if event_data is None:
return False

if data[ATTR_TEXT][0] == '/':
pieces = data[ATTR_TEXT].split(' ')
event_data[ATTR_COMMAND] = pieces[0]
event_data[ATTR_ARGS] = pieces[1:]
return message_ok

if 'text' in data:
if data['text'][0] == '/':
pieces = data['text'].split(' ')
event_data[ATTR_COMMAND] = pieces[0]
event_data[ATTR_ARGS] = pieces[1:]
else:
event_data[ATTR_TEXT] = data['text']
event = EVENT_TELEGRAM_TEXT
else:
event_data[ATTR_TEXT] = data[ATTR_TEXT]
# Some other thing...
_LOGGER.warning("Message without text data received: %s", data)
event_data[ATTR_TEXT] = str(data)
event = EVENT_TELEGRAM_TEXT

self.hass.bus.async_fire(event, event_data)
return True
elif ATTR_CALLBACK_QUERY in data:
event = EVENT_TELEGRAM_CALLBACK
data = data.get(ATTR_CALLBACK_QUERY)
event_data = self._get_message_data(data)
message_ok, event_data = self._get_message_data(data)
if event_data is None:
return False
return message_ok

event_data[ATTR_DATA] = data[ATTR_DATA]
event_data[ATTR_MSG] = data[ATTR_MSG]
Expand All @@ -529,5 +548,5 @@ def process_message(self, data):
return True
else:
# Some other thing...
_LOGGER.warning('SOME OTHER THING RECEIVED --> "%s"', data)
return False
_LOGGER.warning("SOME OTHER THING RECEIVED --> %s", data)
return True
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 45
PATCH_VERSION = '0'
PATCH_VERSION = '1'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 4, 2)
Expand Down
Loading