foxesscloud 2.9.2__tar.gz → 2.9.3__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.2 → foxesscloud-2.9.3}/PKG-INFO +24 -10
- foxesscloud-2.9.2/src/foxesscloud.egg-info/PKG-INFO → foxesscloud-2.9.3/README.md +23 -23
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/pyproject.toml +1 -1
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/src/foxesscloud/foxesscloud.py +4 -4
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/src/foxesscloud/openapi.py +129 -45
- foxesscloud-2.9.2/README.md → foxesscloud-2.9.3/src/foxesscloud.egg-info/PKG-INFO +37 -9
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/LICENCE +0 -0
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/setup.cfg +0 -0
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/src/foxesscloud.egg-info/SOURCES.txt +0 -0
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/src/foxesscloud.egg-info/dependency_links.txt +0 -0
- {foxesscloud-2.9.2 → foxesscloud-2.9.3}/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.3
|
|
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
|
|
@@ -61,6 +61,7 @@ You don't have to configure all of the settings. Your Fox ESS Cloud api key is t
|
|
|
61
61
|
For example, replace _my.fox_api_key_ with the API key. Add you inverter serial number if you have more than 1 inverter linked to your account. Be sure to keep the double quotes around the values you enter or you will get a syntax error.
|
|
62
62
|
|
|
63
63
|
Residual handling configures how battery residual energy reported by Fox is handled:
|
|
64
|
+
+ 0: Use rated capacity, SoH and SoC to calculate residual energy (default)
|
|
64
65
|
+ 1: Fox returns the current battery residual energy and battery capacity is calculated using soc
|
|
65
66
|
+ 2: Fox returns the current battery capacity and battery residual is calculated using soc
|
|
66
67
|
+ 3: Fox returns the residual capacity per battery (Mira)
|
|
@@ -108,8 +109,8 @@ Once an inverter is selected, you can make other calls to get information:
|
|
|
108
109
|
|
|
109
110
|
```
|
|
110
111
|
f.get_generation()
|
|
111
|
-
f.get_battery(
|
|
112
|
-
f.get_batteries(info
|
|
112
|
+
f.get_battery()
|
|
113
|
+
f.get_batteries(info)
|
|
113
114
|
f.get_settings()
|
|
114
115
|
f.get_charge()
|
|
115
116
|
f.get_min()
|
|
@@ -117,17 +118,15 @@ f.get_peakshaving()
|
|
|
117
118
|
f.get_flag()
|
|
118
119
|
f.get_schedule()
|
|
119
120
|
f.get_named_settings(name)
|
|
121
|
+
f.get_battery_heating()
|
|
120
122
|
|
|
121
123
|
```
|
|
122
124
|
Each of these calls will return a dictionary or list containing the relevant information.
|
|
123
125
|
|
|
124
126
|
get_generation() will return the latest generation information for the device. The results are also stored in f.device as 'generationToday', 'generationMonth' and 'generationTotal'.
|
|
125
127
|
|
|
126
|
-
get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and '
|
|
127
|
-
get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery.
|
|
128
|
-
+ 'info': get battery serial number info, if available. Default 0 (not available via Open API)
|
|
129
|
-
+ 'rated': optional rated capacity for the battery in Wh to work out SoH. If not provided, it will try to work this out.
|
|
130
|
-
+ 'count': optional battery count. If not provided, it will try to work this out.
|
|
128
|
+
get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature', 'residual' and 'throughput'. The result also updates f.battery / f.batteries.
|
|
129
|
+
get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery.
|
|
131
130
|
|
|
132
131
|
Additional battery attributes provided include:
|
|
133
132
|
+ 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
|
|
@@ -146,8 +145,9 @@ get_schedule() returns the current work mode / soc schedule settings. The result
|
|
|
146
145
|
|
|
147
146
|
get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
|
|
148
147
|
+ f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
|
|
149
|
-
+ named_settings currently supported
|
|
148
|
+
+ named_settings currently available are stored in f.name_list. The settings supported depends on the inverter model and firmware version. An error will be returned if an unsupported variable is used.
|
|
150
149
|
|
|
150
|
+
get_battery_heating returns the current battery heating parameters and store these in f.battery_heating
|
|
151
151
|
|
|
152
152
|
## Inverter Settings
|
|
153
153
|
You can change inverter settings using:
|
|
@@ -158,6 +158,7 @@ f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
|
|
|
158
158
|
f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
|
|
159
159
|
f.set_schedule(periods, enable)
|
|
160
160
|
f.set_named_settings(name, value, force)
|
|
161
|
+
f.set_battery_heating(enable, start, end, time1, time2, time3)
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
|
|
@@ -198,8 +199,12 @@ set_named_settings() sets the 'name' setting to 'value'.
|
|
|
198
199
|
+ 'name' may also be a list of (name, value) pairs.
|
|
199
200
|
+ force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
|
|
200
201
|
+ a return value of 1 is success. 0 means setting failed. None is another error e.g. device not found, invalid name or value.
|
|
201
|
-
+ named_settings currently supported
|
|
202
|
+
+ named_settings currently available are stored in f.name_list. The settings supported depend on the inverter model and firmware version. An error will be returned if an unsupported varaible is used.
|
|
202
203
|
|
|
204
|
+
set_battery_heating() set the heating parameters as follows:
|
|
205
|
+
+ enable: optional, 0 or 1, default is 1
|
|
206
|
+
+ start, end: optional start and end temperatures. The defaults are start at 9C and end at 12C.
|
|
207
|
+
+ time1, time2, time3: optional times when the battery can heat from the grid time. The structure is {'enable': 1, 'start': '00:30', 'end': '05:30'}. The time slot is disabled by default.
|
|
203
208
|
|
|
204
209
|
## Real Time Data
|
|
205
210
|
Real time data reports the latest values for inverter variables, collected every 5 minutes:
|
|
@@ -825,6 +830,15 @@ This setting can be:
|
|
|
825
830
|
|
|
826
831
|
# Version Info
|
|
827
832
|
|
|
833
|
+
2.9.3 - 2026/01/18<br>
|
|
834
|
+
Update get_device() to use v1 API call.
|
|
835
|
+
Update get_battery() to work out battery count and rated capacity from device battery list.
|
|
836
|
+
Update default residual_handling to option 0 (residual is calculated from rated capacity, SoH and SoC).
|
|
837
|
+
Added get_battery_real() for testing (Fox does not currently populate most of the data).
|
|
838
|
+
Added 'throughput' to battery variables returned.
|
|
839
|
+
Add f.name_list to hold the list of setting variables and update the list.
|
|
840
|
+
Added get_battery_heating() and set_battery_heating().
|
|
841
|
+
|
|
828
842
|
2.9.2 - 2025/11/30<br>
|
|
829
843
|
Update get_schedule(), set_period() and set_schedule() to use v2 interface and add setting import_limit and export_limit.
|
|
830
844
|
Added f.max_periods to allow for more time periods in schedules (default is 8).
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: foxesscloud
|
|
3
|
-
Version: 2.9.2
|
|
4
|
-
Summary: library for accessing Fox ESS cloud data using Open API
|
|
5
|
-
Author-email: Tony Matthews <tony@quasair.co.uk>
|
|
6
|
-
Project-URL: Homepage, https://github.com/TonyM1958/FoxESS-Cloud
|
|
7
|
-
Project-URL: Bug Tracker, https://github.com/TonyM1958/FoxESS-Cloud/issues
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.7
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENCE
|
|
14
|
-
|
|
15
1
|
# FoxESS-Cloud
|
|
16
2
|
|
|
17
3
|
<a href="https://www.buymeacoffee.com/tonym1958" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174" align="right"></a>
|
|
@@ -61,6 +47,7 @@ You don't have to configure all of the settings. Your Fox ESS Cloud api key is t
|
|
|
61
47
|
For example, replace _my.fox_api_key_ with the API key. Add you inverter serial number if you have more than 1 inverter linked to your account. Be sure to keep the double quotes around the values you enter or you will get a syntax error.
|
|
62
48
|
|
|
63
49
|
Residual handling configures how battery residual energy reported by Fox is handled:
|
|
50
|
+
+ 0: Use rated capacity, SoH and SoC to calculate residual energy (default)
|
|
64
51
|
+ 1: Fox returns the current battery residual energy and battery capacity is calculated using soc
|
|
65
52
|
+ 2: Fox returns the current battery capacity and battery residual is calculated using soc
|
|
66
53
|
+ 3: Fox returns the residual capacity per battery (Mira)
|
|
@@ -108,8 +95,8 @@ Once an inverter is selected, you can make other calls to get information:
|
|
|
108
95
|
|
|
109
96
|
```
|
|
110
97
|
f.get_generation()
|
|
111
|
-
f.get_battery(
|
|
112
|
-
f.get_batteries(info
|
|
98
|
+
f.get_battery()
|
|
99
|
+
f.get_batteries(info)
|
|
113
100
|
f.get_settings()
|
|
114
101
|
f.get_charge()
|
|
115
102
|
f.get_min()
|
|
@@ -117,17 +104,15 @@ f.get_peakshaving()
|
|
|
117
104
|
f.get_flag()
|
|
118
105
|
f.get_schedule()
|
|
119
106
|
f.get_named_settings(name)
|
|
107
|
+
f.get_battery_heating()
|
|
120
108
|
|
|
121
109
|
```
|
|
122
110
|
Each of these calls will return a dictionary or list containing the relevant information.
|
|
123
111
|
|
|
124
112
|
get_generation() will return the latest generation information for the device. The results are also stored in f.device as 'generationToday', 'generationMonth' and 'generationTotal'.
|
|
125
113
|
|
|
126
|
-
get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and '
|
|
127
|
-
get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery.
|
|
128
|
-
+ 'info': get battery serial number info, if available. Default 0 (not available via Open API)
|
|
129
|
-
+ 'rated': optional rated capacity for the battery in Wh to work out SoH. If not provided, it will try to work this out.
|
|
130
|
-
+ 'count': optional battery count. If not provided, it will try to work this out.
|
|
114
|
+
get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature', 'residual' and 'throughput'. The result also updates f.battery / f.batteries.
|
|
115
|
+
get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery.
|
|
131
116
|
|
|
132
117
|
Additional battery attributes provided include:
|
|
133
118
|
+ 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
|
|
@@ -146,8 +131,9 @@ get_schedule() returns the current work mode / soc schedule settings. The result
|
|
|
146
131
|
|
|
147
132
|
get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
|
|
148
133
|
+ f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
|
|
149
|
-
+ named_settings currently supported
|
|
134
|
+
+ named_settings currently available are stored in f.name_list. The settings supported depends on the inverter model and firmware version. An error will be returned if an unsupported variable is used.
|
|
150
135
|
|
|
136
|
+
get_battery_heating returns the current battery heating parameters and store these in f.battery_heating
|
|
151
137
|
|
|
152
138
|
## Inverter Settings
|
|
153
139
|
You can change inverter settings using:
|
|
@@ -158,6 +144,7 @@ f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
|
|
|
158
144
|
f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
|
|
159
145
|
f.set_schedule(periods, enable)
|
|
160
146
|
f.set_named_settings(name, value, force)
|
|
147
|
+
f.set_battery_heating(enable, start, end, time1, time2, time3)
|
|
161
148
|
```
|
|
162
149
|
|
|
163
150
|
set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
|
|
@@ -198,8 +185,12 @@ set_named_settings() sets the 'name' setting to 'value'.
|
|
|
198
185
|
+ 'name' may also be a list of (name, value) pairs.
|
|
199
186
|
+ force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
|
|
200
187
|
+ a return value of 1 is success. 0 means setting failed. None is another error e.g. device not found, invalid name or value.
|
|
201
|
-
+ named_settings currently supported
|
|
188
|
+
+ named_settings currently available are stored in f.name_list. The settings supported depend on the inverter model and firmware version. An error will be returned if an unsupported varaible is used.
|
|
202
189
|
|
|
190
|
+
set_battery_heating() set the heating parameters as follows:
|
|
191
|
+
+ enable: optional, 0 or 1, default is 1
|
|
192
|
+
+ start, end: optional start and end temperatures. The defaults are start at 9C and end at 12C.
|
|
193
|
+
+ time1, time2, time3: optional times when the battery can heat from the grid time. The structure is {'enable': 1, 'start': '00:30', 'end': '05:30'}. The time slot is disabled by default.
|
|
203
194
|
|
|
204
195
|
## Real Time Data
|
|
205
196
|
Real time data reports the latest values for inverter variables, collected every 5 minutes:
|
|
@@ -825,6 +816,15 @@ This setting can be:
|
|
|
825
816
|
|
|
826
817
|
# Version Info
|
|
827
818
|
|
|
819
|
+
2.9.3 - 2026/01/18<br>
|
|
820
|
+
Update get_device() to use v1 API call.
|
|
821
|
+
Update get_battery() to work out battery count and rated capacity from device battery list.
|
|
822
|
+
Update default residual_handling to option 0 (residual is calculated from rated capacity, SoH and SoC).
|
|
823
|
+
Added get_battery_real() for testing (Fox does not currently populate most of the data).
|
|
824
|
+
Added 'throughput' to battery variables returned.
|
|
825
|
+
Add f.name_list to hold the list of setting variables and update the list.
|
|
826
|
+
Added get_battery_heating() and set_battery_heating().
|
|
827
|
+
|
|
828
828
|
2.9.2 - 2025/11/30<br>
|
|
829
829
|
Update get_schedule(), set_period() and set_schedule() to use v2 interface and add setting import_limit and export_limit.
|
|
830
830
|
Added f.max_periods to allow for more time periods in schedules (default is 8).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
##################################################################################################
|
|
2
2
|
"""
|
|
3
3
|
Module: Fox ESS Cloud
|
|
4
|
-
Updated:
|
|
4
|
+
Updated: 18 January 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.
|
|
13
|
+
version = "1.11.0"
|
|
14
14
|
print(f"FoxESS-Cloud version {version}")
|
|
15
15
|
|
|
16
16
|
debug_setting = 1
|
|
@@ -1862,8 +1862,8 @@ def rescale_history(data, steps):
|
|
|
1862
1862
|
# station = 0: use device_id, 1 = use station_id
|
|
1863
1863
|
##################################################################################################
|
|
1864
1864
|
|
|
1865
|
-
report_vars = ['PVEnergyTotal', '
|
|
1866
|
-
report_names = ['PV Yield', '
|
|
1865
|
+
report_vars = ['PVEnergyTotal', 'generation', 'feedin', 'loads', 'gridConsumption', 'chargeEnergyToTal', 'dischargeEnergyToTal']
|
|
1866
|
+
report_names = ['PV Yield', 'Generation', 'Grid Export', 'Consumption', 'Grid Import', 'Battery Charge', 'Battery Discharge']
|
|
1867
1867
|
|
|
1868
1868
|
# fix power values after fox corrupts high word of 32-bit energy total
|
|
1869
1869
|
fix_values = 1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
##################################################################################################
|
|
2
2
|
"""
|
|
3
3
|
Module: Fox ESS Cloud using Open API
|
|
4
|
-
Updated:
|
|
4
|
+
Updated: 18 January 2025
|
|
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.3"
|
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
|
15
15
|
|
|
16
16
|
debug_setting = 1
|
|
@@ -486,7 +486,7 @@ def get_device(sn=None, device_type=None):
|
|
|
486
486
|
# load information for the device
|
|
487
487
|
device_sn = device_list[n].get('deviceSN')
|
|
488
488
|
params = {'sn': device_sn }
|
|
489
|
-
response = signed_get(path="/op/
|
|
489
|
+
response = signed_get(path="/op/v1/device/detail", params=params)
|
|
490
490
|
if response.status_code != 200:
|
|
491
491
|
output(f"** get_device() got detail response code {response.status_code}: {response.reason}")
|
|
492
492
|
return None
|
|
@@ -574,11 +574,11 @@ def get_generation(update=1):
|
|
|
574
574
|
battery = None
|
|
575
575
|
batteries = None
|
|
576
576
|
battery_settings = None
|
|
577
|
-
battery_vars = ['SoC', 'invBatVolt', 'invBatCurrent', 'invBatPower', 'batTemperature', 'ResidualEnergy','SOH' ]
|
|
578
|
-
battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual', 'soh']
|
|
577
|
+
battery_vars = ['SoC', 'invBatVolt', 'invBatCurrent', 'invBatPower', 'batTemperature', 'ResidualEnergy','SOH','energyThroughput' ]
|
|
578
|
+
battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual', 'soh', 'throughput']
|
|
579
579
|
|
|
580
580
|
# 1 = Residual Energy, 2 = Residual Capacity (HV), 3 = Residual Capacity per battery (Mira)
|
|
581
|
-
residual_handling =
|
|
581
|
+
residual_handling = 0
|
|
582
582
|
|
|
583
583
|
# charge rates based on residual_handling. Index is bms temperature
|
|
584
584
|
battery_params = {
|
|
@@ -611,57 +611,53 @@ def get_battery(info=0, v=None, rated=None, count=None):
|
|
|
611
611
|
global device_sn, battery, debug_setting, residual_handling, battery_params
|
|
612
612
|
if get_device() is None:
|
|
613
613
|
return None
|
|
614
|
+
battery = {}
|
|
615
|
+
rated = 0
|
|
616
|
+
count = 0
|
|
617
|
+
for b in device['batteryList']:
|
|
618
|
+
if b.get('type') == 'bmu' and b.get('capacity') is not None:
|
|
619
|
+
rated += b['capacity']
|
|
620
|
+
count += 1
|
|
621
|
+
if count > 0:
|
|
622
|
+
battery['count'] = count
|
|
623
|
+
battery['ratedCapacity'] = rated
|
|
624
|
+
else:
|
|
625
|
+
output(f"** get_battery(): battery capacity not available")
|
|
626
|
+
return None
|
|
614
627
|
output(f"getting battery", 2)
|
|
615
628
|
if v is None:
|
|
616
629
|
v = battery_vars
|
|
617
630
|
result = get_real(v)
|
|
618
|
-
battery = {}
|
|
619
631
|
for i in range(0, len(battery_vars)):
|
|
620
632
|
battery[battery_data[i]] = result[i].get('value')
|
|
621
633
|
if debug_setting > 1:
|
|
622
634
|
print(f"raw battery = {battery}")
|
|
623
|
-
battery['residual_handling'] = residual_handling
|
|
624
|
-
battery['soh'] = None
|
|
625
|
-
battery['soh_supported'] = False
|
|
626
635
|
if battery.get('status') is None:
|
|
627
636
|
battery['status'] = 0 if battery.get('volt') is None or battery['volt'] <= 10 else 1
|
|
628
637
|
if battery['status'] == 0:
|
|
629
638
|
output(f"** get_battery(): battery status not available")
|
|
630
639
|
return None
|
|
631
|
-
|
|
640
|
+
capacity = battery['ratedCapacity'] / 1000 * (battery['soh'] if battery.get('soh') is not None else 100) / 100
|
|
641
|
+
soc = battery.get('soc')
|
|
642
|
+
battery['residual_handling'] = residual_handling
|
|
643
|
+
if battery['residual_handling'] == 1:
|
|
644
|
+
capacity = battery['residual'] / soc * 100
|
|
645
|
+
battery['soh'] = round(capacity * 1000 / battery['ratedCapacity'] * 100, 1)
|
|
646
|
+
elif battery['residual_handling'] == 2:
|
|
632
647
|
capacity = battery.get('residual')
|
|
633
|
-
|
|
634
|
-
residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
|
|
635
|
-
if battery.get('count') is None:
|
|
636
|
-
battery['count'] = int(battery['volt'] / 49) if count is None else count
|
|
637
|
-
if battery.get('ratedCapacity') is None:
|
|
638
|
-
battery['ratedCapacity'] = 2560 * battery['count'] if rated is None else rated
|
|
648
|
+
battery['soh'] = round(capacity * 1000 / battery['ratedCapacity'] * 100, 1)
|
|
639
649
|
elif battery['residual_handling'] == 3:
|
|
640
|
-
if battery.get('count') is None:
|
|
641
|
-
battery['count'] = int(battery['volt'] / 49) if count is None else count
|
|
642
650
|
capacity = (battery['residual'] * battery['count']) if battery.get('residual') is not None else None
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if battery.get('ratedCapacity') is None:
|
|
646
|
-
battery['ratedCapacity'] = 2450 * battery['count'] if rated is None else rated
|
|
647
|
-
else:
|
|
648
|
-
residual = battery.get('residual')
|
|
649
|
-
soc = battery.get('soc')
|
|
650
|
-
capacity = residual / soc * 100 if residual is not None and soc is not None and soc > 0 else None
|
|
651
|
-
if battery.get('count') is None or battery['count'] < 1:
|
|
652
|
-
battery['count'] = count
|
|
653
|
-
if battery.get('ratedCapacity') is None or battery['ratedCapacity'] < 100:
|
|
654
|
-
battery['ratedCapacity'] = rated
|
|
651
|
+
battery['soh'] = round(capacity / battery['ratedCapacity'] * 100, 1)
|
|
652
|
+
residual = capacity * soc / 100
|
|
655
653
|
battery['capacity'] = round(capacity, 3)
|
|
656
654
|
battery['residual'] = round(residual, 3)
|
|
657
|
-
battery['
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if battery.get('temperature') is not None:
|
|
664
|
-
battery['charge_rate'] = interpolate((battery['temperature'] - params['offset']) / params['step'], params['table'])
|
|
655
|
+
if battery['residual_handling'] > 0:
|
|
656
|
+
params = battery_params[battery['residual_handling']]
|
|
657
|
+
battery['charge_loss'] = params['charge_loss']
|
|
658
|
+
battery['discharge_loss'] = params['discharge_loss']
|
|
659
|
+
if battery.get('temperature') is not None:
|
|
660
|
+
battery['charge_rate'] = interpolate((battery['temperature'] - params['offset']) / params['step'], params['table'])
|
|
665
661
|
return battery
|
|
666
662
|
|
|
667
663
|
def get_batteries(info=0, rated=None, count=None):
|
|
@@ -676,6 +672,91 @@ def get_batteries(info=0, rated=None, count=None):
|
|
|
676
672
|
batteries = [battery]
|
|
677
673
|
return batteries
|
|
678
674
|
|
|
675
|
+
def get_battery_real():
|
|
676
|
+
global device_sn, device
|
|
677
|
+
if get_device() is None:
|
|
678
|
+
return None
|
|
679
|
+
output(f"getting battery real", 2)
|
|
680
|
+
params = {'sn': device_sn}
|
|
681
|
+
response = signed_get(path="/op/v0/device/battery/real/query", params=params)
|
|
682
|
+
if response.status_code != 200:
|
|
683
|
+
output(f"** get_battery_real() got response code {response.status_code}: {response.reason}")
|
|
684
|
+
return None
|
|
685
|
+
result = response.json().get('result')
|
|
686
|
+
if result is None:
|
|
687
|
+
output(f"** get_battery_real(), no result data, {errno_message(response)}")
|
|
688
|
+
return None
|
|
689
|
+
return result
|
|
690
|
+
|
|
691
|
+
##################################################################################################
|
|
692
|
+
# battery heating settings
|
|
693
|
+
##################################################################################################
|
|
694
|
+
|
|
695
|
+
battery_heating = None
|
|
696
|
+
|
|
697
|
+
def get_battery_heating():
|
|
698
|
+
global device_sn, device, battery_heating
|
|
699
|
+
if get_device() is None:
|
|
700
|
+
return None
|
|
701
|
+
output(f"getting battery heating", 2)
|
|
702
|
+
body = {'sn': device_sn}
|
|
703
|
+
response = signed_post(path="/op/v0/device/batteryHeating/get", body=body)
|
|
704
|
+
if response.status_code != 200:
|
|
705
|
+
output(f"** get_battery_heating() got response code {response.status_code}: {response.reason}")
|
|
706
|
+
return None
|
|
707
|
+
errno = response.json().get('errno')
|
|
708
|
+
if errno != 0:
|
|
709
|
+
if errno == 41200:
|
|
710
|
+
output(f"** get_battery_heating(): not supported")
|
|
711
|
+
else:
|
|
712
|
+
output(f"** get_battery_heating(): {errno_message(response)}")
|
|
713
|
+
return None
|
|
714
|
+
result = response.json().get('result')
|
|
715
|
+
battery_heating = result
|
|
716
|
+
return result
|
|
717
|
+
|
|
718
|
+
def set_time(body, s, time):
|
|
719
|
+
if time is None:
|
|
720
|
+
body[s + 'Enable'] = 0
|
|
721
|
+
body[s + 'StartHour'] = 0
|
|
722
|
+
body[s + 'StartMinute'] = 0
|
|
723
|
+
body[s + 'EndHour'] = 0
|
|
724
|
+
body[s + 'EndMinute'] = 0
|
|
725
|
+
else:
|
|
726
|
+
body[s + 'Enable'] = time['enable']
|
|
727
|
+
t = time_hours(time['start'])
|
|
728
|
+
body[s + 'StartHour'] = int(t)
|
|
729
|
+
body[s + 'StartMinute'] = int(60 * (t - int(t)) + 0.5)
|
|
730
|
+
t = time_hours(time['end'])
|
|
731
|
+
body[s + 'EndHour'] = int(t)
|
|
732
|
+
body[s + 'EndMinute'] = int(60 * (t - int(t)) + 0.5)
|
|
733
|
+
return
|
|
734
|
+
|
|
735
|
+
def set_battery_heating(enable=None, start=None, end=None, time1=None, time2=None, time3=None):
|
|
736
|
+
global device_sn, device
|
|
737
|
+
if get_device() is None:
|
|
738
|
+
return None
|
|
739
|
+
output(f"setting battery heating", 2)
|
|
740
|
+
body = {'sn': device_sn}
|
|
741
|
+
body['batteryWarmUpEnable'] = enable if enable is not None else 1
|
|
742
|
+
body['startTemperature'] = start if start is not None else 9
|
|
743
|
+
body['endTemperature'] = end if end is not None else 12
|
|
744
|
+
set_time(body, 'time1', time1)
|
|
745
|
+
set_time(body, 'time2', time2)
|
|
746
|
+
set_time(body, 'time3', time3)
|
|
747
|
+
response = signed_post(path="/op/v0/device/batteryHeating/set", body=body)
|
|
748
|
+
if response.status_code != 200:
|
|
749
|
+
output(f"** set_battery_heating() got response code {response.status_code}: {response.reason}")
|
|
750
|
+
return None
|
|
751
|
+
errno = response.json().get('errno')
|
|
752
|
+
if errno != 0:
|
|
753
|
+
if errno == 41200:
|
|
754
|
+
output(f"** set_battery_heating(): not supported")
|
|
755
|
+
else:
|
|
756
|
+
output(f"** set_battery_heating(): {errno_message(response)}")
|
|
757
|
+
return None
|
|
758
|
+
return 1
|
|
759
|
+
|
|
679
760
|
##################################################################################################
|
|
680
761
|
# get charge times and save to battery_settings
|
|
681
762
|
##################################################################################################
|
|
@@ -896,6 +977,8 @@ def get_peakshaving():
|
|
|
896
977
|
##################################################################################################
|
|
897
978
|
|
|
898
979
|
# store for named settings info
|
|
980
|
+
name_list = ['ExportLimit','MinSoc','MinSocOnGrid','MaxSoc','GridCode','WorkMode','ExportLimitPower',
|
|
981
|
+
'EpsOutPut','MaxSetChargeCurrent','MaxSetDischargeCurrent','ECOMode','Meter1Enable','Meter2Enable','SysSwitch','GroundProtection']
|
|
899
982
|
named_settings = {}
|
|
900
983
|
|
|
901
984
|
def get_remote_settings(name):
|
|
@@ -911,8 +994,7 @@ def get_remote_settings(name):
|
|
|
911
994
|
v = get_remote_settings(n)
|
|
912
995
|
if v is None:
|
|
913
996
|
continue
|
|
914
|
-
|
|
915
|
-
values[x] = v[x]
|
|
997
|
+
values[n] = v
|
|
916
998
|
return values
|
|
917
999
|
body = {'sn': device_sn, 'key': name}
|
|
918
1000
|
setting_delay()
|
|
@@ -923,7 +1005,7 @@ def get_remote_settings(name):
|
|
|
923
1005
|
result = response.json().get('result')
|
|
924
1006
|
if result is None:
|
|
925
1007
|
errno = response.json().get('errno')
|
|
926
|
-
output(f"** get_remote_settings(), no result data, {errno_message(response)}")
|
|
1008
|
+
output(f"** get_remote_settings(), no result data for {name}, {errno_message(response)}")
|
|
927
1009
|
return None
|
|
928
1010
|
named_settings[name] = result
|
|
929
1011
|
value = result.get('value')
|
|
@@ -1070,7 +1152,7 @@ def get_flag():
|
|
|
1070
1152
|
if result is None:
|
|
1071
1153
|
return None
|
|
1072
1154
|
if schedule is None:
|
|
1073
|
-
schedule = {'enable': None, 'support': None, 'periods': None, 'maxsoc':
|
|
1155
|
+
schedule = {'enable': None, 'support': None, 'periods': None, 'maxsoc': None}
|
|
1074
1156
|
schedule['enable'] = result.get('enable')
|
|
1075
1157
|
schedule['support'] = result.get('support')
|
|
1076
1158
|
if device.get('function') is not None and device['function'].get('scheduler') is not None:
|
|
@@ -1104,6 +1186,7 @@ def get_schedule():
|
|
|
1104
1186
|
enable = True if enable == 1 else False
|
|
1105
1187
|
schedule['enable'] = enable
|
|
1106
1188
|
schedule['periods'] = []
|
|
1189
|
+
schedule['maxsoc'] = False
|
|
1107
1190
|
# remove invalid work mode from periods
|
|
1108
1191
|
for g in result['groups']:
|
|
1109
1192
|
if g['enable'] == 1 and g['workMode'] in work_modes:
|
|
@@ -1139,7 +1222,7 @@ def build_strategy_from_schedule():
|
|
|
1139
1222
|
# create time segment structure. Note: end time is exclusive.
|
|
1140
1223
|
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):
|
|
1141
1224
|
global schedule, device
|
|
1142
|
-
if schedule is None:
|
|
1225
|
+
if schedule is None or schedule.get('maxsoc') is None:
|
|
1143
1226
|
get_schedule()
|
|
1144
1227
|
if segment is not None and type(segment) is dict:
|
|
1145
1228
|
start = segment.get('start')
|
|
@@ -3442,7 +3525,8 @@ def battery_info(log=0, plot=1, rated=None, count=None, info=1, bat=None):
|
|
|
3442
3525
|
output(f"Cell Volts: {avg(cell_volts):.3f}V average, {max(cell_volts):.3f}V maximum, {min(cell_volts):.3f}V minimum")
|
|
3443
3526
|
output(f"Cell Imbalance: {imbalance(cell_volts):.2f}%:")
|
|
3444
3527
|
output(f"BMS Temperature: {bms_temperature:.1f}°C")
|
|
3445
|
-
|
|
3528
|
+
if bat.get('charge_rate') is not None:
|
|
3529
|
+
output(f"BMS Charge Rate: {bat['charge_rate']:.1f}A (estimated)")
|
|
3446
3530
|
output(f"Battery Temperature: {avg(cell_temps):.1f}°C average, {max(cell_temps):.1f}°C maximum, {min(cell_temps):.1f}°C minimum")
|
|
3447
3531
|
output(f"\nInfo by battery:")
|
|
3448
3532
|
for i in range(0, nbat):
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: foxesscloud
|
|
3
|
+
Version: 2.9.3
|
|
4
|
+
Summary: library for accessing Fox ESS cloud data using Open API
|
|
5
|
+
Author-email: Tony Matthews <tony@quasair.co.uk>
|
|
6
|
+
Project-URL: Homepage, https://github.com/TonyM1958/FoxESS-Cloud
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/TonyM1958/FoxESS-Cloud/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENCE
|
|
14
|
+
|
|
1
15
|
# FoxESS-Cloud
|
|
2
16
|
|
|
3
17
|
<a href="https://www.buymeacoffee.com/tonym1958" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174" align="right"></a>
|
|
@@ -47,6 +61,7 @@ You don't have to configure all of the settings. Your Fox ESS Cloud api key is t
|
|
|
47
61
|
For example, replace _my.fox_api_key_ with the API key. Add you inverter serial number if you have more than 1 inverter linked to your account. Be sure to keep the double quotes around the values you enter or you will get a syntax error.
|
|
48
62
|
|
|
49
63
|
Residual handling configures how battery residual energy reported by Fox is handled:
|
|
64
|
+
+ 0: Use rated capacity, SoH and SoC to calculate residual energy (default)
|
|
50
65
|
+ 1: Fox returns the current battery residual energy and battery capacity is calculated using soc
|
|
51
66
|
+ 2: Fox returns the current battery capacity and battery residual is calculated using soc
|
|
52
67
|
+ 3: Fox returns the residual capacity per battery (Mira)
|
|
@@ -94,8 +109,8 @@ Once an inverter is selected, you can make other calls to get information:
|
|
|
94
109
|
|
|
95
110
|
```
|
|
96
111
|
f.get_generation()
|
|
97
|
-
f.get_battery(
|
|
98
|
-
f.get_batteries(info
|
|
112
|
+
f.get_battery()
|
|
113
|
+
f.get_batteries(info)
|
|
99
114
|
f.get_settings()
|
|
100
115
|
f.get_charge()
|
|
101
116
|
f.get_min()
|
|
@@ -103,17 +118,15 @@ f.get_peakshaving()
|
|
|
103
118
|
f.get_flag()
|
|
104
119
|
f.get_schedule()
|
|
105
120
|
f.get_named_settings(name)
|
|
121
|
+
f.get_battery_heating()
|
|
106
122
|
|
|
107
123
|
```
|
|
108
124
|
Each of these calls will return a dictionary or list containing the relevant information.
|
|
109
125
|
|
|
110
126
|
get_generation() will return the latest generation information for the device. The results are also stored in f.device as 'generationToday', 'generationMonth' and 'generationTotal'.
|
|
111
127
|
|
|
112
|
-
get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and '
|
|
113
|
-
get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery.
|
|
114
|
-
+ 'info': get battery serial number info, if available. Default 0 (not available via Open API)
|
|
115
|
-
+ 'rated': optional rated capacity for the battery in Wh to work out SoH. If not provided, it will try to work this out.
|
|
116
|
-
+ 'count': optional battery count. If not provided, it will try to work this out.
|
|
128
|
+
get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature', 'residual' and 'throughput'. The result also updates f.battery / f.batteries.
|
|
129
|
+
get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery.
|
|
117
130
|
|
|
118
131
|
Additional battery attributes provided include:
|
|
119
132
|
+ 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
|
|
@@ -132,8 +145,9 @@ get_schedule() returns the current work mode / soc schedule settings. The result
|
|
|
132
145
|
|
|
133
146
|
get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
|
|
134
147
|
+ f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
|
|
135
|
-
+ named_settings currently supported
|
|
148
|
+
+ named_settings currently available are stored in f.name_list. The settings supported depends on the inverter model and firmware version. An error will be returned if an unsupported variable is used.
|
|
136
149
|
|
|
150
|
+
get_battery_heating returns the current battery heating parameters and store these in f.battery_heating
|
|
137
151
|
|
|
138
152
|
## Inverter Settings
|
|
139
153
|
You can change inverter settings using:
|
|
@@ -144,6 +158,7 @@ f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
|
|
|
144
158
|
f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
|
|
145
159
|
f.set_schedule(periods, enable)
|
|
146
160
|
f.set_named_settings(name, value, force)
|
|
161
|
+
f.set_battery_heating(enable, start, end, time1, time2, time3)
|
|
147
162
|
```
|
|
148
163
|
|
|
149
164
|
set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
|
|
@@ -184,8 +199,12 @@ set_named_settings() sets the 'name' setting to 'value'.
|
|
|
184
199
|
+ 'name' may also be a list of (name, value) pairs.
|
|
185
200
|
+ force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
|
|
186
201
|
+ a return value of 1 is success. 0 means setting failed. None is another error e.g. device not found, invalid name or value.
|
|
187
|
-
+ named_settings currently supported
|
|
202
|
+
+ named_settings currently available are stored in f.name_list. The settings supported depend on the inverter model and firmware version. An error will be returned if an unsupported varaible is used.
|
|
188
203
|
|
|
204
|
+
set_battery_heating() set the heating parameters as follows:
|
|
205
|
+
+ enable: optional, 0 or 1, default is 1
|
|
206
|
+
+ start, end: optional start and end temperatures. The defaults are start at 9C and end at 12C.
|
|
207
|
+
+ time1, time2, time3: optional times when the battery can heat from the grid time. The structure is {'enable': 1, 'start': '00:30', 'end': '05:30'}. The time slot is disabled by default.
|
|
189
208
|
|
|
190
209
|
## Real Time Data
|
|
191
210
|
Real time data reports the latest values for inverter variables, collected every 5 minutes:
|
|
@@ -811,6 +830,15 @@ This setting can be:
|
|
|
811
830
|
|
|
812
831
|
# Version Info
|
|
813
832
|
|
|
833
|
+
2.9.3 - 2026/01/18<br>
|
|
834
|
+
Update get_device() to use v1 API call.
|
|
835
|
+
Update get_battery() to work out battery count and rated capacity from device battery list.
|
|
836
|
+
Update default residual_handling to option 0 (residual is calculated from rated capacity, SoH and SoC).
|
|
837
|
+
Added get_battery_real() for testing (Fox does not currently populate most of the data).
|
|
838
|
+
Added 'throughput' to battery variables returned.
|
|
839
|
+
Add f.name_list to hold the list of setting variables and update the list.
|
|
840
|
+
Added get_battery_heating() and set_battery_heating().
|
|
841
|
+
|
|
814
842
|
2.9.2 - 2025/11/30<br>
|
|
815
843
|
Update get_schedule(), set_period() and set_schedule() to use v2 interface and add setting import_limit and export_limit.
|
|
816
844
|
Added f.max_periods to allow for more time periods in schedules (default is 8).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|