Merge pull request #63 from schizza/migrate_data
Fixes rainfall unit inconsistency Updates daily rain sensor to use consistent measurement units and corrects device class and suggested unit. Adds database path constant and data migration function Defines DATABASE_PATH constant for database file location Introduces migrate_data function to update unit of measurement in long statistics. Adds sensor migration feature Introduces a migration step for sensor statistics Updates schema and translations to support migration Fixes data migration from mm/d to mmpull/67/head v1.6.0
commit
189777fbdb
|
|
@ -7,6 +7,7 @@ import voluptuous as vol
|
||||||
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from .utils import long_term_units_in_statistics_meta, migrate_data
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
API_ID,
|
API_ID,
|
||||||
|
|
@ -15,6 +16,7 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
INVALID_CREDENTIALS,
|
INVALID_CREDENTIALS,
|
||||||
SENSORS_TO_LOAD,
|
SENSORS_TO_LOAD,
|
||||||
|
SENSOR_TO_MIGRATE,
|
||||||
WINDY_API_KEY,
|
WINDY_API_KEY,
|
||||||
WINDY_ENABLED,
|
WINDY_ENABLED,
|
||||||
WINDY_LOGGER_ENABLED,
|
WINDY_LOGGER_ENABLED,
|
||||||
|
|
@ -42,6 +44,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
self.user_data: dict[str, str] = {}
|
self.user_data: dict[str, str] = {}
|
||||||
self.user_data_schema = {}
|
self.user_data_schema = {}
|
||||||
self.sensors: dict[str, Any] = {}
|
self.sensors: dict[str, Any] = {}
|
||||||
|
self.migrate_schema = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def config_entry(self):
|
def config_entry(self):
|
||||||
|
|
@ -90,9 +93,18 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
): bool or False,
|
): 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):
|
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(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):
|
async def async_step_basic(self, user_input=None):
|
||||||
"""Manage basic options - credentials."""
|
"""Manage basic options - credentials."""
|
||||||
|
|
@ -160,6 +172,51 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
|
|
||||||
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):
|
||||||
|
"""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):
|
class ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Sencor SWS 12500 Weather Station."""
|
"""Handle a config flow for Sencor SWS 12500 Weather Station."""
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ DOMAIN = "sws12500"
|
||||||
DEFAULT_URL = "/weatherstation/updateweatherstation.php"
|
DEFAULT_URL = "/weatherstation/updateweatherstation.php"
|
||||||
WSLINK_URL = "/data/upload.php"
|
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"
|
||||||
|
|
||||||
ICON = "mdi:weather"
|
ICON = "mdi:weather"
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@ API_KEY = "API_KEY"
|
||||||
API_ID = "API_ID"
|
API_ID = "API_ID"
|
||||||
|
|
||||||
SENSORS_TO_LOAD: Final = "sensors_to_load"
|
SENSORS_TO_LOAD: Final = "sensors_to_load"
|
||||||
|
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"
|
||||||
|
|
|
||||||
|
|
@ -149,10 +149,10 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
),
|
),
|
||||||
WeatherSensorEntityDescription(
|
WeatherSensorEntityDescription(
|
||||||
key=DAILY_RAIN,
|
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,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
|
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
icon="mdi:weather-pouring",
|
icon="mdi:weather-pouring",
|
||||||
translation_key=DAILY_RAIN,
|
translation_key=DAILY_RAIN,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
|
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Configure SWS12500 Integration",
|
"title": "Configure SWS12500 Integration",
|
||||||
"description": "Choose what do you want to configure. If basic access or resending data for Windy site",
|
"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_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."
|
"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."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@
|
||||||
"description": "Vyberte, co chcete konfigurovat. Zda přihlašovací údaje nebo nastavení pro přeposílání dat na Windy.",
|
"description": "Vyberte, co chcete konfigurovat. Zda přihlašovací údaje nebo nastavení pro přeposílání dat na Windy.",
|
||||||
"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",
|
||||||
|
"migration": "Migrace statistiky senzoru"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -72,6 +73,18 @@
|
||||||
"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://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."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,18 @@
|
||||||
"WINDY_API_KEY": "Windy API KEY obtained from https://https://api.windy.com/keys",
|
"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."
|
"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."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,25 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
import sqlite3
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
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 UnitOfTemperature
|
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
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
AZIMUT,
|
AZIMUT,
|
||||||
|
DATABASE_PATH,
|
||||||
DEV_DBG,
|
DEV_DBG,
|
||||||
OUTSIDE_HUMIDITY,
|
OUTSIDE_HUMIDITY,
|
||||||
OUTSIDE_TEMP,
|
OUTSIDE_TEMP,
|
||||||
|
|
@ -247,3 +254,68 @@ def chill_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
||||||
if temp < 50 and wind > 3
|
if temp < 50 and wind > 3
|
||||||
else temp
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue