foxesscloud 2.7.7__tar.gz → 2.8.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.7.7
3
+ Version: 2.8.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
@@ -358,7 +358,9 @@ All the parameters are optional:
358
358
 
359
359
  charge_needed() uses a number of models to better estimate the state of the battery.
360
360
 
361
- **Manual Consumption:** You can provide your 'annual_consumption' in kWh e.g. 5500. This figure is factored down to a daily consumption by dividing by 365 and applying **f.seasonality**. This normally decreases consumption in the summer and increases it in winter. Seasonality is a list of weightings by month for Jan, Feb, Mar, Apr etc. Preset lists are 'f.high_seasonality' (recommend where electric heating is ued), 'f.medium_seasonality' (default) amd 'f.no_seasonality' (all months the same). The daily consumption is profiled by hour using **f.daily_consumption**. This maps your consumption for a day to the hours when more or less energy is consumed. It is a list of 24 values for the times 00, 01, 02, 03 .. 23. Preset lists are 'f.high_profile' (larger peaks at 8am and 6pm), 'f.medium_profile' (default, more balanced) and 'f.no_profile' (flat).
361
+ **Manual Consumption:** You can provide your expected 'consumption' in kWh e.g. 15. This figure is used directly.
362
+
363
+ **Annual Consumption:** You can provide your 'annual_consumption' in kWh e.g. 5500. This figure is factored down to a daily consumption by dividing by 365 and applying **f.seasonality**. This normally decreases consumption in the summer and increases it in winter. Seasonality is a list of weightings by month for Jan, Feb, Mar, Apr etc. Preset lists are 'f.high_seasonality' (recommend where electric heating is ued), 'f.medium_seasonality' (default) amd 'f.no_seasonality' (all months the same). The daily consumption is profiled by hour using **f.daily_consumption**. This maps your consumption for a day to the hours when more or less energy is consumed. It is a list of 24 values for the times 00, 01, 02, 03 .. 23. Preset lists are 'f.high_profile' (larger peaks at 8am and 6pm), 'f.medium_profile' (default, more balanced) and 'f.no_profile' (flat).
362
364
 
363
365
  **Historic Consumption:** If annual_consumption is not provided, your consumption history is used. By default, this looks at your average consumption for the last 3 days using the load power reported by your inverter. For systems with multiple inverters where CT2 is not connected, the load power may not be correct. For this and other cases where you want to set your consumption, provide your annual_consumption.
364
366
 
@@ -807,6 +809,18 @@ This setting can be:
807
809
 
808
810
  # Version Info
809
811
 
812
+ 2.8.0<br>
813
+ Update from v0 to v1 for scheduler API.
814
+ PVEnergyTotal added to report variables.
815
+ Fix residual if capacity is specified for charge_needed().
816
+
817
+ 2.7.9<br>
818
+ Add 'consumption' input for charge_needed().
819
+ Avoid throwing exception in battery_info() if there is no capacity info.
820
+
821
+ 2.7.8<br>
822
+ Update the battery_params charge rate table to 2025 values.
823
+
810
824
  2.7.7<br>
811
825
  Updates to get_pvoutput() to support solar inverters that don't provide grid energy by setting tou=2.
812
826
  Default tariff changed to None.
@@ -344,7 +344,9 @@ All the parameters are optional:
344
344
 
345
345
  charge_needed() uses a number of models to better estimate the state of the battery.
346
346
 
347
- **Manual Consumption:** You can provide your 'annual_consumption' in kWh e.g. 5500. This figure is factored down to a daily consumption by dividing by 365 and applying **f.seasonality**. This normally decreases consumption in the summer and increases it in winter. Seasonality is a list of weightings by month for Jan, Feb, Mar, Apr etc. Preset lists are 'f.high_seasonality' (recommend where electric heating is ued), 'f.medium_seasonality' (default) amd 'f.no_seasonality' (all months the same). The daily consumption is profiled by hour using **f.daily_consumption**. This maps your consumption for a day to the hours when more or less energy is consumed. It is a list of 24 values for the times 00, 01, 02, 03 .. 23. Preset lists are 'f.high_profile' (larger peaks at 8am and 6pm), 'f.medium_profile' (default, more balanced) and 'f.no_profile' (flat).
347
+ **Manual Consumption:** You can provide your expected 'consumption' in kWh e.g. 15. This figure is used directly.
348
+
349
+ **Annual Consumption:** You can provide your 'annual_consumption' in kWh e.g. 5500. This figure is factored down to a daily consumption by dividing by 365 and applying **f.seasonality**. This normally decreases consumption in the summer and increases it in winter. Seasonality is a list of weightings by month for Jan, Feb, Mar, Apr etc. Preset lists are 'f.high_seasonality' (recommend where electric heating is ued), 'f.medium_seasonality' (default) amd 'f.no_seasonality' (all months the same). The daily consumption is profiled by hour using **f.daily_consumption**. This maps your consumption for a day to the hours when more or less energy is consumed. It is a list of 24 values for the times 00, 01, 02, 03 .. 23. Preset lists are 'f.high_profile' (larger peaks at 8am and 6pm), 'f.medium_profile' (default, more balanced) and 'f.no_profile' (flat).
348
350
 
349
351
  **Historic Consumption:** If annual_consumption is not provided, your consumption history is used. By default, this looks at your average consumption for the last 3 days using the load power reported by your inverter. For systems with multiple inverters where CT2 is not connected, the load power may not be correct. For this and other cases where you want to set your consumption, provide your annual_consumption.
350
352
 
@@ -793,6 +795,18 @@ This setting can be:
793
795
 
794
796
  # Version Info
795
797
 
798
+ 2.8.0<br>
799
+ Update from v0 to v1 for scheduler API.
800
+ PVEnergyTotal added to report variables.
801
+ Fix residual if capacity is specified for charge_needed().
802
+
803
+ 2.7.9<br>
804
+ Add 'consumption' input for charge_needed().
805
+ Avoid throwing exception in battery_info() if there is no capacity info.
806
+
807
+ 2.7.8<br>
808
+ Update the battery_params charge rate table to 2025 values.
809
+
796
810
  2.7.7<br>
797
811
  Updates to get_pvoutput() to support solar inverters that don't provide grid energy by setting tou=2.
798
812
  Default tariff changed to None.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "foxesscloud"
7
- version = "2.7.7"
7
+ version = "2.8.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: 11 January 2025
4
+ Updated: 12 March 2025
5
5
  By: Tony Matthews
6
6
  """
7
7
  ##################################################################################################
@@ -10,7 +10,7 @@ By: Tony Matthews
10
10
  # ALL RIGHTS ARE RESERVED © Tony Matthews 2023
11
11
  ##################################################################################################
12
12
 
13
- version = "1.8.8"
13
+ version = "1.9.2"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -590,32 +590,37 @@ battery_settings = None
590
590
 
591
591
  # 1 = Residual Energy, 2 = Residual Capacity (HV), 3 = Residual Capacity per battery (Mira)
592
592
  residual_handling = 1
593
+ residual_scale = 0.01
593
594
 
594
- # charge rates based on residual_handling
595
+ # charge rates based on residual_handling. Index is bms temperature
595
596
  battery_params = {
596
- # cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
597
597
  # bms temp 5 10 15 20 25 30 35 40 45 50 55 60 65
598
+ # cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
598
599
  1: {'table': [ 0, 2, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 0],
599
600
  'step': 5,
600
601
  'offset': 5,
601
602
  'charge_loss': 0.974,
602
603
  'discharge_loss': 0.974},
603
- # HV BMS v2 with firmware 1.014 or later
604
- 2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
604
+ # HV BMS v2 with firmware 1.014 or later
605
+ # bms temp 10 15 20 25 30 35 40 45 50 55 60 65 70
606
+ # cell temp 0 5 10 15 20 25 30 35 40 45 50 55 60
607
+ 2: {'table': [ 0, 5, 10, 15, 25, 50, 50, 50, 50, 25, 20, 3, 0],
605
608
  'step': 5,
606
- 'offset': 5,
609
+ 'offset': 11,
607
610
  'charge_loss': 1.08,
608
611
  'discharge_loss': 0.95},
609
- # Mira BMS with firmware 1.014 or later
610
- 3: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
612
+ # Mira BMS with firmware 1.014 or later
613
+ # bms temp 10 15 20 25 30 35 40 45 50 55 60 65 70
614
+ # cell temp 0 5 10 15 20 25 30 35 40 45 50 55 60
615
+ 3: {'table': [ 0, 5, 10, 15, 25, 50, 50, 50, 50, 25, 20, 3, 0],
611
616
  'step': 5,
612
- 'offset': 5,
617
+ 'offset': 11,
613
618
  'charge_loss': 0.974,
614
619
  'discharge_loss': 0.974},
615
620
  }
616
621
 
617
622
  def get_battery(info=1, rated=None, count=None):
618
- global device_id, battery, debug_setting, messages, residual_handling, battery_params
623
+ global device_id, battery, debug_setting, messages, residual_handling, battery_params, residual_scale
619
624
  if get_device() is None:
620
625
  return None
621
626
  output(f"getting battery", 2)
@@ -655,7 +660,7 @@ def get_battery(info=1, rated=None, count=None):
655
660
  output(f"** get_battery(): battery status not available")
656
661
  return None
657
662
  if battery.get('residual') is not None:
658
- battery['residual'] /= 1000
663
+ battery['residual'] *= residual_scale / 10
659
664
  if battery['residual_handling'] == 2:
660
665
  capacity = battery.get('residual')
661
666
  soc = battery.get('soc')
@@ -1822,7 +1827,7 @@ def rescale_history(data, steps):
1822
1827
  # station = 0: use device_id, 1 = use station_id
1823
1828
  ##################################################################################################
1824
1829
 
1825
- report_vars = ['yield', 'input','generation', 'feedin', 'loads', 'gridConsumption', 'chargeEnergyToTal', 'dischargeEnergyToTal']
1830
+ report_vars = ['PVEnergyTotal', 'input','generation', 'feedin', 'loads', 'gridConsumption', 'chargeEnergyToTal', 'dischargeEnergyToTal']
1826
1831
  report_names = ['PV Yield', 'Input', 'Generation', 'Grid Export', 'Consumption', 'Grid Import', 'Battery Charge', 'Battery Discharge']
1827
1832
 
1828
1833
  # fix power values after fox corrupts high word of 32-bit energy total
@@ -2953,6 +2958,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2953
2958
 
2954
2959
  # work out the charge times to set using the parameters:
2955
2960
  # forecast: the kWh expected tomorrow. If none, forecast data is loaded from solcast etc
2961
+ # consumption: the kWh consumed. If none, consumption is loaded from history
2956
2962
  # update_settings: 0 no updates, 1 update charge settings. The default is 0
2957
2963
  # show_data: 1 shows battery SoC, 2 shows battery residual. Default = 0
2958
2964
  # show_plot: 1 plots battery SoC, 2 plots battery residual. Default = 1
@@ -2960,7 +2966,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2960
2966
  # forecast_times: list of hours when forecast can be fetched (UTC)
2961
2967
  # force_charge: 1 = hold battery, 2 = charge for whole period
2962
2968
 
2963
- def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2969
+ def charge_needed(forecast=None, consumption=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2964
2970
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2965
2971
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2966
2972
  global timed_strategy, steps_per_hour, base_time, storage, battery, battery_params
@@ -3088,7 +3094,10 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3088
3094
  bat_current = battery['current']
3089
3095
  temperature = battery['temperature']
3090
3096
  residual = battery['residual']
3091
- capacity = charge_config['capacity'] if charge_config.get('capacity') is not None else battery.get('capacity')
3097
+ capacity = battery.get('capacity')
3098
+ if charge_config.get('capacity') is not None:
3099
+ capacity = charge_config['capacity']
3100
+ residual = (capacity * current_soc / 100) if capacity is not None and current_soc is not None else None
3092
3101
  if capacity is None:
3093
3102
  output(f"Battery capacity could not be estimated. Please add the parameter 'capacity=xx' in kWh")
3094
3103
  return None
@@ -3172,6 +3181,9 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3172
3181
  consumption = annual_consumption / 365 * seasonality[now.month - 1] / sum(seasonality) * 12
3173
3182
  consumption_by_hour = daily_consumption
3174
3183
  output(f"\nEstimated consumption: {consumption:.1f}kWh")
3184
+ elif consumption is not None:
3185
+ consumption_by_hour = daily_consumption
3186
+ output(f"\nConsumption: {consumption:.1f}kWh")
3175
3187
  else:
3176
3188
  consumption_days = charge_config['consumption_days']
3177
3189
  consumption_days = 3 if consumption_days > 7 or consumption_days < 1 else consumption_days
@@ -3633,7 +3645,7 @@ def battery_info(log=0, plot=1, rated=None, count=None, info=1, bat=None):
3633
3645
  bat_current = bat['current']
3634
3646
  bat_power = bat['power']
3635
3647
  bms_temperature = bat['temperature']
3636
- capacity = bat['capacity']
3648
+ capacity = bat.get('capacity')
3637
3649
  cell_volts = get_cell_volts()
3638
3650
  if cell_volts is None:
3639
3651
  output_close()
@@ -3678,7 +3690,8 @@ def battery_info(log=0, plot=1, rated=None, count=None, info=1, bat=None):
3678
3690
  s +=f",{v:.0f}"
3679
3691
  return s
3680
3692
  output(f"Current SoC: {current_soc}%")
3681
- output(f"Capacity: {capacity:.2f}kWh" + (" (calculated)" if bat['residual_handling'] in [1,3] else ""))
3693
+ if capacity is not None:
3694
+ output(f"Capacity: {capacity:.2f}kWh" + (" (calculated)" if bat['residual_handling'] in [1,3] else ""))
3682
3695
  output(f"Residual: {residual:.2f}kWh" + (" (calculated)" if bat['residual_handling'] in [2,3] else ""))
3683
3696
  if rated_capacity is not None and bat_soh is not None:
3684
3697
  output(f"Rated Capacity: {rated_capacity / 1000:.2f}kWh")
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 11 January 2025
4
+ Updated: 12 March 2025
5
5
  By: Tony Matthews
6
6
  """
7
7
  ##################################################################################################
@@ -10,7 +10,7 @@ By: Tony Matthews
10
10
  # ALL RIGHTS ARE RESERVED © Tony Matthews 2024
11
11
  ##################################################################################################
12
12
 
13
- version = "2.7.7"
13
+ version = "2.8.0"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -555,25 +555,29 @@ battery_data = ['soc', 'volt', 'current', 'power', 'temperature', 'residual']
555
555
  # 1 = Residual Energy, 2 = Residual Capacity (HV), 3 = Residual Capacity per battery (Mira)
556
556
  residual_handling = 1
557
557
 
558
- # charge rates based on residual_handling
558
+ # charge rates based on residual_handling. Index is bms temperature
559
559
  battery_params = {
560
- # cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
561
560
  # bms temp 5 10 15 20 25 30 35 40 45 50 55 60 65
561
+ # cell temp -5 0 5 10 15 20 25 30 35 40 45 50 55
562
562
  1: {'table': [ 0, 2, 10, 15, 25, 50, 50, 50, 50, 50, 30, 20, 0],
563
563
  'step': 5,
564
564
  'offset': 5,
565
565
  'charge_loss': 0.974,
566
566
  'discharge_loss': 0.974},
567
- # HV BMS v2 with firmware 1.014 or later
568
- 2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
567
+ # HV BMS v2 with firmware 1.014 or later
568
+ # bms temp 10 15 20 25 30 35 40 45 50 55 60 65 70
569
+ # cell temp 0 5 10 15 20 25 30 35 40 45 50 55 60
570
+ 2: {'table': [ 0, 5, 10, 15, 25, 50, 50, 50, 50, 25, 20, 3, 0],
569
571
  'step': 5,
570
- 'offset': 5,
572
+ 'offset': 11,
571
573
  'charge_loss': 1.08,
572
574
  'discharge_loss': 0.95},
573
- # Mira BMS with firmware 1.014 or later
574
- 3: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
575
+ # Mira BMS with firmware 1.014 or later
576
+ # bms temp 10 15 20 25 30 35 40 45 50 55 60 65 70
577
+ # cell temp 0 5 10 15 20 25 30 35 40 45 50 55 60
578
+ 3: {'table': [ 0, 5, 10, 15, 25, 50, 50, 50, 50, 25, 20, 3, 0],
575
579
  'step': 5,
576
- 'offset': 5,
580
+ 'offset': 11,
577
581
  'charge_loss': 0.974,
578
582
  'discharge_loss': 0.974},
579
583
  }
@@ -1006,7 +1010,7 @@ def get_flag():
1006
1010
  return None
1007
1011
  output(f"getting flag", 2)
1008
1012
  body = {'deviceSN': device_sn}
1009
- response = signed_post(path="/op/v0/device/scheduler/get/flag", body=body)
1013
+ response = signed_post(path="/op/v1/device/scheduler/get/flag", body=body)
1010
1014
  if response.status_code != 200:
1011
1015
  output(f"** get_flag() got response code {response.status_code}: {response.reason}")
1012
1016
  return None
@@ -1036,7 +1040,7 @@ def get_schedule():
1036
1040
  return None
1037
1041
  output(f"getting schedule", 2)
1038
1042
  body = {'deviceSN': device_sn}
1039
- response = signed_post(path="/op/v0/device/scheduler/get", body=body)
1043
+ response = signed_post(path="/op/v1/device/scheduler/get", body=body)
1040
1044
  if response.status_code != 200:
1041
1045
  output(f"** get_schedule() got response code {response.status_code}: {response.reason}")
1042
1046
  return None
@@ -1129,7 +1133,7 @@ def set_period(start=None, end=None, mode=None, min_soc=None, max_soc=None, fdso
1129
1133
  period = {'enable': enable, 'startHour': start_hour, 'startMinute': start_minute, 'endHour': end_hour, 'endMinute': end_minute, 'workMode': mode,
1130
1134
  'minSocOnGrid': int(min_soc), 'fdSoc': int(fdsoc), 'fdPwr': int(fdpwr)}
1131
1135
  if max_soc is not None:
1132
- period['maxsoc'] = int(max_soc)
1136
+ period['maxSoc'] = int(max_soc)
1133
1137
  return period
1134
1138
 
1135
1139
  # set a schedule from a period or list of time segment periods
@@ -1156,7 +1160,7 @@ def set_schedule(periods=None, enable=True):
1156
1160
  output(f"** set_schedule(): maximum of 8 periods allowed, {len(periods)} provided")
1157
1161
  body = {'deviceSN': device_sn, 'groups': periods[-8:]}
1158
1162
  setting_delay()
1159
- response = signed_post(path="/op/v0/device/scheduler/enable", body=body)
1163
+ response = signed_post(path="/op/v1/device/scheduler/enable", body=body)
1160
1164
  if response.status_code != 200:
1161
1165
  output(f"** set_schedule() periods response code {response.status_code}: {response.reason}")
1162
1166
  return None
@@ -1167,7 +1171,7 @@ def set_schedule(periods=None, enable=True):
1167
1171
  schedule['periods'] = periods
1168
1172
  body = {'deviceSN': device_sn, 'enable': 1 if enable else 0}
1169
1173
  setting_delay()
1170
- response = signed_post(path="/op/v0/device/scheduler/set/flag", body=body)
1174
+ response = signed_post(path="/op/v1/device/scheduler/set/flag", body=body)
1171
1175
  if response.status_code != 200:
1172
1176
  output(f"** set_schedule() flag response code {response.status_code}: {response.reason}")
1173
1177
  return None
@@ -1526,8 +1530,8 @@ def rescale_history(data, steps):
1526
1530
  # plot = 0: no plot, 1 = plot variables separately, 2 = combine variables
1527
1531
  ##################################################################################################
1528
1532
 
1529
- report_vars = ['generation', 'feedin', 'loads', 'gridConsumption', 'chargeEnergyToTal', 'dischargeEnergyToTal']
1530
- report_names = ['Generation', 'Grid Export', 'Consumption', 'Grid Import', 'Battery Charge', 'Battery Discharge']
1533
+ report_vars = ['generation', 'feedin', 'loads', 'gridConsumption', 'chargeEnergyToTal', 'dischargeEnergyToTal', 'PVEnergyTotal']
1534
+ report_names = ['Generation', 'Grid Export', 'Consumption', 'Grid Import', 'Battery Charge', 'Battery Discharge', 'PV Yield']
1531
1535
 
1532
1536
  # fix power values after corruption of high word of 32-bit energy total
1533
1537
  fix_values = 1
@@ -2624,6 +2628,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2624
2628
 
2625
2629
  # work out the charge times to set using the parameters:
2626
2630
  # forecast: the kWh expected tomorrow. If none, forecast data is loaded from solcast etc
2631
+ # consumption: the kWh consumed. If none, consumption is loaded from history
2627
2632
  # update_settings: 0 no updates, 1 update charge settings. The default is 0
2628
2633
  # show_data: 1 shows battery SoC, 2 shows battery residual. Default = 0
2629
2634
  # show_plot: 1 plots battery SoC, 2 plots battery residual. Default = 1
@@ -2631,7 +2636,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2631
2636
  # forecast_times: list of hours when forecast can be fetched (UTC)
2632
2637
  # force_charge: 1 = hold battery, 2 = charge for whole period
2633
2638
 
2634
- def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2639
+ def charge_needed(forecast=None, consumption=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2635
2640
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2636
2641
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2637
2642
  global timed_strategy, steps_per_hour, base_time, storage, battery, battery_params
@@ -2759,7 +2764,10 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2759
2764
  bat_current = battery['current']
2760
2765
  temperature = battery['temperature']
2761
2766
  residual = battery['residual']
2762
- capacity = charge_config['capacity'] if charge_config.get('capacity') is not None else battery.get('capacity')
2767
+ capacity = battery.get('capacity')
2768
+ if charge_config.get('capacity') is not None:
2769
+ capacity = charge_config['capacity']
2770
+ residual = (capacity * current_soc / 100) if capacity is not None and current_soc is not None else None
2763
2771
  if capacity is None:
2764
2772
  output(f"Battery capacity could not be estimated. Please add the parameter 'capacity=xx' in kWh")
2765
2773
  return None
@@ -2847,6 +2855,9 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2847
2855
  consumption = annual_consumption / 365 * seasonality[now.month - 1] / sum(seasonality) * 12
2848
2856
  consumption_by_hour = daily_consumption
2849
2857
  output(f"\nEstimated consumption: {consumption:.1f}kWh")
2858
+ elif consumption is not None:
2859
+ consumption_by_hour = daily_consumption
2860
+ output(f"\nConsumption: {consumption:.1f}kWh")
2850
2861
  else:
2851
2862
  consumption_days = charge_config['consumption_days']
2852
2863
  consumption_days = 3 if consumption_days > 7 or consumption_days < 1 else consumption_days
@@ -3305,7 +3316,7 @@ def battery_info(log=0, plot=1, rated=None, count=None, info=1, bat=None):
3305
3316
  bat_current = bat['current']
3306
3317
  bat_power = bat['power']
3307
3318
  bms_temperature = bat['temperature']
3308
- capacity = bat['capacity']
3319
+ capacity = bat.get('capacity')
3309
3320
  cell_volts = get_cell_volts()
3310
3321
  if cell_volts is None:
3311
3322
  output_close()
@@ -3350,7 +3361,8 @@ def battery_info(log=0, plot=1, rated=None, count=None, info=1, bat=None):
3350
3361
  s +=f",{v:.0f}"
3351
3362
  return s
3352
3363
  output(f"Current SoC: {current_soc}%")
3353
- output(f"Capacity: {capacity:.2f}kWh" + (" (calculated)" if bat['residual_handling'] in [1,3] else ""))
3364
+ if capacity is not None:
3365
+ output(f"Capacity: {capacity:.2f}kWh" + (" (calculated)" if bat['residual_handling'] in [1,3] else ""))
3354
3366
  output(f"Residual: {residual:.2f}kWh" + (" (calculated)" if bat['residual_handling'] in [2,3] else ""))
3355
3367
  if rated_capacity is not None and bat_soh is not None:
3356
3368
  output(f"Rated Capacity: {rated_capacity / 1000:.2f}kWh")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.7.7
3
+ Version: 2.8.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
@@ -358,7 +358,9 @@ All the parameters are optional:
358
358
 
359
359
  charge_needed() uses a number of models to better estimate the state of the battery.
360
360
 
361
- **Manual Consumption:** You can provide your 'annual_consumption' in kWh e.g. 5500. This figure is factored down to a daily consumption by dividing by 365 and applying **f.seasonality**. This normally decreases consumption in the summer and increases it in winter. Seasonality is a list of weightings by month for Jan, Feb, Mar, Apr etc. Preset lists are 'f.high_seasonality' (recommend where electric heating is ued), 'f.medium_seasonality' (default) amd 'f.no_seasonality' (all months the same). The daily consumption is profiled by hour using **f.daily_consumption**. This maps your consumption for a day to the hours when more or less energy is consumed. It is a list of 24 values for the times 00, 01, 02, 03 .. 23. Preset lists are 'f.high_profile' (larger peaks at 8am and 6pm), 'f.medium_profile' (default, more balanced) and 'f.no_profile' (flat).
361
+ **Manual Consumption:** You can provide your expected 'consumption' in kWh e.g. 15. This figure is used directly.
362
+
363
+ **Annual Consumption:** You can provide your 'annual_consumption' in kWh e.g. 5500. This figure is factored down to a daily consumption by dividing by 365 and applying **f.seasonality**. This normally decreases consumption in the summer and increases it in winter. Seasonality is a list of weightings by month for Jan, Feb, Mar, Apr etc. Preset lists are 'f.high_seasonality' (recommend where electric heating is ued), 'f.medium_seasonality' (default) amd 'f.no_seasonality' (all months the same). The daily consumption is profiled by hour using **f.daily_consumption**. This maps your consumption for a day to the hours when more or less energy is consumed. It is a list of 24 values for the times 00, 01, 02, 03 .. 23. Preset lists are 'f.high_profile' (larger peaks at 8am and 6pm), 'f.medium_profile' (default, more balanced) and 'f.no_profile' (flat).
362
364
 
363
365
  **Historic Consumption:** If annual_consumption is not provided, your consumption history is used. By default, this looks at your average consumption for the last 3 days using the load power reported by your inverter. For systems with multiple inverters where CT2 is not connected, the load power may not be correct. For this and other cases where you want to set your consumption, provide your annual_consumption.
364
366
 
@@ -807,6 +809,18 @@ This setting can be:
807
809
 
808
810
  # Version Info
809
811
 
812
+ 2.8.0<br>
813
+ Update from v0 to v1 for scheduler API.
814
+ PVEnergyTotal added to report variables.
815
+ Fix residual if capacity is specified for charge_needed().
816
+
817
+ 2.7.9<br>
818
+ Add 'consumption' input for charge_needed().
819
+ Avoid throwing exception in battery_info() if there is no capacity info.
820
+
821
+ 2.7.8<br>
822
+ Update the battery_params charge rate table to 2025 values.
823
+
810
824
  2.7.7<br>
811
825
  Updates to get_pvoutput() to support solar inverters that don't provide grid energy by setting tou=2.
812
826
  Default tariff changed to None.
File without changes
File without changes