Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions homeassistant/components/sensor/season.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
Support for tracking which astronomical or meteorological season it is.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor/season/
"""
import logging
from datetime import datetime

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_TYPE
from homeassistant.helpers.entity import Entity
import homeassistant.util as util

REQUIREMENTS = ['ephem==3.7.6.0']

_LOGGER = logging.getLogger(__name__)

NORTHERN = 'northern'
SOUTHERN = 'southern'
EQUATOR = 'equator'
STATE_SPRING = 'Spring'
STATE_SUMMER = 'Summer'
STATE_AUTUMN = 'Autumn'
STATE_WINTER = 'Winter'
TYPE_ASTRONOMICAL = 'astronomical'
TYPE_METEOROLOGICAL = 'meteorological'
VALID_TYPES = [TYPE_ASTRONOMICAL, TYPE_METEOROLOGICAL]

HEMISPHERE_SEASON_SWAP = {STATE_WINTER: STATE_SUMMER,
STATE_SPRING: STATE_AUTUMN,
STATE_AUTUMN: STATE_SPRING,
STATE_SUMMER: STATE_WINTER}


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TYPE, default=TYPE_ASTRONOMICAL): vol.In(VALID_TYPES)
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Display the current season."""
if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False

latitude = util.convert(hass.config.latitude, float)
_type = config.get(CONF_TYPE)

if latitude < 0:
hemisphere = SOUTHERN
elif latitude > 0:
hemisphere = NORTHERN
else:
hemisphere = EQUATOR

_LOGGER.debug(_type)
add_devices([Season(hass, hemisphere, _type)])

return True


def get_season(date, hemisphere, season_tracking_type):
"""Calculate the current season."""
import ephem

if hemisphere == 'equator':
return None

if season_tracking_type == TYPE_ASTRONOMICAL:
spring_start = ephem.next_equinox(str(date.year)).datetime()
summer_start = ephem.next_solstice(str(date.year)).datetime()
autumn_start = ephem.next_equinox(spring_start).datetime()
winter_start = ephem.next_solstice(summer_start).datetime()
else:
spring_start = datetime(2017, 3, 1).replace(year=date.year)
summer_start = spring_start.replace(month=6)
autumn_start = spring_start.replace(month=9)
winter_start = spring_start.replace(month=12)

if spring_start <= date < summer_start:
season = STATE_SPRING
elif summer_start <= date < autumn_start:
season = STATE_SUMMER
elif autumn_start <= date < winter_start:
season = STATE_AUTUMN
elif winter_start <= date or spring_start > date:
season = STATE_WINTER

# If user is located in the southern hemisphere swap the season
if hemisphere == NORTHERN:
return season
return HEMISPHERE_SEASON_SWAP.get(season)


class Season(Entity):
"""Representation of the current season."""

def __init__(self, hass, hemisphere, season_tracking_type):
"""Initialize the season."""
self.hass = hass
self.hemisphere = hemisphere
self.datetime = datetime.now()
self.type = season_tracking_type
self.season = get_season(self.datetime, self.hemisphere, self.type)

@property
def name(self):
"""Return the name."""
return "Season"

@property
def state(self):
"""Return the current season."""
return self.season

def update(self):
"""Update season."""
self.datetime = datetime.now()
self.season = get_season(self.datetime, self.hemisphere, self.type)
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ enocean==0.31
# homeassistant.components.sensor.envirophat
# envirophat==0.0.6

# homeassistant.components.sensor.season
ephem==3.7.6.0

# homeassistant.components.keyboard_remote
# evdev==0.6.1

Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ apns2==0.1.1
# homeassistant.components.sensor.dsmr
dsmr_parser==0.8

# homeassistant.components.sensor.season
ephem==3.7.6.0

# homeassistant.components.climate.honeywell
evohomeclient==0.2.5

Expand Down
1 change: 1 addition & 0 deletions script/gen_requirements_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
'restrictedpython',
'pyunifi',
'prometheus_client',
'ephem'
)

IGNORE_PACKAGES = (
Expand Down
183 changes: 183 additions & 0 deletions tests/components/sensor/test_season.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""The tests for the Season sensor platform."""
# pylint: disable=protected-access
import unittest
from datetime import datetime

import homeassistant.components.sensor.season as season

from tests.common import get_test_home_assistant


# pylint: disable=invalid-name
class TestSeason(unittest.TestCase):
"""Test the season platform."""

DEVICE = None
CONFIG_ASTRONOMICAL = {'type': 'astronomical'}
CONFIG_METEOROLOGICAL = {'type': 'meteorological'}

def add_devices(self, devices):
"""Mock add devices."""
for device in devices:
self.DEVICE = device

def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()

def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()

def test_season_should_be_summer_northern_astonomical(self):
"""Test that season should be summer."""
# A known day in summer
summer_day = datetime(2017, 9, 3, 0, 0)
current_season = season.get_season(summer_day, season.NORTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_SUMMER,
current_season)

def test_season_should_be_summer_northern_meteorological(self):
"""Test that season should be summer."""
# A known day in summer
summer_day = datetime(2017, 8, 13, 0, 0)
current_season = season.get_season(summer_day, season.NORTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_SUMMER,
current_season)

def test_season_should_be_autumn_northern_astonomical(self):
"""Test that season should be autumn."""
# A known day in autumn
autumn_day = datetime(2017, 9, 23, 0, 0)
current_season = season.get_season(autumn_day, season.NORTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_AUTUMN,
current_season)

def test_season_should_be_autumn_northern_meteorological(self):
"""Test that season should be autumn."""
# A known day in autumn
autumn_day = datetime(2017, 9, 3, 0, 0)
current_season = season.get_season(autumn_day, season.NORTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_AUTUMN,
current_season)

def test_season_should_be_winter_northern_astonomical(self):
"""Test that season should be winter."""
# A known day in winter
winter_day = datetime(2017, 12, 25, 0, 0)
current_season = season.get_season(winter_day, season.NORTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_WINTER,
current_season)

def test_season_should_be_winter_northern_meteorological(self):
"""Test that season should be winter."""
# A known day in winter
winter_day = datetime(2017, 12, 3, 0, 0)
current_season = season.get_season(winter_day, season.NORTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_WINTER,
current_season)

def test_season_should_be_spring_northern_astonomical(self):
"""Test that season should be spring."""
# A known day in spring
spring_day = datetime(2017, 4, 1, 0, 0)
current_season = season.get_season(spring_day, season.NORTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_SPRING,
current_season)

def test_season_should_be_spring_northern_meteorological(self):
"""Test that season should be spring."""
# A known day in spring
spring_day = datetime(2017, 3, 3, 0, 0)
current_season = season.get_season(spring_day, season.NORTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_SPRING,
current_season)

def test_season_should_be_winter_southern_astonomical(self):
"""Test that season should be winter."""
# A known day in winter
winter_day = datetime(2017, 9, 3, 0, 0)
current_season = season.get_season(winter_day, season.SOUTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_WINTER,
current_season)

def test_season_should_be_winter_southern_meteorological(self):
"""Test that season should be winter."""
# A known day in winter
winter_day = datetime(2017, 8, 13, 0, 0)
current_season = season.get_season(winter_day, season.SOUTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_WINTER,
current_season)

def test_season_should_be_spring_southern_astonomical(self):
"""Test that season should be spring."""
# A known day in spring
spring_day = datetime(2017, 9, 23, 0, 0)
current_season = season.get_season(spring_day, season.SOUTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_SPRING,
current_season)

def test_season_should_be_spring_southern_meteorological(self):
"""Test that season should be spring."""
# A known day in spring
spring_day = datetime(2017, 9, 3, 0, 0)
current_season = season.get_season(spring_day, season.SOUTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_SPRING,
current_season)

def test_season_should_be_summer_southern_astonomical(self):
"""Test that season should be summer."""
# A known day in summer
summer_day = datetime(2017, 12, 25, 0, 0)
current_season = season.get_season(summer_day, season.SOUTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_SUMMER,
current_season)

def test_season_should_be_summer_southern_meteorological(self):
"""Test that season should be summer."""
# A known day in summer
summer_day = datetime(2017, 12, 3, 0, 0)
current_season = season.get_season(summer_day, season.SOUTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_SUMMER,
current_season)

def test_season_should_be_autumn_southern_astonomical(self):
"""Test that season should be spring."""
# A known day in spring
autumn_day = datetime(2017, 4, 1, 0, 0)
current_season = season.get_season(autumn_day, season.SOUTHERN,
season.TYPE_ASTRONOMICAL)
self.assertEqual(season.STATE_AUTUMN,
current_season)

def test_season_should_be_autumn_southern_meteorological(self):
"""Test that season should be autumn."""
# A known day in autumn
autumn_day = datetime(2017, 3, 3, 0, 0)
current_season = season.get_season(autumn_day, season.SOUTHERN,
season.TYPE_METEOROLOGICAL)
self.assertEqual(season.STATE_AUTUMN,
current_season)

def test_on_equator_results_in_none(self):
"""Test that season should be unknown."""
# A known day in summer if astronomical and northern
summer_day = datetime(2017, 9, 3, 0, 0)
current_season = season.get_season(summer_day,
season.EQUATOR,
season.TYPE_ASTRONOMICAL)
self.assertEqual(None, current_season)