Skip to content

Commit

Permalink
Added switch to use custom charge profile
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanmarlor committed Feb 2, 2023
1 parent a03e222 commit 2981904
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 35 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 |

</details>

Expand Down
4 changes: 4 additions & 0 deletions custom_components/foxess_em/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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] = {
Expand Down
19 changes: 15 additions & 4 deletions custom_components/foxess_em/charge/charge_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from ..fox.fox_cloud_service import FoxCloudService

_LOGGER = logging.getLogger(__name__)
_CHARGE = 18 # nominal charge in A


class ChargeService(UnloadController):
Expand All @@ -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)
Expand All @@ -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()

Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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
8 changes: 8 additions & 0 deletions custom_components/foxess_em/charge/charge_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
}


Expand Down
5 changes: 5 additions & 0 deletions custom_components/foxess_em/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)),
}
)

Expand Down
3 changes: 2 additions & 1 deletion custom_components/foxess_em/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -35,6 +35,7 @@
MIN_SOC = "min_soc"
BATTERY_CAPACITY = "capacity"
BATTERY_SOC = "battery_soc"
CHARGE_RATE = "charge_rate"

# Defaults
DEFAULT_NAME = DOMAIN
Expand Down
39 changes: 16 additions & 23 deletions custom_components/foxess_em/fox/fox_cloud_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,24 @@ 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"""
now = datetime.now().astimezone()
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

Expand Down Expand Up @@ -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)

Expand All @@ -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)
)
Expand All @@ -116,35 +119,25 @@ 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),
)
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"""
Expand Down
6 changes: 4 additions & 2 deletions custom_components/foxess_em/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down

0 comments on commit 2981904

Please sign in to comment.