diff --git a/custom_components/sws12500/config_flow.py b/custom_components/sws12500/config_flow.py index 9ef407f..508d01e 100644 --- a/custom_components/sws12500/config_flow.py +++ b/custom_components/sws12500/config_flow.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigFlow, OptionsFlow from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError +from .utils import long_term_units_in_statistics_meta, migrate_data from .const import ( API_ID, @@ -15,6 +16,7 @@ from .const import ( DOMAIN, INVALID_CREDENTIALS, SENSORS_TO_LOAD, + SENSOR_TO_MIGRATE, WINDY_API_KEY, WINDY_ENABLED, WINDY_LOGGER_ENABLED, @@ -42,6 +44,7 @@ class ConfigOptionsFlowHandler(OptionsFlow): self.user_data: dict[str, str] = {} self.user_data_schema = {} self.sensors: dict[str, Any] = {} + self.migrate_schema = {} @property def config_entry(self): @@ -90,9 +93,18 @@ class ConfigOptionsFlowHandler(OptionsFlow): ): bool or False, } + self.migrate_schema = { + vol.Required(SENSOR_TO_MIGRATE): vol.In( + long_term_units_in_statistics_meta() or {} + ), + vol.Optional("trigger_action", default=False): bool, + } + async def async_step_init(self, user_input=None): """Manage the options - show menu first.""" - return self.async_show_menu(step_id="init", menu_options=["basic", "windy"]) + return self.async_show_menu( + step_id="init", menu_options=["basic", "windy", "migration"] + ) async def async_step_basic(self, user_input=None): """Manage basic options - credentials.""" @@ -160,6 +172,51 @@ class ConfigOptionsFlowHandler(OptionsFlow): return self.async_create_entry(title=DOMAIN, data=user_input) + async def async_step_migration(self, user_input=None): + """Migrate sensors.""" + + # hj + errors = {} + + self._get_entry_data() + + if user_input is None: + return self.async_show_form( + step_id="migration", + data_schema=vol.Schema(self.migrate_schema), + errors=errors, + description_placeholders={ + "migration_status": "-", + "migration_count": "-", + }, + ) + + if user_input.get("trigger_action"): + # Akce se vykoná po zaškrtnutí + count = await self.hass.async_add_executor_job( + migrate_data, user_input.get(SENSOR_TO_MIGRATE) + ) + return self.async_show_form( + step_id="migration", + data_schema=vol.Schema(self.migrate_schema), + errors=errors, + description_placeholders={ + "migration_status": user_input.get(SENSOR_TO_MIGRATE), + "migration_count": count, + }, + ) + + # retain 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) + class ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Sencor SWS 12500 Weather Station.""" diff --git a/custom_components/sws12500/const.py b/custom_components/sws12500/const.py index 19e06d6..14cffb6 100644 --- a/custom_components/sws12500/const.py +++ b/custom_components/sws12500/const.py @@ -7,6 +7,7 @@ DOMAIN = "sws12500" DEFAULT_URL = "/weatherstation/updateweatherstation.php" WSLINK_URL = "/data/upload.php" WINDY_URL = "https://stations.windy.com/pws/update/" +DATABASE_PATH = "./config/home-assistant_v2.db" ICON = "mdi:weather" @@ -14,6 +15,7 @@ API_KEY = "API_KEY" API_ID = "API_ID" SENSORS_TO_LOAD: Final = "sensors_to_load" +SENSOR_TO_MIGRATE: Final = "sensor_to_migrate" DEV_DBG: Final = "dev_debug_checkbox" WSLINK: Final = "wslink" diff --git a/custom_components/sws12500/sensors_wslink.py b/custom_components/sws12500/sensors_wslink.py index f006e05..92cfe03 100644 --- a/custom_components/sws12500/sensors_wslink.py +++ b/custom_components/sws12500/sensors_wslink.py @@ -149,10 +149,10 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = ( ), WeatherSensorEntityDescription( key=DAILY_RAIN, - native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR, + native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS, + device_class=SensorDeviceClass.PRECIPITATION, state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.PRECIPITATION_INTENSITY, - suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_DAY, + suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS, suggested_display_precision=2, icon="mdi:weather-pouring", translation_key=DAILY_RAIN, diff --git a/custom_components/sws12500/strings.json b/custom_components/sws12500/strings.json index 33fccf5..fda0f39 100644 --- a/custom_components/sws12500/strings.json +++ b/custom_components/sws12500/strings.json @@ -35,7 +35,6 @@ }, "step": { - "init": { "title": "Configure SWS12500 Integration", "description": "Choose what do you want to configure. If basic access or resending data for Windy site", @@ -74,6 +73,18 @@ "WINDY_API_KEY": "Windy API KEY obtained from https://https://api.windy.com/keys", "windy_logger_checkbox": "Enable only if you want to send debuging data to the developer." } + }, + "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.", + "data": { + "sensor_to_migrate": "Sensor to migrate", + "trigger_action": "Trigger migration" + }, + "data_description": { + "sensor_to_migrate": "Select the correct sensor for statistics migration.\nThe sensor values will be preserved, they will not be recalculated, only the unit in the long-term statistics will be changed.", + "trigger_action": "Trigger the sensor statistics migration after checking." + } } } }, diff --git a/custom_components/sws12500/translations/cs.json b/custom_components/sws12500/translations/cs.json index fc95044..24bf2f5 100644 --- a/custom_components/sws12500/translations/cs.json +++ b/custom_components/sws12500/translations/cs.json @@ -39,7 +39,8 @@ "description": "Vyberte, co chcete konfigurovat. Zda přihlašovací údaje nebo nastavení pro přeposílání dat na Windy.", "menu_options": { "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", + "migration": "Migrace statistiky senzoru" } }, @@ -72,6 +73,18 @@ "WINDY_API_KEY": "Klíč API KEY získaný z https://https://api.windy.com/keys", "windy_logger_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři." } + }, + "migration": { + "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ů.", + "data": { + "sensor_to_migrate": "Senzor pro migraci", + "trigger_action": "Spustit migraci" + }, + "data_description": { + "sensor_to_migrate": "Vyberte správný senzor pri migraci statistiky. \n Hodnoty senzoru budou zachovány, nepřepočítají se, pouze se změní jednotka v dlouhodobé statistice. ", + "trigger_action": "Po zaškrtnutí se spustí migrace statistiky senzoru." + } } } }, diff --git a/custom_components/sws12500/translations/en.json b/custom_components/sws12500/translations/en.json index 33fccf5..0115968 100644 --- a/custom_components/sws12500/translations/en.json +++ b/custom_components/sws12500/translations/en.json @@ -74,6 +74,18 @@ "WINDY_API_KEY": "Windy API KEY obtained from https://https://api.windy.com/keys", "windy_logger_checkbox": "Enable only if you want to send debuging data to the developer." } + }, + "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.", + "data": { + "sensor_to_migrate": "Sensor to migrate", + "trigger_action": "Trigger migration" + }, + "data_description": { + "sensor_to_migrate": "Select the correct sensor for statistics migration.\nThe sensor values will be preserved, they will not be recalculated, only the unit in the long-term statistics will be changed.", + "trigger_action": "Trigger the sensor statistics migration after checking." + } } } }, diff --git a/custom_components/sws12500/utils.py b/custom_components/sws12500/utils.py index d92671c..3673417 100644 --- a/custom_components/sws12500/utils.py +++ b/custom_components/sws12500/utils.py @@ -2,18 +2,25 @@ import logging import math +from pathlib import Path +import sqlite3 from typing import Any import numpy as np from homeassistant.components import persistent_notification from homeassistant.config_entries import ConfigEntry -from homeassistant.const import UnitOfTemperature +from homeassistant.const import ( + UnitOfPrecipitationDepth, + UnitOfTemperature, + UnitOfVolumetricFlux, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.translation import async_get_translations from .const import ( AZIMUT, + DATABASE_PATH, DEV_DBG, OUTSIDE_HUMIDITY, OUTSIDE_TEMP, @@ -247,3 +254,68 @@ def chill_index(data: Any, convert: bool = False) -> UnitOfTemperature: if temp < 50 and wind > 3 else temp ) + + +def long_term_units_in_statistics_meta(): + """Get units in long term statitstics.""" + + if not Path(DATABASE_PATH).exists(): + _LOGGER.error("Database file not found: %s", DATABASE_PATH) + return False + + conn = sqlite3.connect(DATABASE_PATH) + db = conn.cursor() + + try: + db.execute(""" + SELECT statistic_id, unit_of_measurement from statistics_meta + WHERE statistic_id LIKE 'sensor.weather_station_sws%' + """) + rows = db.fetchall() + sensor_units = { + statistic_id: f"{statistic_id} ({unit})" for statistic_id, unit in rows + } + + except sqlite3.Error as e: + _LOGGER.error("Error during data migration: %s", e) + finally: + conn.close() + + return sensor_units + + +def migrate_data(sensor_id: str | None = None): + """Migrate data from mm/d to mm.""" + updated_rows = 0 + + if not Path(DATABASE_PATH).exists(): + _LOGGER.error("Database file not found: %s", DATABASE_PATH) + return False + + conn = sqlite3.connect(DATABASE_PATH) + db = conn.cursor() + + try: + _LOGGER.info(sensor_id) + db.execute( + """ + UPDATE statistics_meta + SET unit_of_measurement = 'mm' + WHERE statistic_id = ? + AND unit_of_measurement = 'mm/d'; + """, + (sensor_id,), + ) + updated_rows = db.rowcount + conn.commit() + _LOGGER.info( + "Data migration completed successfully. Updated rows: %s for %s", + updated_rows, + sensor_id, + ) + + except sqlite3.Error as e: + _LOGGER.error("Error during data migration: %s", e) + finally: + conn.close() + return updated_rows