foxesscloud 2.6.3__py3-none-any.whl → 2.6.5__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: 13 October 2024
4
+ Updated: 43 October 2024
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.7.5"
13
+ version = "1.7.7"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -593,13 +593,13 @@ battery_params = {
593
593
  1: {'table': [ 0, 2, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 0],
594
594
  'step': 5,
595
595
  'offset': 5,
596
- 'charge_loss': 0.975,
597
- 'discharge_loss': 0.975},
596
+ 'charge_loss': 0.974,
597
+ 'discharge_loss': 0.974},
598
598
  2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
599
599
  'step': 5,
600
600
  'offset': 5,
601
601
  'charge_loss': 1.08,
602
- 'discharge_loss': 0.85},
602
+ 'discharge_loss': 0.95},
603
603
  }
604
604
 
605
605
  def get_battery(info=1):
@@ -690,29 +690,26 @@ def get_batteries(info=1):
690
690
  b['residual_handling'] = residual_handling
691
691
  if b.get('info') is not None and b['info']['masterVersion'] >= '1.014' and b['info']['masterSN'][:7] == '60BBHV2':
692
692
  b['residual_handling'] = 2
693
- if b.get('soh') is not None and b['soh'].isnumeric():
694
- b['soh'] = int(b['soh'])
695
- b['soh_supported'] = True
696
- else:
697
- b['rated_capacity'] = None
698
- b['soh'] = None
699
- b['soh_supported'] = False
693
+ rated_capacity = b.get('ratedCapacity')
694
+ b['ratedCapacity'] = rated_capacity if rated_capacity is not None and rated_capacity > 100 else None
695
+ soh = b.get('soh')
696
+ b['soh'] = int(soh) if soh.isnumeric() and int(soh) > 10 else None
697
+ b['soh_supported'] = b['soh'] is not None
700
698
  for i, b in enumerate(batteries):
701
699
  if i == 0:
702
700
  residual_handling = b['residual_handling']
703
701
  get_battery(info=0)
704
- battery['ratedCapacity'] = b.get('ratedCapacity')
705
702
  b['capacity'] = battery.get('capacity')
706
703
  b['residual'] = battery.get('residual')
707
704
  b['soc'] = battery.get('soc')
708
- if b.get('capacity') is None and b.get('soh') is not None:
709
- capacity = (b['ratedCapacity'] / 1000 * b['soh'] / 100)
705
+ if b.get('capacity') is None and b.get('ratedCapacity') is not None and b.get('soh') is not None:
706
+ b['capacity'] = round(b['ratedCapacity'] / 1000 * b['soh'] / 100, 3)
707
+ if b.get('residual') is None and b.get('capacity') is not None and b.get('soc') is not None:
710
708
  soc = b.get('soc')
711
- residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
712
- b['capacity'] = round(capacity, 3)
709
+ residual = b['capacity'] * b['soc'] / 100
713
710
  b['residual'] = round(residual, 3)
714
711
  if b.get('capacity') is not None and b.get('ratedCapacity') is not None:
715
- b['soh'] = round(b['capacity'] / b['ratedCapacity'] * 1000 * 100, 1)
712
+ b['soh'] = round(b['capacity'] * 1000 / b['ratedCapacity'] * 100, 1)
716
713
  b['charge_rate'] = 50
717
714
  params = battery_params[b['residual_handling']]
718
715
  b['charge_loss'] = params['charge_loss']
@@ -1025,7 +1022,7 @@ def get_remote_settings(key):
1025
1022
  result = response.json().get('result')
1026
1023
  if result is None:
1027
1024
  errno = response.json().get('errno')
1028
- output(f"** get_remote_settings(), no result data, {errno_message(errno)}")
1025
+ output(f"** get_remote_settings(), no result data, {errno_message(response)}")
1029
1026
  return None
1030
1027
  values = result.get('values')
1031
1028
  if values is None:
@@ -1439,7 +1436,7 @@ def set_schedule(periods=None, template=None, enable=True):
1439
1436
  # d = day 'YYYY-MM-DD'. Can also include 'HH:MM' in 'hour' mode
1440
1437
  # v = list of variables to get
1441
1438
  # summary = 0: raw data, 1: add max, min, sum, 2: summarise and drop raw data, 3: calculate state
1442
- # save = "xxxxx": save the raw results to xxxxx_raw_<time_span>_<d>.json
1439
+ # save = "xxxxx": save the raw results to xxxxx_history_<time_span>_<d>.json
1443
1440
  # load = "<file>": load the raw results from <file>
1444
1441
  # plot = 0: no plot, 1: plot variables separately, 2: combine variables
1445
1442
  # station = 0: use device_id, 1: use station_id
@@ -1507,7 +1504,7 @@ def get_raw(time_span='hour', d=None, v=None, summary=1, save=None, load=None, p
1507
1504
  result = json.load(file)
1508
1505
  file.close()
1509
1506
  if save is not None:
1510
- file_name = save + "_raw_" + time_span + "_" + d[0:10].replace('-','') + ".txt"
1507
+ file_name = save + "_history_" + time_span + "_" + d[0:10].replace('-','') + ".txt"
1511
1508
  file = open(storage + file_name, 'w', encoding='utf-8')
1512
1509
  json.dump(result, file, indent=4, ensure_ascii= False)
1513
1510
  file.close()
@@ -1712,7 +1709,7 @@ get_history = get_raw
1712
1709
  # d = day 'YYYY-MM-DD'
1713
1710
  # v = list of report variables to get
1714
1711
  # summary = 0, 1, 2: do a quick total energy report for a day
1715
- # save = "xxxxx": save the report results to xxxxx_raw_<time_span>_<d>.json
1712
+ # save = "xxxxx": save the report results to xxxxx_report_<time_span>_<d>.json
1716
1713
  # load = "<file>": load the report results from <file>
1717
1714
  # plot = 0: no plot, 1 = plot variables separately, 2 = combine variables
1718
1715
  # station = 0: use device_id, 1 = use station_id
@@ -1842,7 +1839,7 @@ def get_report(report_type='day', d=None, v=None, summary=1, save=None, load=Non
1842
1839
  result = json.load(file)
1843
1840
  file.close()
1844
1841
  elif save is not None:
1845
- file_name = save + "_rep_" + report_type + "_" + d.replace('-','') + ".txt"
1842
+ file_name = save + "_report_" + report_type + "_" + d.replace('-','') + ".txt"
1846
1843
  file = open(storage + file_name, 'w', encoding='utf-8')
1847
1844
  json.dump(result, file, indent=4, ensure_ascii= False)
1848
1845
  file.close()
@@ -2418,9 +2415,9 @@ def get_agile_times(tariff=agile_octopus, d=None):
2418
2415
  plunge = []
2419
2416
  plunge_price = tariff_config['plunge_price'] if tariff_config.get('plunge_price') is not None else 2
2420
2417
  plunge_price = [plunge_price] if type(plunge_price) is not list else plunge_price
2421
- plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 6
2418
+ plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 8
2422
2419
  for i in range(0, len(prices)):
2423
- # hour relative index into list of plunge prices, starting at 7am
2420
+ # hour relative index into list of plunge prices and slots, starting at 7am
2424
2421
  x = int(((now.hour - 7 + i / 2) % 24) * len(plunge_price) / 24)
2425
2422
  if prices[i] is not None and prices[i]['price'] < plunge_price[x]:
2426
2423
  plunge.append(i)
@@ -2841,7 +2838,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2841
2838
  def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2842
2839
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2843
2840
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2844
- global timed_strategy, steps_per_hour, base_time, storage, battery, charge_rates
2841
+ global timed_strategy, steps_per_hour, base_time, storage, battery, battery_params
2845
2842
  print(f"\n---------------- charge_needed ----------------")
2846
2843
  # validate parameters
2847
2844
  args = locals()
@@ -2948,8 +2945,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2948
2945
  bat_power = 0.0
2949
2946
  temperature = 30
2950
2947
  bms_charge_current = 15
2951
- charge_loss = charge_rates[2]['charge_loss']
2952
- discharge_loss = charge_rates[2]['discharge_loss']
2948
+ charge_loss = battery_params[2]['charge_loss']
2949
+ discharge_loss = battery_params[2]['discharge_loss']
2953
2950
  bat_current = 0.0
2954
2951
  device_power = 6.0
2955
2952
  device_current = 35
@@ -2971,8 +2968,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2971
2968
  output(f"Battery capacity could not be estimated. Please add the parameter 'capacity=xx' in kWh")
2972
2969
  return None
2973
2970
  bms_charge_current = battery.get('charge_rate')
2974
- charge_loss = battery['charge_loss'] if battery.get('charge_loss') is not None else 1.0
2975
- discharge_loss = battery['discharge_loss'] if battery.get('discharge_loss') is not None else 1.0
2971
+ charge_loss = battery['charge_loss'] if battery.get('charge_loss') is not None else 0.974
2972
+ discharge_loss = battery['discharge_loss'] if battery.get('discharge_loss') is not None else 0.974
2976
2973
  device_power = device.get('power')
2977
2974
  device_current = device.get('max_charge_current')
2978
2975
  model = device.get('deviceType')
foxesscloud/openapi.py CHANGED
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 13 October 2024
4
+ Updated: 14 October 2024
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.6.3"
13
+ version = "2.6.5"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -281,7 +281,7 @@ var_table = None
281
281
  var_list = None
282
282
 
283
283
  def get_vars():
284
- global var_table, var_list, debug_setting, messages, lang, token
284
+ global var_table, var_list, debug_setting, messages, lang
285
285
  if api_key is None:
286
286
  output(f"** please generate an API Key at foxesscloud.com and provide this (f.api_key='your API key')")
287
287
  return None
@@ -558,13 +558,13 @@ battery_params = {
558
558
  1: {'table': [ 0, 2, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 0],
559
559
  'step': 5,
560
560
  'offset': 5,
561
- 'charge_loss': 0.975,
562
- 'discharge_loss': 0.975},
561
+ 'charge_loss': 0.974,
562
+ 'discharge_loss': 0.974},
563
563
  2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
564
564
  'step': 5,
565
565
  'offset': 5,
566
566
  'charge_loss': 1.080,
567
- 'discharge_loss': 0.85},
567
+ 'discharge_loss': 0.95},
568
568
  }
569
569
 
570
570
  def get_battery(info=0, v=None):
@@ -615,7 +615,7 @@ def get_batteries(info=0):
615
615
  ##################################################################################################
616
616
 
617
617
  def get_charge():
618
- global token, device_sn, battery_settings, debug_setting
618
+ global device_sn, battery_settings, debug_setting
619
619
  if get_device() is None:
620
620
  return None
621
621
  if battery_settings is None:
@@ -647,7 +647,7 @@ def time_period(t, n):
647
647
  return result
648
648
 
649
649
  def set_charge(ch1=None, st1=None, en1=None, ch2=None, st2=None, en2=None, force = 0, enable=1):
650
- global token, device_sn, battery_settings, debug_setting, time_period_vars
650
+ global device_sn, battery_settings, debug_setting, time_period_vars
651
651
  if get_device() is None:
652
652
  return None
653
653
  if battery_settings is None:
@@ -723,7 +723,7 @@ def set_charge(ch1=None, st1=None, en1=None, ch2=None, st2=None, en2=None, force
723
723
  ##################################################################################################
724
724
 
725
725
  def get_min():
726
- global token, device_sn, battery_settings, debug_setting
726
+ global device_sn, battery_settings, debug_setting
727
727
  if get_device() is None:
728
728
  return None
729
729
  if battery_settings is None:
@@ -747,7 +747,7 @@ def get_min():
747
747
  ##################################################################################################
748
748
 
749
749
  def set_min(minSocOnGrid = None, minSoc = None, force = 0):
750
- global token, device_sn, schedule, battery_settings, debug_setting
750
+ global device_sn, schedule, battery_settings, debug_setting
751
751
  if get_device() is None:
752
752
  return None
753
753
  if schedule['enable'] == True:
@@ -801,160 +801,71 @@ def get_settings():
801
801
  # get remote settings
802
802
  ##################################################################################################
803
803
 
804
- remote_settings = None # raw UI info
805
- named_settings = None # processed UI info
806
- merge_settings = { # keys to add
807
- 'WorkMode': {'keys': {
808
- 'h115__': 'operation_mode__work_mode',
809
- 'h116__': 'operation_mode__work_mode',
810
- 'h117__': 'operation_mode__work_mode',
811
- # 'k106__': 'operation_mode__work_mode',
812
- },
813
- 'values': ['SelfUse', 'Feedin', 'Backup']},
814
- 'BatteryVolt': {'keys': {
815
- 'h115__': ['h115__14', 'h115__15', 'h115__16'],
816
- 'h116__': ['h116__15', 'h116__16', 'h116__17'],
817
- 'h117__': ['h117__15', 'h117__16', 'h117__17'],
818
- # 'k106__': ['k106__xx', 'k106__xx', 'k106__xx'],
819
- },
820
- 'type': 'list',
821
- 'valueType': 'float',
822
- 'unit': 'V'},
823
- 'BatteryTemp': {'keys': {
824
- 'h115__': 'h115__17',
825
- 'h116__': 'h116__18',
826
- 'h117__': 'h117__18',
827
- # 'k106__': 'k106__xx',
828
- },
829
- 'type': 'list',
830
- 'valueType': 'int',
831
- 'unit': '℃'},
832
- }
804
+ # store for named settings info
805
+ named_settings = {}
833
806
 
834
- def get_ui():
835
- global device_id, debug_setting, messages, remote_settings, named_settings, merge_settings
836
- if get_device() is None:
837
- return None
838
- if remote_settings is None:
839
- output(f"getting ui settings", 2)
840
- params = {'id': device_id}
841
- response = signed_get(path="/generic/v0/device/setting/ui", params=params)
842
- if response.status_code != 200:
843
- output(f"** get_ui() got response code {response.status_code}: {response.reason}")
844
- return None
845
- result = response.json().get('result')
846
- if result is None:
847
- errno = response.json().get('errno')
848
- output(f"** get_ui(), no result data, {errno_message(errno)}")
849
- return None
850
- remote_settings = result
851
- protocol = remote_settings['protocol'].lower().replace('xx','__')
852
- named_settings = {'_protocol': protocol}
853
- volt_n = 0
854
- volt_keys = []
855
- for p in remote_settings['parameters']:
856
- if p['name'][:11] == 'BatteryVolt': # merge BatteryVolts
857
- output(f" found {p['name']} with key {p['key']}", 2)
858
- volt_n += 1
859
- volt_keys.append(p['key'])
860
- if volt_n == 3:
861
- named_settings['BatteryVolt'] = {'keys': volt_keys, 'type': 'list', 'valueType': 'float', 'unit': p['properties'][0]['unit']}
862
- elif volt_n > 3:
863
- print(f"** get_ui(): more than 3 groups found for BatteryVolt, n={volt_n}")
864
- elif p['name'][:11] == 'BatteryTemp':
865
- output(f" found {p['name']} with key {p['key']}", 2)
866
- named_settings['BatteryTemp'] = {'keys': p['key'], 'type': 'list', 'valueType': 'int', 'unit': p['properties'][0]['unit']}
867
- else:
868
- items = []
869
- block = p['block'] and len(p['properties']) > 1
870
- for e in p['properties']:
871
- valueType = e['elemType']['valueType']
872
- item = {'name': e['key'].replace(protocol,'')} if block else {'key': e['key']} #, 'group': p['name']}
873
- if e['elemType'].get('uiItems') is not None:
874
- item['values'] = e['elemType']['uiItems']
875
- elif e.get('range') is not None:
876
- item['range'] = e['range']
877
- item['valueType'] = 'float' if type(e['range']['hi']) is float else 'int'
878
- else:
879
- item['type'] = valueType
880
- if e.get('unit') is not None and len(e['unit']) > 0:
881
- item['unit'] = e['unit']
882
- if block:
883
- items.append(item)
884
- else:
885
- named_settings[e['name']] = item
886
- if block:
887
- named_settings[p['name']] = {'key': p['key'], 'type': 'block', 'items': items}
888
- for name in merge_settings.keys():
889
- if named_settings.get(name) is None and merge_settings[name]['keys'].get(protocol) is not None:
890
- named_settings[name] = {'keys': merge_settings[name]['keys'][protocol]}
891
- for k in merge_settings[name].keys():
892
- if k != 'keys':
893
- named_settings[name][k] = merge_settings[name][k]
894
- return remote_settings
895
-
896
- def get_remote_settings(key):
897
- global token, device_id, debug_setting, messages
807
+ def get_remote_settings(name):
808
+ global device_sn, debug_setting, messages, name_data
898
809
  if get_device() is None:
899
810
  return None
900
811
  output(f"getting remote settings", 2)
901
- if key is None:
812
+ if name is None:
902
813
  return None
903
- if type(key) is list:
814
+ if type(name) is list:
904
815
  values = {}
905
- for k in key:
906
- v = get_remote_settings(k)
816
+ for n in name:
817
+ v = get_remote_settings(n)
907
818
  if v is None:
908
- return
819
+ continue
909
820
  for x in v.keys():
910
821
  values[x] = v[x]
911
822
  return values
912
- params = {'id': device_id, 'hasVersionHead': 1, 'key': key}
913
- response = signed_get(path="/c/v0/device/setting/get", params=params)
823
+ body = {'sn': device_sn, 'key': name}
824
+ response = signed_post(path="/op/v0/device/setting/get", body=body)
914
825
  if response.status_code != 200:
915
826
  output(f"** get_remote_settings() got response code {response.status_code}: {response.reason}")
916
827
  return None
917
828
  result = response.json().get('result')
918
829
  if result is None:
919
830
  errno = response.json().get('errno')
920
- output(f"** get_remote_settings(), no result data, {errno_message(errno)}")
831
+ output(f"** get_remote_settings(), no result data, {errno_message(response)}")
921
832
  return None
922
- values = result.get('values')
923
- if values is None:
924
- output(f"** get_remote_settings(), no values data")
833
+ named_settings[name] = result
834
+ value = result.get('value')
835
+ if value is None:
836
+ output(f"** get_remote_settings(), no value for {name}")
925
837
  return None
926
- return values
838
+ return value
927
839
 
928
840
  def get_named_settings(name):
929
- global named_settings
930
- if type(name) is list:
931
- result = []
932
- for n in name:
933
- result.append(get_named_settings(n))
934
- return result
935
- if named_settings is None or named_settings.get(name) is None:
936
- output(f"** get_named_settings(): {name} was not recognised")
841
+ return get_remote_settings(name)
842
+
843
+ def set_named_setting(name, value):
844
+ global device_sn, debug_setting
845
+ if get_device() is None:
937
846
  return None
938
- keys = named_settings[name].get('keys')
939
- if keys is None:
940
- output(f"** get_named_settings(): no keys for name: {name}")
847
+ if type(name) is list:
848
+ count = 0
849
+ for (n, v) in name:
850
+ result = set_named_settings(name=n, value=v)
851
+ if result is not None:
852
+ count += 1
853
+ return count
854
+ output(f"\nSetting {name} to {value}", 1)
855
+ body = {'sn': device_sn, 'key': name, 'value': f"{value}"}
856
+ setting_delay()
857
+ response = signed_post(path="/op/v0/device/setting/set", body=body)
858
+ if response.status_code != 200:
859
+ output(f"** set_named_settings(): ({name}, {value}) got response code {response.status_code}: {response.reason}")
941
860
  return None
942
- output(f"getting named_settings for {name} using {keys}", 2)
943
- result = get_remote_settings(keys)
944
- if result is None:
945
- output(f"** get_named_settings(): no result for {name} using key: {keys}")
861
+ errno = response.json().get('errno')
862
+ if errno != 0:
863
+ if errno == 44096:
864
+ output(f"** cannot update settings when schedule is active")
865
+ else:
866
+ output(f"** set_named_settings(): ({name}, {value}) {errno_message(response)}")
946
867
  return None
947
- result_type = named_settings[name].get('type')
948
- value_type = named_settings[name].get('valueType')
949
- if result_type is None:
950
- v = result.get([k for k in result.keys()][0])
951
- return v if value_type is None else c_float(v) if value_type == 'float' else c_int(v)
952
- if result_type == 'list':
953
- values = []
954
- for k in sorted(result.keys()):
955
- values.append(result[k] if value_type is None else c_float(result[k]) if value_type == 'float' else c_int(result[k]))
956
- return values
957
- return result
868
+ return 1
958
869
 
959
870
  ##################################################################################################
960
871
  # wrappers for named settings
@@ -964,8 +875,6 @@ work_mode = None
964
875
 
965
876
  def get_work_mode():
966
877
  global work_mode
967
- # print(f"** get_work_mode(): not available via Open API")
968
- return None
969
878
  if get_device() is None:
970
879
  return None
971
880
  work_mode = get_named_settings('WorkMode')
@@ -983,6 +892,8 @@ temp_slots_per_battery = 8
983
892
 
984
893
  def get_cell_temps(nbat=8):
985
894
  global temp_slots_per_battery
895
+ print(f"** get_cell_temps(): not available via Open API")
896
+ return None
986
897
  values = get_named_settings('BatteryTemp')
987
898
  if values is None:
988
899
  return None
@@ -1008,9 +919,7 @@ work_modes = ['SelfUse', 'Feedin', 'Backup', 'ForceCharge', 'ForceDischarge']
1008
919
  settable_modes = work_modes[:3]
1009
920
 
1010
921
  def set_work_mode(mode, force = 0):
1011
- global token, device_sn, work_modes, work_mode, debug_setting
1012
- print(f"** set_work_mode(): not available via Open API")
1013
- return None
922
+ global device_sn, work_modes, work_mode, debug_setting
1014
923
  if get_device() is None:
1015
924
  return None
1016
925
  if mode not in settable_modes:
@@ -1022,7 +931,7 @@ def set_work_mode(mode, force = 0):
1022
931
  return None
1023
932
  set_schedule(enable=0)
1024
933
  output(f"\nSetting work mode: {mode}", 1)
1025
- body = {'sn': device_sn, 'key': 'operation_mode__work_mode', 'values': {'operation_mode__work_mode': mode}, 'raw': ''}
934
+ body = {'sn': device_sn, 'key': 'WorkMode', 'value': mode}
1026
935
  setting_delay()
1027
936
  response = signed_post(path="/op/v0/device/setting/set", body=body)
1028
937
  if response.status_code != 200:
@@ -1180,7 +1089,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1180
1089
 
1181
1090
  # set a schedule from a period or list of time segment periods
1182
1091
  def set_schedule(periods=None, enable=True):
1183
- global token, device_sn, debug_setting, schedule
1092
+ global device_sn, debug_setting, schedule
1184
1093
  if get_flag() is None:
1185
1094
  return None
1186
1095
  if schedule.get('support') == False:
@@ -1279,7 +1188,7 @@ def get_real(v = None):
1279
1188
  # d = day 'YYYY-MM-DD'. Can also include 'HH:MM' in 'hour' mode
1280
1189
  # v = list of variables to get
1281
1190
  # summary = 0: raw data, 1: add max, min, sum, 2: summarise and drop raw data, 3: calculate state
1282
- # save = "xxxxx": save the raw results to xxxxx_raw_<time_span>_<d>.json
1191
+ # save = "xxxxx": save the raw results to xxxxx_history_<time_span>_<d>.json
1283
1192
  # load = "<file>": load the raw results from <file>
1284
1193
  # plot = 0: no plot, 1: plot variables separately, 2: combine variables
1285
1194
  ##################################################################################################
@@ -1294,7 +1203,7 @@ sample_time = 5.0 # 5 minutes default
1294
1203
  sample_rounding = 2 # round to 30 seconds
1295
1204
 
1296
1205
  def get_history(time_span='hour', d=None, v=None, summary=1, save=None, load=None, plot=0):
1297
- global token, device_sn, debug_setting, var_list, invert_ct2, tariff, max_power_kw, sample_rounding, sample_time, residual_scale, storage
1206
+ global device_sn, debug_setting, var_list, invert_ct2, tariff, max_power_kw, sample_rounding, sample_time, residual_scale, storage
1298
1207
  if get_device() is None:
1299
1208
  return None
1300
1209
  time_span = time_span.lower()
@@ -1546,7 +1455,7 @@ def report_value_profile(result):
1546
1455
  # d = day 'YYYY-MM-DD'
1547
1456
  # v = list of report variables to get
1548
1457
  # summary = 0, 1, 2: do a quick total energy report for a day
1549
- # save = "xxxxx": save the report results to xxxxx_raw_<time_span>_<d>.json
1458
+ # save = "xxxxx": save the report results to xxxxx_report_<time_span>_<d>.json
1550
1459
  # load = "<file>": load the report results from <file>
1551
1460
  # plot = 0: no plot, 1 = plot variables separately, 2 = combine variables
1552
1461
  ##################################################################################################
@@ -1560,7 +1469,7 @@ fix_value_threshold = 200000000.0
1560
1469
  fix_value_mask = 0x0000FFFF
1561
1470
 
1562
1471
  def get_report(dimension='day', d=None, v=None, summary=1, save=None, load=None, plot=0):
1563
- global token, device_sn, var_list, debug_setting, report_vars, storage
1472
+ global device_sn, var_list, debug_setting, report_vars, storage
1564
1473
  if get_device() is None:
1565
1474
  return None
1566
1475
  # process list of days
@@ -2219,7 +2128,7 @@ def get_agile_times(tariff=agile_octopus, d=None):
2219
2128
  plunge = []
2220
2129
  plunge_price = tariff_config['plunge_price'] if tariff_config.get('plunge_price') is not None else 2
2221
2130
  plunge_price = [plunge_price] if type(plunge_price) is not list else plunge_price
2222
- plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 6
2131
+ plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 8
2223
2132
  for i in range(0, len(prices)):
2224
2133
  # hour relative index into list of plunge prices, starting at 7am
2225
2134
  x = int(((now.hour - 7 + i / 2) % 24) * len(plunge_price) / 24)
@@ -2642,7 +2551,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2642
2551
  def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2643
2552
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2644
2553
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2645
- global timed_strategy, steps_per_hour, base_time, storage, battery, charge_rates
2554
+ global timed_strategy, steps_per_hour, base_time, storage, battery, battery_params
2646
2555
  print(f"\n---------------- charge_needed ----------------")
2647
2556
  # validate parameters
2648
2557
  args = locals()
@@ -2749,8 +2658,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2749
2658
  bat_power = 0.0
2750
2659
  temperature = 30
2751
2660
  bms_charge_current = 15
2752
- charge_loss = charge_rates[2]['charge_loss']
2753
- discharge_loss = charge_rates[2]['discharge_loss']
2661
+ charge_loss = battery_params[2]['charge_loss']
2662
+ discharge_loss = battery_params[2]['discharge_loss']
2754
2663
  bat_current = 0.0
2755
2664
  device_power = 6.0
2756
2665
  device_current = 35
@@ -2772,8 +2681,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2772
2681
  output(f"Battery capacity could not be estimated. Please add the parameter 'capacity=xx' in kWh")
2773
2682
  return None
2774
2683
  bms_charge_current = battery.get('charge_rate')
2775
- charge_loss = battery['charge_loss'] if battery.get('charge_loss') is not None else 1.0
2776
- discharge_loss = battery['discharge_loss'] if battery.get('discharge_loss') is not None else 1.0
2684
+ charge_loss = battery['charge_loss'] if battery.get('charge_loss') is not None else 0.974
2685
+ discharge_loss = battery['discharge_loss'] if battery.get('discharge_loss') is not None else 0.974
2777
2686
  device_power = device.get('power')
2778
2687
  device_current = device.get('max_charge_current')
2779
2688
  model = device.get('deviceType')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.6.3
3
+ Version: 2.6.5
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
@@ -111,6 +111,7 @@ f.get_charge()
111
111
  f.get_min()
112
112
  f.get_flag()
113
113
  f.get_schedule()
114
+ f.get_named_settings(name)
114
115
 
115
116
  ```
116
117
  Each of these calls will return a dictionary or list containing the relevant information.
@@ -130,6 +131,10 @@ get_flag() returns the current scheduler enable / support / maxsoc flags
130
131
 
131
132
  get_schedule() returns the current work mode / soc schedule settings. The result is stored in f.schedule.
132
133
 
134
+ get_named_settings() returns the value of a named setting. If 'name' is a list, it returns a list of values.
135
+ + f.named_settings is updated. This is dictionary of information and current value, indexed by 'name.
136
+ + the only name currently supported by Fox is 'ExportLimit' and this is only available for H3 inverters.
137
+
133
138
 
134
139
  ## Inverter Settings
135
140
  You can change inverter settings using:
@@ -140,6 +145,7 @@ f.set_charge(ch1, st1, en1, ch2, st2, en2, enable)
140
145
  f.set_period(start, end, mode, min_soc, max_soc, fdsoc, fdpwr, price, segment)
141
146
  f.charge_periods(st0, en0, st1, en1, st2, en2, min_soc, target_soc, start_soc)
142
147
  f.set_schedule(periods, enable)
148
+ f.set_named_settings(name, value)
143
149
  ```
144
150
 
145
151
  set_min() applies new SoC settings to the inverter. The parameters update battery_settings:
@@ -180,6 +186,10 @@ set_schedule() configures a list of scheduled work mode / soc changes with enabl
180
186
  + periods: a time segment or list of time segments created using f.set_period().
181
187
  + enable: 1 to enable schedules, 0 to disable schedules. The default is 1.
182
188
 
189
+ set_named_settings() sets the 'name' setting to 'value'.
190
+ + 'name' may also be a list of (name, value) pairs.
191
+ + the only 'name' currently supported by Fox is 'ExportLimit' on H3 inverters
192
+
183
193
 
184
194
  ## Real Time Data
185
195
  Real time data reports the latest values for inverter variables, collected every 5 minutes:
@@ -787,7 +797,13 @@ This setting can be:
787
797
 
788
798
  # Version Info
789
799
 
790
- 2.6.3<br>
800
+ 2.6.5<br>
801
+ Add get_named_settings() and set_named_settings().
802
+ Update get_work_mode() and set_work_mode() to use named settings (still doesn't work though as blocked by Fox)
803
+ Updated get_history() and get_report() saved filenames to use _history_ and _report_ for consistency.
804
+ Update calibration of 'charge_loss' and 'discharge_loss'.
805
+
806
+ 2.6.4<br>
791
807
  Increase default plungs_slots from 6 to 8.
792
808
  Correct battery capacity in get_batteries().
793
809
 
@@ -0,0 +1,7 @@
1
+ foxesscloud/foxesscloud.py,sha256=I9zb9WVDwV68G-UDrWipB9jYRLSj6dWst-grFdV0KiQ,217574
2
+ foxesscloud/openapi.py,sha256=ZK9GQmOSI44_Dm59-3A0-vtOzOLHmwqeBlzDT9RBl5A,202864
3
+ foxesscloud-2.6.5.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
4
+ foxesscloud-2.6.5.dist-info/METADATA,sha256=S2q4LFhzJOBaFhNjqpEaJboKya9SIKvp67fyTvarGz8,58769
5
+ foxesscloud-2.6.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
+ foxesscloud-2.6.5.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
+ foxesscloud-2.6.5.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- foxesscloud/foxesscloud.py,sha256=qhra8fiKsM3xoQCbzQYsPoY0D4mNPbpOwXP_AgpXQ9E,217489
2
- foxesscloud/openapi.py,sha256=YwFHpTH4CyTMyf9f5ZXD0-umFL-lWnq2aQHA0XV7YuM,207518
3
- foxesscloud-2.6.3.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
4
- foxesscloud-2.6.3.dist-info/METADATA,sha256=j3UCMUoCfv10iOf5_8VcDPA8MbCO5CpHllwYi6jSRzw,57854
5
- foxesscloud-2.6.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- foxesscloud-2.6.3.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
- foxesscloud-2.6.3.dist-info/RECORD,,