foxesscloud 2.5.5__tar.gz → 2.5.7__tar.gz
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-2.5.5/src/foxesscloud.egg-info → foxesscloud-2.5.7}/PKG-INFO +10 -2
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/README.md +9 -1
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/pyproject.toml +1 -1
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/src/foxesscloud/foxesscloud.py +33 -30
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/src/foxesscloud/openapi.py +32 -29
- {foxesscloud-2.5.5 → foxesscloud-2.5.7/src/foxesscloud.egg-info}/PKG-INFO +10 -2
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/LICENCE +0 -0
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/setup.cfg +0 -0
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/src/foxesscloud.egg-info/SOURCES.txt +0 -0
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/src/foxesscloud.egg-info/dependency_links.txt +0 -0
- {foxesscloud-2.5.5 → foxesscloud-2.5.7}/src/foxesscloud.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foxesscloud
|
3
|
-
Version: 2.5.
|
3
|
+
Version: 2.5.7
|
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,12 +783,20 @@ This setting can be:
|
|
783
783
|
|
784
784
|
# Version Info
|
785
785
|
|
786
|
+
2.5.7<br>
|
787
|
+
Fix problem with schedules being set for plunge periods that are more than 24 hours in the future.
|
788
|
+
Add date to plunge period display.
|
789
|
+
|
790
|
+
2.5.6<br>
|
791
|
+
Change plunge slots to 8 and plungs pricing to [3,10].
|
792
|
+
Change min_hours setting in charge_needed to 0.5 (30 minutes) and round up charge times to increments of this.
|
793
|
+
Show data and plot starting at t=0.
|
794
|
+
|
786
795
|
2.5.5<br>
|
787
796
|
Improve validation of plunge price periods so they don't repeat across days.
|
788
797
|
Correct start and end soc times and values when charging using best Agile time periods.
|
789
798
|
Extend charge times when charge needed exceeds battery capacity.
|
790
799
|
|
791
|
-
|
792
800
|
2.5.4<br>
|
793
801
|
Remove preset 'weighting' that were not used.
|
794
802
|
Update weighting to apply the requested charge duration correctly.
|
@@ -769,12 +769,20 @@ This setting can be:
|
|
769
769
|
|
770
770
|
# Version Info
|
771
771
|
|
772
|
+
2.5.7<br>
|
773
|
+
Fix problem with schedules being set for plunge periods that are more than 24 hours in the future.
|
774
|
+
Add date to plunge period display.
|
775
|
+
|
776
|
+
2.5.6<br>
|
777
|
+
Change plunge slots to 8 and plungs pricing to [3,10].
|
778
|
+
Change min_hours setting in charge_needed to 0.5 (30 minutes) and round up charge times to increments of this.
|
779
|
+
Show data and plot starting at t=0.
|
780
|
+
|
772
781
|
2.5.5<br>
|
773
782
|
Improve validation of plunge price periods so they don't repeat across days.
|
774
783
|
Correct start and end soc times and values when charging using best Agile time periods.
|
775
784
|
Extend charge times when charge needed exceeds battery capacity.
|
776
785
|
|
777
|
-
|
778
786
|
2.5.4<br>
|
779
787
|
Remove preset 'weighting' that were not used.
|
780
788
|
Update weighting to apply the requested charge duration correctly.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
##################################################################################################
|
2
2
|
"""
|
3
3
|
Module: Fox ESS Cloud
|
4
|
-
Updated:
|
4
|
+
Updated: 28 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.9"
|
14
14
|
print(f"FoxESS-Cloud version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -742,7 +742,7 @@ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, end_soc=1
|
|
742
742
|
charge.append({'start': st1, 'end': st3, 'mode': 'SelfUse', 'min_soc': start_soc})
|
743
743
|
st1 = st3
|
744
744
|
charge.append({'start': st1, 'end': en1, 'mode': 'SelfUse', 'min_soc': min_soc})
|
745
|
-
strategy = get_strategy(remove=span)[:(8 - len(charge))]
|
745
|
+
strategy = get_strategy(remove=span, limit=24)[:(8 - len(charge))]
|
746
746
|
for c in charge:
|
747
747
|
strategy.append(c)
|
748
748
|
periods = []
|
@@ -2208,7 +2208,7 @@ test_strategy = [
|
|
2208
2208
|
{'start': 21, 'end': 22, 'mode': 'ForceCharge'}]
|
2209
2209
|
|
2210
2210
|
# return a strategy that has been sorted and filtered for charge times:
|
2211
|
-
def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
2211
|
+
def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0, limit=None):
|
2212
2212
|
global tariff, base_time
|
2213
2213
|
if use is None:
|
2214
2214
|
use = tariff
|
@@ -2221,8 +2221,9 @@ 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
|
-
|
2225
|
-
|
2224
|
+
if limit is None or s.get('hour') is None or s['hour'] - base_time_adjust < 24:
|
2225
|
+
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
|
2226
|
+
strategy.append(s)
|
2226
2227
|
if strategy is None or len(strategy) == 0:
|
2227
2228
|
return []
|
2228
2229
|
updated = []
|
@@ -2270,8 +2271,8 @@ tariff_config = {
|
|
2270
2271
|
'region': "H", # region code to use for Octopus API
|
2271
2272
|
'update_time': 16.5, # time in hours when tomrow's data can be fetched
|
2272
2273
|
'weighting': None, # weights for weighted average
|
2273
|
-
'plunge_price': [
|
2274
|
-
'plunge_slots':
|
2274
|
+
'plunge_price': [3, 10], # plunge price in p/kWh inc VAT over 24 hours from 7am, 7pm
|
2275
|
+
'plunge_slots': 8, # number of 30 minute slots to use
|
2275
2276
|
'data_wrap': 6, # prices to show per line
|
2276
2277
|
'show_data': 1, # show pricing data
|
2277
2278
|
'show_plot': 1 # plot pricing data
|
@@ -2344,7 +2345,8 @@ def get_agile_times(tariff=agile_octopus, d=None):
|
|
2344
2345
|
output(f"\nPlunge slots:", 1)
|
2345
2346
|
for t in plunge:
|
2346
2347
|
strategy.append(prices[t])
|
2347
|
-
|
2348
|
+
date = (now + timedelta(hours = prices[t]['hour'])).strftime("%Y-%m-%d")
|
2349
|
+
output(f" {format_period(prices[t])} at {prices[t]['price']:.1f}p on {date}", 1)
|
2348
2350
|
tariff['agile']['strategy'] = strategy
|
2349
2351
|
for key in ['off_peak1', 'off_peak2', 'off_peak3', 'off_peak4']:
|
2350
2352
|
if tariff.get(key) is None:
|
@@ -2681,7 +2683,7 @@ charge_config = {
|
|
2681
2683
|
'consumption_days': 3, # number of days to use for average consumption (1-7)
|
2682
2684
|
'consumption_span': 'week', # 'week' = last n days or 'weekday' = last n weekdays
|
2683
2685
|
'use_today': 21.0, # hour when todays consumption and generation can be used
|
2684
|
-
'min_hours': 0.
|
2686
|
+
'min_hours': 0.5, # minimum charge time in decimal hours
|
2685
2687
|
'min_kwh': 0.5, # minimum to add in kwh
|
2686
2688
|
'forecast_selection': 1, # 0 = use available forecast / generation, 1 only update settings with forecast
|
2687
2689
|
'annual_consumption': None, # optional annual consumption in kWh
|
@@ -2778,7 +2780,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2778
2780
|
for t in times:
|
2779
2781
|
if hour_in(hour_now, t) and update_settings > 0:
|
2780
2782
|
update_settings = 0
|
2781
|
-
output(f"\nSettings will not be updated during a charge period")
|
2783
|
+
output(f"\nSettings will not be updated during a charge period {format_period(t)}")
|
2782
2784
|
time_to_start = round_time(t['start'] - base_hour) * steps_per_hour
|
2783
2785
|
time_to_start += hour_adjustment * steps_per_hour if time_to_start > time_change else 0
|
2784
2786
|
charge_time = round_time(t['end'] - t['start'])
|
@@ -3069,7 +3071,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3069
3071
|
kwh_needed = test_charge
|
3070
3072
|
charge_message = "** test charge **"
|
3071
3073
|
# work out charge needed
|
3072
|
-
if kwh_min >
|
3074
|
+
if kwh_min > reserve and kwh_needed < charge_config['min_kwh'] and test_charge is None:
|
3073
3075
|
output(f"\nNo charging needed:")
|
3074
3076
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
3075
3077
|
charge_message = "no charge needed"
|
@@ -3089,17 +3091,18 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3089
3091
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
3090
3092
|
# work out time to add kwh_needed to battery
|
3091
3093
|
charge_rate = charge_power * charge_loss
|
3092
|
-
hours =
|
3094
|
+
hours = kwh_needed / charge_rate
|
3093
3095
|
# check if charge time exceeded or charge needed exceeds capacity
|
3094
|
-
hours_to_full =
|
3095
|
-
if hours
|
3096
|
-
hours = charge_config['min_hours']
|
3097
|
-
elif hours > charge_time:
|
3096
|
+
hours_to_full = (capacity - start_residual) / charge_rate
|
3097
|
+
if hours > charge_time:
|
3098
3098
|
hours = charge_time
|
3099
3099
|
elif hours > hours_to_full:
|
3100
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 / (
|
3102
|
-
hours = required if required < charge_time else charge_time
|
3101
|
+
required = hours_to_full + charge_time * kwh_shortfall / abs(start_residual - end_residual) # time to recover energy not added
|
3102
|
+
hours = required if required > hours and required < charge_time else charge_time
|
3103
|
+
# round charge time
|
3104
|
+
min_hours = charge_config['min_hours']
|
3105
|
+
hours = int(hours / min_hours + 0.99) * min_hours
|
3103
3106
|
# rework charge and discharge
|
3104
3107
|
charge_period = get_best_charge_period(start_at, hours)
|
3105
3108
|
charge_offset = round_time(charge_period['start'] - start_at) if charge_period is not None else 0
|
@@ -3109,7 +3112,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3109
3112
|
start_residual = interpolate(start_timed, bat_timed)
|
3110
3113
|
end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
|
3111
3114
|
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 ""))
|
3115
|
+
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 ""))
|
3113
3116
|
for i in range(int(time_to_start), int(end_timed) + 1):
|
3114
3117
|
j = i + 1
|
3115
3118
|
# work out time (fraction of hour) when charging in hour from i to j
|
@@ -3141,11 +3144,11 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3141
3144
|
if show_data > 0:
|
3142
3145
|
data_wrap = charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 6
|
3143
3146
|
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3144
|
-
h = base_hour
|
3145
|
-
t =
|
3147
|
+
h = base_hour
|
3148
|
+
t = 0
|
3146
3149
|
while t < len(time_line) and bat_timed[t] is not None:
|
3147
3150
|
col = h % data_wrap
|
3148
|
-
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t ==
|
3151
|
+
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t == 0 or col == 0 else ""
|
3149
3152
|
s += f" {bat_timed[t]:5.2f}" if show_data == 2 else f" {bat_timed[t] / capacity * 100:3.0f}%"
|
3150
3153
|
h += 1
|
3151
3154
|
t += steps_per_hour
|
@@ -3153,8 +3156,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3153
3156
|
if show_plot > 0:
|
3154
3157
|
print()
|
3155
3158
|
plt.figure(figsize=(figure_width, figure_width/2))
|
3156
|
-
x_timed = [i for i in range(
|
3157
|
-
x_ticks = [i for i in range(
|
3159
|
+
x_timed = [i for i in range(0, run_time)]
|
3160
|
+
x_ticks = [i for i in range(0, run_time, steps_per_hour)]
|
3158
3161
|
plt.xticks(ticks=x_ticks, labels=[hours_time(time_line[x]) for x in x_ticks], rotation=90, fontsize=8, ha='center')
|
3159
3162
|
if show_plot == 1:
|
3160
3163
|
title = f"Predicted Battery SoC % at {base_time}({charge_message})"
|
@@ -3278,11 +3281,11 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3278
3281
|
if show_data > 0 and plots.get('SoC') is not None:
|
3279
3282
|
data_wrap = charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 6
|
3280
3283
|
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3281
|
-
h = base_hour
|
3282
|
-
t =
|
3284
|
+
h = base_hour
|
3285
|
+
t = 0
|
3283
3286
|
while t < len(time_line) and bat_timed[t] is not None:
|
3284
3287
|
col = h % data_wrap
|
3285
|
-
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t ==
|
3288
|
+
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t == 0 or col == 0 else ""
|
3286
3289
|
s += f" {plots['SoC'][t]:5.2f}" if show_data == 2 else f" {plots['SoC'][t] / capacity * 100:3.0f}%"
|
3287
3290
|
h += 1
|
3288
3291
|
t += steps_per_hour
|
@@ -3290,8 +3293,8 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3290
3293
|
if show_plot > 0:
|
3291
3294
|
print()
|
3292
3295
|
plt.figure(figsize=(figure_width, figure_width/2))
|
3293
|
-
x_timed = [i for i in range(
|
3294
|
-
x_ticks = [i for i in range(
|
3296
|
+
x_timed = [i for i in range(0, run_time)]
|
3297
|
+
x_ticks = [i for i in range(0, run_time, steps_per_hour)]
|
3295
3298
|
plt.xticks(ticks=x_ticks, labels=[hours_time(time_line[x]) for x in x_ticks], rotation=90, fontsize=8, ha='center')
|
3296
3299
|
if show_plot == 1:
|
3297
3300
|
title = f"Predicted Battery SoC % at {base_time}({charge_message})"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
##################################################################################################
|
2
2
|
"""
|
3
3
|
Module: Fox ESS Cloud using Open API
|
4
|
-
Updated:
|
4
|
+
Updated: 28 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.7"
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -690,7 +690,7 @@ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, end_soc=1
|
|
690
690
|
charge.append({'start': st1, 'end': st3, 'mode': 'SelfUse', 'min_soc': start_soc})
|
691
691
|
st1 = st3
|
692
692
|
charge.append({'start': st1, 'end': en1, 'mode': 'SelfUse', 'min_soc': min_soc})
|
693
|
-
strategy = get_strategy(remove=span)[:(8 - len(charge))]
|
693
|
+
strategy = get_strategy(remove=span, limit=24)[:(8 - len(charge))]
|
694
694
|
for c in charge:
|
695
695
|
strategy.append(c)
|
696
696
|
periods = []
|
@@ -2072,7 +2072,7 @@ test_strategy = [
|
|
2072
2072
|
{'start': 21, 'end': 22, 'mode': 'ForceCharge'}]
|
2073
2073
|
|
2074
2074
|
# return a strategy that has been sorted and filtered for charge times:
|
2075
|
-
def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
|
2075
|
+
def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0, limit=None):
|
2076
2076
|
global tariff, base_time
|
2077
2077
|
if use is None:
|
2078
2078
|
use = tariff
|
@@ -2085,8 +2085,9 @@ 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
|
-
|
2089
|
-
|
2088
|
+
if limit is None or s.get('hour') is None or s['hour'] - base_time_adjust < 24:
|
2089
|
+
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
|
2090
|
+
strategy.append(s)
|
2090
2091
|
if strategy is None or len(strategy) == 0:
|
2091
2092
|
return []
|
2092
2093
|
updated = []
|
@@ -2134,8 +2135,8 @@ tariff_config = {
|
|
2134
2135
|
'region': "H", # region code to use for Octopus API
|
2135
2136
|
'update_time': 16.5, # time in hours when tomrow's data can be fetched
|
2136
2137
|
'weighting': None, # weights for weighted average
|
2137
|
-
'plunge_price': [
|
2138
|
-
'plunge_slots':
|
2138
|
+
'plunge_price': [3, 10], # plunge price in p/kWh inc VAT over 24 hours from 7am, 7pm
|
2139
|
+
'plunge_slots': 8, # number of 30 minute slots to use
|
2139
2140
|
'data_wrap': 6, # prices to show per line
|
2140
2141
|
'show_data': 1, # show pricing data
|
2141
2142
|
'show_plot': 1 # plot pricing data
|
@@ -2208,7 +2209,8 @@ def get_agile_times(tariff=agile_octopus, d=None):
|
|
2208
2209
|
output(f"\nPlunge slots:", 1)
|
2209
2210
|
for t in plunge:
|
2210
2211
|
strategy.append(prices[t])
|
2211
|
-
|
2212
|
+
date = (now + timedelta(hours = prices[t]['hour'])).strftime("%Y-%m-%d")
|
2213
|
+
output(f" {format_period(prices[t])} at {prices[t]['price']:.1f}p on {date}", 1)
|
2212
2214
|
tariff['agile']['strategy'] = strategy
|
2213
2215
|
for key in ['off_peak1', 'off_peak2', 'off_peak3', 'off_peak4']:
|
2214
2216
|
if tariff.get(key) is None:
|
@@ -2545,7 +2547,7 @@ charge_config = {
|
|
2545
2547
|
'consumption_days': 3, # number of days to use for average consumption (1-7)
|
2546
2548
|
'consumption_span': 'week', # 'week' = last n days or 'weekday' = last n weekdays
|
2547
2549
|
'use_today': 21.0, # hour when todays consumption and generation can be used
|
2548
|
-
'min_hours': 0.
|
2550
|
+
'min_hours': 0.5, # minimum charge time in decimal hours
|
2549
2551
|
'min_kwh': 0.5, # minimum to add in kwh
|
2550
2552
|
'forecast_selection': 1, # 0 = use available forecast / generation, 1 only update settings with forecast
|
2551
2553
|
'annual_consumption': None, # optional annual consumption in kWh
|
@@ -2642,7 +2644,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2642
2644
|
for t in times:
|
2643
2645
|
if hour_in(hour_now, t) and update_settings > 0:
|
2644
2646
|
update_settings = 0
|
2645
|
-
output(f"\nSettings will not be updated during a charge period")
|
2647
|
+
output(f"\nSettings will not be updated during a charge period {format_period(t)}")
|
2646
2648
|
time_to_start = round_time(t['start'] - base_hour) * steps_per_hour
|
2647
2649
|
time_to_start += hour_adjustment * steps_per_hour if time_to_start > time_change else 0
|
2648
2650
|
charge_time = round_time(t['end'] - t['start'])
|
@@ -2933,7 +2935,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2933
2935
|
kwh_needed = test_charge
|
2934
2936
|
charge_message = "** test charge **"
|
2935
2937
|
# work out charge needed
|
2936
|
-
if kwh_min >
|
2938
|
+
if kwh_min > reserve and kwh_needed < charge_config['min_kwh'] and test_charge is None:
|
2937
2939
|
output(f"\nNo charging needed:")
|
2938
2940
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
2939
2941
|
charge_message = "no charge needed"
|
@@ -2953,17 +2955,18 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2953
2955
|
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
2954
2956
|
# work out time to add kwh_needed to battery
|
2955
2957
|
charge_rate = charge_power * charge_loss
|
2956
|
-
hours =
|
2958
|
+
hours = kwh_needed / charge_rate
|
2957
2959
|
# check if charge time exceeded or charge needed exceeds capacity
|
2958
|
-
hours_to_full =
|
2959
|
-
if hours
|
2960
|
-
hours = charge_config['min_hours']
|
2961
|
-
elif hours > charge_time:
|
2960
|
+
hours_to_full = (capacity - start_residual) / charge_rate
|
2961
|
+
if hours > charge_time:
|
2962
2962
|
hours = charge_time
|
2963
2963
|
elif hours > hours_to_full:
|
2964
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 / (
|
2966
|
-
hours = required if required < charge_time else charge_time
|
2965
|
+
required = hours_to_full + charge_time * kwh_shortfall / abs(start_residual - end_residual) # hold time to recover energy not added
|
2966
|
+
hours = required if required > hours and required < charge_time else charge_time
|
2967
|
+
# round charge time
|
2968
|
+
min_hours = charge_config['min_hours']
|
2969
|
+
hours = int(hours / min_hours + 0.99) * min_hours
|
2967
2970
|
# rework charge and discharge
|
2968
2971
|
charge_period = get_best_charge_period(start_at, hours)
|
2969
2972
|
charge_offset = round_time(charge_period['start'] - start_at) if charge_period is not None else 0
|
@@ -3005,11 +3008,11 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3005
3008
|
if show_data > 0:
|
3006
3009
|
data_wrap = charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 6
|
3007
3010
|
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3008
|
-
h = base_hour
|
3009
|
-
t =
|
3011
|
+
h = base_hour
|
3012
|
+
t = 0
|
3010
3013
|
while t < len(time_line) and bat_timed[t] is not None:
|
3011
3014
|
col = h % data_wrap
|
3012
|
-
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t ==
|
3015
|
+
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t == 0 or col == 0 else ""
|
3013
3016
|
s += f" {bat_timed[t]:5.2f}" if show_data == 2 else f" {bat_timed[t] / capacity * 100:3.0f}%"
|
3014
3017
|
h += 1
|
3015
3018
|
t += steps_per_hour
|
@@ -3017,8 +3020,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3017
3020
|
if show_plot > 0:
|
3018
3021
|
print()
|
3019
3022
|
plt.figure(figsize=(figure_width, figure_width/2))
|
3020
|
-
x_timed = [i for i in range(
|
3021
|
-
x_ticks = [i for i in range(
|
3023
|
+
x_timed = [i for i in range(0, run_time)]
|
3024
|
+
x_ticks = [i for i in range(0, run_time, steps_per_hour)]
|
3022
3025
|
plt.xticks(ticks=x_ticks, labels=[hours_time(time_line[x]) for x in x_ticks], rotation=90, fontsize=8, ha='center')
|
3023
3026
|
if show_plot == 1:
|
3024
3027
|
title = f"Predicted Battery SoC % at {base_time}({charge_message})"
|
@@ -3141,11 +3144,11 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3141
3144
|
if show_data > 0 and plots.get('SoC') is not None:
|
3142
3145
|
data_wrap = charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 6
|
3143
3146
|
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3144
|
-
h = base_hour
|
3145
|
-
t =
|
3147
|
+
h = base_hour
|
3148
|
+
t = 0
|
3146
3149
|
while t < len(time_line) and bat_timed[t] is not None:
|
3147
3150
|
col = h % data_wrap
|
3148
|
-
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t ==
|
3151
|
+
s += (f"\n {hours_time(time_line[t])}" + " " * col * 6) if t == 0 or col == 0 else ""
|
3149
3152
|
s += f" {plots['SoC'][t]:5.2f}" if show_data == 2 else f" {plots['SoC'][t] / capacity * 100:3.0f}%"
|
3150
3153
|
h += 1
|
3151
3154
|
t += steps_per_hour
|
@@ -3153,8 +3156,8 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3153
3156
|
if show_plot > 0:
|
3154
3157
|
print()
|
3155
3158
|
plt.figure(figsize=(figure_width, figure_width/2))
|
3156
|
-
x_timed = [i for i in range(
|
3157
|
-
x_ticks = [i for i in range(
|
3159
|
+
x_timed = [i for i in range(0, run_time)]
|
3160
|
+
x_ticks = [i for i in range(0, run_time, steps_per_hour)]
|
3158
3161
|
plt.xticks(ticks=x_ticks, labels=[hours_time(time_line[x]) for x in x_ticks], rotation=90, fontsize=8, ha='center')
|
3159
3162
|
if show_plot == 1:
|
3160
3163
|
title = f"Predicted Battery SoC % at {base_time}({charge_message})"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foxesscloud
|
3
|
-
Version: 2.5.
|
3
|
+
Version: 2.5.7
|
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,12 +783,20 @@ This setting can be:
|
|
783
783
|
|
784
784
|
# Version Info
|
785
785
|
|
786
|
+
2.5.7<br>
|
787
|
+
Fix problem with schedules being set for plunge periods that are more than 24 hours in the future.
|
788
|
+
Add date to plunge period display.
|
789
|
+
|
790
|
+
2.5.6<br>
|
791
|
+
Change plunge slots to 8 and plungs pricing to [3,10].
|
792
|
+
Change min_hours setting in charge_needed to 0.5 (30 minutes) and round up charge times to increments of this.
|
793
|
+
Show data and plot starting at t=0.
|
794
|
+
|
786
795
|
2.5.5<br>
|
787
796
|
Improve validation of plunge price periods so they don't repeat across days.
|
788
797
|
Correct start and end soc times and values when charging using best Agile time periods.
|
789
798
|
Extend charge times when charge needed exceeds battery capacity.
|
790
799
|
|
791
|
-
|
792
800
|
2.5.4<br>
|
793
801
|
Remove preset 'weighting' that were not used.
|
794
802
|
Update weighting to apply the requested charge duration correctly.
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|