foxesscloud 2.5.9__tar.gz → 2.6.0__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.5.9
3
+ Version: 2.6.0
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
@@ -116,7 +116,12 @@ Each of these calls will return a dictionary or list containing the relevant inf
116
116
 
117
117
  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
118
 
119
- get_battery() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery.
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
121
+ + 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
122
+ + 'charge_rate': the estimated BMS charge rate available, based on the current 'temperature' of the BMS
123
+ + 'charge_loss': the ratio of the kWh added to the battery for each kWh applied during charging
124
+ + 'discharge_loss': the ratio of the kWh available for each kWh removed from the battery during during discharging
120
125
 
121
126
  get_settings() will return the battery settings and is equivalent to get_charge() and get_min(). The results are stored in f.battery_settings. The settings include minSoc, minSocOnGrid, enable charge from grid and the charge times.
122
127
 
@@ -371,8 +376,6 @@ export_limit: None # maximum export power in kW. None uses the inver
371
376
  dc_ac_loss: 0.970 # loss converting battery DC power to AC grid power
372
377
  pv_loss: 0.950 # loss converting PV power to DC battery charge power
373
378
  ac_dc_loss: 0.960 # loss converting AC grid power to DC battery charge power
374
- charge_loss: [0.975, 1.040] # loss in battery energy for each kWh added (based on residual_handling)
375
- discharge_loss: [0.975, 0.975] # loss in battery energy for each kWh removed (based on residual_handling)
376
379
  inverter_power: None # inverter power consumption in W (dynamically set)
377
380
  bms_power: 50 # BMS power consumption in W
378
381
  force_charge_power: 5.00 # power used when Force Charge is scheduled
@@ -395,9 +398,6 @@ timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy
395
398
  special_contingency: 30 # contingency for special days when consumption might be higher
396
399
  special_days: ['12-25', '12-26', '01-01']
397
400
  full_charge: None # day of month (1-28) to do full charge or 'daily' or day of week: 'Mon', 'Tue' etc
398
- derate_temp: 28 # battery temperature in C when derating charge current is applied
399
- derate_step: 5 # step size for derating e.g. 21, 16, 11
400
- derating: [24, 15, 10, 2] # derated charge current for each temperature step e.g. 28C, 23C, 18C, 13C
401
401
  force: 1 # 1 = disable strategy periods when setting charge. 0 = fail if strategy period has been set.
402
402
  data_wrap: 6 # data items to show per line
403
403
  target_soc: None # target soc for charging (over-rides calculated value)
@@ -786,6 +786,9 @@ This setting can be:
786
786
 
787
787
  # Version Info
788
788
 
789
+ 2.6.0<br>
790
+ Rework charge de-rating with temperature, losses and other info provided by get_battery() to take new BMS behaviour into account.
791
+
789
792
  2.5.9<br>
790
793
  Change loss parameters to separate AC/DC, DC/AC conversion losses and battery charge / discharge losses.
791
794
  Update charge calibration for new BMS firmware.
@@ -102,7 +102,12 @@ Each of these calls will return a dictionary or list containing the relevant inf
102
102
 
103
103
  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
104
 
105
- get_battery() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery.
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
107
+ + 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
108
+ + 'charge_rate': the estimated BMS charge rate available, based on the current 'temperature' of the BMS
109
+ + 'charge_loss': the ratio of the kWh added to the battery for each kWh applied during charging
110
+ + 'discharge_loss': the ratio of the kWh available for each kWh removed from the battery during during discharging
106
111
 
107
112
  get_settings() will return the battery settings and is equivalent to get_charge() and get_min(). The results are stored in f.battery_settings. The settings include minSoc, minSocOnGrid, enable charge from grid and the charge times.
108
113
 
@@ -357,8 +362,6 @@ export_limit: None # maximum export power in kW. None uses the inver
357
362
  dc_ac_loss: 0.970 # loss converting battery DC power to AC grid power
358
363
  pv_loss: 0.950 # loss converting PV power to DC battery charge power
359
364
  ac_dc_loss: 0.960 # loss converting AC grid power to DC battery charge power
360
- charge_loss: [0.975, 1.040] # loss in battery energy for each kWh added (based on residual_handling)
361
- discharge_loss: [0.975, 0.975] # loss in battery energy for each kWh removed (based on residual_handling)
362
365
  inverter_power: None # inverter power consumption in W (dynamically set)
363
366
  bms_power: 50 # BMS power consumption in W
364
367
  force_charge_power: 5.00 # power used when Force Charge is scheduled
@@ -381,9 +384,6 @@ timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy
381
384
  special_contingency: 30 # contingency for special days when consumption might be higher
382
385
  special_days: ['12-25', '12-26', '01-01']
383
386
  full_charge: None # day of month (1-28) to do full charge or 'daily' or day of week: 'Mon', 'Tue' etc
384
- derate_temp: 28 # battery temperature in C when derating charge current is applied
385
- derate_step: 5 # step size for derating e.g. 21, 16, 11
386
- derating: [24, 15, 10, 2] # derated charge current for each temperature step e.g. 28C, 23C, 18C, 13C
387
387
  force: 1 # 1 = disable strategy periods when setting charge. 0 = fail if strategy period has been set.
388
388
  data_wrap: 6 # data items to show per line
389
389
  target_soc: None # target soc for charging (over-rides calculated value)
@@ -772,6 +772,9 @@ This setting can be:
772
772
 
773
773
  # Version Info
774
774
 
775
+ 2.6.0<br>
776
+ Rework charge de-rating with temperature, losses and other info provided by get_battery() to take new BMS behaviour into account.
777
+
775
778
  2.5.9<br>
776
779
  Change loss parameters to separate AC/DC, DC/AC conversion losses and battery charge / discharge losses.
777
780
  Update charge calibration for new BMS firmware.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "foxesscloud"
7
- version = "2.5.9"
7
+ version = "2.6.0"
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: 02 October 2024
4
+ Updated: 05 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.1"
13
+ version = "1.7.2"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -577,11 +577,27 @@ def get_firmware():
577
577
  battery = None
578
578
  battery_settings = None
579
579
 
580
- # 1 = returns Residual Energy. 2 = resturns Residual Capacity
580
+ # 1 = Residual Energy, 2 = Residual Capacity
581
581
  residual_handling = 1
582
582
 
583
- def get_battery(info=0):
584
- global token, device_id, battery, debug_setting, messages
583
+ # charge rates based on residual_handling
584
+ battery_params = {
585
+ # cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
586
+ # bms temp 5 10 15 20 25 30 35 40 45 50 55 60 65
587
+ 1: {'table': [ 0, 2, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 0],
588
+ 'step': 5,
589
+ 'offset': 5,
590
+ 'charge_loss': 0.975,
591
+ 'discharge_loss': 0.975},
592
+ 2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
593
+ 'step': 5,
594
+ 'offset': 5,
595
+ 'charge_loss': 1.040,
596
+ 'discharge_loss': 0.975},
597
+ }
598
+
599
+ def get_battery(info=1):
600
+ global token, device_id, battery, debug_setting, messages, residual_handling, battery_params
585
601
  if get_device() is None:
586
602
  return None
587
603
  output(f"getting battery", 2)
@@ -595,14 +611,11 @@ def get_battery(info=0):
595
611
  errno = response.json().get('errno')
596
612
  output(f"** get_battery(), no result data, {errno_message(errno)}")
597
613
  return None
614
+ saved_info = battery['info'] if battery is not None and battery.get('info') is not None else None
598
615
  battery = result
599
- if battery.get('residual') is not None:
600
- battery['residual'] /=1000
601
- if residual_handling == 2:
602
- capacity = battery.get('residual')
603
- soc = battery.get('soc')
604
- battery['residual'] = capacity * soc / 100 if capacity is not None and soc is not None else capacity
605
- if info == 1:
616
+ if saved_info is not None:
617
+ battery['info'] = saved_info
618
+ elif info == 1:
606
619
  response = signed_get(path="/generic/v0/device/battery/list", params=params)
607
620
  if response.status_code != 200:
608
621
  output(f"** get_battery().info got response code {response.status_code}: {response.reason}")
@@ -613,6 +626,26 @@ def get_battery(info=0):
613
626
  output(f"** get_battery().info, no result data, {errno_message(errno)}")
614
627
  else:
615
628
  battery['info'] = result['batteries']
629
+ if battery['info'][0]['masterVersion'] >= '1.014':
630
+ residual_handling = 2
631
+ if battery.get('residual') is not None:
632
+ battery['residual'] /= 1000
633
+ if residual_handling == 2:
634
+ capacity = battery.get('residual')
635
+ soc = battery.get('soc')
636
+ residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
637
+ else:
638
+ residual = battery.get('residual')
639
+ soc = battery.get('soc')
640
+ capacity = residual / soc * 100 if residual is not None and soc is not None and soc > 0 else None
641
+ battery['capacity'] = round(capacity, 3)
642
+ battery['residual'] = round(residual, 3)
643
+ battery['charge_rate'] = 50
644
+ params = battery_params[residual_handling]
645
+ battery['charge_loss'] = params['charge_loss']
646
+ battery['discharge_loss'] = params['discharge_loss']
647
+ if battery.get('temperature') is not None:
648
+ battery['charge_rate'] = params['table'][int((battery['temperature'] - params['offset']) / params['step'])]
616
649
  return battery
617
650
 
618
651
  ##################################################################################################
@@ -2545,7 +2578,7 @@ def forecast_value_timed(forecast, today, tomorrow, base_hour, run_time, time_of
2545
2578
  profile = []
2546
2579
  h = base_hour - time_offset
2547
2580
  while h < 0:
2548
- profile.append(0.0)
2581
+ profile.append(None)
2549
2582
  h += 1 / steps_per_hour
2550
2583
  while h < 48:
2551
2584
  day = today if h < 24 else tomorrow
@@ -2555,10 +2588,10 @@ def forecast_value_timed(forecast, today, tomorrow, base_hour, run_time, time_of
2555
2588
  value = forecast.daily[day]['hourly'].get(int(h % 24))
2556
2589
  else:
2557
2590
  value = forecast.daily[day]['pt30'].get(hours_time(int(h * 2) / 2))
2558
- profile.append(c_float(value))
2591
+ profile.append(value)
2559
2592
  h += 1 / steps_per_hour
2560
2593
  while len(profile) < run_time:
2561
- profile.append(0.0)
2594
+ profile.append(None)
2562
2595
  return profile[:run_time]
2563
2596
 
2564
2597
  # build the timed work mode profile from the tariff strategy:
@@ -2596,11 +2629,11 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
2596
2629
  # build the timed battery residual from the charge / discharge, work mode and min_soc
2597
2630
  # all power values are as measured at the inverter battery connection
2598
2631
  def battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=None, reserve_drain=None):
2599
- global charge_config, steps_per_hour, residual_handling
2632
+ global charge_config, steps_per_hour
2600
2633
  allowed_drain = charge_config['allowed_drain'] if charge_config.get('allowed_drain') is not None else 4
2601
2634
  bms_loss = (charge_config['bms_power'] / 1000 if charge_config.get('bms_power') is not None else 0.05)
2602
- charge_loss = charge_config['charge_loss'][residual_handling - 1]
2603
- discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
2635
+ charge_loss = charge_config['charge_loss']
2636
+ discharge_loss = charge_config['discharge_loss']
2604
2637
  charge_limit = charge_config['charge_limit']
2605
2638
  float_charge = charge_config['float_charge']
2606
2639
  for i in range(0, len(work_mode_timed)):
@@ -2691,8 +2724,6 @@ charge_config = {
2691
2724
  'dc_ac_loss': 0.970, # loss converting battery DC power to AC grid power
2692
2725
  'pv_loss': 0.950, # loss converting PV power to DC battery charge power
2693
2726
  'ac_dc_loss': 0.960, # loss converting AC grid power to DC battery charge power
2694
- 'charge_loss': [0.975, 1.040], # loss in battery energy for each kWh added (based on residual_handling)
2695
- 'discharge_loss': [0.975, 0.975], # loss in battery energy for each kWh removed (based on residual_handling)
2696
2727
  'inverter_power': 101, # Inverter power consumption in W
2697
2728
  'bms_power': 50, # BMS power consumption in W
2698
2729
  'force_charge_power': 5.00, # charge power in kW when using force charge
@@ -2713,9 +2744,6 @@ charge_config = {
2713
2744
  'special_contingency': 33, # contingency for special days when consumption might be higher
2714
2745
  'special_days': ['12-25', '12-26', '01-01'],
2715
2746
  'full_charge': None, # day of month (1-28) to do full charge, or 'daily' or 'Mon', 'Tue' etc
2716
- 'derate_temp': 28, # BMS temperature when cold derating starts to be applied
2717
- 'derate_step': 5, # scale for derating factors in C
2718
- 'derating': [24, 15, 10, 2], # max charge current de-rating
2719
2747
  'data_wrap': 6, # data items to show per line
2720
2748
  'target_soc': None, # the target SoC for charging (over-rides calculated value)
2721
2749
  'shading': { # effect of shading on Solcast / forecast.solar
@@ -2740,7 +2768,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2740
2768
  def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2741
2769
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2742
2770
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2743
- global timed_strategy, steps_per_hour, base_time, storage, residual_handling
2771
+ global timed_strategy, steps_per_hour, base_time, storage, battery
2744
2772
  print(f"\n---------------- charge_needed ----------------")
2745
2773
  # validate parameters
2746
2774
  args = locals()
@@ -2840,8 +2868,22 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2840
2868
  output(f"base_hour = {base_hour}, hour_adjustment = {hour_adjustment}, change_hour = {change_hour}, time_change = {time_change}")
2841
2869
  output(f"time_to_start = {time_to_start}, run_time = {run_time}, charge_today = {charge_today}")
2842
2870
  output(f"time_to_next = {time_to_next}, full_charge = {full_charge}")
2871
+ if test_soc is not None:
2872
+ current_soc = test_soc
2873
+ capacity = 14.54
2874
+ residual = test_soc * capacity / 100
2875
+ bat_volt = 317.4
2876
+ bat_power = 0.0
2877
+ temperature = 30
2878
+ bms_charge_current = 25
2879
+ charge_loss = 1.040
2880
+ discharge_loss = 0.974
2881
+ bat_current = 0.0
2882
+ device_power = 6.0
2883
+ device_current = 35
2884
+ model = 'H1-6.0-E'
2885
+ else:
2843
2886
  # get device and battery info from inverter
2844
- if test_soc is None:
2845
2887
  get_battery()
2846
2888
  if battery is None or battery['status'] != 1:
2847
2889
  output(f"\nBattery status is not available")
@@ -2852,27 +2894,16 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2852
2894
  bat_current = battery['current']
2853
2895
  temperature = battery['temperature']
2854
2896
  residual = battery['residual']
2855
- if charge_config.get('capacity') is not None:
2856
- capacity = charge_config['capacity']
2857
- elif residual is not None and residual > 0.2 and current_soc is not None and current_soc > 1:
2858
- capacity = residual * 100 / current_soc
2859
- else:
2897
+ capacity = charge_config['capacity'] if charge_config.get('capacity') is not None else battery.get('capacity')
2898
+ if capacity is None:
2860
2899
  output(f"Battery capacity could not be estimated. Please add the parameter 'capacity=xx' in kWh")
2861
2900
  return None
2901
+ bms_charge_current = battery.get('charge_rate')
2902
+ charge_loss = battery['charge_loss'] if battery.get('charge_loss') is not None else 1.0
2903
+ discharge_loss = battery['discharge_loss'] if battery.get('discharge_loss') is not None else 1.0
2862
2904
  device_power = device.get('power')
2863
2905
  device_current = device.get('max_charge_current')
2864
2906
  model = device.get('deviceType')
2865
- else:
2866
- current_soc = test_soc
2867
- capacity = 14.54
2868
- residual = test_soc * capacity / 100
2869
- bat_volt = 317.4
2870
- bat_power = 0.0
2871
- temperature = 30
2872
- bat_current = 0.0
2873
- device_power = 6.0
2874
- device_current = 25
2875
- model = 'H1-6.0-E'
2876
2907
  min_soc = charge_config['min_soc'] if charge_config['min_soc'] is not None else 10
2877
2908
  max_soc = charge_config['max_soc'] if charge_config['max_soc'] is not None else 100
2878
2909
  volt_curve = charge_config['volt_curve']
@@ -2890,27 +2921,14 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2890
2921
  output(f" Min SoC: {min_soc}% ({reserve:.2f}kWh)")
2891
2922
  output(f" Current SoC: {current_soc}%")
2892
2923
  output(f" Max SoC: {max_soc}% ({capacity * max_soc / 100:.2f}kWh)")
2924
+ output(f" Charge Rate: {bms_charge_current:.1f}A")
2893
2925
  output(f" Temperature: {temperature:.1f}°C")
2894
2926
  output(f" Resistance: {bat_resistance:.2f} ohms")
2895
2927
  output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
2896
- # charge times are derated based on temperature
2928
+ # charge current may be derated based on temperature
2897
2929
  charge_current = device_current if charge_config['charge_current'] is None else charge_config['charge_current']
2898
- derate_temp = charge_config['derate_temp']
2899
- if temperature > 36:
2900
- output(f"\nHigh battery temperature may affect the charge rate")
2901
- elif round(temperature, 0) <= derate_temp:
2902
- output(f"\nLow battery temperature may affect the charge rate")
2903
- derating = charge_config['derating']
2904
- derate_step = charge_config['derate_step']
2905
- i = int((derate_temp - temperature) / (derate_step if derate_step is not None and derate_step > 0 else 1))
2906
- if derating is not None and type(derating) is list and i < len(derating):
2907
- derated_current = derating[i]
2908
- if derated_current < charge_current:
2909
- output(f" Charge current reduced from {charge_current:.0f}A to {derated_current:.0f}A" )
2910
- charge_current = derated_current
2911
- else:
2912
- bat_hold = 2
2913
- output(f" Full charge set")
2930
+ if charge_current > bms_charge_current:
2931
+ charge_current = bms_charge_current
2914
2932
  # inverter losses
2915
2933
  inverter_power = charge_config['inverter_power'] if charge_config['inverter_power'] is not None else round(device_power, 0) * 25
2916
2934
  operating_loss = inverter_power / 1000
@@ -2924,21 +2942,24 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2924
2942
  force_charge_power = charge_config['force_charge_power'] if timed_mode > 1 and charge_config.get('force_charge_power') is not None else 100
2925
2943
  charge_power = min([(device_power - operating_loss) * ac_dc_loss, force_charge_power * ac_dc_loss, charge_limit])
2926
2944
  float_charge = (charge_config['float_current'] if charge_config.get('float_current') is not None else 4) * bat_ocv / 1000
2927
- charge_config['charge_limit'] = charge_limit
2928
- charge_config['charge_power'] = charge_power
2929
- charge_config['float_charge'] = float_charge
2930
- charge_loss = charge_config['charge_loss'][residual_handling - 1]
2945
+ pv_loss = charge_config['pv_loss']
2931
2946
  # work out discharge limit = max power coming from the battery before ac conversion losses
2932
2947
  dc_ac_loss = charge_config['dc_ac_loss']
2933
2948
  discharge_limit = device_power / dc_ac_loss
2934
2949
  discharge_current = device_current if charge_config['discharge_current'] is None else charge_config['discharge_current']
2935
2950
  discharge_power = discharge_current * bat_ocv / 1000
2936
2951
  discharge_limit = discharge_power if discharge_power < discharge_limit else discharge_limit
2937
- discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
2938
2952
  # charging happens if generation exceeds export limit in feedin work mode
2939
2953
  export_power = device_power if charge_config['export_limit'] is None else charge_config['export_limit']
2940
2954
  export_limit = export_power / dc_ac_loss
2941
2955
  current_mode = get_work_mode()
2956
+ # set parameters for battery_timed()
2957
+ charge_config['charge_limit'] = charge_limit
2958
+ charge_config['charge_power'] = charge_power
2959
+ charge_config['float_charge'] = float_charge
2960
+ charge_config['charge_loss'] = charge_loss
2961
+ charge_config['discharge_loss'] = discharge_loss
2962
+ # display what we have
2942
2963
  output(f"\ncharge_config = {json.dumps(charge_config, indent=2)}", 3)
2943
2964
  output(f"\nDevice Info:")
2944
2965
  output(f" Model: {model}")
@@ -3048,8 +3069,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3048
3069
  output(f"\nSettings will not be updated when forecast is not available")
3049
3070
  update_settings = 0
3050
3071
  # produce time lines for charge, discharge and work mode
3051
- charge_timed = [min([charge_limit, x * charge_config['pv_loss']]) for x in generation_timed]
3052
- discharge_timed = [min([discharge_limit, x / dc_ac_loss]) + bms_loss for x in consumption_timed]
3072
+ charge_timed = [min([charge_limit, c_float(x) * pv_loss]) for x in generation_timed]
3073
+ discharge_timed = [min([discharge_limit, c_float(x) / dc_ac_loss]) + bms_loss for x in consumption_timed]
3053
3074
  work_mode_timed = strategy_timed(timed_mode, base_hour, run_time, min_soc=min_soc, max_soc=max_soc, current_mode=current_mode)
3054
3075
  for i in range(0, len(work_mode_timed)):
3055
3076
  # get work mode
@@ -3240,10 +3261,14 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3240
3261
  # CHARGE_COMPARE - load saved data and compare with actual
3241
3262
  ##################################################################################################
3242
3263
 
3243
- def charge_compare(save=None, v=None, show_data=1, show_plot=3):
3264
+ def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
3244
3265
  global charge_config, storage
3266
+ now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
3267
+ yesterday = datetime.strftime(datetime.date(now - timedelta(days=1)), '%Y-%m-%d')
3245
3268
  if save is None and charge_config.get('save') is not None:
3246
- save = charge_config.get('save').replace('###', base_time.replace(' ', 'T'))
3269
+ save = charge_config.get('save').replace('###', yesterday)
3270
+ if not os.path.exists(storage + save):
3271
+ save = None
3247
3272
  if save is None:
3248
3273
  print(f"** charge_compare(): please provide a saved file to load")
3249
3274
  return
@@ -3387,14 +3412,14 @@ battery_info_app_key = "aug938dqt5cbqhvq69ixc4v39q6wtw"
3387
3412
 
3388
3413
  # show information about the current state of the batteries
3389
3414
  def battery_info(log=0, plot=1, count=None, info=1):
3390
- global debug_setting, battery_info_app_key
3415
+ global debug_setting, battery_info_app_key, residual_handling
3391
3416
  output_spool(battery_info_app_key)
3392
3417
  bat = get_battery(info=info)
3393
3418
  if bat is None:
3394
3419
  output_close()
3395
3420
  return None
3396
3421
  nbat = None
3397
- if bat.get('info') is not None:
3422
+ if info == 1 and bat.get('info') is not None:
3398
3423
  for b in bat['info']:
3399
3424
  output(f"\nSN {b['masterSN']}, {b['masterBatType']}, Version {b['masterVersion']} (BMS)")
3400
3425
  nbat = 0
@@ -3407,7 +3432,7 @@ def battery_info(log=0, plot=1, count=None, info=1):
3407
3432
  bat_current = bat['current']
3408
3433
  bat_power = bat['power']
3409
3434
  bms_temperature = bat['temperature']
3410
- capacity = residual / current_soc * 100
3435
+ capacity = bat['capacity']
3411
3436
  cell_volts = get_cell_volts()
3412
3437
  if cell_volts is None:
3413
3438
  output_close()
@@ -3462,6 +3487,7 @@ def battery_info(log=0, plot=1, count=None, info=1):
3462
3487
  output(f"Cell Volts: {avg(cell_volts):.3f}V average, {max(cell_volts):.3f}V maximum, {min(cell_volts):.3f}V minimum")
3463
3488
  output(f"Cell Imbalance: {imbalance(cell_volts):.2f}%:")
3464
3489
  output(f"BMS Temperature: {bms_temperature:.1f}°C")
3490
+ output(f"BMS Charge Rate: {bat.get('charge_rate'):.1f}A (estimated)")
3465
3491
  output(f"Battery Temperature: {avg(cell_temps):.1f}°C average, {max(cell_temps):.1f}°C maximum, {min(cell_temps):.1f}°C minimum")
3466
3492
  output(f"\nInfo by battery:")
3467
3493
  for i in range(0, nbat):
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 02 October 2024
4
+ Updated: 05 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.5.9"
13
+ version = "2.6.0"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -545,8 +545,24 @@ battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual']
545
545
  # 1 = returns Residual Energy. 2 = resturns Residual Capacity
546
546
  residual_handling = 1
547
547
 
548
+ # charge rates based on residual_handling
549
+ battery_params = {
550
+ # cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
551
+ # bms temp 5 10 15 20 25 30 35 40 45 50 55 60 65
552
+ 1: {'table': [ 0, 2, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 0],
553
+ 'step': 5,
554
+ 'offset': 5,
555
+ 'charge_loss': 0.975,
556
+ 'discharge_loss': 0.975},
557
+ 2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
558
+ 'step': 5,
559
+ 'offset': 5,
560
+ 'charge_loss': 1.040,
561
+ 'discharge_loss': 0.975},
562
+ }
563
+
548
564
  def get_battery(v = None, info=0):
549
- global device_sn, battery, debug_setting, residual_handling
565
+ global device_sn, battery, debug_setting, residual_handling, battery_params
550
566
  if get_device() is None:
551
567
  return None
552
568
  output(f"getting battery", 2)
@@ -560,10 +576,20 @@ def get_battery(v = None, info=0):
560
576
  if residual_handling == 2:
561
577
  capacity = battery.get('residual')
562
578
  soc = battery.get('soc')
563
- battery['residual'] = capacity * soc / 100 if capacity is not None and soc is not None else capacity
564
- if info == 1:
565
- output(f"** get_battery(): info is not available via Open API")
579
+ residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
580
+ else:
581
+ residual = battery.get('residual')
582
+ soc = battery.get('soc')
583
+ capacity = residual / soc * 100 if residual is not None and soc is not None and soc > 0 else None
584
+ battery['capacity'] = round(capacity, 3)
585
+ battery['residual'] = round(residual, 3)
566
586
  battery['status'] = 1
587
+ battery['charge_rate'] = 50
588
+ params = battery_params[residual_handling]
589
+ battery['charge_loss'] = params['charge_loss']
590
+ battery['discharge_loss'] = params['discharge_loss']
591
+ if battery.get('temperature') is not None:
592
+ battery['charge_rate'] = params['table'][int((battery['temperature'] - params['offset']) / params['step'])]
567
593
  return battery
568
594
 
569
595
  ##################################################################################################
@@ -2408,7 +2434,7 @@ def forecast_value_timed(forecast, today, tomorrow, base_hour, run_time, time_of
2408
2434
  profile = []
2409
2435
  h = base_hour - time_offset
2410
2436
  while h < 0:
2411
- profile.append(0.0)
2437
+ profile.append(None)
2412
2438
  h += 1 / steps_per_hour
2413
2439
  while h < 48:
2414
2440
  day = today if h < 24 else tomorrow
@@ -2418,10 +2444,10 @@ def forecast_value_timed(forecast, today, tomorrow, base_hour, run_time, time_of
2418
2444
  value = forecast.daily[day]['hourly'].get(int(h % 24))
2419
2445
  else:
2420
2446
  value = forecast.daily[day]['pt30'].get(hours_time(int(h * 2) / 2))
2421
- profile.append(c_float(value))
2447
+ profile.append(value)
2422
2448
  h += 1 / steps_per_hour
2423
2449
  while len(profile) < run_time:
2424
- profile.append(0.0)
2450
+ profile.append(None)
2425
2451
  return profile[:run_time]
2426
2452
 
2427
2453
  # build the timed work mode profile from the tariff strategy:
@@ -2459,11 +2485,11 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
2459
2485
  # build the timed battery residual from the charge / discharge, work mode and min_soc
2460
2486
  # note: all power values are as measured at the inverter battery connection
2461
2487
  def battery_timed(work_mode_timed, kwh_current, capacity, time_to_next, kwh_min=None, reserve_drain=None):
2462
- global charge_config, steps_per_hour, residual_handling
2488
+ global charge_config, steps_per_hour
2463
2489
  allowed_drain = charge_config['allowed_drain'] if charge_config.get('allowed_drain') is not None else 4
2464
2490
  bms_loss = (charge_config['bms_power'] / 1000 if charge_config.get('bms_power') is not None else 0.05)
2465
- charge_loss = charge_config['charge_loss'][residual_handling - 1]
2466
- discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
2491
+ charge_loss = charge_config['charge_loss']
2492
+ discharge_loss = charge_config['discharge_loss']
2467
2493
  charge_limit = charge_config['charge_limit']
2468
2494
  float_charge = charge_config['float_charge']
2469
2495
  for i in range(0, len(work_mode_timed)):
@@ -2554,8 +2580,6 @@ charge_config = {
2554
2580
  'dc_ac_loss': 0.97, # loss converting battery DC power to AC grid power
2555
2581
  'pv_loss': 0.95, # loss converting PV power to DC battery charge power
2556
2582
  'ac_dc_loss': 0.962, # loss converting AC grid power to DC battery charge power
2557
- 'charge_loss': [0.975, 1.040], # loss in battery energy for each kWh added based on residual_handling
2558
- 'discharge_loss': [0.975, 0.975], # loss in battery energy for each kWh removed based on residual_handling
2559
2583
  'inverter_power': 101, # Inverter power consumption in W
2560
2584
  'bms_power': 50, # BMS power consumption in W
2561
2585
  'force_charge_power': 5.00, # charge power in kW when using force charge
@@ -2576,9 +2600,6 @@ charge_config = {
2576
2600
  'special_contingency': 33, # contingency for special days when consumption might be higher
2577
2601
  'special_days': ['12-25', '12-26', '01-01'],
2578
2602
  'full_charge': None, # day of month (1-28) to do full charge, or 'daily' or 'Mon', 'Tue' etc
2579
- 'derate_temp': 25, # BMS temperature when cold derating starts to be applied
2580
- 'derate_step': 5, # scale for derating factors in C
2581
- 'derating': [24, 15, 10, 2], # max charge current e.g. 5C step = 25C, 20C, 15C, 10C
2582
2603
  'data_wrap': 6, # data items to show per line
2583
2604
  'target_soc': None, # the target SoC for charging (over-rides calculated value)
2584
2605
  'shading': { # effect of shading on Solcast / forecast.solar
@@ -2603,7 +2624,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2603
2624
  def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2604
2625
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2605
2626
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2606
- global timed_strategy, steps_per_hour, base_time, storage, residual_handling
2627
+ global timed_strategy, steps_per_hour, base_time, storage, battery
2607
2628
  print(f"\n---------------- charge_needed ----------------")
2608
2629
  # validate parameters
2609
2630
  args = locals()
@@ -2703,8 +2724,22 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2703
2724
  output(f"base_hour = {base_hour}, hour_adjustment = {hour_adjustment}, change_hour = {change_hour}, time_change = {time_change}")
2704
2725
  output(f"time_to_start = {time_to_start}, run_time = {run_time}, charge_today = {charge_today}")
2705
2726
  output(f"time_to_next = {time_to_next}, full_charge = {full_charge}")
2727
+ if test_soc is not None:
2728
+ current_soc = test_soc
2729
+ capacity = 14.54
2730
+ residual = test_soc * capacity / 100
2731
+ bat_volt = 317.4
2732
+ bat_power = 0.0
2733
+ temperature = 30
2734
+ bms_charge_current = 25
2735
+ charge_loss = 1.040
2736
+ discharge_loss = 0.974
2737
+ bat_current = 0.0
2738
+ device_power = 6.0
2739
+ device_current = 35
2740
+ model = 'H1-6.0-E'
2741
+ else:
2706
2742
  # get device and battery info from inverter
2707
- if test_soc is None:
2708
2743
  get_battery()
2709
2744
  if battery is None or battery['status'] != 1:
2710
2745
  output(f"\nBattery status is not available")
@@ -2715,27 +2750,16 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2715
2750
  bat_current = battery['current']
2716
2751
  temperature = battery['temperature']
2717
2752
  residual = battery['residual']
2718
- if charge_config.get('capacity') is not None:
2719
- capacity = charge_config['capacity']
2720
- elif residual is not None and residual > 0.2 and current_soc is not None and current_soc > 1:
2721
- capacity = residual * 100 / current_soc
2722
- else:
2753
+ capacity = charge_config['capacity'] if charge_config.get('capacity') is not None else battery.get('capacity')
2754
+ if capacity is None:
2723
2755
  output(f"Battery capacity could not be estimated. Please add the parameter 'capacity=xx' in kWh")
2724
2756
  return None
2757
+ bms_charge_current = battery.get('charge_rate')
2758
+ charge_loss = battery['charge_loss'] if battery.get('charge_loss') is not None else 1.0
2759
+ discharge_loss = battery['discharge_loss'] if battery.get('discharge_loss') is not None else 1.0
2725
2760
  device_power = device.get('power')
2726
2761
  device_current = device.get('max_charge_current')
2727
2762
  model = device.get('deviceType')
2728
- else:
2729
- current_soc = test_soc
2730
- capacity = 14.54
2731
- residual = test_soc * capacity / 100
2732
- bat_volt = 317.4
2733
- bat_power = 0.0
2734
- temperature = 30
2735
- bat_current = 0.0
2736
- device_power = 6.0
2737
- device_current = 25
2738
- model = 'H1-6.0-E'
2739
2763
  min_soc = charge_config['min_soc'] if charge_config['min_soc'] is not None else 10
2740
2764
  max_soc = charge_config['max_soc'] if charge_config['max_soc'] is not None else 100
2741
2765
  volt_curve = charge_config['volt_curve']
@@ -2754,26 +2778,13 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2754
2778
  output(f" Current SoC: {current_soc}%")
2755
2779
  output(f" Max SoC: {max_soc}% ({capacity * max_soc / 100:.2f}kWh)")
2756
2780
  output(f" Temperature: {temperature:.1f}°C")
2781
+ output(f" Charge Rate: {bms_charge_current:.1f}A")
2757
2782
  output(f" Resistance: {bat_resistance:.2f} ohms")
2758
2783
  output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
2759
- # charge times are derated based on temperature
2784
+ # charge current may be derated based on temperature
2760
2785
  charge_current = device_current if charge_config['charge_current'] is None else charge_config['charge_current']
2761
- derate_temp = charge_config['derate_temp']
2762
- if temperature > 36:
2763
- output(f"\nHigh battery temperature may affect the charge rate")
2764
- elif round(temperature, 0) <= derate_temp:
2765
- output(f"\nLow battery temperature may affect the charge rate")
2766
- derating = charge_config['derating']
2767
- derate_step = charge_config['derate_step']
2768
- i = int((derate_temp - temperature) / (derate_step if derate_step is not None and derate_step > 0 else 1))
2769
- if derating is not None and type(derating) is list and i < len(derating):
2770
- derated_current = derating[i]
2771
- if derated_current < charge_current:
2772
- output(f" Charge current reduced from {charge_current:.0f}A to {derated_current:.0f}A" )
2773
- charge_current = derated_current
2774
- else:
2775
- bat_hold = 2
2776
- output(f" Full charge set")
2786
+ if charge_current > bms_charge_current:
2787
+ charge_current = bms_charge_current
2777
2788
  # inverter losses
2778
2789
  inverter_power = charge_config['inverter_power'] if charge_config['inverter_power'] is not None else round(device_power, 0) * 25
2779
2790
  operating_loss = inverter_power / 1000
@@ -2787,21 +2798,24 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2787
2798
  force_charge_power = charge_config['force_charge_power'] if timed_mode > 1 and charge_config.get('force_charge_power') is not None else 100
2788
2799
  charge_power = min([(device_power - operating_loss) * ac_dc_loss, force_charge_power * ac_dc_loss, charge_limit])
2789
2800
  float_charge = (charge_config['float_current'] if charge_config.get('float_current') is not None else 4) * bat_ocv / 1000
2790
- charge_config['charge_limit'] = charge_limit
2791
- charge_config['charge_power'] = charge_power
2792
- charge_config['float_charge'] = float_charge
2793
- charge_loss = charge_config['charge_loss'][residual_handling - 1]
2801
+ pv_loss = charge_config['pv_loss']
2794
2802
  # work out discharge limit = max power coming from the battery before ac conversion losses
2795
2803
  dc_ac_loss = charge_config['dc_ac_loss']
2796
2804
  discharge_limit = device_power / dc_ac_loss
2797
2805
  discharge_current = device_current if charge_config['discharge_current'] is None else charge_config['discharge_current']
2798
2806
  discharge_power = discharge_current * bat_ocv / 1000
2799
2807
  discharge_limit = discharge_power if discharge_power < discharge_limit else discharge_limit
2800
- discharge_loss = charge_config['discharge_loss'][residual_handling - 1]
2801
2808
  # charging happens if generation exceeds export limit in feedin work mode
2802
2809
  export_power = device_power if charge_config['export_limit'] is None else charge_config['export_limit']
2803
2810
  export_limit = export_power / dc_ac_loss
2804
2811
  current_mode = get_work_mode()
2812
+ # set parameters for battery_timed()
2813
+ charge_config['charge_limit'] = charge_limit
2814
+ charge_config['charge_power'] = charge_power
2815
+ charge_config['float_charge'] = float_charge
2816
+ charge_config['charge_loss'] = charge_loss
2817
+ charge_config['discharge_loss'] = discharge_loss
2818
+ # display what we have
2805
2819
  output(f"\ncharge_config = {json.dumps(charge_config, indent=2)}", 3)
2806
2820
  output(f"\nDevice Info:")
2807
2821
  output(f" Model: {model}")
@@ -2910,8 +2924,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2910
2924
  output(f"\nSettings will not be updated when forecast is not available")
2911
2925
  update_settings = 0
2912
2926
  # produce time lines for charge, discharge and work mode
2913
- charge_timed = [min([charge_limit, x * charge_config['pv_loss']]) for x in generation_timed]
2914
- discharge_timed = [min([discharge_limit, x / dc_ac_loss]) + bms_loss for x in consumption_timed]
2927
+ charge_timed = [min([charge_limit, c_float(x) * pv_loss]) for x in generation_timed]
2928
+ discharge_timed = [min([discharge_limit, c_float(x) / dc_ac_loss]) + bms_loss for x in consumption_timed]
2915
2929
  work_mode_timed = strategy_timed(timed_mode, base_hour, run_time, min_soc=min_soc, max_soc=max_soc, current_mode=current_mode)
2916
2930
  for i in range(0, len(work_mode_timed)):
2917
2931
  # get work mode
@@ -3103,8 +3117,12 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3103
3117
 
3104
3118
  def charge_compare(save=None, v=None, show_data=1, show_plot=3):
3105
3119
  global charge_config, storage
3120
+ now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
3121
+ yesterday = datetime.strftime(datetime.date(now - timedelta(days=1)), '%Y-%m-%d')
3106
3122
  if save is None and charge_config.get('save') is not None:
3107
- save = charge_config.get('save').replace('###', base_time.replace(' ', 'T'))
3123
+ save = charge_config.get('save').replace('###', yesterday)
3124
+ if not os.path.exists(storage + save):
3125
+ save = None
3108
3126
  if save is None:
3109
3127
  print(f"** charge_compare(): please provide a saved file to load")
3110
3128
  return
@@ -3247,14 +3265,14 @@ battery_info_app_key = "aug938dqt5cbqhvq69ixc4v39q6wtw"
3247
3265
 
3248
3266
  # show information about the current state of the batteries
3249
3267
  def battery_info(log=0, plot=1, count=None, info=1):
3250
- global debug_setting, battery_info_app_key
3268
+ global debug_setting, battery_info_app_key, residual_handling
3251
3269
  output_spool(battery_info_app_key)
3252
3270
  bat = get_battery(info=info)
3253
3271
  if bat is None:
3254
3272
  output_close()
3255
3273
  return None
3256
3274
  nbat = None
3257
- if bat.get('info') is not None:
3275
+ if info == 1 and bat.get('info') is not None:
3258
3276
  for b in bat['info']:
3259
3277
  output(f"\nSN {b['masterSN']}, {b['masterBatType']}, Version {b['masterVersion']} (BMS)")
3260
3278
  nbat = 0
@@ -3267,7 +3285,7 @@ def battery_info(log=0, plot=1, count=None, info=1):
3267
3285
  bat_current = bat['current']
3268
3286
  bat_power = bat['power']
3269
3287
  bms_temperature = bat['temperature']
3270
- capacity = residual / current_soc * 100
3288
+ capacity = bat['capacity']
3271
3289
  cell_volts = get_cell_volts()
3272
3290
  if cell_volts is None:
3273
3291
  output_close()
@@ -3322,6 +3340,7 @@ def battery_info(log=0, plot=1, count=None, info=1):
3322
3340
  output(f"Cell Volts: {avg(cell_volts):.3f}V average, {max(cell_volts):.3f}V maximum, {min(cell_volts):.3f}V minimum")
3323
3341
  output(f"Cell Imbalance: {imbalance(cell_volts):.2f}%:")
3324
3342
  output(f"BMS Temperature: {bms_temperature:.1f}°C")
3343
+ output(f"BMS Charge Rate: {bat.get('charge_rate'):.1f}A (estimated)")
3325
3344
  output(f"Battery Temperature: {avg(cell_temps):.1f}°C average, {max(cell_temps):.1f}°C maximum, {min(cell_temps):.1f}°C minimum")
3326
3345
  output(f"\nInfo by battery:")
3327
3346
  for i in range(0, nbat):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.5.9
3
+ Version: 2.6.0
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
@@ -116,7 +116,12 @@ Each of these calls will return a dictionary or list containing the relevant inf
116
116
 
117
117
  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
118
 
119
- get_battery() returns the current battery status, including 'soc', 'volt', 'current', 'power', 'temperature' and 'residual'. The result also updates f.battery.
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
121
+ + 'capacity': the estimated battery capacity, derrived from 'residual' and 'soc'
122
+ + 'charge_rate': the estimated BMS charge rate available, based on the current 'temperature' of the BMS
123
+ + 'charge_loss': the ratio of the kWh added to the battery for each kWh applied during charging
124
+ + 'discharge_loss': the ratio of the kWh available for each kWh removed from the battery during during discharging
120
125
 
121
126
  get_settings() will return the battery settings and is equivalent to get_charge() and get_min(). The results are stored in f.battery_settings. The settings include minSoc, minSocOnGrid, enable charge from grid and the charge times.
122
127
 
@@ -371,8 +376,6 @@ export_limit: None # maximum export power in kW. None uses the inver
371
376
  dc_ac_loss: 0.970 # loss converting battery DC power to AC grid power
372
377
  pv_loss: 0.950 # loss converting PV power to DC battery charge power
373
378
  ac_dc_loss: 0.960 # loss converting AC grid power to DC battery charge power
374
- charge_loss: [0.975, 1.040] # loss in battery energy for each kWh added (based on residual_handling)
375
- discharge_loss: [0.975, 0.975] # loss in battery energy for each kWh removed (based on residual_handling)
376
379
  inverter_power: None # inverter power consumption in W (dynamically set)
377
380
  bms_power: 50 # BMS power consumption in W
378
381
  force_charge_power: 5.00 # power used when Force Charge is scheduled
@@ -395,9 +398,6 @@ timed_mode: 0 # 0 = None, 1 = use timed work mode, 2 = strategy
395
398
  special_contingency: 30 # contingency for special days when consumption might be higher
396
399
  special_days: ['12-25', '12-26', '01-01']
397
400
  full_charge: None # day of month (1-28) to do full charge or 'daily' or day of week: 'Mon', 'Tue' etc
398
- derate_temp: 28 # battery temperature in C when derating charge current is applied
399
- derate_step: 5 # step size for derating e.g. 21, 16, 11
400
- derating: [24, 15, 10, 2] # derated charge current for each temperature step e.g. 28C, 23C, 18C, 13C
401
401
  force: 1 # 1 = disable strategy periods when setting charge. 0 = fail if strategy period has been set.
402
402
  data_wrap: 6 # data items to show per line
403
403
  target_soc: None # target soc for charging (over-rides calculated value)
@@ -786,6 +786,9 @@ This setting can be:
786
786
 
787
787
  # Version Info
788
788
 
789
+ 2.6.0<br>
790
+ Rework charge de-rating with temperature, losses and other info provided by get_battery() to take new BMS behaviour into account.
791
+
789
792
  2.5.9<br>
790
793
  Change loss parameters to separate AC/DC, DC/AC conversion losses and battery charge / discharge losses.
791
794
  Update charge calibration for new BMS firmware.
File without changes
File without changes