22from datetime import datetime , timedelta
33from .const import DOMAIN , DATA_OPTIONS
44import pytz
5+ # import logging
6+ # _LOGGER = logging.getLogger(__name__)
57
68def _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
66111def 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
0 commit comments