Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit f1ed257

Browse files
authored
Slot parsing fixes (#49)
* Fix dodgy next end time * Fix bug with start time sensor * Fix regressions * Fix bug with charge slot list when charge in progress * Bump version * Fix end of last slot missing bug * Over zealous rounding * Don't show slots if charge finished
1 parent 14cb8b0 commit f1ed257

File tree

3 files changed

+64
-14
lines changed

3 files changed

+64
-14
lines changed

custom_components/ohme/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Component constants"""
22
DOMAIN = "ohme"
33
USER_AGENT = "dan-r-homeassistant-ohme"
4-
INTEGRATION_VERSION = "0.4.3"
4+
INTEGRATION_VERSION = "0.5.1"
55
CONFIG_VERSION = 1
66
ENTITY_TYPES = ["sensor", "binary_sensor", "switch", "button", "number", "time"]
77

custom_components/ohme/utils.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,57 @@
22
from datetime import datetime, timedelta
33
from .const import DOMAIN, DATA_OPTIONS
44
import pytz
5+
# import logging
6+
# _LOGGER = logging.getLogger(__name__)
57

68
def _format_charge_graph(charge_start, points):
79
"""Convert relative time in points array to real timestamp (s)."""
810

9-
# Add 30s to effectively round all times to the nearest minute
10-
charge_start = round(charge_start / 1000) + 30
11+
charge_start = round(charge_start / 1000)
1112

1213
# _LOGGER.debug("Charge slot graph points: " + str([{"t": datetime.fromtimestamp(x["x"] + charge_start).strftime('%H:%M:%S'), "y": x["y"]} for x in points]))
1314

1415
return [{"t": x["x"] + charge_start, "y": x["y"]} for x in points]
1516

1617

17-
def _next_slot(data, live=False):
18+
def _sanitise_points(points):
19+
"""Discard any points that aren't on a quarter-hour boundary."""
20+
output = []
21+
seen = []
22+
high = max([x['y'] for x in points])
23+
24+
points.reverse()
25+
26+
for point in points:
27+
# Round up the timestamp and get the minute
28+
ts = point['t'] + 30
29+
dt = datetime.fromtimestamp(ts)
30+
hm = dt.strftime('%H:%M')
31+
m = int(dt.strftime('%M'))
32+
33+
# If this point is on a 15m boundary and we haven't seen this time before
34+
# OR y == yMax - so we don't miss the end of the last slot
35+
if (m % 15 == 0 and hm not in seen) or point['y'] == high:
36+
output.append(point)
37+
seen.append(hm)
38+
39+
output.reverse()
40+
# _LOGGER.warning("Charge slot graph points: " + str([{"t": datetime.fromtimestamp(x["t"] + 30).strftime('%H:%M:%S'), "y": x["y"]} for x in output]))
41+
42+
return output
43+
44+
45+
def _charge_finished(data):
46+
"""Is the charge finished?"""
47+
now = int(time())
48+
data = [x['y'] for x in data if x["t"] > now]
49+
50+
if min(data) == max(data):
51+
return True
52+
return False
53+
54+
55+
def _next_slot(data, live=False, in_progress=False):
1856
"""Get the next slot. live is whether or not we may start mid charge. Eg: For the next slot end sensor, we dont have the
1957
start but still want the end of the in progress session, but for the slot list sensor we only want slots that have
2058
a start AND an end."""
@@ -25,15 +63,21 @@ def _next_slot(data, live=False):
2563
for idx in range(0, len(data) - 1):
2664
# Calculate the delta between this element and the next
2765
delta = data[idx + 1]["y"] - data[idx]["y"]
66+
delta = 0 if delta < 0 else delta # Zero floor deltas
2867

2968
# If the next point has a Y delta of 10+, consider this the start of a slot
3069
# This should be 0+ but I had some strange results in testing... revisit
3170
if delta > 10 and not start_ts:
3271
# 1s added here as it otherwise often rounds down to xx:59:59
3372
start_ts = data[idx]["t"] + 1
73+
74+
# If we are working live, in a time slot and haven't seen an end yet,
75+
# disregard.
76+
if start_ts and live and in_progress and not end_ts:
77+
start_ts = None
3478

3579
# Take the first delta of 0 as the end
36-
if delta == 0 and (start_ts or live) and not end_ts:
80+
if delta == 0 and data[idx]["y"] != 0 and (start_ts or live) and not end_ts:
3781
end_ts = data[idx]["t"] + 1
3882

3983
if start_ts and end_ts:
@@ -46,6 +90,7 @@ def charge_graph_next_slot(charge_start, points, skip_format=False):
4690
"""Get the next charge slot start/end times from a list of graph points."""
4791
now = int(time())
4892
data = points if skip_format else _format_charge_graph(charge_start, points)
93+
in_progress = charge_graph_in_slot(charge_start, data, skip_format=True)
4994

5095
# Filter to points from now onwards
5196
data = [x for x in data if x["t"] > now]
@@ -54,7 +99,7 @@ def charge_graph_next_slot(charge_start, points, skip_format=False):
5499
if len(data) < 2:
55100
return {"start": None, "end": None}
56101

57-
start_ts, end_ts, final_idx = _next_slot(data, live=True)
102+
start_ts, end_ts, _ = _next_slot(data, live=True, in_progress=in_progress)
58103

59104
# These need to be presented with tzinfo or Home Assistant will reject them
60105
return {
@@ -65,9 +110,14 @@ def charge_graph_next_slot(charge_start, points, skip_format=False):
65110

66111
def charge_graph_slot_list(charge_start, points, skip_format=False):
67112
"""Get list of charge slots from graph points."""
68-
now = int(time())
69113
data = points if skip_format else _format_charge_graph(charge_start, points)
70114

115+
# Don't return any slots if charge is over
116+
if _charge_finished(data):
117+
return []
118+
119+
data = _sanitise_points(data)
120+
71121
# Give up if we have less than 2 points
72122
if len(data) < 2:
73123
return []
@@ -80,13 +130,13 @@ def charge_graph_slot_list(charge_start, points, skip_format=False):
80130
result = _next_slot(data)
81131

82132
# Break if we fail
83-
if result[0] is None:
133+
if result[0] is None or result[1] is None:
84134
break
85135

86136
# Append a tuple to the slots list with the start end end time
87137
slots.append((
88-
datetime.fromtimestamp(result[0]).strftime('%H:%M'),
89-
datetime.fromtimestamp(result[1]).strftime('%H:%M'),
138+
datetime.fromtimestamp(result[0] + 1).strftime('%H:%M'),
139+
datetime.fromtimestamp(result[1] + 1).strftime('%H:%M'),
90140
))
91141

92142
# Cut off where we got to in this iteration for next time

tests/test_utils.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ async def test_format_charge_graph(hass):
1515
start_time_ms = start_time * 1000
1616

1717
result = utils._format_charge_graph(start_time_ms, TEST_DATA)
18-
expected = [{"t": TEST_DATA[0]['x'] + start_time + 30, "y": mock.ANY},
19-
{"t": TEST_DATA[1]['x'] + start_time + 30, "y": mock.ANY},
20-
{"t": TEST_DATA[2]['x'] + start_time + 30, "y": mock.ANY},
21-
{"t": TEST_DATA[3]['x'] + start_time + 30, "y": mock.ANY}]
18+
expected = [{"t": TEST_DATA[0]['x'] + start_time, "y": mock.ANY},
19+
{"t": TEST_DATA[1]['x'] + start_time, "y": mock.ANY},
20+
{"t": TEST_DATA[2]['x'] + start_time, "y": mock.ANY},
21+
{"t": TEST_DATA[3]['x'] + start_time, "y": mock.ANY}]
2222

2323
assert expected == result
2424

0 commit comments

Comments
 (0)