diff --git a/config/custom_components/media_player/README.md b/config/custom_components/media_player/README.md
deleted file mode 100755
index 5b0965e7..00000000
--- a/config/custom_components/media_player/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-

-
-**Still have questions on my Config?**
-**Message me on twitter :** [@CCostan](https://twitter.com/ccostan) or [@BearStoneHA](https://twitter.com/BearStoneHA)
-
-

.
-
diff --git a/config/custom_components/media_player/alexa.py.DISABLED b/config/custom_components/media_player/alexa.py.DISABLED
deleted file mode 100755
index 02e82beb..00000000
--- a/config/custom_components/media_player/alexa.py.DISABLED
+++ /dev/null
@@ -1,1077 +0,0 @@
-"""
-Support to interface with Alexa Devices.
-
-For more details about this platform, please refer to the documentation at
-https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639
-VERSION 0.9.5
-"""
-import logging
-
-from datetime import timedelta
-
-import requests
-import voluptuous as vol
-
-from homeassistant import util
-from homeassistant.components.media_player import (
- MEDIA_TYPE_MUSIC, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK,
- SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
- SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE,
- SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET,
- MediaPlayerDevice, DOMAIN, MEDIA_PLAYER_SCHEMA,
- SUPPORT_SELECT_SOURCE)
-from homeassistant.const import (
- CONF_EMAIL, CONF_PASSWORD, CONF_URL,
- STATE_IDLE, STATE_STANDBY, STATE_PAUSED,
- STATE_PLAYING)
-from homeassistant.helpers import config_validation as cv
-from homeassistant.helpers.service import extract_entity_ids
-from homeassistant.helpers.event import track_utc_time_change
-# from homeassistant.util.json import load_json, save_json
-# from homeassistant.util import dt as dt_util
-
-SUPPORT_ALEXA = (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK |
- SUPPORT_NEXT_TRACK | SUPPORT_STOP |
- SUPPORT_VOLUME_SET | SUPPORT_PLAY |
- SUPPORT_PLAY_MEDIA | SUPPORT_TURN_OFF |
- SUPPORT_VOLUME_MUTE | SUPPORT_PAUSE |
- SUPPORT_SELECT_SOURCE)
-_CONFIGURING = []
-_LOGGER = logging.getLogger(__name__)
-
-REQUIREMENTS = ['beautifulsoup4==4.6.0', 'simplejson==3.16.0']
-
-MIN_TIME_BETWEEN_SCANS = timedelta(seconds=15)
-MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
-
-ALEXA_DATA = "alexa_media"
-
-SERVICE_ALEXA_TTS = 'alexa_tts'
-
-ATTR_MESSAGE = 'message'
-ALEXA_TTS_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
- vol.Required(ATTR_MESSAGE): cv.string,
-})
-
-CONF_DEBUG = 'debug'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Required(CONF_EMAIL): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Required(CONF_URL): cv.string,
- vol.Optional(CONF_DEBUG, default=False): cv.boolean,
-})
-
-
-def request_configuration(hass, config, setup_platform_callback,
- status=None):
- """Request configuration steps from the user."""
- configurator = hass.components.configurator
-
- async def configuration_callback(callback_data):
- """Handle the submitted configuration."""
- hass.async_add_job(setup_platform_callback, callback_data)
-
- # Get Captcha
- if (status and 'captcha_image_url' in status and
- status['captcha_image_url'] is not None):
- config_id = configurator.request_config(
- "Alexa Media Player - Captcha", configuration_callback,
- description=('Please enter the text for the captcha.'
- ' Please enter anything if the image is missing.'
- ),
- description_image=status['captcha_image_url'],
- submit_caption="Confirm",
- fields=[{'id': 'captcha', 'name': 'Captcha'}]
- )
- elif (status and 'securitycode_required' in status and
- status['securitycode_required']): # Get 2FA code
- config_id = configurator.request_config(
- "Alexa Media Player - 2FA", configuration_callback,
- description=('Please enter your Two-Factor Security code.'),
- submit_caption="Confirm",
- fields=[{'id': 'securitycode', 'name': 'Security Code'}]
- )
- elif (status and 'claimspicker_required' in status and
- status['claimspicker_required']): # Get picker method
- options = status['claimspicker_message']
- config_id = configurator.request_config(
- "Alexa Media Player - Verification Method", configuration_callback,
- description=('Please select the verification method. '
- '(e.g., sms or email).
{}').format(
- options
- ),
- submit_caption="Confirm",
- fields=[{'id': 'claimsoption', 'name': 'Option'}]
- )
- elif (status and 'verificationcode_required' in status and
- status['verificationcode_required']): # Get picker method
- config_id = configurator.request_config(
- "Alexa Media Player - Verification Code", configuration_callback,
- description=('Please enter received verification code.'),
- submit_caption="Confirm",
- fields=[{'id': 'verificationcode', 'name': 'Verification Code'}]
- )
- else: # Check login
- config_id = configurator.request_config(
- "Alexa Media Player - Begin", configuration_callback,
- description=('Please hit confirm to begin login attempt.'),
- submit_caption="Confirm",
- fields=[]
- )
- _CONFIGURING.append(config_id)
- if (len(_CONFIGURING) > 0 and 'error_message' in status
- and status['error_message']):
- configurator.notify_errors( # use sync to delay next pop
- _CONFIGURING[len(_CONFIGURING)-1], status['error_message'])
- if (len(_CONFIGURING) > 1):
- configurator.async_request_done(_CONFIGURING.pop(0))
-
-
-def setup_platform(hass, config, add_devices_callback,
- discovery_info=None):
- """Set up the Alexa platform."""
- if ALEXA_DATA not in hass.data:
- hass.data[ALEXA_DATA] = {}
-
- email = config.get(CONF_EMAIL)
- password = config.get(CONF_PASSWORD)
- url = config.get(CONF_URL)
-
- login = AlexaLogin(url, email, password, hass.config.path,
- config.get(CONF_DEBUG))
-
- async def setup_platform_callback(callback_data):
- _LOGGER.debug(("Status: {} got captcha: {} securitycode: {}"
- " Claimsoption: {} VerificationCode: {}").format(
- login.status,
- callback_data.get('captcha'),
- callback_data.get('securitycode'),
- callback_data.get('claimsoption'),
- callback_data.get('verificationcode')))
- login.login(captcha=callback_data.get('captcha'),
- securitycode=callback_data.get('securitycode'),
- claimsoption=callback_data.get('claimsoption'),
- verificationcode=callback_data.get('verificationcode'))
- testLoginStatus(hass, config, add_devices_callback, login,
- setup_platform_callback)
-
- testLoginStatus(hass, config, add_devices_callback, login,
- setup_platform_callback)
-
-
-def testLoginStatus(hass, config, add_devices_callback, login,
- setup_platform_callback):
- """Test the login status."""
- if 'login_successful' in login.status and login.status['login_successful']:
- _LOGGER.debug("Setting up Alexa devices")
- hass.async_add_job(setup_alexa, hass, config,
- add_devices_callback, login)
- return
- elif ('captcha_required' in login.status and
- login.status['captcha_required']):
- _LOGGER.debug("Creating configurator to request captcha")
- elif ('securitycode_required' in login.status and
- login.status['securitycode_required']):
- _LOGGER.debug("Creating configurator to request 2FA")
- elif ('claimspicker_required' in login.status and
- login.status['claimspicker_required']):
- _LOGGER.debug("Creating configurator to select verification option")
- elif ('verificationcode_required' in login.status and
- login.status['verificationcode_required']):
- _LOGGER.debug("Creating configurator to enter verification code")
- elif ('login_failed' in login.status and
- login.status['login_failed']):
- _LOGGER.debug("Creating configurator to start new login attempt")
- hass.async_add_job(request_configuration, hass, config,
- setup_platform_callback,
- login.status)
-
-
-def setup_alexa(hass, config, add_devices_callback, login_obj):
- """Set up a alexa api based on host parameter."""
- alexa_clients = hass.data[ALEXA_DATA]
- # alexa_sessions = {}
- track_utc_time_change(hass, lambda now: update_devices(), second=30)
-
- url = config.get(CONF_URL)
-
- @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
- def update_devices():
- """Update the devices objects."""
- devices = AlexaAPI.get_devices(url, login_obj._session)
- bluetooth = AlexaAPI.get_bluetooth(url, login_obj._session)
-
- if ((devices is None or bluetooth is None)
- and len(_CONFIGURING) == 0):
- _LOGGER.debug("Alexa API disconnected; attempting to relogin")
- login_obj.login_with_cookie()
-
- new_alexa_clients = []
- available_client_ids = []
- for device in devices:
-
- for b_state in bluetooth['bluetoothStates']:
- if device['serialNumber'] == b_state['deviceSerialNumber']:
- device['bluetooth_state'] = b_state
-
- available_client_ids.append(device['serialNumber'])
-
- if device['serialNumber'] not in alexa_clients:
- new_client = AlexaClient(config, login_obj._session, device,
- update_devices, url)
- alexa_clients[device['serialNumber']] = new_client
- new_alexa_clients.append(new_client)
- elif device['online']:
- alexa_clients[device['serialNumber']].refresh(device)
-
- if new_alexa_clients:
- def tts_handler(call):
- for alexa in service_to_entities(call):
- if call.service == SERVICE_ALEXA_TTS:
- message = call.data.get(ATTR_MESSAGE)
- alexa.send_tts(message)
-
- def service_to_entities(call):
- """Return the known devices that a service call mentions."""
- entity_ids = extract_entity_ids(hass, call)
- if entity_ids:
- entities = [entity for entity in new_alexa_clients
- if entity.entity_id in entity_ids]
- else:
- entities = None
-
- return entities
-
- hass.services.register(DOMAIN, SERVICE_ALEXA_TTS, tts_handler,
- schema=ALEXA_TTS_SCHEMA)
- add_devices_callback(new_alexa_clients)
-
- update_devices()
- # Clear configurator. We delay till here to avoid leaving a modal orphan
- global _CONFIGURING
- for config_id in _CONFIGURING:
- configurator = hass.components.configurator
- configurator.async_request_done(config_id)
- _CONFIGURING = []
-
-
-class AlexaClient(MediaPlayerDevice):
- """Representation of a Alexa device."""
-
- def __init__(self, config, session, device, update_devices, url):
- """Initialize the Alexa device."""
- # Class info
- self.alexa_api = AlexaAPI(self, session, url)
-
- self.update_devices = update_devices
- # Device info
- self._device = None
- self._device_name = None
- self._device_serial_number = None
- self._device_type = None
- self._device_family = None
- self._device_owner_customer_id = None
- self._software_version = None
- self._available = None
- self._capabilities = []
- # Media
- self._session = None
- self._media_duration = None
- self._media_image_url = None
- self._media_title = None
- self._media_pos = None
- self._media_album_name = None
- self._media_artist = None
- self._player_state = None
- self._media_is_muted = None
- self._media_vol_level = None
- self._previous_volume = None
- self._source = None
- self._source_list = []
- self.refresh(device)
-
- def _clear_media_details(self):
- """Set all Media Items to None."""
- # General
- self._media_duration = None
- self._media_image_url = None
- self._media_title = None
- self._media_pos = None
- self._media_album_name = None
- self._media_artist = None
- self._media_player_state = None
- self._media_is_muted = None
- self._media_vol_level = None
-
- def refresh(self, device):
- """Refresh key device data."""
- self._device = device
- self._device_name = device['accountName']
- self._device_family = device['deviceFamily']
- self._device_type = device['deviceType']
- self._device_serial_number = device['serialNumber']
- self._device_owner_customer_id = device['deviceOwnerCustomerId']
- self._software_version = device['softwareVersion']
- self._available = device['online']
- self._capabilities = device['capabilities']
- self._bluetooth_state = device['bluetooth_state']
- self._source = self._get_source()
- self._source_list = self._get_source_list()
- session = self.alexa_api.get_state()
-
- self._clear_media_details()
- # update the session if it exists; not doing relogin here
- if session is not None:
- self._session = session
- if 'playerInfo' in self._session:
- self._session = self._session['playerInfo']
- if self._session['state'] is not None:
- self._media_player_state = self._session['state']
- self._media_pos = (self._session['progress']['mediaProgress']
- if (self._session['progress'] is not None
- and 'mediaProgress' in
- self._session['progress'])
- else None)
- self._media_is_muted = (self._session['volume']['muted']
- if (self._session['volume'] is not None
- and 'muted' in
- self._session['volume'])
- else None)
- self._media_vol_level = (self._session['volume']
- ['volume'] / 100
- if(self._session['volume'] is not None
- and 'volume' in
- self._session['volume'])
- else None)
- self._media_title = (self._session['infoText']['title']
- if (self._session['infoText'] is not None
- and 'title' in
- self._session['infoText'])
- else None)
- self._media_artist = (self._session['infoText']['subText1']
- if (self._session['infoText'] is not None
- and 'subText1' in
- self._session['infoText'])
- else None)
- self._media_album_name = (self._session['infoText']['subText2']
- if (self._session['infoText'] is not
- None and 'subText2' in
- self._session['infoText'])
- else None)
- self._media_image_url = (self._session['mainArt']['url']
- if (self._session['mainArt'] is not
- None and 'url' in
- self._session['mainArt'])
- else None)
- self._media_duration = (self._session['progress']
- ['mediaLength']
- if (self._session['progress'] is not
- None and 'mediaLength' in
- self._session['progress'])
- else None)
-
- @property
- def source(self):
- """Return the current input source."""
- return self._source
-
- @property
- def source_list(self):
- """List of available input sources."""
- return self._source_list
-
- def select_source(self, source):
- """Select input source."""
- if source == 'Local Speaker':
- self.alexa_api.disconnect_bluetooth()
- self._source = 'Local Speaker'
- elif self._bluetooth_state['pairedDeviceList'] is not None:
- for devices in self._bluetooth_state['pairedDeviceList']:
- if devices['friendlyName'] == source:
- self.alexa_api.set_bluetooth(devices['address'])
- self._source = source
-
- def _get_source(self):
- source = 'Local Speaker'
- if self._bluetooth_state['pairedDeviceList'] is not None:
- for device in self._bluetooth_state['pairedDeviceList']:
- if device['connected'] is True:
- return device['friendlyName']
- return source
-
- def _get_source_list(self):
- sources = []
- if self._bluetooth_state['pairedDeviceList'] is not None:
- for devices in self._bluetooth_state['pairedDeviceList']:
- sources.append(devices['friendlyName'])
- return ['Local Speaker'] + sources
-
- @property
- def available(self):
- """Return the availability of the client."""
- return self._available
-
- @property
- def unique_id(self):
- """Return the id of this Alexa client."""
- return self.device_serial_number
-
- @property
- def name(self):
- """Return the name of the device."""
- return self._device_name
-
- @property
- def device_serial_number(self):
- """Return the machine identifier of the device."""
- return self._device_serial_number
-
- @property
- def device(self):
- """Return the device, if any."""
- return self._device
-
- @property
- def session(self):
- """Return the session, if any."""
- return self._session
-
- @property
- def state(self):
- """Return the state of the device."""
- if self._media_player_state == 'PLAYING':
- return STATE_PLAYING
- elif self._media_player_state == 'PAUSED':
- return STATE_PAUSED
- elif self._media_player_state == 'IDLE':
- return STATE_IDLE
- return STATE_STANDBY
-
- def update(self):
- """Get the latest details."""
- self.update_devices(no_throttle=True)
-
- @property
- def media_content_type(self):
- """Return the content type of current playing media."""
- if self.state in [STATE_PLAYING, STATE_PAUSED]:
- return MEDIA_TYPE_MUSIC
- return STATE_STANDBY
-
- @property
- def media_artist(self):
- """Return the artist of current playing media, music track only."""
- return self._media_artist
-
- @property
- def media_album_name(self):
- """Return the album name of current playing media, music track only."""
- return self._media_album_name
-
- @property
- def media_duration(self):
- """Return the duration of current playing media in seconds."""
- return self._media_duration
-
- @property
- def media_image_url(self):
- """Return the image URL of current playing media."""
- return self._media_image_url
-
- @property
- def media_title(self):
- """Return the title of current playing media."""
- return self._media_title
-
- @property
- def device_family(self):
- """Return the make of the device (ex. Echo, Other)."""
- return self._device_family
-
- @property
- def supported_features(self):
- """Flag media player features that are supported."""
- return SUPPORT_ALEXA
-
- def set_volume_level(self, volume):
- """Set volume level, range 0..1."""
- if not (self.state in [STATE_PLAYING, STATE_PAUSED]
- and self.available):
- return
- self.alexa_api.set_volume(volume)
- self._media_vol_level = volume
-
- @property
- def volume_level(self):
- """Return the volume level of the client (0..1)."""
- return self._media_vol_level
-
- @property
- def is_volume_muted(self):
- """Return boolean if volume is currently muted."""
- if self.volume_level == 0:
- return True
- return False
-
- def mute_volume(self, mute):
- """Mute the volume.
-
- Since we can't actually mute, we'll:
- - On mute, store volume and set volume to 0
- - On unmute, set volume to previously stored volume
- """
- if not (self.state == STATE_PLAYING and self.available):
- return
-
- self._media_is_muted = mute
- if mute:
- self._previous_volume = self.volume_level
- self.alexa_api.set_volume(0)
- else:
- if self._previous_volume is not None:
- self.alexa_api.set_volume(self._previous_volume)
- else:
- self.alexa_api.set_volume(50)
-
- def media_play(self):
- """Send play command."""
- if not (self.state in [STATE_PLAYING, STATE_PAUSED]
- and self.available):
- return
- self.alexa_api.play()
-
- def media_pause(self):
- """Send pause command."""
- if not (self.state in [STATE_PLAYING, STATE_PAUSED]
- and self.available):
- return
- self.alexa_api.pause()
-
- def turn_off(self):
- """Turn the client off."""
- # Fake it since we can't turn the client off
- self.media_pause()
-
- def media_next_track(self):
- """Send next track command."""
- if not (self.state in [STATE_PLAYING, STATE_PAUSED]
- and self.available):
- return
- self.alexa_api.next()
-
- def media_previous_track(self):
- """Send previous track command."""
- if not (self.state in [STATE_PLAYING, STATE_PAUSED]
- and self.available):
- return
- self.alexa_api.previous()
-
- def send_tts(self, message):
- """Send TTS to Device NOTE: Does not work on WHA Groups."""
- self.alexa_api.send_tts(message)
-
- def play_media(self, media_type, media_id, **kwargs):
- """Send the play_media command to the media player."""
- if media_type == "music":
- self.alexa_api.send_tts("Sorry, text to speech can only be called "
- " with the media player alexa tts service")
- else:
- self.alexa_api.play_music(media_type, media_id)
-
- @property
- def device_state_attributes(self):
- """Return the scene state attributes."""
- attr = {
- 'available': self._available,
- }
- return attr
-
-
-class AlexaLogin():
- """Class to handle login connection to Alexa."""
-
- def __init__(self, url, email, password, configpath, debug=False):
- """Set up initial connection and log in."""
- self._url = url
- self._email = email
- self._password = password
- self._session = None
- self._data = None
- self.status = {}
- self._cookiefile = configpath("{}.pickle".format(ALEXA_DATA))
- self._debugpost = configpath("{}post.html".format(ALEXA_DATA))
- self._debugget = configpath("{}get.html".format(ALEXA_DATA))
- self._lastreq = None
- self._debug = debug
-
- self.login_with_cookie()
-
- def login_with_cookie(self):
- """Attempt to login after loading cookie."""
- import pickle
- cookies = None
-
- if (self._cookiefile):
- try:
- _LOGGER.debug(
- "Trying cookie from file {}".format(
- self._cookiefile))
- with open(self._cookiefile, 'rb') as myfile:
- cookies = pickle.load(myfile)
- _LOGGER.debug("cookie loaded: {}".format(cookies))
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.debug(
- "Error loading pickled cookie from {}: {}".format(
- self._cookiefile, message))
-
- self.login(cookies=cookies)
-
- def reset_login(self):
- """Remove data related to existing login."""
- self._session = None
- self._data = None
- self._lastreq = None
- self.status = {}
-
- def get_inputs(self, soup, searchfield={'name': 'signIn'}):
- """Parse soup for form with searchfield."""
- data = {}
- form = soup.find('form', searchfield)
- for field in form.find_all('input'):
- try:
- data[field['name']] = ""
- data[field['name']] = field['value']
- except: # noqa: E722 pylint: disable=bare-except
- pass
- return data
-
- def test_loggedin(self, cookies=None):
- """Function that will test the connection is logged in.
-
- Attempts to get device list, and if unsuccessful login failed
- """
- if self._session is None:
- '''initiate session'''
-
- self._session = requests.Session()
-
- '''define session headers'''
- self._session.headers = {
- 'User-Agent': ('Mozilla/5.0 (Windows NT 6.3; Win64; x64) '
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
- 'Chrome/68.0.3440.106 Safari/537.36'),
- 'Accept': ('text/html,application/xhtml+xml, '
- 'application/xml;q=0.9,*/*;q=0.8'),
- 'Accept-Language': '*'
- }
- self._session.cookies = cookies
-
- get_resp = self._session.get('https://alexa.' + self._url +
- '/api/devices-v2/device')
- # with open(self._debugget, mode='wb') as localfile:
- # localfile.write(get_resp.content)
-
- try:
- from json.decoder import JSONDecodeError
- from simplejson import JSONDecodeError as SimpleJSONDecodeError
- # Need to catch both as Python 3.5 appears to use simplejson
- except ImportError:
- JSONDecodeError = ValueError
- try:
- get_resp.json()
- except (JSONDecodeError, SimpleJSONDecodeError) as ex:
- # ValueError is necessary for Python 3.5 for some reason
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.debug("Not logged in: {}".format(message))
- return False
- _LOGGER.debug("Logged in.")
- return True
-
- def login(self, cookies=None, captcha=None, securitycode=None,
- claimsoption=None, verificationcode=None):
- """Login to Amazon."""
- from bs4 import BeautifulSoup
- import pickle
-
- if (cookies is not None and self.test_loggedin(cookies)):
- _LOGGER.debug("Using cookies to log in")
- self.status = {}
- self.status['login_successful'] = True
- _LOGGER.debug("Log in successful with cookies")
- return
- else:
- _LOGGER.debug("No valid cookies for log in; using credentials")
- # site = 'https://www.' + self._url + '/gp/sign-in.html'
- # use alexa site instead
- site = 'https://alexa.' + self._url + '/api/devices-v2/device'
- if self._session is None:
- '''initiate session'''
-
- self._session = requests.Session()
-
- '''define session headers'''
- self._session.headers = {
- 'User-Agent': ('Mozilla/5.0 (Windows NT 6.3; Win64; x64) '
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
- 'Chrome/68.0.3440.106 Safari/537.36'),
- 'Accept': ('text/html,application/xhtml+xml, '
- 'application/xml;q=0.9,*/*;q=0.8'),
- 'Accept-Language': '*'
- }
-
- if self._lastreq is not None:
- site = self._lastreq.url
- _LOGGER.debug("Loaded last request to {} ".format(site))
- html = self._lastreq.text
- '''get BeautifulSoup object of the html of the login page'''
- if self._debug:
- with open(self._debugget, mode='wb') as localfile:
- localfile.write(self._lastreq.content)
-
- soup = BeautifulSoup(html, 'html.parser')
- site = soup.find('form').get('action')
- if site is None:
- site = self._lastreq.url
- elif site == 'verify':
- import re
- site = re.search(r'(.+)/(.*)',
- self._lastreq.url).groups()[0] + "/verify"
-
- if self._data is None:
- resp = self._session.get(site)
- self._lastreq = resp
- if resp.history:
- _LOGGER.debug("Get to {} was redirected to {}".format(
- site,
- resp.url))
- self._session.headers['Referer'] = resp.url
- else:
- _LOGGER.debug("Get to {} was not redirected".format(site))
- self._session.headers['Referer'] = site
-
- html = resp.text
- '''get BeautifulSoup object of the html of the login page'''
- if self._debug:
- with open(self._debugget, mode='wb') as localfile:
- localfile.write(resp.content)
-
- soup = BeautifulSoup(html, 'html.parser')
- '''scrape login page to get all the inputs required for login'''
- self._data = self.get_inputs(soup)
- site = soup.find('form', {'name': 'signIn'}).get('action')
-
- # _LOGGER.debug("Init Form Data: {}".format(self._data))
-
- '''add username and password to the data for post request'''
- '''check if there is an input field'''
- if "email" in self._data:
- self._data['email'] = self._email.encode('utf-8')
- if "password" in self._data:
- self._data['password'] = self._password.encode('utf-8')
- if "rememberMe" in self._data:
- self._data['rememberMe'] = "true".encode('utf-8')
-
- status = {}
- _LOGGER.debug(("Preparing post to {} Captcha: {}"
- " SecurityCode: {} Claimsoption: {} "
- "VerificationCode: {}").format(
- site,
- captcha,
- securitycode,
- claimsoption,
- verificationcode
- ))
- if (captcha is not None and 'guess' in self._data):
- self._data['guess'] = captcha.encode('utf-8')
- if (securitycode is not None and 'otpCode' in self._data):
- self._data['otpCode'] = securitycode.encode('utf-8')
- self._data['rememberDevice'] = ""
- if (claimsoption is not None and 'option' in self._data):
- self._data['option'] = claimsoption.encode('utf-8')
- if (verificationcode is not None and 'code' in self._data):
- self._data['code'] = verificationcode.encode('utf-8')
- self._session.headers['Content-Type'] = ("application/x-www-form-"
- "urlencoded; charset=utf-8")
- self._data.pop('', None)
-
- if self._debug:
- _LOGGER.debug("Cookies: {}".format(self._session.cookies))
- _LOGGER.debug("Submit Form Data: {}".format(self._data))
- _LOGGER.debug("Header: {}".format(self._session.headers))
-
- '''submit post request with username/password and other needed info'''
- post_resp = self._session.post(site, data=self._data)
- self._session.headers['Referer'] = site
-
- self._lastreq = post_resp
- if self._debug:
- with open(self._debugpost, mode='wb') as localfile:
- localfile.write(post_resp.content)
-
- post_soup = BeautifulSoup(post_resp.content, 'html.parser')
-
- login_tag = post_soup.find('form', {'name': 'signIn'})
- captcha_tag = post_soup.find(id="auth-captcha-image")
-
- '''another login required and no captcha request? try once more.
- This is a necessary hack as the first attempt always fails.
- TODO: Figure out how to remove this hack
- '''
- if (login_tag is not None and captcha_tag is None):
- login_url = login_tag.get("action")
- _LOGGER.debug("Performing second login to: {}".format(
- login_url))
- post_resp = self._session.post(login_url,
- data=self._data)
- if self._debug:
- with open(self._debugpost, mode='wb') as localfile:
- localfile.write(post_resp.content)
- post_soup = BeautifulSoup(post_resp.content, 'html.parser')
- login_tag = post_soup.find('form', {'name': 'signIn'})
- captcha_tag = post_soup.find(id="auth-captcha-image")
-
- securitycode_tag = post_soup.find(id="auth-mfa-otpcode")
- errorbox = (post_soup.find(id="auth-error-message-box")
- if post_soup.find(id="auth-error-message-box") else
- post_soup.find(id="auth-warning-message-box"))
- claimspicker_tag = post_soup.find('form', {'name': 'claimspicker'})
- verificationcode_tag = post_soup.find('form', {'action': 'verify'})
-
- '''pull out Amazon error message'''
-
- if errorbox:
- error_message = errorbox.find('h4').string
- for li in errorbox.findAll('li'):
- error_message += li.find('span').string
- _LOGGER.debug("Error message: {}".format(error_message))
- status['error_message'] = error_message
-
- if captcha_tag is not None:
- _LOGGER.debug("Captcha requested")
- status['captcha_required'] = True
- status['captcha_image_url'] = captcha_tag.get('src')
- self._data = self.get_inputs(post_soup)
-
- elif securitycode_tag is not None:
- _LOGGER.debug("2FA requested")
- status['securitycode_required'] = True
- self._data = self.get_inputs(post_soup, {'id': 'auth-mfa-form'})
-
- elif claimspicker_tag is not None:
- claims_message = ""
- options_message = ""
- for div in claimspicker_tag.findAll('div', 'a-row'):
- claims_message += "{}\n".format(div.string)
- for label in claimspicker_tag.findAll('label'):
- value = (label.find('input')['value']) if label.find(
- 'input') else ""
- message = (label.find('span').string) if label.find(
- 'span') else ""
- valuemessage = ("Option: {} = `{}`.\n".format(
- value, message)) if value != "" else ""
- options_message += valuemessage
- _LOGGER.debug("Verification method requested: {}".format(
- claims_message, options_message))
- status['claimspicker_required'] = True
- status['claimspicker_message'] = options_message
- self._data = self.get_inputs(post_soup, {'name': 'claimspicker'})
- elif verificationcode_tag is not None:
- _LOGGER.debug("Verification code requested:")
- status['verificationcode_required'] = True
- self._data = self.get_inputs(post_soup, {'action': 'verify'})
- elif login_tag is not None:
- login_url = login_tag.get("action")
- _LOGGER.debug("Another login requested to: {}".format(
- login_url))
- status['login_failed'] = True
-
- else:
- _LOGGER.debug("Captcha/2FA not requested; confirming login.")
- if self.test_loggedin():
- _LOGGER.debug("Login confirmed; saving cookie to {}".format(
- self._cookiefile))
- status['login_successful'] = True
- with open(self._cookiefile, 'wb') as myfile:
- try:
- pickle.dump(self._session.cookies, myfile)
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.debug(
- "Error saving pickled cookie to {}: {}".format(
- self._cookiefile,
- message))
- else:
- _LOGGER.debug("Login failed; check credentials")
- status['login_failed'] = True
-
- self.status = status
-
-
-class AlexaAPI():
- """Class for accessing Alexa."""
-
- def __init__(self, device, session, url):
- """Initialize Alexa device."""
- self._device = device
- self._session = session
- self._url = 'https://alexa.' + url
-
- csrf = self._session.cookies.get_dict()['csrf']
- self._session.headers['csrf'] = csrf
-
- def _post_request(self, uri, data):
- try:
- self._session.post(self._url + uri, json=data)
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.error("An error occured accessing the API: {}".format(
- message))
-
- def _get_request(self, uri, data=None):
- try:
- return self._session.get(self._url + uri, json=data)
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.error("An error occured accessing the API: {}".format(
- message))
- return None
-
- def play_music(self, provider_id, search_phrase):
- """Play Music based on search."""
- data = {
- "behaviorId": "PREVIEW",
- "sequenceJson": "{\"@type\": \
- \"com.amazon.alexa.behaviors.model.Sequence\", \
- \"startNode\":{\"@type\": \
- \"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\", \
- \"type\":\"Alexa.Music.PlaySearchPhrase\",\"operationPayload\": \
- {\"deviceType\":\"" + self._device._device_type + "\", \
- \"deviceSerialNumber\":\"" + self._device.unique_id +
- "\",\"locale\":\"en-US\", \
- \"customerId\":\"" + self._device._device_owner_customer_id +
- "\", \"searchPhrase\": \"" + search_phrase + "\", \
- \"sanitizedSearchPhrase\": \"" + search_phrase + "\", \
- \"musicProviderId\": \"" + provider_id + "\"}}}",
- "status": "ENABLED"
- }
- self._post_request('/api/behaviors/preview',
- data=data)
-
- def send_tts(self, message):
- """Send message for TTS at speaker."""
- data = {
- "behaviorId": "PREVIEW",
- "sequenceJson": "{\"@type\": \
- \"com.amazon.alexa.behaviors.model.Sequence\", \
- \"startNode\":{\"@type\": \
- \"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\", \
- \"type\":\"Alexa.Speak\",\"operationPayload\": \
- {\"deviceType\":\"" + self._device._device_type + "\", \
- \"deviceSerialNumber\":\"" + self._device.unique_id +
- "\",\"locale\":\"en-US\", \
- \"customerId\":\"" + self._device._device_owner_customer_id +
- "\", \"textToSpeak\": \"" + message + "\"}}}",
- "status": "ENABLED"
- }
- self._post_request('/api/behaviors/preview',
- data=data)
-
- def set_media(self, data):
- """Select the media player."""
- self._post_request('/api/np/command?deviceSerialNumber=' +
- self._device.unique_id + '&deviceType=' +
- self._device._device_type, data=data)
-
- def previous(self):
- """Play previous."""
- self.set_media({"type": "PreviousCommand"})
-
- def next(self):
- """Play next."""
- self.set_media({"type": "NextCommand"})
-
- def pause(self):
- """Pause."""
- self.set_media({"type": "PauseCommand"})
-
- def play(self):
- """Play."""
- self.set_media({"type": "PlayCommand"})
-
- def set_volume(self, volume):
- """Set volume."""
- self.set_media({"type": "VolumeLevelCommand",
- "volumeLevel": volume*100})
-
- def get_state(self):
- """Get state."""
- try:
- response = self._get_request('/api/np/player?deviceSerialNumber=' +
- self._device.unique_id +
- '&deviceType=' +
- self._device._device_type +
- '&screenWidth=2560')
- return response.json()
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.error("An error occured accessing the API: {}".format(
- message))
- return None
-
- @staticmethod
- def get_bluetooth(url, session):
- """Get paired bluetooth devices."""
- try:
-
- response = session.get('https://alexa.' + url +
- '/api/bluetooth?cached=false')
- return response.json()
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.error("An error occured accessing the API: {}".format(
- message))
- return None
-
- def set_bluetooth(self, mac):
- """Pair with bluetooth device with mac address."""
- self._post_request('/api/bluetooth/pair-sink/' +
- self._device._device_type + '/' +
- self._device.unique_id,
- data={"bluetoothDeviceAddress": mac})
-
- def disconnect_bluetooth(self):
- """Disconnect all bluetooth devices."""
- self._post_request('/api/bluetooth/disconnect-sink/' +
- self._device._device_type + '/' +
- self._device.unique_id, data=None)
-
- @staticmethod
- def get_devices(url, session):
- """Identify all Alexa devices."""
- try:
- response = session.get('https://alexa.' + url +
- '/api/devices-v2/device')
- return response.json()['devices']
- except Exception as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
- message = template.format(type(ex).__name__, ex.args)
- _LOGGER.error("An error occured accessing the API: {}".format(
- message))
- return None
\ No newline at end of file
diff --git a/config/custom_components/media_player/floorplan_speaker.py b/config/custom_components/media_player/floorplan_speaker.py
deleted file mode 100755
index da8a4bc9..00000000
--- a/config/custom_components/media_player/floorplan_speaker.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-Support for Floorplan Speaker
-
-"""
-import voluptuous as vol
-
-from homeassistant.components.media_player import (
- ENTITY_ID_FORMAT,
- SUPPORT_PLAY_MEDIA,
- SUPPORT_VOLUME_SET,
- PLATFORM_SCHEMA,
- MediaPlayerDevice)
-from homeassistant.const import (
- CONF_NAME, STATE_IDLE, STATE_PLAYING)
-from homeassistant.components import http
-from homeassistant.components.http import HomeAssistantView
-from homeassistant.helpers.entity import async_generate_entity_id
-import homeassistant.helpers.config_validation as cv
-
-import logging
-
-import os
-import re
-import sys
-import time
-import asyncio
-import json
-
-DEFAULT_NAME = 'Floorplan Speaker'
-DEFAULT_VOLUME = 1.0
-
-SUPPORT_FLOORPLAN_SPEAKER = SUPPORT_PLAY_MEDIA | SUPPORT_VOLUME_SET
-
-CONF_ADDRESS = 'address'
-
-ATTR_ADDRESS = 'address'
-ATTR_BATTERY_LEVEL = 'battery_level'
-ATTR_SCREEN_BRIGHTNESS = 'screen_brightness'
-ATTR_DEVICE_ID = 'device_id'
-ATTR_SERIAL_NUMBER = 'serial_number'
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
-})
-
-_LOGGER = logging.getLogger(__name__)
-
-def setup_platform(hass, config, add_devices, discovery_info=None):
- name = config.get(CONF_NAME)
- address = config.get(CONF_ADDRESS)
-
- device = FloorplanSpeakerDevice(hass, name, address)
-
- """Set up an endpoint for the media player."""
- hass.http.register_view(device)
-
- add_devices([device])
-
- return True
-
-class FloorplanSpeakerDevice(MediaPlayerDevice, http.HomeAssistantView):
- def __init__(self, hass, name, address):
- self._hass = hass
- self._name = name
- self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=hass)
- self._state = STATE_IDLE
- self._media_content_id = None
- self._address = address
- self._volume = DEFAULT_VOLUME
- self._battery_level = None
- self._screen_brightness = None
- self._device_id = None
- self._serial_number = None
- self.url = '/api/fully_kiosk/media_player/' + self.entity_id
- _LOGGER.info('Setting endpoint: %s', self.url)
-
- @asyncio.coroutine
- def post(self, request):
- body = yield from request.text()
- try:
- data = json.loads(body) if body else None
- except ValueError:
- return self.json_message('Event data should be valid JSON', HTTP_BAD_REQUEST)
-
- if data is not None and not isinstance(data, dict):
- return self.json_message('Event data should be a JSON object', HTTP_BAD_REQUEST)
-
- data = json.loads(body) if body else None
-
- _LOGGER.info("Received from Fully Kiosk: %s: %s", self.url, data)
-
- self._state = data['state']
- self._media_content_id = data['attributes']['media_content_id']
- self._volume = data['attributes']['volume_level']
- self._address = data['attributes'][ATTR_ADDRESS]
- self._battery_level = data['attributes'][ATTR_BATTERY_LEVEL]
- self._screen_brightness = data['attributes'][ATTR_SCREEN_BRIGHTNESS]
- self._device_id = data['attributes'][ATTR_DEVICE_ID]
- self._serial_number = data['attributes'][ATTR_SERIAL_NUMBER]
-
- @property
- def name(self):
- return self._name
-
- @property
- def state(self):
- return self._state
-
- @property
- def supported_features(self):
- return SUPPORT_FLOORPLAN_SPEAKER
-
- @property
- def address(self):
- return self._address
-
- @property
- def volume_level(self):
- return self._volume
-
- @property
- def media_content_id(self):
- return self._media_content_id
-
- @property
- def battery_level(self):
- return self._battery_level
-
- @property
- def device_id(self):
- return self._device_id
-
- @property
- def serial_number(self):
- return self._serial_number
-
- @property
- def device_state_attributes(self):
- return {
- ATTR_ADDRESS: self._address,
- ATTR_BATTERY_LEVEL: self._battery_level,
- ATTR_SCREEN_BRIGHTNESS: self._screen_brightness,
- ATTR_DEVICE_ID: self._device_id,
- ATTR_SERIAL_NUMBER: self._serial_number,
- }
-
- def set_volume_level(self, volume):
- self._volume = volume
-
- def play_media(self, media_type, media_id, **kwargs):
- _LOGGER.info('play_media: %s', media_id)
-
- def media_play(self):
- _LOGGER.info('media_play')
-
- def media_pause(self):
- _LOGGER.info('media_pause')
-
- def media_stop(self):
- _LOGGER.info('media_stop')
diff --git a/config/custom_components/sensor/fitbit.py.disabled b/config/custom_components/sensor/fitbit.py.disabled
deleted file mode 100755
index 5bba23a1..00000000
--- a/config/custom_components/sensor/fitbit.py.disabled
+++ /dev/null
@@ -1,501 +0,0 @@
-"""
-Support for the Fitbit API.
-
-For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/sensor.fitbit/
-"""
-import os
-import logging
-import datetime
-import time
-
-import voluptuous as vol
-
-from homeassistant.core import callback
-from homeassistant.components.http import HomeAssistantView
-from homeassistant.components.sensor import PLATFORM_SCHEMA
-from homeassistant.const import ATTR_ATTRIBUTION
-from homeassistant.const import CONF_UNIT_SYSTEM
-from homeassistant.helpers.entity import Entity
-from homeassistant.helpers.icon import icon_for_battery_level
-import homeassistant.helpers.config_validation as cv
-from homeassistant.util.json import load_json, save_json
-
-
-REQUIREMENTS = ['fitbit==0.3.0']
-
-_CONFIGURING = {}
-_LOGGER = logging.getLogger(__name__)
-
-ATTR_ACCESS_TOKEN = 'access_token'
-ATTR_REFRESH_TOKEN = 'refresh_token'
-ATTR_CLIENT_ID = 'client_id'
-ATTR_CLIENT_SECRET = 'client_secret'
-ATTR_LAST_SAVED_AT = 'last_saved_at'
-
-CONF_MONITORED_RESOURCES = 'monitored_resources'
-CONF_CLOCK_FORMAT = 'clock_format'
-CONF_ATTRIBUTION = 'Data provided by Fitbit.com'
-CONF_FILENAME = 'filename'
-
-DEPENDENCIES = ['http']
-
-FITBIT_AUTH_CALLBACK_PATH = '/api/fitbit/callback'
-FITBIT_AUTH_START = '/api/fitbit'
-FITBIT_CONFIG_FILE = 'fitbit.conf'
-FITBIT_DEFAULT_RESOURCES = ['activities/steps']
-
-SCAN_INTERVAL = datetime.timedelta(minutes=30)
-
-DEFAULT_CONFIG = {
- 'client_id': 'CLIENT_ID_HERE',
- 'client_secret': 'CLIENT_SECRET_HERE'
-}
-
-FITBIT_RESOURCES_LIST = {
- 'activities/activityCalories': ['Activity Calories', 'cal', 'fire'],
- 'activities/calories': ['Calories', 'cal', 'fire'],
- 'activities/caloriesBMR': ['Calories BMR', 'cal', 'fire'],
- 'activities/distance': ['Distance', '', 'map-marker'],
- 'activities/elevation': ['Elevation', '', 'walk'],
- 'activities/floors': ['Floors', 'floors', 'walk'],
- 'activities/heart': ['Resting Heart Rate', 'bpm', 'heart-pulse'],
- 'activities/minutesFairlyActive':
- ['Minutes Fairly Active', 'minutes', 'walk'],
- 'activities/minutesLightlyActive':
- ['Minutes Lightly Active', 'minutes', 'walk'],
- 'activities/minutesSedentary':
- ['Minutes Sedentary', 'minutes', 'seat-recline-normal'],
- 'activities/minutesVeryActive': ['Minutes Very Active', 'minutes', 'run'],
- 'activities/steps': ['Steps', 'steps', 'walk'],
- 'activities/tracker/activityCalories':
- ['Tracker Activity Calories', 'cal', 'fire'],
- 'activities/tracker/calories': ['Tracker Calories', 'cal', 'fire'],
- 'activities/tracker/distance': ['Tracker Distance', '', 'map-marker'],
- 'activities/tracker/elevation': ['Tracker Elevation', '', 'walk'],
- 'activities/tracker/floors': ['Tracker Floors', 'floors', 'walk'],
- 'activities/tracker/minutesFairlyActive':
- ['Tracker Minutes Fairly Active', 'minutes', 'walk'],
- 'activities/tracker/minutesLightlyActive':
- ['Tracker Minutes Lightly Active', 'minutes', 'walk'],
- 'activities/tracker/minutesSedentary':
- ['Tracker Minutes Sedentary', 'minutes', 'seat-recline-normal'],
- 'activities/tracker/minutesVeryActive':
- ['Tracker Minutes Very Active', 'minutes', 'run'],
- 'activities/tracker/steps': ['Tracker Steps', 'steps', 'walk'],
- 'body/bmi': ['BMI', 'BMI', 'human'],
- 'body/fat': ['Body Fat', '%', 'human'],
- 'body/weight': ['Weight', '', 'human'],
- 'devices/battery': ['Battery', None, None],
- 'sleep/awakeningsCount':
- ['Awakenings Count', 'times awaken', 'sleep'],
- 'sleep/efficiency': ['Sleep Efficiency', '%', 'sleep'],
- 'sleep/minutesAfterWakeup': ['Minutes After Wakeup', 'minutes', 'sleep'],
- 'sleep/minutesAsleep': ['Sleep Minutes Asleep', 'minutes', 'sleep'],
- 'sleep/minutesAwake': ['Sleep Minutes Awake', 'minutes', 'sleep'],
- 'sleep/minutesToFallAsleep':
- ['Sleep Minutes to Fall Asleep', 'minutes', 'sleep'],
- 'sleep/startTime': ['Sleep Start Time', None, 'clock'],
- 'sleep/timeInBed': ['Sleep Time in Bed', 'minutes', 'hotel']
-}
-
-FITBIT_MEASUREMENTS = {
- 'en_US': {
- 'duration': 'ms',
- 'distance': 'mi',
- 'elevation': 'ft',
- 'height': 'in',
- 'weight': 'lbs',
- 'body': 'in',
- 'liquids': 'fl. oz.',
- 'blood glucose': 'mg/dL',
- 'battery': '',
- },
- 'en_GB': {
- 'duration': 'milliseconds',
- 'distance': 'kilometers',
- 'elevation': 'meters',
- 'height': 'centimeters',
- 'weight': 'stone',
- 'body': 'centimeters',
- 'liquids': 'milliliters',
- 'blood glucose': 'mmol/L',
- 'battery': '',
- },
- 'metric': {
- 'duration': 'milliseconds',
- 'distance': 'kilometers',
- 'elevation': 'meters',
- 'height': 'centimeters',
- 'weight': 'kilograms',
- 'body': 'centimeters',
- 'liquids': 'milliliters',
- 'blood glucose': 'mmol/L',
- 'battery': '',
- }
-}
-
-BATTERY_LEVELS = {
- 'High': 100,
- 'Medium': 50,
- 'Low': 20,
- 'Empty': 0
-}
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES):
- vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]),
- vol.Optional(CONF_CLOCK_FORMAT, default='24H'):
- vol.In(['12H', '24H']),
- vol.Optional(CONF_UNIT_SYSTEM, default='default'):
- vol.In(['en_GB', 'en_US', 'metric', 'default']),
- vol.Optional(CONF_FILENAME, default=FITBIT_CONFIG_FILE):cv.string
-})
-
-
-def request_app_setup(hass, config, add_devices, config_path,
- discovery_info=None):
- """Assist user with configuring the Fitbit dev application."""
- configurator = hass.components.configurator
-
- # pylint: disable=unused-argument
- def fitbit_configuration_callback(callback_data):
- """Handle configuration updates."""
- config_path = hass.config.path(config.get(CONF_FILENAME))
- if os.path.isfile(config_path):
- config_file = load_json(config_path)
- if config_file == DEFAULT_CONFIG:
- error_msg = ("You didn't correctly modify the file",
- " please try again")
- configurator.notify_errors(_CONFIGURING['fitbit'],
- error_msg)
- else:
- setup_platform(hass, config, add_devices, discovery_info)
- else:
- setup_platform(hass, config, add_devices, discovery_info)
-
- start_url = "{}{}".format(hass.config.api.base_url,
- FITBIT_AUTH_CALLBACK_PATH)
-
- description = """Please create a Fitbit developer app at
- https://dev.fitbit.com/apps/new.
- For the OAuth 2.0 Application Type choose Personal.
- Set the Callback URL to {}.
- They will provide you a Client ID and secret.
- These need to be saved into the file located at: {}.
- Then come back here and hit the below button.
- """.format(start_url, config_path)
-
- submit = "I have saved my Client ID and Client Secret into {}.".format(config_path)
-
- _CONFIGURING['fitbit'] = configurator.request_config(
- 'Fitbit ( {} )'.format(config.get(CONF_FILENAME)), fitbit_configuration_callback,
- description=description, submit_caption=submit,
- description_image="/static/images/config_fitbit_app.png"
- )
-
-
-def request_oauth_completion(hass):
- """Request user complete Fitbit OAuth2 flow."""
- configurator = hass.components.configurator
- if "fitbit" in _CONFIGURING:
- configurator.notify_errors(
- _CONFIGURING['fitbit'], "Failed to register, please try again.")
-
- return
-
- # pylint: disable=unused-argument
- def fitbit_configuration_callback(callback_data):
- """Handle configuration updates."""
-
- start_url = '{}{}'.format(hass.config.api.base_url, FITBIT_AUTH_START)
-
- description = "Please authorize Fitbit by visiting {}".format(start_url)
-
- _CONFIGURING['fitbit'] = configurator.request_config(
- 'Fitbit', fitbit_configuration_callback,
- description=description,
- submit_caption="I have authorized Fitbit."
- )
-
-
-def setup_platform(hass, config, add_devices, discovery_info=None):
- """Set up the Fitbit sensor."""
- config_path = hass.config.path(config.get(CONF_FILENAME))
- if os.path.isfile(config_path):
- config_file = load_json(config_path)
- if config_file == DEFAULT_CONFIG:
- request_app_setup(
- hass, config, add_devices, config_path, discovery_info=None)
- return False
- else:
- config_file = save_json(config_path, DEFAULT_CONFIG)
- request_app_setup(
- hass, config, add_devices, config_path, discovery_info=None)
- return False
-
- if "fitbit" in _CONFIGURING:
- hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))
-
- import fitbit
-
- access_token = config_file.get(ATTR_ACCESS_TOKEN)
- refresh_token = config_file.get(ATTR_REFRESH_TOKEN)
- expires_at = config_file.get(ATTR_LAST_SAVED_AT)
- if None not in (access_token, refresh_token):
- authd_client = fitbit.Fitbit(config_file.get(ATTR_CLIENT_ID),
- config_file.get(ATTR_CLIENT_SECRET),
- access_token=access_token,
- refresh_token=refresh_token,
- expires_at=expires_at,
- refresh_cb=lambda x: None)
-
- if int(time.time()) - expires_at > 3600:
- authd_client.client.refresh_token()
-
- unit_system = config.get(CONF_UNIT_SYSTEM)
- if unit_system == 'default':
- authd_client.system = authd_client. \
- user_profile_get()["user"]["locale"]
- if authd_client.system != 'en_GB':
- if hass.config.units.is_metric:
- authd_client.system = 'metric'
- else:
- authd_client.system = 'en_US'
- else:
- authd_client.system = unit_system
-
- dev = []
- registered_devs = authd_client.get_devices()
- clock_format = config.get(CONF_CLOCK_FORMAT)
- for resource in config.get(CONF_MONITORED_RESOURCES):
-
- # monitor battery for all linked FitBit devices
- if resource == 'devices/battery':
- for dev_extra in registered_devs:
- dev.append(FitbitSensor(
- authd_client, config_path, resource,
- hass.config.units.is_metric, clock_format, dev_extra))
- else:
- dev.append(FitbitSensor(
- authd_client, config_path, resource,
- hass.config.units.is_metric, clock_format))
- add_devices(dev, True)
-
- else:
- oauth = fitbit.api.FitbitOauth2Client(
- config_file.get(ATTR_CLIENT_ID),
- config_file.get(ATTR_CLIENT_SECRET))
-
- redirect_uri = '{}{}'.format(hass.config.api.base_url,
- FITBIT_AUTH_CALLBACK_PATH)
-
- fitbit_auth_start_url, _ = oauth.authorize_token_url(
- redirect_uri=redirect_uri,
- scope=['activity', 'heartrate', 'nutrition', 'profile',
- 'settings', 'sleep', 'weight'])
-
- hass.http.register_redirect(FITBIT_AUTH_START, fitbit_auth_start_url)
- hass.http.register_view(FitbitAuthCallbackView(
- config, add_devices, oauth))
-
- request_oauth_completion(hass)
-
-
-class FitbitAuthCallbackView(HomeAssistantView):
- """Handle OAuth finish callback requests."""
-
- requires_auth = False
- url = FITBIT_AUTH_CALLBACK_PATH
- name = 'api:fitbit:callback'
-
- def __init__(self, config, add_devices, oauth):
- """Initialize the OAuth callback view."""
- self.config = config
- self.add_devices = add_devices
- self.oauth = oauth
-
- @callback
- def get(self, request):
- """Finish OAuth callback request."""
- from oauthlib.oauth2.rfc6749.errors import MismatchingStateError
- from oauthlib.oauth2.rfc6749.errors import MissingTokenError
-
- hass = request.app['hass']
- data = request.query
-
- response_message = """Fitbit has been successfully authorized!
- You can close this window now!"""
-
- result = None
- if data.get('code') is not None:
- redirect_uri = '{}{}'.format(
- hass.config.api.base_url, FITBIT_AUTH_CALLBACK_PATH)
-
- try:
- result = self.oauth.fetch_access_token(data.get('code'),
- redirect_uri)
- except MissingTokenError as error:
- _LOGGER.error("Missing token: %s", error)
- response_message = """Something went wrong when
- attempting authenticating with Fitbit. The error
- encountered was {}. Please try again!""".format(error)
- except MismatchingStateError as error:
- _LOGGER.error("Mismatched state, CSRF error: %s", error)
- response_message = """Something went wrong when
- attempting authenticating with Fitbit. The error
- encountered was {}. Please try again!""".format(error)
- else:
- _LOGGER.error("Unknown error when authing")
- response_message = """Something went wrong when
- attempting authenticating with Fitbit.
- An unknown error occurred. Please try again!
- """
-
- if result is None:
- _LOGGER.error("Unknown error when authing")
- response_message = """Something went wrong when
- attempting authenticating with Fitbit.
- An unknown error occurred. Please try again!
- """
-
- html_response = """
Fitbit Auth
-
{}
""".format(response_message)
-
- if result:
- config_contents = {
- ATTR_ACCESS_TOKEN: result.get('access_token'),
- ATTR_REFRESH_TOKEN: result.get('refresh_token'),
- ATTR_CLIENT_ID: self.oauth.client_id,
- ATTR_CLIENT_SECRET: self.oauth.client_secret,
- ATTR_LAST_SAVED_AT: int(time.time())
- }
- save_json(config.get(CONF_FILENAME), config_contents)
-
- hass.async_add_job(setup_platform, hass, self.config, self.add_devices)
-
- return html_response
-
-
-class FitbitSensor(Entity):
- """Implementation of a Fitbit sensor."""
-
- def __init__(self, client, config_path, resource_type,
- is_metric, clock_format, extra=None):
- """Initialize the Fitbit sensor."""
- self.client = client
- self.config_path = config_path
- self.resource_type = resource_type
- self.is_metric = is_metric
- self.clock_format = clock_format
- self.extra = extra
- self._name = FITBIT_RESOURCES_LIST[self.resource_type][0]
- if self.extra:
- self._name = '{0} Battery'.format(self.extra.get('deviceVersion'))
- unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1]
- if unit_type == "":
- split_resource = self.resource_type.split('/')
- try:
- measurement_system = FITBIT_MEASUREMENTS[self.client.system]
- except KeyError:
- if self.is_metric:
- measurement_system = FITBIT_MEASUREMENTS['metric']
- else:
- measurement_system = FITBIT_MEASUREMENTS['en_US']
- unit_type = measurement_system[split_resource[-1]]
- self._unit_of_measurement = unit_type
- self._state = 0
-
- @property
- def name(self):
- """Return the name of the sensor."""
- return self._name
-
- @property
- def state(self):
- """Return the state of the sensor."""
- return self._state
-
- @property
- def unit_of_measurement(self):
- """Return the unit of measurement of this entity, if any."""
- return self._unit_of_measurement
-
- @property
- def icon(self):
- """Icon to use in the frontend, if any."""
- if self.resource_type == 'devices/battery' and self.extra:
- battery_level = BATTERY_LEVELS[self.extra.get('battery')]
- return icon_for_battery_level(battery_level=battery_level,
- charging=None)
- return 'mdi:{}'.format(FITBIT_RESOURCES_LIST[self.resource_type][2])
-
- @property
- def device_state_attributes(self):
- """Return the state attributes."""
- attrs = {}
-
- attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
-
- if self.extra:
- attrs['model'] = self.extra.get('deviceVersion')
- attrs['type'] = self.extra.get('type').lower()
-
- return attrs
-
- def update(self):
- """Get the latest data from the Fitbit API and update the states."""
- if self.resource_type == 'devices/battery' and self.extra:
- self._state = self.extra.get('battery')
- else:
- container = self.resource_type.replace("/", "-")
- response = self.client.time_series(self.resource_type, period='7d')
- raw_state = response[container][-1].get('value')
- if self.resource_type == 'activities/distance':
- self._state = format(float(raw_state), '.2f')
- elif self.resource_type == 'activities/tracker/distance':
- self._state = format(float(raw_state), '.2f')
- elif self.resource_type == 'body/bmi':
- self._state = format(float(raw_state), '.1f')
- elif self.resource_type == 'body/fat':
- self._state = format(float(raw_state), '.1f')
- elif self.resource_type == 'body/weight':
- self._state = format(float(raw_state), '.1f')
- elif self.resource_type == 'sleep/startTime':
- if raw_state == '':
- self._state = '-'
- elif self.clock_format == '12H':
- hours, minutes = raw_state.split(':')
- hours, minutes = int(hours), int(minutes)
- setting = 'AM'
- if hours > 12:
- setting = 'PM'
- hours -= 12
- elif hours == 0:
- hours = 12
- self._state = '{}:{:02d} {}'.format(hours, minutes,
- setting)
- else:
- self._state = raw_state
- else:
- if self.is_metric:
- self._state = raw_state
- else:
- try:
- self._state = '{0:,}'.format(int(raw_state))
- except TypeError:
- self._state = raw_state
-
- if self.resource_type == 'activities/heart':
- self._state = response[container][-1]. \
- get('value').get('restingHeartRate')
-
- token = self.client.client.session.token
- config_contents = {
- ATTR_ACCESS_TOKEN: token.get('access_token'),
- ATTR_REFRESH_TOKEN: token.get('refresh_token'),
- ATTR_CLIENT_ID: self.client.client.client_id,
- ATTR_CLIENT_SECRET: self.client.client.client_secret,
- ATTR_LAST_SAVED_AT: int(time.time())
- }
- save_json(self.config_path, config_contents)
diff --git a/config/custom_components/sensor/minecraft.py.disabled b/config/custom_components/sensor/minecraft.py.disabled
deleted file mode 100755
index ee2f6118..00000000
--- a/config/custom_components/sensor/minecraft.py.disabled
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-Sensor to check the status of a Minecraft server.
-
-"""
-import logging
-from homeassistant.helpers.entity import Entity
-ATTR_USERS = 'users_online'
-ATTR_MAX = 'users_max'
-ATTR_MOTD = 'MOTD'
-ATTR_VERSION = 'Version'
-ICON = 'mdi:minecraft'
-REQUIREMENTS = ['mcstatus==2.1']
-
-# pylint: disable=unused-argument
-def setup_platform(hass, config, add_devices, discovery_info=None):
- """Setup the Minecraft server platform."""
- from mcstatus import MinecraftServer as mcserver
- logger = logging.getLogger(__name__)
-
- server = config.get('server')
- name = config.get('name')
-
- if server is None:
- logger.error('No server specified')
- return False
- elif name is None:
- logger.error('No name specified')
- return False
-
- add_devices([
- MCServerSensor(server, name, mcserver)
- ])
-
-
-class MCServerSensor(Entity):
- """A class for the Minecraft server."""
-
- # pylint: disable=abstract-method
- def __init__(self, server, name, mcserver):
- """Initialize the sensor."""
- self._mcserver = mcserver
- self._server = server
- self._name = name
- self.update()
-
- @property
- def name(self):
- """Return the name of the server."""
- return self._name
-
- @property
- def state(self):
- """Return the state of the sensor."""
- return self._state
-
- # pylint: disable=no-member
- def update(self):
- """Update device state."""
- status = self._mcserver.lookup(self._server).status()
- query = self._mcserver.lookup(self._server).query()
- self._state = status.players.online
- #self._max = str(status.players.max)
- self._max = status.players.max
- self._users = query.players.names
- self._motd = query.motd
- self._version = query.software.version
-
-
- @property
- def device_state_attributes(self):
- """Return the state attributes."""
- return {
- ATTR_USERS: self._users,
- ATTR_MAX: self._max,
- ATTR_MOTD: self._motd,
- ATTR_VERSION: self._version
- }
-
- @property
- def icon(self):
- """Return the icon to use in the frontend."""
- return ICON
diff --git a/config/packages/alarm_clock.yaml b/config/packages/alarm_clock.yaml.DISABLED
similarity index 100%
rename from config/packages/alarm_clock.yaml
rename to config/packages/alarm_clock.yaml.DISABLED
diff --git a/config/packages/floorplan.yaml b/config/packages/floorplan.yaml.DISABLED
similarity index 100%
rename from config/packages/floorplan.yaml
rename to config/packages/floorplan.yaml.DISABLED