foxesscloud 2.5.8__py3-none-any.whl → 2.5.9__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 +52 -45
- foxesscloud/openapi.py +52 -45
- {foxesscloud-2.5.8.dist-info → foxesscloud-2.5.9.dist-info}/METADATA +45 -38
- foxesscloud-2.5.9.dist-info/RECORD +7 -0
- foxesscloud-2.5.8.dist-info/RECORD +0 -7
- {foxesscloud-2.5.8.dist-info → foxesscloud-2.5.9.dist-info}/LICENCE +0 -0
- {foxesscloud-2.5.8.dist-info → foxesscloud-2.5.9.dist-info}/WHEEL +0 -0
- {foxesscloud-2.5.8.dist-info → foxesscloud-2.5.9.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: 02 October 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.7.
|
13
|
+
version = "1.7.1"
|
14
14
|
print(f"FoxESS-Cloud version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -576,7 +576,9 @@ def get_firmware():
|
|
576
576
|
|
577
577
|
battery = None
|
578
578
|
battery_settings = None
|
579
|
-
|
579
|
+
|
580
|
+
# 1 = returns Residual Energy. 2 = resturns Residual Capacity
|
581
|
+
residual_handling = 1
|
580
582
|
|
581
583
|
def get_battery(info=0):
|
582
584
|
global token, device_id, battery, debug_setting, messages
|
@@ -2592,23 +2594,23 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
|
|
2592
2594
|
return work_mode_timed
|
2593
2595
|
|
2594
2596
|
# build the timed battery residual from the charge / discharge, work mode and min_soc
|
2597
|
+
# all power values are as measured at the inverter battery connection
|
2595
2598
|
def battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=None, reserve_drain=None):
|
2596
|
-
global charge_config, steps_per_hour
|
2597
|
-
bat_timed = []
|
2599
|
+
global charge_config, steps_per_hour, residual_handling
|
2598
2600
|
allowed_drain = charge_config['allowed_drain'] if charge_config.get('allowed_drain') is not None else 4
|
2599
2601
|
bms_loss = (charge_config['bms_power'] / 1000 if charge_config.get('bms_power') is not None else 0.05)
|
2600
|
-
charge_loss = charge_config['charge_loss']
|
2602
|
+
charge_loss = charge_config['charge_loss'][residual_handling - 1]
|
2603
|
+
discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
|
2601
2604
|
charge_limit = charge_config['charge_limit']
|
2602
2605
|
float_charge = charge_config['float_charge']
|
2603
2606
|
for i in range(0, len(work_mode_timed)):
|
2604
|
-
bat_timed.append(kwh_current)
|
2605
2607
|
w = work_mode_timed[i]
|
2606
2608
|
w['kwh'] = kwh_current
|
2607
2609
|
max_now = w['max_soc'] * capacity / 100
|
2608
2610
|
if kwh_current < max_now and w['charge'] > 0.0:
|
2609
2611
|
kwh_current += min([w['charge'], charge_limit - w['pv']]) * charge_loss / steps_per_hour
|
2610
2612
|
kwh_current = max_now if kwh_current > max_now else kwh_current
|
2611
|
-
kwh_current += (w['pv'] - w['discharge']
|
2613
|
+
kwh_current += (w['pv'] * charge_loss - w['discharge'] / discharge_loss) / steps_per_hour
|
2612
2614
|
if kwh_current > capacity:
|
2613
2615
|
# battery is full
|
2614
2616
|
kwh_current = capacity
|
@@ -2629,16 +2631,16 @@ def battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=
|
|
2629
2631
|
reserve_drain = reserve_now
|
2630
2632
|
if kwh_min is not None and kwh_current < kwh_min and i >= time_to_next: # track minimum without charge
|
2631
2633
|
kwh_min = kwh_current
|
2632
|
-
return (
|
2634
|
+
return ([work_mode_timed[i]['kwh'] for i in range(0, len(work_mode_timed))], kwh_min)
|
2633
2635
|
|
2636
|
+
# use work_mode_timed to generate time periods for the inverter schedule
|
2634
2637
|
def charge_periods(work_mode_timed, base_hour, min_soc, capacity):
|
2635
2638
|
global steps_per_hour
|
2636
|
-
output(f"\nConfiguring schedule:",1)
|
2637
2639
|
strategy = []
|
2638
2640
|
start = base_hour
|
2639
|
-
|
2641
|
+
times = []
|
2640
2642
|
for t in range(0, min([24 * steps_per_hour, len(work_mode_timed)])):
|
2641
|
-
period =
|
2643
|
+
period = times[0] if len(times) > 0 else work_mode_timed[0]
|
2642
2644
|
next_period = work_mode_timed[t]
|
2643
2645
|
h = base_hour + t / steps_per_hour
|
2644
2646
|
if h == 24 or period['mode'] != next_period['mode'] or period['hold'] != next_period['hold']:
|
@@ -2650,15 +2652,19 @@ def charge_periods(work_mode_timed, base_hour, min_soc, capacity):
|
|
2650
2652
|
s['max_soc'] = period.get('max_soc')
|
2651
2653
|
elif period['mode'] == 'SelfUse' and period['hold'] == 1:
|
2652
2654
|
s['min_soc'] = min([int(period['kwh'] / capacity * 100 + 0.5), 100])
|
2653
|
-
|
2655
|
+
s['end'] = (start + 1 / steps_per_hour) % 24
|
2656
|
+
for p in times:
|
2654
2657
|
p['min_soc'] = s['min_soc']
|
2655
2658
|
if s['mode'] != 'SelfUse' or s['min_soc'] != min_soc:
|
2656
2659
|
strategy.append(s)
|
2657
2660
|
start = h
|
2658
|
-
|
2659
|
-
|
2660
|
-
if len(strategy)
|
2661
|
+
times = []
|
2662
|
+
times.append(work_mode_timed[t])
|
2663
|
+
if len(strategy) == 0:
|
2664
|
+
return []
|
2665
|
+
if strategy[-1]['min_soc'] != min_soc:
|
2661
2666
|
strategy.append({'start': start %24, 'end': (start + 1 / steps_per_hour) % 24, 'mode': 'SelfUse', 'min_soc': min_soc})
|
2667
|
+
output(f"\nConfiguring schedule:",1)
|
2662
2668
|
periods = []
|
2663
2669
|
for s in strategy:
|
2664
2670
|
periods.append(set_period(segment = s, quiet=0))
|
@@ -2682,9 +2688,11 @@ charge_config = {
|
|
2682
2688
|
'charge_current': None, # max battery charge current setting in A
|
2683
2689
|
'discharge_current': None, # max battery discharge current setting in A
|
2684
2690
|
'export_limit': None, # maximum export power in kW
|
2685
|
-
'
|
2686
|
-
'pv_loss': 0.
|
2687
|
-
'
|
2691
|
+
'dc_ac_loss': 0.970, # loss converting battery DC power to AC grid power
|
2692
|
+
'pv_loss': 0.950, # loss converting PV power to DC battery charge power
|
2693
|
+
'ac_dc_loss': 0.960, # loss converting AC grid power to DC battery charge power
|
2694
|
+
'charge_loss': [0.975, 1.040], # loss in battery energy for each kWh added (based on residual_handling)
|
2695
|
+
'discharge_loss': [0.975, 0.975], # loss in battery energy for each kWh removed (based on residual_handling)
|
2688
2696
|
'inverter_power': 101, # Inverter power consumption in W
|
2689
2697
|
'bms_power': 50, # BMS power consumption in W
|
2690
2698
|
'force_charge_power': 5.00, # charge power in kW when using force charge
|
@@ -2705,11 +2713,11 @@ charge_config = {
|
|
2705
2713
|
'special_contingency': 33, # contingency for special days when consumption might be higher
|
2706
2714
|
'special_days': ['12-25', '12-26', '01-01'],
|
2707
2715
|
'full_charge': None, # day of month (1-28) to do full charge, or 'daily' or 'Mon', 'Tue' etc
|
2708
|
-
'derate_temp':
|
2716
|
+
'derate_temp': 28, # BMS temperature when cold derating starts to be applied
|
2709
2717
|
'derate_step': 5, # scale for derating factors in C
|
2710
|
-
'derating': [24, 15, 10, 2], # max charge current
|
2718
|
+
'derating': [24, 15, 10, 2], # max charge current de-rating
|
2711
2719
|
'data_wrap': 6, # data items to show per line
|
2712
|
-
'target_soc': None, #
|
2720
|
+
'target_soc': None, # the target SoC for charging (over-rides calculated value)
|
2713
2721
|
'shading': { # effect of shading on Solcast / forecast.solar
|
2714
2722
|
'solcast': {'adjust': 0.95, 'am_delay': 1.0, 'am_loss': 0.2, 'pm_delay': 1.0, 'pm_loss': 0.2},
|
2715
2723
|
'solar': {'adjust': 1.20, 'am_delay': 1.0, 'am_loss': 0.2, 'pm_delay': 1.0, 'pm_loss': 0.2}
|
@@ -2732,7 +2740,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
|
|
2732
2740
|
def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
|
2733
2741
|
forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
|
2734
2742
|
global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
|
2735
|
-
global timed_strategy, steps_per_hour, base_time, storage
|
2743
|
+
global timed_strategy, steps_per_hour, base_time, storage, residual_handling
|
2736
2744
|
print(f"\n---------------- charge_needed ----------------")
|
2737
2745
|
# validate parameters
|
2738
2746
|
args = locals()
|
@@ -2858,7 +2866,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2858
2866
|
current_soc = test_soc
|
2859
2867
|
capacity = 14.54
|
2860
2868
|
residual = test_soc * capacity / 100
|
2861
|
-
bat_volt =
|
2869
|
+
bat_volt = 317.4
|
2862
2870
|
bat_power = 0.0
|
2863
2871
|
temperature = 30
|
2864
2872
|
bat_current = 0.0
|
@@ -2909,35 +2917,35 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2909
2917
|
bms_power = charge_config['bms_power']
|
2910
2918
|
bms_loss = bms_power / 1000
|
2911
2919
|
# work out charge limit, power and losses. Max power going to the battery after ac conversion losses
|
2920
|
+
ac_dc_loss = charge_config['ac_dc_loss']
|
2912
2921
|
charge_limit = min([charge_current * (bat_ocv + charge_current * bat_resistance) / 1000, max([6, device_power])])
|
2913
2922
|
if charge_limit < 0.1:
|
2914
2923
|
output(f"** charge_current is too low ({charge_current:.1f}A)")
|
2915
|
-
charge_loss = 1.0 - charge_limit * 1000 * bat_resistance / bat_ocv ** 2
|
2916
2924
|
force_charge_power = charge_config['force_charge_power'] if timed_mode > 1 and charge_config.get('force_charge_power') is not None else 100
|
2917
|
-
|
2918
|
-
charge_power = min([(device_power - operating_loss) * grid_loss, force_charge_power * grid_loss, charge_limit])
|
2925
|
+
charge_power = min([(device_power - operating_loss) * ac_dc_loss, force_charge_power * ac_dc_loss, charge_limit])
|
2919
2926
|
float_charge = (charge_config['float_current'] if charge_config.get('float_current') is not None else 4) * bat_ocv / 1000
|
2920
|
-
charge_config['charge_loss'] = charge_loss
|
2921
2927
|
charge_config['charge_limit'] = charge_limit
|
2922
2928
|
charge_config['charge_power'] = charge_power
|
2923
2929
|
charge_config['float_charge'] = float_charge
|
2930
|
+
charge_loss = charge_config['charge_loss'][residual_handling - 1]
|
2924
2931
|
# work out discharge limit = max power coming from the battery before ac conversion losses
|
2925
|
-
|
2926
|
-
discharge_limit = device_power /
|
2932
|
+
dc_ac_loss = charge_config['dc_ac_loss']
|
2933
|
+
discharge_limit = device_power / dc_ac_loss
|
2927
2934
|
discharge_current = device_current if charge_config['discharge_current'] is None else charge_config['discharge_current']
|
2928
2935
|
discharge_power = discharge_current * bat_ocv / 1000
|
2929
2936
|
discharge_limit = discharge_power if discharge_power < discharge_limit else discharge_limit
|
2937
|
+
discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
|
2930
2938
|
# charging happens if generation exceeds export limit in feedin work mode
|
2931
2939
|
export_power = device_power if charge_config['export_limit'] is None else charge_config['export_limit']
|
2932
|
-
export_limit = export_power /
|
2940
|
+
export_limit = export_power / dc_ac_loss
|
2933
2941
|
current_mode = get_work_mode()
|
2934
2942
|
output(f"\ncharge_config = {json.dumps(charge_config, indent=2)}", 3)
|
2935
2943
|
output(f"\nDevice Info:")
|
2936
2944
|
output(f" Model: {model}")
|
2937
2945
|
output(f" Rating: {device_power:.2f}kW")
|
2938
2946
|
output(f" Export: {export_power:.2f}kW")
|
2939
|
-
output(f" Charge: {charge_current:.1f}A, {charge_power:.2f}kW, {
|
2940
|
-
output(f" Discharge: {discharge_current:.1f}A, {discharge_limit:.2f}kW, {
|
2947
|
+
output(f" Charge: {charge_current:.1f}A, {charge_power:.2f}kW, {ac_dc_loss * 100:.1f}% efficient")
|
2948
|
+
output(f" Discharge: {discharge_current:.1f}A, {discharge_limit:.2f}kW, {dc_ac_loss * 100:.1f}% efficient")
|
2941
2949
|
output(f" Inverter: {inverter_power:.0f}W power consumption")
|
2942
2950
|
output(f" BMS: {bms_power:.0f}W power consumption")
|
2943
2951
|
if current_mode is not None:
|
@@ -3041,7 +3049,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3041
3049
|
update_settings = 0
|
3042
3050
|
# produce time lines for charge, discharge and work mode
|
3043
3051
|
charge_timed = [min([charge_limit, x * charge_config['pv_loss']]) for x in generation_timed]
|
3044
|
-
discharge_timed = [min([discharge_limit, x /
|
3052
|
+
discharge_timed = [min([discharge_limit, x / dc_ac_loss]) + bms_loss for x in consumption_timed]
|
3045
3053
|
work_mode_timed = strategy_timed(timed_mode, base_hour, run_time, min_soc=min_soc, max_soc=max_soc, current_mode=current_mode)
|
3046
3054
|
for i in range(0, len(work_mode_timed)):
|
3047
3055
|
# get work mode
|
@@ -3052,9 +3060,9 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3052
3060
|
discharge_timed[i] = discharge_timed[i] * (1.0 - duration)
|
3053
3061
|
work_mode_timed[i]['charge'] = charge_power * duration
|
3054
3062
|
elif timed_mode > 0 and work_mode == 'ForceDischarge':
|
3055
|
-
fdpwr = work_mode_timed[i]['fdpwr'] /
|
3056
|
-
fdpwr = min([discharge_limit, export_limit + discharge_timed[i]
|
3057
|
-
discharge_timed[i] = fdpwr * duration
|
3063
|
+
fdpwr = work_mode_timed[i]['fdpwr'] / dc_ac_loss / 1000
|
3064
|
+
fdpwr = min([discharge_limit, export_limit + discharge_timed[i], fdpwr])
|
3065
|
+
discharge_timed[i] = fdpwr * duration + discharge_timed[i] * (1.0 - duration) - charge_timed[i] * duration
|
3058
3066
|
elif bat_hold > 0 and i >= int(time_to_start) and i < int(time_to_end):
|
3059
3067
|
discharge_timed[i] = bms_loss
|
3060
3068
|
if timed_mode > 1:
|
@@ -3099,13 +3107,13 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3099
3107
|
end_timed = time_to_end
|
3100
3108
|
end_soc = int(end_residual / capacity * 100 + 0.5)
|
3101
3109
|
else:
|
3102
|
-
if test_charge is None:
|
3103
|
-
output(f"\nCharge needed: {kwh_needed:.2f}kWh")
|
3104
|
-
charge_message = "with charge added"
|
3105
|
-
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
3106
3110
|
# work out time to add kwh_needed to battery
|
3107
3111
|
charge_rate = charge_power * charge_loss
|
3108
3112
|
hours = kwh_needed / charge_rate
|
3113
|
+
if test_charge is None:
|
3114
|
+
output(f"\nCharge needed: {kwh_needed:.2f}kWh ({hours_time(hours)})")
|
3115
|
+
charge_message = "with charge added"
|
3116
|
+
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
3109
3117
|
# check if charge time exceeded or charge needed exceeds capacity
|
3110
3118
|
hours_to_full = (capacity - start_residual) / charge_rate
|
3111
3119
|
if hours > charge_time:
|
@@ -3146,7 +3154,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3146
3154
|
if i >= start_timed and i < end_timed:
|
3147
3155
|
work_mode_timed[i]['mode'] = 'ForceCharge'
|
3148
3156
|
work_mode_timed[i]['charge'] = charge_power * t
|
3149
|
-
work_mode_timed[i]['max_soc'] = target_soc if target_soc is not None else
|
3157
|
+
work_mode_timed[i]['max_soc'] = target_soc if target_soc is not None else max_soc
|
3150
3158
|
work_mode_timed[i]['discharge'] *= (1-t)
|
3151
3159
|
# rebuild the battery residual with the charge added and min_soc
|
3152
3160
|
(bat_timed, x) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next)
|
@@ -3218,7 +3226,6 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3218
3226
|
data['capacity'] = capacity
|
3219
3227
|
data['config'] = charge_config
|
3220
3228
|
data['time'] = time_line
|
3221
|
-
data['bat'] = bat_timed
|
3222
3229
|
data['work_mode'] = work_mode_timed
|
3223
3230
|
data['generation'] = generation_timed
|
3224
3231
|
data['consumption'] = consumption_timed
|
@@ -3253,10 +3260,10 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3253
3260
|
steps_per_hour = data.get('steps')
|
3254
3261
|
capacity = data.get('capacity')
|
3255
3262
|
time_line = data.get('time')
|
3256
|
-
bat_timed = data.get('bat')
|
3257
3263
|
generation_timed = data.get('generation')
|
3258
3264
|
consumption_timed = data.get('consumption')
|
3259
3265
|
work_mode_timed = data.get('work_mode')
|
3266
|
+
bat_timed = data['bat'] if data.get('bat') is not None else [work_mode_timed[t]['kwh'] for t in range(0, len(work_mode_timed))]
|
3260
3267
|
run_time = len(time_line)
|
3261
3268
|
base_hour = int(time_hours(base_time[11:16]))
|
3262
3269
|
start_day = base_time[:10]
|
@@ -3298,7 +3305,7 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3298
3305
|
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3299
3306
|
h = base_hour
|
3300
3307
|
t = 0
|
3301
|
-
while t < len(time_line) and bat_timed[t] is not None:
|
3308
|
+
while t < len(time_line) and bat_timed[t] is not None and plots['SoC'][t] is not None:
|
3302
3309
|
col = h % data_wrap
|
3303
3310
|
s += f"\n {hours_time(time_line[t])}" if t == 0 or col == 0 else ""
|
3304
3311
|
s += f" {plots['SoC'][t]:5.2f}" if show_data == 2 else f" {plots['SoC'][t] / capacity * 100:3.0f}%"
|
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: 02 October 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.9"
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -541,7 +541,9 @@ battery = None
|
|
541
541
|
battery_settings = None
|
542
542
|
battery_vars = ['SoC', 'invBatVolt', 'invBatCurrent', 'invBatPower', 'batTemperature', 'ResidualEnergy' ]
|
543
543
|
battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual']
|
544
|
-
|
544
|
+
|
545
|
+
# 1 = returns Residual Energy. 2 = resturns Residual Capacity
|
546
|
+
residual_handling = 1
|
545
547
|
|
546
548
|
def get_battery(v = None, info=0):
|
547
549
|
global device_sn, battery, debug_setting, residual_handling
|
@@ -2455,23 +2457,23 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
|
|
2455
2457
|
return work_mode_timed
|
2456
2458
|
|
2457
2459
|
# build the timed battery residual from the charge / discharge, work mode and min_soc
|
2460
|
+
# note: all power values are as measured at the inverter battery connection
|
2458
2461
|
def battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=None, reserve_drain=None):
|
2459
|
-
global charge_config, steps_per_hour
|
2460
|
-
bat_timed = []
|
2462
|
+
global charge_config, steps_per_hour, residual_handling
|
2461
2463
|
allowed_drain = charge_config['allowed_drain'] if charge_config.get('allowed_drain') is not None else 4
|
2462
2464
|
bms_loss = (charge_config['bms_power'] / 1000 if charge_config.get('bms_power') is not None else 0.05)
|
2463
|
-
charge_loss = charge_config['charge_loss']
|
2465
|
+
charge_loss = charge_config['charge_loss'][residual_handling - 1]
|
2466
|
+
discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
|
2464
2467
|
charge_limit = charge_config['charge_limit']
|
2465
2468
|
float_charge = charge_config['float_charge']
|
2466
2469
|
for i in range(0, len(work_mode_timed)):
|
2467
|
-
bat_timed.append(kwh_current)
|
2468
2470
|
w = work_mode_timed[i]
|
2469
2471
|
w['kwh'] = kwh_current
|
2470
2472
|
max_now = w['max_soc'] * capacity / 100
|
2471
2473
|
if kwh_current < max_now and w['charge'] > 0.0:
|
2472
2474
|
kwh_current += min([w['charge'], charge_limit - w['pv']]) * charge_loss / steps_per_hour
|
2473
2475
|
kwh_current = max_now if kwh_current > max_now else kwh_current
|
2474
|
-
kwh_current += (w['pv'] - w['discharge']
|
2476
|
+
kwh_current += (w['pv'] * charge_loss - w['discharge'] / discharge_loss) / steps_per_hour
|
2475
2477
|
if kwh_current > capacity:
|
2476
2478
|
# battery is full
|
2477
2479
|
kwh_current = capacity
|
@@ -2492,16 +2494,16 @@ def battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=
|
|
2492
2494
|
reserve_drain = reserve_now
|
2493
2495
|
if kwh_min is not None and kwh_current < kwh_min and i >= time_to_next: # track minimum without charge
|
2494
2496
|
kwh_min = kwh_current
|
2495
|
-
return (
|
2497
|
+
return ([work_mode_timed[i]['kwh'] for i in range(0, len(work_mode_timed))], kwh_min)
|
2496
2498
|
|
2499
|
+
# use work_mode_timed to generate time periods for the inverter schedule
|
2497
2500
|
def charge_periods(work_mode_timed, base_hour, min_soc, capacity):
|
2498
2501
|
global steps_per_hour
|
2499
|
-
output(f"\nConfiguring schedule:",1)
|
2500
2502
|
strategy = []
|
2501
2503
|
start = base_hour
|
2502
|
-
|
2504
|
+
times = []
|
2503
2505
|
for t in range(0, min([24 * steps_per_hour, len(work_mode_timed)])):
|
2504
|
-
period =
|
2506
|
+
period = times[0] if len(times) > 0 else work_mode_timed[0]
|
2505
2507
|
next_period = work_mode_timed[t]
|
2506
2508
|
h = base_hour + t / steps_per_hour
|
2507
2509
|
if h == 24 or period['mode'] != next_period['mode'] or period['hold'] != next_period['hold']:
|
@@ -2513,15 +2515,19 @@ def charge_periods(work_mode_timed, base_hour, min_soc, capacity):
|
|
2513
2515
|
s['max_soc'] = period.get('max_soc')
|
2514
2516
|
elif period['mode'] == 'SelfUse' and period['hold'] == 1:
|
2515
2517
|
s['min_soc'] = min([int(period['kwh'] / capacity * 100 + 0.5), 100])
|
2516
|
-
|
2518
|
+
s['end'] = (start + 1 / steps_per_hour) % 24
|
2519
|
+
for p in times:
|
2517
2520
|
p['min_soc'] = s['min_soc']
|
2518
2521
|
if s['mode'] != 'SelfUse' or s['min_soc'] != min_soc:
|
2519
2522
|
strategy.append(s)
|
2520
2523
|
start = h
|
2521
|
-
|
2522
|
-
|
2523
|
-
if len(strategy)
|
2524
|
+
times = []
|
2525
|
+
times.append(work_mode_timed[t])
|
2526
|
+
if len(strategy) == 0:
|
2527
|
+
return []
|
2528
|
+
if strategy[-1]['min_soc'] != min_soc:
|
2524
2529
|
strategy.append({'start': start %24, 'end': (start + 1 / steps_per_hour) % 24, 'mode': 'SelfUse', 'min_soc': min_soc})
|
2530
|
+
output(f"\nConfiguring schedule:",1)
|
2525
2531
|
periods = []
|
2526
2532
|
for s in strategy:
|
2527
2533
|
periods.append(set_period(segment = s, quiet=0))
|
@@ -2545,9 +2551,11 @@ charge_config = {
|
|
2545
2551
|
'charge_current': None, # max battery charge current setting in A
|
2546
2552
|
'discharge_current': None, # max battery discharge current setting in A
|
2547
2553
|
'export_limit': None, # maximum export power in kW
|
2548
|
-
'
|
2549
|
-
'pv_loss': 0.95, # loss converting PV power to battery charge power
|
2550
|
-
'
|
2554
|
+
'dc_ac_loss': 0.97, # loss converting battery DC power to AC grid power
|
2555
|
+
'pv_loss': 0.95, # loss converting PV power to DC battery charge power
|
2556
|
+
'ac_dc_loss': 0.962, # loss converting AC grid power to DC battery charge power
|
2557
|
+
'charge_loss': [0.975, 1.040], # loss in battery energy for each kWh added based on residual_handling
|
2558
|
+
'discharge_loss': [0.975, 0.975], # loss in battery energy for each kWh removed based on residual_handling
|
2551
2559
|
'inverter_power': 101, # Inverter power consumption in W
|
2552
2560
|
'bms_power': 50, # BMS power consumption in W
|
2553
2561
|
'force_charge_power': 5.00, # charge power in kW when using force charge
|
@@ -2568,11 +2576,11 @@ charge_config = {
|
|
2568
2576
|
'special_contingency': 33, # contingency for special days when consumption might be higher
|
2569
2577
|
'special_days': ['12-25', '12-26', '01-01'],
|
2570
2578
|
'full_charge': None, # day of month (1-28) to do full charge, or 'daily' or 'Mon', 'Tue' etc
|
2571
|
-
'derate_temp':
|
2579
|
+
'derate_temp': 25, # BMS temperature when cold derating starts to be applied
|
2572
2580
|
'derate_step': 5, # scale for derating factors in C
|
2573
|
-
'derating': [24, 15, 10, 2], # max charge current e.g. 5C step =
|
2581
|
+
'derating': [24, 15, 10, 2], # max charge current e.g. 5C step = 25C, 20C, 15C, 10C
|
2574
2582
|
'data_wrap': 6, # data items to show per line
|
2575
|
-
'target_soc': None, #
|
2583
|
+
'target_soc': None, # the target SoC for charging (over-rides calculated value)
|
2576
2584
|
'shading': { # effect of shading on Solcast / forecast.solar
|
2577
2585
|
'solcast': {'adjust': 0.95, 'am_delay': 1.0, 'am_loss': 0.2, 'pm_delay': 1.0, 'pm_loss': 0.2},
|
2578
2586
|
'solar': {'adjust': 1.20, 'am_delay': 1.0, 'am_loss': 0.2, 'pm_delay': 1.0, 'pm_loss': 0.2}
|
@@ -2595,7 +2603,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
|
|
2595
2603
|
def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
|
2596
2604
|
forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
|
2597
2605
|
global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
|
2598
|
-
global timed_strategy, steps_per_hour, base_time, storage
|
2606
|
+
global timed_strategy, steps_per_hour, base_time, storage, residual_handling
|
2599
2607
|
print(f"\n---------------- charge_needed ----------------")
|
2600
2608
|
# validate parameters
|
2601
2609
|
args = locals()
|
@@ -2721,7 +2729,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2721
2729
|
current_soc = test_soc
|
2722
2730
|
capacity = 14.54
|
2723
2731
|
residual = test_soc * capacity / 100
|
2724
|
-
bat_volt =
|
2732
|
+
bat_volt = 317.4
|
2725
2733
|
bat_power = 0.0
|
2726
2734
|
temperature = 30
|
2727
2735
|
bat_current = 0.0
|
@@ -2772,35 +2780,35 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2772
2780
|
bms_power = charge_config['bms_power']
|
2773
2781
|
bms_loss = bms_power / 1000
|
2774
2782
|
# work out charge limit, power and losses. Max power going to the battery after ac conversion losses
|
2783
|
+
ac_dc_loss = charge_config['ac_dc_loss']
|
2775
2784
|
charge_limit = min([charge_current * (bat_ocv + charge_current * bat_resistance) / 1000, max([6, device_power])])
|
2776
2785
|
if charge_limit < 0.1:
|
2777
2786
|
output(f"** charge_current is too low ({charge_current:.1f}A)")
|
2778
|
-
charge_loss = 1.0 - charge_limit * 1000 * bat_resistance / bat_ocv ** 2
|
2779
2787
|
force_charge_power = charge_config['force_charge_power'] if timed_mode > 1 and charge_config.get('force_charge_power') is not None else 100
|
2780
|
-
|
2781
|
-
charge_power = min([(device_power - operating_loss) * grid_loss, force_charge_power * grid_loss, charge_limit])
|
2788
|
+
charge_power = min([(device_power - operating_loss) * ac_dc_loss, force_charge_power * ac_dc_loss, charge_limit])
|
2782
2789
|
float_charge = (charge_config['float_current'] if charge_config.get('float_current') is not None else 4) * bat_ocv / 1000
|
2783
|
-
charge_config['charge_loss'] = charge_loss
|
2784
2790
|
charge_config['charge_limit'] = charge_limit
|
2785
2791
|
charge_config['charge_power'] = charge_power
|
2786
2792
|
charge_config['float_charge'] = float_charge
|
2793
|
+
charge_loss = charge_config['charge_loss'][residual_handling - 1]
|
2787
2794
|
# work out discharge limit = max power coming from the battery before ac conversion losses
|
2788
|
-
|
2789
|
-
discharge_limit = device_power /
|
2795
|
+
dc_ac_loss = charge_config['dc_ac_loss']
|
2796
|
+
discharge_limit = device_power / dc_ac_loss
|
2790
2797
|
discharge_current = device_current if charge_config['discharge_current'] is None else charge_config['discharge_current']
|
2791
2798
|
discharge_power = discharge_current * bat_ocv / 1000
|
2792
2799
|
discharge_limit = discharge_power if discharge_power < discharge_limit else discharge_limit
|
2800
|
+
discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
|
2793
2801
|
# charging happens if generation exceeds export limit in feedin work mode
|
2794
2802
|
export_power = device_power if charge_config['export_limit'] is None else charge_config['export_limit']
|
2795
|
-
export_limit = export_power /
|
2803
|
+
export_limit = export_power / dc_ac_loss
|
2796
2804
|
current_mode = get_work_mode()
|
2797
2805
|
output(f"\ncharge_config = {json.dumps(charge_config, indent=2)}", 3)
|
2798
2806
|
output(f"\nDevice Info:")
|
2799
2807
|
output(f" Model: {model}")
|
2800
2808
|
output(f" Rating: {device_power:.2f}kW")
|
2801
2809
|
output(f" Export: {export_power:.2f}kW")
|
2802
|
-
output(f" Charge: {charge_current:.1f}A, {charge_power:.2f}kW, {
|
2803
|
-
output(f" Discharge: {discharge_current:.1f}A, {discharge_limit:.2f}kW, {
|
2810
|
+
output(f" Charge: {charge_current:.1f}A, {charge_power:.2f}kW, {ac_dc_loss * 100:.1f}% efficient")
|
2811
|
+
output(f" Discharge: {discharge_current:.1f}A, {discharge_limit:.2f}kW, {dc_ac_loss * 100:.1f}% efficient")
|
2804
2812
|
output(f" Inverter: {inverter_power:.0f}W power consumption")
|
2805
2813
|
output(f" BMS: {bms_power:.0f}W power consumption")
|
2806
2814
|
if current_mode is not None:
|
@@ -2903,7 +2911,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2903
2911
|
update_settings = 0
|
2904
2912
|
# produce time lines for charge, discharge and work mode
|
2905
2913
|
charge_timed = [min([charge_limit, x * charge_config['pv_loss']]) for x in generation_timed]
|
2906
|
-
discharge_timed = [min([discharge_limit, x /
|
2914
|
+
discharge_timed = [min([discharge_limit, x / dc_ac_loss]) + bms_loss for x in consumption_timed]
|
2907
2915
|
work_mode_timed = strategy_timed(timed_mode, base_hour, run_time, min_soc=min_soc, max_soc=max_soc, current_mode=current_mode)
|
2908
2916
|
for i in range(0, len(work_mode_timed)):
|
2909
2917
|
# get work mode
|
@@ -2914,9 +2922,9 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2914
2922
|
discharge_timed[i] = discharge_timed[i] * (1.0 - duration)
|
2915
2923
|
work_mode_timed[i]['charge'] = charge_power * duration
|
2916
2924
|
elif timed_mode > 0 and work_mode == 'ForceDischarge':
|
2917
|
-
fdpwr = work_mode_timed[i]['fdpwr'] /
|
2918
|
-
fdpwr = min([discharge_limit, export_limit + discharge_timed[i]
|
2919
|
-
discharge_timed[i] = fdpwr * duration
|
2925
|
+
fdpwr = work_mode_timed[i]['fdpwr'] / dc_ac_loss / 1000
|
2926
|
+
fdpwr = min([discharge_limit, export_limit + discharge_timed[i], fdpwr])
|
2927
|
+
discharge_timed[i] = fdpwr * duration + discharge_timed[i] * (1.0 - duration) - charge_timed[i] * duration
|
2920
2928
|
elif bat_hold > 0 and i >= int(time_to_start) and i < int(time_to_end):
|
2921
2929
|
discharge_timed[i] = bms_loss
|
2922
2930
|
if timed_mode > 1:
|
@@ -2961,13 +2969,13 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2961
2969
|
end_timed = time_to_end
|
2962
2970
|
end_soc = int(end_residual / capacity * 100 + 0.5)
|
2963
2971
|
else:
|
2964
|
-
if test_charge is None:
|
2965
|
-
output(f"\nCharge needed: {kwh_needed:.2f}kWh:")
|
2966
|
-
charge_message = "with charge added"
|
2967
|
-
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
2968
2972
|
# work out time to add kwh_needed to battery
|
2969
2973
|
charge_rate = charge_power * charge_loss
|
2970
2974
|
hours = kwh_needed / charge_rate
|
2975
|
+
if test_charge is None:
|
2976
|
+
output(f"\nCharge needed: {kwh_needed:.2f}kWh ({hours_time(hours)})")
|
2977
|
+
charge_message = "with charge added"
|
2978
|
+
output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
|
2971
2979
|
# check if charge time exceeded or charge needed exceeds capacity
|
2972
2980
|
hours_to_full = (capacity - start_residual) / charge_rate
|
2973
2981
|
if hours > charge_time:
|
@@ -3008,7 +3016,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3008
3016
|
if i >= start_timed and i < end_timed:
|
3009
3017
|
work_mode_timed[i]['mode'] = 'ForceCharge'
|
3010
3018
|
work_mode_timed[i]['charge'] = charge_power * t
|
3011
|
-
work_mode_timed[i]['max_soc'] = target_soc if target_soc is not None else
|
3019
|
+
work_mode_timed[i]['max_soc'] = target_soc if target_soc is not None else max_soc
|
3012
3020
|
work_mode_timed[i]['discharge'] *= (1-t)
|
3013
3021
|
# rebuild the battery residual with any charge added and min_soc
|
3014
3022
|
(bat_timed, x) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next)
|
@@ -3080,7 +3088,6 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3080
3088
|
data['capacity'] = capacity
|
3081
3089
|
data['config'] = charge_config
|
3082
3090
|
data['time'] = time_line
|
3083
|
-
data['bat'] = bat_timed
|
3084
3091
|
data['work_mode'] = work_mode_timed
|
3085
3092
|
data['generation'] = generation_timed
|
3086
3093
|
data['consumption'] = consumption_timed
|
@@ -3114,10 +3121,10 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3114
3121
|
steps_per_hour = data.get('steps')
|
3115
3122
|
capacity = data.get('capacity')
|
3116
3123
|
time_line = data.get('time')
|
3117
|
-
bat_timed = data.get('bat')
|
3118
3124
|
generation_timed = data.get('generation')
|
3119
3125
|
consumption_timed = data.get('consumption')
|
3120
3126
|
work_mode_timed = data.get('work_mode')
|
3127
|
+
bat_timed = data['bat'] if data.get('bat') is not None else [work_mode_timed[t]['kwh'] for t in range(0, len(work_mode_timed))]
|
3121
3128
|
run_time = len(time_line)
|
3122
3129
|
base_hour = int(time_hours(base_time[11:16]))
|
3123
3130
|
start_day = base_time[:10]
|
@@ -3159,7 +3166,7 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3159
3166
|
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3160
3167
|
h = base_hour
|
3161
3168
|
t = 0
|
3162
|
-
while t < len(time_line) and bat_timed[t] is not None:
|
3169
|
+
while t < len(time_line) and bat_timed[t] is not None and plots['SoC'][t] is not None:
|
3163
3170
|
col = h % data_wrap
|
3164
3171
|
s += f"\n {hours_time(time_line[t])}" if t == 0 or col == 0 else ""
|
3165
3172
|
s += f" {plots['SoC'][t]:5.2f}" if show_data == 2 else f" {plots['SoC'][t] / capacity * 100:3.0f}%"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foxesscloud
|
3
|
-
Version: 2.5.
|
3
|
+
Version: 2.5.9
|
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
|
@@ -363,44 +363,46 @@ Given the data available, the modelling works as follows:
|
|
363
363
|
|
364
364
|
The following parameters and default values are used to configure charge_needed and may be updated if required using name=value:
|
365
365
|
```
|
366
|
-
contingency: [20,10,5,10]
|
367
|
-
capacity: None
|
368
|
-
charge_current: None
|
369
|
-
discharge_current: None
|
370
|
-
export_limit: None
|
371
|
-
|
372
|
-
pv_loss: 0.
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
366
|
+
contingency: [20,10,5,10] # % of consumption. Single or [winter, spring, summer, autumn] values
|
367
|
+
capacity: None # Battery capacity in kWh (over-rides generated value if set)
|
368
|
+
charge_current: None # max battery charge current setting in A. None uses a value derrived from the inverter model
|
369
|
+
discharge_current: None # max battery discharge current setting in A. None uses a value derrived from the inverter model
|
370
|
+
export_limit: None # maximum export power in kW. None uses the inverter power rating
|
371
|
+
dc_ac_loss: 0.970 # loss converting battery DC power to AC grid power
|
372
|
+
pv_loss: 0.950 # loss converting PV power to DC battery charge power
|
373
|
+
ac_dc_loss: 0.960 # loss converting AC grid power to DC battery charge power
|
374
|
+
charge_loss: [0.975, 1.040] # loss in battery energy for each kWh added (based on residual_handling)
|
375
|
+
discharge_loss: [0.975, 0.975] # loss in battery energy for each kWh removed (based on residual_handling)
|
376
|
+
inverter_power: None # inverter power consumption in W (dynamically set)
|
377
|
+
bms_power: 50 # BMS power consumption in W
|
378
|
+
force_charge_power: 5.00 # power used when Force Charge is scheduled
|
379
|
+
allowed_drain: 4, # % tolerance below min_soc before float charge starts
|
380
|
+
float_current: 4, # BMS float charge current in A
|
381
|
+
bat_resistance: 0.070 # internal resistance of a battery in ohms
|
382
|
+
volt_curve: lifepo4_curve # battery OCV from 0% to 100% SoC
|
383
|
+
nominal_soc: 55 # SoC for nominal open circuit voltage
|
384
|
+
generation_days: 3 # number of days to use for average generation (1-7)
|
385
|
+
consumption_days: 3 # number of days to use for average consumption (1-7)
|
386
|
+
consumption_span: 'week' # 'week' = last 7 days or 'weekday' = last 7 weekdays e.g. Saturdays
|
387
|
+
use_today: 21.0 # hour when today's generation and consumption data will be used
|
388
|
+
min_hours: 0.25 # minimum charge time to set (in decimal hours)
|
389
|
+
min_kwh: 0.5 # minimum charge to add in kwh
|
390
|
+
solcast_adjust: 100 # % adjustment to make to Solcast forecast
|
391
|
+
solar_adjust: 100 # % adjustment to make to Solar forecast
|
392
|
+
forecast_selection: 1 # 1 = only update charge times if forecast is available, 0 = use best available data. Default is 1.
|
393
|
+
annual_consumption: None # optional annual consumption in kWh. If set, this replaces consumption history
|
394
|
+
timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy mode
|
395
|
+
special_contingency: 30 # contingency for special days when consumption might be higher
|
394
396
|
special_days: ['12-25', '12-26', '01-01']
|
395
|
-
full_charge: None
|
396
|
-
derate_temp:
|
397
|
-
derate_step: 5
|
398
|
-
derating: [24, 15, 10, 2]
|
399
|
-
force: 1
|
400
|
-
data_wrap: 6
|
401
|
-
target_soc: None
|
402
|
-
shading: {}
|
403
|
-
save: 'charge_needed.txt'
|
397
|
+
full_charge: None # day of month (1-28) to do full charge or 'daily' or day of week: 'Mon', 'Tue' etc
|
398
|
+
derate_temp: 28 # battery temperature in C when derating charge current is applied
|
399
|
+
derate_step: 5 # step size for derating e.g. 21, 16, 11
|
400
|
+
derating: [24, 15, 10, 2] # derated charge current for each temperature step e.g. 28C, 23C, 18C, 13C
|
401
|
+
force: 1 # 1 = disable strategy periods when setting charge. 0 = fail if strategy period has been set.
|
402
|
+
data_wrap: 6 # data items to show per line
|
403
|
+
target_soc: None # target soc for charging (over-rides calculated value)
|
404
|
+
shading: {} # effect of shading on Solcast / Solar (see below)
|
405
|
+
save: 'charge_needed.txt' # where to save calculation data for charge_compare(). '###' gets replaced with todays date.
|
404
406
|
```
|
405
407
|
|
406
408
|
These values are stored / available in f.charge_config.
|
@@ -784,6 +786,11 @@ This setting can be:
|
|
784
786
|
|
785
787
|
# Version Info
|
786
788
|
|
789
|
+
2.5.9<br>
|
790
|
+
Change loss parameters to separate AC/DC, DC/AC conversion losses and battery charge / discharge losses.
|
791
|
+
Update charge calibration for new BMS firmware.
|
792
|
+
Increase de-rating temperature from 21C to 28C for new BMS firmware.
|
793
|
+
|
787
794
|
2.5.8<br>
|
788
795
|
Fix incorrect charging setup when force_charge=1.
|
789
796
|
Rework charge_periods() to consolidate charge periods to reduce number of time segments when timed_mode=2.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
foxesscloud/foxesscloud.py,sha256=AkyOh5uXAj-J318K6Tgr6J8uLnm-ZkL-lQcAc5L0ehg,212563
|
2
|
+
foxesscloud/openapi.py,sha256=E_JTEiFcbuRBQn9zwAwpY9IWJdglS6xylE8PH_qWLHg,206235
|
3
|
+
foxesscloud-2.5.9.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
|
4
|
+
foxesscloud-2.5.9.dist-info/METADATA,sha256=dwM5NwG8HIyJ_ruXv0zZh6UGgATnRHcMWP3cUJDiXD8,56827
|
5
|
+
foxesscloud-2.5.9.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
+
foxesscloud-2.5.9.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
|
7
|
+
foxesscloud-2.5.9.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
foxesscloud/foxesscloud.py,sha256=9WivKa8ysTZ52aTgPmTxJYDAnAz5TobibgvpJkrK_KM,211855
|
2
|
-
foxesscloud/openapi.py,sha256=SCr8VOz1aP0Nk5vlUi3-kJ-AMNpW0STlVWQRbbABjBE,205502
|
3
|
-
foxesscloud-2.5.8.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
|
4
|
-
foxesscloud-2.5.8.dist-info/METADATA,sha256=Aon8oxksMZP4Z9NgWKOwsz7Dpo8raj8A2cIjakNL_xM,56304
|
5
|
-
foxesscloud-2.5.8.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
-
foxesscloud-2.5.8.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
|
7
|
-
foxesscloud-2.5.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|