mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Add new sensor platform to expose Islamic prayer times (#19444)
* added new sensor platform to expose Islamic prayer times * added new sensor platform to expose Islamic prayer times * updated tests according to feedback * make prayer_times_info a public attribute * remove stale comments
This commit is contained in:
parent
c15445159d
commit
71900ca719
221
homeassistant/components/sensor/islamic_prayer_times.py
Normal file
221
homeassistant/components/sensor/islamic_prayer_times.py
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
"""
|
||||||
|
Platform to retrieve Islamic prayer times information for Home Assistant.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/sensor.islamic_prayer_times/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import voluptuous as vol
|
||||||
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.helpers.event import async_track_point_in_time
|
||||||
|
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
|
||||||
|
|
||||||
|
REQUIREMENTS = ['prayer_times_calculator==0.0.3']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PRAYER_TIMES_ICON = 'mdi:calendar-clock'
|
||||||
|
|
||||||
|
SENSOR_TYPES = ['fajr', 'sunrise', 'dhuhr', 'asr', 'maghrib', 'isha',
|
||||||
|
'midnight']
|
||||||
|
|
||||||
|
CONF_CALC_METHOD = 'calculation_method'
|
||||||
|
CONF_SENSORS = 'sensors'
|
||||||
|
|
||||||
|
CALC_METHODS = ['karachi', 'isna', 'mwl', 'makkah']
|
||||||
|
DEFAULT_CALC_METHOD = 'isna'
|
||||||
|
DEFAULT_SENSORS = ['fajr', 'dhuhr', 'asr', 'maghrib', 'isha']
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_CALC_METHOD, default=DEFAULT_CALC_METHOD): vol.In(
|
||||||
|
CALC_METHODS),
|
||||||
|
vol.Optional(CONF_SENSORS, default=DEFAULT_SENSORS):
|
||||||
|
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 Islamic prayer times sensor platform."""
|
||||||
|
latitude = hass.config.latitude
|
||||||
|
longitude = hass.config.longitude
|
||||||
|
calc_method = config.get(CONF_CALC_METHOD)
|
||||||
|
|
||||||
|
if None in (latitude, longitude):
|
||||||
|
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||||
|
return
|
||||||
|
|
||||||
|
prayer_times_data = IslamicPrayerTimesData(latitude,
|
||||||
|
longitude,
|
||||||
|
calc_method)
|
||||||
|
|
||||||
|
prayer_times = prayer_times_data.get_new_prayer_times()
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
for sensor_type in config[CONF_SENSORS]:
|
||||||
|
sensors.append(IslamicPrayerTimeSensor(sensor_type, prayer_times_data))
|
||||||
|
|
||||||
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
# schedule the next update for the sensors
|
||||||
|
await schedule_future_update(hass, sensors, prayer_times['Midnight'],
|
||||||
|
prayer_times_data)
|
||||||
|
|
||||||
|
|
||||||
|
async def schedule_future_update(hass, sensors, midnight_time,
|
||||||
|
prayer_times_data):
|
||||||
|
"""Schedule future update for sensors.
|
||||||
|
|
||||||
|
Midnight is a calculated time. The specifics of the calculation
|
||||||
|
depends on the method of the prayer time calculation. This calculated
|
||||||
|
midnight is the time at which the time to pray the Isha prayers have
|
||||||
|
expired.
|
||||||
|
|
||||||
|
Calculated Midnight: The Islamic midnight.
|
||||||
|
Traditional Midnight: 12:00AM
|
||||||
|
|
||||||
|
Update logic for prayer times:
|
||||||
|
|
||||||
|
If the Calculated Midnight is before the traditional midnight then wait
|
||||||
|
until the traditional midnight to run the update. This way the day
|
||||||
|
will have changed over and we don't need to do any fancy calculations.
|
||||||
|
|
||||||
|
If the Calculated Midnight is after the traditional midnight, then wait
|
||||||
|
until after the calculated Midnight. We don't want to update the prayer
|
||||||
|
times too early or else the timings might be incorrect.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
calculated midnight = 11:23PM (before traditional midnight)
|
||||||
|
Update time: 12:00AM
|
||||||
|
|
||||||
|
calculated midnight = 1:35AM (after traditional midnight)
|
||||||
|
update time: 1:36AM.
|
||||||
|
"""
|
||||||
|
_LOGGER.debug("Scheduling next update for Islamic prayer times")
|
||||||
|
|
||||||
|
now = dt_util.as_local(dt_util.now())
|
||||||
|
today = now.date()
|
||||||
|
|
||||||
|
midnight_dt_str = '{}::{}'.format(str(today), midnight_time)
|
||||||
|
midnight_dt = datetime.strptime(midnight_dt_str, '%Y-%m-%d::%H:%M')
|
||||||
|
|
||||||
|
if now > dt_util.as_local(midnight_dt):
|
||||||
|
_LOGGER.debug("Midnight is after day the changes so schedule update "
|
||||||
|
"for after Midnight the next day")
|
||||||
|
|
||||||
|
next_update_at = midnight_dt + timedelta(days=1, minutes=1)
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Midnight is before the day changes so schedule update for the "
|
||||||
|
"next start of day")
|
||||||
|
|
||||||
|
tomorrow = now + timedelta(days=1)
|
||||||
|
next_update_at = dt_util.start_of_local_day(tomorrow)
|
||||||
|
|
||||||
|
_LOGGER.debug("Next update scheduled for: %s", str(next_update_at))
|
||||||
|
|
||||||
|
async_track_point_in_time(hass,
|
||||||
|
update_sensors(hass, sensors, prayer_times_data),
|
||||||
|
next_update_at)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_sensors(hass, sensors, prayer_times_data):
|
||||||
|
"""Update sensors with new prayer times."""
|
||||||
|
# Update prayer times
|
||||||
|
prayer_times = prayer_times_data.get_new_prayer_times()
|
||||||
|
|
||||||
|
_LOGGER.debug("New prayer times retrieved. Updating sensors.")
|
||||||
|
|
||||||
|
# Update all prayer times sensors
|
||||||
|
for sensor in sensors:
|
||||||
|
sensor.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
|
# Schedule next update
|
||||||
|
await schedule_future_update(hass, sensors, prayer_times['Midnight'],
|
||||||
|
prayer_times_data)
|
||||||
|
|
||||||
|
|
||||||
|
class IslamicPrayerTimesData:
|
||||||
|
"""Data object for Islamic prayer times."""
|
||||||
|
|
||||||
|
def __init__(self, latitude, longitude, calc_method):
|
||||||
|
"""Create object to hold data."""
|
||||||
|
self.latitude = latitude
|
||||||
|
self.longitude = longitude
|
||||||
|
self.calc_method = calc_method
|
||||||
|
self.prayer_times_info = None
|
||||||
|
|
||||||
|
def get_new_prayer_times(self):
|
||||||
|
"""Fetch prayer times for today."""
|
||||||
|
from prayer_times_calculator import PrayerTimesCalculator
|
||||||
|
|
||||||
|
today = datetime.today().strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
calc = PrayerTimesCalculator(latitude=self.latitude,
|
||||||
|
longitude=self.longitude,
|
||||||
|
calculation_method=self.calc_method,
|
||||||
|
date=str(today))
|
||||||
|
|
||||||
|
self.prayer_times_info = calc.fetch_prayer_times()
|
||||||
|
return self.prayer_times_info
|
||||||
|
|
||||||
|
|
||||||
|
class IslamicPrayerTimeSensor(Entity):
|
||||||
|
"""Representation of an Islamic prayer time sensor."""
|
||||||
|
|
||||||
|
ENTITY_ID_FORMAT = 'sensor.islamic_prayer_time_{}'
|
||||||
|
|
||||||
|
def __init__(self, sensor_type, prayer_times_data):
|
||||||
|
"""Initialize the Islamic prayer time sensor."""
|
||||||
|
self.sensor_type = sensor_type
|
||||||
|
self.entity_id = self.ENTITY_ID_FORMAT.format(self.sensor_type)
|
||||||
|
self.prayer_times_data = prayer_times_data
|
||||||
|
self._name = self.sensor_type.capitalize()
|
||||||
|
self._device_class = DEVICE_CLASS_TIMESTAMP
|
||||||
|
prayer_time = self.prayer_times_data.prayer_times_info[
|
||||||
|
self._name]
|
||||||
|
pt_dt = self.get_prayer_time_as_dt(prayer_time)
|
||||||
|
self._state = pt_dt.isoformat()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Icon to display in the front end."""
|
||||||
|
return PRAYER_TIMES_ICON
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Disable polling."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class."""
|
||||||
|
return self._device_class
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_prayer_time_as_dt(prayer_time):
|
||||||
|
"""Create a datetime object for the respective prayer time."""
|
||||||
|
today = datetime.today().strftime('%Y-%m-%d')
|
||||||
|
date_time_str = '{} {}'.format(str(today), prayer_time)
|
||||||
|
pt_dt = dt_util.parse_datetime(date_time_str)
|
||||||
|
return pt_dt
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update the sensor."""
|
||||||
|
prayer_time = self.prayer_times_data.prayer_times_info[self.name]
|
||||||
|
pt_dt = self.get_prayer_time_as_dt(prayer_time)
|
||||||
|
self._state = pt_dt.isoformat()
|
@ -798,6 +798,9 @@ pocketcasts==0.1
|
|||||||
# homeassistant.components.sensor.postnl
|
# homeassistant.components.sensor.postnl
|
||||||
postnl_api==1.0.2
|
postnl_api==1.0.2
|
||||||
|
|
||||||
|
# homeassistant.components.sensor.islamic_prayer_times
|
||||||
|
prayer_times_calculator==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.sensor.prezzibenzina
|
# homeassistant.components.sensor.prezzibenzina
|
||||||
prezzibenzina-py==1.1.4
|
prezzibenzina-py==1.1.4
|
||||||
|
|
||||||
|
164
tests/components/sensor/test_islamic_prayer_times.py
Normal file
164
tests/components/sensor/test_islamic_prayer_times.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
"""The tests for the Islamic prayer times sensor platform."""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from unittest.mock import patch
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
from homeassistant.components.sensor.islamic_prayer_times import \
|
||||||
|
IslamicPrayerTimesData
|
||||||
|
from tests.common import MockDependency
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
LATITUDE = 41
|
||||||
|
LONGITUDE = -87
|
||||||
|
CALC_METHOD = 'isna'
|
||||||
|
PRAYER_TIMES = {"Fajr": "06:10", "Sunrise": "07:25", "Dhuhr": "12:30",
|
||||||
|
"Asr": "15:32", "Maghrib": "17:35", "Isha": "18:53",
|
||||||
|
"Midnight": "00:45"}
|
||||||
|
ENTITY_ID_FORMAT = 'sensor.islamic_prayer_time_{}'
|
||||||
|
|
||||||
|
|
||||||
|
def get_prayer_time_as_dt(prayer_time):
|
||||||
|
"""Create a datetime object for the respective prayer time."""
|
||||||
|
today = datetime.today().strftime('%Y-%m-%d')
|
||||||
|
date_time_str = '{} {}'.format(str(today), prayer_time)
|
||||||
|
pt_dt = dt_util.parse_datetime(date_time_str)
|
||||||
|
return pt_dt
|
||||||
|
|
||||||
|
|
||||||
|
async def test_islamic_prayer_times_min_config(hass):
|
||||||
|
"""Test minimum Islamic prayer times configuration."""
|
||||||
|
min_config_sensors = ['fajr', 'dhuhr', 'asr', 'maghrib', 'isha']
|
||||||
|
|
||||||
|
with MockDependency('prayer_times_calculator') as mock_pt_calc:
|
||||||
|
mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times \
|
||||||
|
.return_value = PRAYER_TIMES
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'sensor': {
|
||||||
|
'platform': 'islamic_prayer_times'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert await async_setup_component(hass, 'sensor', config) is True
|
||||||
|
|
||||||
|
for sensor in min_config_sensors:
|
||||||
|
entity_id = ENTITY_ID_FORMAT.format(sensor)
|
||||||
|
entity_id_name = sensor.capitalize()
|
||||||
|
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES[entity_id_name])
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == pt_dt.isoformat()
|
||||||
|
assert state.name == entity_id_name
|
||||||
|
|
||||||
|
|
||||||
|
async def test_islamic_prayer_times_multiple_sensors(hass):
|
||||||
|
"""Test Islamic prayer times sensor with multiple sensors setup."""
|
||||||
|
multiple_sensors = ['fajr', 'sunrise', 'dhuhr', 'asr', 'maghrib', 'isha',
|
||||||
|
'midnight']
|
||||||
|
|
||||||
|
with MockDependency('prayer_times_calculator') as mock_pt_calc:
|
||||||
|
mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times \
|
||||||
|
.return_value = PRAYER_TIMES
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'sensor': {
|
||||||
|
'platform': 'islamic_prayer_times',
|
||||||
|
'sensors': multiple_sensors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, 'sensor', config) is True
|
||||||
|
|
||||||
|
for sensor in multiple_sensors:
|
||||||
|
entity_id = ENTITY_ID_FORMAT.format(sensor)
|
||||||
|
entity_id_name = sensor.capitalize()
|
||||||
|
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES[entity_id_name])
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == pt_dt.isoformat()
|
||||||
|
assert state.name == entity_id_name
|
||||||
|
|
||||||
|
|
||||||
|
async def test_islamic_prayer_times_with_calculation_method(hass):
|
||||||
|
"""Test Islamic prayer times configuration with calculation method."""
|
||||||
|
sensors = ['fajr', 'maghrib']
|
||||||
|
|
||||||
|
with MockDependency('prayer_times_calculator') as mock_pt_calc:
|
||||||
|
mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times \
|
||||||
|
.return_value = PRAYER_TIMES
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'sensor': {
|
||||||
|
'platform': 'islamic_prayer_times',
|
||||||
|
'calculation_method': 'mwl',
|
||||||
|
'sensors': sensors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, 'sensor', config) is True
|
||||||
|
|
||||||
|
for sensor in sensors:
|
||||||
|
entity_id = ENTITY_ID_FORMAT.format(sensor)
|
||||||
|
entity_id_name = sensor.capitalize()
|
||||||
|
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES[entity_id_name])
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == pt_dt.isoformat()
|
||||||
|
assert state.name == entity_id_name
|
||||||
|
|
||||||
|
|
||||||
|
async def test_islamic_prayer_times_data_get_prayer_times(hass):
|
||||||
|
"""Test Islamic prayer times data fetcher."""
|
||||||
|
with MockDependency('prayer_times_calculator') as mock_pt_calc:
|
||||||
|
mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times \
|
||||||
|
.return_value = PRAYER_TIMES
|
||||||
|
|
||||||
|
pt_data = IslamicPrayerTimesData(latitude=LATITUDE,
|
||||||
|
longitude=LONGITUDE,
|
||||||
|
calc_method=CALC_METHOD)
|
||||||
|
|
||||||
|
assert pt_data.get_new_prayer_times() == PRAYER_TIMES
|
||||||
|
assert pt_data.prayer_times_info == PRAYER_TIMES
|
||||||
|
|
||||||
|
|
||||||
|
async def test_islamic_prayer_times_sensor_update(hass):
|
||||||
|
"""Test Islamic prayer times sensor update."""
|
||||||
|
new_prayer_times = {"Fajr": "06:10",
|
||||||
|
"Sunrise": "07:25",
|
||||||
|
"Dhuhr": "12:30",
|
||||||
|
"Asr": "15:32",
|
||||||
|
"Maghrib": "17:45",
|
||||||
|
"Isha": "18:53",
|
||||||
|
"Midnight": "00:45"}
|
||||||
|
|
||||||
|
with MockDependency('prayer_times_calculator') as mock_pt_calc:
|
||||||
|
mock_pt_calc.PrayerTimesCalculator.return_value.fetch_prayer_times \
|
||||||
|
.side_effect = [PRAYER_TIMES, new_prayer_times]
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'sensor': {
|
||||||
|
'platform': 'islamic_prayer_times',
|
||||||
|
'sensors': ['maghrib']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, 'sensor', config)
|
||||||
|
|
||||||
|
entity_id = 'sensor.islamic_prayer_time_maghrib'
|
||||||
|
pt_dt = get_prayer_time_as_dt(PRAYER_TIMES['Maghrib'])
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == pt_dt.isoformat()
|
||||||
|
|
||||||
|
midnight = PRAYER_TIMES['Midnight']
|
||||||
|
now = dt_util.as_local(dt_util.now())
|
||||||
|
today = now.date()
|
||||||
|
|
||||||
|
midnight_dt_str = '{}::{}'.format(str(today), midnight)
|
||||||
|
midnight_dt = datetime.strptime(midnight_dt_str, '%Y-%m-%d::%H:%M')
|
||||||
|
future = midnight_dt + timedelta(days=1, minutes=1)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
'homeassistant.components.sensor.islamic_prayer_times'
|
||||||
|
'.dt_util.utcnow', return_value=future):
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, future)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
pt_dt = get_prayer_time_as_dt(new_prayer_times['Maghrib'])
|
||||||
|
assert state.state == pt_dt.isoformat()
|
Loading…
x
Reference in New Issue
Block a user