From ba698d7682799497fb57743f5f1c1990908ada22 Mon Sep 17 00:00:00 2001 From: Nathan Marlor Date: Thu, 2 Feb 2023 08:58:29 +0000 Subject: [PATCH] Added switch to use custom charge profile --- README.md | 12 +++--- custom_components/foxess_em/__init__.py | 4 ++ .../foxess_em/charge/charge_service.py | 19 +++++++-- .../foxess_em/charge/charge_switch.py | 8 ++++ custom_components/foxess_em/config_flow.py | 5 +++ custom_components/foxess_em/const.py | 3 +- .../foxess_em/fox/fox_cloud_service.py | 39 ++++++++----------- .../foxess_em/translations/en.json | 6 ++- 8 files changed, 61 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index deb4e5d..28b30b1 100755 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ Enter your desired battery parameters: - **Day Buffer**: As above, but for the day - **Battery Capacity**: Capacity of battery in kWh - **Minimum SoC**: Minimum State of Charge as set in the FoxESS App +- **Charge Rate**: Nominal charge rate in A - for a 3.6kw inverter this should be ~18A ![Battery Params](images/config-step-3.png) @@ -135,11 +136,12 @@ Notes: Description of switches: -| Switch | Description | -| ------------------- | ---------------------------------------------------------------------------------------- | -| Boost Charge (+1kW) | Adds 1kW to the charge needed sensor. Resets after the charge period. | -| Disable Auto Charge | Prevents the integration from changing FoxESS settings to auto-charge or setting Min-SoC | -| Full Charge | Fully charges the battery during off-peak. Resets after the charge period. | +| Switch | Description | +| --------------------- | ------------------------------------------------------------------------------------------ | +| Boost Charge (+1kW) | Adds 1kW to the charge needed sensor. Resets after the charge period. | +| Disable Auto Charge | Prevents the integration from changing FoxESS settings to auto-charge or setting Min-SoC | +| Full Charge | Fully charges the battery during off-peak. Resets after the charge period. | +| Custom Charge Profile | Uses a custom charge profile which reduces charge current when > 90% to aid with balancing | diff --git a/custom_components/foxess_em/__init__.py b/custom_components/foxess_em/__init__.py index 40cdfad..4ad947b 100755 --- a/custom_components/foxess_em/__init__.py +++ b/custom_components/foxess_em/__init__.py @@ -21,6 +21,7 @@ from .const import AUX_POWER from .const import BATTERY_CAPACITY from .const import BATTERY_SOC +from .const import CHARGE_RATE from .const import DAWN_BUFFER from .const import DAY_BUFFER from .const import DOMAIN @@ -75,6 +76,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): capacity = entry.data.get(BATTERY_CAPACITY) dawn_buffer = entry.data.get(DAWN_BUFFER) day_buffer = entry.data.get(DAY_BUFFER) + # Added for 1.6.1 + charge_rate = entry.data.get(CHARGE_RATE, 18) session = async_get_clientsession(hass) solcast_client = SolcastApiClient(solcast_api_key, SOLCAST_URL, session) @@ -114,6 +117,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): eco_end_time, battery_soc, user_min_soc, + charge_rate, ) hass.data[DOMAIN][entry.entry_id] = { diff --git a/custom_components/foxess_em/charge/charge_service.py b/custom_components/foxess_em/charge/charge_service.py index 146f83e..b9053e0 100755 --- a/custom_components/foxess_em/charge/charge_service.py +++ b/custom_components/foxess_em/charge/charge_service.py @@ -16,7 +16,6 @@ from ..fox.fox_cloud_service import FoxCloudService _LOGGER = logging.getLogger(__name__) -_CHARGE = 18 # nominal charge in A class ChargeService(UnloadController): @@ -32,6 +31,7 @@ def __init__( eco_end_time: time, battery_soc: str, original_soc: int, + charge_rate: float, ) -> None: """Init charge service""" UnloadController.__init__(self) @@ -43,11 +43,13 @@ def __init__( self._eco_end_time = eco_end_time self._battery_soc = battery_soc self._original_soc = original_soc + self._charge_rate = charge_rate self._cancel_listeners = [] self._charge_active = False self._perc_target = 0 self._charge_required = 0 self._disable = False + self._custom_charge_profile = False self._add_listeners() @@ -100,6 +102,7 @@ async def _eco_start_setup(self, *args) -> None: # pylint: disable=unused-argum _LOGGER.debug("Resetting any existing Fox Cloud force charge/min SoC settings") await self._start_force_charge() await self._fox.set_min_soc(self._original_soc * 100) + await self._fox.set_charge_current(self._charge_rate) async def _eco_start(self, *args) -> None: # pylint: disable=unused-argument """Eco start""" @@ -140,7 +143,7 @@ async def _eco_end(self, *args) -> None: # pylint: disable=unused-argument # Reset Fox force charge to enabled and reset charge current await self._start_force_charge() - await self._fox.set_charge_current(_CHARGE) + await self._fox.set_charge_current(self._charge_rate) _LOGGER.debug("Releasing SoC hold") await self._fox.set_min_soc(self._original_soc * 100) @@ -151,8 +154,8 @@ async def _battery_soc_change( new_state = float(new_state.state) - if new_state > 90: - target_current = ((100 - new_state) / 10) * _CHARGE + if self._custom_charge_profile and new_state > 90: + target_current = ((100 - new_state) / 15) * self._charge_rate await self._fox.set_charge_current(target_current) if (new_state >= self._perc_target) and self._charge_active: @@ -197,3 +200,11 @@ def set_disable(self, status: bool) -> None: def disable_status(self) -> bool: """Disable status""" return self._disable + + def set_custom_charge_profile(self, status: bool) -> None: + """Set custom charge profile on/off""" + self._custom_charge_profile = status + + def custom_charge_profile_status(self) -> bool: + """Disable status""" + return self._custom_charge_profile diff --git a/custom_components/foxess_em/charge/charge_switch.py b/custom_components/foxess_em/charge/charge_switch.py index 8fdfc6c..6e994e2 100755 --- a/custom_components/foxess_em/charge/charge_switch.py +++ b/custom_components/foxess_em/charge/charge_switch.py @@ -16,6 +16,14 @@ switch="set_disable", store_state=True, ), + "custom_charge_profile": SwitchDescription( + key="custom_charge_profile", + name="Custom Charge Profile", + icon="mdi:chart-line", + is_on="custom_charge_profile_status", + switch="set_custom_charge_profile", + store_state=True, + ), } diff --git a/custom_components/foxess_em/config_flow.py b/custom_components/foxess_em/config_flow.py index 8dcda92..449b031 100755 --- a/custom_components/foxess_em/config_flow.py +++ b/custom_components/foxess_em/config_flow.py @@ -16,6 +16,7 @@ from .const import AUX_POWER from .const import BATTERY_CAPACITY from .const import BATTERY_SOC +from .const import CHARGE_RATE from .const import DOMAIN from .const import ECO_END_TIME from .const import ECO_START_TIME @@ -100,6 +101,10 @@ def __init__(self, config=None) -> None: MIN_SOC, default=self._data.get(MIN_SOC, 0.11) * 100, ): vol.All(vol.Coerce(float), vol.Range(min=10, max=99)), + vol.Required( + CHARGE_RATE, + default=self._data.get(CHARGE_RATE, 18), + ): vol.All(vol.Coerce(float), vol.Range(min=1, max=99)), } ) diff --git a/custom_components/foxess_em/const.py b/custom_components/foxess_em/const.py index db7c7a4..542d9bc 100755 --- a/custom_components/foxess_em/const.py +++ b/custom_components/foxess_em/const.py @@ -3,7 +3,7 @@ NAME = "foxess_em" DOMAIN = "foxess_em" DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "1.6.1b4" +VERSION = "1.6.1b5" ISSUE_URL = "https://github.com/nathanmarlor/foxess_em/issues" @@ -35,6 +35,7 @@ MIN_SOC = "min_soc" BATTERY_CAPACITY = "capacity" BATTERY_SOC = "battery_soc" +CHARGE_RATE = "charge_rate" # Defaults DEFAULT_NAME = DOMAIN diff --git a/custom_components/foxess_em/fox/fox_cloud_service.py b/custom_components/foxess_em/fox/fox_cloud_service.py index 73648e2..82e4c5d 100755 --- a/custom_components/foxess_em/fox/fox_cloud_service.py +++ b/custom_components/foxess_em/fox/fox_cloud_service.py @@ -34,8 +34,7 @@ def __init__( self._off_peak_start = off_peak_start self._off_peak_end = off_peak_end self._user_min_soc = user_min_soc - self._device_sn = None - self._device_id = None + self._device_info = None async def start_force_charge_now(self, *args) -> None: """Start force charge now""" @@ -43,14 +42,16 @@ async def start_force_charge_now(self, *args) -> None: start = now.replace(hour=0, minute=1).time() stop = now.replace(hour=23, minute=59).time() - device_sn = await self.device_serial_number() + device_info = await self.device_info() + device_sn = device_info["deviceSN"] query = self._build_start_single_charge_query(device_sn, start, stop) await self._start_force_charge(query) async def start_force_charge_off_peak(self, *args) -> None: """Start force charge off peak""" - device_sn = await self.device_serial_number() + device_info = await self.device_info() + device_sn = device_info["deviceSN"] if self._off_peak_start > self._off_peak_end: # Off-peak period crosses midnight @@ -86,7 +87,8 @@ async def stop_force_charge(self, *args) -> None: # pylint: disable=unused-argu _LOGGER.debug("Requesting stop force charge from Fox Cloud") try: - device_sn = await self.device_serial_number() + device_info = await self.device_info() + device_sn = device_info["deviceSN"] query = self._build_stop_charge_query(device_sn) @@ -104,7 +106,8 @@ async def set_min_soc( _LOGGER.debug("Sending min SoC to Fox Cloud") try: - device_sn = await self.device_serial_number() + device_info = await self.device_info() + device_sn = device_info["deviceSN"] await self._api.async_post_data( f"{_BASE_URL}{_MIN_SOC}", self._build_min_soc_query(device_sn, soc) ) @@ -116,7 +119,8 @@ async def set_charge_current(self, charge_current: float, *args) -> None: _LOGGER.debug(f"Sending charge current of {charge_current}A to Fox Cloud") try: - device_id = await self.device_id() + device_info = await self.device_info() + device_id = device_info["deviceID"] await self._api.async_post_data( f"{_BASE_URL}{_SETTINGS}", self._build_charge_query(device_id, charge_current), @@ -124,27 +128,16 @@ async def set_charge_current(self, charge_current: float, *args) -> None: except NoDataError as ex: _LOGGER.error(ex) - async def device_id(self) -> None: + async def device_info(self) -> None: """Get device serial number""" - if self._device_id is None: + if self._device_info is None: device = await self._api.async_post_data( f"{_BASE_URL}{_DEVICE}", self._build_device_query() ) - self._device_id = device["devices"][0]["deviceID"] - _LOGGER.debug(f"Retrieved Fox device ID: {self._device_id}") + self._device_info = device["devices"][0] + _LOGGER.debug(f"Retrieved Fox device info: {self._device_info}") - return self._device_id - - async def device_serial_number(self) -> None: - """Get device serial number""" - if self._device_sn is None: - device = await self._api.async_post_data( - f"{_BASE_URL}{_DEVICE}", self._build_device_query() - ) - self._device_sn = device["devices"][0]["deviceSN"] - _LOGGER.debug(f"Retrieved Fox device serial number: {self._device_sn}") - - return self._device_sn + return self._device_info def _build_device_query(self) -> dict: """Build device query object""" diff --git a/custom_components/foxess_em/translations/en.json b/custom_components/foxess_em/translations/en.json index 535f439..d48f09d 100755 --- a/custom_components/foxess_em/translations/en.json +++ b/custom_components/foxess_em/translations/en.json @@ -25,7 +25,8 @@ "dawn_buffer": "Dawn Buffer (kWh)", "day_buffer": "Day Buffer (kWh)", "capacity": "Battery Capacity (kWh)", - "min_soc": "Minimum SoC (%)" + "min_soc": "Minimum SoC (%)", + "charge_rate": "Charge Rate (A)" } }, "power": { @@ -73,7 +74,8 @@ "dawn_buffer": "Dawn Buffer (kWh)", "day_buffer": "Day Buffer (kWh)", "capacity": "Battery Capacity (kWh)", - "min_soc": "Minimum SoC (%)" + "min_soc": "Minimum SoC (%)", + "charge_rate": "Charge Rate (A)" } }, "power": {