From 33e4a7f2fe325d2abc844457942c276d250b2ea9 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Mon, 4 Mar 2024 15:44:01 +0100 Subject: [PATCH 01/20] preliminary support for multiple ports --- custom_components/hoymiles_wifi/__init__.py | 54 +++++++++ .../hoymiles_wifi/config_flow.py | 34 +++++- custom_components/hoymiles_wifi/const.py | 6 +- custom_components/hoymiles_wifi/entity.py | 23 +++- custom_components/hoymiles_wifi/sensor.py | 114 +++++++++--------- custom_components/hoymiles_wifi/strings.json | 32 ++++- .../hoymiles_wifi/translations/en.json | 32 ++++- 7 files changed, 222 insertions(+), 73 deletions(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 3b51ac7..54bf02a 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -7,8 +7,13 @@ from homeassistant.const import CONF_HOST, Platform from homeassistant.core import Config, HomeAssistant from hoymiles_wifi.inverter import Inverter +from hoymiles_wifi.utils import generate_inverter_serial_number from .const import ( + CONF_DEVICE_NUMBERS, + CONF_DTU_SERIAL_NUMBER, + CONF_INERTER_SERIAL_NUMBERS, + CONF_PV_NUMBERS, CONF_UPDATE_INTERVAL, DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS, DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS, @@ -28,6 +33,8 @@ PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.BINARY_SENSOR, Platform.BUTTON] +CURRENT_VERSION = 2 + async def async_setup(hass: HomeAssistant, config: Config): """Set up this integration using YAML is not supported.""" return True @@ -68,3 +75,50 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True + +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Migrate old entry data to the new entry schema.""" + + _LOGGER.info("Migrating entry %s to version %s", config_entry.entry_id, CURRENT_VERSION) + + # Get the current data from the config entry + data = config_entry.data.copy() + + # Check the version of the existing data + current_version = data.get("version", 1) + + # Perform migrations based on the version + if current_version == 1: + _LOGGER.info("Migrating from version 1 to version 2") + + new = {**config_entry.data} + + host = config_entry.data.get(CONF_HOST) + + inverter = Inverter(host) + app_info_data = await inverter.async_app_information_data() + if(app_info_data is None): + _LOGGER.error("Could not retrieve app information data from inverter: %s. Please ensure inverter is available!", host) + return False + + device_numbers = app_info_data.device_number + pv_numbers = app_info_data.pv_number + dtu_sn = app_info_data.dtu_serial_number + + inverter_serials = [] + + for pv_info in app_info_data.pv_info: + inverter_serials.append(generate_inverter_serial_number(pv_info.pv_serial_number)) + + new[CONF_DTU_SERIAL_NUMBER] = dtu_sn + new[CONF_INERTER_SERIAL_NUMBERS] = inverter_serials + new[CONF_DEVICE_NUMBERS] = device_numbers + new[CONF_PV_NUMBERS] = pv_numbers + + + # Update the config entry with the new data + hass.config_entries.async_update_entry(config_entry, data=new, version=2) + + _LOGGER.info("Migration of entry %s to version %s successful", config_entry.entry_id, CURRENT_VERSION) + return True + diff --git a/custom_components/hoymiles_wifi/config_flow.py b/custom_components/hoymiles_wifi/config_flow.py index 28078e9..5c5c788 100644 --- a/custom_components/hoymiles_wifi/config_flow.py +++ b/custom_components/hoymiles_wifi/config_flow.py @@ -11,8 +11,14 @@ from homeassistant.data_entry_flow import FlowResult from homeassistant.exceptions import HomeAssistantError from hoymiles_wifi.inverter import Inverter +from hoymiles_wifi.protobuf import APPInfomationData_pb2 +from hoymiles_wifi.utils import generate_inverter_serial_number from .const import ( + CONF_DEVICE_NUMBERS, + CONF_DTU_SERIAL_NUMBER, + CONF_INERTER_SERIAL_NUMBERS, + CONF_PV_NUMBERS, CONF_SENSOR_PREFIX, CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS, @@ -34,6 +40,8 @@ class HoymilesInverterConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Hoymiles Inverter config flow.""" + VERSION = 2 + async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: @@ -47,15 +55,29 @@ async def async_step_user( sensor_prefix = user_input.get(CONF_SENSOR_PREFIX, "") try: - await test_host_connection(self.hass, host) + app_info_data = await get_app_information_data(self.hass, host) except CannotConnect: errors["base"] = "cannot_connect" else: + + device_numbers = app_info_data.device_number + pv_numbers = app_info_data.pv_number + dtu_sn = app_info_data.dtu_serial_number + + inverter_serials = [] + + for pv_info in app_info_data.pv_info: + inverter_serials.append(generate_inverter_serial_number(pv_info.pv_serial_number)) + return self.async_create_entry( - title=host, data={ + title=host, data= { CONF_HOST: host, CONF_SENSOR_PREFIX: sensor_prefix, - CONF_UPDATE_INTERVAL: update_interval + CONF_UPDATE_INTERVAL: update_interval, + CONF_DTU_SERIAL_NUMBER: dtu_sn, + CONF_INERTER_SERIAL_NUMBERS: inverter_serials, + CONF_DEVICE_NUMBERS: device_numbers, + CONF_PV_NUMBERS: pv_numbers, } ) @@ -71,13 +93,15 @@ async def async_step_import(self, import_config): ) -async def test_host_connection(hass: HomeAssistant, host: str): +async def get_app_information_data(hass: HomeAssistant, host: str) -> APPInfomationData_pb2.APPInfoDataResDTO: """Test if the host is reachable and is actually a Hoymiles HMS device.""" inverter = Inverter(host) - response = await inverter.async_heartbeat() + response = await inverter.async_app_information_data() if(response is None): raise CannotConnect + return response + class CannotConnect(HomeAssistantError): """Error to indicate we cannot connect.""" diff --git a/custom_components/hoymiles_wifi/const.py b/custom_components/hoymiles_wifi/const.py index dedeabf..eb2a7f9 100644 --- a/custom_components/hoymiles_wifi/const.py +++ b/custom_components/hoymiles_wifi/const.py @@ -1,6 +1,6 @@ """Constants for the Hoymiles integration.""" DOMAIN = "hoymiles_wifi" -NAME = "Hoymiles HMS-XXXXW-T2" +NAME = "Hoymiles HMS-XXXXW-2T" DOMAIN = "hoymiles_wifi" DOMAIN_DATA = f"{DOMAIN}_data" VERSION = "0.1.2" @@ -9,6 +9,10 @@ CONF_UPDATE_INTERVAL = "update_interval" CONF_SENSOR_PREFIX = "sensor_prefix" +CONF_DTU_SERIAL_NUMBER = "dtu_serial_number" +CONF_INERTER_SERIAL_NUMBERS = "inverter_serial_numbers" +CONF_DEVICE_NUMBERS = "device_numbers" +CONF_PV_NUMBERS = "pv_numbers" DEFAULT_UPDATE_INTERVAL_SECONDS = 35 MIN_UPDATE_INTERVAL_SECONDS = 35 diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index 6fedcea..55c8fb4 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -7,12 +7,24 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity import hoymiles_wifi.utils -from .const import CONF_SENSOR_PREFIX, DOMAIN +from .const import ( + CONF_DTU_SERIAL_NUMBER, + CONF_INERTER_SERIAL_NUMBERS, + CONF_SENSOR_PREFIX, + DOMAIN, +) from .coordinator import HoymilesDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) +def safe_get_element(lst, index, default): + try: + return lst[index] + except IndexError: + return default + + class HoymilesEntity(Entity): """Base class for Hoymiles entities.""" @@ -29,16 +41,17 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): self._attr_unique_id = f"hoymiles_{config_entry.entry_id}_{description.key}" serial_number = "" - device_name = "Hoymiles HMS-XXXXW-T2" - device_model="HMS-XXXXW-T2" + device_name = "Hoymiles HMS-XXXXW-2T" + device_model="HMS-XXXXW-2T" device_name_suffix = "" if hasattr(self.entity_description, "is_dtu_sensor") and self.entity_description.is_dtu_sensor is True: - serial_number = self._dtu_serial_number + serial_number = config_entry.data.get(CONF_DTU_SERIAL_NUMBER, "") device_name_suffix = " DTU" else: - serial_number = self._inverter_serial_number + serial_numbers = config_entry.data.get(CONF_INERTER_SERIAL_NUMBERS, []) + serial_number = safe_get_element(serial_numbers, 0, "") device_name += self._sensor_prefix diff --git a/custom_components/hoymiles_wifi/sensor.py b/custom_components/hoymiles_wifi/sensor.py index cff6d32..0618f6e 100644 --- a/custom_components/hoymiles_wifi/sensor.py +++ b/custom_components/hoymiles_wifi/sensor.py @@ -1,5 +1,6 @@ """Support for Hoymiles sensors.""" +import dataclasses from dataclasses import dataclass import datetime from enum import Enum @@ -28,6 +29,8 @@ import hoymiles_wifi.utils from .const import ( + CONF_DEVICE_NUMBERS, + CONF_PV_NUMBERS, DOMAIN, FCTN_GENERATE_DTU_VERSION_STRING, FCTN_GENERATE_INVERTER_HW_VERSION_STRING, @@ -72,7 +75,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): HOYMILES_SENSORS = [ HoymilesSensorEntityDescription( - key="sgs_data[0].active_power", + key="sgs_data[].active_power", translation_key="ac_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, @@ -88,7 +91,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): reset_at_midnight=True, ), HoymilesSensorEntityDescription( - key="sgs_data[0].voltage", + key="sgs_data[].voltage", translation_key="grid_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, @@ -96,7 +99,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): conversion_factor=0.1, ), HoymilesSensorEntityDescription( - key="sgs_data[0].frequency", + key="sgs_data[].frequency", translation_key="grid_frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, device_class=SensorDeviceClass.FREQUENCY, @@ -104,7 +107,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): conversion_factor=0.01, ), HoymilesSensorEntityDescription( - key="sgs_data[0].power_factor", + key="sgs_data[].power_factor", translation_key="inverter_power_factor", native_unit_of_measurement=PERCENTAGE, device_class=SensorDeviceClass.POWER_FACTOR, @@ -112,7 +115,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): conversion_factor=0.1, ), HoymilesSensorEntityDescription( - key="sgs_data[0].temperature", + key="sgs_data[].temperature", translation_key="inverter_temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, @@ -120,78 +123,39 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): conversion_factor=0.1, ), HoymilesSensorEntityDescription( - key="pv_data[0].voltage", - translation_key="port_1_dc_voltage", + key="pv_data[].voltage", + translation_key="port__dc_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, conversion_factor=0.1, ), HoymilesSensorEntityDescription( - key="pv_data[0].current", - translation_key="port_1_dc_current", + key="pv_data[].current", + translation_key="port__dc_current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, conversion_factor=0.01, ), HoymilesSensorEntityDescription( - key="pv_data[0].power", - translation_key="port_1_dc_power", + key="pv_data[].power", + translation_key="port__dc_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, conversion_factor=0.1, ), HoymilesSensorEntityDescription( - key="pv_data[0].energy_total", - translation_key="port_1_dc_total_energy", + key="pv_data[].energy_total", + translation_key="port__dc_total_energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), HoymilesSensorEntityDescription( - key="pv_data[0].energy_daily", - translation_key="port_1_dc_daily_energy", - native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - reset_at_midnight=True, - ), - HoymilesSensorEntityDescription( - key="pv_data[1].voltage", - translation_key="port_2_dc_voltage", - native_unit_of_measurement=UnitOfElectricPotential.VOLT, - device_class=SensorDeviceClass.VOLTAGE, - state_class=SensorStateClass.MEASUREMENT, - conversion_factor=0.1, - ), - HoymilesSensorEntityDescription( - key="pv_data[1].current", - translation_key="port_2_dc_current", - native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, - device_class=SensorDeviceClass.CURRENT, - state_class=SensorStateClass.MEASUREMENT, - conversion_factor=0.01, - ), - HoymilesSensorEntityDescription( - key="pv_data[1].power", - translation_key="port_2_dc_power", - native_unit_of_measurement=UnitOfPower.WATT, - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - conversion_factor=0.1, - ), - HoymilesSensorEntityDescription( - key="pv_data[1].energy_total", - translation_key="port_2_dc_total_energy", - native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - ), - HoymilesSensorEntityDescription( - key="pv_data[1].energy_daily", - translation_key="port_2_dc_daily_energy", + key="pv_data[].energy_daily", + translation_key="port__dc_daily_energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, @@ -256,14 +220,14 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): ), HoymilesSensorEntityDescription( - key = "pv_info[0].pv_sw_version", + key = "pv_info[].pv_sw_version", translation_key="pv_sw_version", entity_category=EntityCategory.DIAGNOSTIC, version_translation_function=FCTN_GENERATE_INVERTER_SW_VERSION_STRING, version_prefix="V" ), HoymilesSensorEntityDescription( - key = "pv_info[0].pv_hw_version", + key = "pv_info[].pv_hw_version", translation_key="pv_hw_version", entity_category=EntityCategory.DIAGNOSTIC, version_translation_function=FCTN_GENERATE_INVERTER_HW_VERSION_STRING, @@ -293,26 +257,56 @@ async def async_setup_entry( data_coordinator = hass_data[HASS_DATA_COORDINATOR] config_coordinator = hass_data[HASS_CONFIG_COORDINATOR] app_info_coordinator = hass_data[HASS_APP_INFO_COORDINATOR] + device_numbers = entry.data[CONF_DEVICE_NUMBERS] + pv_numbers = entry.data[CONF_PV_NUMBERS] sensors = [] # Real Data Sensors for description in HOYMILES_SENSORS: device_class = description.device_class if device_class == SensorDeviceClass.ENERGY: - energy_sensor = HoymilesEnergySensorEntity(entry, description, data_coordinator) - sensors.append(energy_sensor) + class_name = HoymilesEnergySensorEntity else: - sensors.append(HoymilesDataSensorEntity(entry, description, data_coordinator)) + class_name = HoymilesDataSensorEntity + sensor_entities = get_sensors_for_description(entry, description, data_coordinator, class_name, device_numbers, pv_numbers) + sensors.extend(sensor_entities) + + # Diagnostic Sensors for description in CONFIG_DIAGNOSTIC_SENSORS: sensors.append(HoymilesDiagnosticSensorEntity(entry, description, config_coordinator)) for description in APP_INFO_SENSORS: - sensors.append(HoymilesDataSensorEntity(entry, description, app_info_coordinator)) + sensor_entities = get_sensors_for_description(entry, description, app_info_coordinator, HoymilesDataSensorEntity, device_numbers, pv_numbers) + sensors.extend(sensor_entities) async_add_entities(sensors) +def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEntityDescription, coordinator: HoymilesCoordinatorEntity, class_name: SensorEntity, device_numbers: int, pv_numbers: int) -> list[SensorEntity]: + """Get sensors for the given description.""" + + sensors = [] + + if "" in description.key: + for device_count in range(device_numbers): + new_key = description.key.replace("", str(device_count)) + updated_description = dataclasses.replace(description, key=new_key) + sensor = class_name(config_entry, updated_description, coordinator) + sensors.append(sensor) + elif "" in description.key: + for pv_count in range(pv_numbers): + new_key = str(description.key).replace("", str(pv_count)) + new_translation_key = description.translation_key.replace("", str(pv_count+1)) + updated_description = dataclasses.replace(description, key=new_key, translation_key=new_translation_key) + sensor = class_name(config_entry, updated_description, coordinator) + sensors.append(sensor) + else: + sensor = class_name(config_entry, description, coordinator) + sensors.append(sensor) + + return sensors + class HoymilesDataSensorEntity(HoymilesCoordinatorEntity, SensorEntity): """Represents a sensor entity for Hoymiles data.""" diff --git a/custom_components/hoymiles_wifi/strings.json b/custom_components/hoymiles_wifi/strings.json index 3fdbc0b..17268c8 100644 --- a/custom_components/hoymiles_wifi/strings.json +++ b/custom_components/hoymiles_wifi/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Hoymiles HMS-XXXXW-T2 connection", + "title": "Hoymiles HMS-XXXXW-2T connection", "description": "If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi", "data": { "host": "[%key:common::config_flow::data::host%]", @@ -78,6 +78,36 @@ "port_2_dc_daily_energy": { "name": "Port 2 DC daily energy" }, + "port_3_dc_voltage": { + "name": "Port 3 DC voltage" + }, + "port_3_dc_current": { + "name": "Port 3 DC current" + }, + "port_3_dc_power": { + "name": "Port 3 DC power" + }, + "port_3_dc_total_energy": { + "name": "Port 3 DC total energy" + }, + "port_3_dc_daily_energy": { + "name": "Port 3 DC daily energy" + }, + "port_4_dc_voltage": { + "name": "Port 4 DC voltage" + }, + "port_4_dc_current": { + "name": "Port 4 DC current" + }, + "port_4_dc_power": { + "name": "Port 4 DC power" + }, + "port_4_dc_total_energy": { + "name": "Port 4 DC total energy" + }, + "port_4_dc_daily_energy": { + "name": "Port 4 DC daily energy" + }, "wifi_ssid": { "name": "Wi-Fi SSID" }, diff --git a/custom_components/hoymiles_wifi/translations/en.json b/custom_components/hoymiles_wifi/translations/en.json index 29792a5..97c9619 100644 --- a/custom_components/hoymiles_wifi/translations/en.json +++ b/custom_components/hoymiles_wifi/translations/en.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Hoymiles HMS-XXXXW-T2 connection", + "title": "Hoymiles HMS-XXXXW-2T connection", "description": "If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi", "data": { "host": "Host", @@ -78,6 +78,36 @@ "port_2_dc_daily_energy": { "name": "Port 2 DC daily energy" }, + "port_3_dc_voltage": { + "name": "Port 3 DC voltage" + }, + "port_3_dc_current": { + "name": "Port 3 DC current" + }, + "port_3_dc_power": { + "name": "Port 3 DC power" + }, + "port_3_dc_total_energy": { + "name": "Port 3 DC total energy" + }, + "port_3_dc_daily_energy": { + "name": "Port 3 DC daily energy" + }, + "port_4_dc_voltage": { + "name": "Port 4 DC voltage" + }, + "port_4_dc_current": { + "name": "Port 4 DC current" + }, + "port_4_dc_power": { + "name": "Port 4 DC power" + }, + "port_4_dc_total_energy": { + "name": "Port 4 DC total energy" + }, + "port_4_dc_daily_energy": { + "name": "Port 4 DC daily energy" + }, "wifi_ssid": { "name": "Wi-Fi SSID" }, From 6f6151b9191dd8c269e41f256b6eb146dc143d48 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Wed, 6 Mar 2024 07:35:15 +0100 Subject: [PATCH 02/20] preliminary support for multiple inverters --- custom_components/hoymiles_wifi/__init__.py | 40 +++++++++++-------- .../hoymiles_wifi/binary_sensor.py | 2 + custom_components/hoymiles_wifi/button.py | 2 + .../hoymiles_wifi/config_flow.py | 35 +++++++++------- custom_components/hoymiles_wifi/const.py | 5 +-- custom_components/hoymiles_wifi/entity.py | 13 ++---- custom_components/hoymiles_wifi/number.py | 2 + custom_components/hoymiles_wifi/sensor.py | 33 ++++++++------- 8 files changed, 73 insertions(+), 59 deletions(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 54bf02a..0647549 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -10,10 +10,9 @@ from hoymiles_wifi.utils import generate_inverter_serial_number from .const import ( - CONF_DEVICE_NUMBERS, CONF_DTU_SERIAL_NUMBER, - CONF_INERTER_SERIAL_NUMBERS, - CONF_PV_NUMBERS, + CONF_INVERTERS, + CONF_PORTS, CONF_UPDATE_INTERVAL, DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS, DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS, @@ -96,27 +95,34 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> host = config_entry.data.get(CONF_HOST) inverter = Inverter(host) - app_info_data = await inverter.async_app_information_data() - if(app_info_data is None): - _LOGGER.error("Could not retrieve app information data from inverter: %s. Please ensure inverter is available!", host) + + real_data = await inverter.async_get_real_data_new() + if(real_data is None): + _LOGGER.error("Could not retrieve real data information data from inverter: %s. Please ensure inverter is available!", host) return False - device_numbers = app_info_data.device_number - pv_numbers = app_info_data.pv_number - dtu_sn = app_info_data.dtu_serial_number + dtu_sn = real_data.device_serial_number - inverter_serials = [] + inverters = [] - for pv_info in app_info_data.pv_info: - inverter_serials.append(generate_inverter_serial_number(pv_info.pv_serial_number)) + for sgs_data in real_data.sgs_data: + inverter_serial = generate_inverter_serial_number(sgs_data.serial_number) + inverters.append(inverter_serial) - new[CONF_DTU_SERIAL_NUMBER] = dtu_sn - new[CONF_INERTER_SERIAL_NUMBERS] = inverter_serials - new[CONF_DEVICE_NUMBERS] = device_numbers - new[CONF_PV_NUMBERS] = pv_numbers + ports = [] + for pv_data in real_data.pv_data: + inverter_serial = generate_inverter_serial_number(pv_data.serial_number) + port_number = pv_data.port_number + ports.append({ + "inverter_serial_number": inverter_serial, + "port_number": port_number + }) + new[CONF_DTU_SERIAL_NUMBER] = dtu_sn + new[CONF_INVERTERS] = inverters + new[CONF_PORTS] = ports - # Update the config entry with the new data + # Update the config entry with the new data hass.config_entries.async_update_entry(config_entry, data=new, version=2) _LOGGER.info("Migration of entry %s to version %s successful", config_entry.entry_id, CURRENT_VERSION) diff --git a/custom_components/hoymiles_wifi/binary_sensor.py b/custom_components/hoymiles_wifi/binary_sensor.py index b11b27f..db26cb7 100644 --- a/custom_components/hoymiles_wifi/binary_sensor.py +++ b/custom_components/hoymiles_wifi/binary_sensor.py @@ -23,6 +23,8 @@ class HoymilesBinarySensorEntityDescription(BinarySensorEntityDescription): """Describes Homiles binary sensor entity.""" is_dtu_sensor: bool = False + serial_number: str = None + BINARY_SENSORS = ( diff --git a/custom_components/hoymiles_wifi/button.py b/custom_components/hoymiles_wifi/button.py index dae34e4..bd673de 100644 --- a/custom_components/hoymiles_wifi/button.py +++ b/custom_components/hoymiles_wifi/button.py @@ -21,6 +21,8 @@ class HoymilesButtonEntityDescription(ButtonEntityDescription): """Class to describe a Hoymiles Button entity.""" is_dtu_sensor: bool = False + serial_number: str = None + BUTTONS: tuple[HoymilesButtonEntityDescription, ...] = ( diff --git a/custom_components/hoymiles_wifi/config_flow.py b/custom_components/hoymiles_wifi/config_flow.py index 5c5c788..17251fd 100644 --- a/custom_components/hoymiles_wifi/config_flow.py +++ b/custom_components/hoymiles_wifi/config_flow.py @@ -15,10 +15,9 @@ from hoymiles_wifi.utils import generate_inverter_serial_number from .const import ( - CONF_DEVICE_NUMBERS, CONF_DTU_SERIAL_NUMBER, - CONF_INERTER_SERIAL_NUMBERS, - CONF_PV_NUMBERS, + CONF_INVERTERS, + CONF_PORTS, CONF_SENSOR_PREFIX, CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS, @@ -55,19 +54,26 @@ async def async_step_user( sensor_prefix = user_input.get(CONF_SENSOR_PREFIX, "") try: - app_info_data = await get_app_information_data(self.hass, host) + real_data = await get_real_data_new.async_get_real_data_new() except CannotConnect: errors["base"] = "cannot_connect" else: - device_numbers = app_info_data.device_number - pv_numbers = app_info_data.pv_number - dtu_sn = app_info_data.dtu_serial_number + dtu_sn = real_data.device_serial_number + inverters = [] - inverter_serials = [] + for sgs_data in real_data.sgs_data: + inverter_serial = generate_inverter_serial_number(sgs_data.serial_number) + inverters.append(inverter_serial) - for pv_info in app_info_data.pv_info: - inverter_serials.append(generate_inverter_serial_number(pv_info.pv_serial_number)) + ports = [] + for pv_data in real_data.pv_data: + inverter_serial = generate_inverter_serial_number(pv_data.serial_number) + port_number = pv_data.port_number + ports.append({ + "inverter_serial_number": inverter_serial, + "port_number": port_number + }) return self.async_create_entry( title=host, data= { @@ -75,9 +81,8 @@ async def async_step_user( CONF_SENSOR_PREFIX: sensor_prefix, CONF_UPDATE_INTERVAL: update_interval, CONF_DTU_SERIAL_NUMBER: dtu_sn, - CONF_INERTER_SERIAL_NUMBERS: inverter_serials, - CONF_DEVICE_NUMBERS: device_numbers, - CONF_PV_NUMBERS: pv_numbers, + CONF_INVERTERS: inverters, + CONF_PORTS: ports, } ) @@ -93,11 +98,11 @@ async def async_step_import(self, import_config): ) -async def get_app_information_data(hass: HomeAssistant, host: str) -> APPInfomationData_pb2.APPInfoDataResDTO: +async def get_real_data_new(hass: HomeAssistant, host: str) -> APPInfomationData_pb2.APPInfoDataResDTO: """Test if the host is reachable and is actually a Hoymiles HMS device.""" inverter = Inverter(host) - response = await inverter.async_app_information_data() + response = await inverter.get_real_data_new() if(response is None): raise CannotConnect return response diff --git a/custom_components/hoymiles_wifi/const.py b/custom_components/hoymiles_wifi/const.py index eb2a7f9..a661b76 100644 --- a/custom_components/hoymiles_wifi/const.py +++ b/custom_components/hoymiles_wifi/const.py @@ -10,9 +10,8 @@ CONF_UPDATE_INTERVAL = "update_interval" CONF_SENSOR_PREFIX = "sensor_prefix" CONF_DTU_SERIAL_NUMBER = "dtu_serial_number" -CONF_INERTER_SERIAL_NUMBERS = "inverter_serial_numbers" -CONF_DEVICE_NUMBERS = "device_numbers" -CONF_PV_NUMBERS = "pv_numbers" +CONF_INVERTERS = "inverters" +CONF_PORTS = "ports" DEFAULT_UPDATE_INTERVAL_SECONDS = 35 MIN_UPDATE_INTERVAL_SECONDS = 35 diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index 55c8fb4..d373405 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -7,12 +7,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity import hoymiles_wifi.utils -from .const import ( - CONF_DTU_SERIAL_NUMBER, - CONF_INERTER_SERIAL_NUMBERS, - CONF_SENSOR_PREFIX, - DOMAIN, -) +from .const import CONF_DTU_SERIAL_NUMBER, CONF_SENSOR_PREFIX, DOMAIN from .coordinator import HoymilesDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -47,11 +42,9 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): if hasattr(self.entity_description, "is_dtu_sensor") and self.entity_description.is_dtu_sensor is True: - serial_number = config_entry.data.get(CONF_DTU_SERIAL_NUMBER, "") device_name_suffix = " DTU" - else: - serial_numbers = config_entry.data.get(CONF_INERTER_SERIAL_NUMBERS, []) - serial_number = safe_get_element(serial_numbers, 0, "") + + serial_number = self.entity_description.serial_number device_name += self._sensor_prefix diff --git a/custom_components/hoymiles_wifi/number.py b/custom_components/hoymiles_wifi/number.py index 1cabfde..d14c02f 100644 --- a/custom_components/hoymiles_wifi/number.py +++ b/custom_components/hoymiles_wifi/number.py @@ -32,6 +32,8 @@ class HoymilesNumberSensorEntityDescription(NumberEntityDescription): set_action: SetAction = None conversion_factor: float = None + serial_number: str = None + CONFIG_CONTROL_ENTITIES = ( diff --git a/custom_components/hoymiles_wifi/sensor.py b/custom_components/hoymiles_wifi/sensor.py index 0618f6e..1fd9201 100644 --- a/custom_components/hoymiles_wifi/sensor.py +++ b/custom_components/hoymiles_wifi/sensor.py @@ -29,8 +29,8 @@ import hoymiles_wifi.utils from .const import ( - CONF_DEVICE_NUMBERS, - CONF_PV_NUMBERS, + CONF_INVERTERS, + CONF_PORTS, DOMAIN, FCTN_GENERATE_DTU_VERSION_STRING, FCTN_GENERATE_INVERTER_HW_VERSION_STRING, @@ -62,6 +62,7 @@ class HoymilesSensorEntityDescription(SensorEntityDescription): version_translation_function: str = None version_prefix: str = None is_dtu_sensor: bool = False + serial_number: str = None @dataclass(frozen=True) @@ -71,6 +72,8 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): conversion: ConversionAction = None separator: str = None is_dtu_sensor: bool = False + serial_number: str = None + HOYMILES_SENSORS = [ @@ -257,8 +260,8 @@ async def async_setup_entry( data_coordinator = hass_data[HASS_DATA_COORDINATOR] config_coordinator = hass_data[HASS_CONFIG_COORDINATOR] app_info_coordinator = hass_data[HASS_APP_INFO_COORDINATOR] - device_numbers = entry.data[CONF_DEVICE_NUMBERS] - pv_numbers = entry.data[CONF_PV_NUMBERS] + inverters = entry.data[CONF_INVERTERS] + ports = entry.data[CONF_PORTS] sensors = [] # Real Data Sensors @@ -268,7 +271,7 @@ async def async_setup_entry( class_name = HoymilesEnergySensorEntity else: class_name = HoymilesDataSensorEntity - sensor_entities = get_sensors_for_description(entry, description, data_coordinator, class_name, device_numbers, pv_numbers) + sensor_entities = get_sensors_for_description(entry, description, data_coordinator, class_name, inverters, ports) sensors.extend(sensor_entities) @@ -278,27 +281,29 @@ async def async_setup_entry( sensors.append(HoymilesDiagnosticSensorEntity(entry, description, config_coordinator)) for description in APP_INFO_SENSORS: - sensor_entities = get_sensors_for_description(entry, description, app_info_coordinator, HoymilesDataSensorEntity, device_numbers, pv_numbers) + sensor_entities = get_sensors_for_description(entry, description, app_info_coordinator, HoymilesDataSensorEntity, inverters, ports) sensors.extend(sensor_entities) async_add_entities(sensors) -def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEntityDescription, coordinator: HoymilesCoordinatorEntity, class_name: SensorEntity, device_numbers: int, pv_numbers: int) -> list[SensorEntity]: +def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEntityDescription, coordinator: HoymilesCoordinatorEntity, class_name: SensorEntity, inverters: list, ports: list) -> list[SensorEntity]: """Get sensors for the given description.""" sensors = [] if "" in description.key: - for device_count in range(device_numbers): - new_key = description.key.replace("", str(device_count)) - updated_description = dataclasses.replace(description, key=new_key) + for index, inverter_serial in enumerate(inverters): + new_key = description.key.replace("", str(index)) + updated_description = dataclasses.replace(description, key=new_key, serial_number = inverter_serial) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) elif "" in description.key: - for pv_count in range(pv_numbers): - new_key = str(description.key).replace("", str(pv_count)) - new_translation_key = description.translation_key.replace("", str(pv_count+1)) - updated_description = dataclasses.replace(description, key=new_key, translation_key=new_translation_key) + for index, port in enumerate(ports): + inverter_serial = port["inverter_serial_number"] + port_number = port["port_number"] + new_key = str(description.key).replace("", str(index)) + new_translation_key = description.translation_key.replace("", str(port_number)) + updated_description = dataclasses.replace(description, key=new_key, translation_key=new_translation_key, serial_number = inverter_serial) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) else: From c4d908a830b901d925a1e44ac6eecf7055bc78e0 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Wed, 6 Mar 2024 07:39:37 +0100 Subject: [PATCH 03/20] rename custom component to "Hoymiles" --- README.md | 10 +++++----- custom_components/hoymiles_wifi/manifest.json | 2 +- hacs.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 03729a6..41212b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# HMS-XXXXW-2T for Home Assistant -This Home Assistant custom component utilizes the [hoymiles-wifi](https://github.com/suaveolent/hoymiles-wifi) Python library, allowing seamless integration with Hoymiles HMS microinverters, specifically designed for the HMS-XXXXW-2T series. +# Hoymiles for Home Assistant +This Home Assistant custom component utilizes the [hoymiles-wifi](https://github.com/suaveolent/hoymiles-wifi) Python library, allowing seamless integration with Hoymiles HMS microinverters, specifically designed for the HMS-XXXXW-2T series and Hoymiles DTU Wlite. **Disclaimer: This custom component is an independent project and is not affiliated with Hoymiles. It has been developed to provide Home Assistant users with tools for interacting with Hoymiles HMS-XXXXW-2T series micro-inverters featuring integrated WiFi DTU. Any trademarks or product names mentioned are the property of their respective owners.** @@ -10,7 +10,7 @@ This Home Assistant custom component utilizes the [hoymiles-wifi](https://github The custom component was successfully tested with: - Hoymiles HMS-800W-2T - - Hoymiles DTU WLite + - Hoymiles DTU Wlite ## Installation @@ -22,10 +22,10 @@ The custom component was successfully tested with: - **Category:** Integration 5. Click "Add" -6. Click on the `Hoymiles HMS-XXXXW-2T` integration. +6. Click on the `Hoymiles` integration. 7. Click "DOWNLOAD" 8. Navigate to "Settings" - "Devices & Services" -9. Click "ADD INTEGRATION" and select the `Hoymiles HMS-XXXXW-2T` integration. +9. Click "ADD INTEGRATION" and select the `Hoymiles` integration. ### Option 2: Manual Installation diff --git a/custom_components/hoymiles_wifi/manifest.json b/custom_components/hoymiles_wifi/manifest.json index d9e0d25..101041f 100644 --- a/custom_components/hoymiles_wifi/manifest.json +++ b/custom_components/hoymiles_wifi/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://github.com/suaveolent/ha-hoymiles-wifi", "domain": "hoymiles_wifi", "iot_class": "local_polling", - "name": "Hoymiles HMS-XXXXW-2T", + "name": "Hoymiles", "requirements": ["hoymiles-wifi==0.1.7"], "version": "0.1.4" } diff --git a/hacs.json b/hacs.json index 069184a..7832e32 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { - "name": "Hoymiles HMS-XXXXW-T2", + "name": "Hoymiles", "render_readme": true, "iot_class": "local_polling" } From c1cbef3815dfb366e738b9dd16423524fd10d6fc Mon Sep 17 00:00:00 2001 From: suaveolent Date: Thu, 7 Mar 2024 15:22:08 +0100 Subject: [PATCH 04/20] dynamically assign serial number --- .../hoymiles_wifi/binary_sensor.py | 7 +++-- custom_components/hoymiles_wifi/button.py | 11 ++++++-- custom_components/hoymiles_wifi/entity.py | 8 ++---- custom_components/hoymiles_wifi/number.py | 9 ++++-- custom_components/hoymiles_wifi/sensor.py | 28 +++++++++---------- 5 files changed, 35 insertions(+), 28 deletions(-) diff --git a/custom_components/hoymiles_wifi/binary_sensor.py b/custom_components/hoymiles_wifi/binary_sensor.py index db26cb7..68ba0cd 100644 --- a/custom_components/hoymiles_wifi/binary_sensor.py +++ b/custom_components/hoymiles_wifi/binary_sensor.py @@ -1,4 +1,5 @@ """Contains binary sensor entities for Hoymiles WiFi integration.""" +import dataclasses from dataclasses import dataclass import logging @@ -13,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from hoymiles_wifi.inverter import NetworkState -from .const import DOMAIN, HASS_DATA_COORDINATOR +from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN, HASS_DATA_COORDINATOR from .entity import HoymilesCoordinatorEntity _LOGGER = logging.getLogger(__name__) @@ -45,11 +46,13 @@ async def async_setup_entry( """Set up sensor platform.""" hass_data = hass.data[DOMAIN][entry.entry_id] data_coordinator = hass_data[HASS_DATA_COORDINATOR] + dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] sensors = [] for description in BINARY_SENSORS: - sensors.append(HoymilesInverterSensorEntity(entry, description, data_coordinator)) + updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) + sensors.append(HoymilesInverterSensorEntity(entry, updated_description, data_coordinator)) async_add_entities(sensors) diff --git a/custom_components/hoymiles_wifi/button.py b/custom_components/hoymiles_wifi/button.py index bd673de..b758673 100644 --- a/custom_components/hoymiles_wifi/button.py +++ b/custom_components/hoymiles_wifi/button.py @@ -1,5 +1,6 @@ """Support for Hoymiles buttons.""" +import dataclasses from dataclasses import dataclass from homeassistant.components.button import ( @@ -12,7 +13,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from hoymiles_wifi.inverter import Inverter -from .const import DOMAIN, HASS_INVERTER +from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN, HASS_INVERTER from .entity import HoymilesEntity @@ -30,17 +31,19 @@ class HoymilesButtonEntityDescription(ButtonEntityDescription): key="async_restart", translation_key="restart", device_class = ButtonDeviceClass.RESTART, - is_dtu_sensor = True + is_dtu_sensor = True, ), HoymilesButtonEntityDescription( key="async_turn_off", translation_key="turn_off", icon="mdi:power-off", + is_dtu_sensor = True, ), HoymilesButtonEntityDescription( key="async_turn_on", translation_key="turn_on", icon="mdi:power-on", + is_dtu_sensor = True, ), ) @@ -52,10 +55,12 @@ async def async_setup_entry( """Set up the Hoymiles number entities.""" hass_data = hass.data[DOMAIN][entry.entry_id] inverter = hass_data[HASS_INVERTER] + dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] buttons = [] for description in BUTTONS: - buttons.append(HoymilesButtonEntity(entry, description, inverter) + updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) + buttons.append(HoymilesButtonEntity(entry, updated_description, inverter) ) async_add_entities(buttons) diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index d373405..0a49d11 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -24,8 +24,6 @@ class HoymilesEntity(Entity): """Base class for Hoymiles entities.""" _attr_has_entity_name = True - _inverter_serial_number = "" - _dtu_serial_number = "" def __init__(self, config_entry: ConfigEntry, description: EntityDescription): """Initialize the Hoymiles entity.""" @@ -35,7 +33,6 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): self._sensor_prefix = f' {config_entry.data.get(CONF_SENSOR_PREFIX)} ' if config_entry.data.get(CONF_SENSOR_PREFIX) else "" self._attr_unique_id = f"hoymiles_{config_entry.entry_id}_{description.key}" - serial_number = "" device_name = "Hoymiles HMS-XXXXW-2T" device_model="HMS-XXXXW-2T" device_name_suffix = "" @@ -44,15 +41,14 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): if hasattr(self.entity_description, "is_dtu_sensor") and self.entity_description.is_dtu_sensor is True: device_name_suffix = " DTU" - serial_number = self.entity_description.serial_number - device_name += self._sensor_prefix + print(f"Name: {self.entity_description.key}, Serial number: {self.entity_description.serial_number}, is dtu: {self.entity_description.is_dtu_sensor}") device_info = DeviceInfo( identifiers={(DOMAIN, self._config_entry.entry_id + device_name_suffix)}, name = device_name + device_name_suffix, manufacturer="Hoymiles", - serial_number= serial_number, + serial_number= self.entity_description.serial_number, model = device_model, ) diff --git a/custom_components/hoymiles_wifi/number.py b/custom_components/hoymiles_wifi/number.py index d14c02f..e396dfe 100644 --- a/custom_components/hoymiles_wifi/number.py +++ b/custom_components/hoymiles_wifi/number.py @@ -1,4 +1,5 @@ """Support for Hoymiles number sensors.""" +import dataclasses from dataclasses import dataclass from enum import Enum import logging @@ -13,7 +14,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, HASS_CONFIG_COORDINATOR +from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN, HASS_CONFIG_COORDINATOR from .entity import HoymilesCoordinatorEntity @@ -33,7 +34,7 @@ class HoymilesNumberSensorEntityDescription(NumberEntityDescription): set_action: SetAction = None conversion_factor: float = None serial_number: str = None - + is_dtu_sensor: bool = False CONFIG_CONTROL_ENTITIES = ( @@ -58,10 +59,12 @@ async def async_setup_entry( """Set up the Hoymiles number entities.""" hass_data = hass.data[DOMAIN][entry.entry_id] config_coordinator = hass_data[HASS_CONFIG_COORDINATOR] + dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] sensors = [] for description in CONFIG_CONTROL_ENTITIES: - sensors.append(HoymilesNumberEntity(entry, description, config_coordinator)) + updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) + sensors.append(HoymilesNumberEntity(entry, updated_description, config_coordinator)) async_add_entities(sensors) diff --git a/custom_components/hoymiles_wifi/sensor.py b/custom_components/hoymiles_wifi/sensor.py index 1fd9201..8d7cc0e 100644 --- a/custom_components/hoymiles_wifi/sensor.py +++ b/custom_components/hoymiles_wifi/sensor.py @@ -29,6 +29,7 @@ import hoymiles_wifi.utils from .const import ( + CONF_DTU_SERIAL_NUMBER, CONF_INVERTERS, CONF_PORTS, DOMAIN, @@ -61,8 +62,8 @@ class HoymilesSensorEntityDescription(SensorEntityDescription): reset_at_midnight: bool = False version_translation_function: str = None version_prefix: str = None - is_dtu_sensor: bool = False serial_number: str = None + is_dtu_sensor: bool = False @dataclass(frozen=True) @@ -71,8 +72,8 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): conversion: ConversionAction = None separator: str = None - is_dtu_sensor: bool = False serial_number: str = None + is_dtu_sensor: bool = False @@ -92,6 +93,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, reset_at_midnight=True, + is_dtu_sensor=True, ), HoymilesSensorEntityDescription( key="sgs_data[].voltage", @@ -220,7 +222,6 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): version_translation_function=FCTN_GENERATE_DTU_VERSION_STRING, version_prefix="H", is_dtu_sensor=True, - ), HoymilesSensorEntityDescription( key = "pv_info[].pv_sw_version", @@ -247,8 +248,6 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): ) - - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -260,6 +259,7 @@ async def async_setup_entry( data_coordinator = hass_data[HASS_DATA_COORDINATOR] config_coordinator = hass_data[HASS_CONFIG_COORDINATOR] app_info_coordinator = hass_data[HASS_APP_INFO_COORDINATOR] + dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] inverters = entry.data[CONF_INVERTERS] ports = entry.data[CONF_PORTS] sensors = [] @@ -271,22 +271,21 @@ async def async_setup_entry( class_name = HoymilesEnergySensorEntity else: class_name = HoymilesDataSensorEntity - sensor_entities = get_sensors_for_description(entry, description, data_coordinator, class_name, inverters, ports) + sensor_entities = get_sensors_for_description(entry, description, data_coordinator, class_name, dtu_serial_number, inverters, ports) sensors.extend(sensor_entities) - - # Diagnostic Sensors for description in CONFIG_DIAGNOSTIC_SENSORS: - sensors.append(HoymilesDiagnosticSensorEntity(entry, description, config_coordinator)) + sensor_entities = get_sensors_for_description(entry, description, config_coordinator, HoymilesDiagnosticSensorEntity, dtu_serial_number, inverters, ports) + sensors.extend(sensor_entities) for description in APP_INFO_SENSORS: - sensor_entities = get_sensors_for_description(entry, description, app_info_coordinator, HoymilesDataSensorEntity, inverters, ports) + sensor_entities = get_sensors_for_description(entry, description, app_info_coordinator, HoymilesDataSensorEntity, dtu_serial_number, inverters, ports) sensors.extend(sensor_entities) async_add_entities(sensors) -def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEntityDescription, coordinator: HoymilesCoordinatorEntity, class_name: SensorEntity, inverters: list, ports: list) -> list[SensorEntity]: +def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEntityDescription, coordinator: HoymilesCoordinatorEntity, class_name: SensorEntity, dtu_serial_number: str, inverters: list, ports: list) -> list[SensorEntity]: """Get sensors for the given description.""" sensors = [] @@ -294,7 +293,7 @@ def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEn if "" in description.key: for index, inverter_serial in enumerate(inverters): new_key = description.key.replace("", str(index)) - updated_description = dataclasses.replace(description, key=new_key, serial_number = inverter_serial) + updated_description = dataclasses.replace(description, key=new_key, serial_number=inverter_serial) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) elif "" in description.key: @@ -303,11 +302,12 @@ def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEn port_number = port["port_number"] new_key = str(description.key).replace("", str(index)) new_translation_key = description.translation_key.replace("", str(port_number)) - updated_description = dataclasses.replace(description, key=new_key, translation_key=new_translation_key, serial_number = inverter_serial) + updated_description = dataclasses.replace(description, key=new_key, translation_key=new_translation_key, serial_number=inverter_serial) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) else: - sensor = class_name(config_entry, description, coordinator) + updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) + sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) return sensors From a63ab2356d90962435555080e0e1131f021d5bf7 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Thu, 7 Mar 2024 15:42:18 +0100 Subject: [PATCH 05/20] refactoring --- custom_components/hoymiles_wifi/__init__.py | 36 +++++-------------- .../hoymiles_wifi/config_flow.py | 29 +++------------ custom_components/hoymiles_wifi/const.py | 1 + custom_components/hoymiles_wifi/entity.py | 2 -- custom_components/hoymiles_wifi/error.py | 7 ++++ custom_components/hoymiles_wifi/util.py | 30 ++++++++++++++++ 6 files changed, 51 insertions(+), 54 deletions(-) create mode 100644 custom_components/hoymiles_wifi/error.py create mode 100644 custom_components/hoymiles_wifi/util.py diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 0647549..1a3e01d 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -14,6 +14,7 @@ CONF_INVERTERS, CONF_PORTS, CONF_UPDATE_INTERVAL, + CONFIG_VERSION, DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS, DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS, DOMAIN, @@ -27,13 +28,13 @@ HoymilesConfigUpdateCoordinator, HoymilesRealDataUpdateCoordinator, ) +from .error import CannotConnect +from .util import async_get_config_entry_data_for_host _LOGGER = logging.getLogger(__name__) PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.BINARY_SENSOR, Platform.BUTTON] -CURRENT_VERSION = 2 - async def async_setup(hass: HomeAssistant, config: Config): """Set up this integration using YAML is not supported.""" return True @@ -78,8 +79,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry data to the new entry schema.""" - _LOGGER.info("Migrating entry %s to version %s", config_entry.entry_id, CURRENT_VERSION) - # Get the current data from the config entry data = config_entry.data.copy() @@ -88,43 +87,24 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> # Perform migrations based on the version if current_version == 1: - _LOGGER.info("Migrating from version 1 to version 2") - + _LOGGER.info("Migrating entry %s to version %s", config_entry.entry_id, CONFIG_VERSION) new = {**config_entry.data} host = config_entry.data.get(CONF_HOST) - inverter = Inverter(host) - - real_data = await inverter.async_get_real_data_new() - if(real_data is None): + try: + dtu_sn, inverters, ports = await async_get_config_entry_data_for_host(host) + except CannotConnect: _LOGGER.error("Could not retrieve real data information data from inverter: %s. Please ensure inverter is available!", host) return False - dtu_sn = real_data.device_serial_number - - inverters = [] - - for sgs_data in real_data.sgs_data: - inverter_serial = generate_inverter_serial_number(sgs_data.serial_number) - inverters.append(inverter_serial) - - ports = [] - for pv_data in real_data.pv_data: - inverter_serial = generate_inverter_serial_number(pv_data.serial_number) - port_number = pv_data.port_number - ports.append({ - "inverter_serial_number": inverter_serial, - "port_number": port_number - }) - new[CONF_DTU_SERIAL_NUMBER] = dtu_sn new[CONF_INVERTERS] = inverters new[CONF_PORTS] = ports # Update the config entry with the new data hass.config_entries.async_update_entry(config_entry, data=new, version=2) + _LOGGER.info("Migration of entry %s to version %s successful", config_entry.entry_id, CONFIG_VERSION) - _LOGGER.info("Migration of entry %s to version %s successful", config_entry.entry_id, CURRENT_VERSION) return True diff --git a/custom_components/hoymiles_wifi/config_flow.py b/custom_components/hoymiles_wifi/config_flow.py index 17251fd..6d0d805 100644 --- a/custom_components/hoymiles_wifi/config_flow.py +++ b/custom_components/hoymiles_wifi/config_flow.py @@ -9,10 +9,8 @@ from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult -from homeassistant.exceptions import HomeAssistantError from hoymiles_wifi.inverter import Inverter from hoymiles_wifi.protobuf import APPInfomationData_pb2 -from hoymiles_wifi.utils import generate_inverter_serial_number from .const import ( CONF_DTU_SERIAL_NUMBER, @@ -20,10 +18,13 @@ CONF_PORTS, CONF_SENSOR_PREFIX, CONF_UPDATE_INTERVAL, + CONFIG_VERSION, DEFAULT_UPDATE_INTERVAL_SECONDS, DOMAIN, MIN_UPDATE_INTERVAL_SECONDS, ) +from .error import CannotConnect +from .util import async_get_config_entry_data_for_host _LOGGER = logging.getLogger(__name__) @@ -39,7 +40,7 @@ class HoymilesInverterConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Hoymiles Inverter config flow.""" - VERSION = 2 + VERSION = CONFIG_VERSION async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -54,27 +55,10 @@ async def async_step_user( sensor_prefix = user_input.get(CONF_SENSOR_PREFIX, "") try: - real_data = await get_real_data_new.async_get_real_data_new() + dtu_sn, inverters, ports = await async_get_config_entry_data_for_host(host) except CannotConnect: errors["base"] = "cannot_connect" else: - - dtu_sn = real_data.device_serial_number - inverters = [] - - for sgs_data in real_data.sgs_data: - inverter_serial = generate_inverter_serial_number(sgs_data.serial_number) - inverters.append(inverter_serial) - - ports = [] - for pv_data in real_data.pv_data: - inverter_serial = generate_inverter_serial_number(pv_data.serial_number) - port_number = pv_data.port_number - ports.append({ - "inverter_serial_number": inverter_serial, - "port_number": port_number - }) - return self.async_create_entry( title=host, data= { CONF_HOST: host, @@ -107,6 +91,3 @@ async def get_real_data_new(hass: HomeAssistant, host: str) -> APPInfomationData raise CannotConnect return response - -class CannotConnect(HomeAssistantError): - """Error to indicate we cannot connect.""" diff --git a/custom_components/hoymiles_wifi/const.py b/custom_components/hoymiles_wifi/const.py index a661b76..3eaf416 100644 --- a/custom_components/hoymiles_wifi/const.py +++ b/custom_components/hoymiles_wifi/const.py @@ -4,6 +4,7 @@ DOMAIN = "hoymiles_wifi" DOMAIN_DATA = f"{DOMAIN}_data" VERSION = "0.1.2" +CONFIG_VERSION = 2 ISSUE_URL = "https://github.com/suaveolent/ha-hoymiles-wifi/issues" diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index 0a49d11..8eadcf9 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -37,12 +37,10 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): device_model="HMS-XXXXW-2T" device_name_suffix = "" - if hasattr(self.entity_description, "is_dtu_sensor") and self.entity_description.is_dtu_sensor is True: device_name_suffix = " DTU" device_name += self._sensor_prefix - print(f"Name: {self.entity_description.key}, Serial number: {self.entity_description.serial_number}, is dtu: {self.entity_description.is_dtu_sensor}") device_info = DeviceInfo( identifiers={(DOMAIN, self._config_entry.entry_id + device_name_suffix)}, diff --git a/custom_components/hoymiles_wifi/error.py b/custom_components/hoymiles_wifi/error.py new file mode 100644 index 0000000..6ba9ae9 --- /dev/null +++ b/custom_components/hoymiles_wifi/error.py @@ -0,0 +1,7 @@ +"""Errors for hoymiles-wifi.""" + +from homeassistant.exceptions import HomeAssistantError + + +class CannotConnect(HomeAssistantError): + """Error to indicate we cannot connect.""" \ No newline at end of file diff --git a/custom_components/hoymiles_wifi/util.py b/custom_components/hoymiles_wifi/util.py new file mode 100644 index 0000000..1487799 --- /dev/null +++ b/custom_components/hoymiles_wifi/util.py @@ -0,0 +1,30 @@ +"""Utils for hoymiles-wifi.""" + +from typing import Union + +from homeassistant.config_entries import ConfigEntry +from hoymiles_wifi.inverter import Inverter +from hoymiles_wifi.utils import generate_inverter_serial_number + +from .error import CannotConnect + + +async def async_get_config_entry_data_for_host(host) -> tuple[str, list[str], list[dict[str, Union[str, int]]]]: + """Migrate data from version 1 to version 2.""" + + inverter = Inverter(host) + + real_data = await inverter.async_get_real_data_new() + if real_data is None: + raise CannotConnect + + dtu_sn = real_data.device_serial_number + + inverters = [generate_inverter_serial_number(sgs_data.serial_number) for sgs_data in real_data.sgs_data] + + ports = [{ + "inverter_serial_number": generate_inverter_serial_number(pv_data.serial_number), + "port_number": pv_data.port_number + } for pv_data in real_data.pv_data] + + return dtu_sn, inverters, ports From 05aabeb5f1866d36332d5ef2fc991b3df1f19aba Mon Sep 17 00:00:00 2001 From: suaveolent Date: Thu, 14 Mar 2024 16:55:11 +0100 Subject: [PATCH 06/20] Add support for multi inverters --- custom_components/hoymiles_wifi/__init__.py | 42 ++-- .../hoymiles_wifi/binary_sensor.py | 44 ++-- custom_components/hoymiles_wifi/button.py | 73 ++++--- .../hoymiles_wifi/config_flow.py | 40 ++-- custom_components/hoymiles_wifi/const.py | 6 +- .../hoymiles_wifi/coordinator.py | 42 ++-- custom_components/hoymiles_wifi/entity.py | 73 ++++--- custom_components/hoymiles_wifi/error.py | 2 +- custom_components/hoymiles_wifi/manifest.json | 4 +- custom_components/hoymiles_wifi/number.py | 70 +++++-- custom_components/hoymiles_wifi/sensor.py | 189 ++++++++++++------ custom_components/hoymiles_wifi/strings.json | 2 +- custom_components/hoymiles_wifi/util.py | 33 +-- 13 files changed, 407 insertions(+), 213 deletions(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 1a3e01d..93e1934 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -6,8 +6,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform from homeassistant.core import Config, HomeAssistant -from hoymiles_wifi.inverter import Inverter -from hoymiles_wifi.utils import generate_inverter_serial_number +from hoymiles_wifi.dtu import DTU from .const import ( CONF_DTU_SERIAL_NUMBER, @@ -21,7 +20,7 @@ HASS_APP_INFO_COORDINATOR, HASS_CONFIG_COORDINATOR, HASS_DATA_COORDINATOR, - HASS_INVERTER, + HASS_DTU, ) from .coordinator import ( HoymilesAppInfoUpdateCoordinator, @@ -35,6 +34,7 @@ PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.BINARY_SENSOR, Platform.BUTTON] + async def async_setup(hass: HomeAssistant, config: Config): """Set up this integration using YAML is not supported.""" return True @@ -50,19 +50,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): host = entry.data.get(CONF_HOST) update_interval = timedelta(seconds=entry.data.get(CONF_UPDATE_INTERVAL)) - inverter = Inverter(host) + dtu = DTU(host) - hass_data[HASS_INVERTER] = inverter + hass_data[HASS_DTU] = dtu - data_coordinator = HoymilesRealDataUpdateCoordinator(hass, inverter=inverter, entry=entry, update_interval=update_interval) + data_coordinator = HoymilesRealDataUpdateCoordinator( + hass, dtu=dtu, entry=entry, update_interval=update_interval + ) hass_data[HASS_DATA_COORDINATOR] = data_coordinator config_update_interval = timedelta(seconds=DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS) - config_coordinator = HoymilesConfigUpdateCoordinator(hass, inverter=inverter, entry=entry, update_interval=config_update_interval) + config_coordinator = HoymilesConfigUpdateCoordinator( + hass, dtu=dtu, entry=entry, update_interval=config_update_interval + ) hass_data[HASS_CONFIG_COORDINATOR] = config_coordinator - app_info_update_interval = timedelta(seconds=DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS) - app_info_update_coordinator = HoymilesAppInfoUpdateCoordinator(hass, inverter=inverter, entry=entry, update_interval=app_info_update_interval) + app_info_update_interval = timedelta( + seconds=DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS + ) + app_info_update_coordinator = HoymilesAppInfoUpdateCoordinator( + hass, dtu=dtu, entry=entry, update_interval=app_info_update_interval + ) hass_data[HASS_APP_INFO_COORDINATOR] = app_info_update_coordinator hass.data[DOMAIN][entry.entry_id] = hass_data @@ -87,7 +95,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> # Perform migrations based on the version if current_version == 1: - _LOGGER.info("Migrating entry %s to version %s", config_entry.entry_id, CONFIG_VERSION) + _LOGGER.info( + "Migrating entry %s to version %s", config_entry.entry_id, CONFIG_VERSION + ) new = {**config_entry.data} host = config_entry.data.get(CONF_HOST) @@ -95,7 +105,10 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> try: dtu_sn, inverters, ports = await async_get_config_entry_data_for_host(host) except CannotConnect: - _LOGGER.error("Could not retrieve real data information data from inverter: %s. Please ensure inverter is available!", host) + _LOGGER.error( + "Could not retrieve real data information data from inverter: %s. Please ensure inverter is available!", + host, + ) return False new[CONF_DTU_SERIAL_NUMBER] = dtu_sn @@ -104,7 +117,10 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> # Update the config entry with the new data hass.config_entries.async_update_entry(config_entry, data=new, version=2) - _LOGGER.info("Migration of entry %s to version %s successful", config_entry.entry_id, CONFIG_VERSION) + _LOGGER.info( + "Migration of entry %s to version %s successful", + config_entry.entry_id, + CONFIG_VERSION, + ) return True - diff --git a/custom_components/hoymiles_wifi/binary_sensor.py b/custom_components/hoymiles_wifi/binary_sensor.py index 68ba0cd..c07fd3e 100644 --- a/custom_components/hoymiles_wifi/binary_sensor.py +++ b/custom_components/hoymiles_wifi/binary_sensor.py @@ -12,21 +12,20 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from hoymiles_wifi.inverter import NetworkState +from hoymiles_wifi.dtu import NetworkState from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN, HASS_DATA_COORDINATOR -from .entity import HoymilesCoordinatorEntity +from .entity import HoymilesCoordinatorEntity, HoymilesEntityDescription _LOGGER = logging.getLogger(__name__) + @dataclass(frozen=True) -class HoymilesBinarySensorEntityDescription(BinarySensorEntityDescription): +class HoymilesBinarySensorEntityDescription( + HoymilesEntityDescription, BinarySensorEntityDescription +): """Describes Homiles binary sensor entity.""" - is_dtu_sensor: bool = False - serial_number: str = None - - BINARY_SENSORS = ( HoymilesBinarySensorEntityDescription( @@ -38,6 +37,7 @@ class HoymilesBinarySensorEntityDescription(BinarySensorEntityDescription): ), ) + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -51,8 +51,12 @@ async def async_setup_entry( sensors = [] for description in BINARY_SENSORS: - updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) - sensors.append(HoymilesInverterSensorEntity(entry, updated_description, data_coordinator)) + updated_description = dataclasses.replace( + description, serial_number=dtu_serial_number + ) + sensors.append( + HoymilesInverterSensorEntity(entry, updated_description, data_coordinator) + ) async_add_entities(sensors) @@ -60,15 +64,19 @@ async def async_setup_entry( class HoymilesInverterSensorEntity(HoymilesCoordinatorEntity, BinarySensorEntity): """Represents a binary sensor entity for Hoymiles WiFi integration.""" - def __init__(self, config_entry: ConfigEntry, description:HoymilesBinarySensorEntityDescription, coordinator: HoymilesCoordinatorEntity): + def __init__( + self, + config_entry: ConfigEntry, + description: HoymilesBinarySensorEntityDescription, + coordinator: HoymilesCoordinatorEntity, + ): """Initialize the HoymilesInverterSensorEntity.""" super().__init__(config_entry, description, coordinator) - self._inverter = coordinator.get_inverter() + self._dtu = coordinator.get_dtu() self._native_value = None self.update_state_value() - @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" @@ -80,16 +88,12 @@ def is_on(self): """Return the state of the binary sensor.""" return self._native_value - def update_state_value(self): - """Update the state value of the binary sensor based on the inverter's network state.""" - inverter_state = self._inverter.get_state() - if inverter_state == NetworkState.Online: + """Update the state value of the binary sensor based on the DTU's network state.""" + dtu_state = self._dtu.get_state() + if dtu_state == NetworkState.Online: self._native_value = True - elif inverter_state == NetworkState.Offline: + elif dtu_state == NetworkState.Offline: self._native_value = False else: self._native_value = None - - - diff --git a/custom_components/hoymiles_wifi/button.py b/custom_components/hoymiles_wifi/button.py index b758673..bbb6d1b 100644 --- a/custom_components/hoymiles_wifi/button.py +++ b/custom_components/hoymiles_wifi/button.py @@ -11,75 +11,90 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from hoymiles_wifi.inverter import Inverter +from hoymiles_wifi.dtu import DTU -from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN, HASS_INVERTER -from .entity import HoymilesEntity +from .const import CONF_DTU_SERIAL_NUMBER, CONF_INVERTERS, DOMAIN, HASS_DTU +from .entity import HoymilesEntity, HoymilesEntityDescription @dataclass(frozen=True) -class HoymilesButtonEntityDescription(ButtonEntityDescription): +class HoymilesButtonEntityDescription( + HoymilesEntityDescription, ButtonEntityDescription +): """Class to describe a Hoymiles Button entity.""" - is_dtu_sensor: bool = False - serial_number: str = None - - BUTTONS: tuple[HoymilesButtonEntityDescription, ...] = ( HoymilesButtonEntityDescription( - key="async_restart", + key="async_restart_dtu", translation_key="restart", - device_class = ButtonDeviceClass.RESTART, - is_dtu_sensor = True, + device_class=ButtonDeviceClass.RESTART, + is_dtu_sensor=True, ), HoymilesButtonEntityDescription( - key="async_turn_off", + key="async_turn_off_inverter", translation_key="turn_off", icon="mdi:power-off", - is_dtu_sensor = True, ), HoymilesButtonEntityDescription( - key="async_turn_on", + key="async_turn_on_inverter", translation_key="turn_on", icon="mdi:power-on", - is_dtu_sensor = True, ), ) + async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Hoymiles number entities.""" - hass_data = hass.data[DOMAIN][entry.entry_id] - inverter = hass_data[HASS_INVERTER] - dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] + hass_data = hass.data[DOMAIN][config_entry.entry_id] + dtu = hass_data[HASS_DTU] + dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER] + inverters = config_entry.data[CONF_INVERTERS] buttons = [] for description in BUTTONS: - updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) - buttons.append(HoymilesButtonEntity(entry, updated_description, inverter) - ) + if description.is_dtu_sensor is True: + updated_description = dataclasses.replace( + description, serial_number=dtu_serial_number + ) + buttons.append(HoymilesButtonEntity(config_entry, updated_description, dtu)) + else: + for inverter_serial in inverters: + updated_description = dataclasses.replace( + description, serial_number=inverter_serial + ) + buttons.append( + HoymilesButtonEntity(config_entry, updated_description, dtu) + ) + async_add_entities(buttons) class HoymilesButtonEntity(HoymilesEntity, ButtonEntity): """Hoymiles Number entity.""" - def __init__(self, config_entry: ConfigEntry, description: HoymilesButtonEntityDescription, inverter: Inverter) -> None: + def __init__( + self, + config_entry: ConfigEntry, + description: HoymilesButtonEntityDescription, + dtu: DTU, + ) -> None: """Initialize the HoymilesButtonEntity.""" super().__init__(config_entry, description) - self._inverter = inverter + self._dtu = dtu async def async_press(self) -> None: """Press the button.""" - if hasattr(self._inverter, self.entity_description.key) and callable(getattr(self._inverter, self.entity_description.key)): + if hasattr(self._inverter, self.entity_description.key) and callable( + getattr(self._inverter, self.entity_description.key) + ): await getattr(self._inverter, self.entity_description.key)() else: - raise NotImplementedError(f"Method '{self.entity_description.key}' not implemented in Inverter class.") - - - + raise NotImplementedError( + f"Method '{self.entity_description.key}' not implemented in Inverter class." + ) diff --git a/custom_components/hoymiles_wifi/config_flow.py b/custom_components/hoymiles_wifi/config_flow.py index 6d0d805..0bf7d72 100644 --- a/custom_components/hoymiles_wifi/config_flow.py +++ b/custom_components/hoymiles_wifi/config_flow.py @@ -9,7 +9,7 @@ from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult -from hoymiles_wifi.inverter import Inverter +from hoymiles_wifi.dtu import DTU from hoymiles_wifi.protobuf import APPInfomationData_pb2 from .const import ( @@ -28,14 +28,20 @@ _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({ +DATA_SCHEMA = vol.Schema( + { vol.Required(CONF_HOST): str, vol.Optional( CONF_UPDATE_INTERVAL, default=timedelta(seconds=DEFAULT_UPDATE_INTERVAL_SECONDS).seconds, - ): vol.All(vol.Coerce(int), vol.Range(min=timedelta(seconds=MIN_UPDATE_INTERVAL_SECONDS).seconds)), + ): vol.All( + vol.Coerce(int), + vol.Range(min=timedelta(seconds=MIN_UPDATE_INTERVAL_SECONDS).seconds), + ), vol.Optional(CONF_SENSOR_PREFIX): str, -}) + } +) + class HoymilesInverterConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Hoymiles Inverter config flow.""" @@ -51,23 +57,28 @@ async def async_step_user( if user_input is not None: self._async_abort_entries_match(user_input) host = user_input[CONF_HOST] - update_interval = user_input.get(CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS) + update_interval = user_input.get( + CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS + ) sensor_prefix = user_input.get(CONF_SENSOR_PREFIX, "") try: - dtu_sn, inverters, ports = await async_get_config_entry_data_for_host(host) + dtu_sn, inverters, ports = await async_get_config_entry_data_for_host( + host + ) except CannotConnect: errors["base"] = "cannot_connect" else: return self.async_create_entry( - title=host, data= { + title=host, + data={ CONF_HOST: host, CONF_SENSOR_PREFIX: sensor_prefix, CONF_UPDATE_INTERVAL: update_interval, CONF_DTU_SERIAL_NUMBER: dtu_sn, CONF_INVERTERS: inverters, CONF_PORTS: ports, - } + }, ) return self.async_show_form( @@ -82,12 +93,13 @@ async def async_step_import(self, import_config): ) -async def get_real_data_new(hass: HomeAssistant, host: str) -> APPInfomationData_pb2.APPInfoDataResDTO: - """Test if the host is reachable and is actually a Hoymiles HMS device.""" +async def get_real_data_new( + hass: HomeAssistant, host: str +) -> APPInfomationData_pb2.APPInfoDataResDTO: + """Test if the host is reachable and is actually a Hoymiles device.""" - inverter = Inverter(host) - response = await inverter.get_real_data_new() - if(response is None): + dtu = DTU(host) + response = await dtu.get_real_data_new() + if response is None: raise CannotConnect return response - diff --git a/custom_components/hoymiles_wifi/const.py b/custom_components/hoymiles_wifi/const.py index 3eaf416..3aa5ce1 100644 --- a/custom_components/hoymiles_wifi/const.py +++ b/custom_components/hoymiles_wifi/const.py @@ -17,14 +17,14 @@ DEFAULT_UPDATE_INTERVAL_SECONDS = 35 MIN_UPDATE_INTERVAL_SECONDS = 35 -DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS = 60*5 -DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS = 60*60*2 +DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS = 60 * 5 +DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS = 60 * 60 * 2 HASS_DATA_COORDINATOR = "data_coordinator" HASS_CONFIG_COORDINATOR = "config_coordinator" HASS_APP_INFO_COORDINATOR = "app_info_coordinator" -HASS_INVERTER = "inverter" +HASS_DTU = "dtu" HASS_DATA_UNSUB_OPTIONS_UPDATE_LISTENER = "unsub_options_update_listener" diff --git a/custom_components/hoymiles_wifi/coordinator.py b/custom_components/hoymiles_wifi/coordinator.py index efb67bf..35991c6 100644 --- a/custom_components/hoymiles_wifi/coordinator.py +++ b/custom_components/hoymiles_wifi/coordinator.py @@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from hoymiles_wifi.inverter import Inverter +from hoymiles_wifi.dtu import DTU from .const import DOMAIN @@ -14,12 +14,19 @@ PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.BINARY_SENSOR, Platform.BUTTON] + class HoymilesDataUpdateCoordinator(DataUpdateCoordinator): """Base data update coordinator for Hoymiles integration.""" - def __init__(self, hass: homeassistant, inverter: Inverter, entry: ConfigEntry, update_interval: timedelta) -> None: + def __init__( + self, + hass: homeassistant, + dtu: DTU, + entry: ConfigEntry, + update_interval: timedelta, + ) -> None: """Initialize the HoymilesCoordinatorEntity.""" - self._inverter = inverter + self._dtu = dtu self._entities_added = False self._hass = hass self._entry = entry @@ -32,9 +39,10 @@ def __init__(self, hass: homeassistant, inverter: Inverter, entry: ConfigEntry, super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval) - def get_inverter(self): - """Get the inverter object.""" - return self._inverter + def get_dtu(self) -> DTU: + """Get the DTU object.""" + return self._dtu + class HoymilesRealDataUpdateCoordinator(HoymilesDataUpdateCoordinator): """Data coordinator for Hoymiles integration.""" @@ -43,22 +51,25 @@ async def _async_update_data(self): """Update data via library.""" _LOGGER.debug("Hoymiles data coordinator update") - response = await self._inverter.async_get_real_data_new() + response = await self._dtu.async_get_real_data_new() if not self._entities_added: self._hass.async_add_job( - self._hass.config_entries.async_forward_entry_setups(self._entry, PLATFORMS) + self._hass.config_entries.async_forward_entry_setups( + self._entry, PLATFORMS + ) ) self._entities_added = True if response: return response else: - _LOGGER.debug("Unable to retrieve real data new. Inverter might be offline.") + _LOGGER.debug( + "Unable to retrieve real data new. Inverter might be offline." + ) return None - class HoymilesConfigUpdateCoordinator(HoymilesDataUpdateCoordinator): """Config coordinator for Hoymiles integration.""" @@ -66,7 +77,7 @@ async def _async_update_data(self): """Update data via library.""" _LOGGER.debug("Hoymiles data coordinator update") - response = await self._inverter.async_get_config() + response = await self._dtu.async_get_config() if response: return response @@ -74,6 +85,7 @@ async def _async_update_data(self): _LOGGER.debug("Unable to retrieve config data. Inverter might be offline.") return None + class HoymilesAppInfoUpdateCoordinator(HoymilesDataUpdateCoordinator): """App Info coordinator for Hoymiles integration.""" @@ -81,12 +93,12 @@ async def _async_update_data(self): """Update data via library.""" _LOGGER.debug("Hoymiles data coordinator update") - response = await self._inverter.async_app_information_data() + response = await self._dtu.async_app_information_data() if response: return response else: - _LOGGER.debug("Unable to retrieve app information data. Inverter might be offline.") + _LOGGER.debug( + "Unable to retrieve app information data. Inverter might be offline." + ) return None - - diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index 8eadcf9..6223954 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -1,11 +1,16 @@ """Entity base for Hoymiles entities.""" +from dataclasses import dataclass import logging from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity -import hoymiles_wifi.utils +from hoymiles_wifi.hoymiles import ( + generate_inverter_serial_number, + get_dtu_model_name, + get_inverter_model_name, +) from .const import CONF_DTU_SERIAL_NUMBER, CONF_SENSOR_PREFIX, DOMAIN from .coordinator import HoymilesDataUpdateCoordinator @@ -13,11 +18,12 @@ _LOGGER = logging.getLogger(__name__) -def safe_get_element(lst, index, default): - try: - return lst[index] - except IndexError: - return default +@dataclass(frozen=True) +class HoymilesEntityDescription(EntityDescription): + """Class to describe a Hoymiles Button entity.""" + + is_dtu_sensor: bool = False + serial_number: str = None class HoymilesEntity(Entity): @@ -30,46 +36,61 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): super().__init__() self.entity_description = description self._config_entry = config_entry - self._sensor_prefix = f' {config_entry.data.get(CONF_SENSOR_PREFIX)} ' if config_entry.data.get(CONF_SENSOR_PREFIX) else "" + self._sensor_prefix = ( + f" {config_entry.data.get(CONF_SENSOR_PREFIX)} " + if config_entry.data.get(CONF_SENSOR_PREFIX) + else "" + ) self._attr_unique_id = f"hoymiles_{config_entry.entry_id}_{description.key}" - device_name = "Hoymiles HMS-XXXXW-2T" - device_model="HMS-XXXXW-2T" - device_name_suffix = "" + dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER] - if hasattr(self.entity_description, "is_dtu_sensor") and self.entity_description.is_dtu_sensor is True: - device_name_suffix = " DTU" + if self.entity_description.is_dtu_sensor is True: + device_name = "DTU" + device_model = get_dtu_model_name(self.entity_description.serial_number) + else: + device_model = get_inverter_model_name( + self.entity_description.serial_number + ) + device_name = "Inverter" device_name += self._sensor_prefix device_info = DeviceInfo( - identifiers={(DOMAIN, self._config_entry.entry_id + device_name_suffix)}, - name = device_name + device_name_suffix, + identifiers={(DOMAIN, self.entity_description.serial_number)}, + name=device_name, manufacturer="Hoymiles", - serial_number= self.entity_description.serial_number, - model = device_model, + serial_number=self.entity_description.serial_number, + model=device_model, ) - if not hasattr(self.entity_description, "is_dtu_sensor") or self.entity_description.is_dtu_sensor is False: - device_info["via_device"] = (DOMAIN, self._config_entry.entry_id + " DTU") + if self.entity_description.is_dtu_sensor is False: + device_info["via_device"] = (DOMAIN, dtu_serial_number) self._attr_device_info = device_info + class HoymilesCoordinatorEntity(CoordinatorEntity, HoymilesEntity): """Represents a Hoymiles coordinator entity.""" - def __init__(self, config_entry: ConfigEntry, description: EntityDescription, coordinator: HoymilesDataUpdateCoordinator): + def __init__( + self, + config_entry: ConfigEntry, + description: EntityDescription, + coordinator: HoymilesDataUpdateCoordinator, + ): """Pass coordinator to CoordinatorEntity.""" CoordinatorEntity.__init__(self, coordinator) if self.coordinator is not None and hasattr(self.coordinator, "data"): - self._dtu_serial_number = getattr(self.coordinator.data, "device_serial_number", "") + self._dtu_serial_number = getattr( + self.coordinator.data, "device_serial_number", "" + ) sgs_data = getattr(self.coordinator.data, "sgs_data", None) - if(sgs_data is not None): + if sgs_data is not None: serial_number = getattr(sgs_data[0], "serial_number", None) - if(serial_number is not None): - self._inverter_serial_number = hoymiles_wifi.utils.generate_inverter_serial_number(serial_number) + if serial_number is not None: + self._inverter_serial_number = generate_inverter_serial_number( + serial_number + ) HoymilesEntity.__init__(self, config_entry, description) - - - diff --git a/custom_components/hoymiles_wifi/error.py b/custom_components/hoymiles_wifi/error.py index 6ba9ae9..a4ac06b 100644 --- a/custom_components/hoymiles_wifi/error.py +++ b/custom_components/hoymiles_wifi/error.py @@ -4,4 +4,4 @@ class CannotConnect(HomeAssistantError): - """Error to indicate we cannot connect.""" \ No newline at end of file + """Error to indicate we cannot connect.""" diff --git a/custom_components/hoymiles_wifi/manifest.json b/custom_components/hoymiles_wifi/manifest.json index 101041f..f375e6a 100644 --- a/custom_components/hoymiles_wifi/manifest.json +++ b/custom_components/hoymiles_wifi/manifest.json @@ -6,6 +6,6 @@ "domain": "hoymiles_wifi", "iot_class": "local_polling", "name": "Hoymiles", - "requirements": ["hoymiles-wifi==0.1.7"], - "version": "0.1.4" + "requirements": ["hoymiles-wifi==0.1.8"], + "version": "0.1.5-dev" } diff --git a/custom_components/hoymiles_wifi/number.py b/custom_components/hoymiles_wifi/number.py index e396dfe..21f3d54 100644 --- a/custom_components/hoymiles_wifi/number.py +++ b/custom_components/hoymiles_wifi/number.py @@ -14,7 +14,12 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN, HASS_CONFIG_COORDINATOR +from .const import ( + CONF_DTU_SERIAL_NUMBER, + CONF_INVERTERS, + DOMAIN, + HASS_CONFIG_COORDINATOR, +) from .entity import HoymilesCoordinatorEntity @@ -23,10 +28,12 @@ class SetAction(Enum): POWER_LIMIT = 1 + @dataclass(frozen=True) class HoymilesNumberSensorEntityDescriptionMixin: """Mixin for required keys.""" + @dataclass(frozen=True) class HoymilesNumberSensorEntityDescription(NumberEntityDescription): """Describes Hoymiles number sensor entity.""" @@ -39,12 +46,12 @@ class HoymilesNumberSensorEntityDescription(NumberEntityDescription): CONFIG_CONTROL_ENTITIES = ( HoymilesNumberSensorEntityDescription( - key = "limit_power_mypower", + key="limit_power_mypower", translation_key="limit_power_mypower", - mode = NumberMode.SLIDER, - device_class = NumberDeviceClass.POWER_FACTOR, - set_action = SetAction.POWER_LIMIT, - conversion_factor = 0.1, + mode=NumberMode.SLIDER, + device_class=NumberDeviceClass.POWER_FACTOR, + set_action=SetAction.POWER_LIMIT, + conversion_factor=0.1, ), ) @@ -53,18 +60,36 @@ class HoymilesNumberSensorEntityDescription(NumberEntityDescription): async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Hoymiles number entities.""" - hass_data = hass.data[DOMAIN][entry.entry_id] + hass_data = hass.data[DOMAIN][config_entry.entry_id] config_coordinator = hass_data[HASS_CONFIG_COORDINATOR] - dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] + dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER] + inverters = config_entry.data[CONF_INVERTERS] sensors = [] for description in CONFIG_CONTROL_ENTITIES: - updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) - sensors.append(HoymilesNumberEntity(entry, updated_description, config_coordinator)) + if description.is_dtu_sensor is True: + updated_description = dataclasses.replace( + description, serial_number=dtu_serial_number + ) + sensors.append( + HoymilesNumberEntity( + config_entry, updated_description, config_coordinator + ) + ) + else: + for inverter_serial in inverters: + updated_description = dataclasses.replace( + description, serial_number=inverter_serial + ) + sensors.append( + HoymilesNumberEntity( + config_entry, updated_description, config_coordinator + ) + ) async_add_entities(sensors) @@ -72,7 +97,12 @@ async def async_setup_entry( class HoymilesNumberEntity(HoymilesCoordinatorEntity, NumberEntity): """Hoymiles Number entity.""" - def __init__(self, config_entry: ConfigEntry, description: HoymilesNumberSensorEntityDescription, coordinator: HoymilesCoordinatorEntity,) -> None: + def __init__( + self, + config_entry: ConfigEntry, + description: HoymilesNumberSensorEntityDescription, + coordinator: HoymilesCoordinatorEntity, + ) -> None: """Initialize the HoymilesNumberEntity.""" super().__init__(config_entry, description, coordinator) self._attribute_name = description.key @@ -89,7 +119,6 @@ def _handle_coordinator_update(self) -> None: self.update_state_value() super()._handle_coordinator_update() - @property def native_value(self) -> float: """Get the native value of the entity.""" @@ -107,12 +136,12 @@ async def async_set_native_value(self, value: float) -> None: value (float): The value to set. """ if self._set_action == SetAction.POWER_LIMIT: - inverter = self.coordinator.get_inverter() - if(value < 0 and value > 100): - _LOGGER.error("Power limit value out of range") - return - await inverter.async_set_power_limit(value) - await self.coordinator.async_request_refresh() + dtu = self.coordinator.get_dtu() + if value < 0 and value > 100: + _LOGGER.error("Power limit value out of range") + return + await dtu.async_set_power_limit(value) + await self.coordinator.async_request_refresh() else: _LOGGER.error("Invalid set action!") return @@ -120,10 +149,9 @@ async def async_set_native_value(self, value: float) -> None: self._assumed_state = True self._native_value = value - def update_state_value(self): """Update the state value of the entity.""" - self._native_value = getattr(self.coordinator.data, self._attribute_name, None) + self._native_value = getattr(self.coordinator.data, self._attribute_name, None) self._assumed_state = False diff --git a/custom_components/hoymiles_wifi/sensor.py b/custom_components/hoymiles_wifi/sensor.py index 8d7cc0e..2843bc5 100644 --- a/custom_components/hoymiles_wifi/sensor.py +++ b/custom_components/hoymiles_wifi/sensor.py @@ -26,7 +26,7 @@ ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -import hoymiles_wifi.utils +import hoymiles_wifi.hoymiles from .const import ( CONF_DTU_SERIAL_NUMBER, @@ -40,7 +40,7 @@ HASS_CONFIG_COORDINATOR, HASS_DATA_COORDINATOR, ) -from .entity import HoymilesCoordinatorEntity +from .entity import HoymilesCoordinatorEntity, HoymilesEntityDescription _LOGGER = logging.getLogger(__name__) @@ -50,31 +50,32 @@ class ConversionAction(Enum): HEX = 1 + @dataclass(frozen=True) class HoymilesSensorEntityDescriptionMixin: """Mixin for required keys.""" + @dataclass(frozen=True) -class HoymilesSensorEntityDescription(SensorEntityDescription): +class HoymilesSensorEntityDescription( + HoymilesEntityDescription, SensorEntityDescription +): """Describes Hoymiles data sensor entity.""" conversion_factor: float = None reset_at_midnight: bool = False version_translation_function: str = None version_prefix: str = None - serial_number: str = None - is_dtu_sensor: bool = False @dataclass(frozen=True) -class HoymilesDiagnosticEntityDescription(SensorEntityDescription): +class HoymilesDiagnosticEntityDescription( + HoymilesEntityDescription, SensorEntityDescription +): """Describes Hoymiles diagnostic sensor entity.""" conversion: ConversionAction = None separator: str = None - serial_number: str = None - is_dtu_sensor: bool = False - HOYMILES_SENSORS = [ @@ -187,7 +188,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): translation_key="mac_address", entity_category=EntityCategory.DIAGNOSTIC, separator=":", - conversion= ConversionAction.HEX, + conversion=ConversionAction.HEX, is_dtu_sensor=True, ), HoymilesDiagnosticEntityDescription( @@ -208,7 +209,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): APP_INFO_SENSORS: tuple[HoymilesSensorEntityDescription, ...] = ( HoymilesSensorEntityDescription( - key = "dtu_info.dtu_sw_version", + key="dtu_info.dtu_sw_version", translation_key="dtu_sw_version", entity_category=EntityCategory.DIAGNOSTIC, version_translation_function=FCTN_GENERATE_DTU_VERSION_STRING, @@ -216,7 +217,7 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): is_dtu_sensor=True, ), HoymilesSensorEntityDescription( - key = "dtu_info.dtu_hw_version", + key="dtu_info.dtu_hw_version", translation_key="dtu_hw_version", entity_category=EntityCategory.DIAGNOSTIC, version_translation_function=FCTN_GENERATE_DTU_VERSION_STRING, @@ -224,44 +225,44 @@ class HoymilesDiagnosticEntityDescription(SensorEntityDescription): is_dtu_sensor=True, ), HoymilesSensorEntityDescription( - key = "pv_info[].pv_sw_version", + key="pv_info[].pv_sw_version", translation_key="pv_sw_version", entity_category=EntityCategory.DIAGNOSTIC, version_translation_function=FCTN_GENERATE_INVERTER_SW_VERSION_STRING, - version_prefix="V" + version_prefix="V", ), HoymilesSensorEntityDescription( - key = "pv_info[].pv_hw_version", + key="pv_info[].pv_hw_version", translation_key="pv_hw_version", entity_category=EntityCategory.DIAGNOSTIC, version_translation_function=FCTN_GENERATE_INVERTER_HW_VERSION_STRING, - version_prefix="H" + version_prefix="H", ), HoymilesSensorEntityDescription( - key = "dtu_info.signal_strength", + key="dtu_info.signal_strength", translation_key="signal_strength", native_unit_of_measurement=PERCENTAGE, entity_category=EntityCategory.DIAGNOSTIC, icon="mdi:wifi", is_dtu_sensor=True, ), - ) + async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up sensor platform.""" - hass_data = hass.data[DOMAIN][entry.entry_id] + hass_data = hass.data[DOMAIN][config_entry.entry_id] data_coordinator = hass_data[HASS_DATA_COORDINATOR] config_coordinator = hass_data[HASS_CONFIG_COORDINATOR] app_info_coordinator = hass_data[HASS_APP_INFO_COORDINATOR] - dtu_serial_number = entry.data[CONF_DTU_SERIAL_NUMBER] - inverters = entry.data[CONF_INVERTERS] - ports = entry.data[CONF_PORTS] + dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER] + inverters = config_entry.data[CONF_INVERTERS] + ports = config_entry.data[CONF_PORTS] sensors = [] # Real Data Sensors @@ -271,21 +272,54 @@ async def async_setup_entry( class_name = HoymilesEnergySensorEntity else: class_name = HoymilesDataSensorEntity - sensor_entities = get_sensors_for_description(entry, description, data_coordinator, class_name, dtu_serial_number, inverters, ports) + sensor_entities = get_sensors_for_description( + config_entry, + description, + data_coordinator, + class_name, + dtu_serial_number, + inverters, + ports, + ) sensors.extend(sensor_entities) # Diagnostic Sensors for description in CONFIG_DIAGNOSTIC_SENSORS: - sensor_entities = get_sensors_for_description(entry, description, config_coordinator, HoymilesDiagnosticSensorEntity, dtu_serial_number, inverters, ports) + sensor_entities = get_sensors_for_description( + config_entry, + description, + config_coordinator, + HoymilesDiagnosticSensorEntity, + dtu_serial_number, + inverters, + ports, + ) sensors.extend(sensor_entities) for description in APP_INFO_SENSORS: - sensor_entities = get_sensors_for_description(entry, description, app_info_coordinator, HoymilesDataSensorEntity, dtu_serial_number, inverters, ports) + sensor_entities = get_sensors_for_description( + config_entry, + description, + app_info_coordinator, + HoymilesDataSensorEntity, + dtu_serial_number, + inverters, + ports, + ) sensors.extend(sensor_entities) async_add_entities(sensors) -def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEntityDescription, coordinator: HoymilesCoordinatorEntity, class_name: SensorEntity, dtu_serial_number: str, inverters: list, ports: list) -> list[SensorEntity]: + +def get_sensors_for_description( + config_entry: ConfigEntry, + description: SensorEntityDescription, + coordinator: HoymilesCoordinatorEntity, + class_name: SensorEntity, + dtu_serial_number: str, + inverters: list, + ports: list, +) -> list[SensorEntity]: """Get sensors for the given description.""" sensors = [] @@ -293,7 +327,9 @@ def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEn if "" in description.key: for index, inverter_serial in enumerate(inverters): new_key = description.key.replace("", str(index)) - updated_description = dataclasses.replace(description, key=new_key, serial_number=inverter_serial) + updated_description = dataclasses.replace( + description, key=new_key, serial_number=inverter_serial + ) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) elif "" in description.key: @@ -301,12 +337,21 @@ def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEn inverter_serial = port["inverter_serial_number"] port_number = port["port_number"] new_key = str(description.key).replace("", str(index)) - new_translation_key = description.translation_key.replace("", str(port_number)) - updated_description = dataclasses.replace(description, key=new_key, translation_key=new_translation_key, serial_number=inverter_serial) + new_translation_key = description.translation_key.replace( + "", str(port_number) + ) + updated_description = dataclasses.replace( + description, + key=new_key, + translation_key=new_translation_key, + serial_number=inverter_serial, + ) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) else: - updated_description = dataclasses.replace(description, serial_number=dtu_serial_number) + updated_description = dataclasses.replace( + description, serial_number=dtu_serial_number + ) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) @@ -316,7 +361,12 @@ def get_sensors_for_description(config_entry: ConfigEntry, description: SensorEn class HoymilesDataSensorEntity(HoymilesCoordinatorEntity, SensorEntity): """Represents a sensor entity for Hoymiles data.""" - def __init__(self, config_entry: ConfigEntry, description: HoymilesSensorEntityDescription, coordinator: HoymilesCoordinatorEntity): + def __init__( + self, + config_entry: ConfigEntry, + description: HoymilesSensorEntityDescription, + coordinator: HoymilesCoordinatorEntity, + ): """Pass coordinator to CoordinatorEntity.""" super().__init__(config_entry, description, coordinator) @@ -335,7 +385,6 @@ def _handle_coordinator_update(self) -> None: self.update_state_value() super()._handle_coordinator_update() - @property def native_value(self): """Return the native value of the sensor.""" @@ -346,22 +395,29 @@ def assumed_state(self): """Return the assumed state of the sensor.""" return self._assumed_state - def update_state_value(self): """Update the state value of the sensor based on the coordinator data.""" - if self.coordinator is not None and (not hasattr(self.coordinator, "data") or self.coordinator.data is None): + if self.coordinator is not None and ( + not hasattr(self.coordinator, "data") or self.coordinator.data is None + ): self._native_value = 0.0 elif "[" in self._attribute_name and "]" in self._attribute_name: # Extracting the list index and attribute dynamically attribute_name, index = self._attribute_name.split("[") index = int(index.split("]")[0]) - nested_attribute = self._attribute_name.split("].")[1] if "]." in self._attribute_name else None + nested_attribute = ( + self._attribute_name.split("].")[1] + if "]." in self._attribute_name + else None + ) attribute = getattr(self.coordinator.data, attribute_name.split("[")[0], []) if index < len(attribute): if nested_attribute is not None: - self._native_value = getattr(attribute[index], nested_attribute, None) + self._native_value = getattr( + attribute[index], nested_attribute, None + ) else: self._native_value = attribute[index] else: @@ -374,28 +430,39 @@ def update_state_value(self): self._native_value = attribute else: - self._native_value = getattr(self.coordinator.data, self._attribute_name, None) - + self._native_value = getattr( + self.coordinator.data, self._attribute_name, None + ) if self._native_value is not None and self._conversion_factor is not None: self._native_value *= self._conversion_factor - if(self._native_value is not None and self._version_translation_function is not None): - self._native_value = getattr(hoymiles_wifi.utils, self._version_translation_function)(int(self._native_value)) + if ( + self._native_value is not None + and self._version_translation_function is not None + ): + self._native_value = getattr( + hoymiles_wifi.hoymiles, self._version_translation_function + )(int(self._native_value)) - if(self._native_value is not None and self._version_prefix is not None): + if self._native_value is not None and self._version_prefix is not None: self._native_value = f"{self._version_prefix}{self._native_value}" + class HoymilesEnergySensorEntity(HoymilesDataSensorEntity, RestoreSensor): """Represents an energy sensor entity for Hoymiles data.""" - def __init__(self, config_entry: ConfigEntry, description: HoymilesDiagnosticEntityDescription, coordinator: HoymilesCoordinatorEntity): + def __init__( + self, + config_entry: ConfigEntry, + description: HoymilesDiagnosticEntityDescription, + coordinator: HoymilesCoordinatorEntity, + ): """Initialize the HoymilesEnergySensorEntity.""" super().__init__(config_entry, description, coordinator) # Important to set to None to not mess with long term stats self._last_known_value = None - def schedule_midnight_reset(self, reset_sensor_value: bool = True): """Schedule the reset function to run again at the next midnight.""" now = datetime.datetime.now() @@ -419,7 +486,8 @@ def native_value(self): # For an energy sensor a value of 0 would mess up long term stats because of how total_increasing works if super_native_value == 0.0: _LOGGER.debug( - "Returning last known value instead of 0.0 for %s to avoid resetting total_increasing counter", self.name + "Returning last known value instead of 0.0 for %s to avoid resetting total_increasing counter", + self.name, ) self._assumed_state = True return self._last_known_value @@ -439,7 +507,9 @@ async def async_added_to_hass(self) -> None: self.schedule_midnight_reset(reset_sensor_value=False) -class HoymilesDiagnosticSensorEntity(HoymilesCoordinatorEntity, RestoreSensor, SensorEntity): +class HoymilesDiagnosticSensorEntity( + HoymilesCoordinatorEntity, RestoreSensor, SensorEntity +): """Represents a diagnostic sensor entity for Hoymiles data.""" def __init__(self, config_entry, description, coordinator): @@ -455,7 +525,6 @@ def __init__(self, config_entry, description, coordinator): self.update_state_value() self._last_known_value = self._native_value - @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" @@ -473,7 +542,6 @@ def native_value(self): self._assumed_state = False return self._native_value - def update_state_value(self): """Update the state value of the sensor.""" @@ -481,20 +549,30 @@ def update_state_value(self): attribute_parts = self._attribute_name.split("[") attribute_name = attribute_parts[0] index_range = attribute_parts[1].split("]")[0] - start, end = map(int, index_range.split('-')) + start, end = map(int, index_range.split("-")) - new_attribute_names = [f"{attribute_name}{i}" for i in range(start, end + 1)] - attribute_values = [str(getattr(self.coordinator.data, attr, "")) for attr in new_attribute_names] + new_attribute_names = [ + f"{attribute_name}{i}" for i in range(start, end + 1) + ] + attribute_values = [ + str(getattr(self.coordinator.data, attr, "")) + for attr in new_attribute_names + ] - if("" in attribute_values): + if "" in attribute_values: self._native_value = None else: self._native_value = self._separator.join(attribute_values) else: - self._native_value = getattr(self.coordinator.data, self._attribute_name, None) + self._native_value = getattr( + self.coordinator.data, self._attribute_name, None + ) - if(self._native_value is not None and self._conversion == ConversionAction.HEX): - self._native_value = self._separator.join(hex(int(value))[2:] for value in self._native_value.split(self._separator)).upper() + if self._native_value is not None and self._conversion == ConversionAction.HEX: + self._native_value = self._separator.join( + hex(int(value))[2:] + for value in self._native_value.split(self._separator) + ).upper() async def async_added_to_hass(self) -> None: """Call when entity about to be added to hass.""" @@ -502,4 +580,3 @@ async def async_added_to_hass(self) -> None: state = await self.async_get_last_sensor_data() if state: self.last_known_value = state.native_value - diff --git a/custom_components/hoymiles_wifi/strings.json b/custom_components/hoymiles_wifi/strings.json index 17268c8..0314c64 100644 --- a/custom_components/hoymiles_wifi/strings.json +++ b/custom_components/hoymiles_wifi/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Hoymiles HMS-XXXXW-2T connection", + "title": "Hoymiles DTU connection", "description": "If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi", "data": { "host": "[%key:common::config_flow::data::host%]", diff --git a/custom_components/hoymiles_wifi/util.py b/custom_components/hoymiles_wifi/util.py index 1487799..6e02254 100644 --- a/custom_components/hoymiles_wifi/util.py +++ b/custom_components/hoymiles_wifi/util.py @@ -2,29 +2,38 @@ from typing import Union -from homeassistant.config_entries import ConfigEntry -from hoymiles_wifi.inverter import Inverter -from hoymiles_wifi.utils import generate_inverter_serial_number +from hoymiles_wifi.dtu import DTU +from hoymiles_wifi.hoymiles import generate_inverter_serial_number from .error import CannotConnect -async def async_get_config_entry_data_for_host(host) -> tuple[str, list[str], list[dict[str, Union[str, int]]]]: +async def async_get_config_entry_data_for_host( + host, +) -> tuple[str, list[str], list[dict[str, Union[str, int]]]]: """Migrate data from version 1 to version 2.""" - inverter = Inverter(host) + dtu = DTU(host) - real_data = await inverter.async_get_real_data_new() + real_data = await dtu.async_get_real_data_new() if real_data is None: raise CannotConnect dtu_sn = real_data.device_serial_number - inverters = [generate_inverter_serial_number(sgs_data.serial_number) for sgs_data in real_data.sgs_data] - - ports = [{ - "inverter_serial_number": generate_inverter_serial_number(pv_data.serial_number), - "port_number": pv_data.port_number - } for pv_data in real_data.pv_data] + inverters = [ + generate_inverter_serial_number(sgs_data.serial_number) + for sgs_data in real_data.sgs_data + ] + + ports = [ + { + "inverter_serial_number": generate_inverter_serial_number( + pv_data.serial_number + ), + "port_number": pv_data.port_number, + } + for pv_data in real_data.pv_data + ] return dtu_sn, inverters, ports From 9697ae478bb2de0a483e856a90a16ec30f4494c7 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Fri, 15 Mar 2024 10:56:48 +0100 Subject: [PATCH 07/20] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41212b5..693d443 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Hoymiles for Home Assistant -This Home Assistant custom component utilizes the [hoymiles-wifi](https://github.com/suaveolent/hoymiles-wifi) Python library, allowing seamless integration with Hoymiles HMS microinverters, specifically designed for the HMS-XXXXW-2T series and Hoymiles DTU Wlite. +This Home Assistant custom component utilizes the [hoymiles-wifi](https://github.com/suaveolent/hoymiles-wifi) Python library, allowing seamless integration with Hoymiles HMS microinverters via Hoymiles DTUs and the HMS-XXXXW-2T microinverters. **Disclaimer: This custom component is an independent project and is not affiliated with Hoymiles. It has been developed to provide Home Assistant users with tools for interacting with Hoymiles HMS-XXXXW-2T series micro-inverters featuring integrated WiFi DTU. Any trademarks or product names mentioned are the property of their respective owners.** From 01a40be6a692f1d41d9c8a495cba0b5dca6680e4 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Mon, 25 Mar 2024 10:48:02 +0100 Subject: [PATCH 08/20] delete old device entries --- custom_components/hoymiles_wifi/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 93e1934..6128635 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -7,6 +7,7 @@ from homeassistant.const import CONF_HOST, Platform from homeassistant.core import Config, HomeAssistant from hoymiles_wifi.dtu import DTU +from homeassistant.helpers import device_registry as dr from .const import ( CONF_DTU_SERIAL_NUMBER, @@ -87,13 +88,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry data to the new entry schema.""" - # Get the current data from the config entry data = config_entry.data.copy() - # Check the version of the existing data current_version = data.get("version", 1) - # Perform migrations based on the version if current_version == 1: _LOGGER.info( "Migrating entry %s to version %s", config_entry.entry_id, CONFIG_VERSION @@ -115,7 +113,13 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> new[CONF_INVERTERS] = inverters new[CONF_PORTS] = ports - # Update the config entry with the new data + device_registry = dr.async_get(hass) + + for device_entry in dr.async_entries_for_config_entry( + device_registry, config_entry.entry_id + ): + device_registry.async_remove_device(device_entry.id) + hass.config_entries.async_update_entry(config_entry, data=new, version=2) _LOGGER.info( "Migration of entry %s to version %s successful", From 567ed5a5f37f3ab96612931ebfb9b2f9b0da39dc Mon Sep 17 00:00:00 2001 From: suaveolent Date: Mon, 25 Mar 2024 11:01:19 +0100 Subject: [PATCH 09/20] organize imports --- custom_components/hoymiles_wifi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 6128635..4fac340 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -6,8 +6,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform from homeassistant.core import Config, HomeAssistant -from hoymiles_wifi.dtu import DTU from homeassistant.helpers import device_registry as dr +from hoymiles_wifi.dtu import DTU from .const import ( CONF_DTU_SERIAL_NUMBER, From 0125963c48a4ea2531e5c0c59082e079f454ca51 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Mon, 25 Mar 2024 11:06:57 +0100 Subject: [PATCH 10/20] update device instead of deleting to preserve entities --- custom_components/hoymiles_wifi/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 4fac340..03c6d0d 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -118,7 +118,17 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> for device_entry in dr.async_entries_for_config_entry( device_registry, config_entry.entry_id ): - device_registry.async_remove_device(device_entry.id) + if device_entry.name == "Hoymiles HMS-XXXXW-T2": + device_name = "Inverter" + elif device_entry.name == "Hoymiles HMS-XXXXW-T2 DTU": + device_name = "DTU" + else: + continue + + device_registry.async_update_device( + device_entry.id, + name=device_name, + ) hass.config_entries.async_update_entry(config_entry, data=new, version=2) _LOGGER.info( From e82033956bb78aed81e6b15bcd874dab7ffa5c63 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Mon, 25 Mar 2024 11:25:43 +0100 Subject: [PATCH 11/20] allow to delete a device instead of trying to migrate it --- custom_components/hoymiles_wifi/__init__.py | 26 +++++++-------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index 03c6d0d..bc8d8c5 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, Platform from homeassistant.core import Config, HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.device_registry import DeviceEntry from hoymiles_wifi.dtu import DTU from .const import ( @@ -85,6 +85,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): return True +async def async_remove_config_entry_device( + hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry +) -> bool: + """Remove a config entry from a device.""" + return True + + async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Migrate old entry data to the new entry schema.""" @@ -113,23 +120,6 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> new[CONF_INVERTERS] = inverters new[CONF_PORTS] = ports - device_registry = dr.async_get(hass) - - for device_entry in dr.async_entries_for_config_entry( - device_registry, config_entry.entry_id - ): - if device_entry.name == "Hoymiles HMS-XXXXW-T2": - device_name = "Inverter" - elif device_entry.name == "Hoymiles HMS-XXXXW-T2 DTU": - device_name = "DTU" - else: - continue - - device_registry.async_update_device( - device_entry.id, - name=device_name, - ) - hass.config_entries.async_update_entry(config_entry, data=new, version=2) _LOGGER.info( "Migration of entry %s to version %s successful", From 3e0df2240d602f70f2382cd81dff7669caec41c8 Mon Sep 17 00:00:00 2001 From: T4s-H4g Date: Wed, 6 Mar 2024 18:32:57 +0100 Subject: [PATCH 12/20] added german language --- .../hoymiles_wifi/translations/de.json | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 custom_components/hoymiles_wifi/translations/de.json diff --git a/custom_components/hoymiles_wifi/translations/de.json b/custom_components/hoymiles_wifi/translations/de.json new file mode 100644 index 0000000..eab21f9 --- /dev/null +++ b/custom_components/hoymiles_wifi/translations/de.json @@ -0,0 +1,124 @@ +{ + "config": { + "step": { + "user": { + "title": "Hoymiles HMS-XXXXW-T2 Verbindung", + "description": "Wenn Sie Hilfe bei der Konfiguration benötigen, schauen Sie hier vorbei: https://github.com/suaveolent/ha-hoymiles-wifi", + "data": { + "host": "Host", + "update_interval": "Aktualisierungsintervall (Sekunden)", + "sensor_prefix": "Sensorpräfix" + } + } + }, + "error": { + "cannot_connect": "Verbindung nicht möglich." + }, + "abort": { + "already_configured": "Bereits konfiguriert." + } + }, + "entity": { + "binary_sensor": { + "dtu": { + "name": "DTU" + } + }, + "number": { + "limit_power_mypower": { + "name": "Leistungsbegrenzung" + } + }, + "sensor": { + "ac_power": { + "name": "AC-Leistung" + }, + "ac_daily_energy": { + "name": "AC-Tagesenergie" + }, + "grid_voltage": { + "name": "Netzspannung" + }, + "grid_frequency": { + "name": "Netzfrequenz" + }, + "inverter_power_factor": { + "name": "Leistungsfaktor" + }, + "inverter_temperature": { + "name": "Temperatur" + }, + "port_1_dc_voltage": { + "name": "Port 1 Gleichspannung" + }, + "port_1_dc_current": { + "name": "Port 1 Gleichstrom" + }, + "port_1_dc_power": { + "name": "Port 1 Leistung" + }, + "port_1_dc_total_energy": { + "name": "Port 1 DC-Gesamtenergie" + }, + "port_1_dc_daily_energy": { + "name": "Port 1 DC-Tagesenergie" + }, + "port_2_dc_voltage": { + "name": "Port 2 Gleichspannung" + }, + "port_2_dc_current": { + "name": "Port 2 Gleichstrom" + }, + "port_2_dc_power": { + "name": "Port 2 Leistung" + }, + "port_2_dc_total_energy": { + "name": "Port 2 DC-Gesamtenergie" + }, + "port_2_dc_daily_energy": { + "name": "Port 2 DC Tagesenergie" + }, + "wifi_ssid": { + "name": "WLAN-SSID" + }, + "meter_kind": { + "name": "Zählerhersteller und -modell" + }, + "mac_address": { + "name": "MAC-Adresse" + }, + "ip_address": { + "name": "IP Adresse" + }, + "dtu_ap_ssid": { + "name": "AP-SSID" + }, + "dtu_sw_version": { + "name": "SW-Version" + }, + "dtu_hw_version": { + "name": "HW-Version" + }, + "pv_sw_version": { + "name": "SW-Version" + }, + "pv_hw_version": { + "name": "HW-Version" + }, + "signal_strength": { + "name": "Signalstärke" + } + }, + "button": { + "restart": { + "name": "Neustart" + }, + "turn_off": { + "name": "Ausschalten" + }, + "turn_on": { + "name": "Einschalten" + } + } + } +} \ No newline at end of file From 748d475ab57942c92d3a24fb691bd868173b1a66 Mon Sep 17 00:00:00 2001 From: suaveolent <2163625+suaveolent@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:23:42 +0100 Subject: [PATCH 13/20] Create close_inactive_issues.yml --- .github/workflows/close_inactive_issues.yml | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/close_inactive_issues.yml diff --git a/.github/workflows/close_inactive_issues.yml b/.github/workflows/close_inactive_issues.yml new file mode 100644 index 0000000..d5cd3cf --- /dev/null +++ b/.github/workflows/close_inactive_issues.yml @@ -0,0 +1,22 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v5 + with: + days-before-issue-stale: 30 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} From 3cc7dabffa35dccff9c2599855ef0379bdf805b5 Mon Sep 17 00:00:00 2001 From: Nerdix <70015952+N3rdix@users.noreply.github.com> Date: Wed, 27 Mar 2024 07:40:06 +0100 Subject: [PATCH 14/20] Add URL for issue tracker --- custom_components/hoymiles_wifi/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/hoymiles_wifi/manifest.json b/custom_components/hoymiles_wifi/manifest.json index f375e6a..dfcba07 100644 --- a/custom_components/hoymiles_wifi/manifest.json +++ b/custom_components/hoymiles_wifi/manifest.json @@ -3,6 +3,7 @@ "config_flow": true, "dependencies": [], "documentation": "https://github.com/suaveolent/ha-hoymiles-wifi", + "issue_tracker": "https://github.com/suaveolent/ha-hoymiles-wifi/issues", "domain": "hoymiles_wifi", "iot_class": "local_polling", "name": "Hoymiles", From b3996fc1604e9c7c7c7a25511c2762875db771fb Mon Sep 17 00:00:00 2001 From: suaveolent Date: Wed, 27 Mar 2024 10:23:08 +0100 Subject: [PATCH 15/20] adapted translations --- .../hoymiles_wifi/translations/de.json | 40 ++++++++++++++++--- .../hoymiles_wifi/translations/en.json | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/custom_components/hoymiles_wifi/translations/de.json b/custom_components/hoymiles_wifi/translations/de.json index eab21f9..a1cc258 100644 --- a/custom_components/hoymiles_wifi/translations/de.json +++ b/custom_components/hoymiles_wifi/translations/de.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Hoymiles HMS-XXXXW-T2 Verbindung", + "title": "Hoymiles Verbindung", "description": "Wenn Sie Hilfe bei der Konfiguration benötigen, schauen Sie hier vorbei: https://github.com/suaveolent/ha-hoymiles-wifi", "data": { "host": "Host", @@ -55,7 +55,7 @@ "name": "Port 1 Gleichstrom" }, "port_1_dc_power": { - "name": "Port 1 Leistung" + "name": "Port 1 DC-Leistung" }, "port_1_dc_total_energy": { "name": "Port 1 DC-Gesamtenergie" @@ -70,19 +70,49 @@ "name": "Port 2 Gleichstrom" }, "port_2_dc_power": { - "name": "Port 2 Leistung" + "name": "Port 2 DC-Leistung" }, "port_2_dc_total_energy": { "name": "Port 2 DC-Gesamtenergie" }, "port_2_dc_daily_energy": { - "name": "Port 2 DC Tagesenergie" + "name": "Port 2 DC-Tagesenergie" + }, + "port_3_dc_voltage": { + "name": "Port 3 Gleichspannung" + }, + "port_3_dc_current": { + "name": "Port 3 Gleichstrom" + }, + "port_3_dc_power": { + "name": "Port 3 DC-Leistung" + }, + "port_3_dc_total_energy": { + "name": "Port 3 DC-Gesamtenergie" + }, + "port_3_dc_daily_energy": { + "name": "Port 3 DC-Tagesenergie" + }, + "port_4_dc_voltage": { + "name": "Port 4 Gleichspannung" + }, + "port_4_dc_current": { + "name": "Port 4 Gleichspannung" + }, + "port_4_dc_power": { + "name": "Port 4 DC-Leistung" + }, + "port_4_dc_total_energy": { + "name": "Port 4 DC-Gesamtenergie" + }, + "port_4_dc_daily_energy": { + "name": "Port 4 DC-Tagesenergie" }, "wifi_ssid": { "name": "WLAN-SSID" }, "meter_kind": { - "name": "Zählerhersteller und -modell" + "name": "Zählermodell" }, "mac_address": { "name": "MAC-Adresse" diff --git a/custom_components/hoymiles_wifi/translations/en.json b/custom_components/hoymiles_wifi/translations/en.json index 97c9619..8f4ddd9 100644 --- a/custom_components/hoymiles_wifi/translations/en.json +++ b/custom_components/hoymiles_wifi/translations/en.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Hoymiles HMS-XXXXW-2T connection", + "title": "Hoymiles connection", "description": "If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi", "data": { "host": "Host", From 7a4870a5b225e02470380e87981ee4ad701d7030 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Fri, 29 Mar 2024 08:53:08 +0100 Subject: [PATCH 16/20] optimize translation to support arbitrary number of ports --- custom_components/hoymiles_wifi/entity.py | 4 + custom_components/hoymiles_wifi/number.py | 6 +- custom_components/hoymiles_wifi/sensor.py | 15 ++-- custom_components/hoymiles_wifi/strings.json | 65 +++-------------- .../hoymiles_wifi/translations/de.json | 55 ++------------ .../hoymiles_wifi/translations/en.json | 73 ++++--------------- 6 files changed, 43 insertions(+), 175 deletions(-) diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index 6223954..e59df5e 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -24,6 +24,7 @@ class HoymilesEntityDescription(EntityDescription): is_dtu_sensor: bool = False serial_number: str = None + port_number: int = None class HoymilesEntity(Entity): @@ -42,6 +43,9 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): else "" ) self._attr_unique_id = f"hoymiles_{config_entry.entry_id}_{description.key}" + self._attr_translation_placeholders = { + "port_number": f"{description.port_number}" + } dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER] diff --git a/custom_components/hoymiles_wifi/number.py b/custom_components/hoymiles_wifi/number.py index 21f3d54..426e294 100644 --- a/custom_components/hoymiles_wifi/number.py +++ b/custom_components/hoymiles_wifi/number.py @@ -20,7 +20,7 @@ DOMAIN, HASS_CONFIG_COORDINATOR, ) -from .entity import HoymilesCoordinatorEntity +from .entity import HoymilesCoordinatorEntity, HoymilesEntityDescription class SetAction(Enum): @@ -35,7 +35,9 @@ class HoymilesNumberSensorEntityDescriptionMixin: @dataclass(frozen=True) -class HoymilesNumberSensorEntityDescription(NumberEntityDescription): +class HoymilesNumberSensorEntityDescription( + HoymilesEntityDescription, NumberEntityDescription +): """Describes Hoymiles number sensor entity.""" set_action: SetAction = None diff --git a/custom_components/hoymiles_wifi/sensor.py b/custom_components/hoymiles_wifi/sensor.py index 2843bc5..93825fc 100644 --- a/custom_components/hoymiles_wifi/sensor.py +++ b/custom_components/hoymiles_wifi/sensor.py @@ -130,7 +130,7 @@ class HoymilesDiagnosticEntityDescription( ), HoymilesSensorEntityDescription( key="pv_data[].voltage", - translation_key="port__dc_voltage", + translation_key="port_dc_voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, device_class=SensorDeviceClass.VOLTAGE, state_class=SensorStateClass.MEASUREMENT, @@ -138,7 +138,7 @@ class HoymilesDiagnosticEntityDescription( ), HoymilesSensorEntityDescription( key="pv_data[].current", - translation_key="port__dc_current", + translation_key="port_dc_current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, @@ -146,7 +146,7 @@ class HoymilesDiagnosticEntityDescription( ), HoymilesSensorEntityDescription( key="pv_data[].power", - translation_key="port__dc_power", + translation_key="port_dc_power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -154,14 +154,14 @@ class HoymilesDiagnosticEntityDescription( ), HoymilesSensorEntityDescription( key="pv_data[].energy_total", - translation_key="port__dc_total_energy", + translation_key="port_dc_total_energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), HoymilesSensorEntityDescription( key="pv_data[].energy_daily", - translation_key="port__dc_daily_energy", + translation_key="port_dc_daily_energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, @@ -337,14 +337,11 @@ def get_sensors_for_description( inverter_serial = port["inverter_serial_number"] port_number = port["port_number"] new_key = str(description.key).replace("", str(index)) - new_translation_key = description.translation_key.replace( - "", str(port_number) - ) updated_description = dataclasses.replace( description, key=new_key, - translation_key=new_translation_key, serial_number=inverter_serial, + port_number=port_number, ) sensor = class_name(config_entry, updated_description, coordinator) sensors.append(sensor) diff --git a/custom_components/hoymiles_wifi/strings.json b/custom_components/hoymiles_wifi/strings.json index 0314c64..8c8e7af 100644 --- a/custom_components/hoymiles_wifi/strings.json +++ b/custom_components/hoymiles_wifi/strings.json @@ -48,65 +48,20 @@ "inverter_temperature": { "name": "Temperature" }, - "port_1_dc_voltage": { - "name": "Port 1 DC voltage" + "port_dc_voltage": { + "name": "Port {port_number} DC voltage" }, - "port_1_dc_current": { - "name": "Port 1 DC current" + "port_dc_current": { + "name": "Port {port_number} DC current" }, - "port_1_dc_power": { - "name": "Port 1 DC power" + "port_dc_power": { + "name": "Port {port_number} DC power" }, - "port_1_dc_total_energy": { - "name": "Port 1 DC total energy" + "port_dc_total_energy": { + "name": "Port {port_number} DC total energy" }, - "port_1_dc_daily_energy": { - "name": "Port 1 DC daily energy" - }, - "port_2_dc_voltage": { - "name": "Port 2 DC voltage" - }, - "port_2_dc_current": { - "name": "Port 2 DC current" - }, - "port_2_dc_power": { - "name": "Port 2 DC power" - }, - "port_2_dc_total_energy": { - "name": "Port 2 DC total energy" - }, - "port_2_dc_daily_energy": { - "name": "Port 2 DC daily energy" - }, - "port_3_dc_voltage": { - "name": "Port 3 DC voltage" - }, - "port_3_dc_current": { - "name": "Port 3 DC current" - }, - "port_3_dc_power": { - "name": "Port 3 DC power" - }, - "port_3_dc_total_energy": { - "name": "Port 3 DC total energy" - }, - "port_3_dc_daily_energy": { - "name": "Port 3 DC daily energy" - }, - "port_4_dc_voltage": { - "name": "Port 4 DC voltage" - }, - "port_4_dc_current": { - "name": "Port 4 DC current" - }, - "port_4_dc_power": { - "name": "Port 4 DC power" - }, - "port_4_dc_total_energy": { - "name": "Port 4 DC total energy" - }, - "port_4_dc_daily_energy": { - "name": "Port 4 DC daily energy" + "port_dc_daily_energy": { + "name": "Port {port_number} DC daily energy" }, "wifi_ssid": { "name": "Wi-Fi SSID" diff --git a/custom_components/hoymiles_wifi/translations/de.json b/custom_components/hoymiles_wifi/translations/de.json index a1cc258..6305b4a 100644 --- a/custom_components/hoymiles_wifi/translations/de.json +++ b/custom_components/hoymiles_wifi/translations/de.json @@ -49,64 +49,19 @@ "name": "Temperatur" }, "port_1_dc_voltage": { - "name": "Port 1 Gleichspannung" + "name": "Port {port_number} Gleichspannung" }, "port_1_dc_current": { - "name": "Port 1 Gleichstrom" + "name": "Port {port_number} Gleichstrom" }, "port_1_dc_power": { - "name": "Port 1 DC-Leistung" + "name": "Port {port_number} DC-Leistung" }, "port_1_dc_total_energy": { - "name": "Port 1 DC-Gesamtenergie" + "name": "Port {port_numbe} DC-Gesamtenergie" }, "port_1_dc_daily_energy": { - "name": "Port 1 DC-Tagesenergie" - }, - "port_2_dc_voltage": { - "name": "Port 2 Gleichspannung" - }, - "port_2_dc_current": { - "name": "Port 2 Gleichstrom" - }, - "port_2_dc_power": { - "name": "Port 2 DC-Leistung" - }, - "port_2_dc_total_energy": { - "name": "Port 2 DC-Gesamtenergie" - }, - "port_2_dc_daily_energy": { - "name": "Port 2 DC-Tagesenergie" - }, - "port_3_dc_voltage": { - "name": "Port 3 Gleichspannung" - }, - "port_3_dc_current": { - "name": "Port 3 Gleichstrom" - }, - "port_3_dc_power": { - "name": "Port 3 DC-Leistung" - }, - "port_3_dc_total_energy": { - "name": "Port 3 DC-Gesamtenergie" - }, - "port_3_dc_daily_energy": { - "name": "Port 3 DC-Tagesenergie" - }, - "port_4_dc_voltage": { - "name": "Port 4 Gleichspannung" - }, - "port_4_dc_current": { - "name": "Port 4 Gleichspannung" - }, - "port_4_dc_power": { - "name": "Port 4 DC-Leistung" - }, - "port_4_dc_total_energy": { - "name": "Port 4 DC-Gesamtenergie" - }, - "port_4_dc_daily_energy": { - "name": "Port 4 DC-Tagesenergie" + "name": "Port {port_number} DC-Tagesenergie" }, "wifi_ssid": { "name": "WLAN-SSID" diff --git a/custom_components/hoymiles_wifi/translations/en.json b/custom_components/hoymiles_wifi/translations/en.json index 8f4ddd9..8c8e7af 100644 --- a/custom_components/hoymiles_wifi/translations/en.json +++ b/custom_components/hoymiles_wifi/translations/en.json @@ -2,20 +2,20 @@ "config": { "step": { "user": { - "title": "Hoymiles connection", + "title": "Hoymiles DTU connection", "description": "If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi", "data": { - "host": "Host", + "host": "[%key:common::config_flow::data::host%]", "update_interval": "Update Interval (seconds)", "sensor_prefix" : "Sensor Prefix" } } }, "error": { - "cannot_connect": "Failed to connect." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "Already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "entity": { @@ -48,65 +48,20 @@ "inverter_temperature": { "name": "Temperature" }, - "port_1_dc_voltage": { - "name": "Port 1 DC voltage" + "port_dc_voltage": { + "name": "Port {port_number} DC voltage" }, - "port_1_dc_current": { - "name": "Port 1 DC current" + "port_dc_current": { + "name": "Port {port_number} DC current" }, - "port_1_dc_power": { - "name": "Port 1 DC power" + "port_dc_power": { + "name": "Port {port_number} DC power" }, - "port_1_dc_total_energy": { - "name": "Port 1 DC total energy" + "port_dc_total_energy": { + "name": "Port {port_number} DC total energy" }, - "port_1_dc_daily_energy": { - "name": "Port 1 DC daily energy" - }, - "port_2_dc_voltage": { - "name": "Port 2 DC voltage" - }, - "port_2_dc_current": { - "name": "Port 2 DC current" - }, - "port_2_dc_power": { - "name": "Port 2 DC power" - }, - "port_2_dc_total_energy": { - "name": "Port 2 DC total energy" - }, - "port_2_dc_daily_energy": { - "name": "Port 2 DC daily energy" - }, - "port_3_dc_voltage": { - "name": "Port 3 DC voltage" - }, - "port_3_dc_current": { - "name": "Port 3 DC current" - }, - "port_3_dc_power": { - "name": "Port 3 DC power" - }, - "port_3_dc_total_energy": { - "name": "Port 3 DC total energy" - }, - "port_3_dc_daily_energy": { - "name": "Port 3 DC daily energy" - }, - "port_4_dc_voltage": { - "name": "Port 4 DC voltage" - }, - "port_4_dc_current": { - "name": "Port 4 DC current" - }, - "port_4_dc_power": { - "name": "Port 4 DC power" - }, - "port_4_dc_total_energy": { - "name": "Port 4 DC total energy" - }, - "port_4_dc_daily_energy": { - "name": "Port 4 DC daily energy" + "port_dc_daily_energy": { + "name": "Port {port_number} DC daily energy" }, "wifi_ssid": { "name": "Wi-Fi SSID" From 646913c583885cb32fcfcf3f36d1b7c8eba9773d Mon Sep 17 00:00:00 2001 From: suaveolent Date: Fri, 29 Mar 2024 09:15:38 +0100 Subject: [PATCH 17/20] device name translation support --- custom_components/hoymiles_wifi/config_flow.py | 4 ---- custom_components/hoymiles_wifi/const.py | 3 +-- custom_components/hoymiles_wifi/entity.py | 15 ++++----------- custom_components/hoymiles_wifi/strings.json | 8 ++++++++ .../hoymiles_wifi/translations/de.json | 8 ++++++++ .../hoymiles_wifi/translations/en.json | 8 ++++++++ 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/custom_components/hoymiles_wifi/config_flow.py b/custom_components/hoymiles_wifi/config_flow.py index 0bf7d72..88867ea 100644 --- a/custom_components/hoymiles_wifi/config_flow.py +++ b/custom_components/hoymiles_wifi/config_flow.py @@ -16,7 +16,6 @@ CONF_DTU_SERIAL_NUMBER, CONF_INVERTERS, CONF_PORTS, - CONF_SENSOR_PREFIX, CONF_UPDATE_INTERVAL, CONFIG_VERSION, DEFAULT_UPDATE_INTERVAL_SECONDS, @@ -38,7 +37,6 @@ vol.Coerce(int), vol.Range(min=timedelta(seconds=MIN_UPDATE_INTERVAL_SECONDS).seconds), ), - vol.Optional(CONF_SENSOR_PREFIX): str, } ) @@ -60,7 +58,6 @@ async def async_step_user( update_interval = user_input.get( CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS ) - sensor_prefix = user_input.get(CONF_SENSOR_PREFIX, "") try: dtu_sn, inverters, ports = await async_get_config_entry_data_for_host( @@ -73,7 +70,6 @@ async def async_step_user( title=host, data={ CONF_HOST: host, - CONF_SENSOR_PREFIX: sensor_prefix, CONF_UPDATE_INTERVAL: update_interval, CONF_DTU_SERIAL_NUMBER: dtu_sn, CONF_INVERTERS: inverters, diff --git a/custom_components/hoymiles_wifi/const.py b/custom_components/hoymiles_wifi/const.py index 3aa5ce1..00bee78 100644 --- a/custom_components/hoymiles_wifi/const.py +++ b/custom_components/hoymiles_wifi/const.py @@ -3,13 +3,12 @@ NAME = "Hoymiles HMS-XXXXW-2T" DOMAIN = "hoymiles_wifi" DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "0.1.2" +VERSION = "0.1.5" CONFIG_VERSION = 2 ISSUE_URL = "https://github.com/suaveolent/ha-hoymiles-wifi/issues" CONF_UPDATE_INTERVAL = "update_interval" -CONF_SENSOR_PREFIX = "sensor_prefix" CONF_DTU_SERIAL_NUMBER = "dtu_serial_number" CONF_INVERTERS = "inverters" CONF_PORTS = "ports" diff --git a/custom_components/hoymiles_wifi/entity.py b/custom_components/hoymiles_wifi/entity.py index e59df5e..c768ffc 100644 --- a/custom_components/hoymiles_wifi/entity.py +++ b/custom_components/hoymiles_wifi/entity.py @@ -12,7 +12,7 @@ get_inverter_model_name, ) -from .const import CONF_DTU_SERIAL_NUMBER, CONF_SENSOR_PREFIX, DOMAIN +from .const import CONF_DTU_SERIAL_NUMBER, DOMAIN from .coordinator import HoymilesDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) @@ -37,11 +37,6 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): super().__init__() self.entity_description = description self._config_entry = config_entry - self._sensor_prefix = ( - f" {config_entry.data.get(CONF_SENSOR_PREFIX)} " - if config_entry.data.get(CONF_SENSOR_PREFIX) - else "" - ) self._attr_unique_id = f"hoymiles_{config_entry.entry_id}_{description.key}" self._attr_translation_placeholders = { "port_number": f"{description.port_number}" @@ -50,19 +45,17 @@ def __init__(self, config_entry: ConfigEntry, description: EntityDescription): dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER] if self.entity_description.is_dtu_sensor is True: - device_name = "DTU" + device_translation_key = "dtu" device_model = get_dtu_model_name(self.entity_description.serial_number) else: device_model = get_inverter_model_name( self.entity_description.serial_number ) - device_name = "Inverter" - - device_name += self._sensor_prefix + device_translation_key = "inverter" device_info = DeviceInfo( identifiers={(DOMAIN, self.entity_description.serial_number)}, - name=device_name, + translation_key=device_translation_key, manufacturer="Hoymiles", serial_number=self.entity_description.serial_number, model=device_model, diff --git a/custom_components/hoymiles_wifi/strings.json b/custom_components/hoymiles_wifi/strings.json index 8c8e7af..aa9a001 100644 --- a/custom_components/hoymiles_wifi/strings.json +++ b/custom_components/hoymiles_wifi/strings.json @@ -105,5 +105,13 @@ "name" : "Turn on" } } + }, + "device": { + "inverter": { + "name": "Inverter" + }, + "dtu": { + "name": "DTU" + } } } \ No newline at end of file diff --git a/custom_components/hoymiles_wifi/translations/de.json b/custom_components/hoymiles_wifi/translations/de.json index 6305b4a..2f9a103 100644 --- a/custom_components/hoymiles_wifi/translations/de.json +++ b/custom_components/hoymiles_wifi/translations/de.json @@ -105,5 +105,13 @@ "name": "Einschalten" } } + }, + "device": { + "inverter": { + "name": "Wechselrichter" + }, + "dtu": { + "name": "DTU" + } } } \ No newline at end of file diff --git a/custom_components/hoymiles_wifi/translations/en.json b/custom_components/hoymiles_wifi/translations/en.json index 8c8e7af..aa9a001 100644 --- a/custom_components/hoymiles_wifi/translations/en.json +++ b/custom_components/hoymiles_wifi/translations/en.json @@ -105,5 +105,13 @@ "name" : "Turn on" } } + }, + "device": { + "inverter": { + "name": "Inverter" + }, + "dtu": { + "name": "DTU" + } } } \ No newline at end of file From 5a9963b4bf24bfa98f59c74928dc125076a99ee1 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Fri, 29 Mar 2024 09:22:43 +0100 Subject: [PATCH 18/20] speed up start --- custom_components/hoymiles_wifi/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/hoymiles_wifi/__init__.py b/custom_components/hoymiles_wifi/__init__.py index bc8d8c5..ed8797e 100644 --- a/custom_components/hoymiles_wifi/__init__.py +++ b/custom_components/hoymiles_wifi/__init__.py @@ -1,4 +1,5 @@ """Platform for retrieving values of a Hoymiles inverter.""" + import asyncio from datetime import timedelta import logging @@ -77,9 +78,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][entry.entry_id] = hass_data await data_coordinator.async_config_entry_first_refresh() - await asyncio.sleep(5) + await asyncio.sleep(2) await config_coordinator.async_config_entry_first_refresh() - await asyncio.sleep(5) + await asyncio.sleep(2) await app_info_update_coordinator.async_config_entry_first_refresh() return True From 627c1d263036463178e18c68c23a2e9f34128da4 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Fri, 29 Mar 2024 09:29:39 +0100 Subject: [PATCH 19/20] fix translations --- custom_components/hoymiles_wifi/translations/de.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/hoymiles_wifi/translations/de.json b/custom_components/hoymiles_wifi/translations/de.json index 2f9a103..3fa7ab9 100644 --- a/custom_components/hoymiles_wifi/translations/de.json +++ b/custom_components/hoymiles_wifi/translations/de.json @@ -48,16 +48,16 @@ "inverter_temperature": { "name": "Temperatur" }, - "port_1_dc_voltage": { + "port_dc_voltage": { "name": "Port {port_number} Gleichspannung" }, - "port_1_dc_current": { + "port_dc_current": { "name": "Port {port_number} Gleichstrom" }, - "port_1_dc_power": { + "port_dc_power": { "name": "Port {port_number} DC-Leistung" }, - "port_1_dc_total_energy": { + "port_dc_total_energy": { "name": "Port {port_numbe} DC-Gesamtenergie" }, "port_1_dc_daily_energy": { From a483ab721c1c3fd0b09d5d3d8689d1b9f0aedbe6 Mon Sep 17 00:00:00 2001 From: suaveolent Date: Fri, 29 Mar 2024 09:31:03 +0100 Subject: [PATCH 20/20] fix german translation --- custom_components/hoymiles_wifi/translations/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/hoymiles_wifi/translations/de.json b/custom_components/hoymiles_wifi/translations/de.json index 3fa7ab9..0fb3a40 100644 --- a/custom_components/hoymiles_wifi/translations/de.json +++ b/custom_components/hoymiles_wifi/translations/de.json @@ -60,7 +60,7 @@ "port_dc_total_energy": { "name": "Port {port_numbe} DC-Gesamtenergie" }, - "port_1_dc_daily_energy": { + "port_dc_daily_energy": { "name": "Port {port_number} DC-Tagesenergie" }, "wifi_ssid": {