mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Merge pull request #203 from balloob/auto-dependency
Automatic dependency management
This commit is contained in:
commit
590b6ba6e7
4
.gitignore
vendored
4
.gitignore
vendored
@ -65,3 +65,7 @@ nosetests.xml
|
||||
.pydevproject
|
||||
|
||||
.python-version
|
||||
|
||||
# venv stuff
|
||||
pyvenv.cfg
|
||||
pip-selfcheck.json
|
||||
|
@ -39,8 +39,8 @@ Running Home Assistant requires that [Python](https://www.python.org/) 3.4 and t
|
||||
|
||||
```python
|
||||
git clone --recursive https://github.com/balloob/home-assistant.git
|
||||
python3 -m venv home-assistant
|
||||
cd home-assistant
|
||||
python3 -m pip install --user -r requirements.txt
|
||||
python3 -m homeassistant --open-ui
|
||||
```
|
||||
|
||||
|
@ -4,15 +4,9 @@ from __future__ import print_function
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import importlib
|
||||
import subprocess
|
||||
|
||||
|
||||
# Home Assistant dependencies, mapped module -> package name
|
||||
DEPENDENCIES = {
|
||||
'requests': 'requests',
|
||||
'yaml': 'pyyaml',
|
||||
'pytz': 'pytz',
|
||||
}
|
||||
DEPENDENCIES = ['requests>=2.0', 'pyyaml>=3.11', 'pytz>=2015.2']
|
||||
|
||||
|
||||
def validate_python():
|
||||
@ -24,21 +18,29 @@ def validate_python():
|
||||
sys.exit()
|
||||
|
||||
|
||||
# Copy of homeassistant.util.package because we can't import yet
|
||||
def install_package(package):
|
||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||
Return boolean if install successfull."""
|
||||
args = ['python3', '-m', 'pip', 'install', '--quiet', package]
|
||||
if sys.base_prefix == sys.prefix:
|
||||
args.append('--user')
|
||||
return not subprocess.call(args)
|
||||
|
||||
|
||||
def validate_dependencies():
|
||||
""" Validate all dependencies that HA uses. """
|
||||
print("Validating dependencies...")
|
||||
import_fail = False
|
||||
|
||||
for module, name in DEPENDENCIES.items():
|
||||
try:
|
||||
importlib.import_module(module)
|
||||
except ImportError:
|
||||
for requirement in DEPENDENCIES:
|
||||
if not install_package(requirement):
|
||||
import_fail = True
|
||||
print(
|
||||
'Fatal Error: Unable to find dependency {}'.format(name))
|
||||
print('Fatal Error: Unable to install dependency', requirement)
|
||||
|
||||
if import_fail:
|
||||
print(("Install dependencies by running: "
|
||||
"pip3 install -r requirements.txt"))
|
||||
"python3 -m pip install -r requirements.txt"))
|
||||
sys.exit()
|
||||
|
||||
|
||||
|
@ -14,8 +14,9 @@ import logging
|
||||
from collections import defaultdict
|
||||
|
||||
import homeassistant
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.dt as date_util
|
||||
import homeassistant.util.package as pkg_util
|
||||
import homeassistant.util.location as loc_util
|
||||
import homeassistant.config as config_util
|
||||
import homeassistant.loader as loader
|
||||
import homeassistant.components as core_components
|
||||
@ -60,6 +61,17 @@ def setup_component(hass, domain, config=None):
|
||||
return True
|
||||
|
||||
|
||||
def _handle_requirements(component, name):
|
||||
""" Installs requirements for component. """
|
||||
if hasattr(component, 'REQUIREMENTS'):
|
||||
for req in component.REQUIREMENTS:
|
||||
if not pkg_util.install_package(req):
|
||||
_LOGGER.error('Not initializing %s because could not install '
|
||||
'dependency %s', name, req)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _setup_component(hass, domain, config):
|
||||
""" Setup a component for Home Assistant. """
|
||||
component = loader.get_component(domain)
|
||||
@ -74,6 +86,9 @@ def _setup_component(hass, domain, config):
|
||||
|
||||
return False
|
||||
|
||||
if not _handle_requirements(component, domain):
|
||||
return False
|
||||
|
||||
try:
|
||||
if component.setup(hass, config):
|
||||
hass.config.components.append(component.DOMAIN)
|
||||
@ -109,18 +124,22 @@ def prepare_setup_platform(hass, config, domain, platform_name):
|
||||
if platform is None:
|
||||
return None
|
||||
|
||||
# Already loaded or no dependencies
|
||||
elif (platform_path in hass.config.components or
|
||||
not hasattr(platform, 'DEPENDENCIES')):
|
||||
# Already loaded
|
||||
elif platform_path in hass.config.components:
|
||||
return platform
|
||||
|
||||
# Load dependencies
|
||||
for component in platform.DEPENDENCIES:
|
||||
if not setup_component(hass, component, config):
|
||||
_LOGGER.error(
|
||||
'Unable to prepare setup for platform %s because dependency '
|
||||
'%s could not be initialized', platform_path, component)
|
||||
return None
|
||||
if hasattr(platform, 'DEPENDENCIES'):
|
||||
for component in platform.DEPENDENCIES:
|
||||
if not setup_component(hass, component, config):
|
||||
_LOGGER.error(
|
||||
'Unable to prepare setup for platform %s because '
|
||||
'dependency %s could not be initialized', platform_path,
|
||||
component)
|
||||
return None
|
||||
|
||||
if not _handle_requirements(platform, platform_path):
|
||||
return None
|
||||
|
||||
return platform
|
||||
|
||||
@ -276,7 +295,7 @@ def process_ha_core_config(hass, config):
|
||||
|
||||
_LOGGER.info('Auto detecting location and temperature unit')
|
||||
|
||||
info = util.detect_location_info()
|
||||
info = loc_util.detect_location_info()
|
||||
|
||||
if info is None:
|
||||
_LOGGER.error('Could not detect location information')
|
||||
|
@ -27,8 +27,10 @@ every initialization the pins are set to off/low.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from PyMata.pymata import PyMata
|
||||
import serial
|
||||
try:
|
||||
from PyMata.pymata import PyMata
|
||||
except ImportError:
|
||||
PyMata = None
|
||||
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||
@ -36,6 +38,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||
|
||||
DOMAIN = "arduino"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['PyMata==2.07a']
|
||||
BOARD = None
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -43,12 +46,18 @@ _LOGGER = logging.getLogger(__name__)
|
||||
def setup(hass, config):
|
||||
""" Setup the Arduino component. """
|
||||
|
||||
global PyMata # pylint: disable=invalid-name
|
||||
if PyMata is None:
|
||||
from PyMata.pymata import PyMata as PyMata_
|
||||
PyMata = PyMata_
|
||||
|
||||
import serial
|
||||
|
||||
if not validate_config(config,
|
||||
{DOMAIN: ['port']},
|
||||
_LOGGER):
|
||||
return False
|
||||
|
||||
# pylint: disable=global-statement
|
||||
global BOARD
|
||||
try:
|
||||
BOARD = ArduinoBoard(config[DOMAIN]['port'])
|
||||
|
@ -22,6 +22,7 @@ from homeassistant.const import (
|
||||
|
||||
DOMAIN = "discovery"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['zeroconf>=0.16.0']
|
||||
|
||||
SCAN_INTERVAL = 300 # seconds
|
||||
|
||||
|
@ -22,6 +22,7 @@ from homeassistant.const import (
|
||||
# homeassistant constants
|
||||
DOMAIN = "isy994"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['PyISY>=1.0.5']
|
||||
DISCOVER_LIGHTS = "isy994.lights"
|
||||
DISCOVER_SWITCHES = "isy994.switches"
|
||||
DISCOVER_SENSORS = "isy994.sensors"
|
||||
|
@ -14,6 +14,7 @@ from homeassistant.const import (
|
||||
|
||||
DOMAIN = "keyboard"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['pyuserinput>=0.1.9']
|
||||
|
||||
|
||||
def volume_up(hass):
|
||||
|
@ -56,6 +56,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
from homeassistant.components import group, discovery, wink, isy994
|
||||
@ -243,9 +244,9 @@ def setup(hass, config):
|
||||
|
||||
if len(rgb_color) == 3:
|
||||
params[ATTR_XY_COLOR] = \
|
||||
util.color_RGB_to_xy(int(rgb_color[0]),
|
||||
int(rgb_color[1]),
|
||||
int(rgb_color[2]))
|
||||
color_util.color_RGB_to_xy(int(rgb_color[0]),
|
||||
int(rgb_color[1]),
|
||||
int(rgb_color[2]))
|
||||
|
||||
except (TypeError, ValueError):
|
||||
# TypeError if rgb_color is not iterable
|
||||
|
@ -12,6 +12,7 @@ from homeassistant.components.light import (
|
||||
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_EFFECT,
|
||||
EFFECT_COLORLOOP)
|
||||
|
||||
REQUIREMENTS = ['phue>=0.8']
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
||||
|
||||
|
@ -27,15 +27,12 @@ from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
from homeassistant.components.light import Light, ATTR_BRIGHTNESS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['ledcontroller>=1.0.7']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
""" Gets the LimitlessLED lights. """
|
||||
try:
|
||||
import ledcontroller
|
||||
except ImportError:
|
||||
_LOGGER.exception("Error while importing dependency ledcontroller.")
|
||||
return
|
||||
import ledcontroller
|
||||
|
||||
led = ledcontroller.LedController(config['host'])
|
||||
|
||||
|
@ -10,7 +10,6 @@ import logging
|
||||
|
||||
try:
|
||||
import pychromecast
|
||||
import pychromecast.controllers.youtube as youtube
|
||||
except ImportError:
|
||||
pychromecast = None
|
||||
|
||||
@ -25,6 +24,7 @@ from homeassistant.components.media_player import (
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
|
||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
|
||||
|
||||
REQUIREMENTS = ['pychromecast>=0.6.9']
|
||||
CONF_IGNORE_CEC = 'ignore_cec'
|
||||
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
|
||||
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
@ -36,14 +36,12 @@ KNOWN_HOSTS = []
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the cast platform. """
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
global pychromecast # pylint: disable=invalid-name
|
||||
if pychromecast is None:
|
||||
logger.error((
|
||||
"Failed to import pychromecast. Did you maybe not install the "
|
||||
"'pychromecast' dependency?"))
|
||||
import pychromecast as pychromecast_
|
||||
pychromecast = pychromecast_
|
||||
|
||||
return False
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# import CEC IGNORE attributes
|
||||
ignore_cec = config.get(CONF_IGNORE_CEC, [])
|
||||
@ -81,6 +79,7 @@ class CastDevice(MediaPlayerDevice):
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
def __init__(self, host):
|
||||
import pychromecast.controllers.youtube as youtube
|
||||
self.cast = pychromecast.Chromecast(host)
|
||||
self.youtube = youtube.YouTubeController()
|
||||
self.cast.register_handler(self.youtube)
|
||||
|
@ -49,6 +49,7 @@ except ImportError:
|
||||
jsonrpc_requests = None
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['jsonrpc-requests>=0.1']
|
||||
|
||||
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
||||
@ -58,12 +59,10 @@ SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the kodi platform. """
|
||||
|
||||
global jsonrpc_requests # pylint: disable=invalid-name
|
||||
if jsonrpc_requests is None:
|
||||
_LOGGER.exception(
|
||||
"Unable to import jsonrpc_requests. "
|
||||
"Did you maybe not install the 'jsonrpc-requests' pip module?")
|
||||
|
||||
return False
|
||||
import jsonrpc_requests as jsonrpc_requests_
|
||||
jsonrpc_requests = jsonrpc_requests_
|
||||
|
||||
add_devices([
|
||||
KodiDevice(
|
||||
|
@ -48,7 +48,7 @@ from homeassistant.components.media_player import (
|
||||
MEDIA_TYPE_MUSIC)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['python-mpd2>=0.5.4']
|
||||
|
||||
SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
|
||||
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
|
||||
@ -62,12 +62,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
port = config.get('port', 6600)
|
||||
location = config.get('location', 'MPD')
|
||||
|
||||
global mpd # pylint: disable=invalid-name
|
||||
if mpd is None:
|
||||
_LOGGER.exception(
|
||||
"Unable to import mpd2. "
|
||||
"Did you maybe not install the 'python-mpd2' package?")
|
||||
|
||||
return False
|
||||
import mpd as mpd_
|
||||
mpd = mpd_
|
||||
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
|
@ -28,6 +28,7 @@ from homeassistant.components.notify import (
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['pushbullet.py>=0.7.1']
|
||||
|
||||
|
||||
def get_service(hass, config):
|
||||
|
@ -42,6 +42,7 @@ from homeassistant.components.notify import (
|
||||
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
|
||||
REQUIREMENTS = ['python-pushover>=0.2']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -47,6 +47,8 @@ from homeassistant.helpers import validate_config
|
||||
from homeassistant.components.notify import (
|
||||
DOMAIN, ATTR_TITLE, BaseNotificationService)
|
||||
|
||||
REQUIREMENTS = ['sleekxmpp>=1.3.1']
|
||||
|
||||
|
||||
def get_service(hass, config):
|
||||
""" Get the Jabber (XMPP) notification service. """
|
||||
|
@ -71,6 +71,7 @@ from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
|
||||
REQUIREMENTS = ['blockchain>=1.1.2']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
OPTION_TYPES = {
|
||||
'wallet': ['Wallet balance', 'BTC'],
|
||||
|
@ -50,7 +50,10 @@ Details for the API : https://developer.forecast.io/docs/v2
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import forecastio
|
||||
try:
|
||||
import forecastio
|
||||
except ImportError:
|
||||
forecastio = None
|
||||
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
@ -79,6 +82,11 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Get the Forecast.io sensor. """
|
||||
|
||||
global forecastio # pylint: disable=invalid-name
|
||||
if forecastio is None:
|
||||
import forecastio as forecastio_
|
||||
forecastio = forecastio_
|
||||
|
||||
if None in (hass.config.latitude, hass.config.longitude):
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
@ -39,6 +39,7 @@ ATTR_NODE_ID = "node_id"
|
||||
ATTR_CHILD_ID = "child_id"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['pyserial>=2.7']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -48,6 +48,7 @@ from homeassistant.util import Throttle
|
||||
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['pywm>=2.2.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SENSOR_TYPES = {
|
||||
'weather': ['Condition', ''],
|
||||
|
@ -66,6 +66,7 @@ import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
REQUIREMENTS = ['psutil>=3.0.0']
|
||||
SENSOR_TYPES = {
|
||||
'disk_use_percent': ['Disk Use', '%'],
|
||||
'disk_use': ['Disk Use', 'GiB'],
|
||||
|
@ -67,6 +67,7 @@ from transmissionrpc.error import TransmissionError
|
||||
|
||||
import logging
|
||||
|
||||
REQUIREMENTS = ['transmissionrpc>=0.11']
|
||||
SENSOR_TYPES = {
|
||||
'current_status': ['Status', ''],
|
||||
'download_speed': ['Down Speed', 'MB/s'],
|
||||
|
@ -25,7 +25,7 @@ from datetime import timedelta
|
||||
try:
|
||||
import ephem
|
||||
except ImportError:
|
||||
# Error will be raised during setup
|
||||
# Will be fixed during setup
|
||||
ephem = None
|
||||
|
||||
import homeassistant.util.dt as dt_util
|
||||
@ -33,6 +33,7 @@ from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.components.scheduler import ServiceEventListener
|
||||
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['pyephem>=3.7']
|
||||
DOMAIN = "sun"
|
||||
ENTITY_ID = "sun.sun"
|
||||
|
||||
@ -100,9 +101,10 @@ def setup(hass, config):
|
||||
""" Tracks the state of the sun. """
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
global ephem # pylint: disable=invalid-name
|
||||
if ephem is None:
|
||||
logger.exception("Error while importing dependency ephem.")
|
||||
return False
|
||||
import ephem as ephem_
|
||||
ephem = ephem_
|
||||
|
||||
if None in (hass.config.latitude, hass.config.longitude):
|
||||
logger.error("Latitude or longitude not set in Home Assistant config")
|
||||
|
@ -53,7 +53,7 @@ except ImportError:
|
||||
hikvision.api = None
|
||||
|
||||
_LOGGING = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['hikvision>=0.4']
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
|
@ -54,6 +54,7 @@ from transmissionrpc.error import TransmissionError
|
||||
import logging
|
||||
|
||||
_LOGGING = logging.getLogger(__name__)
|
||||
REQUIREMENTS = ['transmissionrpc>=0.11']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -6,6 +6,8 @@ import logging
|
||||
from homeassistant.components.thermostat import ThermostatDevice
|
||||
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS)
|
||||
|
||||
REQUIREMENTS = ['python-nest>=2.3.1']
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.const import (
|
||||
|
||||
DOMAIN = "zwave"
|
||||
DEPENDENCIES = []
|
||||
REQUIREMENTS = ['pydispatcher>=2.0.5']
|
||||
|
||||
CONF_USB_STICK_PATH = "usb_path"
|
||||
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
|
||||
|
@ -11,7 +11,7 @@ from homeassistant import HomeAssistantError
|
||||
from homeassistant.const import (
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||
CONF_TIME_ZONE)
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.location as loc_util
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -55,7 +55,7 @@ def create_default_config(config_dir, detect_location=True):
|
||||
|
||||
info = {attr: default for attr, default, *_ in DEFAULT_CONFIG}
|
||||
|
||||
location_info = detect_location and util.detect_location_info()
|
||||
location_info = detect_location and loc_util.detect_location_info()
|
||||
|
||||
if location_info:
|
||||
if location_info.use_fahrenheit:
|
||||
|
@ -16,8 +16,6 @@ import random
|
||||
import string
|
||||
from functools import wraps
|
||||
|
||||
import requests
|
||||
|
||||
# DEPRECATED AS OF 4/27/2015 - moved to homeassistant.util.dt package
|
||||
# pylint: disable=unused-import
|
||||
from .dt import ( # noqa
|
||||
@ -64,46 +62,6 @@ def repr_helper(inp):
|
||||
return str(inp)
|
||||
|
||||
|
||||
# Taken from: http://www.cse.unr.edu/~quiroz/inc/colortransforms.py
|
||||
# License: Code is given as is. Use at your own risk and discretion.
|
||||
# pylint: disable=invalid-name
|
||||
def color_RGB_to_xy(R, G, B):
|
||||
""" Convert from RGB color to XY color. """
|
||||
if R + G + B == 0:
|
||||
return 0, 0
|
||||
|
||||
var_R = (R / 255.)
|
||||
var_G = (G / 255.)
|
||||
var_B = (B / 255.)
|
||||
|
||||
if var_R > 0.04045:
|
||||
var_R = ((var_R + 0.055) / 1.055) ** 2.4
|
||||
else:
|
||||
var_R /= 12.92
|
||||
|
||||
if var_G > 0.04045:
|
||||
var_G = ((var_G + 0.055) / 1.055) ** 2.4
|
||||
else:
|
||||
var_G /= 12.92
|
||||
|
||||
if var_B > 0.04045:
|
||||
var_B = ((var_B + 0.055) / 1.055) ** 2.4
|
||||
else:
|
||||
var_B /= 12.92
|
||||
|
||||
var_R *= 100
|
||||
var_G *= 100
|
||||
var_B *= 100
|
||||
|
||||
# Observer. = 2 deg, Illuminant = D65
|
||||
X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
|
||||
Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
|
||||
Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
|
||||
|
||||
# Convert XYZ to xy, see CIE 1931 color space on wikipedia
|
||||
return X / (X + Y + Z), Y / (X + Y + Z)
|
||||
|
||||
|
||||
def convert(value, to_type, default=None):
|
||||
""" Converts value to to_type, returns default if fails. """
|
||||
try:
|
||||
@ -154,32 +112,6 @@ def get_random_string(length=10):
|
||||
return ''.join(generator.choice(source_chars) for _ in range(length))
|
||||
|
||||
|
||||
LocationInfo = collections.namedtuple(
|
||||
"LocationInfo",
|
||||
['ip', 'country_code', 'country_name', 'region_code', 'region_name',
|
||||
'city', 'zip_code', 'time_zone', 'latitude', 'longitude',
|
||||
'use_fahrenheit'])
|
||||
|
||||
|
||||
def detect_location_info():
|
||||
""" Detect location information. """
|
||||
try:
|
||||
raw_info = requests.get(
|
||||
'https://freegeoip.net/json/', timeout=5).json()
|
||||
except requests.RequestException:
|
||||
return
|
||||
|
||||
data = {key: raw_info.get(key) for key in LocationInfo._fields}
|
||||
|
||||
# From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
|
||||
# the Cayman Islands, Palau, and the United States and associated
|
||||
# territories of American Samoa and the U.S. Virgin Islands
|
||||
data['use_fahrenheit'] = data['country_code'] in (
|
||||
'BS', 'BZ', 'KY', 'PW', 'US', 'AS', 'VI')
|
||||
|
||||
return LocationInfo(**data)
|
||||
|
||||
|
||||
class OrderedEnum(enum.Enum):
|
||||
""" Taken from Python 3.4.0 docs. """
|
||||
# pylint: disable=no-init, too-few-public-methods
|
||||
|
41
homeassistant/util/color.py
Normal file
41
homeassistant/util/color.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""Color util methods."""
|
||||
|
||||
|
||||
# Taken from: http://www.cse.unr.edu/~quiroz/inc/colortransforms.py
|
||||
# License: Code is given as is. Use at your own risk and discretion.
|
||||
# pylint: disable=invalid-name
|
||||
def color_RGB_to_xy(R, G, B):
|
||||
""" Convert from RGB color to XY color. """
|
||||
if R + G + B == 0:
|
||||
return 0, 0
|
||||
|
||||
var_R = (R / 255.)
|
||||
var_G = (G / 255.)
|
||||
var_B = (B / 255.)
|
||||
|
||||
if var_R > 0.04045:
|
||||
var_R = ((var_R + 0.055) / 1.055) ** 2.4
|
||||
else:
|
||||
var_R /= 12.92
|
||||
|
||||
if var_G > 0.04045:
|
||||
var_G = ((var_G + 0.055) / 1.055) ** 2.4
|
||||
else:
|
||||
var_G /= 12.92
|
||||
|
||||
if var_B > 0.04045:
|
||||
var_B = ((var_B + 0.055) / 1.055) ** 2.4
|
||||
else:
|
||||
var_B /= 12.92
|
||||
|
||||
var_R *= 100
|
||||
var_G *= 100
|
||||
var_B *= 100
|
||||
|
||||
# Observer. = 2 deg, Illuminant = D65
|
||||
X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
|
||||
Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
|
||||
Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
|
||||
|
||||
# Convert XYZ to xy, see CIE 1931 color space on wikipedia
|
||||
return X / (X + Y + Z), Y / (X + Y + Z)
|
7
homeassistant/util/environment.py
Normal file
7
homeassistant/util/environment.py
Normal file
@ -0,0 +1,7 @@
|
||||
""" Environement helpers. """
|
||||
import sys
|
||||
|
||||
|
||||
def is_virtual():
|
||||
""" Return if we run in a virtual environtment. """
|
||||
return sys.base_prefix != sys.prefix
|
30
homeassistant/util/location.py
Normal file
30
homeassistant/util/location.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""Module with location helpers."""
|
||||
import collections
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
LocationInfo = collections.namedtuple(
|
||||
"LocationInfo",
|
||||
['ip', 'country_code', 'country_name', 'region_code', 'region_name',
|
||||
'city', 'zip_code', 'time_zone', 'latitude', 'longitude',
|
||||
'use_fahrenheit'])
|
||||
|
||||
|
||||
def detect_location_info():
|
||||
""" Detect location information. """
|
||||
try:
|
||||
raw_info = requests.get(
|
||||
'https://freegeoip.net/json/', timeout=5).json()
|
||||
except requests.RequestException:
|
||||
return
|
||||
|
||||
data = {key: raw_info.get(key) for key in LocationInfo._fields}
|
||||
|
||||
# From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
|
||||
# the Cayman Islands, Palau, and the United States and associated
|
||||
# territories of American Samoa and the U.S. Virgin Islands
|
||||
data['use_fahrenheit'] = data['country_code'] in (
|
||||
'BS', 'BZ', 'KY', 'PW', 'US', 'AS', 'VI')
|
||||
|
||||
return LocationInfo(**data)
|
19
homeassistant/util/package.py
Normal file
19
homeassistant/util/package.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""Helpers to install PyPi packages."""
|
||||
import subprocess
|
||||
|
||||
from . import environment as env
|
||||
|
||||
# If we are not in a virtual environment, install in user space
|
||||
INSTALL_USER = not env.is_virtual()
|
||||
|
||||
|
||||
def install_package(package, upgrade=False, user=INSTALL_USER):
|
||||
"""Install a package on PyPi. Accepts pip compatible package strings.
|
||||
Return boolean if install successfull."""
|
||||
# Not using 'import pip; pip.main([])' because it breaks the logger
|
||||
args = ['python3', '-m', 'pip', 'install', '--quiet', package]
|
||||
if upgrade:
|
||||
args.append('--upgrade')
|
||||
if user:
|
||||
args.append('--user')
|
||||
return not subprocess.call(args)
|
4
pylintrc
4
pylintrc
@ -9,13 +9,15 @@ reports=no
|
||||
# abstract-class-little-used - Prevents from setting right foundation
|
||||
# abstract-class-not-used - is flaky, should not show up but does
|
||||
# unused-argument - generic callbacks and setup methods create a lot of warnings
|
||||
# global-statement - used for the on-demand requirement installation
|
||||
disable=
|
||||
locally-disabled,
|
||||
duplicate-code,
|
||||
cyclic-import,
|
||||
abstract-class-little-used,
|
||||
abstract-class-not-used,
|
||||
unused-argument
|
||||
unused-argument,
|
||||
global-statement
|
||||
|
||||
[EXCEPTIONS]
|
||||
overgeneral-exceptions=Exception,HomeAssistantError
|
||||
|
@ -68,7 +68,7 @@ hikvision>=0.4
|
||||
# console log coloring
|
||||
colorlog>=2.6.0
|
||||
|
||||
# JSON-RPC interface
|
||||
# JSON-RPC interface (media_player.kodi)
|
||||
jsonrpc-requests>=0.1
|
||||
|
||||
# Forecast.io Bindings (sensor.forecast)
|
||||
|
@ -9,7 +9,7 @@ import unittest
|
||||
import os
|
||||
|
||||
import homeassistant.loader as loader
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM,
|
||||
SERVICE_TURN_ON, SERVICE_TURN_OFF)
|
||||
@ -154,7 +154,7 @@ class TestLight(unittest.TestCase):
|
||||
|
||||
method, data = dev2.last_call('turn_on')
|
||||
self.assertEqual(
|
||||
{light.ATTR_XY_COLOR: util.color_RGB_to_xy(255, 255, 255)},
|
||||
{light.ATTR_XY_COLOR: color_util.color_RGB_to_xy(255, 255, 255)},
|
||||
data)
|
||||
|
||||
method, data = dev3.last_call('turn_on')
|
||||
|
@ -10,7 +10,7 @@ import unittest.mock as mock
|
||||
import os
|
||||
|
||||
from homeassistant import DOMAIN, HomeAssistantError
|
||||
import homeassistant.util as util
|
||||
import homeassistant.util.location as location_util
|
||||
import homeassistant.config as config_util
|
||||
from homeassistant.const import (
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
|
||||
@ -31,7 +31,7 @@ def create_file(path):
|
||||
|
||||
def mock_detect_location_info():
|
||||
""" Mock implementation of util.detect_location_info. """
|
||||
return util.LocationInfo(
|
||||
return location_util.LocationInfo(
|
||||
ip='1.1.1.1',
|
||||
country_code='US',
|
||||
country_name='United States',
|
||||
@ -151,7 +151,7 @@ class TestConfig(unittest.TestCase):
|
||||
|
||||
def test_create_default_config_detect_location(self):
|
||||
""" Test that detect location sets the correct config keys. """
|
||||
with mock.patch('homeassistant.util.detect_location_info',
|
||||
with mock.patch('homeassistant.util.location.detect_location_info',
|
||||
mock_detect_location_info):
|
||||
config_util.ensure_config_exists(CONFIG_DIR)
|
||||
|
||||
|
@ -50,21 +50,6 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual("12:00:00 09-07-1986",
|
||||
util.repr_helper(datetime(1986, 7, 9, 12, 0, 0)))
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def test_color_RGB_to_xy(self):
|
||||
""" Test color_RGB_to_xy. """
|
||||
self.assertEqual((0, 0), util.color_RGB_to_xy(0, 0, 0))
|
||||
self.assertEqual((0.3127159072215825, 0.3290014805066623),
|
||||
util.color_RGB_to_xy(255, 255, 255))
|
||||
|
||||
self.assertEqual((0.15001662234042554, 0.060006648936170214),
|
||||
util.color_RGB_to_xy(0, 0, 255))
|
||||
|
||||
self.assertEqual((0.3, 0.6), util.color_RGB_to_xy(0, 255, 0))
|
||||
|
||||
self.assertEqual((0.6400744994567747, 0.3299705106316933),
|
||||
util.color_RGB_to_xy(255, 0, 0))
|
||||
|
||||
def test_convert(self):
|
||||
""" Test convert. """
|
||||
self.assertEqual(5, util.convert("5", int))
|
||||
|
22
tests/test_util_color.py
Normal file
22
tests/test_util_color.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""
|
||||
Tests Home Assistant color util methods.
|
||||
"""
|
||||
import unittest
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
|
||||
class TestColorUtil(unittest.TestCase):
|
||||
# pylint: disable=invalid-name
|
||||
def test_color_RGB_to_xy(self):
|
||||
""" Test color_RGB_to_xy. """
|
||||
self.assertEqual((0, 0), color_util.color_RGB_to_xy(0, 0, 0))
|
||||
self.assertEqual((0.3127159072215825, 0.3290014805066623),
|
||||
color_util.color_RGB_to_xy(255, 255, 255))
|
||||
|
||||
self.assertEqual((0.15001662234042554, 0.060006648936170214),
|
||||
color_util.color_RGB_to_xy(0, 0, 255))
|
||||
|
||||
self.assertEqual((0.3, 0.6), color_util.color_RGB_to_xy(0, 255, 0))
|
||||
|
||||
self.assertEqual((0.6400744994567747, 0.3299705106316933),
|
||||
color_util.color_RGB_to_xy(255, 0, 0))
|
Loading…
x
Reference in New Issue
Block a user