foxesscloud 2.9.1__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.1 → foxesscloud-2.9.3}/PKG-INFO +32 -10
- foxesscloud-2.9.1/src/foxesscloud.egg-info/PKG-INFO → foxesscloud-2.9.3/README.md +31 -23
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/pyproject.toml +1 -1
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/src/foxesscloud/foxesscloud.py +4 -4
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/src/foxesscloud/openapi.py +147 -58
- foxesscloud-2.9.1/README.md → foxesscloud-2.9.3/src/foxesscloud.egg-info/PKG-INFO +45 -9
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/LICENCE +0 -0
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/setup.cfg +0 -0
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/src/foxesscloud.egg-info/SOURCES.txt +0 -0
- {foxesscloud-2.9.1 → foxesscloud-2.9.3}/src/foxesscloud.egg-info/dependency_links.txt +0 -0
- {foxesscloud-2.9.1 → 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:
|
|
@@ -180,12 +181,16 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
180
181
|
+ max_soc: optional, default is 100
|
|
181
182
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
182
183
|
+ fdpwr: optional, default is 0. Used when setting a period with ForceDischarge mode
|
|
184
|
+
+ import_limit: optional, default is None (not set). Used when setting a period with ForceCharge mode
|
|
185
|
+
+ export_limit: optional, default is None (not set). Used when setting a period with ForceDischarge mode
|
|
183
186
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
184
187
|
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
185
188
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
186
189
|
|
|
187
190
|
Before calling set_period(), do at least one call to get_schedule(). This will inspect the schedule result to check if max_soc is supported and set the flag f.schedule['maxsoc'] to enable or disable this field as appropriate.
|
|
188
191
|
|
|
192
|
+
The variable f.max_periods is used to check the number of time periods allowed. The default is 8.
|
|
193
|
+
|
|
189
194
|
set_schedule() configures a list of scheduled work mode / soc changes with enable=1. If called with enable=0, any existing schedules are disabled. To enable a schedule, you must provide a list of time segments
|
|
190
195
|
+ periods: a time segment or list of time segments created using f.set_period().
|
|
191
196
|
+ enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
|
|
@@ -194,8 +199,12 @@ set_named_settings() sets the 'name' setting to 'value'.
|
|
|
194
199
|
+ 'name' may also be a list of (name, value) pairs.
|
|
195
200
|
+ force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
|
|
196
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.
|
|
197
|
-
+ 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.
|
|
198
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.
|
|
199
208
|
|
|
200
209
|
## Real Time Data
|
|
201
210
|
Real time data reports the latest values for inverter variables, collected every 5 minutes:
|
|
@@ -821,6 +830,19 @@ This setting can be:
|
|
|
821
830
|
|
|
822
831
|
# Version Info
|
|
823
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
|
+
|
|
842
|
+
2.9.2 - 2025/11/30<br>
|
|
843
|
+
Update get_schedule(), set_period() and set_schedule() to use v2 interface and add setting import_limit and export_limit.
|
|
844
|
+
Added f.max_periods to allow for more time periods in schedules (default is 8).
|
|
845
|
+
|
|
824
846
|
2.9.1 - 2025/10/25<br>
|
|
825
847
|
Change default contingency for charge_needed() to [25,15,10,20] and special contingency to 35.
|
|
826
848
|
Update info shown around contingency.
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: foxesscloud
|
|
3
|
-
Version: 2.9.1
|
|
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:
|
|
@@ -180,12 +167,16 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
180
167
|
+ max_soc: optional, default is 100
|
|
181
168
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
182
169
|
+ fdpwr: optional, default is 0. Used when setting a period with ForceDischarge mode
|
|
170
|
+
+ import_limit: optional, default is None (not set). Used when setting a period with ForceCharge mode
|
|
171
|
+
+ export_limit: optional, default is None (not set). Used when setting a period with ForceDischarge mode
|
|
183
172
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
184
173
|
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
185
174
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
186
175
|
|
|
187
176
|
Before calling set_period(), do at least one call to get_schedule(). This will inspect the schedule result to check if max_soc is supported and set the flag f.schedule['maxsoc'] to enable or disable this field as appropriate.
|
|
188
177
|
|
|
178
|
+
The variable f.max_periods is used to check the number of time periods allowed. The default is 8.
|
|
179
|
+
|
|
189
180
|
set_schedule() configures a list of scheduled work mode / soc changes with enable=1. If called with enable=0, any existing schedules are disabled. To enable a schedule, you must provide a list of time segments
|
|
190
181
|
+ periods: a time segment or list of time segments created using f.set_period().
|
|
191
182
|
+ enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
|
|
@@ -194,8 +185,12 @@ set_named_settings() sets the 'name' setting to 'value'.
|
|
|
194
185
|
+ 'name' may also be a list of (name, value) pairs.
|
|
195
186
|
+ force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
|
|
196
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.
|
|
197
|
-
+ 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.
|
|
198
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.
|
|
199
194
|
|
|
200
195
|
## Real Time Data
|
|
201
196
|
Real time data reports the latest values for inverter variables, collected every 5 minutes:
|
|
@@ -821,6 +816,19 @@ This setting can be:
|
|
|
821
816
|
|
|
822
817
|
# Version Info
|
|
823
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
|
+
2.9.2 - 2025/11/30<br>
|
|
829
|
+
Update get_schedule(), set_period() and set_schedule() to use v2 interface and add setting import_limit and export_limit.
|
|
830
|
+
Added f.max_periods to allow for more time periods in schedules (default is 8).
|
|
831
|
+
|
|
824
832
|
2.9.1 - 2025/10/25<br>
|
|
825
833
|
Change default contingency for charge_needed() to [25,15,10,20] and special contingency to 35.
|
|
826
834
|
Update info shown around contingency.
|
|
@@ -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')
|
|
@@ -1053,6 +1135,7 @@ def set_work_mode(mode, force = 0):
|
|
|
1053
1135
|
##################################################################################################
|
|
1054
1136
|
|
|
1055
1137
|
schedule = None
|
|
1138
|
+
max_periods = 8
|
|
1056
1139
|
|
|
1057
1140
|
# get the current switch status
|
|
1058
1141
|
def get_flag():
|
|
@@ -1069,7 +1152,7 @@ def get_flag():
|
|
|
1069
1152
|
if result is None:
|
|
1070
1153
|
return None
|
|
1071
1154
|
if schedule is None:
|
|
1072
|
-
schedule = {'enable': None, 'support': None, 'periods': None, 'maxsoc':
|
|
1155
|
+
schedule = {'enable': None, 'support': None, 'periods': None, 'maxsoc': None}
|
|
1073
1156
|
schedule['enable'] = result.get('enable')
|
|
1074
1157
|
schedule['support'] = result.get('support')
|
|
1075
1158
|
if device.get('function') is not None and device['function'].get('scheduler') is not None:
|
|
@@ -1090,7 +1173,7 @@ def get_schedule():
|
|
|
1090
1173
|
return None
|
|
1091
1174
|
output(f"getting schedule", 2)
|
|
1092
1175
|
body = {'deviceSN': device_sn}
|
|
1093
|
-
response = signed_post(path="/op/
|
|
1176
|
+
response = signed_post(path="/op/v2/device/scheduler/get", body=body)
|
|
1094
1177
|
if response.status_code != 200:
|
|
1095
1178
|
output(f"** get_schedule() got response code {response.status_code}: {response.reason}")
|
|
1096
1179
|
return None
|
|
@@ -1103,11 +1186,12 @@ def get_schedule():
|
|
|
1103
1186
|
enable = True if enable == 1 else False
|
|
1104
1187
|
schedule['enable'] = enable
|
|
1105
1188
|
schedule['periods'] = []
|
|
1189
|
+
schedule['maxsoc'] = False
|
|
1106
1190
|
# remove invalid work mode from periods
|
|
1107
1191
|
for g in result['groups']:
|
|
1108
1192
|
if g['enable'] == 1 and g['workMode'] in work_modes:
|
|
1109
1193
|
schedule['periods'].append(g)
|
|
1110
|
-
if g.get('maxSoc') is not None:
|
|
1194
|
+
if g.get('extraParam') is not None and g['extraParam'].get('maxSoc') is not None:
|
|
1111
1195
|
schedule['maxsoc'] = True
|
|
1112
1196
|
return schedule
|
|
1113
1197
|
|
|
@@ -1126,6 +1210,8 @@ def build_strategy_from_schedule():
|
|
|
1126
1210
|
period['max_soc'] = p.get('maxSoc')
|
|
1127
1211
|
period['fdsoc'] = p.get('fdsoc')
|
|
1128
1212
|
period['fdpwr'] = p.get('fdpwr')
|
|
1213
|
+
period['import_limit'] = p.get('importLimit')
|
|
1214
|
+
period['export_limit'] = p.get('exportLimit')
|
|
1129
1215
|
strategy.append(period)
|
|
1130
1216
|
return strategy
|
|
1131
1217
|
|
|
@@ -1134,9 +1220,9 @@ def build_strategy_from_schedule():
|
|
|
1134
1220
|
##################################################################################################
|
|
1135
1221
|
|
|
1136
1222
|
# create time segment structure. Note: end time is exclusive.
|
|
1137
|
-
def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdsoc=None, fdpwr=None, price=None, segment=None, enable=1, quiet=1):
|
|
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):
|
|
1138
1224
|
global schedule, device
|
|
1139
|
-
if schedule is None:
|
|
1225
|
+
if schedule is None or schedule.get('maxsoc') is None:
|
|
1140
1226
|
get_schedule()
|
|
1141
1227
|
if segment is not None and type(segment) is dict:
|
|
1142
1228
|
start = segment.get('start')
|
|
@@ -1146,6 +1232,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1146
1232
|
max_soc = segment.get('max_soc')
|
|
1147
1233
|
fdsoc = segment.get('fdSoc')
|
|
1148
1234
|
fdpwr = segment.get('fdPwr')
|
|
1235
|
+
import_limit = segment.get('import_limit')
|
|
1236
|
+
export_limit = segment.get('export_limit')
|
|
1149
1237
|
price = segment.get('price')
|
|
1150
1238
|
start = time_hours(start)
|
|
1151
1239
|
# adjust exclusive time to inclusive
|
|
@@ -1166,8 +1254,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1166
1254
|
power = (device['power'] * 1000) if device.get('power') is not None else None
|
|
1167
1255
|
fdpwr = power if fdpwr is None and device.get('power') is not None and ('ForceCharge' in mode or 'ForceDischarge' in mode) else fdpwr
|
|
1168
1256
|
fdpwr = 0 if fdpwr is None else fdpwr
|
|
1169
|
-
if min_soc <
|
|
1170
|
-
output(f"set_period(): ** min_soc must be between
|
|
1257
|
+
if min_soc < 0 or min_soc > 100:
|
|
1258
|
+
output(f"set_period(): ** min_soc must be between 0 and 100")
|
|
1171
1259
|
return None
|
|
1172
1260
|
if max_soc is not None and (max_soc < 10 or max_soc > 100):
|
|
1173
1261
|
output(f"set_period(): ** max_soc must be between 10 and 100")
|
|
@@ -1187,14 +1275,13 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1187
1275
|
start_hour, start_minute = split_hours(start)
|
|
1188
1276
|
end_hour, end_minute = split_hours(end)
|
|
1189
1277
|
period = {'enable': enable, 'startHour': start_hour, 'startMinute': start_minute, 'endHour': end_hour, 'endMinute': end_minute, 'workMode': mode,
|
|
1190
|
-
'minSocOnGrid': int(min_soc), 'fdSoc': int(fdsoc), 'fdPwr': int(fdpwr)
|
|
1191
|
-
|
|
1192
|
-
period['maxSoc'] = int(max_soc)
|
|
1278
|
+
'extraParam': {'minSocOnGrid': int(min_soc), 'fdSoc': int(fdsoc), 'fdPwr': int(fdpwr), 'maxSoc': max_soc, 'importLimit': import_limit,
|
|
1279
|
+
'exportLimit': export_limit }}
|
|
1193
1280
|
return period
|
|
1194
1281
|
|
|
1195
1282
|
# set a schedule from a period or list of time segment periods
|
|
1196
1283
|
def set_schedule(periods=None, enable=True):
|
|
1197
|
-
global device_sn, debug_setting, schedule
|
|
1284
|
+
global device_sn, debug_setting, schedule, max_periods
|
|
1198
1285
|
if get_flag() is None:
|
|
1199
1286
|
return None
|
|
1200
1287
|
if schedule.get('support') == False:
|
|
@@ -1202,6 +1289,7 @@ def set_schedule(periods=None, enable=True):
|
|
|
1202
1289
|
return None
|
|
1203
1290
|
output(f"set_schedule(): enable = {enable}, periods = {periods}", 2)
|
|
1204
1291
|
if debug_setting > 2:
|
|
1292
|
+
print(f"** schedule not set (debug_setting={debug_setting})")
|
|
1205
1293
|
return None
|
|
1206
1294
|
if type(enable) is int:
|
|
1207
1295
|
enable = True if enable == 1 else False
|
|
@@ -1212,11 +1300,11 @@ def set_schedule(periods=None, enable=True):
|
|
|
1212
1300
|
if periods is not None:
|
|
1213
1301
|
if type(periods) is not list:
|
|
1214
1302
|
periods = [periods]
|
|
1215
|
-
if len(periods) >
|
|
1216
|
-
output(f"** set_schedule(): maximum of
|
|
1217
|
-
body = {'deviceSN': device_sn, 'groups': periods[-
|
|
1303
|
+
if len(periods) > max_periods:
|
|
1304
|
+
output(f"** set_schedule(): maximum of {max_periods} periods allowed, {len(periods)} provided")
|
|
1305
|
+
body = {'deviceSN': device_sn, 'groups': periods[-max_periods:]}
|
|
1218
1306
|
setting_delay()
|
|
1219
|
-
response = signed_post(path="/op/
|
|
1307
|
+
response = signed_post(path="/op/v2/device/scheduler/enable", body=body)
|
|
1220
1308
|
if response.status_code != 200:
|
|
1221
1309
|
output(f"** set_schedule() periods response code {response.status_code}: {response.reason}")
|
|
1222
1310
|
return None
|
|
@@ -3437,7 +3525,8 @@ def battery_info(log=0, plot=1, rated=None, count=None, info=1, bat=None):
|
|
|
3437
3525
|
output(f"Cell Volts: {avg(cell_volts):.3f}V average, {max(cell_volts):.3f}V maximum, {min(cell_volts):.3f}V minimum")
|
|
3438
3526
|
output(f"Cell Imbalance: {imbalance(cell_volts):.2f}%:")
|
|
3439
3527
|
output(f"BMS Temperature: {bms_temperature:.1f}°C")
|
|
3440
|
-
|
|
3528
|
+
if bat.get('charge_rate') is not None:
|
|
3529
|
+
output(f"BMS Charge Rate: {bat['charge_rate']:.1f}A (estimated)")
|
|
3441
3530
|
output(f"Battery Temperature: {avg(cell_temps):.1f}°C average, {max(cell_temps):.1f}°C maximum, {min(cell_temps):.1f}°C minimum")
|
|
3442
3531
|
output(f"\nInfo by battery:")
|
|
3443
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:
|
|
@@ -166,12 +181,16 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
166
181
|
+ max_soc: optional, default is 100
|
|
167
182
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
168
183
|
+ fdpwr: optional, default is 0. Used when setting a period with ForceDischarge mode
|
|
184
|
+
+ import_limit: optional, default is None (not set). Used when setting a period with ForceCharge mode
|
|
185
|
+
+ export_limit: optional, default is None (not set). Used when setting a period with ForceDischarge mode
|
|
169
186
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
170
187
|
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
171
188
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
172
189
|
|
|
173
190
|
Before calling set_period(), do at least one call to get_schedule(). This will inspect the schedule result to check if max_soc is supported and set the flag f.schedule['maxsoc'] to enable or disable this field as appropriate.
|
|
174
191
|
|
|
192
|
+
The variable f.max_periods is used to check the number of time periods allowed. The default is 8.
|
|
193
|
+
|
|
175
194
|
set_schedule() configures a list of scheduled work mode / soc changes with enable=1. If called with enable=0, any existing schedules are disabled. To enable a schedule, you must provide a list of time segments
|
|
176
195
|
+ periods: a time segment or list of time segments created using f.set_period().
|
|
177
196
|
+ enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
|
|
@@ -180,8 +199,12 @@ set_named_settings() sets the 'name' setting to 'value'.
|
|
|
180
199
|
+ 'name' may also be a list of (name, value) pairs.
|
|
181
200
|
+ force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
|
|
182
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.
|
|
183
|
-
+ 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.
|
|
184
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.
|
|
185
208
|
|
|
186
209
|
## Real Time Data
|
|
187
210
|
Real time data reports the latest values for inverter variables, collected every 5 minutes:
|
|
@@ -807,6 +830,19 @@ This setting can be:
|
|
|
807
830
|
|
|
808
831
|
# Version Info
|
|
809
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
|
+
|
|
842
|
+
2.9.2 - 2025/11/30<br>
|
|
843
|
+
Update get_schedule(), set_period() and set_schedule() to use v2 interface and add setting import_limit and export_limit.
|
|
844
|
+
Added f.max_periods to allow for more time periods in schedules (default is 8).
|
|
845
|
+
|
|
810
846
|
2.9.1 - 2025/10/25<br>
|
|
811
847
|
Change default contingency for charge_needed() to [25,15,10,20] and special contingency to 35.
|
|
812
848
|
Update info shown around contingency.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|