Skip to content

Commit

Permalink
Update to 1.2.2 (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
alandtse authored Mar 17, 2019
2 parents 8dbd8f5 + 04ab7db commit bd48da1
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 42 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "alexapy"]
path = alexapy
url = https://gitlab.com/alandtse/alexapy.git
65 changes: 50 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[Alexa Media Player Custom Component](https://github.com/keatontaylor/custom_components) for homeassistant
[Alexa Media Player Custom Component](https://github.com/keatontaylor/alexa_media_player) for homeassistant

# What This Is:
This is a custom component to allow control of Amazon Alexa devices in [Homeassistant](https://home-assistant.io) using the unofficial Alexa API. Please note this mimics the Alexa app but Amazon may cut off access at anytime.
Expand All @@ -16,31 +16,66 @@ Allows for control of Amazon Echo products as home assistant media devices with
* Album Image

# Notable Additional Features
## Text-to-Speech
Can be invoked from the HA UI services menu. media_player.alexa_tts and requires a payload like this:

```json
{"entity_id": "media_player.bedroom_echo_dot", "message": "Test message"}
```
## Play Music
We can basically do anything a Alexa [Routine](https://www.amazon.com/gp/help/customer/display.html?nodeId=G202200080) can do. You'll have to [discover specifics](https://github.com/keatontaylor/alexa_media_player/wiki/Sequence-Discovery), but here are some examples (and please help add them below!).
To play music using the `media_player.play_media` service, you have to define the media_content_type appropriately. Search the [forum](https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639/2055) for other examples.

**The Home-Asistant Media_Player UI does not work!**
## Text-to-Speech
For version 1.2.0 and above, can be provided via the [Notification Component](https://github.com/keatontaylor/alexa_media_player/wiki/Notification-Component) using `TTS` or `Announce`.

### Known working languages:
* US English
* [Italian](https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639/1334)
* [Mexican Spanish](https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639/1431)
**The Media_Player UI will not work!**

## Online status of devices
Additional attribute to tell you if the Alexa device is online (extremely useful if you want to send a TTS after one has come back online (such as one in a vehicle)

## Last called device
## Last called device (versions >= 0.10.0)
Each device will report whether it is the `last_called` or not. This allows us to identify the device that was called according to the Alexa Activities API.

## Sequence commands (versions >= 1.0.0)
Alexa accepts certain pre-defined sequences and this is what provides TTS and play_media. This is now exposed through the `media_player.play_media service` when the `media_content_type` is set to `sequence`

Supported sequences (may be region specific):
* Alexa.Weather.Play
* Alexa.Traffic.Play
* Alexa.FlashBriefing.Play
* Alexa.GoodMorning.Play
* Alexa.GoodNight.Play
* Alexa.SingASong.Play
* Alexa.TellStory.Play
* Alexa.FunFact.Play
* Alexa.Joke.Play
* Alexa.Music.PlaySearchPhrase
* Alexa.Calendar.PlayTomorrow
* Alexa.Calendar.PlayToday
* Alexa.Calendar.PlayNext
* Alexa.CleanUp.Play
* Alexa.ImHome.Play

## Automation routines (versions >= 1.0.0)
Running Alexa automation routines is now supported. Routines are tasks you can trigger through the Alexa App.
Please create them using the Alexa [app](https://www.amazon.com/gp/help/customer/display.html?nodeId=G202200080) and ensure they are **enabled**. This is now exposed through the media_player.play_media service when the `media_content_type` is set to `routine`

## Custom_updater (versions >= 1.1.0)
We now support [custom_updater](https://github.com/custom-components/custom_updater).

Add this to your configuration:
```yaml
custom_updater:
component_urls:
# Dev build (unstable)
- https://raw.githubusercontent.com/keatontaylor/alexa_media_player/next/custom_components.json
# Released build
- https://raw.githubusercontent.com/keatontaylor/alexa_media_player/master/custom_components.json
```
## Notification service (versions >= 1.2.0)
Please see [Notification Component](https://github.com/keatontaylor/alexa_media_player/wiki/Notification-Component).
# Further Documentation
Please see the [wiki](https://github.com/keatontaylor/custom_components/wiki)
Please see the [wiki](https://github.com/keatontaylor/alexa_media_player/wiki)
# Changelog
Use the commit history but we try to maintain this [wiki](https://github.com/keatontaylor/custom_components/wiki/Changelog).
Use the commit history but we try to maintain this [wiki](https://github.com/keatontaylor/alexa_media_player/wiki/Changelog).
# License
[Apache-2.0](LICENSE). By providing a contribution, you agree the contribution is licensed under Apache-2.0. This is required for Home Assistant contributions.
61 changes: 57 additions & 4 deletions alexa_media/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

# from .config_flow import configured_instances

REQUIREMENTS = ['alexapy==0.3.0']
REQUIREMENTS = ['alexapy==0.4.0']

__version__ = '1.2.1'
__version__ = '1.2.2'

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -309,14 +309,17 @@ def update_devices():
# Process last_called data to fire events
update_last_called(login_obj)

def update_last_called(login_obj):
def update_last_called(login_obj, last_called=None):
"""Update the last called device for the login_obj.
This will store the last_called in hass.data and also fire an event
to notify listeners.
"""
from alexapy import AlexaAPI
last_called = AlexaAPI.get_last_device_serial(login_obj)
if last_called:
last_called = last_called
else:
last_called = AlexaAPI.get_last_device_serial(login_obj)
_LOGGER.debug("%s: Updated last_called: %s",
hide_email(email),
hide_serial(last_called))
Expand Down Expand Up @@ -353,10 +356,60 @@ def last_call_handler(call):
login_obj = account_dict['login_obj']
update_last_called(login_obj)

def ws_handler(message_obj):
"""Handle websocket messages.
This allows push notifications from Alexa to update last_called
and media state.
"""
command = (message_obj.json_payload['command']
if isinstance(message_obj.json_payload, dict) and
'command' in message_obj.json_payload
else None)
json_payload = (message_obj.json_payload['payload']
if isinstance(message_obj.json_payload, dict) and
'payload' in message_obj.json_payload
else None)
if command and json_payload:
_LOGGER.debug("%s: Received websocket command: %s : %s",
hide_email(email),
command, json_payload)
if command == 'PUSH_ACTIVITY':
# Last_Alexa Updated
last_called = {
'serialNumber': (json_payload
['key']
['entryId']).split('#')[2],
'timestamp': json_payload['timestamp']
}
update_last_called(login_obj, last_called)
elif command == 'PUSH_AUDIO_PLAYER_STATE':
# Player update
_LOGGER.debug("Updating media_player: %s", json_payload)
hass.bus.fire(('{}_{}'.format(DOMAIN,
hide_email(email)))[0:32],
{'player_state': json_payload})
elif command == 'PUSH_VOLUME_CHANGE':
# Player volume update
_LOGGER.debug("Updating media_player volume: %s", json_payload)
hass.bus.fire(('{}_{}'.format(DOMAIN,
hide_email(email)))[0:32],
{'player_state': json_payload})

include = config.get(CONF_INCLUDE_DEVICES)
exclude = config.get(CONF_EXCLUDE_DEVICES)
scan_interval = config.get(CONF_SCAN_INTERVAL)
email = login_obj.email
from alexapy import WebsocketEchoClient
try:
websocket = WebsocketEchoClient(login_obj, ws_handler)
_LOGGER.debug("%s: Websocket created: %s", hide_email(email),
websocket)
except BaseException as exception_:
_LOGGER.exception("%s: Websocket failed: %s", hide_email(email),
exception_)
websocket = None
(hass.data[DOMAIN]['accounts'][email]['websocket']) = websocket
(hass.data[DOMAIN]['accounts'][email]['login_obj']) = login_obj
(hass.data[DOMAIN]['accounts'][email]['devices']) = {'media_player': {}}
(hass.data[DOMAIN]['accounts'][email]['entities']) = {'media_player': {}}
Expand Down
81 changes: 63 additions & 18 deletions alexa_media/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,48 @@ def __init__(self, device, login, hass):
def _handle_event(self, event):
"""Handle events.
Each MediaClient reports if it's the last_called MediaClient. All
devices on account update to handle starting music with other Alexas.
This will update last_called and player_state events.
Each MediaClient reports if it's the last_called MediaClient and will
listen for HA events to determine it is the last_called.
When polling instead of websockets, all devices on same account will
update to handle starting music with other devices. If websocket is on
only the updated alexa will update.
Last_called events are only sent if it's a new device or timestamp.
Without polling, we must schedule the HA update manually.
https://developers.home-assistant.io/docs/en/entity_index.html#subscribing-to-updates
The difference between self.update and self.schedule_update_ha_state
is self.update will pull data from Amazon, while schedule_update
assumes the MediaClient state is already updated.
"""
if (event.data['last_called_change']['serialNumber'] ==
self.device_serial_number):
_LOGGER.debug("%s is last_called: %s", self.name,
hide_serial(self.device_serial_number))
self._last_called = True
self._last_called = False
# Without polling, we must schedule the HA update.
# https://developers.home-assistant.io/docs/en/entity_index.html#subscribing-to-updates
self.schedule_update_ha_state(force_refresh=True)
if 'last_called_change' in event.data:
if (event.data['last_called_change']['serialNumber'] ==
self.device_serial_number):
_LOGGER.debug("%s is last_called: %s", self.name,
hide_serial(self.device_serial_number))
self._last_called = True
else:
self._last_called = False
if (self.hass and self.schedule_update_ha_state):
email = self._login.email
force_refresh = not (self.hass.data[DATA_ALEXAMEDIA]
['accounts'][email]['websocket'])
self.schedule_update_ha_state(force_refresh=force_refresh)
elif 'player_state' in event.data:
player_state = event.data['player_state']
if (player_state['dopplerId']
['deviceSerialNumber'] == self.device_serial_number):
if 'audioPlayerState' in player_state:
_LOGGER.debug("%s state update: %s",
self.name,
player_state['audioPlayerState'])
self.update() # refresh is necessary to pull all data
elif 'volumeSetting' in player_state:
_LOGGER.debug("%s volume updated: %s",
self.name,
player_state['volumeSetting'])
self._media_vol_level = player_state['volumeSetting']/100
if (self.hass and self.schedule_update_ha_state):
self.schedule_update_ha_state()

def _clear_media_details(self):
"""Set all Media Items to None."""
Expand Down Expand Up @@ -390,8 +419,19 @@ def update(self):
if (self._device is None or self.entity_id is None):
# Device has not initialized yet
return
self.refresh(no_throttle=True)
if self.state in [STATE_PLAYING]:
email = self._login.email
device = (self.hass.data[DATA_ALEXAMEDIA]
['accounts']
[email]
['devices']
['media_player']
[self.unique_id])
self.refresh(device, # pylint: disable=unexpected-keyword-arg
no_throttle=True)
if (self.state in [STATE_PLAYING] and
# only enable polling if websocket not connected
(not self.hass.data[DATA_ALEXAMEDIA]
['accounts'][email]['websocket'])):
self._should_poll = False # disable polling since manual update
if(self._last_update == 0 or util.dt.as_timestamp(util.utcnow()) -
util.dt.as_timestamp(self._last_update)
Expand All @@ -402,11 +442,16 @@ def update(self):
self.schedule_update_ha_state(force_refresh=True))
elif self._should_poll: # Not playing, one last poll
self._should_poll = False
_LOGGER.debug("Disabling polling and scheduling last update in 300"
" seconds for %s",
self.name)
call_later(self.hass, 300, lambda _:
self.schedule_update_ha_state(force_refresh=True))
if not (self.hass.data[DATA_ALEXAMEDIA]
['accounts'][email]['websocket']):
_LOGGER.debug("Disabling polling and scheduling last update in"
" 300 seconds for %s",
self.name)
call_later(self.hass, 300, lambda _:
self.schedule_update_ha_state(force_refresh=True))
else:
_LOGGER.debug("Disabling polling for %s",
self.name)
self._last_update = util.utcnow()
self.schedule_update_ha_state()

Expand Down
1 change: 0 additions & 1 deletion alexapy
Submodule alexapy deleted from 8154fa
2 changes: 1 addition & 1 deletion custom_components.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"alexa_media": {
"version": "1.2.1",
"version": "1.2.2",
"local_location": "/custom_components/alexa_media/__init__.py",
"remote_location": "https://raw.githubusercontent.com/keatontaylor/alexa_media_player/master/alexa_media/__init__.py",
"visit_repo": "https://github.com/keatontaylor/alexa_media_player",
Expand Down

0 comments on commit bd48da1

Please sign in to comment.