From 80eea784dbb5290249067ef4b73f071fa8b3ba9a Mon Sep 17 00:00:00 2001 From: Nathan Marlor Date: Tue, 28 Mar 2023 14:00:33 +0100 Subject: [PATCH 1/5] Automatically configure energy dashboard through config flow --- .vscode/launch.json | 1 + .../foxess_modbus/config_flow.py | 43 ++++++++++++++++++- custom_components/foxess_modbus/const.py | 2 + .../foxess_modbus/translations/en.json | 3 +- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index cc5337a7..f5c3d6c7 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,7 @@ "request": "attach", "port": 5678, "host": "localhost", + "justMyCode": false, "pathMappings": [ { "localRoot": "${workspaceFolder}", diff --git a/custom_components/foxess_modbus/config_flow.py b/custom_components/foxess_modbus/config_flow.py index cbcce559..f7258a43 100755 --- a/custom_components/foxess_modbus/config_flow.py +++ b/custom_components/foxess_modbus/config_flow.py @@ -7,6 +7,13 @@ import voluptuous as vol from custom_components.foxess_modbus import ModbusClient from homeassistant import config_entries +from homeassistant.components.energy import data +from homeassistant.components.energy.data import BatterySourceType +from homeassistant.components.energy.data import EnergyPreferencesUpdate +from homeassistant.components.energy.data import FlowFromGridSourceType +from homeassistant.components.energy.data import FlowToGridSourceType +from homeassistant.components.energy.data import GridSourceType +from homeassistant.components.energy.data import SolarSourceType from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.selector import selector @@ -16,6 +23,7 @@ from .const import ADD_ANOTHER from .const import CONFIG_SAVE_TIME from .const import DOMAIN +from .const import ENERGY_DASHBOARD from .const import FRIENDLY_NAME from .const import INVERTER_BASE from .const import INVERTER_CONN @@ -57,7 +65,8 @@ def __init__(self, config=None) -> None: { vol.Required(INVERTER_TYPE, default="TCP"): selector( {"select": {"options": ["TCP", "SERIAL"]}} - ) + ), + vol.Required(ENERGY_DASHBOARD, default=True): bool, } ) @@ -102,6 +111,9 @@ async def async_step_user(self, user_input: dict[str, Any] = None): """Handle a flow initialized by the user.""" self._errors = {} if user_input is not None: + if user_input[ENERGY_DASHBOARD]: + await self._setup_energy_dashboard() + if user_input[INVERTER_TYPE] == TCP: return await self.async_step_tcp(user_input) else: @@ -211,6 +223,35 @@ async def _autodetect_modbus(self, inv_type, host, slave): self._errors["base"] = "modbus_error" return False, None + async def _setup_energy_dashboard(self): + """Setup Energy Dashboard""" + manager = await data.async_get_manager(self.hass) + energy_prefs = EnergyPreferencesUpdate( + energy_sources=[ + SolarSourceType( + type="solar", stat_energy_from="sensor.solar_sum_total" + ), + GridSourceType( + type="grid", + flow_from=[ + FlowFromGridSourceType( + stat_energy_from="sensor.grid_consumption_sum_total" + ) + ], + flow_to=[ + FlowToGridSourceType(stat_energy_to="sensor.feed_in_sum_total") + ], + cost_adjustment_day=0, + ), + BatterySourceType( + type="battery", + stat_energy_to="sensor.battery_charge_total", + stat_energy_from="sensor.battery_discharge_total", + ), + ] + ) + await manager.async_update(energy_prefs) + @staticmethod @callback def async_get_options_flow(config_entry): diff --git a/custom_components/foxess_modbus/const.py b/custom_components/foxess_modbus/const.py index ced31b2a..8cd54c16 100755 --- a/custom_components/foxess_modbus/const.py +++ b/custom_components/foxess_modbus/const.py @@ -48,6 +48,8 @@ CONNECTION = "connection" MODBUS = "modbus" +ENERGY_DASHBOARD = "energy_dashboard" + # Defaults DEFAULT_NAME = DOMAIN diff --git a/custom_components/foxess_modbus/translations/en.json b/custom_components/foxess_modbus/translations/en.json index d345d21e..1e711682 100755 --- a/custom_components/foxess_modbus/translations/en.json +++ b/custom_components/foxess_modbus/translations/en.json @@ -4,7 +4,8 @@ "user": { "description": "Please choose how your modbus adapter is connected", "data": { - "modbus_type": "Modbus Type" + "modbus_type": "Modbus Type", + "energy_dashboard": "Automatically configure Energy Dashboard" } }, "tcp": { From f2d04f739da6dec7da6f0dde905d0701479228f1 Mon Sep 17 00:00:00 2001 From: Nathan Marlor Date: Tue, 28 Mar 2023 14:04:53 +0100 Subject: [PATCH 2/5] Fixed hassfest --- custom_components/foxess_modbus/manifest.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/custom_components/foxess_modbus/manifest.json b/custom_components/foxess_modbus/manifest.json index ada06748..78a9b037 100755 --- a/custom_components/foxess_modbus/manifest.json +++ b/custom_components/foxess_modbus/manifest.json @@ -1,13 +1,20 @@ { "domain": "foxess_modbus", "name": "FoxESS - Modbus", - "codeowners": ["@nathanmarlor"], + "codeowners": [ + "@nathanmarlor" + ], "config_flow": true, - "dependencies": ["integration"], + "dependencies": [ + "energy", + "integration" + ], "documentation": "https://github.com/nathanmarlor/foxess_modbus", "integration_type": "service", "iot_class": "local_push", "issue_tracker": "https://github.com/nathanmarlor/foxess_modbus/issues", - "requirements": ["pymodbus==3.1.3"], + "requirements": [ + "pymodbus==3.1.3" + ], "version": "1.0.0" -} +} \ No newline at end of file From 0321d1bfa23eeb65917e1ab4563976a8bbc10b32 Mon Sep 17 00:00:00 2001 From: Nathan Marlor Date: Thu, 30 Mar 2023 11:19:11 +0100 Subject: [PATCH 3/5] Updated to account for friendly name and use the latest total sensors --- .../foxess_modbus/config_flow.py | 104 +++++++++++++----- .../foxess_modbus/translations/en.json | 8 +- 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/custom_components/foxess_modbus/config_flow.py b/custom_components/foxess_modbus/config_flow.py index f7258a43..9b770a47 100755 --- a/custom_components/foxess_modbus/config_flow.py +++ b/custom_components/foxess_modbus/config_flow.py @@ -66,7 +66,6 @@ def __init__(self, config=None) -> None: vol.Required(INVERTER_TYPE, default="TCP"): selector( {"select": {"options": ["TCP", "SERIAL"]}} ), - vol.Required(ENERGY_DASHBOARD, default=True): bool, } ) @@ -101,6 +100,12 @@ def __init__(self, config=None) -> None: } ) + self._energy_dash = vol.Schema( + { + vol.Required(ENERGY_DASHBOARD, default=False): bool, + } + ) + async def async_step_init(self, user_input: dict[str, Any] = None): """Handle a flow initialized by the user.""" self._errors = {} @@ -111,9 +116,6 @@ async def async_step_user(self, user_input: dict[str, Any] = None): """Handle a flow initialized by the user.""" self._errors = {} if user_input is not None: - if user_input[ENERGY_DASHBOARD]: - await self._setup_energy_dashboard() - if user_input[INVERTER_TYPE] == TCP: return await self.async_step_tcp(user_input) else: @@ -138,7 +140,7 @@ async def async_step_tcp(self, user_input: dict[str, Any] = None): errors=self._errors, ) elif result: - return self.async_create_entry(title=_TITLE, data=self._data) + return await self.async_step_energy() return self.async_show_form( step_id="tcp", data_schema=self._modbus_tcp_schema, errors=self._errors @@ -160,7 +162,7 @@ async def async_step_serial(self, user_input: dict[str, Any] = None): errors=self._errors, ) elif result: - return self.async_create_entry(title=_TITLE, data=self._data) + return await self.async_step_energy() return self.async_show_form( step_id="serial", @@ -168,6 +170,19 @@ async def async_step_serial(self, user_input: dict[str, Any] = None): errors=self._errors, ) + async def async_step_energy(self, user_input: dict[str, Any] = None): + """Handle a flow initialized by the user.""" + if user_input is not None: + if user_input[ENERGY_DASHBOARD]: + await self._setup_energy_dashboard() + return self.async_create_entry(title=_TITLE, data=self._data) + + return self.async_show_form( + step_id="energy", + data_schema=self._energy_dash, + errors=self._errors, + ) + def detect_duplicate(self, inv_type, host, friendly_name): """Detect duplicates""" if host in self._data[inv_type]: @@ -226,32 +241,63 @@ async def _autodetect_modbus(self, inv_type, host, slave): async def _setup_energy_dashboard(self): """Setup Energy Dashboard""" manager = await data.async_get_manager(self.hass) - energy_prefs = EnergyPreferencesUpdate( - energy_sources=[ - SolarSourceType( - type="solar", stat_energy_from="sensor.solar_sum_total" - ), - GridSourceType( - type="grid", - flow_from=[ - FlowFromGridSourceType( - stat_energy_from="sensor.grid_consumption_sum_total" - ) - ], - flow_to=[ - FlowToGridSourceType(stat_energy_to="sensor.feed_in_sum_total") - ], - cost_adjustment_day=0, - ), - BatterySourceType( - type="battery", - stat_energy_to="sensor.battery_charge_total", - stat_energy_from="sensor.battery_discharge_total", - ), - ] + + friendly_names = self._get_friendly_names(self._data) + + def _prefix_name(name): + if name != "": + return f"sensor.{name}_" + else: + return "sensor." + + energy_prefs = EnergyPreferencesUpdate(energy_sources=[]) + for name in friendly_names: + name_prefix = _prefix_name(name) + energy_prefs["energy_sources"].extend( + [ + SolarSourceType( + type="solar", stat_energy_from=f"{name_prefix}pv1_energy_total" + ), + SolarSourceType( + type="solar", stat_energy_from=f"{name_prefix}pv2_energy_total" + ), + BatterySourceType( + type="battery", + stat_energy_to=f"{name_prefix}battery_charge_total", + stat_energy_from=f"{name_prefix}battery_discharge_total", + ), + ] + ) + + grid_source = GridSourceType( + type="grid", flow_from=[], flow_to=[], cost_adjustment_day=0 ) + for name in friendly_names: + name_prefix = _prefix_name(name) + grid_source["flow_from"].append( + FlowFromGridSourceType( + stat_energy_from=f"{name_prefix}grid_consumption_energy_total" + ) + ) + grid_source["flow_to"].append( + FlowToGridSourceType( + stat_energy_to=f"{name_prefix}feed_in_energy_total" + ) + ) + energy_prefs["energy_sources"].append(grid_source) + await manager.async_update(energy_prefs) + def _get_friendly_names(self, data_dict): + """Return all friendly names""" + names = [] + inverters = {k: v for k, v in data_dict.items() if k in (TCP, SERIAL)} + for _, host_dict in inverters.items(): + for _, name_dict in host_dict.items(): + names.extend(list(name_dict.keys())) + + return names + @staticmethod @callback def async_get_options_flow(config_entry): diff --git a/custom_components/foxess_modbus/translations/en.json b/custom_components/foxess_modbus/translations/en.json index 1e711682..0cce7826 100755 --- a/custom_components/foxess_modbus/translations/en.json +++ b/custom_components/foxess_modbus/translations/en.json @@ -4,8 +4,7 @@ "user": { "description": "Please choose how your modbus adapter is connected", "data": { - "modbus_type": "Modbus Type", - "energy_dashboard": "Automatically configure Energy Dashboard" + "modbus_type": "Modbus Type" } }, "tcp": { @@ -26,6 +25,11 @@ "modbus_slave": "Modbus Slave", "add_another": "Add another device" } + }, + "energy": { + "data": { + "energy_dashboard": "Automatically configure a basic Energy Dashboard? Warning: this will overwrite any existing settings." + } } }, "error": { From dcd84d77e2d310e5f735847a7173511b0ef9004b Mon Sep 17 00:00:00 2001 From: Nathan Marlor Date: Thu, 30 Mar 2023 11:24:20 +0100 Subject: [PATCH 4/5] Fixed linting --- custom_components/foxess_modbus/manifest.json | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/custom_components/foxess_modbus/manifest.json b/custom_components/foxess_modbus/manifest.json index 78a9b037..f6242673 100755 --- a/custom_components/foxess_modbus/manifest.json +++ b/custom_components/foxess_modbus/manifest.json @@ -1,20 +1,13 @@ { "domain": "foxess_modbus", "name": "FoxESS - Modbus", - "codeowners": [ - "@nathanmarlor" - ], + "codeowners": ["@nathanmarlor"], "config_flow": true, - "dependencies": [ - "energy", - "integration" - ], + "dependencies": ["energy", "integration"], "documentation": "https://github.com/nathanmarlor/foxess_modbus", "integration_type": "service", "iot_class": "local_push", "issue_tracker": "https://github.com/nathanmarlor/foxess_modbus/issues", - "requirements": [ - "pymodbus==3.1.3" - ], + "requirements": ["pymodbus==3.1.3"], "version": "1.0.0" -} \ No newline at end of file +} From 895e07e53688b89f4944428b8ac092c1c0bc20c7 Mon Sep 17 00:00:00 2001 From: Nathan Marlor Date: Thu, 30 Mar 2023 13:06:14 +0100 Subject: [PATCH 5/5] Updated messaging --- custom_components/foxess_modbus/translations/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/foxess_modbus/translations/en.json b/custom_components/foxess_modbus/translations/en.json index 0cce7826..49608be8 100755 --- a/custom_components/foxess_modbus/translations/en.json +++ b/custom_components/foxess_modbus/translations/en.json @@ -27,8 +27,9 @@ } }, "energy": { + "description": "Warning: this will overwrite any existing settings. \nWe recommend checking the configuration at (https://your-ha.com/config/energy)", "data": { - "energy_dashboard": "Automatically configure a basic Energy Dashboard? Warning: this will overwrite any existing settings." + "energy_dashboard": "Automatically configure the Energy Dashboard" } } },