Add Islamic Prayer Times config flow (#31590)
* Add Islamic Prayer Times config_flow * Add Islamic Prayer Times config_flow * handle options update and fix tests * fix sensor update handling * fix pylint * fix scheduled update and add test * update test_init * update flow options to show drop list * clean up code * async scheduling and revert state to timestamp * fix update retry method * update strings * keep title as root keypull/34457/head
parent
1b4851f2e0
commit
2a6948e696
|
@ -193,6 +193,7 @@ homeassistant/components/ipma/* @dgomes @abmantis
|
|||
homeassistant/components/ipp/* @ctalkington
|
||||
homeassistant/components/iqvia/* @bachya
|
||||
homeassistant/components/irish_rail_transport/* @ttroy50
|
||||
homeassistant/components/islamic_prayer_times/* @engrbm87
|
||||
homeassistant/components/izone/* @Swamp-Ig
|
||||
homeassistant/components/jewish_calendar/* @tsvi
|
||||
homeassistant/components/juicenet/* @jesserockz
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"title": "Islamic Prayer Times",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up Islamic Prayer Times",
|
||||
"description": "Do you want to set up Islamic Prayer Times?"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"one_instance_allowed": "Only a single instance is necessary."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"calc_method": "Prayer calculation method"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1,206 @@
|
|||
"""The islamic_prayer_times component."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from prayer_times_calculator import PrayerTimesCalculator, exceptions
|
||||
from requests.exceptions import ConnectionError as ConnError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later, async_track_point_in_time
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import (
|
||||
CALC_METHODS,
|
||||
CONF_CALC_METHOD,
|
||||
DATA_UPDATED,
|
||||
DEFAULT_CALC_METHOD,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: {
|
||||
vol.Optional(CONF_CALC_METHOD, default=DEFAULT_CALC_METHOD): vol.In(
|
||||
CALC_METHODS
|
||||
),
|
||||
}
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Import the Islamic Prayer component from config."""
|
||||
if DOMAIN in config:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up the Islamic Prayer Component."""
|
||||
client = IslamicPrayerClient(hass, config_entry)
|
||||
|
||||
if not await client.async_setup():
|
||||
return False
|
||||
|
||||
hass.data.setdefault(DOMAIN, client)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload Islamic Prayer entry from config_entry."""
|
||||
if hass.data[DOMAIN].event_unsub:
|
||||
hass.data[DOMAIN].event_unsub()
|
||||
hass.data.pop(DOMAIN)
|
||||
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class IslamicPrayerClient:
|
||||
"""Islamic Prayer Client Object."""
|
||||
|
||||
def __init__(self, hass, config_entry):
|
||||
"""Initialize the Islamic Prayer client."""
|
||||
self.hass = hass
|
||||
self.config_entry = config_entry
|
||||
self.prayer_times_info = {}
|
||||
self.available = True
|
||||
self.event_unsub = None
|
||||
|
||||
@property
|
||||
def calc_method(self):
|
||||
"""Return the calculation method."""
|
||||
return self.config_entry.options[CONF_CALC_METHOD]
|
||||
|
||||
def get_new_prayer_times(self):
|
||||
"""Fetch prayer times for today."""
|
||||
calc = PrayerTimesCalculator(
|
||||
latitude=self.hass.config.latitude,
|
||||
longitude=self.hass.config.longitude,
|
||||
calculation_method=self.calc_method,
|
||||
date=str(dt_util.now().date()),
|
||||
)
|
||||
return calc.fetch_prayer_times()
|
||||
|
||||
async def async_schedule_future_update(self):
|
||||
"""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())
|
||||
|
||||
midnight_dt = self.prayer_times_info["Midnight"]
|
||||
|
||||
if now > dt_util.as_local(midnight_dt):
|
||||
next_update_at = midnight_dt + timedelta(days=1, minutes=1)
|
||||
_LOGGER.debug(
|
||||
"Midnight is after day the changes so schedule update for after Midnight the next day"
|
||||
)
|
||||
else:
|
||||
_LOGGER.debug(
|
||||
"Midnight is before the day changes so schedule update for the next start of day"
|
||||
)
|
||||
next_update_at = dt_util.start_of_local_day(now + timedelta(days=1))
|
||||
|
||||
_LOGGER.info("Next update scheduled for: %s", next_update_at)
|
||||
|
||||
self.event_unsub = async_track_point_in_time(
|
||||
self.hass, self.async_update, next_update_at
|
||||
)
|
||||
|
||||
async def async_update(self, *_):
|
||||
"""Update sensors with new prayer times."""
|
||||
try:
|
||||
prayer_times = await self.hass.async_add_executor_job(
|
||||
self.get_new_prayer_times
|
||||
)
|
||||
self.available = True
|
||||
except (exceptions.InvalidResponseError, ConnError):
|
||||
self.available = False
|
||||
_LOGGER.debug("Error retrieving prayer times.")
|
||||
async_call_later(self.hass, 60, self.async_update)
|
||||
return
|
||||
|
||||
for prayer, time in prayer_times.items():
|
||||
self.prayer_times_info[prayer] = dt_util.parse_datetime(
|
||||
f"{dt_util.now().date()} {time}"
|
||||
)
|
||||
await self.async_schedule_future_update()
|
||||
|
||||
_LOGGER.debug("New prayer times retrieved. Updating sensors.")
|
||||
async_dispatcher_send(self.hass, DATA_UPDATED)
|
||||
|
||||
async def async_setup(self):
|
||||
"""Set up the Islamic prayer client."""
|
||||
await self.async_add_options()
|
||||
|
||||
try:
|
||||
await self.hass.async_add_executor_job(self.get_new_prayer_times)
|
||||
except (exceptions.InvalidResponseError, ConnError):
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
await self.async_update()
|
||||
self.config_entry.add_update_listener(self.async_options_updated)
|
||||
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_forward_entry_setup(
|
||||
self.config_entry, "sensor"
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def async_add_options(self):
|
||||
"""Add options for entry."""
|
||||
if not self.config_entry.options:
|
||||
data = dict(self.config_entry.data)
|
||||
calc_method = data.pop(CONF_CALC_METHOD, DEFAULT_CALC_METHOD)
|
||||
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.config_entry, data=data, options={CONF_CALC_METHOD: calc_method}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
async def async_options_updated(hass, entry):
|
||||
"""Triggered by config entry options updates."""
|
||||
if hass.data[DOMAIN].event_unsub:
|
||||
hass.data[DOMAIN].event_unsub()
|
||||
await hass.data[DOMAIN].async_update()
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
"""Config flow for Islamic Prayer Times integration."""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import callback
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from .const import CALC_METHODS, CONF_CALC_METHOD, DEFAULT_CALC_METHOD, DOMAIN, NAME
|
||||
|
||||
|
||||
class IslamicPrayerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the Islamic Prayer config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow for this handler."""
|
||||
return IslamicPrayerOptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="one_instance_allowed")
|
||||
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="user")
|
||||
|
||||
return self.async_create_entry(title=NAME, data=user_input)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import from config."""
|
||||
return await self.async_step_user(user_input=import_config)
|
||||
|
||||
|
||||
class IslamicPrayerOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle Islamic Prayer client options."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
options = {
|
||||
vol.Optional(
|
||||
CONF_CALC_METHOD,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_CALC_METHOD, DEFAULT_CALC_METHOD
|
||||
),
|
||||
): vol.In(CALC_METHODS)
|
||||
}
|
||||
|
||||
return self.async_show_form(step_id="init", data_schema=vol.Schema(options))
|
|
@ -0,0 +1,14 @@
|
|||
"""Constants for the Islamic Prayer component."""
|
||||
DOMAIN = "islamic_prayer_times"
|
||||
NAME = "Islamic Prayer Times"
|
||||
SENSOR_SUFFIX = "Prayer"
|
||||
PRAYER_TIMES_ICON = "mdi:calendar-clock"
|
||||
|
||||
SENSOR_TYPES = ["Fajr", "Sunrise", "Dhuhr", "Asr", "Maghrib", "Isha", "Midnight"]
|
||||
|
||||
CONF_CALC_METHOD = "calc_method"
|
||||
|
||||
CALC_METHODS = ["isna", "karachi", "mwl", "makkah"]
|
||||
DEFAULT_CALC_METHOD = "isna"
|
||||
|
||||
DATA_UPDATED = "Islamic_prayer_data_updated"
|
|
@ -3,5 +3,6 @@
|
|||
"name": "Islamic Prayer Times",
|
||||
"documentation": "https://www.home-assistant.io/integrations/islamic_prayer_times",
|
||||
"requirements": ["prayer_times_calculator==0.0.3"],
|
||||
"codeowners": []
|
||||
"codeowners": ["@engrbm87"],
|
||||
"config_flow": true
|
||||
}
|
||||
|
|
|
@ -1,186 +1,44 @@
|
|||
"""Platform to retrieve Islamic prayer times information for Home Assistant."""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from prayer_times_calculator import PrayerTimesCalculator
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import DEVICE_CLASS_TIMESTAMP
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import async_track_point_in_time
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import DATA_UPDATED, DOMAIN, PRAYER_TIMES_ICON, SENSOR_SUFFIX, SENSOR_TYPES
|
||||
|
||||
_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):
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""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
|
||||
client = hass.data[DOMAIN]
|
||||
|
||||
prayer_times_data = IslamicPrayerTimesData(latitude, longitude, calc_method)
|
||||
entities = []
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
entities.append(IslamicPrayerTimeSensor(sensor_type, client))
|
||||
|
||||
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 = f"{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 def update_sensors(_):
|
||||
"""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
|
||||
)
|
||||
|
||||
async_track_point_in_time(hass, update_sensors, next_update_at)
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
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
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class IslamicPrayerTimeSensor(Entity):
|
||||
"""Representation of an Islamic prayer time sensor."""
|
||||
|
||||
def __init__(self, sensor_type, prayer_times_data):
|
||||
def __init__(self, sensor_type, client):
|
||||
"""Initialize the Islamic prayer time sensor."""
|
||||
self.sensor_type = sensor_type
|
||||
self.entity_id = f"sensor.islamic_prayer_time_{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()
|
||||
self.client = client
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
return f"{self.sensor_type} {SENSOR_SUFFIX}"
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of the entity."""
|
||||
return self.sensor_type
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
|
@ -190,7 +48,7 @@ class IslamicPrayerTimeSensor(Entity):
|
|||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
return self.client.prayer_times_info.get(self.sensor_type).isoformat()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -200,18 +58,10 @@ class IslamicPrayerTimeSensor(Entity):
|
|||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
return DEVICE_CLASS_TIMESTAMP
|
||||
|
||||
@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 = f"{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()
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle entity which will be added."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, DATA_UPDATED, self.async_write_ha_state)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"title": "Islamic Prayer Times",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up Islamic Prayer Times",
|
||||
"description": "Do you want to set up Islamic Prayer Times?"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"one_instance_allowed": "Only a single instance is necessary."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"calc_method": "Prayer calculation method"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ FLOWS = [
|
|||
"ipma",
|
||||
"ipp",
|
||||
"iqvia",
|
||||
"islamic_prayer_times",
|
||||
"izone",
|
||||
"konnected",
|
||||
"life360",
|
||||
|
|
|
@ -1 +1,45 @@
|
|||
"""Tests for the islamic_prayer_times component."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
PRAYER_TIMES = {
|
||||
"Fajr": "06:10",
|
||||
"Sunrise": "07:25",
|
||||
"Dhuhr": "12:30",
|
||||
"Asr": "15:32",
|
||||
"Maghrib": "17:35",
|
||||
"Isha": "18:53",
|
||||
"Midnight": "00:45",
|
||||
}
|
||||
|
||||
PRAYER_TIMES_TIMESTAMPS = {
|
||||
"Fajr": datetime(2020, 1, 1, 6, 10, 0),
|
||||
"Sunrise": datetime(2020, 1, 1, 7, 25, 0),
|
||||
"Dhuhr": datetime(2020, 1, 1, 12, 30, 0),
|
||||
"Asr": datetime(2020, 1, 1, 15, 32, 0),
|
||||
"Maghrib": datetime(2020, 1, 1, 17, 35, 0),
|
||||
"Isha": datetime(2020, 1, 1, 18, 53, 0),
|
||||
"Midnight": datetime(2020, 1, 1, 00, 45, 0),
|
||||
}
|
||||
|
||||
NEW_PRAYER_TIMES = {
|
||||
"Fajr": "06:00",
|
||||
"Sunrise": "07:25",
|
||||
"Dhuhr": "12:30",
|
||||
"Asr": "15:32",
|
||||
"Maghrib": "17:45",
|
||||
"Isha": "18:53",
|
||||
"Midnight": "00:43",
|
||||
}
|
||||
|
||||
NEW_PRAYER_TIMES_TIMESTAMPS = {
|
||||
"Fajr": datetime(2020, 1, 1, 6, 00, 0),
|
||||
"Sunrise": datetime(2020, 1, 1, 7, 25, 0),
|
||||
"Dhuhr": datetime(2020, 1, 1, 12, 30, 0),
|
||||
"Asr": datetime(2020, 1, 1, 15, 32, 0),
|
||||
"Maghrib": datetime(2020, 1, 1, 17, 45, 0),
|
||||
"Isha": datetime(2020, 1, 1, 18, 53, 0),
|
||||
"Midnight": datetime(2020, 1, 1, 00, 43, 0),
|
||||
}
|
||||
|
||||
NOW = datetime(2020, 1, 1, 00, 00, 0)
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
"""Tests for Islamic Prayer Times config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components import islamic_prayer_times
|
||||
from homeassistant.components.islamic_prayer_times.const import CONF_CALC_METHOD, DOMAIN
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_setup", autouse=True)
|
||||
def mock_setup():
|
||||
"""Mock entry setup."""
|
||||
with patch(
|
||||
"homeassistant.components.islamic_prayer_times.async_setup_entry",
|
||||
return_value=True,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
async def test_flow_works(hass):
|
||||
"""Test user config."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
islamic_prayer_times.DOMAIN, context={"source": "user"}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "Islamic Prayer Times"
|
||||
|
||||
|
||||
async def test_options(hass):
|
||||
"""Test updating options."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="Islamic Prayer Times",
|
||||
data={},
|
||||
options={CONF_CALC_METHOD: "isna"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={CONF_CALC_METHOD: "makkah"}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"][CONF_CALC_METHOD] == "makkah"
|
||||
|
||||
|
||||
async def test_import(hass):
|
||||
"""Test import step."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
islamic_prayer_times.DOMAIN,
|
||||
context={"source": "import"},
|
||||
data={CONF_CALC_METHOD: "makkah"},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == "Islamic Prayer Times"
|
||||
assert result["data"][CONF_CALC_METHOD] == "makkah"
|
||||
|
||||
|
||||
async def test_integration_already_configured(hass):
|
||||
"""Test integration is already configured."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data={}, options={},)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
islamic_prayer_times.DOMAIN, context={"source": "user"}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "one_instance_allowed"
|
|
@ -0,0 +1,133 @@
|
|||
"""Tests for Islamic Prayer Times init."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from prayer_times_calculator.exceptions import InvalidResponseError
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import islamic_prayer_times
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import (
|
||||
NEW_PRAYER_TIMES,
|
||||
NEW_PRAYER_TIMES_TIMESTAMPS,
|
||||
NOW,
|
||||
PRAYER_TIMES,
|
||||
PRAYER_TIMES_TIMESTAMPS,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_setup_with_config(hass):
|
||||
"""Test that we import the config and setup the client."""
|
||||
config = {
|
||||
islamic_prayer_times.DOMAIN: {islamic_prayer_times.CONF_CALC_METHOD: "isna"}
|
||||
}
|
||||
with patch(
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
|
||||
return_value=PRAYER_TIMES,
|
||||
):
|
||||
assert (
|
||||
await async_setup_component(hass, islamic_prayer_times.DOMAIN, config)
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
async def test_successful_config_entry(hass):
|
||||
"""Test that Islamic Prayer Times is configured successfully."""
|
||||
|
||||
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={},)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
|
||||
return_value=PRAYER_TIMES,
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert entry.state == config_entries.ENTRY_STATE_LOADED
|
||||
assert entry.options == {
|
||||
islamic_prayer_times.CONF_CALC_METHOD: islamic_prayer_times.DEFAULT_CALC_METHOD
|
||||
}
|
||||
|
||||
|
||||
async def test_setup_failed(hass):
|
||||
"""Test Islamic Prayer Times failed due to an error."""
|
||||
|
||||
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={},)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
# test request error raising ConfigEntryNotReady
|
||||
with patch(
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
|
||||
side_effect=InvalidResponseError(),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY
|
||||
|
||||
|
||||
async def test_unload_entry(hass):
|
||||
"""Test removing Islamic Prayer Times."""
|
||||
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={},)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
|
||||
return_value=PRAYER_TIMES,
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
|
||||
assert islamic_prayer_times.DOMAIN not in hass.data
|
||||
|
||||
|
||||
async def test_islamic_prayer_times_timestamp_format(hass):
|
||||
"""Test Islamic prayer times timestamp format."""
|
||||
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
|
||||
return_value=PRAYER_TIMES,
|
||||
), patch("homeassistant.util.dt.now", return_value=NOW):
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
assert (
|
||||
hass.data[islamic_prayer_times.DOMAIN].prayer_times_info
|
||||
== PRAYER_TIMES_TIMESTAMPS
|
||||
)
|
||||
|
||||
|
||||
async def test_update(hass):
|
||||
"""Test sensors are updated with new prayer times."""
|
||||
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times"
|
||||
) as FetchPrayerTimes, patch("homeassistant.util.dt.now", return_value=NOW):
|
||||
FetchPrayerTimes.side_effect = [
|
||||
PRAYER_TIMES,
|
||||
PRAYER_TIMES,
|
||||
NEW_PRAYER_TIMES,
|
||||
]
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
pt_data = hass.data[islamic_prayer_times.DOMAIN]
|
||||
assert pt_data.prayer_times_info == PRAYER_TIMES_TIMESTAMPS
|
||||
|
||||
future = pt_data.prayer_times_info["Midnight"] + timedelta(days=1, minutes=1)
|
||||
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
assert (
|
||||
hass.data[islamic_prayer_times.DOMAIN].prayer_times_info
|
||||
== NEW_PRAYER_TIMES_TIMESTAMPS
|
||||
)
|
|
@ -1,186 +1,28 @@
|
|||
"""The tests for the Islamic prayer times sensor platform."""
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.islamic_prayer_times.sensor import IslamicPrayerTimesData
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components import islamic_prayer_times
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
from . import NOW, PRAYER_TIMES, PRAYER_TIMES_TIMESTAMPS
|
||||
|
||||
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_{}"
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
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):
|
||||
async def test_islamic_prayer_times_sensors(hass):
|
||||
"""Test minimum Islamic prayer times configuration."""
|
||||
min_config_sensors = ["fajr", "dhuhr", "asr", "maghrib", "isha"]
|
||||
entry = MockConfigEntry(domain=islamic_prayer_times.DOMAIN, data={})
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
|
||||
) as PrayerTimesCalculator:
|
||||
PrayerTimesCalculator.return_value.fetch_prayer_times.return_value = (
|
||||
PRAYER_TIMES
|
||||
)
|
||||
"prayer_times_calculator.PrayerTimesCalculator.fetch_prayer_times",
|
||||
return_value=PRAYER_TIMES,
|
||||
), patch("homeassistant.util.dt.now", return_value=NOW):
|
||||
|
||||
config = {"sensor": {"platform": "islamic_prayer_times"}}
|
||||
assert await async_setup_component(hass, "sensor", config) is True
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
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 patch(
|
||||
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
|
||||
) as PrayerTimesCalculator:
|
||||
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 patch(
|
||||
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
|
||||
) as PrayerTimesCalculator:
|
||||
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 patch(
|
||||
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
|
||||
) as PrayerTimesCalculator:
|
||||
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 patch(
|
||||
"homeassistant.components.islamic_prayer_times.sensor.PrayerTimesCalculator"
|
||||
) as PrayerTimesCalculator:
|
||||
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.islamic_prayer_times.sensor.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()
|
||||
for prayer in PRAYER_TIMES:
|
||||
assert (
|
||||
hass.states.get(f"sensor.{prayer}_prayer").state
|
||||
== PRAYER_TIMES_TIMESTAMPS[prayer].isoformat()
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue