From cd02600c7a7d7af965192b5eef2948635fb7bc1f Mon Sep 17 00:00:00 2001 From: ccostan Date: Wed, 19 Jun 2019 11:09:21 -0400 Subject: [PATCH] Disabling FloorPlan Media Players for now. #565 --- .../custom_components/media_player/README.md | 61 - .../media_player/alexa.py.DISABLED | 1077 ----------------- .../media_player/floorplan_speaker.py | 160 --- .../sensor/fitbit.py.disabled | 501 -------- .../sensor/minecraft.py.disabled | 82 -- ...m_clock.yaml => alarm_clock.yaml.DISABLED} | 0 ...floorplan.yaml => floorplan.yaml.DISABLED} | 0 7 files changed, 1881 deletions(-) delete mode 100755 config/custom_components/media_player/README.md delete mode 100755 config/custom_components/media_player/alexa.py.DISABLED delete mode 100755 config/custom_components/media_player/floorplan_speaker.py delete mode 100755 config/custom_components/sensor/fitbit.py.disabled delete mode 100755 config/custom_components/sensor/minecraft.py.disabled rename config/packages/{alarm_clock.yaml => alarm_clock.yaml.DISABLED} (100%) rename config/packages/{floorplan.yaml => floorplan.yaml.DISABLED} (100%) 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 @@ -

- Bear Stone Smart Home -
- Bear Stone Smart Home Documentation -

-

Be sure to :star: my configuration repo so you can keep up to date on any daily progress!

-
-

- - - -

-
-

-This is a Custom Component by pkozul to allow me to use my Floorplan installation as a Media Player. This allows me to send all TTS and home notifications to the Fire Tablets I use with Floorplan.

-
-

- - Blog - - | - - Devices - - | - - Todo List - - | - - Smart Home Stats - - | - - Facebook - - | - - Code - - | - - Diagram - - | - - Youtube - - | - - Merch - -

- - - -**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