foxesscloud 2.9.5__tar.gz → 2.9.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.9.5/src/foxesscloud.egg-info → foxesscloud-2.9.7}/PKG-INFO +14 -3
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/README.md +13 -2
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/pyproject.toml +1 -1
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/src/foxesscloud/foxesscloud.py +6 -3
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/src/foxesscloud/openapi.py +22 -17
- {foxesscloud-2.9.5 → foxesscloud-2.9.7/src/foxesscloud.egg-info}/PKG-INFO +14 -3
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/LICENCE +0 -0
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/setup.cfg +0 -0
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/src/foxesscloud.egg-info/SOURCES.txt +0 -0
- {foxesscloud-2.9.5 → foxesscloud-2.9.7}/src/foxesscloud.egg-info/dependency_links.txt +0 -0
- {foxesscloud-2.9.5 → foxesscloud-2.9.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.9.
|
|
3
|
+
Version: 2.9.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
|
|
@@ -182,8 +182,9 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
182
182
|
+ max_soc: optional, default is 100
|
|
183
183
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
184
184
|
+ fdpwr: optional, default is 0. Used when setting a period with ForceDischarge mode
|
|
185
|
-
+ import_limit: optional, default is None (not set)
|
|
186
|
-
+ export_limit: optional, default is None (not set).
|
|
185
|
+
+ import_limit: optional, default is None (not set) but set to 0 if the mode is force discharge
|
|
186
|
+
+ export_limit: optional, default is None (not set).
|
|
187
|
+
+ pv_limit: optional, default is 1.5 x inverter rating.
|
|
187
188
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
188
189
|
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
189
190
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
@@ -832,6 +833,16 @@ This setting can be:
|
|
|
832
833
|
|
|
833
834
|
# Version Info
|
|
834
835
|
|
|
836
|
+
2.9.7 - 2026/03/18<br>
|
|
837
|
+
Add pv_limit to set_period().
|
|
838
|
+
Default import_linit to 0 for force discharge in set_period()
|
|
839
|
+
|
|
840
|
+
2.9.6 - 2026/03/10<br>
|
|
841
|
+
Fix problem setting fdsoc and fdpwr from segment for set_period().
|
|
842
|
+
Correctly identify H1-G2 model inverter.
|
|
843
|
+
Add maxChargeCurrent and maxDischargeCurrent to f.battery.
|
|
844
|
+
Update battery charge derating with temperature so charge current is not 0.
|
|
845
|
+
|
|
835
846
|
2.9.5 - 2026/02/27<br>
|
|
836
847
|
Add step parameter to date_list().
|
|
837
848
|
Get maxGroupCount from v3 scheduler and update f.max_periods with maximum number of time periods supported.
|
|
@@ -168,8 +168,9 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
168
168
|
+ max_soc: optional, default is 100
|
|
169
169
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
170
170
|
+ fdpwr: optional, default is 0. Used when setting a period with ForceDischarge mode
|
|
171
|
-
+ import_limit: optional, default is None (not set)
|
|
172
|
-
+ export_limit: optional, default is None (not set).
|
|
171
|
+
+ import_limit: optional, default is None (not set) but set to 0 if the mode is force discharge
|
|
172
|
+
+ export_limit: optional, default is None (not set).
|
|
173
|
+
+ pv_limit: optional, default is 1.5 x inverter rating.
|
|
173
174
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
174
175
|
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
175
176
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
@@ -818,6 +819,16 @@ This setting can be:
|
|
|
818
819
|
|
|
819
820
|
# Version Info
|
|
820
821
|
|
|
822
|
+
2.9.7 - 2026/03/18<br>
|
|
823
|
+
Add pv_limit to set_period().
|
|
824
|
+
Default import_linit to 0 for force discharge in set_period()
|
|
825
|
+
|
|
826
|
+
2.9.6 - 2026/03/10<br>
|
|
827
|
+
Fix problem setting fdsoc and fdpwr from segment for set_period().
|
|
828
|
+
Correctly identify H1-G2 model inverter.
|
|
829
|
+
Add maxChargeCurrent and maxDischargeCurrent to f.battery.
|
|
830
|
+
Update battery charge derating with temperature so charge current is not 0.
|
|
831
|
+
|
|
821
832
|
2.9.5 - 2026/02/27<br>
|
|
822
833
|
Add step parameter to date_list().
|
|
823
834
|
Get maxGroupCount from v3 scheduler and update f.max_periods with maximum number of time periods supported.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
##################################################################################################
|
|
2
2
|
"""
|
|
3
3
|
Module: Fox ESS Cloud
|
|
4
|
-
Updated:
|
|
4
|
+
Updated: 18 March 2026
|
|
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.11.
|
|
13
|
+
version = "1.11.3"
|
|
14
14
|
print(f"FoxESS-Cloud version {version}")
|
|
15
15
|
|
|
16
16
|
debug_setting = 1
|
|
@@ -502,6 +502,9 @@ def get_device(sn=None, device_type=None):
|
|
|
502
502
|
model_code = 'EVO-' + model_code[4:]
|
|
503
503
|
parts = model_code.split('-')
|
|
504
504
|
model = parts[0]
|
|
505
|
+
if parts[-1] == 'G2':
|
|
506
|
+
parts[0] += 'G2'
|
|
507
|
+
del parts[-1]
|
|
505
508
|
device['eps'] = ('E' in parts[-1]) or (model == 'EVO' and 'H' in parts[-1])
|
|
506
509
|
if model not in ['F1', 'G1', 'R3', 'S1', 'T3', 'KH', 'H1', 'AC1', 'H3', 'AC3', 'AIOH1', 'AIOH3', 'EVO']:
|
|
507
510
|
output(f"** device model not recognised for deviceType: {device['deviceType']}")
|
|
@@ -1475,7 +1478,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1475
1478
|
if quiet == 0:
|
|
1476
1479
|
s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
|
|
1477
1480
|
s += f", maxsoc {max_soc}%" if max_soc is not None and 'ForceCharge' in mode else ""
|
|
1478
|
-
s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if ('ForceCharge' in
|
|
1481
|
+
s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if ('ForceCharge' in mode or 'ForceDischarge' in mode) else ""
|
|
1479
1482
|
s += f", {price:.2f}p/kWh" if price is not None else ""
|
|
1480
1483
|
output(s, 1)
|
|
1481
1484
|
start_h, start_m = split_hours(start)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
##################################################################################################
|
|
2
2
|
"""
|
|
3
3
|
Module: Fox ESS Cloud using Open API
|
|
4
|
-
Updated:
|
|
4
|
+
Updated: 18 March 2026
|
|
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.9.
|
|
13
|
+
version = "2.9.7"
|
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
|
15
15
|
|
|
16
16
|
debug_setting = 1
|
|
@@ -515,6 +515,9 @@ def get_device(sn=None, device_type=None):
|
|
|
515
515
|
model_code = 'EVO-' + model_code[4:]
|
|
516
516
|
parts = model_code.split('-')
|
|
517
517
|
model = parts[0]
|
|
518
|
+
if parts[-1] == 'G2':
|
|
519
|
+
parts[0] += 'G2'
|
|
520
|
+
del parts[-1]
|
|
518
521
|
device['eps'] = ('E' in parts[-1]) or (model == 'EVO' and 'H' in parts[-1])
|
|
519
522
|
if model not in ['F1', 'G1', 'R3', 'S1', 'T3', 'KH', 'H1', 'AC1', 'H3', 'AC3', 'AIOH1', 'AIOH3', 'EVO']:
|
|
520
523
|
output(f"** device model not recognised for deviceType: {device['deviceType']}")
|
|
@@ -574,8 +577,8 @@ def get_generation(update=1):
|
|
|
574
577
|
battery = None
|
|
575
578
|
batteries = None
|
|
576
579
|
battery_settings = None
|
|
577
|
-
battery_vars = ['SoC', 'invBatVolt', 'invBatCurrent', 'invBatPower', 'batTemperature', 'ResidualEnergy','SOH','energyThroughput' ]
|
|
578
|
-
battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual', 'soh', 'throughput']
|
|
580
|
+
battery_vars = ['SoC', 'invBatVolt', 'invBatCurrent', 'invBatPower', 'batTemperature', 'ResidualEnergy','SOH','energyThroughput', 'maxChargeCurrent', 'maxDischargeCurrent' ]
|
|
581
|
+
battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual', 'soh', 'throughput', 'maxChargeCurrent', 'maxDischargeCurrent']
|
|
579
582
|
|
|
580
583
|
# 1 = Residual Energy, 2 = Residual Capacity (HV), 3 = Residual Capacity per battery (Mira)
|
|
581
584
|
residual_handling = 0
|
|
@@ -584,7 +587,7 @@ residual_handling = 0
|
|
|
584
587
|
battery_params = {
|
|
585
588
|
# bms temp 5 10 15 20 25 30 35 40 45 50 55 60 65
|
|
586
589
|
# cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
|
|
587
|
-
1: {'table': [
|
|
590
|
+
1: {'table': [ 1, 5, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 1],
|
|
588
591
|
'step': 5,
|
|
589
592
|
'offset': 5,
|
|
590
593
|
'charge_loss': 0.974,
|
|
@@ -592,7 +595,7 @@ battery_params = {
|
|
|
592
595
|
# HV BMS v2 with firmware 1.014 or later
|
|
593
596
|
# bms temp 10 15 20 25 30 35 40 45 50 55 60 65 70
|
|
594
597
|
# cell temp 0 5 10 15 20 25 30 35 40 45 50 55 60
|
|
595
|
-
2: {'table': [
|
|
598
|
+
2: {'table': [ 1, 5, 10, 15, 25, 50, 50, 50, 50, 25, 20, 3, 1],
|
|
596
599
|
'step': 5,
|
|
597
600
|
'offset': 11,
|
|
598
601
|
'charge_loss': 1.08,
|
|
@@ -600,7 +603,7 @@ battery_params = {
|
|
|
600
603
|
# Mira BMS with firmware 1.014 or later
|
|
601
604
|
# bms temp 10 15 20 25 30 35 40 45 50 55 60 65 70
|
|
602
605
|
# cell temp 0 5 10 15 20 25 30 35 40 45 50 55 60
|
|
603
|
-
3: {'table': [
|
|
606
|
+
3: {'table': [ 1, 5, 10, 15, 25, 50, 50, 50, 50, 25, 20, 3, 1],
|
|
604
607
|
'step': 5,
|
|
605
608
|
'offset': 11,
|
|
606
609
|
'charge_loss': 0.974,
|
|
@@ -1243,7 +1246,7 @@ def build_strategy_from_schedule():
|
|
|
1243
1246
|
##################################################################################################
|
|
1244
1247
|
|
|
1245
1248
|
# create time segment structure. Note: end time is exclusive.
|
|
1246
|
-
def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdsoc=None, fdpwr=None, import_limit=None, export_limit=None, price=None, segment=None, enable=1, quiet=1):
|
|
1249
|
+
def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdsoc=None, fdpwr=None, import_limit=None, export_limit=None, pv_limit=None, price=None, segment=None, enable=1, quiet=1):
|
|
1247
1250
|
global schedule, device
|
|
1248
1251
|
if schedule is None or schedule.get('maxsoc') is None:
|
|
1249
1252
|
get_schedule()
|
|
@@ -1253,10 +1256,11 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1253
1256
|
mode = segment.get('mode')
|
|
1254
1257
|
min_soc = segment.get('min_soc')
|
|
1255
1258
|
max_soc = segment.get('max_soc')
|
|
1256
|
-
fdsoc = segment.get('
|
|
1257
|
-
fdpwr = segment.get('
|
|
1259
|
+
fdsoc = segment.get('fdsoc')
|
|
1260
|
+
fdpwr = segment.get('fdpwr')
|
|
1258
1261
|
import_limit = segment.get('import_limit')
|
|
1259
1262
|
export_limit = segment.get('export_limit')
|
|
1263
|
+
pv_limit = segment.get('pv_limit')
|
|
1260
1264
|
price = segment.get('price')
|
|
1261
1265
|
start = time_hours(start)
|
|
1262
1266
|
# adjust exclusive time to inclusive
|
|
@@ -1276,6 +1280,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1276
1280
|
fdsoc = min_soc if fdsoc is None else fdsoc
|
|
1277
1281
|
power = (device['power'] * 1000) if device.get('power') is not None else None
|
|
1278
1282
|
fdpwr = power if fdpwr is None and device.get('power') is not None and ('ForceCharge' in mode or 'ForceDischarge' in mode) else fdpwr
|
|
1283
|
+
pv_limit = int(1.5 * power) if pv_limit is None and device.get('power') is not None and ('ForceCharge' in mode or 'ForceDischarge' in mode) else pv_limit
|
|
1284
|
+
import_limit = 0 if import_limit is None and 'ForceDischarge' in mode else import_limit
|
|
1279
1285
|
fdpwr = 0 if fdpwr is None else fdpwr
|
|
1280
1286
|
if min_soc < 0 or min_soc > 100:
|
|
1281
1287
|
output(f"set_period(): ** min_soc must be between 0 and 100")
|
|
@@ -1292,14 +1298,17 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1292
1298
|
if quiet == 0:
|
|
1293
1299
|
s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
|
|
1294
1300
|
s += f", maxsoc {max_soc}%" if max_soc is not None and 'ForceCharge' in mode else ""
|
|
1295
|
-
s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if ('ForceCharge' in mode or 'ForceDischarge' in mode) else ""
|
|
1301
|
+
s += f", fdPwr {int(fdpwr)}W, fdSoC {fdsoc}%" if ('ForceCharge' in mode or 'ForceDischarge' in mode) else ""
|
|
1302
|
+
s += f", exportLimit {int(export_limit)}W" if export_limit is not None else ""
|
|
1303
|
+
s += f", importLimit {int(import_limit)}W" if import_limit is not None else ""
|
|
1304
|
+
s += f", pvLimit {int(pv_limit)}W" if pv_limit is not None else ""
|
|
1296
1305
|
s += f", {price:.2f}p/kWh" if price is not None else ""
|
|
1297
1306
|
output(s, 1)
|
|
1298
1307
|
start_hour, start_minute = split_hours(start)
|
|
1299
1308
|
end_hour, end_minute = split_hours(end)
|
|
1300
1309
|
period = {'enable': enable, 'startHour': start_hour, 'startMinute': start_minute, 'endHour': end_hour, 'endMinute': end_minute, 'workMode': mode,
|
|
1301
1310
|
'extraParam': {'minSocOnGrid': int(min_soc), 'fdSoc': int(fdsoc), 'fdPwr': int(fdpwr), 'maxSoc': max_soc, 'importLimit': import_limit,
|
|
1302
|
-
'exportLimit': export_limit }}
|
|
1311
|
+
'exportLimit': export_limit, 'pvLimit': pv_limit}}
|
|
1303
1312
|
return period
|
|
1304
1313
|
|
|
1305
1314
|
# set a schedule from a period or list of time segment periods
|
|
@@ -2955,7 +2964,7 @@ def charge_needed(forecast=None, consumption=None, update_settings=0, timed_mode
|
|
|
2955
2964
|
reserve = capacity * min_soc / 100
|
|
2956
2965
|
# charge current may be derated based on temperature
|
|
2957
2966
|
charge_current = device_current if charge_config['charge_current'] is None else charge_config['charge_current']
|
|
2958
|
-
if bms_charge_current is not None and
|
|
2967
|
+
if bms_charge_current is not None and bms_charge_current < charge_current:
|
|
2959
2968
|
charge_current = bms_charge_current
|
|
2960
2969
|
volt_curve = charge_config['volt_curve']
|
|
2961
2970
|
nominal_soc = charge_config['nominal_soc']
|
|
@@ -2976,10 +2985,6 @@ def charge_needed(forecast=None, consumption=None, update_settings=0, timed_mode
|
|
|
2976
2985
|
output(f" Resistance: {bat_resistance:.2f} ohms")
|
|
2977
2986
|
output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
|
|
2978
2987
|
output(f" Losses: {charge_loss * 100:.1f}% charge / {discharge_loss * 100:.1f}% discharge", 2)
|
|
2979
|
-
# charge current may be derated based on temperature
|
|
2980
|
-
charge_current = device_current if charge_config['charge_current'] is None else charge_config['charge_current']
|
|
2981
|
-
if charge_current > bms_charge_current:
|
|
2982
|
-
charge_current = bms_charge_current
|
|
2983
2988
|
# inverter losses
|
|
2984
2989
|
inverter_power = charge_config['inverter_power'] if charge_config['inverter_power'] is not None else round(device_power, 0) * 25
|
|
2985
2990
|
operating_loss = inverter_power / 1000
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: foxesscloud
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.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
|
|
@@ -182,8 +182,9 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
182
182
|
+ max_soc: optional, default is 100
|
|
183
183
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
184
184
|
+ fdpwr: optional, default is 0. Used when setting a period with ForceDischarge mode
|
|
185
|
-
+ import_limit: optional, default is None (not set)
|
|
186
|
-
+ export_limit: optional, default is None (not set).
|
|
185
|
+
+ import_limit: optional, default is None (not set) but set to 0 if the mode is force discharge
|
|
186
|
+
+ export_limit: optional, default is None (not set).
|
|
187
|
+
+ pv_limit: optional, default is 1.5 x inverter rating.
|
|
187
188
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
188
189
|
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
189
190
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
@@ -832,6 +833,16 @@ This setting can be:
|
|
|
832
833
|
|
|
833
834
|
# Version Info
|
|
834
835
|
|
|
836
|
+
2.9.7 - 2026/03/18<br>
|
|
837
|
+
Add pv_limit to set_period().
|
|
838
|
+
Default import_linit to 0 for force discharge in set_period()
|
|
839
|
+
|
|
840
|
+
2.9.6 - 2026/03/10<br>
|
|
841
|
+
Fix problem setting fdsoc and fdpwr from segment for set_period().
|
|
842
|
+
Correctly identify H1-G2 model inverter.
|
|
843
|
+
Add maxChargeCurrent and maxDischargeCurrent to f.battery.
|
|
844
|
+
Update battery charge derating with temperature so charge current is not 0.
|
|
845
|
+
|
|
835
846
|
2.9.5 - 2026/02/27<br>
|
|
836
847
|
Add step parameter to date_list().
|
|
837
848
|
Get maxGroupCount from v3 scheduler and update f.max_periods with maximum number of time periods supported.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|