mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Support for season sensor (#8958)
Add an optional extended description…
This commit is contained in:
parent
0687a457b1
commit
3e0eb8763f
122
homeassistant/components/sensor/season.py
Normal file
122
homeassistant/components/sensor/season.py
Normal file
@ -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)
|
@ -202,6 +202,9 @@ enocean==0.31
|
|||||||
# homeassistant.components.sensor.envirophat
|
# homeassistant.components.sensor.envirophat
|
||||||
# envirophat==0.0.6
|
# envirophat==0.0.6
|
||||||
|
|
||||||
|
# homeassistant.components.sensor.season
|
||||||
|
ephem==3.7.6.0
|
||||||
|
|
||||||
# homeassistant.components.keyboard_remote
|
# homeassistant.components.keyboard_remote
|
||||||
# evdev==0.6.1
|
# evdev==0.6.1
|
||||||
|
|
||||||
|
@ -39,6 +39,9 @@ apns2==0.1.1
|
|||||||
# homeassistant.components.sensor.dsmr
|
# homeassistant.components.sensor.dsmr
|
||||||
dsmr_parser==0.8
|
dsmr_parser==0.8
|
||||||
|
|
||||||
|
# homeassistant.components.sensor.season
|
||||||
|
ephem==3.7.6.0
|
||||||
|
|
||||||
# homeassistant.components.climate.honeywell
|
# homeassistant.components.climate.honeywell
|
||||||
evohomeclient==0.2.5
|
evohomeclient==0.2.5
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ TEST_REQUIREMENTS = (
|
|||||||
'restrictedpython',
|
'restrictedpython',
|
||||||
'pyunifi',
|
'pyunifi',
|
||||||
'prometheus_client',
|
'prometheus_client',
|
||||||
|
'ephem'
|
||||||
)
|
)
|
||||||
|
|
||||||
IGNORE_PACKAGES = (
|
IGNORE_PACKAGES = (
|
||||||
|
183
tests/components/sensor/test_season.py
Normal file
183
tests/components/sensor/test_season.py
Normal file
@ -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)
|
Loading…
x
Reference in New Issue
Block a user