Skip to content

Commit

Permalink
feat: Add pring-debug service
Browse files Browse the repository at this point in the history
  • Loading branch information
GuyKh committed Oct 29, 2024
1 parent 8a67cd3 commit bb54a92
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 212 deletions.
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

0 comments on commit bb54a92

Please sign in to comment.