Compare commits
64 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
94ec3cb0e5 | |
|
|
743e62c6dd | |
|
|
6edaec73d8 | |
|
|
44d0ee5c7b | |
|
|
e482fcea2b | |
|
|
826a9a71cc | |
|
|
08de8b5570 | |
|
|
0679f1e559 | |
|
|
0c42c8d827 | |
|
|
de346ed914 | |
|
|
7950e1be46 | |
|
|
0d47e14834 | |
|
|
92eadbb4e2 | |
|
|
040f70c027 | |
|
|
67d8acf9cc | |
|
|
006376fc49 | |
|
|
a43d8202dd | |
|
|
4ccc37951d | |
|
|
688bb9e374 | |
|
|
b3032d072f | |
|
|
a2bc74c2ad | |
|
|
59116a6c48 | |
|
|
06a8a7ff1b | |
|
|
1d8928bf12 | |
|
|
397005bd3f | |
|
|
80909e88c0 | |
|
|
5022cb7767 | |
|
|
f7cea43722 | |
|
|
7ff8bb7f92 | |
|
|
dbebc501e3 | |
|
|
8247f2b854 | |
|
|
d48f0fda6e | |
|
|
99fd6d266c | |
|
|
64dd47a3e9 | |
|
|
720c2148e6 | |
|
|
b858f648b9 | |
|
|
07ca4a6833 | |
|
|
de013891c0 | |
|
|
0d0922a494 | |
|
|
af19358ac7 | |
|
|
3dbf8b8a7a | |
|
|
a68a4c929a | |
|
|
af286648e9 | |
|
|
b6080fe9fd | |
|
|
a07af5a4fd | |
|
|
f14e6500d4 | |
|
|
a1f2bf10ea | |
|
|
e10ea9901c | |
|
|
fc8349c06e | |
|
|
d4d2440ae8 | |
|
|
827fb71e25 | |
|
|
2d758835dc | |
|
|
0027a80968 | |
|
|
e11e068c0f | |
|
|
1ecd88269d | |
|
|
09d79e2032 | |
|
|
bbe31da4c5 | |
|
|
68da7aad98 | |
|
|
de8d2a7b0c | |
|
|
cf0938a6fd | |
|
|
4d2dedbb11 | |
|
|
feed730818 | |
|
|
b1cec2f38f | |
|
|
6eceee1f4e |
|
|
@ -0,0 +1,21 @@
|
||||||
|
name: Build & Attach ZIP asset
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-zip:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create ZIP
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
cd custom_components/sws12500
|
||||||
|
zip -r ../../dist/weather-station.zip . -x "*/__pycache__/*"
|
||||||
|
|
||||||
|
- name: Upload ZIP to release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: dist/weather-station.zip
|
||||||
13
README.md
13
README.md
|
|
@ -1,3 +1,6 @@
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
# Integrates your Sencor SWS 12500 or 16600, GARNI, BRESSER weather stations seamlessly into Home Assistant
|
# Integrates your Sencor SWS 12500 or 16600, GARNI, BRESSER weather stations seamlessly into Home Assistant
|
||||||
|
|
||||||
This integration will listen for data from your station and passes them to respective sensors. It also provides the ability to push data to Windy API.
|
This integration will listen for data from your station and passes them to respective sensors. It also provides the ability to push data to Windy API.
|
||||||
|
|
@ -99,6 +102,16 @@ As soon as the integration is added into Home Assistant it will listen for incom
|
||||||
|
|
||||||
- You are done.
|
- You are done.
|
||||||
|
|
||||||
|
## Resending data to Pocasi Meteo
|
||||||
|
- If you are willing to use [Pocasi Meteo Application](https://pocasimeteo.cz) you can enable resending your data to their servers
|
||||||
|
- You must have account at Pocasi Meteo, where you will recieve `ID` and `KEY`, which are needed to connect to server
|
||||||
|
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
|
||||||
|
- In dialog box choose `Pocasi Meteo configuration`.
|
||||||
|
- Fill in `ID` and `KEY` you were provided at `Pocasi Meteo`.
|
||||||
|
- Tick `Enable` checkbox.
|
||||||
|
|
||||||
|
- You are done.
|
||||||
|
|
||||||
## WSLink notes
|
## WSLink notes
|
||||||
|
|
||||||
While your station is using WSLink you have to have Home Assistant in SSL mode or behind SSL proxy server.
|
While your station is using WSLink you have to have Home Assistant in SSL mode or behind SSL proxy server.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp.web
|
||||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
from aiohttp.web_exceptions import HTTPUnauthorized
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
|
@ -17,11 +17,13 @@ from .const import (
|
||||||
DEFAULT_URL,
|
DEFAULT_URL,
|
||||||
DEV_DBG,
|
DEV_DBG,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
POCASI_CZ_ENABLED,
|
||||||
SENSORS_TO_LOAD,
|
SENSORS_TO_LOAD,
|
||||||
WINDY_ENABLED,
|
WINDY_ENABLED,
|
||||||
WSLINK,
|
WSLINK,
|
||||||
WSLINK_URL,
|
WSLINK_URL,
|
||||||
)
|
)
|
||||||
|
from .pocasti_cz import PocasiPush
|
||||||
from .routes import Routes, unregistred
|
from .routes import Routes, unregistred
|
||||||
from .utils import (
|
from .utils import (
|
||||||
anonymize,
|
anonymize,
|
||||||
|
|
@ -51,6 +53,7 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.config = config
|
self.config = config
|
||||||
self.windy = WindyPush(hass, config)
|
self.windy = WindyPush(hass, config)
|
||||||
|
self.pocasi: PocasiPush = PocasiPush(hass, config)
|
||||||
super().__init__(hass, _LOGGER, name=DOMAIN)
|
super().__init__(hass, _LOGGER, name=DOMAIN)
|
||||||
|
|
||||||
async def recieved_data(self, webdata):
|
async def recieved_data(self, webdata):
|
||||||
|
|
@ -85,6 +88,9 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
if self.config_entry.options.get(WINDY_ENABLED):
|
if self.config_entry.options.get(WINDY_ENABLED):
|
||||||
response = await self.windy.push_data_to_windy(data)
|
response = await self.windy.push_data_to_windy(data)
|
||||||
|
|
||||||
|
if self.config.options.get(POCASI_CZ_ENABLED):
|
||||||
|
await self.pocasi.push_data_to_server(data, "WSLINK" if _wslink else "WU")
|
||||||
|
|
||||||
remaped_items = (
|
remaped_items = (
|
||||||
remap_wslink_items(data)
|
remap_wslink_items(data)
|
||||||
if self.config_entry.options.get(WSLINK)
|
if self.config_entry.options.get(WSLINK)
|
||||||
|
|
@ -134,11 +140,11 @@ def register_path(
|
||||||
|
|
||||||
hass_data = hass.data.setdefault(DOMAIN, {})
|
hass_data = hass.data.setdefault(DOMAIN, {})
|
||||||
debug = config.options.get(DEV_DBG)
|
debug = config.options.get(DEV_DBG)
|
||||||
_wslink = config.options.get(WSLINK)
|
_wslink = config.options.get(WSLINK, False)
|
||||||
|
|
||||||
routes: Routes = hass_data.get("routes") if "routes" in hass_data else None
|
routes: Routes = hass_data.get("routes", Routes())
|
||||||
|
|
||||||
if routes is None:
|
if not routes.routes:
|
||||||
routes = Routes()
|
routes = Routes()
|
||||||
_LOGGER.info("Routes not found, creating new routes")
|
_LOGGER.info("Routes not found, creating new routes")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
"""Config flow for Sencor SWS 12500 Weather Station integration."""
|
"""Config flow for Sencor SWS 12500 Weather Station integration."""
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||||
from homeassistant.const import UnitOfPrecipitationDepth, UnitOfVolumetricFlux
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.helpers.entity_registry as er
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
API_ID,
|
API_ID,
|
||||||
|
|
@ -17,18 +14,18 @@ from .const import (
|
||||||
DEV_DBG,
|
DEV_DBG,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
INVALID_CREDENTIALS,
|
INVALID_CREDENTIALS,
|
||||||
MIG_FROM,
|
POCASI_CZ_API_ID,
|
||||||
MIG_TO,
|
POCASI_CZ_API_KEY,
|
||||||
SENSOR_TO_MIGRATE,
|
POCASI_CZ_ENABLED,
|
||||||
|
POCASI_CZ_LOGGER_ENABLED,
|
||||||
|
POCASI_CZ_SEND_INTERVAL,
|
||||||
|
POCASI_CZ_SEND_MINIMUM,
|
||||||
SENSORS_TO_LOAD,
|
SENSORS_TO_LOAD,
|
||||||
WINDY_API_KEY,
|
WINDY_API_KEY,
|
||||||
WINDY_ENABLED,
|
WINDY_ENABLED,
|
||||||
WINDY_LOGGER_ENABLED,
|
WINDY_LOGGER_ENABLED,
|
||||||
WSLINK,
|
WSLINK,
|
||||||
)
|
)
|
||||||
from .utils import long_term_units_in_statistics_meta, migrate_data
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class CannotConnect(HomeAssistantError):
|
class CannotConnect(HomeAssistantError):
|
||||||
|
|
@ -52,13 +49,8 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
self.user_data_schema = {}
|
self.user_data_schema = {}
|
||||||
self.sensors: dict[str, Any] = {}
|
self.sensors: dict[str, Any] = {}
|
||||||
self.migrate_schema = {}
|
self.migrate_schema = {}
|
||||||
self.migrate_sensor_select = {}
|
self.pocasi_cz: dict[str, Any] = {}
|
||||||
self.migrate_unit_selection = {}
|
self.pocasi_cz_schema = {}
|
||||||
self.count = 0
|
|
||||||
self.selected_sensor = ""
|
|
||||||
|
|
||||||
self.unit_values = [unit.value for unit in UnitOfVolumetricFlux]
|
|
||||||
self.unit_values.extend([unit.value for unit in UnitOfPrecipitationDepth])
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def config_entry(self):
|
def config_entry(self):
|
||||||
|
|
@ -67,7 +59,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
async def _get_entry_data(self):
|
async def _get_entry_data(self):
|
||||||
"""Get entry data."""
|
"""Get entry data."""
|
||||||
|
|
||||||
self.user_data: dict[str, Any] = {
|
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),
|
||||||
|
|
@ -81,13 +73,15 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
vol.Optional(DEV_DBG, default=self.user_data.get(DEV_DBG, False)): bool,
|
vol.Optional(DEV_DBG, default=self.user_data.get(DEV_DBG, False)): bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sensors: dict[str, Any] = {
|
self.sensors = {
|
||||||
SENSORS_TO_LOAD: self.config_entry.options.get(SENSORS_TO_LOAD)
|
SENSORS_TO_LOAD: (
|
||||||
|
self.config_entry.options.get(SENSORS_TO_LOAD)
|
||||||
if isinstance(self.config_entry.options.get(SENSORS_TO_LOAD), list)
|
if isinstance(self.config_entry.options.get(SENSORS_TO_LOAD), list)
|
||||||
else []
|
else []
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.windy_data: dict[str, Any] = {
|
self.windy_data = {
|
||||||
WINDY_API_KEY: self.config_entry.options.get(WINDY_API_KEY),
|
WINDY_API_KEY: self.config_entry.options.get(WINDY_API_KEY),
|
||||||
WINDY_ENABLED: self.config_entry.options.get(WINDY_ENABLED, False),
|
WINDY_ENABLED: self.config_entry.options.get(WINDY_ENABLED, False),
|
||||||
WINDY_LOGGER_ENABLED: self.config_entry.options.get(
|
WINDY_LOGGER_ENABLED: self.config_entry.options.get(
|
||||||
|
|
@ -107,39 +101,42 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
): bool or False,
|
): bool or False,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.migrate_sensor_select = {
|
self.pocasi_cz = {
|
||||||
vol.Required(SENSOR_TO_MIGRATE): vol.In(
|
POCASI_CZ_API_ID: self.config_entry.options.get(POCASI_CZ_API_ID, ""),
|
||||||
await self.load_sensors_to_migrate() or {}
|
POCASI_CZ_API_KEY: self.config_entry.options.get(POCASI_CZ_API_KEY, ""),
|
||||||
|
POCASI_CZ_ENABLED: self.config_entry.options.get(POCASI_CZ_ENABLED, False),
|
||||||
|
POCASI_CZ_LOGGER_ENABLED: self.config_entry.options.get(
|
||||||
|
POCASI_CZ_LOGGER_ENABLED, False
|
||||||
|
),
|
||||||
|
POCASI_CZ_SEND_INTERVAL: self.config_entry.options.get(
|
||||||
|
POCASI_CZ_SEND_INTERVAL, 30
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.migrate_unit_selection = {
|
self.pocasi_cz_schema = {
|
||||||
vol.Required(MIG_FROM): vol.In(self.unit_values),
|
vol.Required(
|
||||||
vol.Required(MIG_TO): vol.In(self.unit_values),
|
POCASI_CZ_API_ID, default=self.pocasi_cz.get(POCASI_CZ_API_ID)
|
||||||
vol.Optional("trigger_action", default=False): bool,
|
): str,
|
||||||
}
|
vol.Required(
|
||||||
# "mm/d", "mm/h", "mm", "in/d", "in/h", "in"
|
POCASI_CZ_API_KEY, default=self.pocasi_cz.get(POCASI_CZ_API_KEY)
|
||||||
|
): str,
|
||||||
async def load_sensors_to_migrate(self) -> dict[str, Any]:
|
vol.Required(
|
||||||
"""Load sensors to migrate."""
|
POCASI_CZ_SEND_INTERVAL,
|
||||||
|
default=self.pocasi_cz.get(POCASI_CZ_SEND_INTERVAL),
|
||||||
sensor_statistics = await long_term_units_in_statistics_meta(self.hass)
|
): int,
|
||||||
|
vol.Optional(
|
||||||
entity_registry = er.async_get(self.hass)
|
POCASI_CZ_ENABLED, default=self.pocasi_cz.get(POCASI_CZ_ENABLED)
|
||||||
sensors = entity_registry.entities.get_entries_for_config_entry_id(
|
): bool,
|
||||||
self.config_entry.entry_id
|
vol.Optional(
|
||||||
)
|
POCASI_CZ_LOGGER_ENABLED,
|
||||||
|
default=self.pocasi_cz.get(POCASI_CZ_LOGGER_ENABLED),
|
||||||
return {
|
): bool,
|
||||||
sensor.entity_id: f"{sensor.name or sensor.original_name} (current settings: {sensor.unit_of_measurement}, longterm stats unit: {sensor_statistics.get(sensor.entity_id)})"
|
|
||||||
for sensor in sensors
|
|
||||||
if sensor.unique_id in {"rain", "daily_rain"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(self, user_input=None):
|
||||||
"""Manage the options - show menu first."""
|
"""Manage the options - show menu first."""
|
||||||
return self.async_show_menu(
|
return self.async_show_menu(
|
||||||
step_id="init", menu_options=["basic", "windy", "migration"]
|
step_id="init", menu_options=["basic", "windy", "pocasi"]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_basic(self, user_input=None):
|
async def async_step_basic(self, user_input=None):
|
||||||
|
|
@ -168,6 +165,9 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
# retain sensors
|
# retain sensors
|
||||||
user_input.update(self.sensors)
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
|
# retain pocasi data
|
||||||
|
user_input.update(self.pocasi_cz)
|
||||||
|
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
self.user_data = user_input
|
self.user_data = user_input
|
||||||
|
|
@ -206,155 +206,54 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
# retain senors
|
# retain senors
|
||||||
user_input.update(self.sensors)
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
|
# retain pocasi cz
|
||||||
|
|
||||||
|
user_input.update(self.pocasi_cz)
|
||||||
|
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
async def async_step_migration(self, user_input=None):
|
async def async_step_pocasi(self, user_input: Any = None) -> ConfigFlowResult:
|
||||||
"""Migrate sensors."""
|
"""Handle the pocasi step."""
|
||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
data_schema = vol.Schema(self.migrate_sensor_select)
|
|
||||||
data_schema.schema.update()
|
|
||||||
|
|
||||||
await self._get_entry_data()
|
await self._get_entry_data()
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="migration",
|
step_id="pocasi",
|
||||||
data_schema=vol.Schema(self.migrate_sensor_select),
|
data_schema=vol.Schema(self.pocasi_cz_schema),
|
||||||
errors=errors,
|
errors=errors,
|
||||||
description_placeholders={
|
|
||||||
"migration_status": "-",
|
|
||||||
"migration_count": "-",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.selected_sensor = user_input.get(SENSOR_TO_MIGRATE)
|
if user_input.get(POCASI_CZ_SEND_INTERVAL, 0) < POCASI_CZ_SEND_MINIMUM:
|
||||||
|
errors[POCASI_CZ_SEND_INTERVAL] = "pocasi_send_minimum"
|
||||||
|
|
||||||
return await self.async_step_migration_units()
|
if user_input.get(POCASI_CZ_ENABLED):
|
||||||
|
if user_input.get(POCASI_CZ_API_ID) == "":
|
||||||
|
errors[POCASI_CZ_API_ID] = "pocasi_id_required"
|
||||||
|
if user_input.get(POCASI_CZ_API_KEY) == "":
|
||||||
|
errors[POCASI_CZ_API_KEY] = "pocasi_key_required"
|
||||||
|
|
||||||
async def async_step_migration_units(self, user_input=None):
|
if len(errors) > 0:
|
||||||
"""Migrate unit step."""
|
|
||||||
|
|
||||||
registry = er.async_get(self.hass)
|
|
||||||
sensor_entry = registry.async_get(self.selected_sensor)
|
|
||||||
sensor_stats = await long_term_units_in_statistics_meta(self.hass)
|
|
||||||
|
|
||||||
default_unit = sensor_entry.unit_of_measurement if sensor_entry else None
|
|
||||||
|
|
||||||
if default_unit not in self.unit_values:
|
|
||||||
default_unit = self.unit_values[0]
|
|
||||||
|
|
||||||
data_schema = vol.Schema({
|
|
||||||
vol.Required(MIG_FROM, default=default_unit): vol.In(self.unit_values),
|
|
||||||
vol.Required(MIG_TO): vol.In(self.unit_values),
|
|
||||||
vol.Optional("trigger_action", default=False): bool,
|
|
||||||
})
|
|
||||||
|
|
||||||
if user_input is None:
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="migration_units",
|
step_id="pocasi",
|
||||||
data_schema=data_schema,
|
data_schema=vol.Schema(self.pocasi_cz_schema),
|
||||||
errors={},
|
errors=errors,
|
||||||
description_placeholders={
|
|
||||||
"migration_sensor": sensor_entry.original_name,
|
|
||||||
"migration_stats": sensor_stats.get(self.selected_sensor),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
# retain user data
|
||||||
if user_input.get("trigger_action"):
|
|
||||||
self.count = await migrate_data(
|
|
||||||
self.hass,
|
|
||||||
self.selected_sensor,
|
|
||||||
user_input.get(MIG_FROM),
|
|
||||||
user_input.get(MIG_TO),
|
|
||||||
)
|
|
||||||
|
|
||||||
registry.async_update_entity(self.selected_sensor,
|
|
||||||
unit_of_measurement=user_input.get(MIG_TO),
|
|
||||||
)
|
|
||||||
|
|
||||||
state = self.hass.states.get(self.selected_sensor)
|
|
||||||
if state:
|
|
||||||
_LOGGER.info("State attributes before update: %s", state.attributes)
|
|
||||||
attributes = dict(state.attributes)
|
|
||||||
attributes["unit_of_measurement"] = user_input.get(MIG_TO)
|
|
||||||
self.hass.states.async_set(self.selected_sensor, state.state, attributes)
|
|
||||||
_LOGGER.info("State attributes after update: %s", attributes)
|
|
||||||
|
|
||||||
options = {**self.config_entry.options, "reload_sensor": self.selected_sensor}
|
|
||||||
self.hass.config_entries.async_update_entry(self.config_entry, options=options)
|
|
||||||
|
|
||||||
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
|
||||||
|
|
||||||
await self.hass.async_block_till_done()
|
|
||||||
|
|
||||||
_LOGGER.info("Migration complete for sensor %s: %s row updated, new measurement unit: %s, ",
|
|
||||||
self.selected_sensor,
|
|
||||||
self.count,
|
|
||||||
user_input.get(MIG_TO),
|
|
||||||
)
|
|
||||||
|
|
||||||
await self._get_entry_data()
|
|
||||||
sensor_entry = er.async_get(self.hass).async_get(self.selected_sensor)
|
|
||||||
sensor_stat = await self.load_sensors_to_migrate()
|
|
||||||
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id="migration_complete",
|
|
||||||
data_schema=vol.Schema({}),
|
|
||||||
errors={},
|
|
||||||
description_placeholders={
|
|
||||||
"migration_sensor": sensor_entry.unit_of_measurement,
|
|
||||||
"migration_stats": sensor_stat.get(self.selected_sensor),
|
|
||||||
"migration_count": self.count,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# retain windy data
|
|
||||||
user_input.update(self.windy_data)
|
|
||||||
|
|
||||||
# retain user_data
|
|
||||||
user_input.update(self.user_data)
|
user_input.update(self.user_data)
|
||||||
|
|
||||||
# retain senors
|
# retain senors
|
||||||
user_input.update(self.sensors)
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
# retain windy
|
||||||
|
|
||||||
async def async_step_migration_complete(self, user_input=None):
|
|
||||||
"""Migration complete."""
|
|
||||||
|
|
||||||
errors = {}
|
|
||||||
|
|
||||||
await self._get_entry_data()
|
|
||||||
sensor_entry = er.async_get(self.hass).async_get(self.selected_sensor)
|
|
||||||
sensor_stat = await self.load_sensors_to_migrate()
|
|
||||||
|
|
||||||
if user_input is None:
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id="migration_complete",
|
|
||||||
data_schema=vol.Schema({}),
|
|
||||||
errors=errors,
|
|
||||||
description_placeholders={
|
|
||||||
"migration_sensor": sensor_entry.unit_of_measurement,
|
|
||||||
"migration_stats": sensor_stat.get(self.selected_sensor),
|
|
||||||
"migration_count": self.count,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
# retain windy data
|
|
||||||
user_input.update(self.windy_data)
|
user_input.update(self.windy_data)
|
||||||
|
|
||||||
# retain user_data
|
|
||||||
user_input.update(self.user_data)
|
|
||||||
|
|
||||||
# retain senors
|
|
||||||
user_input.update(self.sensors)
|
|
||||||
|
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
|
|
||||||
class ConfigFlow(ConfigFlow, domain=DOMAIN):
|
class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Sencor SWS 12500 Weather Station."""
|
"""Handle a config flow for Sencor SWS 12500 Weather Station."""
|
||||||
|
|
||||||
data_schema = {
|
data_schema = {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ WSLINK_URL = "/data/upload.php"
|
||||||
WINDY_URL = "https://stations.windy.com/pws/update/"
|
WINDY_URL = "https://stations.windy.com/pws/update/"
|
||||||
DATABASE_PATH = "/config/home-assistant_v2.db"
|
DATABASE_PATH = "/config/home-assistant_v2.db"
|
||||||
|
|
||||||
|
POCASI_CZ_URL: Final = "http://ms.pocasimeteo.cz"
|
||||||
|
POCASI_CZ_SEND_MINIMUM: Final = 12 # minimal time to resend data
|
||||||
|
|
||||||
ICON = "mdi:weather"
|
ICON = "mdi:weather"
|
||||||
|
|
||||||
API_KEY = "API_KEY"
|
API_KEY = "API_KEY"
|
||||||
|
|
@ -20,6 +23,19 @@ 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"
|
||||||
|
|
||||||
|
POCASI_CZ_API_KEY = "POCASI_CZ_API_KEY"
|
||||||
|
POCASI_CZ_API_ID = "POCASI_CZ_API_ID"
|
||||||
|
POCASI_CZ_SEND_INTERVAL = "POCASI_SEND_INTERVAL"
|
||||||
|
POCASI_CZ_ENABLED = "pocasi_enabled_chcekbox"
|
||||||
|
POCASI_CZ_LOGGER_ENABLED = "pocasi_logger_checkbox"
|
||||||
|
POCASI_INVALID_KEY: Final = (
|
||||||
|
"Pocasi Meteo refused to accept data. Invalid ID/Key combination?"
|
||||||
|
)
|
||||||
|
POCASI_CZ_SUCCESS: Final = "Successfully sent data to Pocasi Meteo"
|
||||||
|
POCASI_CZ_UNEXPECTED: Final = (
|
||||||
|
"Pocasti Meteo responded unexpectedly 3 times in row. Resendig is now disabled!"
|
||||||
|
)
|
||||||
|
|
||||||
WINDY_API_KEY = "WINDY_API_KEY"
|
WINDY_API_KEY = "WINDY_API_KEY"
|
||||||
WINDY_ENABLED: Final = "windy_enabled_checkbox"
|
WINDY_ENABLED: Final = "windy_enabled_checkbox"
|
||||||
WINDY_LOGGER_ENABLED: Final = "windy_logger_checkbox"
|
WINDY_LOGGER_ENABLED: Final = "windy_logger_checkbox"
|
||||||
|
|
@ -57,12 +73,20 @@ PURGE_DATA: Final = [
|
||||||
"dailyrainin",
|
"dailyrainin",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PURGE_DATA_POCAS: Final = [
|
||||||
|
"ID",
|
||||||
|
"PASSWORD",
|
||||||
|
"action",
|
||||||
|
"rtfreq",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
BARO_PRESSURE: Final = "baro_pressure"
|
BARO_PRESSURE: Final = "baro_pressure"
|
||||||
OUTSIDE_TEMP: Final = "outside_temp"
|
OUTSIDE_TEMP: Final = "outside_temp"
|
||||||
DEW_POINT: Final = "dew_point"
|
DEW_POINT: Final = "dew_point"
|
||||||
OUTSIDE_HUMIDITY: Final = "outside_humidity"
|
OUTSIDE_HUMIDITY: Final = "outside_humidity"
|
||||||
OUTSIDE_CONNECTION: Final = "outside_connection"
|
OUTSIDE_CONNECTION: Final = "outside_connection"
|
||||||
|
OUTSIDE_BATTERY: Final = "outside_battery"
|
||||||
WIND_SPEED: Final = "wind_speed"
|
WIND_SPEED: Final = "wind_speed"
|
||||||
WIND_GUST: Final = "wind_gust"
|
WIND_GUST: Final = "wind_gust"
|
||||||
WIND_DIR: Final = "wind_dir"
|
WIND_DIR: Final = "wind_dir"
|
||||||
|
|
@ -76,10 +100,12 @@ DAILY_RAIN: Final = "daily_rain"
|
||||||
SOLAR_RADIATION: Final = "solar_radiation"
|
SOLAR_RADIATION: Final = "solar_radiation"
|
||||||
INDOOR_TEMP: Final = "indoor_temp"
|
INDOOR_TEMP: Final = "indoor_temp"
|
||||||
INDOOR_HUMIDITY: Final = "indoor_humidity"
|
INDOOR_HUMIDITY: Final = "indoor_humidity"
|
||||||
|
INDOOR_BATTERY: Final = "indoor_battery"
|
||||||
UV: Final = "uv"
|
UV: Final = "uv"
|
||||||
CH2_TEMP: Final = "ch2_temp"
|
CH2_TEMP: Final = "ch2_temp"
|
||||||
CH2_HUMIDITY: Final = "ch2_humidity"
|
CH2_HUMIDITY: Final = "ch2_humidity"
|
||||||
CH2_CONNECTION: Final = "ch2_connection"
|
CH2_CONNECTION: Final = "ch2_connection"
|
||||||
|
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"
|
||||||
|
|
@ -88,9 +114,10 @@ CH4_HUMIDITY: Final = "ch4_humidity"
|
||||||
CH4_CONNECTION: Final = "ch4_connection"
|
CH4_CONNECTION: Final = "ch4_connection"
|
||||||
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"
|
||||||
|
|
||||||
|
|
||||||
REMAP_ITEMS: dict = {
|
REMAP_ITEMS: dict[str, str] = {
|
||||||
"baromin": BARO_PRESSURE,
|
"baromin": BARO_PRESSURE,
|
||||||
"tempf": OUTSIDE_TEMP,
|
"tempf": OUTSIDE_TEMP,
|
||||||
"dewptf": DEW_POINT,
|
"dewptf": DEW_POINT,
|
||||||
|
|
@ -112,7 +139,7 @@ REMAP_ITEMS: dict = {
|
||||||
"soilmoisture3": CH4_HUMIDITY,
|
"soilmoisture3": CH4_HUMIDITY,
|
||||||
}
|
}
|
||||||
|
|
||||||
REMAP_WSLINK_ITEMS: dict = {
|
REMAP_WSLINK_ITEMS: dict[str, str] = {
|
||||||
"intem": INDOOR_TEMP,
|
"intem": INDOOR_TEMP,
|
||||||
"inhum": INDOOR_HUMIDITY,
|
"inhum": INDOOR_HUMIDITY,
|
||||||
"t1tem": OUTSIDE_TEMP,
|
"t1tem": OUTSIDE_TEMP,
|
||||||
|
|
@ -137,6 +164,12 @@ REMAP_WSLINK_ITEMS: dict = {
|
||||||
"t1rainwy": WEEKLY_RAIN,
|
"t1rainwy": WEEKLY_RAIN,
|
||||||
"t1rainmth": MONTHLY_RAIN,
|
"t1rainmth": MONTHLY_RAIN,
|
||||||
"t1rainyr": YEARLY_RAIN,
|
"t1rainyr": YEARLY_RAIN,
|
||||||
|
"t234c2tem": CH3_TEMP,
|
||||||
|
"t234c2hum": CH3_HUMIDITY,
|
||||||
|
"t1bat": OUTSIDE_BATTERY,
|
||||||
|
"inbat": INDOOR_BATTERY,
|
||||||
|
"t234c1bat": CH2_BATTERY,
|
||||||
|
"t1wbgt": WBGT_TEMP,
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: Add more sensors
|
# TODO: Add more sensors
|
||||||
|
|
@ -149,10 +182,19 @@ REMAP_WSLINK_ITEMS: dict = {
|
||||||
DISABLED_BY_DEFAULT: Final = [
|
DISABLED_BY_DEFAULT: Final = [
|
||||||
CH2_TEMP,
|
CH2_TEMP,
|
||||||
CH2_HUMIDITY,
|
CH2_HUMIDITY,
|
||||||
|
CH2_BATTERY,
|
||||||
CH3_TEMP,
|
CH3_TEMP,
|
||||||
CH3_HUMIDITY,
|
CH3_HUMIDITY,
|
||||||
CH4_TEMP,
|
CH4_TEMP,
|
||||||
CH4_HUMIDITY,
|
CH4_HUMIDITY,
|
||||||
|
OUTSIDE_BATTERY,
|
||||||
|
WBGT_TEMP,
|
||||||
|
]
|
||||||
|
|
||||||
|
BATTERY_LIST = [
|
||||||
|
OUTSIDE_BATTERY,
|
||||||
|
INDOOR_BATTERY,
|
||||||
|
CH2_BATTERY,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -195,3 +237,18 @@ AZIMUT: list[UnitOfDir] = [
|
||||||
UnitOfDir.NNW,
|
UnitOfDir.NNW,
|
||||||
UnitOfDir.N,
|
UnitOfDir.N,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UnitOfBat(StrEnum):
|
||||||
|
"""Battery level unit of measure."""
|
||||||
|
|
||||||
|
LOW = "low"
|
||||||
|
NORMAL = "normal"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
BATTERY_LEVEL: list[UnitOfBat] = [
|
||||||
|
UnitOfBat.LOW,
|
||||||
|
UnitOfBat.NORMAL,
|
||||||
|
UnitOfBat.UNKNOWN,
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@
|
||||||
"issue_tracker": "https://github.com/schizza/SWS-12500-custom-component/issues",
|
"issue_tracker": "https://github.com/schizza/SWS-12500-custom-component/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"version": "1.6.2",
|
"version": "1.6.9",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
"""Pocasi CZ resend functions."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
from typing import Any, Literal
|
||||||
|
|
||||||
|
from aiohttp import ClientError
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
DEFAULT_URL,
|
||||||
|
POCASI_CZ_API_ID,
|
||||||
|
POCASI_CZ_API_KEY,
|
||||||
|
POCASI_CZ_ENABLED,
|
||||||
|
POCASI_CZ_LOGGER_ENABLED,
|
||||||
|
POCASI_CZ_SEND_INTERVAL,
|
||||||
|
POCASI_CZ_SUCCESS,
|
||||||
|
POCASI_CZ_UNEXPECTED,
|
||||||
|
POCASI_CZ_URL,
|
||||||
|
POCASI_INVALID_KEY,
|
||||||
|
WSLINK_URL,
|
||||||
|
)
|
||||||
|
from .utils import update_options
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PocasiNotInserted(Exception):
|
||||||
|
"""NotInserted state."""
|
||||||
|
|
||||||
|
|
||||||
|
class PocasiSuccess(Exception):
|
||||||
|
"""WindySucces state."""
|
||||||
|
|
||||||
|
|
||||||
|
class PocasiApiKeyError(Exception):
|
||||||
|
"""Windy API Key error."""
|
||||||
|
|
||||||
|
|
||||||
|
class PocasiPush:
|
||||||
|
"""Push data to Windy."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, config: ConfigEntry) -> None:
|
||||||
|
"""Init."""
|
||||||
|
self.hass = hass
|
||||||
|
self.config = config
|
||||||
|
self._interval = int(self.config.options.get(POCASI_CZ_SEND_INTERVAL, 30))
|
||||||
|
|
||||||
|
self.last_update = datetime.now()
|
||||||
|
self.next_update = datetime.now() + timedelta(seconds=self._interval)
|
||||||
|
|
||||||
|
self.log = self.config.options.get(POCASI_CZ_LOGGER_ENABLED)
|
||||||
|
self.invalid_response_count = 0
|
||||||
|
|
||||||
|
def verify_response(
|
||||||
|
self,
|
||||||
|
response: str,
|
||||||
|
) -> PocasiNotInserted | PocasiSuccess | PocasiApiKeyError | None:
|
||||||
|
"""Verify answer form server."""
|
||||||
|
|
||||||
|
if self.log:
|
||||||
|
_LOGGER.debug("Pocasi CZ responded: %s", response)
|
||||||
|
|
||||||
|
# Server does not provide any responses.
|
||||||
|
# This is placeholder if future state is changed
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def push_data_to_server(
|
||||||
|
self, data: dict[str, Any], mode: Literal["WU", "WSLINK"]
|
||||||
|
):
|
||||||
|
"""Pushes weather data to server."""
|
||||||
|
|
||||||
|
_data = data.copy()
|
||||||
|
_api_id = self.config.options.get(POCASI_CZ_API_ID)
|
||||||
|
_api_key = self.config.options.get(POCASI_CZ_API_KEY)
|
||||||
|
|
||||||
|
if self.log:
|
||||||
|
_LOGGER.info(
|
||||||
|
"Pocasi CZ last update = %s, next update at: %s",
|
||||||
|
str(self.last_update),
|
||||||
|
str(self.next_update),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.next_update > datetime.now():
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Triggered update interval limit of %s seconds. Next possilbe update is set to: %s",
|
||||||
|
self._interval,
|
||||||
|
self.next_update,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
request_url: str = ""
|
||||||
|
if mode == "WSLINK":
|
||||||
|
_data["wsid"] = _api_id
|
||||||
|
_data["wspw"] = _api_key
|
||||||
|
request_url = f"{POCASI_CZ_URL}{WSLINK_URL}"
|
||||||
|
|
||||||
|
if mode == "WU":
|
||||||
|
_data["ID"] = _api_id
|
||||||
|
_data["PASSWORD"] = _api_key
|
||||||
|
request_url = f"{POCASI_CZ_URL}{DEFAULT_URL}"
|
||||||
|
|
||||||
|
session = async_get_clientsession(self.hass, verify_ssl=False)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Payload for Pocasi Meteo server: [mode=%s] [request_url=%s] = %s",
|
||||||
|
mode,
|
||||||
|
request_url,
|
||||||
|
_data,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
async with session.get(request_url, params=_data) as resp:
|
||||||
|
status = await resp.text()
|
||||||
|
try:
|
||||||
|
self.verify_response(status)
|
||||||
|
|
||||||
|
except PocasiApiKeyError:
|
||||||
|
# log despite of settings
|
||||||
|
_LOGGER.critical(POCASI_INVALID_KEY)
|
||||||
|
await update_options(
|
||||||
|
self.hass, self.config, POCASI_CZ_ENABLED, False
|
||||||
|
)
|
||||||
|
except PocasiSuccess:
|
||||||
|
if self.log:
|
||||||
|
_LOGGER.info(POCASI_CZ_SUCCESS)
|
||||||
|
|
||||||
|
except ClientError as ex:
|
||||||
|
_LOGGER.critical("Invalid response from Pocasi Meteo: %s", str(ex))
|
||||||
|
self.invalid_response_count += 1
|
||||||
|
if self.invalid_response_count > 3:
|
||||||
|
_LOGGER.critical(POCASI_CZ_UNEXPECTED)
|
||||||
|
await update_options(self.hass, self.config, POCASI_CZ_ENABLED, False)
|
||||||
|
|
||||||
|
self.last_update = datetime.now()
|
||||||
|
self.next_update = datetime.now() + timedelta(seconds=self._interval)
|
||||||
|
|
||||||
|
if self.log:
|
||||||
|
_LOGGER.info("Next update: %s", str(self.next_update))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Store routes info."""
|
"""Store routes info."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ class Route:
|
||||||
|
|
||||||
url_path: str
|
url_path: str
|
||||||
route: AbstractRoute
|
route: AbstractRoute
|
||||||
handler: callable
|
handler: Callable
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
@ -29,7 +30,7 @@ class Routes:
|
||||||
"""Initialize routes."""
|
"""Initialize routes."""
|
||||||
self.routes = {}
|
self.routes = {}
|
||||||
|
|
||||||
def switch_route(self, coordinator: callable, url_path: str):
|
def switch_route(self, coordinator: Callable, url_path: str):
|
||||||
"""Switch route."""
|
"""Switch route."""
|
||||||
|
|
||||||
for url, route in self.routes.items():
|
for url, route in self.routes.items():
|
||||||
|
|
@ -47,7 +48,7 @@ class Routes:
|
||||||
self,
|
self,
|
||||||
url_path: str,
|
url_path: str,
|
||||||
route: AbstractRoute,
|
route: AbstractRoute,
|
||||||
handler: callable,
|
handler: Callable,
|
||||||
enabled: bool = False,
|
enabled: bool = False,
|
||||||
):
|
):
|
||||||
"""Add route."""
|
"""Add route."""
|
||||||
|
|
@ -55,7 +56,7 @@ class Routes:
|
||||||
|
|
||||||
def get_route(self, url_path: str) -> Route:
|
def get_route(self, url_path: str) -> Route:
|
||||||
"""Get route."""
|
"""Get route."""
|
||||||
return self.routes.get(url_path)
|
return self.routes.get(url_path, Route)
|
||||||
|
|
||||||
def get_enabled(self) -> str:
|
def get_enabled(self) -> str:
|
||||||
"""Get enabled routes."""
|
"""Get enabled routes."""
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.sensor import RestoreSensor, SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
|
|
@ -12,6 +12,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import WeatherDataUpdateCoordinator
|
from . import WeatherDataUpdateCoordinator
|
||||||
from .const import (
|
from .const import (
|
||||||
|
BATTERY_LIST,
|
||||||
CHILL_INDEX,
|
CHILL_INDEX,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HEAT_INDEX,
|
HEAT_INDEX,
|
||||||
|
|
@ -22,11 +23,12 @@ from .const import (
|
||||||
WIND_DIR,
|
WIND_DIR,
|
||||||
WIND_SPEED,
|
WIND_SPEED,
|
||||||
WSLINK,
|
WSLINK,
|
||||||
|
UnitOfBat,
|
||||||
)
|
)
|
||||||
from .sensors_common import WeatherSensorEntityDescription
|
from .sensors_common import WeatherSensorEntityDescription
|
||||||
from .sensors_weather import SENSOR_TYPES_WEATHER_API
|
from .sensors_weather import SENSOR_TYPES_WEATHER_API
|
||||||
from .sensors_wslink import SENSOR_TYPES_WSLINK
|
from .sensors_wslink import SENSOR_TYPES_WSLINK
|
||||||
from .utils import chill_index, heat_index
|
from .utils import battery_level_to_icon, battery_level_to_text, chill_index, heat_index
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -42,12 +44,12 @@ async def async_setup_entry(
|
||||||
|
|
||||||
sensors_to_load: list = []
|
sensors_to_load: list = []
|
||||||
sensors: list = []
|
sensors: list = []
|
||||||
_wslink = config_entry.data.get(WSLINK)
|
_wslink = config_entry.options.get(WSLINK)
|
||||||
|
|
||||||
SENSOR_TYPES = SENSOR_TYPES_WSLINK if _wslink else SENSOR_TYPES_WEATHER_API
|
SENSOR_TYPES = SENSOR_TYPES_WSLINK if _wslink else SENSOR_TYPES_WEATHER_API
|
||||||
|
|
||||||
# Check if we have some sensors to load.
|
# Check if we have some sensors to load.
|
||||||
if sensors_to_load := config_entry.options.get(SENSORS_TO_LOAD):
|
if sensors_to_load := config_entry.options.get(SENSORS_TO_LOAD, []):
|
||||||
if WIND_DIR in sensors_to_load:
|
if WIND_DIR in sensors_to_load:
|
||||||
sensors_to_load.append(WIND_AZIMUT)
|
sensors_to_load.append(WIND_AZIMUT)
|
||||||
if (OUTSIDE_HUMIDITY in sensors_to_load) and (OUTSIDE_TEMP in sensors_to_load):
|
if (OUTSIDE_HUMIDITY in sensors_to_load) and (OUTSIDE_TEMP in sensors_to_load):
|
||||||
|
|
@ -63,9 +65,9 @@ async def async_setup_entry(
|
||||||
async_add_entities(sensors)
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class WeatherSensor(
|
class WeatherSensor( # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
CoordinatorEntity[WeatherDataUpdateCoordinator], RestoreSensor, SensorEntity
|
CoordinatorEntity[WeatherDataUpdateCoordinator], SensorEntity
|
||||||
):
|
): # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
"""Implementation of Weather Sensor entity."""
|
"""Implementation of Weather Sensor entity."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
@ -92,12 +94,6 @@ class WeatherSensor(
|
||||||
|
|
||||||
self.coordinator.async_add_listener(self._handle_coordinator_update)
|
self.coordinator.async_add_listener(self._handle_coordinator_update)
|
||||||
|
|
||||||
# prev_state_data = await self.async_get_last_sensor_data()
|
|
||||||
# prev_state = await self.async_get_last_state()
|
|
||||||
# if not prev_state:
|
|
||||||
# return
|
|
||||||
# self._data = prev_state_data.native_value
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
|
|
@ -108,29 +104,31 @@ class WeatherSensor(
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> str | int | float | None:
|
def native_value(self): # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
"""Return value of entity."""
|
"""Return value of entity."""
|
||||||
|
|
||||||
_wslink = self.coordinator.config.options.get(WSLINK)
|
_wslink = self.coordinator.config.options.get(WSLINK)
|
||||||
|
|
||||||
if self.coordinator.data and (WIND_AZIMUT in self.entity_description.key):
|
if self.coordinator.data and (WIND_AZIMUT in self.entity_description.key):
|
||||||
return self.entity_description.value_fn(self.coordinator.data.get(WIND_DIR))
|
return self.entity_description.value_fn(self.coordinator.data.get(WIND_DIR)) # pyright: ignore[ reportAttributeAccessIssue]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.coordinator.data
|
self.coordinator.data
|
||||||
and (HEAT_INDEX in self.entity_description.key)
|
and (HEAT_INDEX in self.entity_description.key)
|
||||||
and not _wslink
|
and not _wslink
|
||||||
):
|
):
|
||||||
return self.entity_description.value_fn(heat_index(self.coordinator.data))
|
return self.entity_description.value_fn(heat_index(self.coordinator.data)) # pyright: ignore[ reportAttributeAccessIssue]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.coordinator.data
|
self.coordinator.data
|
||||||
and (CHILL_INDEX in self.entity_description.key)
|
and (CHILL_INDEX in self.entity_description.key)
|
||||||
and not _wslink
|
and not _wslink
|
||||||
):
|
):
|
||||||
return self.entity_description.value_fn(chill_index(self.coordinator.data))
|
return self.entity_description.value_fn(chill_index(self.coordinator.data)) # pyright: ignore[ reportAttributeAccessIssue]
|
||||||
|
|
||||||
return None if self._data == "" else self.entity_description.value_fn(self._data)
|
return (
|
||||||
|
None if self._data == "" else self.entity_description.value_fn(self._data) # pyright: ignore[ reportAttributeAccessIssue]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def suggested_entity_id(self) -> str:
|
def suggested_entity_id(self) -> str:
|
||||||
|
|
@ -138,7 +136,20 @@ class WeatherSensor(
|
||||||
return generate_entity_id("sensor.{}", self.entity_description.key)
|
return generate_entity_id("sensor.{}", self.entity_description.key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def icon(self) -> str | None: # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
|
"""Return the dynamic icon for battery representation."""
|
||||||
|
|
||||||
|
if self.entity_description.key in BATTERY_LIST:
|
||||||
|
if self.native_value:
|
||||||
|
battery_level = battery_level_to_text(self.native_value)
|
||||||
|
return battery_level_to_icon(battery_level)
|
||||||
|
|
||||||
|
return battery_level_to_icon(UnitOfBat.UNKNOWN)
|
||||||
|
|
||||||
|
return self.entity_description.icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo: # pyright: ignore[reportIncompatibleVariableOverride]
|
||||||
"""Device info."""
|
"""Device info."""
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
connections=set(),
|
connections=set(),
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,8 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
key=WIND_DIR,
|
key=WIND_DIR,
|
||||||
native_unit_of_measurement=DEGREE,
|
native_unit_of_measurement=DEGREE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT_ANGLE,
|
||||||
|
device_class=SensorDeviceClass.WIND_DIRECTION,
|
||||||
suggested_display_precision=None,
|
suggested_display_precision=None,
|
||||||
icon="mdi:sign-direction",
|
icon="mdi:sign-direction",
|
||||||
translation_key=WIND_DIR,
|
translation_key=WIND_DIR,
|
||||||
|
|
|
||||||
|
|
@ -17,32 +17,34 @@ from homeassistant.const import (
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BARO_PRESSURE,
|
BARO_PRESSURE,
|
||||||
|
CH2_BATTERY,
|
||||||
CH2_HUMIDITY,
|
CH2_HUMIDITY,
|
||||||
CH2_TEMP,
|
CH2_TEMP,
|
||||||
CH3_HUMIDITY,
|
CH3_HUMIDITY,
|
||||||
CH3_TEMP,
|
CH3_TEMP,
|
||||||
CH4_HUMIDITY,
|
|
||||||
CH4_TEMP,
|
|
||||||
CHILL_INDEX,
|
CHILL_INDEX,
|
||||||
DAILY_RAIN,
|
DAILY_RAIN,
|
||||||
DEW_POINT,
|
DEW_POINT,
|
||||||
HEAT_INDEX,
|
HEAT_INDEX,
|
||||||
|
HOURLY_RAIN,
|
||||||
|
INDOOR_BATTERY,
|
||||||
INDOOR_HUMIDITY,
|
INDOOR_HUMIDITY,
|
||||||
INDOOR_TEMP,
|
INDOOR_TEMP,
|
||||||
|
MONTHLY_RAIN,
|
||||||
|
OUTSIDE_BATTERY,
|
||||||
OUTSIDE_HUMIDITY,
|
OUTSIDE_HUMIDITY,
|
||||||
OUTSIDE_TEMP,
|
OUTSIDE_TEMP,
|
||||||
RAIN,
|
RAIN,
|
||||||
SOLAR_RADIATION,
|
SOLAR_RADIATION,
|
||||||
UV,
|
UV,
|
||||||
|
WBGT_TEMP,
|
||||||
|
WEEKLY_RAIN,
|
||||||
WIND_AZIMUT,
|
WIND_AZIMUT,
|
||||||
WIND_DIR,
|
WIND_DIR,
|
||||||
WIND_GUST,
|
WIND_GUST,
|
||||||
WIND_SPEED,
|
WIND_SPEED,
|
||||||
UnitOfDir,
|
|
||||||
MONTHLY_RAIN,
|
|
||||||
YEARLY_RAIN,
|
YEARLY_RAIN,
|
||||||
HOURLY_RAIN,
|
UnitOfDir,
|
||||||
WEEKLY_RAIN,
|
|
||||||
)
|
)
|
||||||
from .sensors_common import WeatherSensorEntityDescription
|
from .sensors_common import WeatherSensorEntityDescription
|
||||||
from .utils import wind_dir_to_text
|
from .utils import wind_dir_to_text
|
||||||
|
|
@ -126,7 +128,8 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
key=WIND_DIR,
|
key=WIND_DIR,
|
||||||
native_unit_of_measurement=DEGREE,
|
native_unit_of_measurement=DEGREE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT_ANGLE,
|
||||||
|
device_class=SensorDeviceClass.WIND_DIRECTION,
|
||||||
suggested_display_precision=None,
|
suggested_display_precision=None,
|
||||||
icon="mdi:sign-direction",
|
icon="mdi:sign-direction",
|
||||||
translation_key=WIND_DIR,
|
translation_key=WIND_DIR,
|
||||||
|
|
@ -143,8 +146,8 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
key=RAIN,
|
key=RAIN,
|
||||||
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||||
device_class=SensorDeviceClass.PRECIPITATION,
|
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||||
state_class=SensorStateClass.TOTAL,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
icon="mdi:weather-pouring",
|
icon="mdi:weather-pouring",
|
||||||
|
|
@ -243,25 +246,25 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
translation_key=CH2_HUMIDITY,
|
translation_key=CH2_HUMIDITY,
|
||||||
value_fn=lambda data: cast("int", data),
|
value_fn=lambda data: cast("int", data),
|
||||||
),
|
),
|
||||||
# WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
# key=CH3_TEMP,
|
key=CH3_TEMP,
|
||||||
# native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
# state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
# device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
# suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
# icon="mdi:weather-sunny",
|
icon="mdi:weather-sunny",
|
||||||
# translation_key=CH3_TEMP,
|
translation_key=CH3_TEMP,
|
||||||
# value_fn=lambda data: cast(float, data),
|
value_fn=lambda data: cast("float", data),
|
||||||
# ),
|
),
|
||||||
# WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
# key=CH3_HUMIDITY,
|
key=CH3_HUMIDITY,
|
||||||
# native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
# state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
# device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
# icon="mdi:weather-sunny",
|
icon="mdi:weather-sunny",
|
||||||
# translation_key=CH3_HUMIDITY,
|
translation_key=CH3_HUMIDITY,
|
||||||
# value_fn=lambda data: cast(int, data),
|
value_fn=lambda data: cast("int", data),
|
||||||
# ),
|
),
|
||||||
# WeatherSensorEntityDescription(
|
# WeatherSensorEntityDescription(
|
||||||
# key=CH4_TEMP,
|
# key=CH4_TEMP,
|
||||||
# native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
# native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
|
@ -303,4 +306,35 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
translation_key=CHILL_INDEX,
|
translation_key=CHILL_INDEX,
|
||||||
value_fn=lambda data: cast("int", data),
|
value_fn=lambda data: cast("int", data),
|
||||||
),
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=OUTSIDE_BATTERY,
|
||||||
|
translation_key=OUTSIDE_BATTERY,
|
||||||
|
icon="mdi:battery-unknown",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
value_fn=lambda data: (data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH2_BATTERY,
|
||||||
|
translation_key=CH2_BATTERY,
|
||||||
|
icon="mdi:battery-unknown",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
value_fn=lambda data: (data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=INDOOR_BATTERY,
|
||||||
|
translation_key=INDOOR_BATTERY,
|
||||||
|
icon="mdi:battery-unknown",
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
value_fn=lambda data: (data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WBGT_TEMP,
|
||||||
|
translation_key=WBGT_TEMP,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
"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."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
|
|
@ -25,7 +24,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"options": {
|
"options": {
|
||||||
"error": {
|
"error": {
|
||||||
"valid_credentials_api": "Provide valid API ID.",
|
"valid_credentials_api": "Provide valid API ID.",
|
||||||
|
|
@ -33,7 +31,6 @@
|
||||||
"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_key_required": "Windy API key is required if you want to enable this function."
|
"windy_key_required": "Windy API key is required if you want to enable this function."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Configure SWS12500 Integration",
|
"title": "Configure SWS12500 Integration",
|
||||||
|
|
@ -43,7 +40,6 @@
|
||||||
"windy": "Windy configuration"
|
"windy": "Windy configuration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"basic": {
|
"basic": {
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
"title": "Configure credentials",
|
"title": "Configure credentials",
|
||||||
|
|
@ -60,7 +56,6 @@
|
||||||
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"windy": {
|
"windy": {
|
||||||
"description": "Resend weather data to your Windy stations.",
|
"description": "Resend weather data to your Windy stations.",
|
||||||
"title": "Configure Windy",
|
"title": "Configure Windy",
|
||||||
|
|
@ -74,6 +69,24 @@
|
||||||
"windy_logger_checkbox": "Enable only if you want to send debuging data to the developer."
|
"windy_logger_checkbox": "Enable only if you want to send debuging data to the developer."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pocasi": {
|
||||||
|
"description": "Resend data to Pocasi Meteo CZ",
|
||||||
|
"title": "Configure Pocasi Meteo CZ",
|
||||||
|
"data": {
|
||||||
|
"POCASI_CZ_API_ID": "ID from your Pocasi Meteo APP",
|
||||||
|
"POCASI_CZ_API_KEY": "Key from your Pocasi Meteo APP",
|
||||||
|
"POCASI_CZ_SEND_INTERVAL": "Resend interval in seconds",
|
||||||
|
"pocasi_enabled_checkbox": "Enable resending data to Pocasi Meteo",
|
||||||
|
"pocasi_logger_checkbox": "Log data and responses"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"POCASI_CZ_API_ID": "You can obtain your ID in Pocasi Meteo App",
|
||||||
|
"POCASI_CZ_API_KEY": "You can obtain your KEY in Pocasi Meteo App",
|
||||||
|
"POCASI_CZ_SEND_INTERVAL": "Resend interval in seconds (minimum 12s, default 30s)",
|
||||||
|
"pocasi_enabled_checkbox": "Enables resending data to Pocasi Meteo",
|
||||||
|
"pocasi_logger_checkbox": "Enable only if you want to send debbug data to the developer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"migration": {
|
"migration": {
|
||||||
"title": "Statistic migration.",
|
"title": "Statistic migration.",
|
||||||
"description": "For the correct functioning of long-term statistics, it is necessary to migrate the sensor unit in the long-term statistics. The original unit of long-term statistics for daily precipitation was in mm/d, however, the station only sends data in mm without time differentiation.\n\n The sensor to be migrated is for daily precipitation. If the correct value is already in the list for the daily precipitation sensor (mm), then the migration is already complete.\n\n Migration result for the sensor: {migration_status}, a total of {migration_count} rows converted.",
|
"description": "For the correct functioning of long-term statistics, it is necessary to migrate the sensor unit in the long-term statistics. The original unit of long-term statistics for daily precipitation was in mm/d, however, the station only sends data in mm without time differentiation.\n\n The sensor to be migrated is for daily precipitation. If the correct value is already in the list for the daily precipitation sensor (mm), then the migration is already complete.\n\n Migration result for the sensor: {migration_status}, a total of {migration_count} rows converted.",
|
||||||
|
|
@ -88,30 +101,71 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"indoor_temp": { "name": "Indoor temperature" },
|
"indoor_temp": {
|
||||||
"indoor_humidity": { "name": "Indoor humidity" },
|
"name": "Indoor temperature"
|
||||||
"outside_temp": { "name": "Outside Temperature" },
|
},
|
||||||
"outside_humidity": { "name": "Outside humidity" },
|
"indoor_humidity": {
|
||||||
"uv": { "name": "UV index" },
|
"name": "Indoor humidity"
|
||||||
"baro_pressure": { "name": "Barometric pressure" },
|
},
|
||||||
"dew_point": { "name": "Dew point" },
|
"outside_temp": {
|
||||||
"wind_speed": { "name": "Wind speed" },
|
"name": "Outside Temperature"
|
||||||
"wind_dir": { "name": "Wind direction" },
|
},
|
||||||
"wind_gust": { "name": "Wind gust" },
|
"outside_humidity": {
|
||||||
"rain": { "name": "Rain" },
|
"name": "Outside humidity"
|
||||||
"daily_rain": { "name": "Daily precipitation" },
|
},
|
||||||
"solar_radiation": { "name": "Solar irradiance" },
|
"uv": {
|
||||||
"ch2_temp": { "name": "Channel 2 temperature" },
|
"name": "UV index"
|
||||||
"ch2_humidity": { "name": "Channel 2 humidity" },
|
},
|
||||||
"ch3_temp": { "name": "Channel 3 temperature" },
|
"baro_pressure": {
|
||||||
"ch3_humidity": { "name": "Channel 3 humidity" },
|
"name": "Barometric pressure"
|
||||||
"ch4_temp": { "name": "Channel 4 temperature" },
|
},
|
||||||
"ch4_humidity": { "name": "Channel 4 humidity" },
|
"dew_point": {
|
||||||
"heat_index": { "name": "Apparent temperature" },
|
"name": "Dew point"
|
||||||
"chill_index": { "name": "Wind chill" },
|
},
|
||||||
|
"wind_speed": {
|
||||||
|
"name": "Wind speed"
|
||||||
|
},
|
||||||
|
"wind_dir": {
|
||||||
|
"name": "Wind direction"
|
||||||
|
},
|
||||||
|
"wind_gust": {
|
||||||
|
"name": "Wind gust"
|
||||||
|
},
|
||||||
|
"rain": {
|
||||||
|
"name": "Rain"
|
||||||
|
},
|
||||||
|
"daily_rain": {
|
||||||
|
"name": "Daily precipitation"
|
||||||
|
},
|
||||||
|
"solar_radiation": {
|
||||||
|
"name": "Solar irradiance"
|
||||||
|
},
|
||||||
|
"ch2_temp": {
|
||||||
|
"name": "Channel 2 temperature"
|
||||||
|
},
|
||||||
|
"ch2_humidity": {
|
||||||
|
"name": "Channel 2 humidity"
|
||||||
|
},
|
||||||
|
"ch3_temp": {
|
||||||
|
"name": "Channel 3 temperature"
|
||||||
|
},
|
||||||
|
"ch3_humidity": {
|
||||||
|
"name": "Channel 3 humidity"
|
||||||
|
},
|
||||||
|
"ch4_temp": {
|
||||||
|
"name": "Channel 4 temperature"
|
||||||
|
},
|
||||||
|
"ch4_humidity": {
|
||||||
|
"name": "Channel 4 humidity"
|
||||||
|
},
|
||||||
|
"heat_index": {
|
||||||
|
"name": "Apparent temperature"
|
||||||
|
},
|
||||||
|
"chill_index": {
|
||||||
|
"name": "Wind chill"
|
||||||
|
},
|
||||||
"wind_azimut": {
|
"wind_azimut": {
|
||||||
"name": "Bearing",
|
"name": "Bearing",
|
||||||
"state": {
|
"state": {
|
||||||
|
|
@ -131,6 +185,14 @@
|
||||||
"wnw": "WNW",
|
"wnw": "WNW",
|
||||||
"nw": "NW",
|
"nw": "NW",
|
||||||
"nnw": "NNW"
|
"nnw": "NNW"
|
||||||
|
},
|
||||||
|
"outside_battery": {
|
||||||
|
"name": "Outside battery level",
|
||||||
|
"state": {
|
||||||
|
"normal": "OK",
|
||||||
|
"low": "Low",
|
||||||
|
"unknown": "Unknown / drained out"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"options": {
|
"options": {
|
||||||
"error": {
|
"error": {
|
||||||
"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_key_required": "Je vyžadován Windy API key, 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",
|
||||||
|
"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_send_minimum": "Minimální interval pro přeposílání je 12 sekund."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Nastavení integrace SWS12500",
|
"title": "Nastavení integrace SWS12500",
|
||||||
|
|
@ -40,10 +41,10 @@
|
||||||
"menu_options": {
|
"menu_options": {
|
||||||
"basic": "Základní - přístupové údaje (přihlášení)",
|
"basic": "Základní - přístupové údaje (přihlášení)",
|
||||||
"windy": "Nastavení pro přeposílání dat na Windy",
|
"windy": "Nastavení pro přeposílání dat na Windy",
|
||||||
|
"pocasi": "Nastavení pro přeposlání dat na Počasí Meteo CZ",
|
||||||
"migration": "Migrace statistiky senzoru"
|
"migration": "Migrace statistiky senzoru"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"basic": {
|
"basic": {
|
||||||
"description": "Zadejte API ID a API KEY, aby meteostanice mohla komunikovat s HomeAssistantem",
|
"description": "Zadejte API ID a API KEY, aby meteostanice mohla komunikovat s HomeAssistantem",
|
||||||
"title": "Nastavení přihlášení",
|
"title": "Nastavení přihlášení",
|
||||||
|
|
@ -60,7 +61,6 @@
|
||||||
"wslink": "WSLink API zapněte, pokud je stanice nastavena na zasílání dat přes WSLink."
|
"wslink": "WSLink API zapněte, pokud je stanice nastavena na zasílání dat přes WSLink."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"windy": {
|
"windy": {
|
||||||
"description": "Přeposílání dat z metostanice na Windy",
|
"description": "Přeposílání dat z metostanice na Windy",
|
||||||
"title": "Konfigurace Windy",
|
"title": "Konfigurace Windy",
|
||||||
|
|
@ -70,10 +70,28 @@
|
||||||
"windy_logger_checkbox": "Logovat data a odpovědi z Windy"
|
"windy_logger_checkbox": "Logovat data a odpovědi z Windy"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"WINDY_API_KEY": "Klíč API KEY získaný z https://https://api.windy.com/keys",
|
"WINDY_API_KEY": "Klíč API KEY získaný z https://api.windy.com/keys",
|
||||||
"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."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pocasi": {
|
||||||
|
"description": "Přeposílání dat do aplikace Počasí Meteo",
|
||||||
|
"title": "Konfigurace Počasí Meteo",
|
||||||
|
"data": {
|
||||||
|
"POCASI_CZ_API_ID": "ID účtu na Počasí Meteo",
|
||||||
|
"POCASI_CZ_API_KEY": "Klíč (Key) k účtu Počasí Meteo",
|
||||||
|
"POCASI_CZ_SEND_INTERVAL": "Interval v sekundách",
|
||||||
|
"pocasi_enabled_chcekbox": "Povolit přeposílání dat na server Počasí Meteo",
|
||||||
|
"pocasi_logger_checkbox": "Logovat data a odpovědi z Počasí Meteo"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"POCASI_API_ID": "ID získáte ve své aplikaci Počasí Meteo",
|
||||||
|
"POCASI_API_KEY": "Klíč (Key) získáte ve své aplikaci Počasí Meteo",
|
||||||
|
"POCASI_SEND_INTERVAL": "Interval v jakém se mají data na server přeposílat (minimum 12s, defaultně 30s)",
|
||||||
|
"pocasi_enabled_checkbox": "Zapne přeposílání data na server Počasí Meteo",
|
||||||
|
"pocasi_logger_checkbox": "Zapnout pouze v případě, že chcete zaslat ladící informace vývojáři."
|
||||||
|
}
|
||||||
|
},
|
||||||
"migration": {
|
"migration": {
|
||||||
"title": "Migrace statistiky senzoru.",
|
"title": "Migrace statistiky senzoru.",
|
||||||
"description": "Pro správnou funkci dlouhodobé statistiky je nutné provést migraci jednotky senzoru v dlouhodobé statistice. Původní jednotka dlouhodobé statistiky pro denní úhrn srážek byla v mm/d, nicméně stanice zasílá pouze data v mm bez časového rozlišení.\n\n Senzor, který má být migrován je pro denní úhrn srážek. Pokud je v seznamu již správná hodnota u senzoru pro denní úhrn (mm), pak je již migrace hotová.\n\n Výsledek migrace pro senzor: {migration_status}, přepvedeno celkem {migration_count} řádků.",
|
"description": "Pro správnou funkci dlouhodobé statistiky je nutné provést migraci jednotky senzoru v dlouhodobé statistice. Původní jednotka dlouhodobé statistiky pro denní úhrn srážek byla v mm/d, nicméně stanice zasílá pouze data v mm bez časového rozlišení.\n\n Senzor, který má být migrován je pro denní úhrn srážek. Pokud je v seznamu již správná hodnota u senzoru pro denní úhrn (mm), pak je již migrace hotová.\n\n Výsledek migrace pro senzor: {migration_status}, přepvedeno celkem {migration_count} řádků.",
|
||||||
|
|
@ -88,34 +106,86 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"indoor_temp": { "name": "Vnitřní teplota" },
|
"indoor_temp": {
|
||||||
"indoor_humidity": { "name": "Vnitřní vlhkost vzduchu" },
|
"name": "Vnitřní teplota"
|
||||||
"outside_temp": { "name": "Venkovní teplota" },
|
},
|
||||||
"outside_humidity": { "name": "Venkovní vlhkost vzduchu" },
|
"indoor_humidity": {
|
||||||
"uv": { "name": "UV index" },
|
"name": "Vnitřní vlhkost vzduchu"
|
||||||
"baro_pressure": { "name": "Tlak vzduchu" },
|
},
|
||||||
"dew_point": { "name": "Rosný bod" },
|
"outside_temp": {
|
||||||
"wind_speed": { "name": "Rychlost větru" },
|
"name": "Venkovní teplota"
|
||||||
"wind_dir": { "name": "Směr větru" },
|
},
|
||||||
"wind_gust": { "name": "Poryvy větru" },
|
"outside_humidity": {
|
||||||
"rain": { "name": "Srážky" },
|
"name": "Venkovní vlhkost vzduchu"
|
||||||
"daily_rain": { "name": "Denní úhrn srážek" },
|
},
|
||||||
"solar_radiation": { "name": "Sluneční osvit" },
|
"uv": {
|
||||||
"ch2_temp": { "name": "Teplota senzoru 2" },
|
"name": "UV index"
|
||||||
"ch2_humidity": { "name": "Vlhkost sensoru 2" },
|
},
|
||||||
"ch3_temp": { "name": "Teplota senzoru 3" },
|
"baro_pressure": {
|
||||||
"ch3_humidity": { "name": "Vlhkost sensoru 3" },
|
"name": "Tlak vzduchu"
|
||||||
"ch4_temp": { "name": "Teplota senzoru 4" },
|
},
|
||||||
"ch4_humidity": { "name": "Vlhkost sensoru 4" },
|
"dew_point": {
|
||||||
"heat_index": { "name": "Tepelný index" },
|
"name": "Rosný bod"
|
||||||
"chill_index": { "name": "Pocitová teplota" },
|
},
|
||||||
"hourly_rain": { "name": "Hodinový úhrn srážek" },
|
"wind_speed": {
|
||||||
"weekly_rain": { "name": "Týdenní úhrn srážek" },
|
"name": "Rychlost větru"
|
||||||
"monthly_rain": { "name": "Měsíční úhrn srážek" },
|
},
|
||||||
"yearly_rain": { "name": "Roční úhrn srážek" },
|
"wind_dir": {
|
||||||
|
"name": "Směr větru"
|
||||||
|
},
|
||||||
|
"wind_gust": {
|
||||||
|
"name": "Poryvy větru"
|
||||||
|
},
|
||||||
|
"rain": {
|
||||||
|
"name": "Srážky"
|
||||||
|
},
|
||||||
|
"daily_rain": {
|
||||||
|
"name": "Denní úhrn srážek"
|
||||||
|
},
|
||||||
|
"solar_radiation": {
|
||||||
|
"name": "Sluneční osvit"
|
||||||
|
},
|
||||||
|
"ch2_temp": {
|
||||||
|
"name": "Teplota senzoru 2"
|
||||||
|
},
|
||||||
|
"ch2_humidity": {
|
||||||
|
"name": "Vlhkost sensoru 2"
|
||||||
|
},
|
||||||
|
"ch3_temp": {
|
||||||
|
"name": "Teplota senzoru 3"
|
||||||
|
},
|
||||||
|
"ch3_humidity": {
|
||||||
|
"name": "Vlhkost sensoru 3"
|
||||||
|
},
|
||||||
|
"ch4_temp": {
|
||||||
|
"name": "Teplota senzoru 4"
|
||||||
|
},
|
||||||
|
"ch4_humidity": {
|
||||||
|
"name": "Vlhkost sensoru 4"
|
||||||
|
},
|
||||||
|
"heat_index": {
|
||||||
|
"name": "Tepelný index"
|
||||||
|
},
|
||||||
|
"chill_index": {
|
||||||
|
"name": "Pocitová teplota"
|
||||||
|
},
|
||||||
|
"hourly_rain": {
|
||||||
|
"name": "Hodinový úhrn srážek"
|
||||||
|
},
|
||||||
|
"weekly_rain": {
|
||||||
|
"name": "Týdenní úhrn srážek"
|
||||||
|
},
|
||||||
|
"monthly_rain": {
|
||||||
|
"name": "Měsíční úhrn srážek"
|
||||||
|
},
|
||||||
|
"yearly_rain": {
|
||||||
|
"name": "Roční úhrn srážek"
|
||||||
|
},
|
||||||
|
"wbgt_temp": {
|
||||||
|
"name": "WBGT index"
|
||||||
|
},
|
||||||
"wind_azimut": {
|
"wind_azimut": {
|
||||||
"name": "Azimut",
|
"name": "Azimut",
|
||||||
"state": {
|
"state": {
|
||||||
|
|
@ -136,6 +206,30 @@
|
||||||
"nw": "SZ",
|
"nw": "SZ",
|
||||||
"nnw": "SSZ"
|
"nnw": "SSZ"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"outside_battery": {
|
||||||
|
"name": "Stav nabití venkovní baterie",
|
||||||
|
"state": {
|
||||||
|
"low": "Nízká",
|
||||||
|
"normal": "Normální",
|
||||||
|
"unknown": "Neznámá / zcela vybitá"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor_battery": {
|
||||||
|
"name": "Stav nabití baterie kozole",
|
||||||
|
"state": {
|
||||||
|
"low": "Nízká",
|
||||||
|
"normal": "Normální",
|
||||||
|
"unknown": "Neznámá / zcela vybitá"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ch2_battery": {
|
||||||
|
"name": "Stav nabití baterie kanálu 2",
|
||||||
|
"state": {
|
||||||
|
"low": "Nízká",
|
||||||
|
"normal": "Normální",
|
||||||
|
"unknown": "Neznámá / zcela vybitá"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
"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."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
|
|
@ -25,7 +24,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"options": {
|
"options": {
|
||||||
"error": {
|
"error": {
|
||||||
"valid_credentials_api": "Provide valid API ID.",
|
"valid_credentials_api": "Provide valid API ID.",
|
||||||
|
|
@ -33,7 +31,6 @@
|
||||||
"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_key_required": "Windy API key is required if you want to enable this function."
|
"windy_key_required": "Windy API key is required if you want to enable this function."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Configure SWS12500 Integration",
|
"title": "Configure SWS12500 Integration",
|
||||||
|
|
@ -43,7 +40,6 @@
|
||||||
"windy": "Windy configuration"
|
"windy": "Windy configuration"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"basic": {
|
"basic": {
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
"title": "Configure credentials",
|
"title": "Configure credentials",
|
||||||
|
|
@ -60,7 +56,6 @@
|
||||||
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"windy": {
|
"windy": {
|
||||||
"description": "Resend weather data to your Windy stations.",
|
"description": "Resend weather data to your Windy stations.",
|
||||||
"title": "Configure Windy",
|
"title": "Configure Windy",
|
||||||
|
|
@ -74,6 +69,24 @@
|
||||||
"windy_logger_checkbox": "Enable only if you want to send debuging data to the developer."
|
"windy_logger_checkbox": "Enable only if you want to send debuging data to the developer."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pocasi": {
|
||||||
|
"description": "Resend data to Pocasi Meteo CZ",
|
||||||
|
"title": "Configure Pocasi Meteo CZ",
|
||||||
|
"data": {
|
||||||
|
"POCASI_CZ_API_ID": "ID from your Pocasi Meteo APP",
|
||||||
|
"POCASI_CZ_API_KEY": "Key from your Pocasi Meteo APP",
|
||||||
|
"POCASI_CZ_SEND_INTERVAL": "Resend interval in seconds",
|
||||||
|
"pocasi_enabled_checkbox": "Enable resending data to Pocasi Meteo",
|
||||||
|
"pocasi_logger_checkbox": "Log data and responses"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"POCASI_CZ_API_ID": "You can obtain your ID in Pocasi Meteo App",
|
||||||
|
"POCASI_CZ_API_KEY": "You can obtain your KEY in Pocasi Meteo App",
|
||||||
|
"POCASI_CZ_SEND_INTERVAL": "Resend interval in seconds (minimum 12s, default 30s)",
|
||||||
|
"pocasi_enabled_checkbox": "Enables resending data to Pocasi Meteo",
|
||||||
|
"pocasi_logger_checkbox": "Enable only if you want to send debbug data to the developer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"migration": {
|
"migration": {
|
||||||
"title": "Statistic migration.",
|
"title": "Statistic migration.",
|
||||||
"description": "For the correct functioning of long-term statistics, it is necessary to migrate the sensor unit in the long-term statistics. The original unit of long-term statistics for daily precipitation was in mm/d, however, the station only sends data in mm without time differentiation.\n\n The sensor to be migrated is for daily precipitation. If the correct value is already in the list for the daily precipitation sensor (mm), then the migration is already complete.\n\n Migration result for the sensor: {migration_status}, a total of {migration_count} rows converted.",
|
"description": "For the correct functioning of long-term statistics, it is necessary to migrate the sensor unit in the long-term statistics. The original unit of long-term statistics for daily precipitation was in mm/d, however, the station only sends data in mm without time differentiation.\n\n The sensor to be migrated is for daily precipitation. If the correct value is already in the list for the daily precipitation sensor (mm), then the migration is already complete.\n\n Migration result for the sensor: {migration_status}, a total of {migration_count} rows converted.",
|
||||||
|
|
@ -88,34 +101,86 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"indoor_temp": { "name": "Indoor temperature" },
|
"indoor_temp": {
|
||||||
"indoor_humidity": { "name": "Indoor humidity" },
|
"name": "Indoor temperature"
|
||||||
"outside_temp": { "name": "Outside Temperature" },
|
},
|
||||||
"outside_humidity": { "name": "Outside humidity" },
|
"indoor_humidity": {
|
||||||
"uv": { "name": "UV index" },
|
"name": "Indoor humidity"
|
||||||
"baro_pressure": { "name": "Barometric pressure" },
|
},
|
||||||
"dew_point": { "name": "Dew point" },
|
"outside_temp": {
|
||||||
"wind_speed": { "name": "Wind speed" },
|
"name": "Outside Temperature"
|
||||||
"wind_dir": { "name": "Wind direction" },
|
},
|
||||||
"wind_gust": { "name": "Wind gust" },
|
"outside_humidity": {
|
||||||
"rain": { "name": "Rain" },
|
"name": "Outside humidity"
|
||||||
"daily_rain": { "name": "Daily precipitation" },
|
},
|
||||||
"solar_radiation": { "name": "Solar irradiance" },
|
"uv": {
|
||||||
"ch2_temp": { "name": "Channel 2 temperature" },
|
"name": "UV index"
|
||||||
"ch2_humidity": { "name": "Channel 2 humidity" },
|
},
|
||||||
"ch3_temp": { "name": "Channel 3 temperature" },
|
"baro_pressure": {
|
||||||
"ch3_humidity": { "name": "Channel 3 humidity" },
|
"name": "Barometric pressure"
|
||||||
"ch4_temp": { "name": "Channel 4 temperature" },
|
},
|
||||||
"ch4_humidity": { "name": "Channel 4 humidity" },
|
"dew_point": {
|
||||||
"heat_index": { "name": "Apparent temperature" },
|
"name": "Dew point"
|
||||||
"chill_index": { "name": "Wind chill" },
|
},
|
||||||
"hourly_rain": { "name": "Hourly precipitation" },
|
"wind_speed": {
|
||||||
"weekly_rain": { "name": "Weekly precipitation" },
|
"name": "Wind speed"
|
||||||
"monthly_rain": { "name": "Monthly precipitation" },
|
},
|
||||||
"yearly_rain": { "name": "Yearly precipitation" },
|
"wind_dir": {
|
||||||
|
"name": "Wind direction"
|
||||||
|
},
|
||||||
|
"wind_gust": {
|
||||||
|
"name": "Wind gust"
|
||||||
|
},
|
||||||
|
"rain": {
|
||||||
|
"name": "Rain"
|
||||||
|
},
|
||||||
|
"daily_rain": {
|
||||||
|
"name": "Daily precipitation"
|
||||||
|
},
|
||||||
|
"solar_radiation": {
|
||||||
|
"name": "Solar irradiance"
|
||||||
|
},
|
||||||
|
"ch2_temp": {
|
||||||
|
"name": "Channel 2 temperature"
|
||||||
|
},
|
||||||
|
"ch2_humidity": {
|
||||||
|
"name": "Channel 2 humidity"
|
||||||
|
},
|
||||||
|
"ch3_temp": {
|
||||||
|
"name": "Channel 3 temperature"
|
||||||
|
},
|
||||||
|
"ch3_humidity": {
|
||||||
|
"name": "Channel 3 humidity"
|
||||||
|
},
|
||||||
|
"ch4_temp": {
|
||||||
|
"name": "Channel 4 temperature"
|
||||||
|
},
|
||||||
|
"ch4_humidity": {
|
||||||
|
"name": "Channel 4 humidity"
|
||||||
|
},
|
||||||
|
"heat_index": {
|
||||||
|
"name": "Apparent temperature"
|
||||||
|
},
|
||||||
|
"chill_index": {
|
||||||
|
"name": "Wind chill"
|
||||||
|
},
|
||||||
|
"hourly_rain": {
|
||||||
|
"name": "Hourly precipitation"
|
||||||
|
},
|
||||||
|
"weekly_rain": {
|
||||||
|
"name": "Weekly precipitation"
|
||||||
|
},
|
||||||
|
"monthly_rain": {
|
||||||
|
"name": "Monthly precipitation"
|
||||||
|
},
|
||||||
|
"yearly_rain": {
|
||||||
|
"name": "Yearly precipitation"
|
||||||
|
},
|
||||||
|
"wbgt_index": {
|
||||||
|
"name": "WBGT index"
|
||||||
|
},
|
||||||
"wind_azimut": {
|
"wind_azimut": {
|
||||||
"name": "Bearing",
|
"name": "Bearing",
|
||||||
"state": {
|
"state": {
|
||||||
|
|
@ -136,6 +201,30 @@
|
||||||
"nw": "NW",
|
"nw": "NW",
|
||||||
"nnw": "NNW"
|
"nnw": "NNW"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"outside_battery": {
|
||||||
|
"name": "Outside battery level",
|
||||||
|
"state": {
|
||||||
|
"normal": "OK",
|
||||||
|
"low": "Low",
|
||||||
|
"unknown": "Unknown / drained out"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ch2_battery": {
|
||||||
|
"name": "Channel 2 battery level",
|
||||||
|
"state": {
|
||||||
|
"normal": "OK",
|
||||||
|
"low": "Low",
|
||||||
|
"unknown": "Unknown / drained out"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor_battery": {
|
||||||
|
"name": "Console battery level",
|
||||||
|
"state": {
|
||||||
|
"normal": "OK",
|
||||||
|
"low": "Low",
|
||||||
|
"unknown": "Unknown / drained out"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,6 @@ import numpy as np
|
||||||
|
|
||||||
from homeassistant.components import persistent_notification
|
from homeassistant.components import persistent_notification
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
|
||||||
UnitOfPrecipitationDepth,
|
|
||||||
UnitOfTemperature,
|
|
||||||
UnitOfVolumetricFlux,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.translation import async_get_translations
|
from homeassistant.helpers.translation import async_get_translations
|
||||||
|
|
||||||
|
|
@ -28,6 +23,7 @@ from .const import (
|
||||||
REMAP_WSLINK_ITEMS,
|
REMAP_WSLINK_ITEMS,
|
||||||
SENSORS_TO_LOAD,
|
SENSORS_TO_LOAD,
|
||||||
WIND_SPEED,
|
WIND_SPEED,
|
||||||
|
UnitOfBat,
|
||||||
UnitOfDir,
|
UnitOfDir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -53,7 +49,7 @@ async def translations(
|
||||||
)
|
)
|
||||||
if localize_key in _translations:
|
if localize_key in _translations:
|
||||||
return _translations[localize_key]
|
return _translations[localize_key]
|
||||||
return None
|
return ""
|
||||||
|
|
||||||
|
|
||||||
async def translated_notification(
|
async def translated_notification(
|
||||||
|
|
@ -65,7 +61,7 @@ async def translated_notification(
|
||||||
*,
|
*,
|
||||||
key: str = "message",
|
key: str = "message",
|
||||||
category: str = "notify",
|
category: str = "notify",
|
||||||
) -> str:
|
):
|
||||||
"""Translate notification."""
|
"""Translate notification."""
|
||||||
|
|
||||||
localize_key = f"component.{translation_domain}.{category}.{translation_key}.{key}"
|
localize_key = f"component.{translation_domain}.{category}.{translation_key}.{key}"
|
||||||
|
|
@ -96,7 +92,7 @@ async def translated_notification(
|
||||||
|
|
||||||
async def update_options(
|
async def update_options(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, update_key, update_value
|
hass: HomeAssistant, entry: ConfigEntry, update_key, update_value
|
||||||
) -> None:
|
) -> bool:
|
||||||
"""Update config.options entry."""
|
"""Update config.options entry."""
|
||||||
conf = {**entry.options}
|
conf = {**entry.options}
|
||||||
conf[update_key] = update_value
|
conf[update_key] = update_value
|
||||||
|
|
@ -151,7 +147,7 @@ def check_disabled(
|
||||||
Returns list of found sensors or None
|
Returns list of found sensors or None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
log: bool = config_entry.options.get(DEV_DBG)
|
log: bool = config_entry.options.get(DEV_DBG, False)
|
||||||
entityFound: bool = False
|
entityFound: bool = False
|
||||||
_loaded_sensors = loaded_sensors(config_entry)
|
_loaded_sensors = loaded_sensors(config_entry)
|
||||||
missing_sensors: list = []
|
missing_sensors: list = []
|
||||||
|
|
@ -181,6 +177,37 @@ def wind_dir_to_text(deg: float) -> UnitOfDir | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def battery_level_to_text(battery: int) -> UnitOfBat:
|
||||||
|
"""Return battery level in text representation.
|
||||||
|
|
||||||
|
Returns UnitOfBat
|
||||||
|
"""
|
||||||
|
|
||||||
|
level_map: dict[int, UnitOfBat] = {
|
||||||
|
0: UnitOfBat.LOW,
|
||||||
|
1: UnitOfBat.NORMAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
if battery is None:
|
||||||
|
return UnitOfBat.UNKNOWN
|
||||||
|
|
||||||
|
return level_map.get(int(battery), UnitOfBat.UNKNOWN)
|
||||||
|
|
||||||
|
|
||||||
|
def battery_level_to_icon(battery: UnitOfBat) -> str:
|
||||||
|
"""Return battery level in icon representation.
|
||||||
|
|
||||||
|
Returns str
|
||||||
|
"""
|
||||||
|
|
||||||
|
icons = {
|
||||||
|
UnitOfBat.LOW: "mdi:battery-low",
|
||||||
|
UnitOfBat.NORMAL: "mdi:battery",
|
||||||
|
}
|
||||||
|
|
||||||
|
return icons.get(battery, "mdi:battery-unknown")
|
||||||
|
|
||||||
|
|
||||||
def fahrenheit_to_celsius(fahrenheit: float) -> float:
|
def fahrenheit_to_celsius(fahrenheit: float) -> float:
|
||||||
"""Convert Fahrenheit to Celsius."""
|
"""Convert Fahrenheit to Celsius."""
|
||||||
return (fahrenheit - 32) * 5.0 / 9.0
|
return (fahrenheit - 32) * 5.0 / 9.0
|
||||||
|
|
@ -191,15 +218,22 @@ def celsius_to_fahrenheit(celsius: float) -> float:
|
||||||
return celsius * 9.0 / 5.0 + 32
|
return celsius * 9.0 / 5.0 + 32
|
||||||
|
|
||||||
|
|
||||||
def heat_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
def heat_index(data: Any, convert: bool = False) -> float | None:
|
||||||
"""Calculate heat index from temperature.
|
"""Calculate heat index from temperature.
|
||||||
|
|
||||||
data: dict with temperature and humidity
|
data: dict with temperature and humidity
|
||||||
convert: bool, convert recieved data from Celsius to Fahrenheit
|
convert: bool, convert recieved data from Celsius to Fahrenheit
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temp = float(data[OUTSIDE_TEMP])
|
temp = data.get(OUTSIDE_TEMP, None)
|
||||||
rh = float(data[OUTSIDE_HUMIDITY])
|
rh = data.get(OUTSIDE_HUMIDITY, None)
|
||||||
|
|
||||||
|
if not temp or not rh:
|
||||||
|
return None
|
||||||
|
|
||||||
|
temp = float(temp)
|
||||||
|
rh = float(rh)
|
||||||
|
|
||||||
adjustment = None
|
adjustment = None
|
||||||
|
|
||||||
if convert:
|
if convert:
|
||||||
|
|
@ -229,15 +263,21 @@ def heat_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
||||||
return simple
|
return simple
|
||||||
|
|
||||||
|
|
||||||
def chill_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
def chill_index(data: Any, convert: bool = False) -> float | None:
|
||||||
"""Calculate wind chill index from temperature and wind speed.
|
"""Calculate wind chill index from temperature and wind speed.
|
||||||
|
|
||||||
data: dict with temperature and wind speed
|
data: dict with temperature and wind speed
|
||||||
convert: bool, convert recieved data from Celsius to Fahrenheit
|
convert: bool, convert recieved data from Celsius to Fahrenheit
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temp = float(data[OUTSIDE_TEMP])
|
temp = data.get(OUTSIDE_TEMP, None)
|
||||||
wind = float(data[WIND_SPEED])
|
wind = data.get(WIND_SPEED, None)
|
||||||
|
|
||||||
|
if not temp or not wind:
|
||||||
|
return None
|
||||||
|
|
||||||
|
temp = float(temp)
|
||||||
|
wind = float(wind)
|
||||||
|
|
||||||
if convert:
|
if convert:
|
||||||
temp = celsius_to_fahrenheit(temp)
|
temp = celsius_to_fahrenheit(temp)
|
||||||
|
|
@ -258,7 +298,7 @@ def chill_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
||||||
|
|
||||||
def long_term_units_in_statistics_meta():
|
def long_term_units_in_statistics_meta():
|
||||||
"""Get units in long term statitstics."""
|
"""Get units in long term statitstics."""
|
||||||
|
sensor_units = []
|
||||||
if not Path(DATABASE_PATH).exists():
|
if not Path(DATABASE_PATH).exists():
|
||||||
_LOGGER.error("Database file not found: %s", DATABASE_PATH)
|
_LOGGER.error("Database file not found: %s", DATABASE_PATH)
|
||||||
return False
|
return False
|
||||||
|
|
@ -267,10 +307,12 @@ def long_term_units_in_statistics_meta():
|
||||||
db = conn.cursor()
|
db = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.execute("""
|
db.execute(
|
||||||
|
"""
|
||||||
SELECT statistic_id, unit_of_measurement from statistics_meta
|
SELECT statistic_id, unit_of_measurement from statistics_meta
|
||||||
WHERE statistic_id LIKE 'sensor.weather_station_sws%'
|
WHERE statistic_id LIKE 'sensor.weather_station_sws%'
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
rows = db.fetchall()
|
rows = db.fetchall()
|
||||||
sensor_units = {
|
sensor_units = {
|
||||||
statistic_id: f"{statistic_id} ({unit})" for statistic_id, unit in rows
|
statistic_id: f"{statistic_id} ({unit})" for statistic_id, unit in rows
|
||||||
|
|
@ -284,7 +326,7 @@ def long_term_units_in_statistics_meta():
|
||||||
return sensor_units
|
return sensor_units
|
||||||
|
|
||||||
|
|
||||||
async def migrate_data(hass: HomeAssistant, sensor_id: str | None = None) -> bool:
|
async def migrate_data(hass: HomeAssistant, sensor_id: str | None = None) -> int | bool:
|
||||||
"""Migrate data from mm/d to mm."""
|
"""Migrate data from mm/d to mm."""
|
||||||
|
|
||||||
_LOGGER.debug("Sensor %s is required for data migration", sensor_id)
|
_LOGGER.debug("Sensor %s is required for data migration", sensor_id)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from aiohttp.client_exceptions import ClientError
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -106,7 +108,7 @@ class WindyPush:
|
||||||
if self.next_update > datetime.now():
|
if self.next_update > datetime.now():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
purged_data = dict(data)
|
purged_data = data.copy()
|
||||||
|
|
||||||
for purge in PURGE_DATA:
|
for purge in PURGE_DATA:
|
||||||
if purge in purged_data:
|
if purge in purged_data:
|
||||||
|
|
@ -138,20 +140,20 @@ class WindyPush:
|
||||||
_LOGGER.critical(WINDY_INVALID_KEY)
|
_LOGGER.critical(WINDY_INVALID_KEY)
|
||||||
text_for_test = WINDY_INVALID_KEY
|
text_for_test = WINDY_INVALID_KEY
|
||||||
|
|
||||||
update_options(self.hass, self.config, WINDY_ENABLED, False)
|
await update_options(self.hass, self.config, WINDY_ENABLED, False)
|
||||||
|
|
||||||
except WindySuccess:
|
except WindySuccess:
|
||||||
if self.log:
|
if self.log:
|
||||||
_LOGGER.info(WINDY_SUCCESS)
|
_LOGGER.info(WINDY_SUCCESS)
|
||||||
text_for_test = WINDY_SUCCESS
|
text_for_test = WINDY_SUCCESS
|
||||||
|
|
||||||
except session.ClientError as ex:
|
except ClientError as ex:
|
||||||
_LOGGER.critical("Invalid response from Windy: %s", str(ex))
|
_LOGGER.critical("Invalid response from Windy: %s", str(ex))
|
||||||
self.invalid_response_count += 1
|
self.invalid_response_count += 1
|
||||||
if self.invalid_response_count > 3:
|
if self.invalid_response_count > 3:
|
||||||
_LOGGER.critical(WINDY_UNEXPECTED)
|
_LOGGER.critical(WINDY_UNEXPECTED)
|
||||||
text_for_test = WINDY_UNEXPECTED
|
text_for_test = WINDY_UNEXPECTED
|
||||||
update_options(self.hass, self.config, WINDY_ENABLED, False)
|
await update_options(self.hass, self.config, WINDY_ENABLED, 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