From 25712f16b3b60aa5067ff29425190ff5552afb9b Mon Sep 17 00:00:00 2001 From: Tsvi Mostovicz <ttmost@gmail.com> Date: Mon, 17 Sep 2018 23:43:31 +0300 Subject: [PATCH] Jewish calendar sensor (#16393) * Initial commit for jewish calendar sensor * Make check for logging errors into it's own function * Can't use f-strings as we need to support python3.5 * Implement basic functionality: printing of date * Update requirements_all.txt * Allow user to specify date for sensor * Add hdate to test requirements * Update to match pull request * Support date output in hebrew * Limit languages to english and hebrew * Add name back to sensor * Change icon to be calendar-today * Add multiple sensors * Fix tests * Make Hound happy, remove unused imported class * hdate expects datetime.date not datetime.datetime * Return sensor name * Times should be returned as time object, not datetime * Add myself to codeowners for jewish calendar component * Return actual reading, not index * Add more tests. Currently failing. Will need to update hdate API and version before continuing. * Fix weekly portion test * Make all tests pass * Make travis happy and add a test so it doesnt happen again * Remove defaults in __init__ method * Change sensor state variable to local variable in update() method * Minor changes --- CODEOWNERS | 1 + .../components/sensor/jewish_calendar.py | 134 ++++++++++++++++++ requirements_all.txt | 3 + requirements_test_all.txt | 3 + script/gen_requirements_all.py | 1 + .../components/sensor/test_jewish_calendar.py | 134 ++++++++++++++++++ 6 files changed, 276 insertions(+) create mode 100644 homeassistant/components/sensor/jewish_calendar.py create mode 100644 tests/components/sensor/test_jewish_calendar.py diff --git a/CODEOWNERS b/CODEOWNERS index 68d0d4a833d..b6ce8c04909 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -72,6 +72,7 @@ homeassistant/components/sensor/airvisual.py @bachya homeassistant/components/sensor/filter.py @dgomes homeassistant/components/sensor/gearbest.py @HerrHofrat homeassistant/components/sensor/irish_rail_transport.py @ttroy50 +homeassistant/components/sensor/jewish_calendar.py @tsvi homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel homeassistant/components/sensor/nsw_fuel_station.py @nickw444 homeassistant/components/sensor/pollen.py @bachya diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py new file mode 100644 index 00000000000..544b7be0c5d --- /dev/null +++ b/homeassistant/components/sensor/jewish_calendar.py @@ -0,0 +1,134 @@ +""" +Platform to retrieve Jewish calendar information for Home Assistant. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.jewish_calendar/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +import homeassistant.util.dt as dt_util + +REQUIREMENTS = ['hdate==0.6.3'] + +_LOGGER = logging.getLogger(__name__) + +SENSOR_TYPES = { + 'date': ['Date', 'mdi:judaism'], + 'weekly_portion': ['Parshat Hashavua', 'mdi:book-open-variant'], + 'holiday_name': ['Holiday', 'mdi:calendar-star'], + 'holyness': ['Holyness', 'mdi:counter'], + 'first_light': ['Alot Hashachar', 'mdi:weather-sunset-up'], + 'gra_end_shma': ['Latest time for Shm"a GR"A', 'mdi:calendar-clock'], + 'mga_end_shma': ['Latest time for Shm"a MG"A', 'mdi:calendar-clock'], + 'plag_mincha': ['Plag Hamincha', 'mdi:weather-sunset-down'], + 'first_stars': ['T\'set Hakochavim', 'mdi:weather-night'], +} + +CONF_DIASPORA = 'diaspora' +CONF_LANGUAGE = 'language' +CONF_SENSORS = 'sensors' + +DEFAULT_NAME = 'Jewish Calendar' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DIASPORA, default=False): cv.boolean, + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_LANGUAGE, default='english'): + vol.In(['hebrew', 'english']), + vol.Optional(CONF_SENSORS, default=['date']): + vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)]), +}) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the Jewish calendar sensor platform.""" + language = config.get(CONF_LANGUAGE) + name = config.get(CONF_NAME) + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + diaspora = config.get(CONF_DIASPORA) + + if None in (latitude, longitude): + _LOGGER.error("Latitude or longitude not set in Home Assistant config") + return + + dev = [] + for sensor_type in config[CONF_SENSORS]: + dev.append(JewishCalSensor( + name, language, sensor_type, latitude, longitude, diaspora)) + async_add_entities(dev, True) + + +class JewishCalSensor(Entity): + """Representation of an Jewish calendar sensor.""" + + def __init__( + self, name, language, sensor_type, latitude, longitude, diaspora): + """Initialize the Jewish calendar sensor.""" + self.client_name = name + self._name = SENSOR_TYPES[sensor_type][0] + self.type = sensor_type + self._hebrew = (language == 'hebrew') + self._state = None + self.latitude = latitude + self.longitude = longitude + self.diaspora = diaspora + _LOGGER.debug("Sensor %s initialized", self.type) + + @property + def name(self): + """Return the name of the sensor.""" + return '{} {}'.format(self.client_name, self._name) + + @property + def icon(self): + """Icon to display in the front end.""" + return SENSOR_TYPES[self.type][1] + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + async def async_update(self): + """Update the state of the sensor.""" + import hdate + + today = dt_util.now().date() + + date = hdate.HDate( + today, diaspora=self.diaspora, hebrew=self._hebrew) + + if self.type == 'date': + self._state = hdate.date.get_hebrew_date( + date.h_day, date.h_month, date.h_year, hebrew=self._hebrew) + elif self.type == 'weekly_portion': + self._state = hdate.date.get_parashe( + date.get_reading(self.diaspora), hebrew=self._hebrew) + elif self.type == 'holiday_name': + try: + self._state = next( + x.description[self._hebrew].long + for x in hdate.htables.HOLIDAYS + if x.index == date.get_holyday()) + except StopIteration: + self._state = None + elif self.type == 'holyness': + self._state = hdate.date.get_holyday_type(date.get_holyday()) + else: + times = hdate.Zmanim( + date=today, + latitude=self.latitude, longitude=self.longitude, + hebrew=self._hebrew).zmanim + self._state = times[self.type].time() + + _LOGGER.debug("New value: %s", self._state) diff --git a/requirements_all.txt b/requirements_all.txt index 13cf9082e6c..96e5ed0b1e3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -435,6 +435,9 @@ haversine==0.4.5 # homeassistant.components.mqtt.server hbmqtt==0.9.4 +# homeassistant.components.sensor.jewish_calendar +hdate==0.6.3 + # homeassistant.components.climate.heatmiser heatmiserV3==0.9.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8693627ba95..42561336ff9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -80,6 +80,9 @@ haversine==0.4.5 # homeassistant.components.mqtt.server hbmqtt==0.9.4 +# homeassistant.components.sensor.jewish_calendar +hdate==0.6.3 + # homeassistant.components.binary_sensor.workday holidays==0.9.7 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index d1d29affeff..3cde13a2a97 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -55,6 +55,7 @@ TEST_REQUIREMENTS = ( 'ha-ffmpeg', 'haversine', 'hbmqtt', + 'hdate', 'holidays', 'home-assistant-frontend', 'homematicip', diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/sensor/test_jewish_calendar.py new file mode 100644 index 00000000000..3260512495f --- /dev/null +++ b/tests/components/sensor/test_jewish_calendar.py @@ -0,0 +1,134 @@ +"""The tests for the Jewish calendar sensor platform.""" +import unittest +from datetime import datetime as dt +from unittest.mock import patch + +from homeassistant.util.async_ import run_coroutine_threadsafe +from homeassistant.setup import setup_component +from homeassistant.components.sensor.jewish_calendar import JewishCalSensor +from tests.common import get_test_home_assistant + + +class TestJewishCalenderSensor(unittest.TestCase): + """Test the Jewish Calendar sensor.""" + + TEST_LATITUDE = 31.778 + TEST_LONGITUDE = 35.235 + + def setUp(self): + """Set up things to run when tests begin.""" + self.hass = get_test_home_assistant() + + def tearDown(self): + """Stop everything that was started.""" + self.hass.stop() + + def checkForLoggingErrors(self): + """Check whether logger spitted out errors.""" + errors = [rec for rec in self.cm.records if rec.levelname == "ERROR"] + self.assertFalse(errors, ("Logger reported error(s): ", + [err.getMessage() for err in errors])) + + def test_jewish_calendar_min_config(self): + """Test minimum jewish calendar configuration.""" + config = { + 'sensor': { + 'platform': 'jewish_calendar' + } + } + with self.assertLogs() as self.cm: + assert setup_component(self.hass, 'sensor', config) + self.checkForLoggingErrors() + + def test_jewish_calendar_hebrew(self): + """Test jewish calendar sensor with language set to hebrew.""" + config = { + 'sensor': { + 'platform': 'jewish_calendar', + 'language': 'hebrew', + } + } + with self.assertLogs() as self.cm: + assert setup_component(self.hass, 'sensor', config) + self.checkForLoggingErrors() + + def test_jewish_calendar_multiple_sensors(self): + """Test jewish calendar sensor with multiple sensors setup.""" + config = { + 'sensor': { + 'platform': 'jewish_calendar', + 'sensors': [ + 'date', 'weekly_portion', 'holiday_name', + 'holyness', 'first_light', 'gra_end_shma', + 'mga_end_shma', 'plag_mincha', 'first_stars' + ] + } + } + with self.assertLogs() as self.cm: + assert setup_component(self.hass, 'sensor', config) + self.checkForLoggingErrors() + + def test_jewish_calendar_sensor_date_output(self): + """Test Jewish calendar sensor date output.""" + test_time = dt(2018, 9, 3) + sensor = JewishCalSensor( + name='test', language='english', sensor_type='date', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + diaspora=False) + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), + self.hass.loop).result() + self.assertEqual(sensor.state, '23 Elul 5778') + + def test_jewish_calendar_sensor_date_output_hebrew(self): + """Test Jewish calendar sensor date output in hebrew.""" + test_time = dt(2018, 9, 3) + sensor = JewishCalSensor( + name='test', language='hebrew', sensor_type='date', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + diaspora=False) + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), + self.hass.loop).result() + self.assertEqual(sensor.state, "כ\"ג באלול ה\' תשע\"ח") + + def test_jewish_calendar_sensor_holiday_name(self): + """Test Jewish calendar sensor date output in hebrew.""" + test_time = dt(2018, 9, 10) + sensor = JewishCalSensor( + name='test', language='hebrew', sensor_type='holiday_name', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + diaspora=False) + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), + self.hass.loop).result() + self.assertEqual(sensor.state, "א\' ראש השנה") + + def test_jewish_calendar_sensor_holyness(self): + """Test Jewish calendar sensor date output in hebrew.""" + test_time = dt(2018, 9, 10) + sensor = JewishCalSensor( + name='test', language='hebrew', sensor_type='holyness', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + diaspora=False) + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), + self.hass.loop).result() + self.assertEqual(sensor.state, 1) + + def test_jewish_calendar_sensor_torah_reading(self): + """Test Jewish calendar sensor date output in hebrew.""" + test_time = dt(2018, 9, 8) + sensor = JewishCalSensor( + name='test', language='hebrew', sensor_type='weekly_portion', + latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE, + diaspora=False) + with patch('homeassistant.util.dt.now', return_value=test_time): + run_coroutine_threadsafe( + sensor.async_update(), + self.hass.loop).result() + self.assertEqual(sensor.state, "פרשת נצבים") -- GitLab