Add zodiac integration (#38935)

* add zodiac sign integration

* add tests & refacto

* Apply suggestions from code review

Co-authored-by: Chris Talkington <chris@talkingtontech.com>

* fix indentation from suggested correction, fix quality scale and remove useless functions

* fix code formatting

* Create const.py

* Update sensor.py

* Update const.py

* Update test_sensor.py

* Update test_sensor.py

* Update test_sensor.py

* Update test_sensor.py

* Update test_sensor.py

* Update sensor.py

* Update test_sensor.py

* Update __init__.py

* Update sensor.py

* Update test_sensor.py

* Update sensor.py

* Update __init__.py

* Update test_sensor.py

* Update __init__.py

* Fix zodiac time patch

* Delete sensor.fr.json

* Update sensor.py

* Delete sensor.en.json

* Update test_sensor.py

* Apply suggestions from code review

Co-authored-by: Chris Talkington <chris@talkingtontech.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Julien Tant 2020-09-21 08:41:30 -07:00 committed by GitHub
parent 2ef3dfb673
commit 8b9c757fdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 347 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1,7 @@
{
"domain": "zodiac",
"name": "Zodiac",
"documentation": "https://www.home-assistant.io/integrations/zodiac",
"codeowners": ["@JulienTant"],
"quality_scale": "silver"
}

View File

@ -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

View File

@ -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"
}
}
}

View File

@ -0,0 +1 @@
"""Tests for the zodiac component."""

View File

@ -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