megakid / ha_octopus_intelligent

Apache License 2.0
86 stars 10 forks source link

Fix issues #35 #36: Planned dispatch source is None #37

Closed pdcastro closed 4 months ago

pdcastro commented 4 months ago

This PR proposes a fix for issues #35 and #36. As @matt7aylor had pointed out, sometimes the source attribute in the Octopus API planned dispatch list gets changed from "source": "smart-charge" to "source": None. The proposed solution / workaround is to remember the last seen non-None source and use it when source is None. For example:

Initially, 'source': 'smart-charge':

2024-02-28 05:55:02.838 ...
 'plannedDispatches': [{'chargeKwh': '-7.31',
                        'endDtUtc': '2024-02-28 08:30:00+00:00',
                        'meta': {'location': None, 'source': 'smart-charge'},
                        'startDtUtc': '2024-02-28 05:20:00+00:00'}],

Then it goes rogue, 'source': None:

2024-02-28 06:00:04.618 ...
 'plannedDispatches': [{'chargeKwh': '-5.77',
                        'endDtUtc': '2024-02-28 08:30:00+00:00',
                        'meta': {'location': None, 'source': None},
                        'startDtUtc': '2024-02-28 06:00:00+00:00'}],

The proposed solution replaces 'source': None with 'source': 'smart-charge' (in this particular example) before passing it on to the integration sensors etc.

The proposed solution also introduces a PersistentData class that uses the Home Assistant helpers.storage.Store class to persist the last seen non-None source to disk when Home Assistant is stopped / restarted, and restores it when the integration is loaded again.

It is worth noting that initially I tried something else. I looked at the Octopus GraphQL schema for plannedDispatches and noticed that they document two source attributes, one in the meta inner object and one in the outer scope:

Octopus API query documentation:

query PlannedDispatches($accountNumber: String!) {
  plannedDispatches(accountNumber: $accountNumber) {
    startDt
    endDt
    deltaKwh
    delta
    source
    meta {
        source
        location
    }
  }
}

Octopus API response documentation:

{
  "data": {
    "plannedDispatches": [
      {
        "startDt": "abc123",
        "endDt": "abc123",
        "deltaKwh": 1,
        "delta": 1.0,
        "source": "abc123",
        "meta": {
            "source": "abc123",
            "location": "abc123"
        }
      }
    ]
  }
}

So I tried modifying the query in graphql_client.py __async_get_combined_state() to include both source attributes, in the hope that when one is None, the other isn’t. Alas, I observed that the newly added outer source attribute was always None, regardless of the whether the meta.source attribute was None or not. Example:

2024-03-03 22:23:19.619 ...
 'plannedDispatches': [{'chargeKwh': '-12.00',
                        'endDtUtc': '2024-03-04 04:00:00+00:00',
                        'meta': {'location': None, 'source': 'smart-charge'},
                        'source': None,
                        'startDtUtc': '2024-03-03 22:00:00+00:00'}],
...
2024-03-03 22:28:20.464 ...
 'plannedDispatches': [{'chargeKwh': '-12.00',
                        'endDtUtc': '2024-03-04 04:00:00+00:00',
                        'meta': {'location': None, 'source': None},
                        'source': None,
                        'startDtUtc': '2024-03-03 22:00:00+00:00'}],
pdcastro commented 4 months ago

The proposed solution has a corner-case limitation involving bump charge. During the 5-minute API polling interval, or while Home Assistant is down for any reason (upgrade, server maintenance...), it is in theory possible for the planned dispatch source attribute to change e.g. from smart-charge to bump-charge to None again, if the user initiates a bump charge during that period (or the opposite change, if the user ends a bump charge during that period). In this case, the integration could assume the wrong value for source, and in principle it could take several hours until the Octopus API produced a non-None source value again. During those hours, issues #35 and #36 would remain unsolved. This is a fundamental limitation of the approach of assuming the last seen non-None source value while the Octopus API provides a None value.

I believe that most users will never come across that corner case, even if they regularly use bump charge (which I suspect is also rare). This PR thus offers an effective workaround until Octopus fixes their API to ensure that the source attribute is never None.