foxesscloud 2.8.5__py3-none-any.whl → 2.8.7__py3-none-any.whl

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,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud
4
- Updated: 21 May 2025
4
+ Updated: 15 September 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.7"
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
@@ -1417,7 +1419,7 @@ def find_template(name):
1417
1419
 
1418
1420
  # create a period structure. Note: end time is exclusive.
1419
1421
  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
1422
+ global schedule, device
1421
1423
  if schedule is None and get_flag() is None:
1422
1424
  return None
1423
1425
  if segment is not None and type(segment) is dict:
@@ -1442,7 +1444,11 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1442
1444
  return None
1443
1445
  min_soc = 10 if min_soc is None else min_soc
1444
1446
  max_soc = None if schedule.get('maxsoc') is None or schedule['maxsoc'] == False else 100 if max_soc is None else max_soc
1447
+ if mode == 'ForceCharge' and fdsoc is None:
1448
+ fdsoc = max_soc if max_soc is not None else 100
1445
1449
  fdsoc = min_soc if fdsoc is None else fdsoc
1450
+ power = (device['power'] * 1000) if device.get('power') is not None else None
1451
+ fdpwr = power if fdpwr is None and power is not None and mode in ['ForceCharge', 'ForceDischarge'] else fdpwr
1446
1452
  fdpwr = 0 if fdpwr is None else fdpwr
1447
1453
  if min_soc < 10 or min_soc > 100:
1448
1454
  output(f"set_period(): ** min_soc must be between 10 and 100")
@@ -1450,8 +1456,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1450
1456
  if max_soc is not None and (max_soc < 10 or max_soc > 100):
1451
1457
  output(f"set_period(): ** max_soc must be between 10 and 100")
1452
1458
  return None
1453
- if fdpwr < 0 or fdpwr > 6000:
1454
- output(f"set_period(): ** fdpwr must be between 0 and 6000")
1459
+ if fdpwr < 0 or fdpwr > 30000:
1460
+ output(f"set_period(): ** fdpwr must be between 0 and 30000")
1455
1461
  return None
1456
1462
  if fdsoc < min_soc or fdsoc > 100:
1457
1463
  output(f"set_period(): ** fdsoc must between {min_soc} and 100")
@@ -1459,7 +1465,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1459
1465
  if quiet == 0:
1460
1466
  s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
1461
1467
  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 ""
1468
+ s += f", fdPwr {fdpwr}W, fdSoC {fdsoc}%" if mode in ['ForceCharge', 'ForceDischarge'] else ""
1463
1469
  s += f", {price:.2f}p/kWh" if price is not None else ""
1464
1470
  output(s, 1)
1465
1471
  start_h, start_m = split_hours(start)
foxesscloud/openapi.py CHANGED
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 28 May 2025
4
+ Updated: 15 September 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.5"
13
+ version = "2.8.7"
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
@@ -950,8 +975,6 @@ def get_work_mode():
950
975
  global work_mode
951
976
  if get_device() is None:
952
977
  return None
953
- # not implemented by Open API, skip to avoid error
954
- return None
955
978
  work_mode = get_named_settings('WorkMode')
956
979
  return work_mode
957
980
 
@@ -1110,9 +1133,9 @@ def build_strategy_from_schedule():
1110
1133
 
1111
1134
  # create time segment structure. Note: end time is exclusive.
1112
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):
1113
- global schedule
1114
- if schedule is None and get_flag() is None:
1115
- return None
1136
+ global schedule, device
1137
+ if schedule is None:
1138
+ get_schedule()
1116
1139
  if segment is not None and type(segment) is dict:
1117
1140
  start = segment.get('start')
1118
1141
  end = segment.get('end')
@@ -1134,8 +1157,12 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1134
1157
  output(f"** mode must be one of {work_modes}")
1135
1158
  return None
1136
1159
  min_soc = 10 if min_soc is None else min_soc
1137
- 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
1138
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
1139
1166
  fdpwr = 0 if fdpwr is None else fdpwr
1140
1167
  if min_soc < 10 or min_soc > 100:
1141
1168
  output(f"set_period(): ** min_soc must be between 10 and 100")
@@ -1143,8 +1170,8 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1143
1170
  if max_soc is not None and (max_soc < 10 or max_soc > 100):
1144
1171
  output(f"set_period(): ** max_soc must be between 10 and 100")
1145
1172
  return None
1146
- if fdpwr < 0 or fdpwr > 6000:
1147
- 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")
1148
1175
  return None
1149
1176
  if fdsoc < min_soc or fdsoc > 100:
1150
1177
  output(f"set_period(): ** fdsoc must between {min_soc} and 100")
@@ -1152,7 +1179,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1152
1179
  if quiet == 0:
1153
1180
  s = f" {hours_time(start)}-{hours_time(end)} {mode}, minsoc {min_soc}%"
1154
1181
  s += f", maxsoc {max_soc}%" if max_soc is not None and mode == 'ForceCharge' else ""
1155
- 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 ""
1156
1183
  s += f", {price:.2f}p/kWh" if price is not None else ""
1157
1184
  output(s, 1)
1158
1185
  start_hour, start_minute = split_hours(start)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.8.5
3
+ Version: 2.8.7
4
4
  Summary: library for accessing Fox ESS cloud data using Open API
5
5
  Author-email: Tony Matthews <tony@quasair.co.uk>
6
6
  Project-URL: Homepage, https://github.com/TonyM1958/FoxESS-Cloud
@@ -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
  ```
@@ -180,16 +183,7 @@ set_period() returns a period structure that can be used to build a list for set
180
183
  + enable: sets whether this time segment is enable (1) or disabled (0). The default is enabled.
181
184
  + segment: optional, allows the parameters for the period to be passed as a dictionary instead of individual values.
182
185
 
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
186
+ 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
187
 
194
188
  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
189
  + periods: a time segment or list of time segments created using f.set_period().
@@ -199,7 +193,7 @@ set_named_settings() sets the 'name' setting to 'value'.
199
193
  + 'name' may also be a list of (name, value) pairs.
200
194
  + 'force': setting to 1 will disable Mode Scheduler, if enabled. Default is 0.
201
195
  + 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
196
+ + named_settings currently supported include: ExportLimit, MinSoc, MinSocOnGrid, MaxSoc, GridCode, WorkMode
203
197
 
204
198
 
205
199
  ## Real Time Data
@@ -826,6 +820,15 @@ This setting can be:
826
820
 
827
821
  # Version Info
828
822
 
823
+ 2.8.7<br>
824
+ Added f.get_signal().
825
+ Updated set_period so you don't have to call get_schedule before.
826
+ Update set_period() to pass fdpwr and fdsoc for ForceCharge and display value.
827
+ Increase max value of fdpwr from 6000 to 30000.
828
+
829
+ 2.8.6<br>
830
+ Update to charge_needed() to get current work mode.
831
+
829
832
  2.8.5<br>
830
833
  Update battery variables to include SOH.
831
834
  Add get_peakshaving().
@@ -0,0 +1,7 @@
1
+ foxesscloud/foxesscloud.py,sha256=fUB4O3p4UvnHAU2YMzUYh5eunMYjqJu9FpIfE9mPtVE,225412
2
+ foxesscloud/openapi.py,sha256=ZpVcrpMU11FI8oAQlrbhoXCiFGP-ooD6me7D37CbkkE,210915
3
+ foxesscloud-2.8.7.dist-info/LICENCE,sha256=8JF-24QkE8UfdII-g6RaIEvM-PZ9zwaEcxlwYUDMt-4,1079
4
+ foxesscloud-2.8.7.dist-info/METADATA,sha256=XqyaBg9PXtXn82IkUJmvRQ59GFOCgUKUyqtxfvfr1xk,65846
5
+ foxesscloud-2.8.7.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
+ foxesscloud-2.8.7.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
+ foxesscloud-2.8.7.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- foxesscloud/foxesscloud.py,sha256=_Su3Dcs0CSM2TlbAmCtouM4LkhYfYZ2Bk70eVgZqBJg,225011
2
- foxesscloud/openapi.py,sha256=fRW2f2kIsvib3Y8reYXIegqQBBkMFgqjHhy1n29uzoY,209773
3
- foxesscloud-2.8.5.dist-info/LICENCE,sha256=8JF-24QkE8UfdII-g6RaIEvM-PZ9zwaEcxlwYUDMt-4,1079
4
- foxesscloud-2.8.5.dist-info/METADATA,sha256=b5LAyooq_E3GPMcDXEwb-ghyxbYOs4Ughah03NH7I78,65861
5
- foxesscloud-2.8.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- foxesscloud-2.8.5.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
- foxesscloud-2.8.5.dist-info/RECORD,,