Disabling FloorPlan Media Players for now. #565
This commit is contained in:
parent
f63ff35410
commit
cd02600c7a
|
@ -1,61 +0,0 @@
|
||||||
<h1 align="center">
|
|
||||||
<a name="logo" href="http://www.vCloudInfo.com/tag/iot"><img src="https://raw.githubusercontent.com/CCOSTAN/Home-AssistantConfig/master/config/www/custom_ui/floorplan/images/branding/twitter_profile.png" alt="Bear Stone Smart Home" width="200"></a>
|
|
||||||
<br>
|
|
||||||
Bear Stone Smart Home Documentation
|
|
||||||
</h1>
|
|
||||||
<h4 align="center">Be sure to :star: my configuration repo so you can keep up to date on any daily progress!</h4>
|
|
||||||
<div align="center">
|
|
||||||
<h4>
|
|
||||||
<a href="https://travis-ci.org/CCOSTAN/Home-AssistantConfig"><img src="https://travis-ci.org/CCOSTAN/Home-AssistantConfig.svg?branch=master"/></a>
|
|
||||||
<a href="https://github.com/CCOSTAN/Home-AssistantConfig/stargazers"><img src="https://img.shields.io/github/stars/CCOSTAN/Home-AssistantConfig.svg?style=plasticr"/></a>
|
|
||||||
<a href="https://github.com/CCOSTAN/Home-AssistantConfig/commits/master"><img src="https://img.shields.io/github/last-commit/CCOSTAN/Home-AssistantConfig.svg?style=plasticr"/></a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<p><font size="3">
|
|
||||||
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.</p>
|
|
||||||
<div align="center"><a name="menu"></a>
|
|
||||||
<h4>
|
|
||||||
<a href="http://www.vCloudInfo.com/tag/iot">
|
|
||||||
Blog
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://github.com/CCOSTAN/Home-AssistantConfig#devices">
|
|
||||||
Devices
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://github.com/CCOSTAN/Home-AssistantConfig/issues">
|
|
||||||
Todo List
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://twitter.com/BearStoneHA">
|
|
||||||
Smart Home Stats
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://www.facebook.com/BearStoneHA">
|
|
||||||
Facebook
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://github.com/CCOSTAN/Home-AssistantConfig/tree/master/config">
|
|
||||||
Code
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://github.com/CCOSTAN/Home-AssistantConfig#diagram">
|
|
||||||
Diagram
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://youtube.com/vCloudInfo">
|
|
||||||
Youtube
|
|
||||||
</a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://amzn.to/2HXSx2M">
|
|
||||||
Merch
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<a name="bottom" href="https://github.com/CCOSTAN/Home-AssistantConfig#logo"><img align="right" border="0" src="https://raw.githubusercontent.com/CCOSTAN/Home-AssistantConfig/master/config/www/custom_ui/floorplan/images/branding/up_arrow.png" width="25" ></a>
|
|
||||||
|
|
||||||
**Still have questions on my Config?**
|
|
||||||
**Message me on twitter :** [@CCostan](https://twitter.com/ccostan) or [@BearStoneHA](https://twitter.com/BearStoneHA)
|
|
||||||
<!-- Subscribe Section -->
|
|
||||||
<a href="http://eepurl.com/dmXFYz"><img align="center" border="0" src="https://raw.githubusercontent.com/CCOSTAN/Home-AssistantConfig/master/config/www/custom_ui/floorplan/images/branding/email_link.png" height="50" ></a>.
|
|
||||||
<!-- Subscribe Section END-->
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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')
|
|
|
@ -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 = """<html><head><title>Fitbit Auth</title></head>
|
|
||||||
<body><h1>{}</h1></body></html>""".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)
|
|
|
@ -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
|
|
Loading…
Reference in New Issue