Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add pring-debug service #120

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions custom_components/ims/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_MODE,
CONF_NAME, CONF_MONITORED_CONDITIONS,
CONF_NAME,
CONF_MONITORED_CONDITIONS,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand Down Expand Up @@ -50,14 +51,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
ims_scan_int = entry.data[CONF_UPDATE_INTERVAL]
conditions = _get_config_value(entry, CONF_MONITORED_CONDITIONS)


# Extract list of int from forecast days/ hours string if present
# _LOGGER.warning('forecast_days_type: ' + str(type(forecast_days)))
is_legacy_city = False
if isinstance(city, int | str):
is_legacy_city = True

city_id = city if is_legacy_city else city['lid']
city_id = city if is_legacy_city else city["lid"]

unique_location = f"ims-{language}-{city_id}"

Expand Down Expand Up @@ -93,7 +93,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

# If both platforms
if (IMS_PLATFORMS[0] in ims_entity_platform) and (
IMS_PLATFORMS[1] in ims_entity_platform
IMS_PLATFORMS[1] in ims_entity_platform
):
platforms = PLATFORMS
# If only sensor
Expand All @@ -102,11 +102,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# If only weather
elif IMS_PLATFORMS[1] in ims_entity_platform:
platforms = [PLATFORMS[1]]

await hass.config_entries.async_forward_entry_setups(entry, platforms)

update_listener = entry.add_update_listener(async_update_options)
hass.data[DOMAIN][entry.entry_id][UPDATE_LISTENER] = update_listener

# Register the debug service
async def handle_debug_get_coordinator_data(call) -> None: # noqa: ANN001 ARG001
# Log or return coordinator data
data = weather_coordinator.data
_LOGGER.info("Coordinator data: %s", data)
hass.bus.async_fire("custom_component_debug_event", {"data": data})

hass.services.async_register(
DOMAIN, "debug_get_coordinator_data", handle_debug_get_coordinator_data
)
return True


Expand All @@ -122,7 +133,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = False
# If both
if (IMS_PLATFORMS[0] in ims_entity_prevplatform) and (
IMS_PLATFORMS[1] in ims_entity_prevplatform
IMS_PLATFORMS[1] in ims_entity_prevplatform
):
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
# If only sensor
Expand All @@ -147,7 +158,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True


def _get_config_value(config_entry: ConfigEntry, key: str, default = None) -> Any:
def _get_config_value(config_entry: ConfigEntry, key: str, default=None) -> Any:
if config_entry.options:
val = config_entry.options.get(key)
if val:
Expand All @@ -163,14 +174,14 @@ def _get_config_value(config_entry: ConfigEntry, key: str, default = None) -> An
return default



def _filter_domain_configs(elements, domain):
return list(filter(lambda elem: elem["platform"] == domain, elements))


@dataclass(kw_only=True, frozen=True)
class ImsSensorEntityDescription(SensorEntityDescription):
"""Describes IMS Weather sensor entity."""

field_name: str | None = None
forecast_mode: str | None = None

Expand All @@ -181,7 +192,9 @@ class ImsEntity(CoordinatorEntity):
_attr_has_entity_name = True

def __init__(
self, coordinator: WeatherUpdateCoordinator, description: ImsSensorEntityDescription
self,
coordinator: WeatherUpdateCoordinator,
description: ImsSensorEntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
Expand Down
38 changes: 27 additions & 11 deletions custom_components/ims/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
from collections.abc import Callable
from dataclasses import dataclass

from homeassistant.components.binary_sensor import BinarySensorEntityDescription, BinarySensorEntity
from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import ImsEntity, ImsSensorEntityDescription
from .const import (TYPE_IS_RAINING, IMS_SENSOR_KEY_PREFIX, FORECAST_MODE, FIELD_NAME_RAIN, DOMAIN,
ENTRY_WEATHER_COORDINATOR)
from .const import (
TYPE_IS_RAINING,
IMS_SENSOR_KEY_PREFIX,
FORECAST_MODE,
FIELD_NAME_RAIN,
DOMAIN,
ENTRY_WEATHER_COORDINATOR,
)
from .weather_update_coordinator import WeatherData



@dataclass(frozen=True, kw_only=True)
class ImsBinaryEntityDescriptionMixin:
"""Mixin values for required keys."""
Expand All @@ -22,7 +30,11 @@ class ImsBinaryEntityDescriptionMixin:


@dataclass(frozen=True, kw_only=True)
class ImsBinarySensorEntityDescription(ImsSensorEntityDescription, BinarySensorEntityDescription, ImsBinaryEntityDescriptionMixin):
class ImsBinarySensorEntityDescription(
ImsSensorEntityDescription,
BinarySensorEntityDescription,
ImsBinaryEntityDescriptionMixin,
):
"""Class describing IMS Binary sensors entities"""


Expand All @@ -33,17 +45,21 @@ class ImsBinarySensorEntityDescription(ImsSensorEntityDescription, BinarySensorE
icon="mdi:weather-rainy",
forecast_mode=FORECAST_MODE.CURRENT,
field_name=FIELD_NAME_RAIN,
value_fn=lambda data: data.current_weather.rain and data.current_weather.rain > 0.0
value_fn=lambda data: data.current_weather.rain
and data.current_weather.rain > 0.0,
),
)

BINARY_SENSOR_DESCRIPTIONS_DICT = {desc.key: desc for desc in BINARY_SENSORS_DESCRIPTIONS}
BINARY_SENSOR_DESCRIPTIONS_DICT = {
desc.key: desc for desc in BINARY_SENSORS_DESCRIPTIONS
}
BINARY_SENSOR_DESCRIPTIONS_KEYS = [desc.key for desc in BINARY_SENSORS_DESCRIPTIONS]


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up a IMS binary sensors based on a config entry."""
domain_data = hass.data[DOMAIN][entry.entry_id]
Expand All @@ -62,9 +78,9 @@ async def async_setup_entry(
description = BINARY_SENSOR_DESCRIPTIONS_DICT[condition]
sensors.append(ImsBinarySensor(weather_coordinator, description))


async_add_entities(sensors, update_before_add=True)


class ImsBinarySensor(ImsEntity, BinarySensorEntity):
"""Defines an IMS binary sensor."""

Expand Down
46 changes: 31 additions & 15 deletions custom_components/ims/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Config flow for IMS Weather."""

import logging
from datetime import timedelta

Expand All @@ -10,7 +11,8 @@
from homeassistant import config_entries
from homeassistant.const import (
CONF_MODE,
CONF_NAME, CONF_MONITORED_CONDITIONS,
CONF_NAME,
CONF_MONITORED_CONDITIONS,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -41,6 +43,7 @@
cities_data = None
SENSOR_KEYS = SENSOR_DESCRIPTIONS_KEYS + BINARY_SENSOR_DESCRIPTIONS_KEYS


class IMSWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for IMSWeather."""

Expand Down Expand Up @@ -106,7 +109,9 @@ async def async_step_user(self, user_input=None):
cities = await _get_localized_cities(self.hass)
if not cities:
errors["base"] = "cannot_retrieve_cities"
return self.async_show_form(step_id="user", data_schema=vol.Schema({}), errors=errors)
return self.async_show_form(
step_id="user", data_schema=vol.Schema({}), errors=errors
)

# Step 2: Calculate the closest city based on Home Assistant's coordinates
ha_latitude = self.hass.config.latitude
Expand All @@ -115,11 +120,13 @@ async def async_step_user(self, user_input=None):

# Step 3: Create a selection field for cities
city_options = {city_id: city["name"] for city_id, city in cities.items()}

schema = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Required(CONF_CITY, default=closest_city["lid"]): vol.In(city_options),
vol.Required(CONF_CITY, default=closest_city["lid"]): vol.In(
city_options
),
vol.Required(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(
LANGUAGES
),
Expand All @@ -132,9 +139,9 @@ async def async_step_user(self, user_input=None):
vol.Required(CONF_MODE, default=DEFAULT_FORECAST_MODE): vol.In(
FORECAST_MODES
),
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): cv.multi_select(
SENSOR_KEYS
),
vol.Optional(
CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS
): cv.multi_select(SENSOR_KEYS),
vol.Required(CONF_IMAGES_PATH, default="/tmp"): cv.string,
}
)
Expand Down Expand Up @@ -165,25 +172,30 @@ async def async_step_import(self, import_input=None):
config[CONF_MONITORED_CONDITIONS] = SENSOR_KEYS
return await self.async_step_user(config)


supported_ims_languages = ["en", "he", "ar"]


async def _is_ims_api_online(hass, language, city):
forecast_url = "https://ims.gov.il/" + language + "/forecast_data/" + str(city)

async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(family=socket.AF_INET), raise_for_status=False) as session:
async with aiohttp.ClientSession(
connector=aiohttp.TCPConnector(family=socket.AF_INET), raise_for_status=False
) as session:
async with session.get(forecast_url) as resp:
status = resp.status

return status


async def _get_localized_cities(hass):
global cities_data
if cities_data:
return cities_data

lang = hass.config.language
if lang not in supported_ims_languages:
lang = 'en'
lang = "en"
locations_info_url = "https://ims.gov.il/" + lang + "/locations_info"

try:
Expand All @@ -204,13 +216,16 @@ async def _get_localized_cities(hass):

return cities_data


@callback
def _handle_http_error(self, error):
"""Handle HTTP errors."""
self.hass.logger.error(f"Error fetching data from URL: {error}")


def _find_closest_city(cities, ha_latitude, ha_longitude):
"""Find the closest city based on the Home Assistant coordinates."""

def distance(lat1, lon1, lat2, lon2):
# Calculate the distance between two lat/lon points (Haversine formula)
R = 6371 # Radius of Earth in kilometers
Expand All @@ -223,23 +238,22 @@ def distance(lat1, lon1, lat2, lon2):

closest_city = None
closest_distance = float("inf")

for city_id, city in cities.items():
city_lat = float(city["lat"])
city_lon = float(city["lon"])
dist = distance(ha_latitude, ha_longitude, city_lat, city_lon)

if dist < closest_distance:
closest_distance = dist
closest_city = city

if closest_distance > 10:
return cities["1"]
else:
return closest_city



class IMSWeatherOptionsFlow(config_entries.OptionsFlow):
"""Handle options."""

Expand Down Expand Up @@ -313,7 +327,9 @@ async def async_step_init(self, user_input=None):
CONF_MONITORED_CONDITIONS,
default=self.config_entry.options.get(
CONF_MONITORED_CONDITIONS,
self.config_entry.data.get(CONF_MONITORED_CONDITIONS, SENSOR_KEYS),
self.config_entry.data.get(
CONF_MONITORED_CONDITIONS, SENSOR_KEYS
),
),
): cv.multi_select(SENSOR_KEYS),
vol.Optional(
Expand All @@ -327,4 +343,4 @@ async def async_step_init(self, user_input=None):
): str,
}
),
)
)
3 changes: 2 additions & 1 deletion custom_components/ims/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Consts for the OpenWeatherMap."""

from __future__ import annotations
import types

Expand Down Expand Up @@ -171,7 +172,7 @@
"1140": ATTR_CONDITION_POURING,
"1160": ATTR_CONDITION_FOG,
"1220": ATTR_CONDITION_PARTLYCLOUDY,
"1220-night": ATTR_CONDITION_PARTLYCLOUDY, #no "-night"
"1220-night": ATTR_CONDITION_PARTLYCLOUDY, # no "-night"
"1230": ATTR_CONDITION_CLOUDY,
"1250": ATTR_CONDITION_SUNNY,
"1250-night": ATTR_CONDITION_CLEAR_NIGHT,
Expand Down
Loading