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

Add a service to schedule based on an Octopus Target Rate time #47

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

Meatballs1
Copy link

@Meatballs1 Meatballs1 commented Mar 25, 2024

This takes a Target Rate tracker from https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy and applies the schedule to Hypervolt. Tested OK on my HV2.0...

It also attempts to use binary_sensor.octopus_energy_{{ACCOUNT_ID}}_intelligent_dispatching entity planned_dispatches - e.g. if HV was a second charger to a compatible unit or car. But I dont have intelligent so can't test...

The reason I want to take this approach is due to cloud based nature of integration. We could just have HA wait for the target entity to come on, and then switch between plug and charge/schedule or stop/start charges, but this causes more API calls, and if any cloud is down it will fail (and likely overnight when you cant see it). By setting a schedule we can observe that this is set in HV before we goto bed and not have to worry about any internet outages 👍

Resolves: #44

  • It could possibly be improved by merging in any continuous timeslots (e.g. if the tracker has 00:30->01:00, 01:00->01:30 these should be merged into one schedule block). This is too much for my addled brain at the moment as it was painful enough just working out how to do HomeAssistant internals as the documentation is pretty poor 🤮

Service:

image

The Hypervolt should be filtered under devices by services.yaml, and target rates should be partially filtered to the Octopus integration - but can only constrain it to binary_sensors rather than target rate items specifically.

Target Rate tested:

image

Resulting Hypervolt Schedule:

image

@Meatballs1
Copy link
Author

Added the option to add a backup schedule to the request. If no times are found in the tracker it will default to the backup. We can also append the backup to the schedule (maybe if you want to cover preheat or just want it to always set a certain period as a backup).

Also merged the times if they are continuous...

@gndean
Copy link
Owner

gndean commented Mar 26, 2024

Wow, thanks for submitting this!
I'll take a look at whether this can be merged once I've got some HV 3.0 fixes out of the way (some things are currently broken for 3.0 users). Should hopefully be in the next week or so.

In the meantime, feel free to commit any further updates and/or think about what could be added to the Readme

@Meatballs1
Copy link
Author

Yeah Im expecting the 3.0 changes to potentially break scheduling as they will include the charging mode, but hopefully doesn't require too many changes. Added some readme changes.

@martinpelant
Copy link

I believe it's off by 1h since the daylight saving time started (Octopus tracker still returns times in GMT e.g.
start: '2024-03-31T21:30:00+00:00'
end: '2024-03-31T22:00:00+00:00') but hypervolt takes them in local time

@Meatballs1
Copy link
Author

If someone campaigned on removing daylight savings they'd probably get my vote 😅

Copy link
Author

@Meatballs1 Meatballs1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess should pass in TZ from the tracker...

@Meatballs1
Copy link
Author

So I think I've fixed the timezone by converting it into the local timezone that is set in the coordinator. Had to import pytz but it seems to already be there, not 100% sure on the HA way to add requirements if not.

Not sure if should use python-datetime instead, will give it a go using that and see if I can get it working... https://developers.home-assistant.io/blog/2021/05/07/switch-pytz-to-python-dateutil/

2024-03-31 18:55:44.390 INFO (MainThread) [custom_components.hypervolt_charger.service] Setting schedule intervals
2024-03-31 18:55:44.432 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Current time: 2024-03-31 18:55:44.432785+00:00
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] Scheduled blocks from tracker: [{'value_inc_vat': 0.07392, 'start': datetime.datetime(2024, 3, 31, 23, 30, tzinfo=datetime.timezone.utc), 'end': datetime.datetime(2024, 4, 1, 0, 0, tzinfo=datetime.timezone.utc), 'tariff_code': 'E-1R-AGILE-23-12-06-B', 'is_capped': False}, {'value_inc_vat': 0.07392, 'start': datetime.datetime(2024, 4, 1, 0, 0, tzinfo=datetime.timezone.utc), 'end': datetime.datetime(2024, 4, 1, 0, 30, tzinfo=datetime.timezone.utc), 'tariff_code': 'E-1R-AGILE-23-12-06-B', 'is_capped': False}, {'value_inc_vat': 0.078855, 'start': datetime.datetime(2024, 4, 1, 1, 30, tzinfo=datetime.timezone.utc), 'end': datetime.datetime(2024, 4, 1, 2, 0, tzinfo=datetime.timezone.utc), 'tariff_code': 'E-1R-AGILE-23-12-06-B', 'is_capped': False}, {'value_inc_vat': 0.07749, 'start': datetime.datetime(2024, 4, 1, 2, 0, tzinfo=datetime.timezone.utc), 'end': datetime.datetime(2024, 4, 1, 2, 30, tzinfo=datetime.timezone.utc), 'tariff_code': 'E-1R-AGILE-23-12-06-B', 'is_capped': False}, {'value_inc_vat': 0.07812, 'start': datetime.datetime(2024, 4, 1, 3, 0, tzinfo=datetime.timezone.utc), 'end': datetime.datetime(2024, 4, 1, 3, 30, tzinfo=datetime.timezone.utc), 'tariff_code': 'E-1R-AGILE-23-12-06-B', 'is_capped': False}, {'value_inc_vat': 0.0777, 'start': datetime.datetime(2024, 4, 1, 4, 0, tzinfo=datetime.timezone.utc), 'end': datetime.datetime(2024, 4, 1, 4, 30, tzinfo=datetime.timezone.utc), 'tariff_code': 'E-1R-AGILE-23-12-06-B', 'is_capped': False}]
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start: 2024-03-31 23:30:00+00:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start tz adjusted: 2024-04-01 00:30:00+01:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start: 2024-04-01 00:00:00+00:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start tz adjusted: 2024-04-01 01:00:00+01:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start: 2024-04-01 01:30:00+00:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start tz adjusted: 2024-04-01 02:30:00+01:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start: 2024-04-01 02:00:00+00:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start tz adjusted: 2024-04-01 03:00:00+01:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start: 2024-04-01 03:00:00+00:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start tz adjusted: 2024-04-01 04:00:00+01:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start: 2024-04-01 04:00:00+00:00
2024-03-31 18:55:44.433 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Start tz adjusted: 2024-04-01 05:00:00+01:00
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] Intervals to set:
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 00:30:00+01:00 -> 2024-04-01 01:00:00+01:00
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 01:00:00+01:00 -> 2024-04-01 01:30:00+01:00
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 02:30:00+01:00 -> 2024-04-01 03:00:00+01:00
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 03:00:00+01:00 -> 2024-04-01 03:30:00+01:00
2024-03-31 18:55:44.433 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 04:00:00+01:00 -> 2024-04-01 04:30:00+01:00
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 05:00:00+01:00 -> 2024-04-01 05:30:00+01:00
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] Merging continuous times:
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 00:30:00+01:00 -> 2024-04-01 01:30:00+01:00
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 02:30:00+01:00 -> 2024-04-01 03:30:00+01:00
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 04:00:00+01:00 -> 2024-04-01 04:30:00+01:00
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] 2024-04-01 05:00:00+01:00 -> 2024-04-01 05:30:00+01:00
2024-03-31 18:55:44.434 INFO (MainThread) [custom_components.hypervolt_charger.service] Appending backup 1900-01-01 04:30:00 -> 1900-01-01 06:00:00
2024-03-31 18:55:44.434 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Coordinator schedule timezone: Europe/London
2024-03-31 18:55:44.434 DEBUG (MainThread) [custom_components.hypervolt_charger.service] Timezone used: Europe/London
2024-03-31 18:55:44.434 DEBUG (MainThread) [custom_components.hypervolt_charger.hypervolt_api_client] Set schedule: {'type': 'restricted', 'tz': 'Europe/London', 'enabled': True, 'intervals': [[{'hours': 0, 'minutes': 30, 'seconds': 0}, {'hours': 1, 'minutes': 30, 'seconds': 0}], [{'hours': 2, 'minutes': 30, 'seconds': 0}, {'hours': 3, 'minutes': 30, 'seconds': 0}], [{'hours': 4, 'minutes': 0, 'seconds': 0}, {'hours': 4, 'minutes': 30, 'seconds': 0}], [{'hours': 5, 'minutes': 0, 'seconds': 0}, {'hours': 5, 'minutes': 30, 'seconds': 0}], [{'hours': 4, 'minutes': 30, 'seconds': 0}, {'hours': 6, 'minutes': 0, 'seconds': 0}]]}

@Meatballs1
Copy link
Author

I'm not 100% sure what happens if schedules clash - e.g. if the backup schedule spans the other schedules. The app shows a warning...

@Meatballs1
Copy link
Author

Seeing the following error tonight:

2024-04-01 19:34:41.855 ERROR (MainThread) [homeassistant.components.automation.octopus_agile_ev_charger_scheduling] While executing automation automation.octopus_agile_ev_charger_scheduling
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/automation/__init__.py", line 666, in async_trigger
    return await self.action_script.async_run(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1600, in async_run
    return await asyncio.shield(run.async_run())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 435, in async_run
    await self._async_step(log_exceptions=False)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 487, in _async_step
    self._handle_exception(
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 512, in _handle_exception
    raise exception
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 723, in _async_call_service_step
    response_data = await self._async_run_long_action(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action
    return long_task.result()
           ^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2319, in async_call
    response_data = await coro
                    ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2356, in _execute_service
    return await target(service_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/hypervolt_charger/service.py", line 52, in async_set_schedule
    coordinator: HypervoltUpdateCoordinator = hass.data[DOMAIN][config_id]
                                              ~~~~~~~~~~~~~~~~~^^^^^^^^^^^
KeyError: 'd31a9decb0a03b63cbfaff34c78b5e79'

d31a9decb appears to be the config_id - it doesn't match up with my Hypervolt device ID in HA?

Put in some code to loop over the config_ids and they appear to bring back multiple ones:

2024-04-01 19:51:58.337 WARNING (MainThread) [custom_components.hypervolt_charger.service] Unknown config_id 3095ded07c59eaebe0ed594c815305a1
2024-04-01 19:51:58.337 WARNING (MainThread) [custom_components.hypervolt_charger.service] Unknown config_id d31a9decb0a03b63cbfaff34c78b5e79

Eventually we hit a config_id that works...

@Meatballs1
Copy link
Author

Updated with latest changes to main

@Meatballs1
Copy link
Author

Merged into the 2.0 changes and still seems to work with my HV2.0 but could use HV3.0 testing? @gndean

@Meatballs1
Copy link
Author

Looks like it would work OK with the default values passed in for HV3.0:

        charge_mode: HypervoltChargeMode = HypervoltChargeMode.BOOST,
        days_of_week: int = HypervoltDayOfWeek.ALL,
    ) -> None:

Calculating the DayOfTheWeek maybe annoying if we want to be more specific.

@gndean
Copy link
Owner

gndean commented Apr 16, 2024

I agree that it looks like this should work for HV 3.0 devices too. I will test this when I get chance.

custom_components/hypervolt_charger/service.py Outdated Show resolved Hide resolved
custom_components/hypervolt_charger/service.py Outdated Show resolved Hide resolved
for interval in intervals:
_LOGGER.info(f"{interval.start_time} -> {interval.end_time}")

_LOGGER.info("Merging continuous times:")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoyingly, the HV 3.0 charger does not allow times to span midnight. If trying to set that, the API returns an error like:
"schedule.set failed: assertion failed: Session startTime (HoursMinutes(12,0)) must be < endTime (HoursMinutes(3,30))"}}
When trying to do this in the HV app, two sessions are created:
startTime -> 24:00
00:00 -> endTime
So I think there needs to be logic for this too 😢

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have to do something like this for the merging and splitting, then I put a kludge in the API client code that converts 23:59:59 into 24:00 when it's passed to the HV API.

            _LOGGER.info("Merging continuous times:")

            if intervals:
                merged_intervals.append(intervals[0])
            for interval in intervals[1:]:
                last_interval = merged_intervals[-1]
                if last_interval.end_time == interval.start_time:
                    last_interval.end_time = interval.end_time
                else:
                    merged_intervals.append(interval)

            # Break up any interval spanning midnight
            for interval in merged_intervals.copy():
                if interval.start_time > interval.end_time:
                    _LOGGER.info(
                        f"Splitting interval {interval.start_time} -> {interval.end_time}"
                    )
                    end = interval.end_time
                    interval.end_time = datetime.time(hour=23, minute=59, second=59)
                    merged_intervals.append(
                        HypervoltScheduleInterval(datetime.time(hour=0, minute=0), end)
                    )

            for interval in merged_intervals:
                _LOGGER.info(f"{interval.start_time} -> {interval.end_time}")```

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, re-thinking this. The splitting should probably happen with the hypervolt_api_client code, not in here. So then it works for anything that calls set_schedule.

So I've changed my mind - don't split intervals spanning midnight here.

@Meatballs1
Copy link
Author

The incoming changes to bottlecapdaves integration look like he's changing to timezones for everything instead of UTC. So probably need to double check when that's merged

@Meatballs1
Copy link
Author

It seems quite happy with the new timezone changes in bottlecapdave's v11.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 Max Schedule Slots?
3 participants