foxesscloud 2.6.1__py3-none-any.whl → 2.6.2__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 +58 -36
- foxesscloud/openapi.py +29 -23
- {foxesscloud-2.6.1.dist-info → foxesscloud-2.6.2.dist-info}/METADATA +6 -1
- foxesscloud-2.6.2.dist-info/RECORD +7 -0
- foxesscloud-2.6.1.dist-info/RECORD +0 -7
- {foxesscloud-2.6.1.dist-info → foxesscloud-2.6.2.dist-info}/LICENCE +0 -0
- {foxesscloud-2.6.1.dist-info → foxesscloud-2.6.2.dist-info}/WHEEL +0 -0
- {foxesscloud-2.6.1.dist-info → foxesscloud-2.6.2.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: 12 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.4"
|
14
14
|
print(f"FoxESS-Cloud version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -65,7 +65,7 @@ def plot_show():
|
|
65
65
|
##################################################################################################
|
66
66
|
##################################################################################################
|
67
67
|
|
68
|
-
def
|
68
|
+
def convert_date(d):
|
69
69
|
if d is not None and len(d) < 18:
|
70
70
|
if len(d) == 10:
|
71
71
|
d += ' 00:00:00'
|
@@ -76,8 +76,12 @@ def query_date(d, offset = None):
|
|
76
76
|
try:
|
77
77
|
t = datetime.now() if d is None else datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
|
78
78
|
except Exception as e:
|
79
|
-
output(f"**
|
79
|
+
output(f"** convert_date(): {str(e)}")
|
80
80
|
return None
|
81
|
+
return t
|
82
|
+
|
83
|
+
def query_date(d, offset = None):
|
84
|
+
t = convert_date(d)
|
81
85
|
if offset is not None:
|
82
86
|
t += timedelta(days = offset)
|
83
87
|
return {'year': t.year, 'month': t.month, 'day': t.day, 'hour': t.hour, 'minute': t.minute, 'second': t.second}
|
@@ -594,8 +598,8 @@ battery_params = {
|
|
594
598
|
2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
|
595
599
|
'step': 5,
|
596
600
|
'offset': 5,
|
597
|
-
'charge_loss': 1.
|
598
|
-
'discharge_loss': 0.
|
601
|
+
'charge_loss': 1.08,
|
602
|
+
'discharge_loss': 0.85},
|
599
603
|
}
|
600
604
|
|
601
605
|
def get_battery(info=1):
|
@@ -630,9 +634,13 @@ def get_battery(info=1):
|
|
630
634
|
battery['info'] = result['batteries'][0]
|
631
635
|
if battery['info']['masterVersion'] >= '1.014':
|
632
636
|
residual_handling = 2
|
637
|
+
battery['residual_handling'] = residual_handling
|
638
|
+
battery['rated_capacity'] = None
|
639
|
+
battery['soh'] = None
|
640
|
+
battery['soh_supported'] = False
|
633
641
|
if battery.get('residual') is not None:
|
634
642
|
battery['residual'] /= 1000
|
635
|
-
if residual_handling == 2:
|
643
|
+
if battery['residual_handling'] == 2:
|
636
644
|
capacity = battery.get('residual')
|
637
645
|
soc = battery.get('soc')
|
638
646
|
residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
|
@@ -643,7 +651,7 @@ def get_battery(info=1):
|
|
643
651
|
battery['capacity'] = round(capacity, 3)
|
644
652
|
battery['residual'] = round(residual, 3)
|
645
653
|
battery['charge_rate'] = 50
|
646
|
-
params = battery_params[residual_handling]
|
654
|
+
params = battery_params[battery['residual_handling']]
|
647
655
|
battery['charge_loss'] = params['charge_loss']
|
648
656
|
battery['discharge_loss'] = params['discharge_loss']
|
649
657
|
if battery.get('temperature') is not None:
|
@@ -680,18 +688,30 @@ def get_batteries(info=1):
|
|
680
688
|
batteries[i]['info'] = result['batteries'][i]
|
681
689
|
for b in batteries:
|
682
690
|
if b.get('info') is not None and b['info']['masterVersion'] >= '1.014':
|
683
|
-
residual_handling = 2
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
b
|
692
|
-
|
693
|
-
|
694
|
-
|
691
|
+
b['residual_handling'] = 2
|
692
|
+
if b.get('soh') is not None and b['soh'].isnumeric():
|
693
|
+
b['soh_supported'] = True
|
694
|
+
else:
|
695
|
+
b['rated_capacity'] = None
|
696
|
+
b['soh'] = None
|
697
|
+
b['soh_supported'] = False
|
698
|
+
for b in batteries:
|
699
|
+
if b.get('soh_supported') is not None and b['soh_supported']:
|
700
|
+
capacity = (b['ratedCapacity'] / 1000 * int(b['soh']) / 100)
|
701
|
+
soc = b.get('soc')
|
702
|
+
residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
|
703
|
+
b['capacity'] = round(capacity, 3)
|
704
|
+
b['residual'] = round(residual, 3)
|
705
|
+
b['charge_rate'] = 50
|
706
|
+
params = battery_params[residual_handling]
|
707
|
+
b['charge_loss'] = params['charge_loss']
|
708
|
+
b['discharge_loss'] = params['discharge_loss']
|
709
|
+
if b.get('temperature') is not None:
|
710
|
+
b['charge_rate'] = params['table'][int((b['temperature'] - params['offset']) / params['step'])]
|
711
|
+
else:
|
712
|
+
get_battery(info=info)
|
713
|
+
batteries = [battery]
|
714
|
+
break
|
695
715
|
battery = batteries[0]
|
696
716
|
return batteries
|
697
717
|
|
@@ -2165,9 +2185,9 @@ def hours_difference(t1, t2):
|
|
2165
2185
|
if t1 == t2:
|
2166
2186
|
return 0.0
|
2167
2187
|
if type(t1) is str:
|
2168
|
-
t1 =
|
2188
|
+
t1 = convert_date(t1)
|
2169
2189
|
if type(t2) is str:
|
2170
|
-
t2 =
|
2190
|
+
t2 = convert_date(t2)
|
2171
2191
|
return round((t1 - t2).total_seconds() / 3600,1)
|
2172
2192
|
|
2173
2193
|
##################################################################################################
|
@@ -2344,7 +2364,7 @@ def get_agile_times(tariff=agile_octopus, d=None):
|
|
2344
2364
|
if d is not None and len(d) < 11:
|
2345
2365
|
d += " 18:00"
|
2346
2366
|
# get dates and times
|
2347
|
-
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else
|
2367
|
+
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else convert_date(d)
|
2348
2368
|
time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
|
2349
2369
|
# adjust system to get local time now
|
2350
2370
|
now = system_time + timedelta(hours=time_offset)
|
@@ -2815,7 +2835,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
|
|
2815
2835
|
def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
|
2816
2836
|
forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
|
2817
2837
|
global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
|
2818
|
-
global timed_strategy, steps_per_hour, base_time, storage, battery
|
2838
|
+
global timed_strategy, steps_per_hour, base_time, storage, battery, charge_rates
|
2819
2839
|
print(f"\n---------------- charge_needed ----------------")
|
2820
2840
|
# validate parameters
|
2821
2841
|
args = locals()
|
@@ -2843,7 +2863,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2843
2863
|
if type(forecast_times) is not list:
|
2844
2864
|
forecast_times = [forecast_times]
|
2845
2865
|
# get dates and times
|
2846
|
-
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else
|
2866
|
+
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else convert_date(test_time)
|
2847
2867
|
time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
|
2848
2868
|
now = system_time + timedelta(hours=time_offset)
|
2849
2869
|
today = datetime.strftime(now, '%Y-%m-%d')
|
@@ -2916,14 +2936,14 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2916
2936
|
output(f"full_charge = {full_charge}")
|
2917
2937
|
if test_soc is not None:
|
2918
2938
|
current_soc = test_soc
|
2919
|
-
capacity = 14.
|
2939
|
+
capacity = 14.53
|
2920
2940
|
residual = test_soc * capacity / 100
|
2921
2941
|
bat_volt = 317.4
|
2922
2942
|
bat_power = 0.0
|
2923
2943
|
temperature = 30
|
2924
2944
|
bms_charge_current = 15
|
2925
|
-
charge_loss =
|
2926
|
-
discharge_loss =
|
2945
|
+
charge_loss = charge_rates[2]['charge_loss']
|
2946
|
+
discharge_loss = charge_rates[2]['discharge_loss']
|
2927
2947
|
bat_current = 0.0
|
2928
2948
|
device_power = 6.0
|
2929
2949
|
device_current = 35
|
@@ -2971,6 +2991,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2971
2991
|
output(f" Temperature: {temperature:.1f}°C")
|
2972
2992
|
output(f" Resistance: {bat_resistance:.2f} ohms")
|
2973
2993
|
output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
|
2994
|
+
output(f" Losses: {charge_loss * 100:.1f}% charge / {discharge_loss * 100:.1f}% discharge")
|
2974
2995
|
# charge current may be derated based on temperature
|
2975
2996
|
charge_current = device_current if charge_config['charge_current'] is None else charge_config['charge_current']
|
2976
2997
|
if charge_current > bms_charge_current:
|
@@ -3309,10 +3330,10 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3309
3330
|
|
3310
3331
|
def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
|
3311
3332
|
global charge_config, storage
|
3312
|
-
now =
|
3333
|
+
now = convert_date(d)
|
3313
3334
|
yesterday = datetime.strftime(datetime.date(now - timedelta(days=1)), '%Y-%m-%d')
|
3314
3335
|
if save is None and charge_config.get('save') is not None:
|
3315
|
-
save = charge_config.get('save').replace('###', yesterday)
|
3336
|
+
save = charge_config.get('save').replace('###', yesterday if d is None else d[:10])
|
3316
3337
|
if not os.path.exists(storage + save):
|
3317
3338
|
save = None
|
3318
3339
|
if save is None:
|
@@ -3339,7 +3360,7 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
|
|
3339
3360
|
base_hour = int(time_hours(base_time[11:16]))
|
3340
3361
|
start_day = base_time[:10]
|
3341
3362
|
print(f"Run at {start_day} {hours_time(hour_now)} with SoC {current_soc:.0f}%")
|
3342
|
-
now =
|
3363
|
+
now = convert_date(base_time)
|
3343
3364
|
end_day = datetime.strftime(now + timedelta(hours=run_time / steps_per_hour), '%Y-%m-%d')
|
3344
3365
|
if v is None:
|
3345
3366
|
v = ['pvPower', 'loadsPower', 'SoC']
|
@@ -3372,13 +3393,14 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
|
|
3372
3393
|
for i in range(0, run_time):
|
3373
3394
|
plots[v][i] = plots[v][i] / count[v][i] if count[v][i] > 0 else None
|
3374
3395
|
if show_data > 0 and plots.get('SoC') is not None:
|
3375
|
-
data_wrap = charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else
|
3376
|
-
s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
|
3396
|
+
data_wrap = 1 #charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 1
|
3397
|
+
s = f"\nBattery Energy kWh (predicted / actual):" if show_data == 2 else f"\nBattery SoC (predicted / actual):"
|
3377
3398
|
h = base_hour
|
3378
3399
|
t = 0
|
3379
3400
|
while t < len(time_line) and bat_timed[t] is not None and plots['SoC'][t] is not None:
|
3380
3401
|
col = h % data_wrap
|
3381
3402
|
s += f"\n {hours_time(time_line[t])}" if t == 0 or col == 0 else ""
|
3403
|
+
s += f" {bat_timed[t]:5.2f}" if show_data == 2 else f" {bat_timed[t] / capacity * 100:3.0f}%"
|
3382
3404
|
s += f" {plots['SoC'][t]:5.2f}" if show_data == 2 else f" {plots['SoC'][t] / capacity * 100:3.0f}%"
|
3383
3405
|
h += 1
|
3384
3406
|
t += steps_per_hour
|
@@ -3534,7 +3556,7 @@ def battery_info(log=0, plot=1, count=None, info=1, bat=None):
|
|
3534
3556
|
output(f"SoH: {bat_soh}%")
|
3535
3557
|
output(f"Current SoC: {current_soc}%")
|
3536
3558
|
output(f"Capacity: {capacity:.2f}kWh")
|
3537
|
-
output(f"Residual: {residual:.2f}kWh")
|
3559
|
+
output(f"Residual: {residual:.2f}kWh" + (" (SoC x Capacity)" if bat['residual_handling'] == 2 else ""))
|
3538
3560
|
output(f"InvBatVolt: {bat_volt:.1f}V")
|
3539
3561
|
output(f"InvBatCurrent: {bat_current:.1f}A")
|
3540
3562
|
output(f"State: {'Charging' if bat_power < 0 else 'Discharging'} ({abs(bat_power):.3f}kW)")
|
@@ -3944,7 +3966,7 @@ class Solcast :
|
|
3944
3966
|
# The forecasts and estimated also both include the current time, so the data has to be de-duplicated to get an accurate total for a day
|
3945
3967
|
global debug_setting, solcast_url, solcast_api_key, solcast_save, storage
|
3946
3968
|
self.data = {}
|
3947
|
-
now =
|
3969
|
+
now = convert_date(d)
|
3948
3970
|
self.shading = None if shading is None else shading if shading.get('solcast') is None else shading['solcast']
|
3949
3971
|
self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
|
3950
3972
|
self.quarter = int(self.today[5:7]) // 3 % 4
|
@@ -4288,7 +4310,7 @@ class Solar :
|
|
4288
4310
|
def __init__(self, reload=0, quiet=False, shading=None, d=None):
|
4289
4311
|
global solar_arrays, solar_save, solar_total, solar_url, solar_api_key, storage
|
4290
4312
|
self.shading = None if shading is None else shading if shading.get('solar') is None else shading['solar']
|
4291
|
-
now =
|
4313
|
+
now = convert_date(d)
|
4292
4314
|
self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
|
4293
4315
|
self.quarter = int(self.today[5:7]) // 3 % 4
|
4294
4316
|
self.tomorrow = datetime.strftime(datetime.date(now + timedelta(days=1)), '%Y-%m-%d')
|
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: 12 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.6.
|
13
|
+
version = "2.6.2"
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
15
15
|
|
16
16
|
debug_setting = 1
|
@@ -66,8 +66,7 @@ def plot_show():
|
|
66
66
|
##################################################################################################
|
67
67
|
##################################################################################################
|
68
68
|
|
69
|
-
|
70
|
-
def query_date(d, offset = None):
|
69
|
+
def convert_date(d):
|
71
70
|
if d is not None and len(d) < 18:
|
72
71
|
if len(d) == 10:
|
73
72
|
d += ' 00:00:00'
|
@@ -78,8 +77,13 @@ def query_date(d, offset = None):
|
|
78
77
|
try:
|
79
78
|
t = datetime.now() if d is None else datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
|
80
79
|
except Exception as e:
|
81
|
-
output(f"**
|
80
|
+
output(f"** convert_date(): {str(e)}")
|
82
81
|
return None
|
82
|
+
return t
|
83
|
+
|
84
|
+
# return query date as a dictionary with year, month, day, hour, minute, second
|
85
|
+
def query_date(d, offset = None):
|
86
|
+
t = convert_date(d)
|
83
87
|
if offset is not None:
|
84
88
|
t += timedelta(days = offset)
|
85
89
|
return {'year': t.year, 'month': t.month, 'day': t.day, 'hour': t.hour, 'minute': t.minute, 'second': t.second}
|
@@ -94,7 +98,7 @@ def query_time(d, time_span):
|
|
94
98
|
else:
|
95
99
|
d += ':00'
|
96
100
|
try:
|
97
|
-
t = datetime.now().replace(minute=0, second=0, microsecond=0) if d is None else
|
101
|
+
t = datetime.now().replace(minute=0, second=0, microsecond=0) if d is None else convert_date(d)
|
98
102
|
except Exception as e:
|
99
103
|
output(f"** query_time(): {str(e)}")
|
100
104
|
return (None, None)
|
@@ -560,7 +564,7 @@ battery_params = {
|
|
560
564
|
'step': 5,
|
561
565
|
'offset': 5,
|
562
566
|
'charge_loss': 1.080,
|
563
|
-
'discharge_loss': 0.
|
567
|
+
'discharge_loss': 0.85},
|
564
568
|
}
|
565
569
|
|
566
570
|
def get_battery(info=0, v=None):
|
@@ -575,7 +579,11 @@ def get_battery(info=0, v=None):
|
|
575
579
|
battery = {}
|
576
580
|
for i in range(0, len(battery_vars)):
|
577
581
|
battery[battery_data[i]] = result[i].get('value')
|
578
|
-
|
582
|
+
battery['residual_handling'] = residual_handling
|
583
|
+
battery['rated_capacity'] = None
|
584
|
+
battery['soh'] = None
|
585
|
+
battery['soh_supported'] = False
|
586
|
+
if battery['residual_handling'] == 2:
|
579
587
|
capacity = battery.get('residual')
|
580
588
|
soc = battery.get('soc')
|
581
589
|
residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
|
@@ -587,7 +595,7 @@ def get_battery(info=0, v=None):
|
|
587
595
|
battery['residual'] = round(residual, 3)
|
588
596
|
battery['status'] = 1
|
589
597
|
battery['charge_rate'] = 50
|
590
|
-
params = battery_params[residual_handling]
|
598
|
+
params = battery_params[battery['residual_handling']]
|
591
599
|
battery['charge_loss'] = params['charge_loss']
|
592
600
|
battery['discharge_loss'] = params['discharge_loss']
|
593
601
|
if battery.get('temperature') is not None:
|
@@ -597,8 +605,6 @@ def get_battery(info=0, v=None):
|
|
597
605
|
def get_batteries(info=0):
|
598
606
|
global battery, batteries
|
599
607
|
get_battery(info=info)
|
600
|
-
battery['ratedCapacity'] = None
|
601
|
-
battery['soh'] = None
|
602
608
|
batteries = [battery]
|
603
609
|
return batteries
|
604
610
|
|
@@ -1984,9 +1990,9 @@ def hours_difference(t1, t2):
|
|
1984
1990
|
if t1 == t2:
|
1985
1991
|
return 0.0
|
1986
1992
|
if type(t1) is str:
|
1987
|
-
t1 =
|
1993
|
+
t1 = convert_date(t1)
|
1988
1994
|
if type(t2) is str:
|
1989
|
-
t2 =
|
1995
|
+
t2 = convert_date(t2)
|
1990
1996
|
return round((t1 - t2).total_seconds() / 3600,1)
|
1991
1997
|
|
1992
1998
|
##################################################################################################
|
@@ -2163,7 +2169,7 @@ def get_agile_times(tariff=agile_octopus, d=None):
|
|
2163
2169
|
if d is not None and len(d) < 11:
|
2164
2170
|
d += " 18:00"
|
2165
2171
|
# get dates and times
|
2166
|
-
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else
|
2172
|
+
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else convert_date(d)
|
2167
2173
|
time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
|
2168
2174
|
# adjust system to get local time now
|
2169
2175
|
now = system_time + timedelta(hours=time_offset)
|
@@ -2634,7 +2640,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
|
|
2634
2640
|
def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
|
2635
2641
|
forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
|
2636
2642
|
global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
|
2637
|
-
global timed_strategy, steps_per_hour, base_time, storage, battery
|
2643
|
+
global timed_strategy, steps_per_hour, base_time, storage, battery, charge_rates
|
2638
2644
|
print(f"\n---------------- charge_needed ----------------")
|
2639
2645
|
# validate parameters
|
2640
2646
|
args = locals()
|
@@ -2662,7 +2668,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2662
2668
|
if type(forecast_times) is not list:
|
2663
2669
|
forecast_times = [forecast_times]
|
2664
2670
|
# get dates and times
|
2665
|
-
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else
|
2671
|
+
system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else convert_date(test_time)
|
2666
2672
|
time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
|
2667
2673
|
now = system_time + timedelta(hours=time_offset)
|
2668
2674
|
today = datetime.strftime(now, '%Y-%m-%d')
|
@@ -2741,8 +2747,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
2741
2747
|
bat_power = 0.0
|
2742
2748
|
temperature = 30
|
2743
2749
|
bms_charge_current = 15
|
2744
|
-
charge_loss =
|
2745
|
-
discharge_loss =
|
2750
|
+
charge_loss = charge_rates[2]['charge_loss']
|
2751
|
+
discharge_loss = charge_rates[2]['discharge_loss']
|
2746
2752
|
bat_current = 0.0
|
2747
2753
|
device_power = 6.0
|
2748
2754
|
device_current = 35
|
@@ -3126,10 +3132,10 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
|
|
3126
3132
|
|
3127
3133
|
def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
3128
3134
|
global charge_config, storage
|
3129
|
-
now =
|
3135
|
+
now = convert_date(d)
|
3130
3136
|
yesterday = datetime.strftime(datetime.date(now - timedelta(days=1)), '%Y-%m-%d')
|
3131
3137
|
if save is None and charge_config.get('save') is not None:
|
3132
|
-
save = charge_config.get('save').replace('###', yesterday)
|
3138
|
+
save = charge_config.get('save').replace('###', yesterday if d is None else d[:10])
|
3133
3139
|
if not os.path.exists(storage + save):
|
3134
3140
|
save = None
|
3135
3141
|
if save is None:
|
@@ -3156,7 +3162,7 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
|
|
3156
3162
|
base_hour = int(time_hours(base_time[11:16]))
|
3157
3163
|
start_day = base_time[:10]
|
3158
3164
|
print(f"Run at {start_day} {hours_time(hour_now)} with SoC {current_soc:.0f}%")
|
3159
|
-
now =
|
3165
|
+
now = convert_date(base_time)
|
3160
3166
|
end_day = datetime.strftime(now + timedelta(hours=run_time / steps_per_hour), '%Y-%m-%d')
|
3161
3167
|
if v is None:
|
3162
3168
|
v = ['pvPower', 'loadsPower', 'SoC']
|
@@ -3760,7 +3766,7 @@ class Solcast :
|
|
3760
3766
|
# The forecasts and estimated also both include the current time, so the data has to be de-duplicated to get an accurate total for a day
|
3761
3767
|
global debug_setting, solcast_url, solcast_api_key, solcast_save, storage
|
3762
3768
|
self.data = {}
|
3763
|
-
now =
|
3769
|
+
now = convert_date(d)
|
3764
3770
|
self.shading = None if shading is None else shading if shading.get('solcast') is None else shading['solcast']
|
3765
3771
|
self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
|
3766
3772
|
self.quarter = int(self.today[5:7]) // 3 % 4
|
@@ -4104,7 +4110,7 @@ class Solar :
|
|
4104
4110
|
def __init__(self, reload=0, quiet=False, shading=None, d=None):
|
4105
4111
|
global solar_arrays, solar_save, solar_total, solar_url, solar_api_key, storage
|
4106
4112
|
self.shading = None if shading is None else shading if shading.get('solar') is None else shading['solar']
|
4107
|
-
now =
|
4113
|
+
now = convert_date(d)
|
4108
4114
|
self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
|
4109
4115
|
self.quarter = int(self.today[5:7]) // 3 % 4
|
4110
4116
|
self.tomorrow = datetime.strftime(datetime.date(now + timedelta(days=1)), '%Y-%m-%d')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: foxesscloud
|
3
|
-
Version: 2.6.
|
3
|
+
Version: 2.6.2
|
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
|
@@ -787,6 +787,11 @@ This setting can be:
|
|
787
787
|
|
788
788
|
# Version Info
|
789
789
|
|
790
|
+
2.6.2<br>
|
791
|
+
Update battery calibration for charge_needed() when residual_handling is 2.
|
792
|
+
Update get_battery() and get_batteries() to include states for ratedCapacity, soh, residual_handling and soh_supported.
|
793
|
+
Update charge_compare(), Solcast() and Solar() so date (d) parameter is more flexible.
|
794
|
+
|
790
795
|
2.6.1<br>
|
791
796
|
Fix problem where battery discharges below min_soc while waiting for charging to start.
|
792
797
|
Update calibration for Force Charge with BMS 1.014 and later.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
foxesscloud/foxesscloud.py,sha256=j1-EcJ3vudFCalHBbyBXUTl99s4-foWqq1831YLdHXg,216792
|
2
|
+
foxesscloud/openapi.py,sha256=USny4eYMLxG2rg3FsijGp7w_cOT8eMMvy0pVN0DbVSU,207227
|
3
|
+
foxesscloud-2.6.2.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
|
4
|
+
foxesscloud-2.6.2.dist-info/METADATA,sha256=Y96zL2xTMWPk1yjNnO_QQgulzpPy82Q6IXiEVYHyxxE,57751
|
5
|
+
foxesscloud-2.6.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
+
foxesscloud-2.6.2.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
|
7
|
+
foxesscloud-2.6.2.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
foxesscloud/foxesscloud.py,sha256=DHP0NqeFXj6rFIdwyTp7U935YBhJcFG2YMwaHGRBaF4,215964
|
2
|
-
foxesscloud/openapi.py,sha256=Rp1HdtBLpMt1iK5psUkfnCSzw7vmmABs_SkOZ4G2V0M,207271
|
3
|
-
foxesscloud-2.6.1.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
|
4
|
-
foxesscloud-2.6.1.dist-info/METADATA,sha256=aYpWZtJnpnzmEjkB5ip7fCW4Qx9Ra5WOL-5uFCYHemQ,57452
|
5
|
-
foxesscloud-2.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
6
|
-
foxesscloud-2.6.1.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
|
7
|
-
foxesscloud-2.6.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|