Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added switch to use custom charge profile #175

Merged
merged 1 commit into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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