foxesscloud 2.8.6__tar.gz → 2.8.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.8.6
3
+ Version: 2.8.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
@@ -63,13 +63,14 @@ For example, replace _my.fox_api_key_ with the API key. Add you inverter serial
63
63
  Residual handling configures how battery residual energy reported by Fox is handled:
64
64
  + 1: Fox returns the current battery residual energy and battery capacity is calculated using soc
65
65
  + 2: Fox returns the current battery capacity and battery residual is calculated using soc
66
+ + 3: Fox returns the residual capacity per battery (Mira)
66
67
 
67
68
  If a value is set for f.plot_file, any charts created will also be saved to an image file:
68
69
  + f.plot_file: the file name to use. The file extension determines the format - .png, .pdf or .svg. If you provide just a filename, each chart will over-write the file. The default is None and disables saving.
69
70
  + f.plot_no: if the file name contains ###, this will be replaced by 3 digit plot number that increases for each chart created. The default is 0.
70
71
  + f.plot_dpi: sets the image resolution. The default is 150. Reducing this value produces smaller, lower resolution images. Increasing this value produces larger, highe resolution images
71
72
 
72
- If you set f.pushover_user_key to your user_key for pushover.net, a summary from set_tariff(), charge_needed(), set_pvoutput() and battery_info() will be sent to your pushover app.
73
+ If you set f.pushover_user_key to your user_key for [pushover.net](https://pushover.net), a summary from set_tariff(), charge_needed(), set_pvoutput() and battery_info() will be sent to your pushover app.
73
74
 
74
75
  You can set 'f.storage' to a path to save files to a different location such as cloud storage. The default is to use the current working directory.
75
76
 
@@ -90,6 +91,7 @@ Load information about a site, data logger or inverter (device):
90
91
  ```
91
92
  f.get_site()
92
93
  f.get_logger()
94
+ f.get_signal()
93
95
  f.get_device()
94
96
  ```
95
97
 
@@ -98,7 +100,9 @@ By default, this will load the first item in the list provided by the cloud. If
98
100
  + Logger: full or partial serial number
99
101
  + Inverter: full or partial serial number
100
102
 
101
- When an item is selected, the functions returns a dictionary containing item details and saves these to a global variable (f.site, f.logger, f.device respectively)
103
+ When an item is selected, the functions returns a dictionary containing item details and saves these to a global variable (f.site, f.logger, f.device respectively).
104
+
105
+ get_signal() is ancillary to get_logger() and returns the current data logger signal strength and time stamp.
102
106
 
103
107
  Once an inverter is selected, you can make other calls to get information:
104
108
 
@@ -142,7 +146,7 @@ get_schedule() returns the current work mode / soc schedule settings. The result
142
146
 
143
147
  get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
144
148
  + f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
145
- + named_settings current supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode
149
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
146
150
 
147
151
 
148
152
  ## Inverter Settings
@@ -152,7 +156,6 @@ You can change inverter settings using:
152
156
  f.set_min(minSocOnGrid, minSoc)
153
157
  f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
154
158
  f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
155
- f.charge_periods(st0, en0, st1, en1, st2, en2, min_soc, target_soc, start_soc)
156
159
  f.set_schedule(periods, enable)
157
160
  f.set_named_settings(name, value, force)
158
161
  ```
@@ -160,6 +163,7 @@ f.set_named_settings(name, value, force)
160
163
  set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
161
164
  + minSocOnGrid: min Soc on Grid setting e.g. 15 = 15%
162
165
  + minSoc: min Soc setting e.g. 10 = 10%
166
+ + force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
163
167
 
164
168
  set_charge() takes the charge times from the battery_settings and applies these to the inverter. The parameters are optional and will update battery_settings. You should specify all 3 parameter for a time period:
165
169
  + ch1: enable charge from grid for period 1 (default True)
@@ -180,16 +184,7 @@ set_period() returns a period structure that can be used to build a list for set
180
184
  + enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
181
185
  + segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
182
186
 
183
- charge_periods(): returns a list of periods that describe the strategy for the current tariff and adds the periods required for charging:
184
- + st0: the start time for period 0 when you don't want the battery to discharge before charging
185
- + en0: the end time for period 0
186
- + st1: the start time for the period when the battery charges from the grid
187
- + en1: the end time for period 1
188
- + st2: the start time for period 2 when you don't want the batteru to discharge after charging
189
- + en2: the end time for period 2
190
- + min_soc: the min_soc to use when building the strategy
191
- + start_soc: the min_soc to use for period 0
192
- + target_soc: the max_soc to set during period 1 and min_soc to use for period 2
187
+ 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.
193
188
 
194
189
  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
195
190
  + periods: a time segment or list of time segments created using f.set_period().
@@ -197,9 +192,9 @@ set_schedule() configures a list of scheduled work mode / soc changes with enabl
197
192
 
198
193
  set_named_settings() sets the 'name' setting to 'value'.
199
194
  + 'name' may also be a list of (name, value) pairs.
200
- + 'force': setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
195
+ + force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
201
196
  + a return value of 1 is success. 0 means setting failed. None is another error e.g. device not found, invalid name or value.
202
- + named_settings current supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode
197
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
203
198
 
204
199
 
205
200
  ## Real Time Data
@@ -826,7 +821,18 @@ This setting can be:
826
821
 
827
822
  # Version Info
828
823
 
829
- 2.8.6<br>#
824
+ 2.8.8<br>
825
+ Fix problem where Open API returns conflicting variable names when reporting stats by year.
826
+ Update daily template for Saturn Cloud to new YAML format.
827
+ Update set_min() to accept 0 instead of 10.
828
+
829
+ 2.8.7<br>
830
+ Added f.get_signal().
831
+ Updated set_period so you don't have to call get_schedule before.
832
+ Update set_period() to pass fdpwr and fdsoc for ForceCharge and display value.
833
+ Increase max value of fdpwr from 6000 to 30000.
834
+
835
+ 2.8.6<br>
830
836
  Update to charge_needed() to get current work mode.
831
837
 
832
838
  2.8.5<br>
@@ -49,13 +49,14 @@ For example, replace _my.fox_api_key_ with the API key. Add you inverter serial
49
49
  Residual handling configures how battery residual energy reported by Fox is handled:
50
50
  + 1: Fox returns the current battery residual energy and battery capacity is calculated using soc
51
51
  + 2: Fox returns the current battery capacity and battery residual is calculated using soc
52
+ + 3: Fox returns the residual capacity per battery (Mira)
52
53
 
53
54
  If a value is set for f.plot_file, any charts created will also be saved to an image file:
54
55
  + f.plot_file: the file name to use. The file extension determines the format - .png, .pdf or .svg. If you provide just a filename, each chart will over-write the file. The default is None and disables saving.
55
56
  + f.plot_no: if the file name contains ###, this will be replaced by 3 digit plot number that increases for each chart created. The default is 0.
56
57
  + f.plot_dpi: sets the image resolution. The default is 150. Reducing this value produces smaller, lower resolution images. Increasing this value produces larger, highe resolution images
57
58
 
58
- If you set f.pushover_user_key to your user_key for pushover.net, a summary from set_tariff(), charge_needed(), set_pvoutput() and battery_info() will be sent to your pushover app.
59
+ If you set f.pushover_user_key to your user_key for [pushover.net](https://pushover.net), a summary from set_tariff(), charge_needed(), set_pvoutput() and battery_info() will be sent to your pushover app.
59
60
 
60
61
  You can set 'f.storage' to a path to save files to a different location such as cloud storage. The default is to use the current working directory.
61
62
 
@@ -76,6 +77,7 @@ Load information about a site, data logger or inverter (device):
76
77
  ```
77
78
  f.get_site()
78
79
  f.get_logger()
80
+ f.get_signal()
79
81
  f.get_device()
80
82
  ```
81
83
 
@@ -84,7 +86,9 @@ By default, this will load the first item in the list provided by the cloud. If
84
86
  + Logger: full or partial serial number
85
87
  + Inverter: full or partial serial number
86
88
 
87
- When an item is selected, the functions returns a dictionary containing item details and saves these to a global variable (f.site, f.logger, f.device respectively)
89
+ When an item is selected, the functions returns a dictionary containing item details and saves these to a global variable (f.site, f.logger, f.device respectively).
90
+
91
+ get_signal() is ancillary to get_logger() and returns the current data logger signal strength and time stamp.
88
92
 
89
93
  Once an inverter is selected, you can make other calls to get information:
90
94
 
@@ -128,7 +132,7 @@ get_schedule() returns the current work mode / soc schedule settings. The result
128
132
 
129
133
  get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
130
134
  + f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
131
- + named_settings current supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode
135
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
132
136
 
133
137
 
134
138
  ## Inverter Settings
@@ -138,7 +142,6 @@ You can change inverter settings using:
138
142
  f.set_min(minSocOnGrid, minSoc)
139
143
  f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
140
144
  f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
141
- f.charge_periods(st0, en0, st1, en1, st2, en2, min_soc, target_soc, start_soc)
142
145
  f.set_schedule(periods, enable)
143
146
  f.set_named_settings(name, value, force)
144
147
  ```
@@ -146,6 +149,7 @@ f.set_named_settings(name, value, force)
146
149
  set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
147
150
  + minSocOnGrid: min Soc on Grid setting e.g. 15 = 15%
148
151
  + minSoc: min Soc setting e.g. 10 = 10%
152
+ + force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
149
153
 
150
154
  set_charge() takes the charge times from the battery_settings and applies these to the inverter. The parameters are optional and will update battery_settings. You should specify all 3 parameter for a time period:
151
155
  + ch1: enable charge from grid for period 1 (default True)
@@ -166,16 +170,7 @@ set_period() returns a period structure that can be used to build a list for set
166
170
  + enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
167
171
  + segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
168
172
 
169
- charge_periods(): returns a list of periods that describe the strategy for the current tariff and adds the periods required for charging:
170
- + st0: the start time for period 0 when you don't want the battery to discharge before charging
171
- + en0: the end time for period 0
172
- + st1: the start time for the period when the battery charges from the grid
173
- + en1: the end time for period 1
174
- + st2: the start time for period 2 when you don't want the batteru to discharge after charging
175
- + en2: the end time for period 2
176
- + min_soc: the min_soc to use when building the strategy
177
- + start_soc: the min_soc to use for period 0
178
- + target_soc: the max_soc to set during period 1 and min_soc to use for period 2
173
+ 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.
179
174
 
180
175
  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
181
176
  + periods: a time segment or list of time segments created using f.set_period().
@@ -183,9 +178,9 @@ set_schedule() configures a list of scheduled work mode / soc changes with enabl
183
178
 
184
179
  set_named_settings() sets the 'name' setting to 'value'.
185
180
  + 'name' may also be a list of (name, value) pairs.
186
- + 'force': setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
181
+ + force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
187
182
  + a return value of 1 is success. 0 means setting failed. None is another error e.g. device not found, invalid name or value.
188
- + named_settings current supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode
183
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
189
184
 
190
185
 
191
186
  ## Real Time Data
@@ -812,7 +807,18 @@ This setting can be:
812
807
 
813
808
  # Version Info
814
809
 
815
- 2.8.6<br>#
810
+ 2.8.8<br>
811
+ Fix problem where Open API returns conflicting variable names when reporting stats by year.
812
+ Update daily template for Saturn Cloud to new YAML format.
813
+ Update set_min() to accept 0 instead of 10.
814
+
815
+ 2.8.7<br>
816
+ Added f.get_signal().
817
+ Updated set_period so you don't have to call get_schedule before.
818
+ Update set_period() to pass fdpwr and fdsoc for ForceCharge and display value.
819
+ Increase max value of fdpwr from 6000 to 30000.
820
+
821
+ 2.8.6<br>
816
822
  Update to charge_needed() to get current work mode.
817
823
 
818
824
  2.8.5<br>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "foxesscloud"
7
- version = "2.8.6"
7
+ version = "2.8.8"
8
8
  authors = [
9
9
  {name="Tony Matthews", email="tony@quasair.co.uk"},
10
10
  ]
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud
4
- Updated: 21 May 2025
4
+ Updated: 2 October 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 2023
11
11
  ##################################################################################################
12
12
 
13
- version = "1.9.6"
13
+ version = "1.9.8"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -653,6 +653,8 @@ def get_battery(info=1, rated=None, count=None):
653
653
  residual_handling = 2
654
654
  elif battery['info']['masterSN'][:7] == '60MBB01' and battery['info']['masterVersion'] >= '1.014':
655
655
  residual_handling = 3
656
+ if debug_setting > 1:
657
+ print(f"raw battery = {battery}")
656
658
  battery['residual_handling'] = residual_handling
657
659
  battery['soh'] = None
658
660
  battery['soh_supported'] = False
@@ -910,14 +912,14 @@ def get_min():
910
912
  output(f"** get_min(), no result data, {errno_message(errno)}")
911
913
  return None
912
914
  battery_settings['minSoc'] = result.get('minSoc')
913
- battery_settings['minGridSoc'] = result.get('minGridSoc')
915
+ battery_settings['minSocOnGrid'] = result.get('minGridSoc')
914
916
  return battery_settings
915
917
 
916
918
  ##################################################################################################
917
919
  # set min soc from battery_settings or parameters
918
920
  ##################################################################################################
919
921
 
920
- def set_min(minGridSoc = None, minSoc = None, force = 0):
922
+ def set_min(minSocOnGrid = None, minSoc = None, force = 0):
921
923
  global device_sn, battery_settings, debug_setting, messages
922
924
  if get_device() is None:
923
925
  return None
@@ -926,16 +928,24 @@ def set_min(minGridSoc = None, minSoc = None, force = 0):
926
928
  output(f"** set_min(): cannot set min SoC mode when a schedule is enabled")
927
929
  return None
928
930
  set_schedule(enable=0)
929
- data = {'sn': device_sn}
930
931
  if battery_settings is None:
931
932
  battery_settings = {}
932
- if minGridSoc is not None:
933
- data['minGridSoc'] = minGridSoc
934
- battery_settings['minGridSoc'] = minGridSoc
933
+ if minSocOnGrid is not None:
934
+ if minSocOnGrid < 0 or minSocOnGrid > 100:
935
+ output(f"** set_min(): invalid minSocOnGrid = {minSocOnGrid}. Must be between 0 and 100")
936
+ return None
937
+ battery_settings['minSocOnGrid'] = minSocOnGrid
935
938
  if minSoc is not None:
936
- data['minSoc'] = minSoc
939
+ if minSoc < 0 or minSoc > 100:
940
+ output(f"** set_min(): invalid minSoc = {minSoc}. Must be between 0 and 100")
941
+ return None
937
942
  battery_settings['minSoc'] = minSoc
938
- output(f"\nSetting minSoc = {battery_settings.get('minSoc')}, minGridSoc = {battery_settings.get('minGridSoc')}", 1)
943
+ data = {'sn': device_sn}
944
+ if battery_settings.get('minSocOnGrid') is not None:
945
+ data['minGridSoc'] = battery_settings['minSocOnGrid']
946
+ if battery_settings.get('minSoc') is not None:
947
+ data['minSoc'] = battery_settings['minSoc']
948
+ output(f"\nSetting minSocOnGrid = {battery_settings.get('minSocOnGrid')}, minSoc = {battery_settings.get('minSoc')}", 1)
939
949
  setting_delay()
940
950
  response = signed_post(path="/c/v0/device/battery/soc/set", data=data)
941
951
  if response.status_code != 200:
@@ -1417,7 +1427,7 @@ def find_template(name):
1417
1427
 
1418
1428
  # create a period structure. Note: end time is exclusive.
1419
1429
  def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdsoc=None, fdpwr=None, price=None, segment=None, quiet=1):
1420
- global schedule
1430
+ global schedule, device
1421
1431
  if schedule is None and get_flag() is None:
1422
1432
  return None
1423
1433
  if segment is not None and type(segment) is dict:
@@ -1442,7 +1452,11 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1442
1452
  return None
1443
1453
  min_soc = 10 if min_soc is None else min_soc
1444
1454
  max_soc = None if schedule.get('maxsoc') is None or schedule['maxsoc'] == False else 100 if max_soc is None else max_soc
1455
+ if mode == 'ForceCharge' and fdsoc is None:
1456
+ fdsoc = max_soc if max_soc is not None else 100
1445
1457
  fdsoc = min_soc if fdsoc is None else fdsoc
1458
+ power = (device['power'] * 1000) if device.get('power') is not None else None
1459
+ fdpwr = power if fdpwr is None and power is not None and mode in ['ForceCharge', 'ForceDischarge'] else fdpwr
1446
1460
  fdpwr = 0 if fdpwr is None else fdpwr
1447
1461
  if min_soc < 10 or min_soc > 100:
1448
1462
  output(f"set_period(): ** min_soc must be between 10 and 100")
@@ -1450,8 +1464,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1450
1464
  if max_soc is not None and (max_soc < 10 or max_soc > 100):
1451
1465
  output(f"set_period(): ** max_soc must be between 10 and 100")
1452
1466
  return None
1453
- if fdpwr < 0 or fdpwr > 6000:
1454
- output(f"set_period(): ** fdpwr must be between 0 and 6000")
1467
+ if fdpwr < 0 or fdpwr > 30000:
1468
+ output(f"set_period(): ** fdpwr must be between 0 and 30000")
1455
1469
  return None
1456
1470
  if fdsoc < min_soc or fdsoc > 100:
1457
1471
  output(f"set_period(): ** fdsoc must between {min_soc} and 100")
@@ -1459,7 +1473,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1459
1473
  if quiet == 0:
1460
1474
  s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
1461
1475
  s += f", maxsoc {max_soc}%" if max_soc is not None and mode == 'ForceCharge' else ""
1462
- s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if mode == 'ForceDischarge' else ""
1476
+ s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if mode in ['ForceCharge', 'ForceDischarge'] else ""
1463
1477
  s += f", {price:.2f}p/kWh" if price is not None else ""
1464
1478
  output(s, 1)
1465
1479
  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: 5 July 2025
4
+ Updated: 2 October 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.8.6"
13
+ version = "2.8.8"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -371,9 +371,10 @@ def get_site(name=None):
371
371
 
372
372
  logger_list = None
373
373
  logger = None
374
+ logger_sn = None
374
375
 
375
376
  def get_logger(sn=None):
376
- global logger_list, logger, debug_setting
377
+ global logger_list, logger, logger_sn, debug_setting
377
378
  if get_vars() is None:
378
379
  return None
379
380
  if logger is not None and sn is None:
@@ -408,8 +409,30 @@ def get_logger(sn=None):
408
409
  else:
409
410
  n = 0
410
411
  logger = logger_list[n]
412
+ logger_sn = logger.get('moduleSN')
411
413
  return logger
412
414
 
415
+ def get_signal(sn=None):
416
+ global logger_list, logger, logger_sn, debug_setting
417
+ if get_vars() is None:
418
+ return None
419
+ if sn is None:
420
+ if logger_sn is None:
421
+ get_logger()
422
+ sn = logger_sn
423
+ if sn is None:
424
+ return None
425
+ output(f"getting signal", 2)
426
+ body = {'sn': sn}
427
+ response = signed_post(path="/op/v0/module/getSignal", body=body)
428
+ if response.status_code != 200:
429
+ output(f"** get_signal() got response code {response.status_code}: {response.reason}")
430
+ return None
431
+ result = response.json().get('result')
432
+ if result is None:
433
+ output(f"** get_signal(), no result data, {errno_message(response)}")
434
+ return None
435
+ return result
413
436
 
414
437
  ##################################################################################################
415
438
  # get list of devices and select one, using the serial number if there is more than 1
@@ -593,6 +616,8 @@ def get_battery(info=0, v=None, rated=None, count=None):
593
616
  battery = {}
594
617
  for i in range(0, len(battery_vars)):
595
618
  battery[battery_data[i]] = result[i].get('value')
619
+ if debug_setting > 1:
620
+ print(f"raw battery = {battery}")
596
621
  battery['residual_handling'] = residual_handling
597
622
  battery['soh'] = None
598
623
  battery['soh_supported'] = False
@@ -798,13 +823,13 @@ def set_min(minSocOnGrid = None, minSoc = None, force = 0):
798
823
  if battery_settings is None:
799
824
  battery_settings = {}
800
825
  if minSocOnGrid is not None:
801
- if minSocOnGrid < 10 or minSocOnGrid > 100:
802
- output(f"** set_min(): invalid minSocOnGrid = {minSocOnGrid}. Must be between 10 and 100")
826
+ if minSocOnGrid < 0 or minSocOnGrid > 100:
827
+ output(f"** set_min(): invalid minSocOnGrid = {minSocOnGrid}. Must be between 0 and 100")
803
828
  return None
804
829
  battery_settings['minSocOnGrid'] = minSocOnGrid
805
830
  if minSoc is not None:
806
- if minSoc < 10 or minSoc > 100:
807
- output(f"** set_min(): invalid minSoc = {minSoc}. Must be between 10 and 100")
831
+ if minSoc < 0 or minSoc > 100:
832
+ output(f"** set_min(): invalid minSoc = {minSoc}. Must be between 0 and 100")
808
833
  return None
809
834
  battery_settings['minSoc'] = minSoc
810
835
  body = {'sn': device_sn}
@@ -812,7 +837,7 @@ def set_min(minSocOnGrid = None, minSoc = None, force = 0):
812
837
  body['minSocOnGrid'] = battery_settings['minSocOnGrid']
813
838
  if battery_settings.get('minSoc') is not None:
814
839
  body['minSoc'] = battery_settings['minSoc']
815
- output(f"\nSetting minSoc = {battery_settings.get('minSoc')}, minSocOnGrid = {battery_settings.get('minSocOnGrid')}", 1)
840
+ output(f"\nSetting minSocOnGrid = {battery_settings.get('minSocOnGrid')}, minSoc = {battery_settings.get('minSoc')}", 1)
816
841
  setting_delay()
817
842
  response = signed_post(path="/op/v0/device/battery/soc/set", body=body)
818
843
  if response.status_code != 200:
@@ -1108,9 +1133,9 @@ def build_strategy_from_schedule():
1108
1133
 
1109
1134
  # create time segment structure. Note: end time is exclusive.
1110
1135
  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):
1111
- global schedule
1112
- if schedule is None and get_flag() is None:
1113
- return None
1136
+ global schedule, device
1137
+ if schedule is None:
1138
+ get_schedule()
1114
1139
  if segment is not None and type(segment) is dict:
1115
1140
  start = segment.get('start')
1116
1141
  end = segment.get('end')
@@ -1132,8 +1157,12 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1132
1157
  output(f"** mode must be one of {work_modes}")
1133
1158
  return None
1134
1159
  min_soc = 10 if min_soc is None else min_soc
1135
- max_soc = None if schedule.get('maxsoc') is None or schedule['maxsoc'] == False else 100 if max_soc is None else max_soc
1160
+ max_soc = None if schedule is None or schedule.get('maxsoc') is None or schedule['maxsoc'] == False else 100 if max_soc is None else max_soc
1161
+ if mode == 'ForceCharge' and fdsoc is None:
1162
+ fdsoc = max_soc if max_soc is not None else 100
1136
1163
  fdsoc = min_soc if fdsoc is None else fdsoc
1164
+ power = (device['power'] * 1000) if device.get('power') is not None else None
1165
+ fdpwr = power if fdpwr is None and device.get('power') is not None and mode in ['ForceCharge', 'ForceDischarge'] else fdpwr
1137
1166
  fdpwr = 0 if fdpwr is None else fdpwr
1138
1167
  if min_soc < 10 or min_soc > 100:
1139
1168
  output(f"set_period(): ** min_soc must be between 10 and 100")
@@ -1141,8 +1170,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1141
1170
  if max_soc is not None and (max_soc < 10 or max_soc > 100):
1142
1171
  output(f"set_period(): ** max_soc must be between 10 and 100")
1143
1172
  return None
1144
- if fdpwr < 0 or fdpwr > 6000:
1145
- output(f"set_period(): ** fdpwr must be between 0 and 6000")
1173
+ if fdpwr < 0 or fdpwr > 30000:
1174
+ output(f"set_period(): ** fdpwr must be between 0 and 30000")
1146
1175
  return None
1147
1176
  if fdsoc < min_soc or fdsoc > 100:
1148
1177
  output(f"set_period(): ** fdsoc must between {min_soc} and 100")
@@ -1150,7 +1179,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1150
1179
  if quiet == 0:
1151
1180
  s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
1152
1181
  s += f", maxsoc {max_soc}%" if max_soc is not None and mode == 'ForceCharge' else ""
1153
- s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if mode == 'ForceDischarge' else ""
1182
+ s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if mode in ['ForceCharge', 'ForceDischarge'] else ""
1154
1183
  s += f", {price:.2f}p/kWh" if price is not None else ""
1155
1184
  output(s, 1)
1156
1185
  start_hour, start_minute = split_hours(start)
@@ -1636,6 +1665,10 @@ def get_report(dimension='day', d=None, v=None, summary=1, save=None, load=None,
1636
1665
  if errno > 0 or result is None or len(result) == 0:
1637
1666
  output(f"** get_report(), no report data available, {errno_message(response)}")
1638
1667
  return None
1668
+ # correct variables in year report (AP 19/09/2025):
1669
+ if dimension == 'year':
1670
+ for i, var in enumerate(result):
1671
+ var['variable'] = v[i]
1639
1672
  # correct errors in report values:
1640
1673
  if fix_values == 1:
1641
1674
  for var in result:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.8.6
3
+ Version: 2.8.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
@@ -63,13 +63,14 @@ For example, replace _my.fox_api_key_ with the API key. Add you inverter serial
63
63
  Residual handling configures how battery residual energy reported by Fox is handled:
64
64
  + 1: Fox returns the current battery residual energy and battery capacity is calculated using soc
65
65
  + 2: Fox returns the current battery capacity and battery residual is calculated using soc
66
+ + 3: Fox returns the residual capacity per battery (Mira)
66
67
 
67
68
  If a value is set for f.plot_file, any charts created will also be saved to an image file:
68
69
  + f.plot_file: the file name to use. The file extension determines the format - .png, .pdf or .svg. If you provide just a filename, each chart will over-write the file. The default is None and disables saving.
69
70
  + f.plot_no: if the file name contains ###, this will be replaced by 3 digit plot number that increases for each chart created. The default is 0.
70
71
  + f.plot_dpi: sets the image resolution. The default is 150. Reducing this value produces smaller, lower resolution images. Increasing this value produces larger, highe resolution images
71
72
 
72
- If you set f.pushover_user_key to your user_key for pushover.net, a summary from set_tariff(), charge_needed(), set_pvoutput() and battery_info() will be sent to your pushover app.
73
+ If you set f.pushover_user_key to your user_key for [pushover.net](https://pushover.net), a summary from set_tariff(), charge_needed(), set_pvoutput() and battery_info() will be sent to your pushover app.
73
74
 
74
75
  You can set 'f.storage' to a path to save files to a different location such as cloud storage. The default is to use the current working directory.
75
76
 
@@ -90,6 +91,7 @@ Load information about a site, data logger or inverter (device):
90
91
  ```
91
92
  f.get_site()
92
93
  f.get_logger()
94
+ f.get_signal()
93
95
  f.get_device()
94
96
  ```
95
97
 
@@ -98,7 +100,9 @@ By default, this will load the first item in the list provided by the cloud. If
98
100
  + Logger: full or partial serial number
99
101
  + Inverter: full or partial serial number
100
102
 
101
- When an item is selected, the functions returns a dictionary containing item details and saves these to a global variable (f.site, f.logger, f.device respectively)
103
+ When an item is selected, the functions returns a dictionary containing item details and saves these to a global variable (f.site, f.logger, f.device respectively).
104
+
105
+ get_signal() is ancillary to get_logger() and returns the current data logger signal strength and time stamp.
102
106
 
103
107
  Once an inverter is selected, you can make other calls to get information:
104
108
 
@@ -142,7 +146,7 @@ get_schedule() returns the current work mode / soc schedule settings. The result
142
146
 
143
147
  get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
144
148
  + f.named_settings is updated. This is dictionary of information and current value, indexed by 'name'.
145
- + named_settings current supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode
149
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
146
150
 
147
151
 
148
152
  ## Inverter Settings
@@ -152,7 +156,6 @@ You can change inverter settings using:
152
156
  f.set_min(minSocOnGrid, minSoc)
153
157
  f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
154
158
  f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
155
- f.charge_periods(st0, en0, st1, en1, st2, en2, min_soc, target_soc, start_soc)
156
159
  f.set_schedule(periods, enable)
157
160
  f.set_named_settings(name, value, force)
158
161
  ```
@@ -160,6 +163,7 @@ f.set_named_settings(name, value, force)
160
163
  set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
161
164
  + minSocOnGrid: min Soc on Grid setting e.g. 15 = 15%
162
165
  + minSoc: min Soc setting e.g. 10 = 10%
166
+ + force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
163
167
 
164
168
  set_charge() takes the charge times from the battery_settings and applies these to the inverter. The parameters are optional and will update battery_settings. You should specify all 3 parameter for a time period:
165
169
  + ch1: enable charge from grid for period 1 (default True)
@@ -180,16 +184,7 @@ set_period() returns a period structure that can be used to build a list for set
180
184
  + enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
181
185
  + segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
182
186
 
183
- charge_periods(): returns a list of periods that describe the strategy for the current tariff and adds the periods required for charging:
184
- + st0: the start time for period 0 when you don't want the battery to discharge before charging
185
- + en0: the end time for period 0
186
- + st1: the start time for the period when the battery charges from the grid
187
- + en1: the end time for period 1
188
- + st2: the start time for period 2 when you don't want the batteru to discharge after charging
189
- + en2: the end time for period 2
190
- + min_soc: the min_soc to use when building the strategy
191
- + start_soc: the min_soc to use for period 0
192
- + target_soc: the max_soc to set during period 1 and min_soc to use for period 2
187
+ 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.
193
188
 
194
189
  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
195
190
  + periods: a time segment or list of time segments created using f.set_period().
@@ -197,9 +192,9 @@ set_schedule() configures a list of scheduled work mode / soc changes with enabl
197
192
 
198
193
  set_named_settings() sets the 'name' setting to 'value'.
199
194
  + 'name' may also be a list of (name, value) pairs.
200
- + 'force': setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
195
+ + force: setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
201
196
  + a return value of 1 is success. 0 means setting failed. None is another error e.g. device not found, invalid name or value.
202
- + named_settings current supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode
197
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
203
198
 
204
199
 
205
200
  ## Real Time Data
@@ -826,7 +821,18 @@ This setting can be:
826
821
 
827
822
  # Version Info
828
823
 
829
- 2.8.6<br>#
824
+ 2.8.8<br>
825
+ Fix problem where Open API returns conflicting variable names when reporting stats by year.
826
+ Update daily template for Saturn Cloud to new YAML format.
827
+ Update set_min() to accept 0 instead of 10.
828
+
829
+ 2.8.7<br>
830
+ Added f.get_signal().
831
+ Updated set_period so you don't have to call get_schedule before.
832
+ Update set_period() to pass fdpwr and fdsoc for ForceCharge and display value.
833
+ Increase max value of fdpwr from 6000 to 30000.
834
+
835
+ 2.8.6<br>
830
836
  Update to charge_needed() to get current work mode.
831
837
 
832
838
  2.8.5<br>
File without changes
File without changes