diff --git a/CODEOWNERS b/CODEOWNERS index 2913dd68953..3fa8a7a366d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -505,6 +505,7 @@ homeassistant/components/yi/* @bachya homeassistant/components/zeroconf/* @Kane610 homeassistant/components/zerproc/* @emlove homeassistant/components/zha/* @dmulcahey @adminiuga +homeassistant/components/zodiac/* @JulienTant homeassistant/components/zone/* @home-assistant/core homeassistant/components/zoneminder/* @rohankapoorcom @vangorra homeassistant/components/zwave/* @home-assistant/z-wave diff --git a/homeassistant/components/zodiac/__init__.py b/homeassistant/components/zodiac/__init__.py new file mode 100644 index 00000000000..d00cc560f22 --- /dev/null +++ b/homeassistant/components/zodiac/__init__.py @@ -0,0 +1,19 @@ +"""The zodiac component.""" +import voluptuous as vol + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.discovery import async_load_platform + +from .const import DOMAIN + +CONFIG_SCHEMA = vol.Schema( + {vol.Optional(DOMAIN): {}}, + extra=vol.ALLOW_EXTRA, +) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the zodiac component.""" + hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config)) + + return True diff --git a/homeassistant/components/zodiac/const.py b/homeassistant/components/zodiac/const.py new file mode 100644 index 00000000000..c3e7f13d5e3 --- /dev/null +++ b/homeassistant/components/zodiac/const.py @@ -0,0 +1,31 @@ +"""Constants for Zodiac.""" +DOMAIN = "zodiac" + +# Signs +SIGN_ARIES = "aries" +SIGN_TAURUS = "taurus" +SIGN_GEMINI = "gemini" +SIGN_CANCER = "cancer" +SIGN_LEO = "leo" +SIGN_VIRGO = "virgo" +SIGN_LIBRA = "libra" +SIGN_SCORPIO = "scorpio" +SIGN_SAGITTARIUS = "sagittarius" +SIGN_CAPRICORN = "capricorn" +SIGN_AQUARIUS = "aquarius" +SIGN_PISCES = "pisces" + +# Elements +ELEMENT_FIRE = "fire" +ELEMENT_AIR = "air" +ELEMENT_EARTH = "earth" +ELEMENT_WATER = "water" + +# Modality +MODALITY_CARDINAL = "cardinal" +MODALITY_FIXED = "fixed" +MODALITY_MUTABLE = "mutable" + +# Attributes +ATTR_ELEMENT = "element" +ATTR_MODALITY = "modality" diff --git a/homeassistant/components/zodiac/manifest.json b/homeassistant/components/zodiac/manifest.json new file mode 100644 index 00000000000..9d38c2cff39 --- /dev/null +++ b/homeassistant/components/zodiac/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "zodiac", + "name": "Zodiac", + "documentation": "https://www.home-assistant.io/integrations/zodiac", + "codeowners": ["@JulienTant"], + "quality_scale": "silver" +} diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py new file mode 100644 index 00000000000..06bd52f6bf5 --- /dev/null +++ b/homeassistant/components/zodiac/sensor.py @@ -0,0 +1,220 @@ +"""Support for tracking the zodiac sign.""" +import logging + +from homeassistant.helpers.entity import Entity +from homeassistant.util.dt import as_local, utcnow + +from .const import ( + ATTR_ELEMENT, + ATTR_MODALITY, + DOMAIN, + ELEMENT_AIR, + ELEMENT_EARTH, + ELEMENT_FIRE, + ELEMENT_WATER, + MODALITY_CARDINAL, + MODALITY_FIXED, + MODALITY_MUTABLE, + SIGN_AQUARIUS, + SIGN_ARIES, + SIGN_CANCER, + SIGN_CAPRICORN, + SIGN_GEMINI, + SIGN_LEO, + SIGN_LIBRA, + SIGN_PISCES, + SIGN_SAGITTARIUS, + SIGN_SCORPIO, + SIGN_TAURUS, + SIGN_VIRGO, +) + +_LOGGER = logging.getLogger(__name__) + +ZODIAC_BY_DATE = ( + ( + (21, 3), + (20, 4), + SIGN_ARIES, + { + ATTR_ELEMENT: ELEMENT_FIRE, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (21, 4), + (20, 5), + SIGN_TAURUS, + { + ATTR_ELEMENT: ELEMENT_EARTH, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (21, 5), + (21, 6), + SIGN_GEMINI, + { + ATTR_ELEMENT: ELEMENT_AIR, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), + ( + (22, 6), + (22, 7), + SIGN_CANCER, + { + ATTR_ELEMENT: ELEMENT_WATER, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (23, 7), + (22, 8), + SIGN_LEO, + { + ATTR_ELEMENT: ELEMENT_FIRE, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (23, 8), + (21, 9), + SIGN_VIRGO, + { + ATTR_ELEMENT: ELEMENT_EARTH, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), + ( + (22, 9), + (22, 10), + SIGN_LIBRA, + { + ATTR_ELEMENT: ELEMENT_AIR, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (23, 10), + (22, 11), + SIGN_SCORPIO, + { + ATTR_ELEMENT: ELEMENT_WATER, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (23, 11), + (21, 12), + SIGN_SAGITTARIUS, + { + ATTR_ELEMENT: ELEMENT_FIRE, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), + ( + (22, 12), + (20, 1), + SIGN_CAPRICORN, + { + ATTR_ELEMENT: ELEMENT_EARTH, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (21, 1), + (19, 2), + SIGN_AQUARIUS, + { + ATTR_ELEMENT: ELEMENT_AIR, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (20, 2), + (20, 3), + SIGN_PISCES, + { + ATTR_ELEMENT: ELEMENT_WATER, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), +) + +ZODIAC_ICONS = { + SIGN_ARIES: "mdi:zodiac-aries", + SIGN_TAURUS: "mdi:zodiac-taurus", + SIGN_GEMINI: "mdi:zodiac-gemini", + SIGN_CANCER: "mdi:zodiac-cancer", + SIGN_LEO: "mdi:zodiac-leo", + SIGN_VIRGO: "mdi:zodiac-virgo", + SIGN_LIBRA: "mdi:zodiac-libra", + SIGN_SCORPIO: "mdi:zodiac-scorpio", + SIGN_SAGITTARIUS: "mdi:zodiac-sagittarius", + SIGN_CAPRICORN: "mdi:zodiac-capricorn", + SIGN_AQUARIUS: "mdi:zodiac-aquarius", + SIGN_PISCES: "mdi:zodiac-pisces", +} + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Zodiac sensor platform.""" + if discovery_info is None: + return + + async_add_entities([ZodiacSensor()], True) + + +class ZodiacSensor(Entity): + """Representation of a Zodiac sensor.""" + + def __init__(self): + """Initialize the zodiac sensor.""" + self._attrs = None + self._state = None + + @property + def unique_id(self): + """Return a unique ID.""" + return DOMAIN + + @property + def name(self): + """Return the name of the entity.""" + return "Zodiac" + + @property + def device_class(self): + """Return the device class of the entity.""" + return "zodiac__sign" + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ZODIAC_ICONS.get(self._state) + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attrs + + async def async_update(self): + """Get the time and updates the state.""" + today = as_local(utcnow()).date() + + month = int(today.month) + day = int(today.day) + + for sign in ZODIAC_BY_DATE: + if (month == sign[0][1] and day >= sign[0][0]) or ( + month == sign[1][1] and day <= sign[1][0] + ): + self._state = sign[2] + self._attrs = sign[3] + break diff --git a/homeassistant/components/zodiac/strings.sensor.json b/homeassistant/components/zodiac/strings.sensor.json new file mode 100644 index 00000000000..e33465967e3 --- /dev/null +++ b/homeassistant/components/zodiac/strings.sensor.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aries": "Aries", + "taurus": "Taurus", + "gemini": "Gemini", + "cancer": "Cancer", + "leo": "Leo", + "virgo": "Virgo", + "libra": "Libra", + "scorpio": "Scorpio", + "sagittarius": "Sagittarius", + "capricorn": "Capricorn", + "aquarius": "Aquarius", + "pisces": "Pisces" + } + } +} \ No newline at end of file diff --git a/tests/components/zodiac/__init__.py b/tests/components/zodiac/__init__.py new file mode 100644 index 00000000000..e9bae20c442 --- /dev/null +++ b/tests/components/zodiac/__init__.py @@ -0,0 +1 @@ +"""Tests for the zodiac component.""" diff --git a/tests/components/zodiac/test_sensor.py b/tests/components/zodiac/test_sensor.py new file mode 100644 index 00000000000..b08352dd2c0 --- /dev/null +++ b/tests/components/zodiac/test_sensor.py @@ -0,0 +1,50 @@ +"""The test for the zodiac sensor platform.""" +from datetime import datetime + +import pytest + +from homeassistant.components.zodiac.const import ( + ATTR_ELEMENT, + ATTR_MODALITY, + DOMAIN, + ELEMENT_EARTH, + ELEMENT_FIRE, + ELEMENT_WATER, + MODALITY_CARDINAL, + MODALITY_FIXED, + SIGN_ARIES, + SIGN_SCORPIO, + SIGN_TAURUS, +) +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util + +from tests.async_mock import patch + +DAY1 = datetime(2020, 11, 15, tzinfo=dt_util.UTC) +DAY2 = datetime(2020, 4, 20, tzinfo=dt_util.UTC) +DAY3 = datetime(2020, 4, 21, tzinfo=dt_util.UTC) + + +@pytest.mark.parametrize( + "now,sign,element,modality", + [ + (DAY1, SIGN_SCORPIO, ELEMENT_WATER, MODALITY_FIXED), + (DAY2, SIGN_ARIES, ELEMENT_FIRE, MODALITY_CARDINAL), + (DAY3, SIGN_TAURUS, ELEMENT_EARTH, MODALITY_FIXED), + ], +) +async def test_zodiac_day(hass, now, sign, element, modality): + """Test the zodiac sensor.""" + config = {DOMAIN: {}} + + with patch("homeassistant.components.zodiac.sensor.utcnow", return_value=now): + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() + + state = hass.states.get("sensor.zodiac") + assert state + assert state.state == sign + assert state.attributes + assert state.attributes[ATTR_ELEMENT] == element + assert state.attributes[ATTR_MODALITY] == modality