Merge pull request #222 from balloob/change-dependency-sun

Sun component: ephem->astral
This commit is contained in:
Paulus Schoutsen 2015-07-16 21:35:06 -07:00
commit f4562fa352
4 changed files with 82 additions and 58 deletions

View File

@ -22,21 +22,18 @@ The sun event need to have the type 'sun', which service to call, which event
import logging
from datetime import timedelta
try:
import ephem
except ImportError:
# Will be fixed during setup
ephem = None
import homeassistant.util as util
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.components.scheduler import ServiceEventListener
DEPENDENCIES = []
REQUIREMENTS = ['pyephem>=3.7']
REQUIREMENTS = ['astral>=0.8.1']
DOMAIN = "sun"
ENTITY_ID = "sun.sun"
CONF_ELEVATION = 'elevation'
STATE_ABOVE_HORIZON = "above_horizon"
STATE_BELOW_HORIZON = "below_horizon"
@ -99,24 +96,43 @@ def next_rising_utc(hass, entity_id=None):
def setup(hass, config):
""" Tracks the state of the sun. """
logger = logging.getLogger(__name__)
global ephem # pylint: disable=invalid-name
if ephem is None:
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")
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False
try:
sun = Sun(hass, str(hass.config.latitude), str(hass.config.longitude))
except ValueError:
# Raised when invalid latitude or longitude is given to Observer
logger.exception("Invalid value for latitude or longitude")
latitude = util.convert(hass.config.latitude, float)
longitude = util.convert(hass.config.longitude, float)
errors = []
if latitude is None:
errors.append('Latitude needs to be a decimal value')
elif -90 > latitude < 90:
errors.append('Latitude needs to be -90 .. 90')
if longitude is None:
errors.append('Longitude needs to be a decimal value')
elif -180 > longitude < 180:
errors.append('Longitude needs to be -180 .. 180')
if errors:
_LOGGER.error('Invalid configuration received: %s', ", ".join(errors))
return False
platform_config = config.get(DOMAIN, {})
elevation = platform_config.get(CONF_ELEVATION)
from astral import Location, GoogleGeocoder
location = Location(('', '', latitude, longitude, hass.config.time_zone,
elevation or 0))
if elevation is None:
google = GoogleGeocoder()
google._get_elevation(location) # pylint: disable=protected-access
_LOGGER.info('Retrieved elevation from Google: %s', location.elevation)
sun = Sun(hass, location)
sun.point_in_time_listener(dt_util.utcnow())
return True
@ -127,14 +143,9 @@ class Sun(Entity):
entity_id = ENTITY_ID
def __init__(self, hass, latitude, longitude):
def __init__(self, hass, location):
self.hass = hass
self.observer = ephem.Observer()
# pylint: disable=assigning-non-slot
self.observer.lat = latitude
# pylint: disable=assigning-non-slot
self.observer.long = longitude
self.location = location
self._state = self.next_rising = self.next_setting = None
@property
@ -167,17 +178,24 @@ class Sun(Entity):
def update_as_of(self, utc_point_in_time):
""" Calculate sun state at a point in UTC time. """
sun = ephem.Sun() # pylint: disable=no-member
mod = -1
while True:
next_rising_dt = self.location.sunrise(
utc_point_in_time + timedelta(days=mod), local=False)
if next_rising_dt > utc_point_in_time:
break
mod += 1
# pylint: disable=assigning-non-slot
self.observer.date = ephem.date(utc_point_in_time)
mod = -1
while True:
next_setting_dt = (self.location.sunset(
utc_point_in_time + timedelta(days=mod), local=False))
if next_setting_dt > utc_point_in_time:
break
mod += 1
self.next_rising = self.observer.next_rising(
sun,
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
self.next_setting = self.observer.next_setting(
sun,
start=utc_point_in_time).datetime().replace(tzinfo=dt_util.UTC)
self.next_rising = next_rising_dt
self.next_setting = next_setting_dt
def point_in_time_listener(self, now):
""" Called when the state of the sun has changed. """

View File

@ -9,7 +9,7 @@ pytz>=2015.2
zeroconf>=0.16.0
# Sun (sun)
pyephem>=3.7
astral>=0.8.1
# Philips Hue library (lights.hue)
phue>=0.8

View File

@ -67,7 +67,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
light.DOMAIN: {CONF_PLATFORM: 'test'}
})
sun.setup(self.hass, {})
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """

View File

@ -8,7 +8,7 @@ Tests Sun component.
import unittest
from datetime import timedelta
import ephem
from astral import Astral
import homeassistant as ha
import homeassistant.util.dt as dt_util
@ -34,29 +34,35 @@ class TestSun(unittest.TestCase):
def test_setting_rising(self):
""" Test retrieving sun setting and rising. """
latitude = 32.87336
longitude = 117.22743
# Compare it with the real data
self.hass.config.latitude = '32.87336'
self.hass.config.longitude = '117.22743'
sun.setup(self.hass, None)
observer = ephem.Observer()
observer.lat = '32.87336' # pylint: disable=assigning-non-slot
observer.long = '117.22743' # pylint: disable=assigning-non-slot
self.hass.config.latitude = latitude
self.hass.config.longitude = longitude
sun.setup(self.hass, {sun.DOMAIN: {sun.CONF_ELEVATION: 0}})
astral = Astral()
utc_now = dt_util.utcnow()
body_sun = ephem.Sun() # pylint: disable=no-member
next_rising_dt = observer.next_rising(
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
next_setting_dt = observer.next_setting(
body_sun, start=utc_now).datetime().replace(tzinfo=dt_util.UTC)
# Home Assistant strips out microseconds
# strip it out of the datetime objects
next_rising_dt = dt_util.strip_microseconds(next_rising_dt)
next_setting_dt = dt_util.strip_microseconds(next_setting_dt)
mod = -1
while True:
next_rising = (astral.sunrise_utc(utc_now +
timedelta(days=mod), latitude, longitude))
if next_rising > utc_now:
break
mod += 1
self.assertEqual(next_rising_dt, sun.next_rising_utc(self.hass))
self.assertEqual(next_setting_dt, sun.next_setting_utc(self.hass))
mod = -1
while True:
next_setting = (astral.sunset_utc(utc_now +
timedelta(days=mod), latitude, longitude))
if next_setting > utc_now:
break
mod += 1
self.assertEqual(next_rising, sun.next_rising_utc(self.hass))
self.assertEqual(next_setting, sun.next_setting_utc(self.hass))
# Point it at a state without the proper attributes
self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON)
@ -71,7 +77,7 @@ class TestSun(unittest.TestCase):
""" Test if the state changes at next setting/rising. """
self.hass.config.latitude = '32.87336'
self.hass.config.longitude = '117.22743'
sun.setup(self.hass, None)
sun.setup(self.hass, {})
if sun.is_on(self.hass):
test_state = sun.STATE_BELOW_HORIZON