Compare commits
No commits in common. "7d1494f29b0069fbad7be7a5fdb6811bef2d62eb" and "cc1afaa2182da0d33cd46849588ff0dc03be7ac1" have entirely different histories.
7d1494f29b
...
cc1afaa218
|
|
@ -197,8 +197,6 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
_wslink: bool = checked_or(self.config.options.get(WSLINK), bool, False)
|
_wslink: bool = checked_or(self.config.options.get(WSLINK), bool, False)
|
||||||
|
|
||||||
# Incoming station payload is delivered as query params.
|
# Incoming station payload is delivered as query params.
|
||||||
# Some stations posts data in body, so we need to contracts those data.
|
|
||||||
#
|
|
||||||
# We copy it to a plain dict so it can be passed around safely.
|
# We copy it to a plain dict so it can be passed around safely.
|
||||||
get_data = webdata.query
|
get_data = webdata.query
|
||||||
post_data = await webdata.post()
|
post_data = await webdata.post()
|
||||||
|
|
@ -343,52 +341,28 @@ def register_path(
|
||||||
|
|
||||||
_wslink: bool = checked_or(config.options.get(WSLINK), bool, False)
|
_wslink: bool = checked_or(config.options.get(WSLINK), bool, False)
|
||||||
|
|
||||||
# Load registred routes
|
# Create internal route dispatcher with provided urls
|
||||||
routes: Routes | None = config.options.get("routes", None)
|
routes: Routes = Routes()
|
||||||
|
routes.add_route(DEFAULT_URL, coordinator.received_data, enabled=not _wslink)
|
||||||
|
routes.add_route(WSLINK_URL, coordinator.received_data, enabled=_wslink)
|
||||||
|
routes.add_route(HEALTH_URL, coordinator_h.health_status, enabled=True)
|
||||||
|
|
||||||
if not isinstance(routes, Routes):
|
# Register webhooks in HomeAssistant with dispatcher
|
||||||
routes = Routes()
|
try:
|
||||||
|
_ = hass.http.app.router.add_get(DEFAULT_URL, routes.dispatch)
|
||||||
|
_ = hass.http.app.router.add_post(WSLINK_URL, routes.dispatch)
|
||||||
|
_ = hass.http.app.router.add_get(HEALTH_URL, routes.dispatch)
|
||||||
|
|
||||||
# Register webhooks in HomeAssistant with dispatcher
|
# Save initialised routes
|
||||||
try:
|
hass_data["routes"] = routes
|
||||||
_default_route = hass.http.app.router.add_get(
|
|
||||||
DEFAULT_URL, routes.dispatch, name="_default_route"
|
|
||||||
)
|
|
||||||
_wslink_post_route = hass.http.app.router.add_post(
|
|
||||||
WSLINK_URL, routes.dispatch, name="_wslink_post_route"
|
|
||||||
)
|
|
||||||
_wslink_get_route = hass.http.app.router.add_get(
|
|
||||||
WSLINK_URL, routes.dispatch, name="_wslink_get_route"
|
|
||||||
)
|
|
||||||
_health_route = hass.http.app.router.add_get(
|
|
||||||
HEALTH_URL, routes.dispatch, name="_health_route"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Save initialised routes
|
except RuntimeError as Ex:
|
||||||
hass_data["routes"] = routes
|
_LOGGER.critical(
|
||||||
|
"Routes cannot be added. Integration will not work as expected. %s", Ex
|
||||||
except RuntimeError as Ex:
|
|
||||||
_LOGGER.critical(
|
|
||||||
"Routes cannot be added. Integration will not work as expected. %s", Ex
|
|
||||||
)
|
|
||||||
raise ConfigEntryNotReady from Ex
|
|
||||||
|
|
||||||
# Finally create internal route dispatcher with provided urls, while we have webhooks registered.
|
|
||||||
routes.add_route(
|
|
||||||
DEFAULT_URL, _default_route, coordinator.received_data, enabled=not _wslink
|
|
||||||
)
|
|
||||||
routes.add_route(
|
|
||||||
WSLINK_URL, _wslink_post_route, coordinator.received_data, enabled=_wslink
|
|
||||||
)
|
|
||||||
routes.add_route(
|
|
||||||
WSLINK_URL, _wslink_get_route, coordinator.received_data, enabled=_wslink
|
|
||||||
)
|
|
||||||
routes.add_route(
|
|
||||||
HEALTH_URL, _health_route, coordinator_h.health_status, enabled=True
|
|
||||||
)
|
)
|
||||||
|
raise ConfigEntryNotReady from Ex
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("We have already registered routes: %s", routes.show_enabled())
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
@ -454,9 +428,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
if routes:
|
if routes:
|
||||||
_LOGGER.debug("We have routes registered, will try to switch dispatcher.")
|
_LOGGER.debug("We have routes registered, will try to switch dispatcher.")
|
||||||
routes.switch_route(
|
routes.switch_route(DEFAULT_URL if not _wslink else WSLINK_URL)
|
||||||
coordinator.received_data, DEFAULT_URL if not _wslink else WSLINK_URL
|
|
||||||
)
|
|
||||||
_LOGGER.debug("%s", routes.show_enabled())
|
_LOGGER.debug("%s", routes.show_enabled())
|
||||||
else:
|
else:
|
||||||
routes_enabled = register_path(hass, coordinator, coordinator_health, entry)
|
routes_enabled = register_path(hass, coordinator, coordinator_health, entry)
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class InvalidAuth(HomeAssistantError):
|
||||||
class ConfigOptionsFlowHandler(OptionsFlow):
|
class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
"""Handle WeatherStation ConfigFlow."""
|
"""Handle WeatherStation ConfigFlow."""
|
||||||
|
|
||||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize flow."""
|
"""Initialize flow."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|
@ -66,12 +66,16 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
self.ecowitt: dict[str, Any] = {}
|
self.ecowitt: dict[str, Any] = {}
|
||||||
self.ecowitt_schema = {}
|
self.ecowitt_schema = {}
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def config_entry(self) -> ConfigEntry:
|
||||||
|
# return self.hass.config_entries.async_get_entry(self.handler)
|
||||||
|
|
||||||
async def _get_entry_data(self):
|
async def _get_entry_data(self):
|
||||||
"""Get entry data."""
|
"""Get entry data."""
|
||||||
|
|
||||||
self.user_data = {
|
self.user_data = {
|
||||||
API_ID: self.config_entry.options.get(API_ID, ""),
|
API_ID: self.config_entry.options.get(API_ID),
|
||||||
API_KEY: self.config_entry.options.get(API_KEY, ""),
|
API_KEY: self.config_entry.options.get(API_KEY),
|
||||||
WSLINK: self.config_entry.options.get(WSLINK, False),
|
WSLINK: self.config_entry.options.get(WSLINK, False),
|
||||||
DEV_DBG: self.config_entry.options.get(DEV_DBG, False),
|
DEV_DBG: self.config_entry.options.get(DEV_DBG, False),
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +159,6 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
|
|
||||||
async def async_step_init(self, user_input: dict[str, Any] = {}):
|
async def async_step_init(self, user_input: dict[str, Any] = {}):
|
||||||
"""Manage the options - show menu first."""
|
"""Manage the options - show menu first."""
|
||||||
_ = user_input
|
|
||||||
return self.async_show_menu(
|
return self.async_show_menu(
|
||||||
step_id="init", menu_options=["basic", "ecowitt", "windy", "pocasi"]
|
step_id="init", menu_options=["basic", "ecowitt", "windy", "pocasi"]
|
||||||
)
|
)
|
||||||
|
|
@ -353,4 +356,4 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
@callback
|
@callback
|
||||||
def async_get_options_flow(config_entry: ConfigEntry) -> ConfigOptionsFlowHandler:
|
def async_get_options_flow(config_entry: ConfigEntry) -> ConfigOptionsFlowHandler:
|
||||||
"""Get the options flow for this handler."""
|
"""Get the options flow for this handler."""
|
||||||
return ConfigOptionsFlowHandler(config_entry=config_entry)
|
return ConfigOptionsFlowHandler()
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@ SENSOR_TO_MIGRATE: Final = "sensor_to_migrate"
|
||||||
DEV_DBG: Final = "dev_debug_checkbox"
|
DEV_DBG: Final = "dev_debug_checkbox"
|
||||||
WSLINK: Final = "wslink"
|
WSLINK: Final = "wslink"
|
||||||
|
|
||||||
WINDY_MAX_RETRIES: Final = 3
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DOMAIN",
|
"DOMAIN",
|
||||||
"DEFAULT_URL",
|
"DEFAULT_URL",
|
||||||
|
|
@ -200,27 +198,9 @@ CH2_BATTERY: Final = "ch2_battery"
|
||||||
CH3_TEMP: Final = "ch3_temp"
|
CH3_TEMP: Final = "ch3_temp"
|
||||||
CH3_HUMIDITY: Final = "ch3_humidity"
|
CH3_HUMIDITY: Final = "ch3_humidity"
|
||||||
CH3_CONNECTION: Final = "ch3_connection"
|
CH3_CONNECTION: Final = "ch3_connection"
|
||||||
CH3_BATTERY: Final = "ch3_battery"
|
|
||||||
CH4_TEMP: Final = "ch4_temp"
|
CH4_TEMP: Final = "ch4_temp"
|
||||||
CH4_HUMIDITY: Final = "ch4_humidity"
|
CH4_HUMIDITY: Final = "ch4_humidity"
|
||||||
CH4_CONNECTION: Final = "ch4_connection"
|
CH4_CONNECTION: Final = "ch4_connection"
|
||||||
CH4_BATTERY: Final = "ch4_battery"
|
|
||||||
CH5_TEMP: Final = "ch5_temp"
|
|
||||||
CH5_HUMIDITY: Final = "ch5_humidity"
|
|
||||||
CH5_CONNECTION: Final = "ch5_connection"
|
|
||||||
CH5_BATTERY: Final = "ch5_battery"
|
|
||||||
CH6_TEMP: Final = "ch6_temp"
|
|
||||||
CH6_HUMIDITY: Final = "ch6_humidity"
|
|
||||||
CH6_CONNECTION: Final = "ch6_connection"
|
|
||||||
CH6_BATTERY: Final = "ch6_battery"
|
|
||||||
CH7_TEMP: Final = "ch7_temp"
|
|
||||||
CH7_HUMIDITY: Final = "ch7_humidity"
|
|
||||||
CH7_CONNECTION: Final = "ch7_connection"
|
|
||||||
CH7_BATTERY: Final = "ch7_battery"
|
|
||||||
CH8_TEMP: Final = "ch8_temp"
|
|
||||||
CH8_HUMIDITY: Final = "ch8_humidity"
|
|
||||||
CH8_CONNECTION: Final = "ch8_connection"
|
|
||||||
CH8_BATTERY: Final = "ch8_battery"
|
|
||||||
HEAT_INDEX: Final = "heat_index"
|
HEAT_INDEX: Final = "heat_index"
|
||||||
CHILL_INDEX: Final = "chill_index"
|
CHILL_INDEX: Final = "chill_index"
|
||||||
WBGT_TEMP: Final = "wbgt_temp"
|
WBGT_TEMP: Final = "wbgt_temp"
|
||||||
|
|
@ -246,8 +226,6 @@ REMAP_ITEMS: dict[str, str] = {
|
||||||
"soilmoisture2": CH3_HUMIDITY,
|
"soilmoisture2": CH3_HUMIDITY,
|
||||||
"soiltemp3f": CH4_TEMP,
|
"soiltemp3f": CH4_TEMP,
|
||||||
"soilmoisture3": CH4_HUMIDITY,
|
"soilmoisture3": CH4_HUMIDITY,
|
||||||
"soiltemp4f": CH5_TEMP,
|
|
||||||
"soilmoisture4": CH5_HUMIDITY,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
REMAP_WSLINK_ITEMS: dict[str, str] = {
|
REMAP_WSLINK_ITEMS: dict[str, str] = {
|
||||||
|
|
@ -269,11 +247,6 @@ REMAP_WSLINK_ITEMS: dict[str, str] = {
|
||||||
"t1cn": OUTSIDE_CONNECTION,
|
"t1cn": OUTSIDE_CONNECTION,
|
||||||
"t234c1cn": CH2_CONNECTION,
|
"t234c1cn": CH2_CONNECTION,
|
||||||
"t234c2cn": CH3_CONNECTION,
|
"t234c2cn": CH3_CONNECTION,
|
||||||
"t234c3cn": CH4_CONNECTION,
|
|
||||||
"t234c4cn": CH5_CONNECTION,
|
|
||||||
"t234c5cn": CH6_CONNECTION,
|
|
||||||
"t234c6cn": CH7_CONNECTION,
|
|
||||||
"t234c7cn": CH8_CONNECTION,
|
|
||||||
"t1chill": CHILL_INDEX,
|
"t1chill": CHILL_INDEX,
|
||||||
"t1heat": HEAT_INDEX,
|
"t1heat": HEAT_INDEX,
|
||||||
"t1rainhr": HOURLY_RAIN,
|
"t1rainhr": HOURLY_RAIN,
|
||||||
|
|
@ -282,25 +255,9 @@ REMAP_WSLINK_ITEMS: dict[str, str] = {
|
||||||
"t1rainyr": YEARLY_RAIN,
|
"t1rainyr": YEARLY_RAIN,
|
||||||
"t234c2tem": CH3_TEMP,
|
"t234c2tem": CH3_TEMP,
|
||||||
"t234c2hum": CH3_HUMIDITY,
|
"t234c2hum": CH3_HUMIDITY,
|
||||||
"t234c3tem": CH4_TEMP,
|
|
||||||
"t234c3hum": CH4_HUMIDITY,
|
|
||||||
"t234c4tem": CH5_TEMP,
|
|
||||||
"t234c4hum": CH5_HUMIDITY,
|
|
||||||
"t234c5tem": CH6_TEMP,
|
|
||||||
"t234c5hum": CH6_HUMIDITY,
|
|
||||||
"t234c6tem": CH7_TEMP,
|
|
||||||
"t234c6hum": CH7_HUMIDITY,
|
|
||||||
"t234c7tem": CH8_TEMP,
|
|
||||||
"t234c7hum": CH8_HUMIDITY,
|
|
||||||
"t1bat": OUTSIDE_BATTERY,
|
"t1bat": OUTSIDE_BATTERY,
|
||||||
"inbat": INDOOR_BATTERY,
|
"inbat": INDOOR_BATTERY,
|
||||||
"t234c1bat": CH2_BATTERY,
|
"t234c1bat": CH2_BATTERY,
|
||||||
"t234c2bat": CH3_BATTERY,
|
|
||||||
"t234c3bat": CH4_BATTERY,
|
|
||||||
"t234c4bat": CH5_BATTERY,
|
|
||||||
"t234c5bat": CH6_BATTERY,
|
|
||||||
"t234c6bat": CH7_BATTERY,
|
|
||||||
"t234c7bat": CH8_BATTERY,
|
|
||||||
"t1wbgt": WBGT_TEMP,
|
"t1wbgt": WBGT_TEMP,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,22 +274,8 @@ DISABLED_BY_DEFAULT: Final = [
|
||||||
CH2_BATTERY,
|
CH2_BATTERY,
|
||||||
CH3_TEMP,
|
CH3_TEMP,
|
||||||
CH3_HUMIDITY,
|
CH3_HUMIDITY,
|
||||||
CH3_BATTERY,
|
|
||||||
CH4_TEMP,
|
CH4_TEMP,
|
||||||
CH4_HUMIDITY,
|
CH4_HUMIDITY,
|
||||||
CH4_BATTERY,
|
|
||||||
CH5_TEMP,
|
|
||||||
CH5_HUMIDITY,
|
|
||||||
CH5_BATTERY,
|
|
||||||
CH6_TEMP,
|
|
||||||
CH6_HUMIDITY,
|
|
||||||
CH6_BATTERY,
|
|
||||||
CH7_TEMP,
|
|
||||||
CH7_HUMIDITY,
|
|
||||||
CH7_BATTERY,
|
|
||||||
CH8_TEMP,
|
|
||||||
CH8_HUMIDITY,
|
|
||||||
CH8_BATTERY,
|
|
||||||
OUTSIDE_BATTERY,
|
OUTSIDE_BATTERY,
|
||||||
WBGT_TEMP,
|
WBGT_TEMP,
|
||||||
]
|
]
|
||||||
|
|
@ -341,13 +284,6 @@ BATTERY_LIST = [
|
||||||
OUTSIDE_BATTERY,
|
OUTSIDE_BATTERY,
|
||||||
INDOOR_BATTERY,
|
INDOOR_BATTERY,
|
||||||
CH2_BATTERY,
|
CH2_BATTERY,
|
||||||
CH2_BATTERY,
|
|
||||||
CH3_BATTERY,
|
|
||||||
CH4_BATTERY,
|
|
||||||
CH5_BATTERY,
|
|
||||||
CH6_BATTERY,
|
|
||||||
CH7_BATTERY,
|
|
||||||
CH8_BATTERY,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from collections.abc import Awaitable, Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.web import AbstractRoute, Request, Response
|
from aiohttp.web import Request, Response
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -35,16 +35,10 @@ class RouteInfo:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url_path: str
|
url_path: str
|
||||||
route: AbstractRoute
|
|
||||||
handler: Handler
|
handler: Handler
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
|
|
||||||
fallback: Handler = field(default_factory=lambda: unregistered)
|
fallback: Handler = field(default_factory=lambda: unregistered)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""Return string representation."""
|
|
||||||
return f"RouteInfo(url_path={self.url_path}, route={self.route}, handler={self.handler}, enabled={self.enabled}, fallback={self.fallback})"
|
|
||||||
|
|
||||||
|
|
||||||
class Routes:
|
class Routes:
|
||||||
"""Simple route dispatcher.
|
"""Simple route dispatcher.
|
||||||
|
|
@ -60,65 +54,41 @@ class Routes:
|
||||||
|
|
||||||
async def dispatch(self, request: Request) -> Response:
|
async def dispatch(self, request: Request) -> Response:
|
||||||
"""Dispatch incoming request to either the enabled handler or a fallback."""
|
"""Dispatch incoming request to either the enabled handler or a fallback."""
|
||||||
key = f"{request.method}:{request.path}"
|
info = self.routes.get(request.path)
|
||||||
info = self.routes.get(key)
|
|
||||||
if not info:
|
if not info:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug("Route %s is not registered!", request.path)
|
||||||
"Route (%s):%s is not registered!", request.method, request.path
|
|
||||||
)
|
|
||||||
return await unregistered(request)
|
return await unregistered(request)
|
||||||
handler = info.handler if info.enabled else info.fallback
|
handler = info.handler if info.enabled else info.fallback
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
|
||||||
def switch_route(self, handler: Handler, url_path: str) -> None:
|
def switch_route(self, url_path: str) -> None:
|
||||||
"""Enable exactly one route and disable all others.
|
"""Enable exactly one route and disable all others.
|
||||||
|
|
||||||
This is called when options change (e.g. WSLink toggle). The aiohttp router stays
|
This is called when options change (e.g. WSLink toggle). The aiohttp router stays
|
||||||
untouched; we only flip which internal handler is active.
|
untouched; we only flip which internal handler is active.
|
||||||
"""
|
"""
|
||||||
for route in self.routes.values():
|
for path, info in self.routes.items():
|
||||||
if route.url_path == url_path:
|
info.enabled = path == url_path
|
||||||
_LOGGER.info(
|
|
||||||
"New coordinator to route: (%s):%s",
|
|
||||||
route.route.method,
|
|
||||||
route.url_path,
|
|
||||||
)
|
|
||||||
route.enabled = True
|
|
||||||
route.handler = handler
|
|
||||||
else:
|
|
||||||
route.enabled = False
|
|
||||||
route.handler = unregistered
|
|
||||||
|
|
||||||
def add_route(
|
def add_route(
|
||||||
self,
|
self, url_path: str, handler: Handler, *, enabled: bool = False
|
||||||
url_path: str,
|
|
||||||
route: AbstractRoute,
|
|
||||||
handler: Handler,
|
|
||||||
*,
|
|
||||||
enabled: bool = False,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Register a route in the dispatcher.
|
"""Register a route in the dispatcher.
|
||||||
|
|
||||||
This does not register anything in aiohttp. It only stores routing metadata that
|
This does not register anything in aiohttp. It only stores routing metadata that
|
||||||
`dispatch` uses after aiohttp has routed the request by path.
|
`dispatch` uses after aiohttp has routed the request by path.
|
||||||
"""
|
"""
|
||||||
key = f"{route.method}:{url_path}"
|
self.routes[url_path] = RouteInfo(url_path, handler, enabled=enabled)
|
||||||
self.routes[key] = RouteInfo(
|
_LOGGER.debug("Registered dispatcher for route %s", url_path)
|
||||||
url_path, route=route, handler=handler, enabled=enabled
|
|
||||||
)
|
|
||||||
_LOGGER.debug("Registered dispatcher for route (%s):%s", route.method, url_path)
|
|
||||||
|
|
||||||
def show_enabled(self) -> str:
|
def show_enabled(self) -> str:
|
||||||
"""Return a human-readable description of the currently enabled route."""
|
"""Return a human-readable description of the currently enabled route."""
|
||||||
|
for url, route in self.routes.items():
|
||||||
enabled_routes = {
|
if route.enabled:
|
||||||
f"Dispatcher enabled for ({route.route.method}):{route.url_path}, with handler: {route.handler}"
|
return (
|
||||||
for route in self.routes.values()
|
f"Dispatcher enabled for URL: {url}, with handler: {route.handler}"
|
||||||
if route.enabled
|
)
|
||||||
}
|
return "No routes is enabled."
|
||||||
return ", ".join(
|
|
||||||
sorted(enabled_routes) if enabled_routes else "No routes are enabled."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def unregistered(request: Request) -> Response:
|
async def unregistered(request: Request) -> Response:
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,15 @@ class WeatherSensor( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
|
|
||||||
description = cast("WeatherSensorEntityDescription", self.entity_description)
|
description = cast("WeatherSensorEntityDescription", self.entity_description)
|
||||||
|
|
||||||
|
if self._dev_log:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"native_value start: key=%s, has_value_from_data_fn=%s, has_value_fn=%s, data_keys=%s",
|
||||||
|
key,
|
||||||
|
description.value_from_data_fn is not None,
|
||||||
|
description.value_fn is not None,
|
||||||
|
sorted(data),
|
||||||
|
)
|
||||||
|
|
||||||
if description.value_from_data_fn is not None:
|
if description.value_from_data_fn is not None:
|
||||||
try:
|
try:
|
||||||
value = description.value_from_data_fn(data)
|
value = description.value_from_data_fn(data)
|
||||||
|
|
@ -241,7 +250,12 @@ class WeatherSensor( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
"native_value compute failed via value_from_data_fn for key=%s", key
|
"native_value compute failed via value_from_data_fn for key=%s", key
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
if self._dev_log:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"native_value computed via value_from_data_fn: key=%s -> %s",
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
raw = data.get(key)
|
raw = data.get(key)
|
||||||
|
|
@ -263,6 +277,14 @@ class WeatherSensor( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if self._dev_log:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"native_value computed via value_fn: key=%s raw=%s -> %s",
|
||||||
|
key,
|
||||||
|
raw,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -20,24 +20,8 @@ from .const import (
|
||||||
CH2_BATTERY,
|
CH2_BATTERY,
|
||||||
CH2_HUMIDITY,
|
CH2_HUMIDITY,
|
||||||
CH2_TEMP,
|
CH2_TEMP,
|
||||||
CH3_BATTERY,
|
|
||||||
CH3_HUMIDITY,
|
CH3_HUMIDITY,
|
||||||
CH3_TEMP,
|
CH3_TEMP,
|
||||||
CH4_BATTERY,
|
|
||||||
CH4_HUMIDITY,
|
|
||||||
CH4_TEMP,
|
|
||||||
CH5_BATTERY,
|
|
||||||
CH5_HUMIDITY,
|
|
||||||
CH5_TEMP,
|
|
||||||
CH6_BATTERY,
|
|
||||||
CH6_HUMIDITY,
|
|
||||||
CH6_TEMP,
|
|
||||||
CH7_BATTERY,
|
|
||||||
CH7_HUMIDITY,
|
|
||||||
CH7_TEMP,
|
|
||||||
CH8_BATTERY,
|
|
||||||
CH8_HUMIDITY,
|
|
||||||
CH8_TEMP,
|
|
||||||
CHILL_INDEX,
|
CHILL_INDEX,
|
||||||
DAILY_RAIN,
|
DAILY_RAIN,
|
||||||
DEW_POINT,
|
DEW_POINT,
|
||||||
|
|
@ -285,149 +269,6 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
translation_key=CH3_HUMIDITY,
|
translation_key=CH3_HUMIDITY,
|
||||||
value_fn=lambda data: cast("int", data),
|
value_fn=lambda data: cast("int", data),
|
||||||
),
|
),
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH4_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH4_TEMP,
|
|
||||||
value_fn=lambda data: cast("float", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH4_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH4_HUMIDITY,
|
|
||||||
value_fn=lambda data: cast("int", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH5_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH5_TEMP,
|
|
||||||
value_fn=lambda data: cast("float", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH5_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH5_HUMIDITY,
|
|
||||||
value_fn=lambda data: cast("int", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH6_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH6_TEMP,
|
|
||||||
value_fn=lambda data: cast("float", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH6_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH6_HUMIDITY,
|
|
||||||
value_fn=lambda data: cast("int", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH7_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH7_TEMP,
|
|
||||||
value_fn=lambda data: cast("float", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH7_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH7_HUMIDITY,
|
|
||||||
value_fn=lambda data: cast("int", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH8_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH8_TEMP,
|
|
||||||
value_fn=lambda data: cast("float", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH8_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=CH8_HUMIDITY,
|
|
||||||
value_fn=lambda data: cast("int", data),
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=HEAT_INDEX,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH3_BATTERY,
|
|
||||||
translation_key=CH3_BATTERY,
|
|
||||||
icon="mdi:battery-unknown",
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH4_BATTERY,
|
|
||||||
translation_key=CH4_BATTERY,
|
|
||||||
icon="mdi:battery-unknown",
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH5_BATTERY,
|
|
||||||
translation_key=CH5_BATTERY,
|
|
||||||
icon="mdi:battery-unknown",
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH6_BATTERY,
|
|
||||||
translation_key=CH6_BATTERY,
|
|
||||||
icon="mdi:battery-unknown",
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH7_BATTERY,
|
|
||||||
translation_key=CH7_BATTERY,
|
|
||||||
icon="mdi:battery-unknown",
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=CH8_BATTERY,
|
|
||||||
translation_key=CH8_BATTERY,
|
|
||||||
icon="mdi:battery-unknown",
|
|
||||||
device_class=SensorDeviceClass.ENUM,
|
|
||||||
value_fn=lambda data: data,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
key=HEAT_INDEX,
|
key=HEAT_INDEX,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@
|
||||||
"valid_credentials_api": "Vyplňte platné API ID",
|
"valid_credentials_api": "Vyplňte platné API ID",
|
||||||
"valid_credentials_key": "Vyplňte platný API KEY",
|
"valid_credentials_key": "Vyplňte platný API KEY",
|
||||||
"valid_credentials_match": "API ID a API KEY nesmějí být stejné!",
|
"valid_credentials_match": "API ID a API KEY nesmějí být stejné!",
|
||||||
"windy_id_required": "Je vyžadováno Windy ID, pokud chcete aktivovat přeposílání dat na Windy",
|
"windy_key_required": "Je vyžadován Windy API key, pokud chcete aktivovat přeposílání dat na Windy",
|
||||||
"windy_pw_required": "Je vyžadován Windy KEY, pokud chcete aktivovat přeposílání dat na Windy",
|
|
||||||
"pocasi_id_required": "Je vyžadován Počasí ID, pokud chcete aktivovat přeposílání dat na Počasí Meteo CZ",
|
"pocasi_id_required": "Je vyžadován Počasí ID, pokud chcete aktivovat přeposílání dat na Počasí Meteo CZ",
|
||||||
"pocasi_key_required": "Klíč k účtu Počasí Meteo je povinný.",
|
"pocasi_key_required": "Klíč k účtu Počasí Meteo je povinný.",
|
||||||
"pocasi_send_minimum": "Minimální interval pro přeposílání je 12 sekund."
|
"pocasi_send_minimum": "Minimální interval pro přeposílání je 12 sekund."
|
||||||
|
|
@ -74,7 +73,7 @@
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"WINDY_STATION_ID": "ID stanice získaný z https://stations.windy.com/station",
|
"WINDY_STATION_ID": "ID stanice získaný z https://stations.windy.com/station",
|
||||||
"WINDY_STATION_PWD": "Heslo stanice získané z https://stations.windy.com/station",
|
"WINDY_STATION_PWD": "Heslo stanice získané z https://stations.windy.com/station",
|
||||||
"windy_logger_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři."
|
"windy_logger_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@
|
||||||
"valid_credentials_api": "Provide valid API ID.",
|
"valid_credentials_api": "Provide valid API ID.",
|
||||||
"valid_credentials_key": "Provide valid API KEY.",
|
"valid_credentials_key": "Provide valid API KEY.",
|
||||||
"valid_credentials_match": "API ID and API KEY should not be the same.",
|
"valid_credentials_match": "API ID and API KEY should not be the same.",
|
||||||
"windy_id_required": "Windy API ID is required if you want to enable this function.",
|
"windy_key_required": "Windy API key is required if you want to enable this function."
|
||||||
"windy_pw_required": "Windy API password is required if you want to enable this function."
|
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
|
|
@ -175,30 +174,6 @@
|
||||||
"ch4_humidity": {
|
"ch4_humidity": {
|
||||||
"name": "Channel 4 humidity"
|
"name": "Channel 4 humidity"
|
||||||
},
|
},
|
||||||
"ch5_temp": {
|
|
||||||
"name": "Channel 5 temperature"
|
|
||||||
},
|
|
||||||
"ch5_humidity": {
|
|
||||||
"name": "Channel 5 humidity"
|
|
||||||
},
|
|
||||||
"ch6_temp": {
|
|
||||||
"name": "Channel 6 temperature"
|
|
||||||
},
|
|
||||||
"ch6_humidity": {
|
|
||||||
"name": "Channel 6 humidity"
|
|
||||||
},
|
|
||||||
"ch7_temp": {
|
|
||||||
"name": "Channel 7 temperature"
|
|
||||||
},
|
|
||||||
"ch7_humidity": {
|
|
||||||
"name": "Channel 7 humidity"
|
|
||||||
},
|
|
||||||
"ch8_temp": {
|
|
||||||
"name": "Channel 8 temperature"
|
|
||||||
},
|
|
||||||
"ch8_humidity": {
|
|
||||||
"name": "Channel 8 humidity"
|
|
||||||
},
|
|
||||||
"heat_index": {
|
"heat_index": {
|
||||||
"name": "Apparent temperature"
|
"name": "Apparent temperature"
|
||||||
},
|
},
|
||||||
|
|
@ -257,54 +232,6 @@
|
||||||
"unknown": "Unknown / drained out"
|
"unknown": "Unknown / drained out"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ch3_battery": {
|
|
||||||
"name": "Channel 3 battery level",
|
|
||||||
"state": {
|
|
||||||
"normal": "OK",
|
|
||||||
"low": "Low",
|
|
||||||
"unknown": "Unknown / drained out"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ch4_battery": {
|
|
||||||
"name": "Channel 4 battery level",
|
|
||||||
"state": {
|
|
||||||
"normal": "OK",
|
|
||||||
"low": "Low",
|
|
||||||
"unknown": "Unknown / drained out"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ch5_battery": {
|
|
||||||
"name": "Channel 5 battery level",
|
|
||||||
"state": {
|
|
||||||
"normal": "OK",
|
|
||||||
"low": "Low",
|
|
||||||
"unknown": "Unknown / drained out"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ch6_battery": {
|
|
||||||
"name": "Channel 6 battery level",
|
|
||||||
"state": {
|
|
||||||
"normal": "OK",
|
|
||||||
"low": "Low",
|
|
||||||
"unknown": "Unknown / drained out"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ch7_battery": {
|
|
||||||
"name": "Channel 7 battery level",
|
|
||||||
"state": {
|
|
||||||
"normal": "OK",
|
|
||||||
"low": "Low",
|
|
||||||
"unknown": "Unknown / drained out"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ch8_battery": {
|
|
||||||
"name": "Channel 8 battery level",
|
|
||||||
"state": {
|
|
||||||
"normal": "OK",
|
|
||||||
"low": "Low",
|
|
||||||
"unknown": "Unknown / drained out"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indoor_battery": {
|
"indoor_battery": {
|
||||||
"name": "Console battery level",
|
"name": "Console battery level",
|
||||||
"state": {
|
"state": {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiohttp.client import ClientResponse
|
|
||||||
from aiohttp.client_exceptions import ClientError
|
from aiohttp.client_exceptions import ClientError
|
||||||
from py_typecheck import checked
|
from py_typecheck import checked
|
||||||
|
|
||||||
from homeassistant.components import persistent_notification
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
@ -17,7 +15,6 @@ from .const import (
|
||||||
WINDY_ENABLED,
|
WINDY_ENABLED,
|
||||||
WINDY_INVALID_KEY,
|
WINDY_INVALID_KEY,
|
||||||
WINDY_LOGGER_ENABLED,
|
WINDY_LOGGER_ENABLED,
|
||||||
WINDY_MAX_RETRIES,
|
|
||||||
WINDY_NOT_INSERTED,
|
WINDY_NOT_INSERTED,
|
||||||
WINDY_STATION_ID,
|
WINDY_STATION_ID,
|
||||||
WINDY_STATION_PW,
|
WINDY_STATION_PW,
|
||||||
|
|
@ -31,36 +28,15 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WindyNotInserted(Exception):
|
class WindyNotInserted(Exception):
|
||||||
"""NotInserted state.
|
"""NotInserted state."""
|
||||||
|
|
||||||
Possible variants are:
|
|
||||||
- station password is invalid
|
|
||||||
- station password does not match the station
|
|
||||||
- payload failed validation
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class WindySuccess(Exception):
|
class WindySuccess(Exception):
|
||||||
"""WindySucces state."""
|
"""WindySucces state."""
|
||||||
|
|
||||||
|
|
||||||
class WindyPasswordMissing(Exception):
|
class WindyApiKeyError(Exception):
|
||||||
"""Windy password is missing in query or Authorization header.
|
"""Windy API Key error."""
|
||||||
|
|
||||||
This should not happend, while we are checking if we have password set and do exits early.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class WindyDuplicatePayloadDetected(Exception):
|
|
||||||
"""Duplicate payload detected."""
|
|
||||||
|
|
||||||
|
|
||||||
class WindyRateLimitExceeded(Exception):
|
|
||||||
"""Rate limit exceeded. Minimum interval is 5 minutes.
|
|
||||||
|
|
||||||
This should not happend in runnig integration.
|
|
||||||
Might be seen, if restart of HomeAssistant occured and we are not aware of previous update.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def timed(minutes: int):
|
def timed(minutes: int):
|
||||||
|
|
@ -86,34 +62,28 @@ class WindyPush:
|
||||||
self.next_update: datetime = datetime.now() + timed(minutes=1)
|
self.next_update: datetime = datetime.now() + timed(minutes=1)
|
||||||
|
|
||||||
self.log: bool = self.config.options.get(WINDY_LOGGER_ENABLED, False)
|
self.log: bool = self.config.options.get(WINDY_LOGGER_ENABLED, False)
|
||||||
|
|
||||||
# Lets chcek if Windy server is responding right.
|
|
||||||
# Otherwise, try 3 times and then disable resending.
|
|
||||||
self.invalid_response_count: int = 0
|
self.invalid_response_count: int = 0
|
||||||
|
|
||||||
# Refactored responses verification.
|
def verify_windy_response(
|
||||||
#
|
self,
|
||||||
# We now comply to API at https://stations.windy.com/api-reference
|
response: str,
|
||||||
def verify_windy_response(self, response: ClientResponse):
|
):
|
||||||
"""Verify answer form Windy."""
|
"""Verify answer form Windy."""
|
||||||
|
|
||||||
if self.log and response:
|
if self.log:
|
||||||
_LOGGER.info("Windy raw response: %s", response.text)
|
_LOGGER.info("Windy response raw response: %s", response)
|
||||||
|
|
||||||
if response.status == 200:
|
if "NOTICE" in response:
|
||||||
raise WindySuccess
|
|
||||||
|
|
||||||
if response.status == 400:
|
|
||||||
raise WindyNotInserted
|
raise WindyNotInserted
|
||||||
|
|
||||||
if response.status == 401:
|
if "SUCCESS" in response:
|
||||||
raise WindyPasswordMissing
|
raise WindySuccess
|
||||||
|
|
||||||
if response.status == 409:
|
if "Invalid API key" in response:
|
||||||
raise WindyDuplicatePayloadDetected
|
raise WindyApiKeyError
|
||||||
|
|
||||||
if response.status == 429:
|
if "Unauthorized" in response:
|
||||||
raise WindyRateLimitExceeded
|
raise WindyApiKeyError
|
||||||
|
|
||||||
def _covert_wslink_to_pws(self, indata: dict[str, str]) -> dict[str, str]:
|
def _covert_wslink_to_pws(self, indata: dict[str, str]) -> dict[str, str]:
|
||||||
"""Convert WSLink API data to Windy API data protocol."""
|
"""Convert WSLink API data to Windy API data protocol."""
|
||||||
|
|
@ -140,14 +110,6 @@ class WindyPush:
|
||||||
|
|
||||||
return indata
|
return indata
|
||||||
|
|
||||||
async def _disable_windy(self, reason: str) -> None:
|
|
||||||
"""Disable Windy resending."""
|
|
||||||
|
|
||||||
if not await update_options(self.hass, self.config, WINDY_ENABLED, False):
|
|
||||||
_LOGGER.debug("Failed to set Windy options to false.")
|
|
||||||
|
|
||||||
persistent_notification.create(self.hass, reason, "Windy resending disabled.")
|
|
||||||
|
|
||||||
async def push_data_to_windy(
|
async def push_data_to_windy(
|
||||||
self, data: dict[str, str], wslink: bool = False
|
self, data: dict[str, str], wslink: bool = False
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
@ -159,27 +121,6 @@ class WindyPush:
|
||||||
from station. But we need to do some clean up.
|
from station. But we need to do some clean up.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# First check if we have valid credentials, before any data manipulation.
|
|
||||||
if (
|
|
||||||
windy_station_id := checked(self.config.options.get(WINDY_STATION_ID), str)
|
|
||||||
) is None:
|
|
||||||
_LOGGER.error("Windy API key is not provided! Check your configuration.")
|
|
||||||
await self._disable_windy(
|
|
||||||
"Windy API key is not provided. Resending is disabled for now. Reconfigure your integration."
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if (
|
|
||||||
windy_station_pw := checked(self.config.options.get(WINDY_STATION_PW), str)
|
|
||||||
) is None:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Windy station password is missing! Check your configuration."
|
|
||||||
)
|
|
||||||
await self._disable_windy(
|
|
||||||
"Windy password is not provided. Resending is disabled for now. Reconfigure your integration."
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.log:
|
if self.log:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Windy last update = %s, next update at: %s",
|
"Windy last update = %s, next update at: %s",
|
||||||
|
|
@ -198,7 +139,21 @@ class WindyPush:
|
||||||
|
|
||||||
if wslink:
|
if wslink:
|
||||||
# WSLink -> Windy params
|
# WSLink -> Windy params
|
||||||
purged_data = self._covert_wslink_to_pws(purged_data)
|
self._covert_wslink_to_pws(purged_data)
|
||||||
|
|
||||||
|
if (
|
||||||
|
windy_station_id := checked(self.config.options.get(WINDY_STATION_ID), str)
|
||||||
|
) is None:
|
||||||
|
_LOGGER.error("Windy API key is not provided! Check your configuration.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
windy_station_pw := checked(self.config.options.get(WINDY_STATION_PW), str)
|
||||||
|
) is None:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Windy station password is missing! Check your configuration."
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
request_url = f"{WINDY_URL}"
|
request_url = f"{WINDY_URL}"
|
||||||
|
|
||||||
|
|
@ -215,25 +170,23 @@ class WindyPush:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
request_url, params=purged_data, headers=headers
|
request_url, params=purged_data, headers=headers
|
||||||
) as resp:
|
) as resp:
|
||||||
|
status = await resp.text()
|
||||||
try:
|
try:
|
||||||
self.verify_windy_response(response=resp)
|
self.verify_windy_response(status)
|
||||||
except WindyNotInserted:
|
except WindyNotInserted:
|
||||||
# log despite of settings
|
# log despite of settings
|
||||||
_LOGGER.error(WINDY_NOT_INSERTED)
|
_LOGGER.error(WINDY_NOT_INSERTED)
|
||||||
self.invalid_response_count += 1
|
|
||||||
|
|
||||||
except WindyPasswordMissing:
|
except WindyApiKeyError:
|
||||||
# log despite of settings
|
# log despite of settings
|
||||||
_LOGGER.critical(WINDY_INVALID_KEY)
|
_LOGGER.critical(WINDY_INVALID_KEY)
|
||||||
await self._disable_windy(
|
|
||||||
reason="Windy password is missing in payload or Authorization header. Resending is disabled for now. Reconfigure your Windy settings."
|
if not (
|
||||||
)
|
await update_options(
|
||||||
except WindyDuplicatePayloadDetected:
|
self.hass, self.config, WINDY_ENABLED, False
|
||||||
_LOGGER.critical(
|
)
|
||||||
"Duplicate payload detected by Windy server. Will try again later. Max retries before disabling resend function: %s",
|
):
|
||||||
(WINDY_MAX_RETRIES - self.invalid_response_count),
|
_LOGGER.debug("Failed to set Windy option to false.")
|
||||||
)
|
|
||||||
self.invalid_response_count += 1
|
|
||||||
|
|
||||||
except WindySuccess:
|
except WindySuccess:
|
||||||
if self.log:
|
if self.log:
|
||||||
|
|
@ -243,17 +196,15 @@ class WindyPush:
|
||||||
_LOGGER.debug(WINDY_NOT_INSERTED)
|
_LOGGER.debug(WINDY_NOT_INSERTED)
|
||||||
|
|
||||||
except ClientError as ex:
|
except ClientError as ex:
|
||||||
_LOGGER.critical(
|
_LOGGER.critical("Invalid response from Windy: %s", str(ex))
|
||||||
"Invalid response from Windy: %s. Will try again later, max retries before disabling resend function: %s",
|
|
||||||
str(ex),
|
|
||||||
(WINDY_MAX_RETRIES - self.invalid_response_count),
|
|
||||||
)
|
|
||||||
self.invalid_response_count += 1
|
self.invalid_response_count += 1
|
||||||
if self.invalid_response_count >= WINDY_MAX_RETRIES:
|
if self.invalid_response_count > 3:
|
||||||
_LOGGER.critical(WINDY_UNEXPECTED)
|
_LOGGER.critical(WINDY_UNEXPECTED)
|
||||||
await self._disable_windy(
|
if not await update_options(
|
||||||
reason="Invalid response from Windy 3 times. Disabling resending option."
|
self.hass, self.config, WINDY_ENABLED, False
|
||||||
)
|
):
|
||||||
|
_LOGGER.debug("Failed to set Windy options to false.")
|
||||||
|
|
||||||
self.last_update = datetime.now()
|
self.last_update = datetime.now()
|
||||||
self.next_update = self.last_update + timed(minutes=5)
|
self.next_update = self.last_update + timed(minutes=5)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue