foxesscloud 2.5.4__py3-none-any.whl → 2.5.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- foxesscloud/foxesscloud.py +32 -27
- foxesscloud/openapi.py +32 -27
- {foxesscloud-2.5.4.dist-info → foxesscloud-2.5.5.dist-info}/METADATA +7 -1
- foxesscloud-2.5.5.dist-info/RECORD +7 -0
- foxesscloud-2.5.4.dist-info/RECORD +0 -7
- {foxesscloud-2.5.4.dist-info → foxesscloud-2.5.5.dist-info}/LICENCE +0 -0
- {foxesscloud-2.5.4.dist-info → foxesscloud-2.5.5.dist-info}/WHEEL +0 -0
- {foxesscloud-2.5.4.dist-info → foxesscloud-2.5.5.dist-info}/top_level.txt +0 -0
foxesscloud/foxesscloud.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
##################################################################################################
|
2
2
|
"""
|
3
3
|
Module: Fox ESS Cloud
|
4
|
-
Updated:
|
4
|
+
Updated: 26 September 2024
|
5
5
|
By: Tony Matthews
|
6
6
|
"""
|
7
7
|
##################################################################################################
|
@@ -10,7 +10,7 @@ By: Tony Matthews
|
|
10
10
|
# ALL RIGHTS ARE RESERVED © Tony Matthews 2023
|
11
11
|
##################################################################################################
|
12
12
|
|
13
|
-
version = "1.6.
|
13
|
+
version = "1.6.7"
|
14
14
|
print(f"FoxESS-Cloud version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -721,7 +721,7 @@ def set_charge(ch1=None, st1=None, en1=None, ch2=None, st2=None, en2=None, force
|
|
721
721
|
output(f"success", 2)
|
722
722
|
return battery_settings
|
723
723
|
|
724
|
-
def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10,
|
724
|
+
def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, end_soc=100, start_soc=10):
|
725
725
|
output(f"\nConfiguring schedule",1)
|
726
726
|
charge = []
|
727
727
|
st1 = time_hours(st1)
|
@@ -730,7 +730,7 @@ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, target_so
|
|
730
730
|
en2 = time_hours(en2)
|
731
731
|
span = None
|
732
732
|
if st2 is not None and en2 is not None and st2 != en2:
|
733
|
-
charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc':
|
733
|
+
charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc': end_soc})
|
734
734
|
span = {'start': st2, 'end': en2}
|
735
735
|
if st1 is not None and en1 is not None and st1 != en1:
|
736
736
|
charge.append({'start': st1, 'end': en1, 'mode': 'SelfUse', 'min_soc': start_soc})
|
@@ -2221,6 +2221,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
|
2221
2221
|
if use.get('agile') is not None and use['agile'].get('strategy') is not None:
|
2222
2222
|
base_time_adjust = hours_difference(base_time, use['agile'].get('base_time') )
|
2223
2223
|
for s in use['agile']['strategy']:
|
2224
|
+
s['valid_for'] = [int((s['hour'] - base_time_adjust) * steps_per_hour + i) for i in range(0, steps_per_hour // 2)] if s.get('hour') is not None else None
|
2224
2225
|
strategy.append(s)
|
2225
2226
|
if strategy is None or len(strategy) == 0:
|
2226
2227
|
return []
|
@@ -2230,7 +2231,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
|
2230
2231
|
start = s['start']
|
2231
2232
|
end = s['end']
|
2232
2233
|
if hour_overlap(s, remove):
|
2233
|
-
output(f" {hours_time(start)}-{hours_time(end)}
|
2234
|
+
output(f" {hours_time(start)}-{hours_time(end)} was removed from strategy", 2)
|
2234
2235
|
continue
|
2235
2236
|
# add segment
|
2236
2237
|
min_soc_now = s['min_soc'] if s.get('min_soc') is not None and s['min_soc'] > 10 else 10
|
@@ -2239,9 +2240,9 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
|
2239
2240
|
fdsoc = s.get('fdsoc')
|
2240
2241
|
fdpwr = s.get('fdpwr')
|
2241
2242
|
price = s.get('price')
|
2242
|
-
|
2243
|
+
valid_for = s.get('valid_for')
|
2243
2244
|
segment = {'start': start, 'end': end, 'mode': mode, 'min_soc': min_soc_now, 'max_soc': max_soc,
|
2244
|
-
'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, '
|
2245
|
+
'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, 'valid_for': valid_for}
|
2245
2246
|
if quiet == 0:
|
2246
2247
|
s = f" {hours_time(start)}-{hours_time(end)} {mode}, min_soc {min_soc_now}%"
|
2247
2248
|
s += f", max_soc {max_soc}%" if max_soc is not None else ""
|
@@ -2317,21 +2318,22 @@ def get_agile_times(tariff=agile_octopus, d=None):
|
|
2317
2318
|
# extract times and prices. Times are Zulu (UTC)
|
2318
2319
|
prices = [] # ordered list of 30 minute prices
|
2319
2320
|
for i in range(0, len(results)):
|
2320
|
-
|
2321
|
+
hour = i / 2
|
2322
|
+
start = (now.hour + hour) % 24
|
2321
2323
|
time_offset = daylight_saving(results[i]['valid_from'][:16]) if daylight_saving is not None else 0
|
2322
2324
|
prices.append({
|
2323
2325
|
'start': start,
|
2324
2326
|
'end': round_time(start + 0.5),
|
2325
2327
|
'time': hours_time(time_hours(results[i]['valid_from'][11:16]) + time_offset + time_shift),
|
2326
2328
|
'price': results[i]['value_inc_vat'],
|
2327
|
-
'
|
2329
|
+
'hour': hour})
|
2328
2330
|
tariff['agile']['base_time'] = period_from.replace('T', ' ')
|
2329
2331
|
tariff['agile']['prices'] = prices
|
2330
2332
|
plunge = []
|
2331
2333
|
plunge_price = tariff_config['plunge_price'] if tariff_config.get('plunge_price') is not None else 2
|
2332
2334
|
plunge_price = [plunge_price] if type(plunge_price) is not list else plunge_price
|
2333
2335
|
plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 6
|
2334
|
-
for i in range(0,
|
2336
|
+
for i in range(0, len(prices)):
|
2335
2337
|
# hour relative index into list of plunge prices, starting at 7am
|
2336
2338
|
x = int(((now.hour - 7 + i / 2) % 24) * len(plunge_price) / 24)
|
2337
2339
|
if prices[i] is not None and prices[i]['price'] < plunge_price[x]:
|
@@ -2591,7 +2593,7 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
|
|
2591
2593
|
if strategy is not None:
|
2592
2594
|
period['mode'] = 'SelfUse'
|
2593
2595
|
for d in strategy:
|
2594
|
-
if hour_in(h, d) and (d.get('
|
2596
|
+
if hour_in(h, d) and (d.get('valid_for') is None or i in d['valid_for']):
|
2595
2597
|
mode = d['mode']
|
2596
2598
|
period['mode'] = mode
|
2597
2599
|
min_soc_now = d['min_soc'] if d.get('min_soc') is not None else min_soc
|
@@ -3058,18 +3060,16 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3058
3060
|
start_residual = interpolate(time_to_start, bat_timed) # residual when charge time starts
|
3059
3061
|
start_soc = int(start_residual / capacity * 100 + 0.5)
|
3060
3062
|
end_residual = interpolate(time_to_end, bat_timed) # residual when charge time ends without charging
|
3061
|
-
target_soc = charge_config
|
3062
|
-
target_kwh = target_soc / 100 * capacity if target_soc is not None else 0
|
3063
|
+
target_soc = charge_config.get('target_soc')
|
3064
|
+
target_kwh = capacity if full_charge is not None or force_charge == 2 else (target_soc / 100 * capacity) if target_soc is not None else 0
|
3063
3065
|
if target_kwh > (end_residual + kwh_needed):
|
3064
3066
|
kwh_needed = target_kwh - end_residual
|
3065
|
-
elif full_charge is not None or force_charge == 2:
|
3066
|
-
kwh_needed = capacity - start_residual
|
3067
3067
|
elif test_charge is not None:
|
3068
3068
|
output(f"\nTest charge of {test_charge}kWh")
|
3069
3069
|
kwh_needed = test_charge
|
3070
3070
|
charge_message = "** test charge **"
|
3071
3071
|
# work out charge needed
|
3072
|
-
if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh']:
|
3072
|
+
if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh'] and test_charge is None:
|
3073
3073
|
output(f"\nNo charging needed:")
|
3074
3074
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
3075
3075
|
charge_message = "no charge needed"
|
@@ -3087,24 +3087,29 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3087
3087
|
output(f"\nCharge needed {kwh_needed:.2f}kWh:")
|
3088
3088
|
charge_message = "with charge added"
|
3089
3089
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
3090
|
-
output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(time_to_start, time_line))} ({start_residual:.2f}kWh)")
|
3091
3090
|
# work out time to add kwh_needed to battery
|
3092
|
-
|
3093
|
-
hours = round_time(kwh_needed /
|
3094
|
-
# charge time exceeded or charge needed exceeds capacity
|
3095
|
-
|
3096
|
-
|
3097
|
-
hours = charge_time
|
3098
|
-
elif hours < charge_config['min_hours']:
|
3091
|
+
charge_rate = charge_power * charge_loss
|
3092
|
+
hours = round_time(kwh_needed / charge_rate)
|
3093
|
+
# check if charge time exceeded or charge needed exceeds capacity
|
3094
|
+
hours_to_full = round_time((capacity - start_residual) / (charge_rate) + 10)
|
3095
|
+
if hours < charge_config['min_hours']:
|
3099
3096
|
hours = charge_config['min_hours']
|
3100
|
-
|
3097
|
+
elif hours > charge_time:
|
3098
|
+
hours = charge_time
|
3099
|
+
elif hours > hours_to_full:
|
3100
|
+
kwh_shortfall = (hours - hours_to_full) * charge_rate # amount of energy that won't be added
|
3101
|
+
required = hours_to_full + charge_time * kwh_shortfall / (end_residual - start_residual) # time to recover energy not added
|
3102
|
+
hours = required if required < charge_time else charge_time
|
3101
3103
|
# rework charge and discharge
|
3102
3104
|
charge_period = get_best_charge_period(start_at, hours)
|
3103
3105
|
charge_offset = round_time(charge_period['start'] - start_at) if charge_period is not None else 0
|
3104
3106
|
price = charge_period.get('price') if charge_period is not None else None
|
3105
3107
|
start_timed = time_to_start + charge_offset * steps_per_hour
|
3106
3108
|
end_timed = (start_timed + hours * steps_per_hour) if force_charge == 0 else time_to_end
|
3107
|
-
|
3109
|
+
start_residual = interpolate(start_timed, bat_timed)
|
3110
|
+
end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
|
3111
|
+
output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(start_timed, time_line))} ({start_residual:.2f}kWh)")
|
3112
|
+
output(f" Charge to: {end_soc:.0f} {hours_time(adjusted_hour(start_timed, time_line))}-{hours_time(adjusted_hour(end_timed, time_line))}" + (f" at {price:5.2f}p/kWh" if price is not None else ""))
|
3108
3113
|
for i in range(int(time_to_start), int(end_timed) + 1):
|
3109
3114
|
j = i + 1
|
3110
3115
|
# work out time (fraction of hour) when charging in hour from i to j
|
@@ -3196,7 +3201,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3196
3201
|
end1 = start1 if force_charge == 0 else start2
|
3197
3202
|
end2 = round_time(base_hour + (end_timed if force_charge == 0 else time_to_end) / steps_per_hour)
|
3198
3203
|
if timed_mode > 1:
|
3199
|
-
periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc,
|
3204
|
+
periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc, end_soc=end_soc, start_soc=start_soc)
|
3200
3205
|
set_schedule(periods = periods)
|
3201
3206
|
else:
|
3202
3207
|
set_charge(ch1=False, st1=start1, en1=end1, ch2=True, st2=start2, en2=end2, force=1)
|
foxesscloud/openapi.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
##################################################################################################
|
2
2
|
"""
|
3
3
|
Module: Fox ESS Cloud using Open API
|
4
|
-
Updated:
|
4
|
+
Updated: 26 September 2024
|
5
5
|
By: Tony Matthews
|
6
6
|
"""
|
7
7
|
##################################################################################################
|
@@ -10,7 +10,7 @@ By: Tony Matthews
|
|
10
10
|
# ALL RIGHTS ARE RESERVED © Tony Matthews 2024
|
11
11
|
##################################################################################################
|
12
12
|
|
13
|
-
version = "2.5.
|
13
|
+
version = "2.5.5"
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -669,7 +669,7 @@ def set_charge(ch1=None, st1=None, en1=None, ch2=None, st2=None, en2=None, force
|
|
669
669
|
output(f"success", 2)
|
670
670
|
return battery_settings
|
671
671
|
|
672
|
-
def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10,
|
672
|
+
def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, end_soc=100, start_soc=10):
|
673
673
|
output(f"\nConfiguring schedule",1)
|
674
674
|
charge = []
|
675
675
|
st1 = time_hours(st1)
|
@@ -678,7 +678,7 @@ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, target_so
|
|
678
678
|
en2 = time_hours(en2)
|
679
679
|
span = None
|
680
680
|
if st2 is not None and en2 is not None and st2 != en2:
|
681
|
-
charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc':
|
681
|
+
charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc': end_soc})
|
682
682
|
span = {'start': st2, 'end': en2}
|
683
683
|
if st1 is not None and en1 is not None and st1 != en1:
|
684
684
|
charge.append({'start': st1, 'end': en1, 'mode': 'SelfUse', 'min_soc': start_soc})
|
@@ -2085,6 +2085,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
|
2085
2085
|
if use.get('agile') is not None and use['agile'].get('strategy') is not None:
|
2086
2086
|
base_time_adjust = hours_difference(base_time, use['agile'].get('base_time') )
|
2087
2087
|
for s in use['agile']['strategy']:
|
2088
|
+
s['valid_for'] = [int((s['hour'] - base_time_adjust) * steps_per_hour + i) for i in range(0, steps_per_hour // 2)] if s.get('hour') is not None else None
|
2088
2089
|
strategy.append(s)
|
2089
2090
|
if strategy is None or len(strategy) == 0:
|
2090
2091
|
return []
|
@@ -2094,7 +2095,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
|
2094
2095
|
start = s['start']
|
2095
2096
|
end = s['end']
|
2096
2097
|
if hour_overlap(s, remove):
|
2097
|
-
output(f" {hours_time(start)}-{hours_time(end)}
|
2098
|
+
output(f" {hours_time(start)}-{hours_time(end)} was removed from strategy", 2)
|
2098
2099
|
continue
|
2099
2100
|
# add segment
|
2100
2101
|
min_soc_now = s['min_soc'] if s.get('min_soc') is not None and s['min_soc'] > 10 else 10
|
@@ -2103,9 +2104,9 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
|
2103
2104
|
fdsoc = s.get('fdsoc')
|
2104
2105
|
fdpwr = s.get('fdpwr')
|
2105
2106
|
price = s.get('price')
|
2106
|
-
|
2107
|
+
valid_for = s.get('valid_for')
|
2107
2108
|
segment = {'start': start, 'end': end, 'mode': mode, 'min_soc': min_soc_now, 'max_soc': max_soc,
|
2108
|
-
'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, '
|
2109
|
+
'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, 'valid_for': valid_for}
|
2109
2110
|
if quiet == 0:
|
2110
2111
|
s = f" {hours_time(start)}-{hours_time(end)} {mode}, min_soc {min_soc_now}%"
|
2111
2112
|
s += f", max_soc {max_soc}%" if max_soc is not None else ""
|
@@ -2181,21 +2182,22 @@ def get_agile_times(tariff=agile_octopus, d=None):
|
|
2181
2182
|
# extract times and prices. Times are Zulu (UTC)
|
2182
2183
|
prices = [] # ordered list of 30 minute prices
|
2183
2184
|
for i in range(0, len(results)):
|
2184
|
-
|
2185
|
+
hour = i / 2
|
2186
|
+
start = (now.hour + hour) % 24
|
2185
2187
|
time_offset = daylight_saving(results[i]['valid_from'][:16]) if daylight_saving is not None else 0
|
2186
2188
|
prices.append({
|
2187
2189
|
'start': start,
|
2188
2190
|
'end': round_time(start + 0.5),
|
2189
2191
|
'time': hours_time(time_hours(results[i]['valid_from'][11:16]) + time_offset + time_shift),
|
2190
2192
|
'price': results[i]['value_inc_vat'],
|
2191
|
-
'
|
2193
|
+
'hour': hour})
|
2192
2194
|
tariff['agile']['base_time'] = period_from.replace('T', ' ')
|
2193
2195
|
tariff['agile']['prices'] = prices
|
2194
2196
|
plunge = []
|
2195
2197
|
plunge_price = tariff_config['plunge_price'] if tariff_config.get('plunge_price') is not None else 2
|
2196
2198
|
plunge_price = [plunge_price] if type(plunge_price) is not list else plunge_price
|
2197
2199
|
plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 6
|
2198
|
-
for i in range(0,
|
2200
|
+
for i in range(0, len(prices)):
|
2199
2201
|
# hour relative index into list of plunge prices, starting at 7am
|
2200
2202
|
x = int(((now.hour - 7 + i / 2) % 24) * len(plunge_price) / 24)
|
2201
2203
|
if prices[i] is not None and prices[i]['price'] < plunge_price[x]:
|
@@ -2455,7 +2457,7 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
|
|
2455
2457
|
if strategy is not None:
|
2456
2458
|
period['mode'] = 'SelfUse'
|
2457
2459
|
for d in strategy:
|
2458
|
-
if hour_in(h, d) and (d.get('
|
2460
|
+
if hour_in(h, d) and (d.get('valid_for') is None or i in d['valid_for']):
|
2459
2461
|
mode = d['mode']
|
2460
2462
|
period['mode'] = mode
|
2461
2463
|
min_soc_now = d['min_soc'] if d.get('min_soc') is not None else min_soc
|
@@ -2922,18 +2924,16 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2922
2924
|
start_residual = interpolate(time_to_start, bat_timed) # residual when charge time starts
|
2923
2925
|
start_soc = int(start_residual / capacity * 100 + 0.5)
|
2924
2926
|
end_residual = interpolate(time_to_end, bat_timed) # residual when charge time ends without charging
|
2925
|
-
target_soc = charge_config
|
2926
|
-
target_kwh = target_soc / 100 * capacity if target_soc is not None else 0
|
2927
|
+
target_soc = charge_config.get('target_soc')
|
2928
|
+
target_kwh = capacity if full_charge is not None or force_charge == 2 else (target_soc / 100 * capacity) if target_soc is not None else 0
|
2927
2929
|
if target_kwh > (end_residual + kwh_needed):
|
2928
2930
|
kwh_needed = target_kwh - end_residual
|
2929
|
-
elif full_charge is not None or force_charge == 2:
|
2930
|
-
kwh_needed = capacity - start_residual
|
2931
2931
|
elif test_charge is not None:
|
2932
2932
|
output(f"\nTest charge of {test_charge}kWh")
|
2933
2933
|
kwh_needed = test_charge
|
2934
2934
|
charge_message = "** test charge **"
|
2935
2935
|
# work out charge needed
|
2936
|
-
if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh'] and
|
2936
|
+
if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh'] and test_charge is None:
|
2937
2937
|
output(f"\nNo charging needed:")
|
2938
2938
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
2939
2939
|
charge_message = "no charge needed"
|
@@ -2951,24 +2951,29 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2951
2951
|
output(f"\nCharge needed {kwh_needed:.2f}kWh:")
|
2952
2952
|
charge_message = "with charge added"
|
2953
2953
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
2954
|
-
output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(time_to_start, time_line))} ({start_residual:.2f}kWh)")
|
2955
2954
|
# work out time to add kwh_needed to battery
|
2956
|
-
|
2957
|
-
hours = round_time(kwh_needed /
|
2958
|
-
# charge time exceeded or charge needed exceeds capacity
|
2959
|
-
|
2960
|
-
|
2961
|
-
hours = charge_time
|
2962
|
-
elif hours < charge_config['min_hours']:
|
2955
|
+
charge_rate = charge_power * charge_loss
|
2956
|
+
hours = round_time(kwh_needed / charge_rate)
|
2957
|
+
# check if charge time exceeded or charge needed exceeds capacity
|
2958
|
+
hours_to_full = round_time((capacity - start_residual) / (charge_rate) + 10)
|
2959
|
+
if hours < charge_config['min_hours']:
|
2963
2960
|
hours = charge_config['min_hours']
|
2964
|
-
|
2961
|
+
elif hours > charge_time:
|
2962
|
+
hours = charge_time
|
2963
|
+
elif hours > hours_to_full:
|
2964
|
+
kwh_shortfall = (hours - hours_to_full) * charge_rate # amount of energy that won't be added
|
2965
|
+
required = hours_to_full + charge_time * kwh_shortfall / (end_residual - start_residual) # time to recover energy not added
|
2966
|
+
hours = required if required < charge_time else charge_time
|
2965
2967
|
# rework charge and discharge
|
2966
2968
|
charge_period = get_best_charge_period(start_at, hours)
|
2967
2969
|
charge_offset = round_time(charge_period['start'] - start_at) if charge_period is not None else 0
|
2968
2970
|
price = charge_period.get('price') if charge_period is not None else None
|
2969
2971
|
start_timed = time_to_start + charge_offset * steps_per_hour
|
2970
2972
|
end_timed = (start_timed + hours * steps_per_hour) if force_charge == 0 else time_to_end
|
2971
|
-
|
2973
|
+
start_residual = interpolate(start_timed, bat_timed)
|
2974
|
+
end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
|
2975
|
+
output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(start_timed, time_line))} ({start_residual:.2f}kWh)")
|
2976
|
+
output(f" Charge to: {end_soc:.0f}% {hours_time(adjusted_hour(start_timed, time_line))}-{hours_time(adjusted_hour(end_timed, time_line))}" + (f" at {price:5.2f}p/kWh" if price is not None else ""))
|
2972
2977
|
for i in range(int(time_to_start), int(end_timed) + 1):
|
2973
2978
|
j = i + 1
|
2974
2979
|
# work out time (fraction of hour) when charging in hour from i to j
|
@@ -3060,7 +3065,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3060
3065
|
end1 = start1 if force_charge == 0 else start2
|
3061
3066
|
end2 = round_time(base_hour + (end_timed if force_charge == 0 else time_to_end) / steps_per_hour)
|
3062
3067
|
if timed_mode > 1:
|
3063
|
-
periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc,
|
3068
|
+
periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc, end_soc=end_soc, start_soc=start_soc)
|
3064
3069
|
set_schedule(periods = periods)
|
3065
3070
|
else:
|
3066
3071
|
set_charge(ch1=False, st1=start1, en1=end1, ch2=True, st2=start2, en2=end2, force=1)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foxesscloud
|
3
|
-
Version: 2.5.
|
3
|
+
Version: 2.5.5
|
4
4
|
Summary: library for accessing Fox ESS cloud data using Open API
|
5
5
|
Author-email: Tony Matthews <tony@quasair.co.uk>
|
6
6
|
Project-URL: Homepage, https://github.com/TonyM1958/FoxESS-Cloud
|
@@ -783,6 +783,12 @@ This setting can be:
|
|
783
783
|
|
784
784
|
# Version Info
|
785
785
|
|
786
|
+
2.5.5<br>
|
787
|
+
Improve validation of plunge price periods so they don't repeat across days.
|
788
|
+
Correct start and end soc times and values when charging using best Agile time periods.
|
789
|
+
Extend charge times when charge needed exceeds battery capacity.
|
790
|
+
|
791
|
+
|
786
792
|
2.5.4<br>
|
787
793
|
Remove preset 'weighting' that were not used.
|
788
794
|
Update weighting to apply the requested charge duration correctly.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
foxesscloud/foxesscloud.py,sha256=biC30gvvQHFTu3CCDhuK6KRGNi2hFbgFxHjSykj-UpE,211062
|
2
|
+
foxesscloud/openapi.py,sha256=MEzJPleAV8-2gR17e2qmQezVeotiqkHMzeCBDVJOfsY,204698
|
3
|
+
foxesscloud-2.5.5.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
|
4
|
+
foxesscloud-2.5.5.dist-info/METADATA,sha256=kvZCDYUyu7v1OkE_dik_f_4V4OXQTaZKwOYWYFTircU,55462
|
5
|
+
foxesscloud-2.5.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
+
foxesscloud-2.5.5.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
|
7
|
+
foxesscloud-2.5.5.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
foxesscloud/foxesscloud.py,sha256=elLQc40Zt8QkjmiOhZ2gNe-Wqp5NE38LgnflHpUQMmc,210693
|
2
|
-
foxesscloud/openapi.py,sha256=aBVMx8eyh5WRbUQTiAFHh6SDNiKeksBplODIO1x3L-k,204407
|
3
|
-
foxesscloud-2.5.4.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
|
4
|
-
foxesscloud-2.5.4.dist-info/METADATA,sha256=KXfCjbfLt8t5jjQbLW_-7UJyoKBvskga5X0sxMkzxmk,55214
|
5
|
-
foxesscloud-2.5.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
-
foxesscloud-2.5.4.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
|
7
|
-
foxesscloud-2.5.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|