Skip to content

Commit

Permalink
Set charge times to off-peak period only to prevent overrun
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanmarlor committed Jan 25, 2023
1 parent 36152ef commit 6367426
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 46 deletions.
6 changes: 4 additions & 2 deletions custom_components/foxess_em/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
schedule,
peak_utils,
)
fox_service = FoxCloudService(fox_client, user_min_soc)
fox_service = FoxCloudService(
hass, fox_client, eco_start_time, eco_end_time, user_min_soc
)
charge_service = ChargeService(
hass,
battery_controller,
Expand All @@ -128,7 +130,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
average_controller.add_update_listener(battery_controller)

hass.services.async_register(
DOMAIN, "start_force_charge", fox_service.start_force_charge
DOMAIN, "start_force_charge", fox_service.start_force_charge_now
)
hass.services.async_register(
DOMAIN, "stop_force_charge", fox_service.stop_force_charge
Expand Down
2 changes: 1 addition & 1 deletion custom_components/foxess_em/charge/charge_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ async def _start_force_charge(
"""Battery SoC has not yet met desired percentage"""
_LOGGER.debug(f"Starting force charge to {self._perc_target}")
self._charge_active = True
await self._fox.start_force_charge()
await self._fox.start_force_charge_off_peak()

async def _stop_force_charge(
self, *args
Expand Down
2 changes: 1 addition & 1 deletion custom_components/foxess_em/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ async def _test_fox(self, fox_username: str, fox_password: str):
try:
session = async_create_clientsession(self.hass)
api = FoxApiClient(session, fox_username, fox_password)
service = FoxCloudService(api)
service = FoxCloudService(None, api, None, None)
result = await service.device_serial_number()
if result is not None:
return True
Expand Down
15 changes: 11 additions & 4 deletions custom_components/foxess_em/forecast/forecast_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,7 @@ def __init__(self, hass: HomeAssistant, api: SolcastApiClient) -> None:
async def _setup_refresh(self, *args):
"""Setup refresh intervals"""

_LOGGER.debug(f"Clearing {len(self._refresh_listeners)} refresh listeners")
for listener in self._refresh_listeners:
listener()
self._refresh_listeners.clear()
self._clear_listeners()

now = datetime.now().astimezone()
default_start = now.replace(hour=_START_HOUR, minute=0, second=0, microsecond=0)
Expand Down Expand Up @@ -79,6 +76,16 @@ async def _setup_refresh(self, *args):
update_time = (actual_start + timedelta(minutes=interval * i)).time()
self._add_refresh(update_time)

def _clear_listeners(self):
"""Clear all listeners"""
_LOGGER.debug(f"Clearing {len(self._refresh_listeners)} refresh listeners")
for listener in self._refresh_listeners:
listener()
# Remove any dangling references in unload listeners
if listener in self._unload_listeners:
self._unload_listeners.remove(listener)
self._refresh_listeners.clear()

def _add_refresh(self, refresh_time: time) -> None:
"""Add a forecast refresh"""
_LOGGER.debug(f"Setting up forecast refresh at {refresh_time}")
Expand Down
113 changes: 75 additions & 38 deletions custom_components/foxess_em/fox/fox_cloud_service.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""Fox controller"""
import logging
from datetime import datetime
from datetime import time

from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import async_track_utc_time_change

from ..common.unload_controller import UnloadController
from ..fox.fox_api import FoxApiClient
from ..util.exceptions import NoDataError

Expand All @@ -13,23 +18,73 @@
_LOGGER = logging.getLogger(__name__)


class FoxCloudService:
class FoxCloudService(UnloadController):
"""Fox Cloud service"""

def __init__(self, api: FoxApiClient, user_min_soc: int = 11) -> None:
def __init__(
self,
hass: HomeAssistant,
api: FoxApiClient,
off_peak_start: time,
off_peak_end: time,
user_min_soc: int = 11,
) -> None:
"""Init Fox Cloud service"""
UnloadController.__init__(self)
self._hass = hass
self._api = api
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._off_peak_listener = 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()

await self._start_force_charge(start, stop)

async def start_force_charge_off_peak(self, *args) -> None:
"""Start force charge off peak"""
if self._off_peak_start > self._off_peak_end:
_LOGGER.debug("Setting charge to midnight first")
before_midnight = time(hour=23, minute=59)
await self._start_force_charge(self._off_peak_start, before_midnight)
# Setup trigger to reset times after midnight
midnight = async_track_utc_time_change(
self._hass,
self._finish_force_charge_off_peak,
hour=before_midnight.hour,
minute=before_midnight.minute,
second=0,
local=True,
)
self._off_peak_listener = midnight
self._unload_listeners.append(midnight)
else:
await self._start_force_charge(self._off_peak_start, self._off_peak_end)

async def start_force_charge(self, *args) -> None:
async def _finish_force_charge_off_peak(self, *args) -> None:
"""Finish force charge off peak"""
_LOGGER.debug("Finishing charge from midnight to eco end")
self._off_peak_listener()
self._unload_listeners.remove(self._off_peak_listener)

after_midnight = time(hour=0, minute=1)
await self._start_force_charge(after_midnight, self._off_peak_end)

async def _start_force_charge(self, start, stop) -> None:
"""Start force charge"""
_LOGGER.debug("Requesting start force charge from Fox Cloud")

try:
device_sn = await self.device_serial_number()
await self._api.async_post_data(
f"{_BASE_URL}{_SET_TIMES}", self._build_charge_start_query(device_sn)
f"{_BASE_URL}{_SET_TIMES}",
self._build_charge_start_stop_query(device_sn, True, start, stop),
)
except NoDataError as ex:
_LOGGER.error(ex)
Expand All @@ -41,7 +96,13 @@ async def stop_force_charge(self, *args) -> None: # pylint: disable=unused-argu
try:
device_sn = await self.device_serial_number()
await self._api.async_post_data(
f"{_BASE_URL}{_SET_TIMES}", self._build_charge_stop_query(device_sn)
f"{_BASE_URL}{_SET_TIMES}",
self._build_charge_start_stop_query(
device_sn,
False,
self._off_peak_start,
self._off_peak_end,
),
)
except NoDataError as ex:
_LOGGER.error(ex)
Expand Down Expand Up @@ -84,25 +145,25 @@ def _build_min_soc_query(self, device_sn: str, soc: int) -> dict:
"""Build min SoC query object"""
return {"sn": device_sn, "minGridSoc": soc, "minSoc": self._user_min_soc * 100}

def _build_charge_start_query(self, device_sn: str) -> dict:
def _build_charge_start_stop_query(
self, device_sn: str, start_stop: bool, start_time: time, end_time: time
) -> dict:
"""Build device query object"""
now = datetime.now().astimezone()
midnight = now.replace(hour=23, minute=59, second=59, microsecond=0)

query = {
"sn": device_sn,
"times": [
{
"tip": "",
"enableCharge": True,
"enableGrid": True,
"enableCharge": start_stop,
"enableGrid": start_stop,
"startTime": {
"hour": str(now.hour).zfill(2),
"minute": str(now.minute).zfill(2),
"hour": str(start_time.hour).zfill(2),
"minute": str(start_time.minute).zfill(2),
},
"endTime": {
"hour": str(midnight.hour).zfill(2),
"minute": str(midnight.minute).zfill(2),
"hour": str(end_time.hour).zfill(2),
"minute": str(end_time.minute).zfill(2),
},
},
{
Expand All @@ -116,27 +177,3 @@ def _build_charge_start_query(self, device_sn: str) -> dict:
}

return query

def _build_charge_stop_query(self, device_sn: str) -> dict:
"""Build device query object"""
query = {
"sn": device_sn,
"times": [
{
"tip": "",
"enableCharge": False,
"enableGrid": False,
"startTime": {"hour": 0, "minute": 0},
"endTime": {"hour": 0, "minute": 0},
},
{
"tip": "",
"enableCharge": False,
"enableGrid": False,
"startTime": {"hour": 0, "minute": 0},
"endTime": {"hour": 0, "minute": 0},
},
],
}

return query

0 comments on commit 6367426

Please sign in to comment.