foxesscloud 2.9.6__tar.gz → 2.9.8__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.6 → foxesscloud-2.9.8}/PKG-INFO +25 -8
- foxesscloud-2.9.6/src/foxesscloud.egg-info/PKG-INFO → foxesscloud-2.9.8/README.md +24 -21
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/pyproject.toml +1 -1
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/src/foxesscloud/foxesscloud.py +3 -3
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/src/foxesscloud/openapi.py +60 -44
- foxesscloud-2.9.6/README.md → foxesscloud-2.9.8/src/foxesscloud.egg-info/PKG-INFO +38 -7
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/LICENCE +0 -0
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/setup.cfg +0 -0
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/src/foxesscloud.egg-info/SOURCES.txt +0 -0
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/src/foxesscloud.egg-info/dependency_links.txt +0 -0
- {foxesscloud-2.9.6 → foxesscloud-2.9.8}/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.8
|
|
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
|
|
@@ -140,9 +140,9 @@ get_peakshaving() will return the current peak shaving settings for inverters th
|
|
|
140
140
|
|
|
141
141
|
get_flag() returns the current scheduler enable / support / maxsoc flags. By default support for Max Soc is set to False.
|
|
142
142
|
+ f.max_periods / schedule['maxGroupCount'] gives the maximum number of time periods that are supported.
|
|
143
|
+
+ f.schedule['properties'] details the parameters supported in Mode Scheduler for the current inverter.
|
|
143
144
|
|
|
144
145
|
get_schedule() returns the current work mode / soc schedule settings. The result is stored in f.schedule.
|
|
145
|
-
+ if the schedule returned contains any values for 'maxSoc', the f.schedule['maxsoc'] is set to True to indicate that the current inverter supports setting Max Soc in schedules and Max Soc values are set by set_schedule().
|
|
146
146
|
|
|
147
147
|
get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
|
|
148
148
|
+ f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
|
|
@@ -157,7 +157,7 @@ You can change inverter settings using:
|
|
|
157
157
|
f.set_min(minSocOnGrid, minSoc)
|
|
158
158
|
f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
|
|
159
159
|
f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
|
|
160
|
-
f.set_schedule(periods, enable)
|
|
160
|
+
f.set_schedule(periods, enable, is_default)
|
|
161
161
|
f.set_named_settings(name, value, force)
|
|
162
162
|
f.set_heating(enable, start, end, time1, time2, time3)
|
|
163
163
|
```
|
|
@@ -182,10 +182,10 @@ 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
|
-
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
189
189
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
190
190
|
|
|
191
191
|
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.
|
|
@@ -195,6 +195,7 @@ The variable f.max_periods is used to check the number of time periods allowed.
|
|
|
195
195
|
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
|
|
196
196
|
+ periods: a time segment or list of time segments created using f.set_period().
|
|
197
197
|
+ enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
|
|
198
|
+
+ is_default: False (default): parameters not provided remain unchanged. True: parameters not provided are restored to system defaults
|
|
198
199
|
|
|
199
200
|
set_named_settings() sets the 'name' setting to 'value'.
|
|
200
201
|
+ 'name' may also be a list of (name, value) pairs.
|
|
@@ -426,7 +427,7 @@ min_hours: 0.25 # minimum charge time to set (in decimal hours)
|
|
|
426
427
|
min_kwh: 0.5 # minimum charge to add in kwh
|
|
427
428
|
solcast_adjust: 100 # % adjustment to make to Solcast forecast
|
|
428
429
|
solar_adjust: 100 # % adjustment to make to Solar forecast
|
|
429
|
-
forecast_selection:
|
|
430
|
+
forecast_selection: 0 # 1 = only update charge times if forecast is available, 0 = use best available data.
|
|
430
431
|
annual_consumption: None # optional annual consumption in kWh. If set, this replaces consumption history
|
|
431
432
|
timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy mode
|
|
432
433
|
special_contingency: 35 # contingency for special days when consumption might be higher
|
|
@@ -832,13 +833,29 @@ This setting can be:
|
|
|
832
833
|
|
|
833
834
|
# Version Info
|
|
834
835
|
|
|
836
|
+
2.9.8 - 2026/03/22<br>
|
|
837
|
+
** potential breaking changes **
|
|
838
|
+
Update to use Mode Scheduler API v3.
|
|
839
|
+
This adds 'properties' to f.schedule that describes the work modes and parameters supported on different inverters.
|
|
840
|
+
f.work_modes and f.settable_modes are now set dynamically from the properties.
|
|
841
|
+
The period parameters that are sent are trimmed to the supported set and unused parameters for the mode are removed.
|
|
842
|
+
** API v3 rejects unsupported parameters. This limits the testing I can do for different inverters. Please report any issues **
|
|
843
|
+
API v3 removes the previous ability to send disabled time periods. If you try to send a disabled period, set_period() will return None.
|
|
844
|
+
set_period() now supports 'reactive_power' as a parameter.
|
|
845
|
+
set_schedule() now supports 'is_default'. See Open API documentation for more info on what this does.
|
|
846
|
+
charge_needed() changed from using min_soc for battery hold to using ForceDischarge with fdpwr=0 when timed_mode=2.
|
|
847
|
+
charge_needed() default configuration now has forecast_selection=0 so settings will be updated if a forecast is not available.
|
|
848
|
+
|
|
849
|
+
2.9.7 - 2026/03/18<br>
|
|
850
|
+
Add pv_limit to set_period().
|
|
851
|
+
Default import_linit to 0 for force discharge in set_period()
|
|
852
|
+
|
|
835
853
|
2.9.6 - 2026/03/10<br>
|
|
836
854
|
Fix problem setting fdsoc and fdpwr from segment for set_period().
|
|
837
855
|
Correctly identify H1-G2 model inverter.
|
|
838
856
|
Add maxChargeCurrent and maxDischargeCurrent to f.battery.
|
|
839
857
|
Update battery charge derating with temperature so charge current is not 0.
|
|
840
858
|
|
|
841
|
-
|
|
842
859
|
2.9.5 - 2026/02/27<br>
|
|
843
860
|
Add step parameter to date_list().
|
|
844
861
|
Get maxGroupCount from v3 scheduler and update f.max_periods with maximum number of time periods supported.
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: foxesscloud
|
|
3
|
-
Version: 2.9.6
|
|
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>
|
|
@@ -140,9 +126,9 @@ get_peakshaving() will return the current peak shaving settings for inverters th
|
|
|
140
126
|
|
|
141
127
|
get_flag() returns the current scheduler enable / support / maxsoc flags. By default support for Max Soc is set to False.
|
|
142
128
|
+ f.max_periods / schedule['maxGroupCount'] gives the maximum number of time periods that are supported.
|
|
129
|
+
+ f.schedule['properties'] details the parameters supported in Mode Scheduler for the current inverter.
|
|
143
130
|
|
|
144
131
|
get_schedule() returns the current work mode / soc schedule settings. The result is stored in f.schedule.
|
|
145
|
-
+ if the schedule returned contains any values for 'maxSoc', the f.schedule['maxsoc'] is set to True to indicate that the current inverter supports setting Max Soc in schedules and Max Soc values are set by set_schedule().
|
|
146
132
|
|
|
147
133
|
get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
|
|
148
134
|
+ f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
|
|
@@ -157,7 +143,7 @@ You can change inverter settings using:
|
|
|
157
143
|
f.set_min(minSocOnGrid, minSoc)
|
|
158
144
|
f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
|
|
159
145
|
f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
|
|
160
|
-
f.set_schedule(periods, enable)
|
|
146
|
+
f.set_schedule(periods, enable, is_default)
|
|
161
147
|
f.set_named_settings(name, value, force)
|
|
162
148
|
f.set_heating(enable, start, end, time1, time2, time3)
|
|
163
149
|
```
|
|
@@ -182,10 +168,10 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
182
168
|
+ max_soc: optional, default is 100
|
|
183
169
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
184
170
|
+ 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).
|
|
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.
|
|
187
174
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
188
|
-
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
189
175
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
190
176
|
|
|
191
177
|
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.
|
|
@@ -195,6 +181,7 @@ The variable f.max_periods is used to check the number of time periods allowed.
|
|
|
195
181
|
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
|
|
196
182
|
+ periods: a time segment or list of time segments created using f.set_period().
|
|
197
183
|
+ enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
|
|
184
|
+
+ is_default: False (default): parameters not provided remain unchanged. True: parameters not provided are restored to system defaults
|
|
198
185
|
|
|
199
186
|
set_named_settings() sets the 'name' setting to 'value'.
|
|
200
187
|
+ 'name' may also be a list of (name, value) pairs.
|
|
@@ -426,7 +413,7 @@ min_hours: 0.25 # minimum charge time to set (in decimal hours)
|
|
|
426
413
|
min_kwh: 0.5 # minimum charge to add in kwh
|
|
427
414
|
solcast_adjust: 100 # % adjustment to make to Solcast forecast
|
|
428
415
|
solar_adjust: 100 # % adjustment to make to Solar forecast
|
|
429
|
-
forecast_selection:
|
|
416
|
+
forecast_selection: 0 # 1 = only update charge times if forecast is available, 0 = use best available data.
|
|
430
417
|
annual_consumption: None # optional annual consumption in kWh. If set, this replaces consumption history
|
|
431
418
|
timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy mode
|
|
432
419
|
special_contingency: 35 # contingency for special days when consumption might be higher
|
|
@@ -832,13 +819,29 @@ This setting can be:
|
|
|
832
819
|
|
|
833
820
|
# Version Info
|
|
834
821
|
|
|
822
|
+
2.9.8 - 2026/03/22<br>
|
|
823
|
+
** potential breaking changes **
|
|
824
|
+
Update to use Mode Scheduler API v3.
|
|
825
|
+
This adds 'properties' to f.schedule that describes the work modes and parameters supported on different inverters.
|
|
826
|
+
f.work_modes and f.settable_modes are now set dynamically from the properties.
|
|
827
|
+
The period parameters that are sent are trimmed to the supported set and unused parameters for the mode are removed.
|
|
828
|
+
** API v3 rejects unsupported parameters. This limits the testing I can do for different inverters. Please report any issues **
|
|
829
|
+
API v3 removes the previous ability to send disabled time periods. If you try to send a disabled period, set_period() will return None.
|
|
830
|
+
set_period() now supports 'reactive_power' as a parameter.
|
|
831
|
+
set_schedule() now supports 'is_default'. See Open API documentation for more info on what this does.
|
|
832
|
+
charge_needed() changed from using min_soc for battery hold to using ForceDischarge with fdpwr=0 when timed_mode=2.
|
|
833
|
+
charge_needed() default configuration now has forecast_selection=0 so settings will be updated if a forecast is not available.
|
|
834
|
+
|
|
835
|
+
2.9.7 - 2026/03/18<br>
|
|
836
|
+
Add pv_limit to set_period().
|
|
837
|
+
Default import_linit to 0 for force discharge in set_period()
|
|
838
|
+
|
|
835
839
|
2.9.6 - 2026/03/10<br>
|
|
836
840
|
Fix problem setting fdsoc and fdpwr from segment for set_period().
|
|
837
841
|
Correctly identify H1-G2 model inverter.
|
|
838
842
|
Add maxChargeCurrent and maxDischargeCurrent to f.battery.
|
|
839
843
|
Update battery charge derating with temperature so charge current is not 0.
|
|
840
844
|
|
|
841
|
-
|
|
842
845
|
2.9.5 - 2026/02/27<br>
|
|
843
846
|
Add step parameter to date_list().
|
|
844
847
|
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
|
|
@@ -1478,7 +1478,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1478
1478
|
if quiet == 0:
|
|
1479
1479
|
s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
|
|
1480
1480
|
s += f", maxsoc {max_soc}%" if max_soc is not None and 'ForceCharge' in mode else ""
|
|
1481
|
-
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 ""
|
|
1482
1482
|
s += f", {price:.2f}p/kWh" if price is not None else ""
|
|
1483
1483
|
output(s, 1)
|
|
1484
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: 22 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.8"
|
|
14
14
|
print(f"FoxESS-Cloud Open API version {version}")
|
|
15
15
|
|
|
16
16
|
debug_setting = 1
|
|
@@ -1154,7 +1154,7 @@ max_periods = 8
|
|
|
1154
1154
|
|
|
1155
1155
|
# get the current switch status
|
|
1156
1156
|
def get_flag():
|
|
1157
|
-
global device_sn, schedule, debug_setting, max_periods
|
|
1157
|
+
global device_sn, schedule, debug_setting, max_periods, work_modes, settable_modes
|
|
1158
1158
|
if get_device() is None:
|
|
1159
1159
|
return None
|
|
1160
1160
|
output(f"getting flag", 2)
|
|
@@ -1167,22 +1167,29 @@ def get_flag():
|
|
|
1167
1167
|
if result is None:
|
|
1168
1168
|
return None
|
|
1169
1169
|
if schedule is None:
|
|
1170
|
-
schedule = {'enable': None, 'support': None, 'periods':
|
|
1170
|
+
schedule = {'enable': None, 'support': None, 'periods': [], 'maxsoc': None}
|
|
1171
1171
|
schedule['enable'] = result.get('enable')
|
|
1172
1172
|
schedule['support'] = result.get('support')
|
|
1173
1173
|
if device.get('function') is not None and device['function'].get('scheduler') is not None:
|
|
1174
1174
|
device['function']['scheduler'] = schedule['support']
|
|
1175
1175
|
if schedule.get('maxGroupCount') is None:
|
|
1176
|
-
output(f"getting
|
|
1176
|
+
output(f"getting properties", 2)
|
|
1177
1177
|
body = {'deviceSN': device_sn}
|
|
1178
1178
|
response = signed_post(path="/op/v3/device/scheduler/get", body=body)
|
|
1179
1179
|
if response.status_code != 200:
|
|
1180
|
-
output(f"** get_flag() got response code getting
|
|
1180
|
+
output(f"** get_flag() got response code getting properties {response.status_code}: {response.reason}")
|
|
1181
1181
|
return None
|
|
1182
1182
|
result = response.json().get('result')
|
|
1183
1183
|
if result is not None:
|
|
1184
1184
|
schedule['maxGroupCount'] = result.get('maxGroupCount')
|
|
1185
1185
|
max_periods = schedule['maxGroupCount']
|
|
1186
|
+
schedule['properties'] = result.get('properties')
|
|
1187
|
+
if schedule['properties'] is not None:
|
|
1188
|
+
schedule['maxsoc'] = schedule['properties'].get('maxsoc') is not None
|
|
1189
|
+
modes = schedule['properties'].get('workmode')
|
|
1190
|
+
if modes is not None:
|
|
1191
|
+
work_modes = sorted(modes['enumList'])
|
|
1192
|
+
settable_modes = [w for w in work_modes if 'Force' not in w]
|
|
1186
1193
|
return schedule
|
|
1187
1194
|
|
|
1188
1195
|
##################################################################################################
|
|
@@ -1199,7 +1206,7 @@ def get_schedule():
|
|
|
1199
1206
|
return None
|
|
1200
1207
|
output(f"getting schedule", 2)
|
|
1201
1208
|
body = {'deviceSN': device_sn}
|
|
1202
|
-
response = signed_post(path="/op/
|
|
1209
|
+
response = signed_post(path="/op/v3/device/scheduler/get", body=body)
|
|
1203
1210
|
if response.status_code != 200:
|
|
1204
1211
|
output(f"** get_schedule() got response code {response.status_code}: {response.reason}")
|
|
1205
1212
|
return None
|
|
@@ -1212,13 +1219,10 @@ def get_schedule():
|
|
|
1212
1219
|
enable = True if enable == 1 else False
|
|
1213
1220
|
schedule['enable'] = enable
|
|
1214
1221
|
schedule['periods'] = []
|
|
1215
|
-
schedule['maxsoc'] = False
|
|
1216
1222
|
# remove invalid work mode from periods
|
|
1217
1223
|
for g in result['groups']:
|
|
1218
|
-
if g['
|
|
1224
|
+
if g['workMode'] in work_modes:
|
|
1219
1225
|
schedule['periods'].append(g)
|
|
1220
|
-
if g.get('extraParam') is not None and g['extraParam'].get('maxSoc') is not None:
|
|
1221
|
-
schedule['maxsoc'] = True
|
|
1222
1226
|
return schedule
|
|
1223
1227
|
|
|
1224
1228
|
# build strategy using current schedule
|
|
@@ -1246,9 +1250,10 @@ def build_strategy_from_schedule():
|
|
|
1246
1250
|
##################################################################################################
|
|
1247
1251
|
|
|
1248
1252
|
# create time segment structure. Note: end time is exclusive.
|
|
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,
|
|
1253
|
+
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, reactive_power=None
|
|
1254
|
+
, price=None, segment=None, enable=1, quiet=1):
|
|
1250
1255
|
global schedule, device
|
|
1251
|
-
if schedule is None
|
|
1256
|
+
if schedule is None:
|
|
1252
1257
|
get_schedule()
|
|
1253
1258
|
if segment is not None and type(segment) is dict:
|
|
1254
1259
|
start = segment.get('start')
|
|
@@ -1260,7 +1265,11 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1260
1265
|
fdpwr = segment.get('fdpwr')
|
|
1261
1266
|
import_limit = segment.get('import_limit')
|
|
1262
1267
|
export_limit = segment.get('export_limit')
|
|
1268
|
+
pv_limit = segment.get('pv_limit')
|
|
1269
|
+
reactive_power = segment.get('reactive_power')
|
|
1263
1270
|
price = segment.get('price')
|
|
1271
|
+
if enable == 0:
|
|
1272
|
+
return None
|
|
1264
1273
|
start = time_hours(start)
|
|
1265
1274
|
# adjust exclusive time to inclusive
|
|
1266
1275
|
end = time_hours(end)
|
|
@@ -1272,41 +1281,48 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
|
|
|
1272
1281
|
if mode not in work_modes:
|
|
1273
1282
|
output(f"** mode must be one of {work_modes}")
|
|
1274
1283
|
return None
|
|
1284
|
+
properties = schedule.get('properties')
|
|
1275
1285
|
min_soc = 10 if min_soc is None else min_soc
|
|
1276
|
-
max_soc = None if
|
|
1286
|
+
max_soc = None if properties.get('maxsoc') is None or 'ForceCharge' not in mode else 100 if max_soc is None else max_soc
|
|
1277
1287
|
if 'ForceCharge' in mode and fdsoc is None:
|
|
1278
1288
|
fdsoc = max_soc if max_soc is not None else 100
|
|
1279
|
-
fdsoc = min_soc if fdsoc is None else fdsoc
|
|
1289
|
+
fdsoc = None if properties.get('fdsoc') is None or 'Force' not in mode else min_soc if fdsoc is None else fdsoc
|
|
1280
1290
|
power = (device['power'] * 1000) if device.get('power') is not None else None
|
|
1281
|
-
fdpwr = power if fdpwr is None and device.get('power') is not None and ('
|
|
1282
|
-
|
|
1283
|
-
if
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
if max_soc is not None and (max_soc < 10 or max_soc > 100):
|
|
1287
|
-
output(f"set_period(): ** max_soc must be between 10 and 100")
|
|
1288
|
-
return None
|
|
1289
|
-
if fdpwr < 0 or fdpwr > 30000:
|
|
1290
|
-
output(f"set_period(): ** fdpwr must be between 0 and 30000")
|
|
1291
|
-
return None
|
|
1292
|
-
if fdsoc < min_soc or fdsoc > 100:
|
|
1293
|
-
output(f"set_period(): ** fdsoc must between {min_soc} and 100")
|
|
1294
|
-
return None
|
|
1291
|
+
fdpwr = None if properties.get('fdpwr') is None else power if fdpwr is None and device.get('power') is not None and ('Force' in mode) else fdpwr
|
|
1292
|
+
pv_limit = None if properties.get('pvlimit') is None else int(1.5 * power) if pv_limit is None and device.get('power') is not None and ('Force' in mode) else pv_limit
|
|
1293
|
+
import_limit = None if properties.get('importlimit') is None else 0 if import_limit is None and 'ForceDischarge' in mode else import_limit
|
|
1294
|
+
export_limit = None if properties.get('exportlimit') is None else export_limit
|
|
1295
|
+
reactive_power = None if properties.get('reactivepower') is None else reactive_power
|
|
1295
1296
|
if quiet == 0:
|
|
1296
1297
|
s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
|
|
1297
1298
|
s += f", maxsoc {max_soc}%" if max_soc is not None and 'ForceCharge' in mode else ""
|
|
1298
|
-
s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if ('ForceCharge' in mode or 'ForceDischarge' in mode) else ""
|
|
1299
|
+
s += f", fdPwr {int(fdpwr)}W, fdSoC {fdsoc}%" if ('ForceCharge' in mode or 'ForceDischarge' in mode) else ""
|
|
1300
|
+
s += f", exportLimit {int(export_limit)}W" if export_limit is not None else ""
|
|
1301
|
+
s += f", importLimit {int(import_limit)}W" if import_limit is not None else ""
|
|
1302
|
+
s += f", pvLimit {int(pv_limit)}W" if pv_limit is not None else ""
|
|
1299
1303
|
s += f", {price:.2f}p/kWh" if price is not None else ""
|
|
1300
1304
|
output(s, 1)
|
|
1301
1305
|
start_hour, start_minute = split_hours(start)
|
|
1302
1306
|
end_hour, end_minute = split_hours(end)
|
|
1303
|
-
period = {'
|
|
1304
|
-
|
|
1305
|
-
'
|
|
1307
|
+
period = {'startHour': start_hour, 'startMinute': start_minute, 'endHour': end_hour, 'endMinute': end_minute, 'workMode': mode, 'extraParam': {'minSocOnGrid': min_soc}}
|
|
1308
|
+
if max_soc is not None:
|
|
1309
|
+
period['extraParam']['maxSoc'] = max_soc
|
|
1310
|
+
if fdsoc is not None:
|
|
1311
|
+
period['extraParam']['fdSoc'] = int(fdsoc)
|
|
1312
|
+
if fdpwr is not None:
|
|
1313
|
+
period['extraParam']['fdPwr'] = int(fdpwr)
|
|
1314
|
+
if import_limit is not None:
|
|
1315
|
+
period['extraParam']['importLimit'] = import_limit
|
|
1316
|
+
if export_limit is not None:
|
|
1317
|
+
period['extraParam']['exportLimit'] = export_limit
|
|
1318
|
+
if pv_limit is not None:
|
|
1319
|
+
period['extraParam']['pvLimit'] = pv_limit
|
|
1320
|
+
if reactive_power is not None:
|
|
1321
|
+
period['extraParam']['reactivePower'] = reactive_power
|
|
1306
1322
|
return period
|
|
1307
1323
|
|
|
1308
1324
|
# set a schedule from a period or list of time segment periods
|
|
1309
|
-
def set_schedule(periods=None, enable=True):
|
|
1325
|
+
def set_schedule(periods=None, enable=True, is_default=False):
|
|
1310
1326
|
global device_sn, debug_setting, schedule, max_periods
|
|
1311
1327
|
if get_flag() is None:
|
|
1312
1328
|
return None
|
|
@@ -1319,18 +1335,18 @@ def set_schedule(periods=None, enable=True):
|
|
|
1319
1335
|
return None
|
|
1320
1336
|
if type(enable) is int:
|
|
1321
1337
|
enable = True if enable == 1 else False
|
|
1322
|
-
if enable
|
|
1323
|
-
output(f"\nDisabling schedule", 1)
|
|
1324
|
-
else:
|
|
1338
|
+
if enable:
|
|
1325
1339
|
output(f"\nEnabling schedule", 1)
|
|
1340
|
+
else:
|
|
1341
|
+
output(f"\nDisabling schedule", 1)
|
|
1326
1342
|
if periods is not None:
|
|
1327
1343
|
if type(periods) is not list:
|
|
1328
1344
|
periods = [periods]
|
|
1329
1345
|
if len(periods) > max_periods:
|
|
1330
1346
|
output(f"** set_schedule(): maximum of {max_periods} periods allowed, {len(periods)} provided")
|
|
1331
|
-
body = {'deviceSN': device_sn, 'groups': periods[-max_periods:]}
|
|
1347
|
+
body = {'deviceSN': device_sn, 'isDefault': is_default, 'groups': periods[-max_periods:]}
|
|
1332
1348
|
setting_delay()
|
|
1333
|
-
response = signed_post(path="/op/
|
|
1349
|
+
response = signed_post(path="/op/v3/device/scheduler/enable", body=body)
|
|
1334
1350
|
if response.status_code != 200:
|
|
1335
1351
|
output(f"** set_schedule() periods response code {response.status_code}: {response.reason}")
|
|
1336
1352
|
return None
|
|
@@ -2728,10 +2744,10 @@ def charge_periods(work_mode_timed, base_hour, min_soc, capacity):
|
|
|
2728
2744
|
elif 'ForceCharge' in period['mode']:
|
|
2729
2745
|
s['max_soc'] = period.get('max_soc')
|
|
2730
2746
|
elif period['mode'] == 'SelfUse' and period['hold'] == 1:
|
|
2731
|
-
s['
|
|
2732
|
-
s['
|
|
2733
|
-
|
|
2734
|
-
|
|
2747
|
+
s['mode'] = 'ForceDischarge'
|
|
2748
|
+
s['fdpwr'] = 0
|
|
2749
|
+
s['fdsoc'] = min([int(period['kwh'] / capacity * 100 + 0.5), 100])
|
|
2750
|
+
s['min_soc'] = 10
|
|
2735
2751
|
if s['mode'] != 'SelfUse' or s['min_soc'] != min_soc:
|
|
2736
2752
|
strategy.append(s)
|
|
2737
2753
|
start = h
|
|
@@ -2784,7 +2800,7 @@ charge_config = {
|
|
|
2784
2800
|
'use_today': 21.0, # hour when todays consumption and generation can be used
|
|
2785
2801
|
'min_hours': 0.5, # minimum charge time in decimal hours
|
|
2786
2802
|
'min_kwh': 0.5, # minimum to add in kwh
|
|
2787
|
-
'forecast_selection':
|
|
2803
|
+
'forecast_selection': 0, # 0 = use available forecast / generation, 1 only update settings with forecast
|
|
2788
2804
|
'annual_consumption': None, # optional annual consumption in kWh
|
|
2789
2805
|
'timed_mode': 0, # 0 = None, 1 = timed mode, 2 = strategy mode
|
|
2790
2806
|
'special_contingency': 35, # contingency for special days when consumption might be higher
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: foxesscloud
|
|
3
|
+
Version: 2.9.8
|
|
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>
|
|
@@ -126,9 +140,9 @@ get_peakshaving() will return the current peak shaving settings for inverters th
|
|
|
126
140
|
|
|
127
141
|
get_flag() returns the current scheduler enable / support / maxsoc flags. By default support for Max Soc is set to False.
|
|
128
142
|
+ f.max_periods / schedule['maxGroupCount'] gives the maximum number of time periods that are supported.
|
|
143
|
+
+ f.schedule['properties'] details the parameters supported in Mode Scheduler for the current inverter.
|
|
129
144
|
|
|
130
145
|
get_schedule() returns the current work mode / soc schedule settings. The result is stored in f.schedule.
|
|
131
|
-
+ if the schedule returned contains any values for 'maxSoc', the f.schedule['maxsoc'] is set to True to indicate that the current inverter supports setting Max Soc in schedules and Max Soc values are set by set_schedule().
|
|
132
146
|
|
|
133
147
|
get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
|
|
134
148
|
+ f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
|
|
@@ -143,7 +157,7 @@ You can change inverter settings using:
|
|
|
143
157
|
f.set_min(minSocOnGrid, minSoc)
|
|
144
158
|
f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
|
|
145
159
|
f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
|
|
146
|
-
f.set_schedule(periods, enable)
|
|
160
|
+
f.set_schedule(periods, enable, is_default)
|
|
147
161
|
f.set_named_settings(name, value, force)
|
|
148
162
|
f.set_heating(enable, start, end, time1, time2, time3)
|
|
149
163
|
```
|
|
@@ -168,10 +182,10 @@ set_period() returns a period structure that can be used to build a list for set
|
|
|
168
182
|
+ max_soc: optional, default is 100
|
|
169
183
|
+ fdsoc: optional, default is 10. Used when setting a period with ForceDischarge mode
|
|
170
184
|
+ 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).
|
|
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.
|
|
173
188
|
+ price: optional, default None. Used to display plunge pricing for time period.
|
|
174
|
-
+ enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
|
|
175
189
|
+ segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
|
|
176
190
|
|
|
177
191
|
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.
|
|
@@ -181,6 +195,7 @@ The variable f.max_periods is used to check the number of time periods allowed.
|
|
|
181
195
|
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
|
|
182
196
|
+ periods: a time segment or list of time segments created using f.set_period().
|
|
183
197
|
+ enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
|
|
198
|
+
+ is_default: False (default): parameters not provided remain unchanged. True: parameters not provided are restored to system defaults
|
|
184
199
|
|
|
185
200
|
set_named_settings() sets the 'name' setting to 'value'.
|
|
186
201
|
+ 'name' may also be a list of (name, value) pairs.
|
|
@@ -412,7 +427,7 @@ min_hours: 0.25 # minimum charge time to set (in decimal hours)
|
|
|
412
427
|
min_kwh: 0.5 # minimum charge to add in kwh
|
|
413
428
|
solcast_adjust: 100 # % adjustment to make to Solcast forecast
|
|
414
429
|
solar_adjust: 100 # % adjustment to make to Solar forecast
|
|
415
|
-
forecast_selection:
|
|
430
|
+
forecast_selection: 0 # 1 = only update charge times if forecast is available, 0 = use best available data.
|
|
416
431
|
annual_consumption: None # optional annual consumption in kWh. If set, this replaces consumption history
|
|
417
432
|
timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy mode
|
|
418
433
|
special_contingency: 35 # contingency for special days when consumption might be higher
|
|
@@ -818,13 +833,29 @@ This setting can be:
|
|
|
818
833
|
|
|
819
834
|
# Version Info
|
|
820
835
|
|
|
836
|
+
2.9.8 - 2026/03/22<br>
|
|
837
|
+
** potential breaking changes **
|
|
838
|
+
Update to use Mode Scheduler API v3.
|
|
839
|
+
This adds 'properties' to f.schedule that describes the work modes and parameters supported on different inverters.
|
|
840
|
+
f.work_modes and f.settable_modes are now set dynamically from the properties.
|
|
841
|
+
The period parameters that are sent are trimmed to the supported set and unused parameters for the mode are removed.
|
|
842
|
+
** API v3 rejects unsupported parameters. This limits the testing I can do for different inverters. Please report any issues **
|
|
843
|
+
API v3 removes the previous ability to send disabled time periods. If you try to send a disabled period, set_period() will return None.
|
|
844
|
+
set_period() now supports 'reactive_power' as a parameter.
|
|
845
|
+
set_schedule() now supports 'is_default'. See Open API documentation for more info on what this does.
|
|
846
|
+
charge_needed() changed from using min_soc for battery hold to using ForceDischarge with fdpwr=0 when timed_mode=2.
|
|
847
|
+
charge_needed() default configuration now has forecast_selection=0 so settings will be updated if a forecast is not available.
|
|
848
|
+
|
|
849
|
+
2.9.7 - 2026/03/18<br>
|
|
850
|
+
Add pv_limit to set_period().
|
|
851
|
+
Default import_linit to 0 for force discharge in set_period()
|
|
852
|
+
|
|
821
853
|
2.9.6 - 2026/03/10<br>
|
|
822
854
|
Fix problem setting fdsoc and fdpwr from segment for set_period().
|
|
823
855
|
Correctly identify H1-G2 model inverter.
|
|
824
856
|
Add maxChargeCurrent and maxDischargeCurrent to f.battery.
|
|
825
857
|
Update battery charge derating with temperature so charge current is not 0.
|
|
826
858
|
|
|
827
|
-
|
|
828
859
|
2.9.5 - 2026/02/27<br>
|
|
829
860
|
Add step parameter to date_list().
|
|
830
861
|
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
|