foxesscloud 2.6.0__tar.gz → 2.6.1__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.6.0
3
+ Version: 2.6.1
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
@@ -105,6 +105,7 @@ Once an inverter is selected, you can make other calls to get information:
105
105
  ```
106
106
  f.get_generation()
107
107
  f.get_battery()
108
+ f.get_batteries()
108
109
  f.get_settings()
109
110
  f.get_charge()
110
111
  f.get_min()
@@ -116,8 +117,8 @@ Each of these calls will return a dictionary or list containing the relevant inf
116
117
 
117
118
  get_generation() will return the latest generation information for the device. The results are also stored in f.device as 'generationToday', 'generationMonth' and 'generationTotal'.
118
119
 
119
- get_battery() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery. Additional battery attributes include:
120
- + 'info': a list of BMS and battery serial numbers and firmware versions
120
+ get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery / f.batteries.
121
+ get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery. Additional battery attributes include:
121
122
  + 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
122
123
  + 'charge_rate': the estimated BMS charge rate available, based on the current 'temperature' of the BMS
123
124
  + 'charge_loss': the ratio of the kWh added to the battery for each kWh applied during charging
@@ -786,6 +787,13 @@ This setting can be:
786
787
 
787
788
  # Version Info
788
789
 
790
+ 2.6.1<br>
791
+ Fix problem where battery discharges below min_soc while waiting for charging to start.
792
+ Update calibration for Force Charge with BMS 1.014 and later.
793
+ Add get_batteries() to return a list of BMS and batteries where inverters support more than 1 BMS.
794
+ Update battery_info() to support multiple BMS.
795
+ Add rated capacity and SoH to battery info if available.
796
+
789
797
  2.6.0<br>
790
798
  Rework charge de-rating with temperature, losses and other info provided by get_battery() to take new BMS behaviour into account.
791
799
 
@@ -91,6 +91,7 @@ Once an inverter is selected, you can make other calls to get information:
91
91
  ```
92
92
  f.get_generation()
93
93
  f.get_battery()
94
+ f.get_batteries()
94
95
  f.get_settings()
95
96
  f.get_charge()
96
97
  f.get_min()
@@ -102,8 +103,8 @@ Each of these calls will return a dictionary or list containing the relevant inf
102
103
 
103
104
  get_generation() will return the latest generation information for the device. The results are also stored in f.device as 'generationToday', 'generationMonth' and 'generationTotal'.
104
105
 
105
- get_battery() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery. Additional battery attributes include:
106
- + 'info': a list of BMS and battery serial numbers and firmware versions
106
+ get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery / f.batteries.
107
+ get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery. Additional battery attributes include:
107
108
  + 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
108
109
  + 'charge_rate': the estimated BMS charge rate available, based on the current 'temperature' of the BMS
109
110
  + 'charge_loss': the ratio of the kWh added to the battery for each kWh applied during charging
@@ -772,6 +773,13 @@ This setting can be:
772
773
 
773
774
  # Version Info
774
775
 
776
+ 2.6.1<br>
777
+ Fix problem where battery discharges below min_soc while waiting for charging to start.
778
+ Update calibration for Force Charge with BMS 1.014 and later.
779
+ Add get_batteries() to return a list of BMS and batteries where inverters support more than 1 BMS.
780
+ Update battery_info() to support multiple BMS.
781
+ Add rated capacity and SoH to battery info if available.
782
+
775
783
  2.6.0<br>
776
784
  Rework charge de-rating with temperature, losses and other info provided by get_battery() to take new BMS behaviour into account.
777
785
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "foxesscloud"
7
- version = "2.6.0"
7
+ version = "2.6.1"
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: 05 October 2024
4
+ Updated: 09 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.2"
13
+ version = "1.7.3"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -477,6 +477,7 @@ def get_device(sn=None):
477
477
  device_id = device.get('deviceID')
478
478
  device_sn = device.get('deviceSN')
479
479
  battery = None
480
+ batteries = None
480
481
  battery_settings = None
481
482
  schedule = None
482
483
  templates = None
@@ -575,6 +576,7 @@ def get_firmware():
575
576
  ##################################################################################################
576
577
 
577
578
  battery = None
579
+ batteries = None
578
580
  battery_settings = None
579
581
 
580
582
  # 1 = Residual Energy, 2 = Residual Capacity
@@ -592,7 +594,7 @@ battery_params = {
592
594
  2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
593
595
  'step': 5,
594
596
  'offset': 5,
595
- 'charge_loss': 1.040,
597
+ 'charge_loss': 1.080,
596
598
  'discharge_loss': 0.975},
597
599
  }
598
600
 
@@ -625,8 +627,8 @@ def get_battery(info=1):
625
627
  errno = response.json().get('errno')
626
628
  output(f"** get_battery().info, no result data, {errno_message(errno)}")
627
629
  else:
628
- battery['info'] = result['batteries']
629
- if battery['info'][0]['masterVersion'] >= '1.014':
630
+ battery['info'] = result['batteries'][0]
631
+ if battery['info']['masterVersion'] >= '1.014':
630
632
  residual_handling = 2
631
633
  if battery.get('residual') is not None:
632
634
  battery['residual'] /= 1000
@@ -648,6 +650,51 @@ def get_battery(info=1):
648
650
  battery['charge_rate'] = params['table'][int((battery['temperature'] - params['offset']) / params['step'])]
649
651
  return battery
650
652
 
653
+ def get_batteries(info=1):
654
+ global token, device_id, battery, debug_setting, messages, batteries, battery_params, residual_handling
655
+ if get_device() is None:
656
+ return None
657
+ output(f"getting batteries", 2)
658
+ params = {'id': device_id}
659
+ response = signed_get(path="/generic/v0/device/battery/info", params=params)
660
+ if response.status_code != 200:
661
+ output(f"** get_batteries() got response code {response.status_code}: {response.reason}")
662
+ return None
663
+ result = response.json().get('result')
664
+ if result is None:
665
+ errno = response.json().get('errno')
666
+ output(f"** get_batteries(), no result data, {errno_message(errno)}")
667
+ return None
668
+ batteries = result['batterys']
669
+ if info == 1:
670
+ response = signed_get(path="/generic/v0/device/battery/list", params=params)
671
+ if response.status_code != 200:
672
+ output(f"** get_battery().info got response code {response.status_code}: {response.reason}")
673
+ else:
674
+ result = response.json().get('result')
675
+ if result is None:
676
+ errno = response.json().get('errno')
677
+ output(f"** get_battery().info, no result data, {errno_message(errno)}")
678
+ else:
679
+ for i in range(0, len(batteries)):
680
+ batteries[i]['info'] = result['batteries'][i]
681
+ for b in batteries:
682
+ if b.get('info') is not None and b['info']['masterVersion'] >= '1.014':
683
+ residual_handling = 2
684
+ capacity = b['ratedCapacity'] / 1000 * int(b['soh']) / 100
685
+ soc = b.get('soc')
686
+ residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
687
+ b['capacity'] = round(capacity, 3)
688
+ b['residual'] = round(residual, 3)
689
+ b['charge_rate'] = 50
690
+ params = battery_params[residual_handling]
691
+ b['charge_loss'] = params['charge_loss']
692
+ b['discharge_loss'] = params['discharge_loss']
693
+ if b.get('temperature') is not None:
694
+ b['charge_rate'] = params['table'][int((b['temperature'] - params['offset']) / params['step'])]
695
+ battery = batteries[0]
696
+ return batteries
697
+
651
698
  ##################################################################################################
652
699
  # get charge times and save to battery_settings
653
700
  ##################################################################################################
@@ -2723,7 +2770,7 @@ charge_config = {
2723
2770
  'export_limit': None, # maximum export power in kW
2724
2771
  'dc_ac_loss': 0.970, # loss converting battery DC power to AC grid power
2725
2772
  'pv_loss': 0.950, # loss converting PV power to DC battery charge power
2726
- 'ac_dc_loss': 0.960, # loss converting AC grid power to DC battery charge power
2773
+ 'ac_dc_loss': 0.963, # loss converting AC grid power to DC battery charge power
2727
2774
  'inverter_power': 101, # Inverter power consumption in W
2728
2775
  'bms_power': 50, # BMS power consumption in W
2729
2776
  'force_charge_power': 5.00, # charge power in kW when using force charge
@@ -2848,7 +2895,6 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2848
2895
  time_to_end = times[0]['time_to_end']
2849
2896
  charge_time = times[0]['charge_time']
2850
2897
  # work out time window and times with clock changes
2851
- time_to_next = int(time_to_start)
2852
2898
  charge_today = (base_hour + time_to_start / steps_per_hour) < 24
2853
2899
  forecast_day = today if charge_today else tomorrow
2854
2900
  run_to = time_to_end1 if time_to_end < time_to_end1 else time_to_end1 + 24 * steps_per_hour
@@ -2867,7 +2913,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2867
2913
  output(f"start_at = {start_at}, end_by = {end_by}, bat_hold = {bat_hold}")
2868
2914
  output(f"base_hour = {base_hour}, hour_adjustment = {hour_adjustment}, change_hour = {change_hour}, time_change = {time_change}")
2869
2915
  output(f"time_to_start = {time_to_start}, run_time = {run_time}, charge_today = {charge_today}")
2870
- output(f"time_to_next = {time_to_next}, full_charge = {full_charge}")
2916
+ output(f"full_charge = {full_charge}")
2871
2917
  if test_soc is not None:
2872
2918
  current_soc = test_soc
2873
2919
  capacity = 14.54
@@ -2875,9 +2921,9 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2875
2921
  bat_volt = 317.4
2876
2922
  bat_power = 0.0
2877
2923
  temperature = 30
2878
- bms_charge_current = 25
2879
- charge_loss = 1.040
2880
- discharge_loss = 0.974
2924
+ bms_charge_current = 15
2925
+ charge_loss = 1.080
2926
+ discharge_loss = 0.975
2881
2927
  bat_current = 0.0
2882
2928
  device_power = 6.0
2883
2929
  device_current = 35
@@ -2921,7 +2967,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2921
2967
  output(f" Min SoC: {min_soc}% ({reserve:.2f}kWh)")
2922
2968
  output(f" Current SoC: {current_soc}%")
2923
2969
  output(f" Max SoC: {max_soc}% ({capacity * max_soc / 100:.2f}kWh)")
2924
- output(f" Charge Rate: {bms_charge_current:.1f}A")
2970
+ output(f" Max Charge: {bms_charge_current:.1f}A")
2925
2971
  output(f" Temperature: {temperature:.1f}°C")
2926
2972
  output(f" Resistance: {bat_resistance:.2f} ohms")
2927
2973
  output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
@@ -3100,7 +3146,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3100
3146
  work_mode_timed[i]['discharge'] = discharge_timed[i]
3101
3147
  # build the battery residual if we don't add any charge and don't limit discharge at min_soc
3102
3148
  kwh_current = residual - (charge_timed[0] - discharge_timed[0]) * (hour_now % 1)
3103
- (bat_timed, kwh_min) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=capacity)
3149
+ (bat_timed, kwh_min) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next=time_to_end, kwh_min=capacity)
3104
3150
  # work out what we need to add to stay above reserve and provide contingency or to hit target_soc
3105
3151
  contingency = charge_config['special_contingency'] if tomorrow[-5:] in charge_config['special_days'] else charge_config['contingency']
3106
3152
  contingency = contingency[quarter] if type(contingency) is list else contingency
@@ -3178,7 +3224,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3178
3224
  work_mode_timed[i]['max_soc'] = target_soc if target_soc is not None else max_soc
3179
3225
  work_mode_timed[i]['discharge'] *= (1-t)
3180
3226
  # rebuild the battery residual with the charge added and min_soc
3181
- (bat_timed, x) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next)
3227
+ (bat_timed, x) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next=start_timed)
3182
3228
  end_residual = interpolate(time_to_end, bat_timed) # residual when charge time ends
3183
3229
  # show the results
3184
3230
  output(f" End SoC: {end_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(time_to_end, time_line))} ({end_residual:.2f}kWh)")
@@ -3411,21 +3457,28 @@ def bat_count(cell_count):
3411
3457
  battery_info_app_key = "aug938dqt5cbqhvq69ixc4v39q6wtw"
3412
3458
 
3413
3459
  # show information about the current state of the batteries
3414
- def battery_info(log=0, plot=1, count=None, info=1):
3415
- global debug_setting, battery_info_app_key, residual_handling
3416
- output_spool(battery_info_app_key)
3417
- bat = get_battery(info=info)
3460
+ def battery_info(log=0, plot=1, count=None, info=1, bat=None):
3461
+ global debug_setting, battery_info_app_key
3418
3462
  if bat is None:
3419
- output_close()
3463
+ bats = get_batteries(info=info)
3464
+ if bats is None:
3465
+ return None
3466
+ for i in range(0, len(bats)):
3467
+ output(f"\n----------------------- BMS {i+1} -----------------------")
3468
+ battery_info(log=log, plot=plot, count=count, info=info, bat=bats[i])
3420
3469
  return None
3470
+ output_spool(battery_info_app_key)
3421
3471
  nbat = None
3422
3472
  if info == 1 and bat.get('info') is not None:
3423
- for b in bat['info']:
3424
- output(f"\nSN {b['masterSN']}, {b['masterBatType']}, Version {b['masterVersion']} (BMS)")
3425
- nbat = 0
3426
- for s in b['slaveBatteries']:
3427
- nbat += 1
3428
- output(f"SN {s['sn']}, {s['batType']}, Version {s['version']} (Battery {nbat})")
3473
+ b = bat['info']
3474
+ output(f"SN {b['masterSN']}, {b['masterBatType']}, Version {b['masterVersion']} (BMS)")
3475
+ nbat = 0
3476
+ for s in b['slaveBatteries']:
3477
+ nbat += 1
3478
+ output(f"SN {s['sn']}, {s['batType']}, Version {s['version']} (Battery {nbat})")
3479
+ output()
3480
+ rated_capacity = bat.get('ratedCapacity')
3481
+ bat_soh = bat.get('soh')
3429
3482
  bat_volt = bat['volt']
3430
3483
  current_soc = bat['soc']
3431
3484
  residual = bat['residual']
@@ -3476,7 +3529,10 @@ def battery_info(log=0, plot=1, count=None, info=1):
3476
3529
  for v in cell_temps:
3477
3530
  s +=f",{v:.0f}"
3478
3531
  return s
3479
- output(f"\nCurrent SoC: {current_soc}%")
3532
+ if rated_capacity is not None:
3533
+ output(f"Rated Capacity: {rated_capacity / 1000:.2f}kWh")
3534
+ output(f"SoH: {bat_soh}%")
3535
+ output(f"Current SoC: {current_soc}%")
3480
3536
  output(f"Capacity: {capacity:.2f}kWh")
3481
3537
  output(f"Residual: {residual:.2f}kWh")
3482
3538
  output(f"InvBatVolt: {bat_volt:.1f}V")
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 05 October 2024
4
+ Updated: 09 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.0"
13
+ version = "2.6.1"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -469,6 +469,7 @@ def get_device(sn=None):
469
469
  return None
470
470
  device = result
471
471
  battery = None
472
+ batteries = None
472
473
  battery_settings = None
473
474
  schedule = None
474
475
  get_flag()
@@ -538,6 +539,7 @@ def get_generation(update=1):
538
539
  ##################################################################################################
539
540
 
540
541
  battery = None
542
+ batteries = None
541
543
  battery_settings = None
542
544
  battery_vars = ['SoC', 'invBatVolt', 'invBatCurrent', 'invBatPower', 'batTemperature', 'ResidualEnergy' ]
543
545
  battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual']
@@ -557,11 +559,11 @@ battery_params = {
557
559
  2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
558
560
  'step': 5,
559
561
  'offset': 5,
560
- 'charge_loss': 1.040,
562
+ 'charge_loss': 1.080,
561
563
  'discharge_loss': 0.975},
562
564
  }
563
565
 
564
- def get_battery(v = None, info=0):
566
+ def get_battery(info=0, v=None):
565
567
  global device_sn, battery, debug_setting, residual_handling, battery_params
566
568
  if get_device() is None:
567
569
  return None
@@ -592,6 +594,14 @@ def get_battery(v = None, info=0):
592
594
  battery['charge_rate'] = params['table'][int((battery['temperature'] - params['offset']) / params['step'])]
593
595
  return battery
594
596
 
597
+ def get_batteries(info=0):
598
+ global battery, batteries
599
+ get_battery(info=info)
600
+ battery['ratedCapacity'] = None
601
+ battery['soh'] = None
602
+ batteries = [battery]
603
+ return batteries
604
+
595
605
  ##################################################################################################
596
606
  # get charge times and save to battery_settings
597
607
  ##################################################################################################
@@ -2579,7 +2589,7 @@ charge_config = {
2579
2589
  'export_limit': None, # maximum export power in kW
2580
2590
  'dc_ac_loss': 0.97, # loss converting battery DC power to AC grid power
2581
2591
  'pv_loss': 0.95, # loss converting PV power to DC battery charge power
2582
- 'ac_dc_loss': 0.962, # loss converting AC grid power to DC battery charge power
2592
+ 'ac_dc_loss': 0.963, # loss converting AC grid power to DC battery charge power
2583
2593
  'inverter_power': 101, # Inverter power consumption in W
2584
2594
  'bms_power': 50, # BMS power consumption in W
2585
2595
  'force_charge_power': 5.00, # charge power in kW when using force charge
@@ -2704,7 +2714,6 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2704
2714
  time_to_end = times[0]['time_to_end']
2705
2715
  charge_time = times[0]['charge_time']
2706
2716
  # work out time window and times with clock changes
2707
- time_to_next = int(time_to_start)
2708
2717
  charge_today = (base_hour + time_to_start / steps_per_hour) < 24
2709
2718
  forecast_day = today if charge_today else tomorrow
2710
2719
  run_to = time_to_end1 if time_to_end < time_to_end1 else time_to_end1 + 24 * steps_per_hour
@@ -2723,7 +2732,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2723
2732
  output(f"start_at = {start_at}, end_by = {end_by}, force_charge = {force_charge}")
2724
2733
  output(f"base_hour = {base_hour}, hour_adjustment = {hour_adjustment}, change_hour = {change_hour}, time_change = {time_change}")
2725
2734
  output(f"time_to_start = {time_to_start}, run_time = {run_time}, charge_today = {charge_today}")
2726
- output(f"time_to_next = {time_to_next}, full_charge = {full_charge}")
2735
+ output(f"full_charge = {full_charge}")
2727
2736
  if test_soc is not None:
2728
2737
  current_soc = test_soc
2729
2738
  capacity = 14.54
@@ -2731,9 +2740,9 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2731
2740
  bat_volt = 317.4
2732
2741
  bat_power = 0.0
2733
2742
  temperature = 30
2734
- bms_charge_current = 25
2735
- charge_loss = 1.040
2736
- discharge_loss = 0.974
2743
+ bms_charge_current = 15
2744
+ charge_loss = 1.080
2745
+ discharge_loss = 0.975
2737
2746
  bat_current = 0.0
2738
2747
  device_power = 6.0
2739
2748
  device_current = 35
@@ -2778,7 +2787,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2778
2787
  output(f" Current SoC: {current_soc}%")
2779
2788
  output(f" Max SoC: {max_soc}% ({capacity * max_soc / 100:.2f}kWh)")
2780
2789
  output(f" Temperature: {temperature:.1f}°C")
2781
- output(f" Charge Rate: {bms_charge_current:.1f}A")
2790
+ output(f" Max Charge: {bms_charge_current:.1f}A")
2782
2791
  output(f" Resistance: {bat_resistance:.2f} ohms")
2783
2792
  output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
2784
2793
  # charge current may be derated based on temperature
@@ -2955,7 +2964,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2955
2964
  work_mode_timed[i]['discharge'] = discharge_timed[i]
2956
2965
  # build the battery residual if we don't add any charge and don't limit discharge at min_soc
2957
2966
  kwh_current = residual - (charge_timed[0] - discharge_timed[0]) * (hour_now % 1)
2958
- (bat_timed, kwh_min) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=capacity)
2967
+ (bat_timed, kwh_min) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next=time_to_end, kwh_min=capacity)
2959
2968
  # work out what we need to add to stay above reserve and provide contingency or to hit target_soc
2960
2969
  contingency = charge_config['special_contingency'] if tomorrow[-5:] in charge_config['special_days'] else charge_config['contingency']
2961
2970
  contingency = contingency[quarter] if type(contingency) is list else contingency
@@ -3033,7 +3042,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3033
3042
  work_mode_timed[i]['max_soc'] = target_soc if target_soc is not None else max_soc
3034
3043
  work_mode_timed[i]['discharge'] *= (1-t)
3035
3044
  # rebuild the battery residual with any charge added and min_soc
3036
- (bat_timed, x) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next)
3045
+ (bat_timed, x) = battery_timed(work_mode_timed, kwh_current, capacity, time_to_next=start_timed)
3037
3046
  end_residual = interpolate(time_to_end, bat_timed) # residual when charge time ends
3038
3047
  # show the results
3039
3048
  output(f" End SoC: {end_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(time_to_end, time_line))} ({end_residual:.2f}kWh)")
@@ -3264,21 +3273,28 @@ def bat_count(cell_count):
3264
3273
  battery_info_app_key = "aug938dqt5cbqhvq69ixc4v39q6wtw"
3265
3274
 
3266
3275
  # show information about the current state of the batteries
3267
- def battery_info(log=0, plot=1, count=None, info=1):
3268
- global debug_setting, battery_info_app_key, residual_handling
3269
- output_spool(battery_info_app_key)
3270
- bat = get_battery(info=info)
3276
+ def battery_info(log=0, plot=1, count=None, info=1, bat=None):
3277
+ global debug_setting, battery_info_app_key
3271
3278
  if bat is None:
3272
- output_close()
3279
+ bats = get_batteries(info=info)
3280
+ if bats is None:
3281
+ return None
3282
+ for i in range(0, len(bats)):
3283
+ output(f"\n----------------------- BMS {i+1} -----------------------")
3284
+ battery_info(log=log, plot=plot, count=count, info=info, bat=bats[i])
3273
3285
  return None
3286
+ output_spool(battery_info_app_key)
3274
3287
  nbat = None
3275
3288
  if info == 1 and bat.get('info') is not None:
3276
- for b in bat['info']:
3277
- output(f"\nSN {b['masterSN']}, {b['masterBatType']}, Version {b['masterVersion']} (BMS)")
3278
- nbat = 0
3279
- for s in b['slaveBatteries']:
3280
- nbat += 1
3281
- output(f"SN {s['sn']}, {s['batType']}, Version {s['version']} (Battery {nbat})")
3289
+ b = bat['info']
3290
+ output(f"SN {b['masterSN']}, {b['masterBatType']}, Version {b['masterVersion']} (BMS)")
3291
+ nbat = 0
3292
+ for s in b['slaveBatteries']:
3293
+ nbat += 1
3294
+ output(f"SN {s['sn']}, {s['batType']}, Version {s['version']} (Battery {nbat})")
3295
+ output()
3296
+ rated_capacity = bat.get('ratedCapacity')
3297
+ bat_soh = bat.get('soh')
3282
3298
  bat_volt = bat['volt']
3283
3299
  current_soc = bat['soc']
3284
3300
  residual = bat['residual']
@@ -3329,7 +3345,10 @@ def battery_info(log=0, plot=1, count=None, info=1):
3329
3345
  for v in cell_temps:
3330
3346
  s +=f",{v:.0f}"
3331
3347
  return s
3332
- output(f"\nCurrent SoC: {current_soc}%")
3348
+ if rated_capacity is not None:
3349
+ output(f"Rated Capacity: {rated_capacity / 1000:.2f}kWh")
3350
+ output(f"SoH: {bat_soh}%")
3351
+ output(f"Current SoC: {current_soc}%")
3333
3352
  output(f"Capacity: {capacity:.2f}kWh")
3334
3353
  output(f"Residual: {residual:.2f}kWh")
3335
3354
  output(f"InvBatVolt: {bat_volt:.1f}V")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.6.0
3
+ Version: 2.6.1
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
@@ -105,6 +105,7 @@ Once an inverter is selected, you can make other calls to get information:
105
105
  ```
106
106
  f.get_generation()
107
107
  f.get_battery()
108
+ f.get_batteries()
108
109
  f.get_settings()
109
110
  f.get_charge()
110
111
  f.get_min()
@@ -116,8 +117,8 @@ Each of these calls will return a dictionary or list containing the relevant inf
116
117
 
117
118
  get_generation() will return the latest generation information for the device. The results are also stored in f.device as 'generationToday', 'generationMonth' and 'generationTotal'.
118
119
 
119
- get_battery() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery. Additional battery attributes include:
120
- + 'info': a list of BMS and battery serial numbers and firmware versions
120
+ get_battery() / get_batteries() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery / f.batteries.
121
+ get_batteries() returns multiple batteries (if available) as a list. get_battery() returns the first battery. Additional battery attributes include:
121
122
  + 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
122
123
  + 'charge_rate': the estimated BMS charge rate available, based on the current 'temperature' of the BMS
123
124
  + 'charge_loss': the ratio of the kWh added to the battery for each kWh applied during charging
@@ -786,6 +787,13 @@ This setting can be:
786
787
 
787
788
  # Version Info
788
789
 
790
+ 2.6.1<br>
791
+ Fix problem where battery discharges below min_soc while waiting for charging to start.
792
+ Update calibration for Force Charge with BMS 1.014 and later.
793
+ Add get_batteries() to return a list of BMS and batteries where inverters support more than 1 BMS.
794
+ Update battery_info() to support multiple BMS.
795
+ Add rated capacity and SoH to battery info if available.
796
+
789
797
  2.6.0<br>
790
798
  Rework charge de-rating with temperature, losses and other info provided by get_battery() to take new BMS behaviour into account.
791
799
 
File without changes
File without changes