foxesscloud 2.7.1__tar.gz → 2.7.2__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.1
3
+ Version: 2.7.2
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
@@ -807,6 +807,13 @@ This setting can be:
807
807
 
808
808
  # Version Info
809
809
 
810
+ 2.7.2<br>
811
+ Fix to get_battery() to return error and flag status=0 in f.battery when the cloud is not returning valid data.
812
+ Fix exception calculating SoH if ratedCapacity is returned as 0 when cloud is not returning valid data.
813
+ Update charge_rate in charge_needed() to use a blended charge rate based on battery warming up during charging.
814
+ Fix exception in set_charge() caused by incorrect default parameter values.
815
+ Update charge_needed() to only show forecast that is in use.
816
+
810
817
  2.7.1<br>
811
818
  Update charge_needed() so it only gets generation history if there is no forecast to reduce API calls and save time.
812
819
  Update default parameter values for set_charge() so the other time period is cleared if you only set 1 time.
@@ -816,7 +823,6 @@ Update battery predictions to more accurately reflect what happens when SoC gets
816
823
  Correct model to use inverter operating losses instead of BMS losses when the battery is above min_soc.
817
824
  Correct exception in Solcast and Solar when a forecast is not available.
818
825
 
819
-
820
826
  2.7.0<br>
821
827
  Allow charge_loss / discharge_loss to be configured for charge_needed().
822
828
  Change 'Force Charge' to 'Battery Hold' in charge times to avoid confusion with Force Charge work mode.
@@ -793,6 +793,13 @@ This setting can be:
793
793
 
794
794
  # Version Info
795
795
 
796
+ 2.7.2<br>
797
+ Fix to get_battery() to return error and flag status=0 in f.battery when the cloud is not returning valid data.
798
+ Fix exception calculating SoH if ratedCapacity is returned as 0 when cloud is not returning valid data.
799
+ Update charge_rate in charge_needed() to use a blended charge rate based on battery warming up during charging.
800
+ Fix exception in set_charge() caused by incorrect default parameter values.
801
+ Update charge_needed() to only show forecast that is in use.
802
+
796
803
  2.7.1<br>
797
804
  Update charge_needed() so it only gets generation history if there is no forecast to reduce API calls and save time.
798
805
  Update default parameter values for set_charge() so the other time period is cleared if you only set 1 time.
@@ -802,7 +809,6 @@ Update battery predictions to more accurately reflect what happens when SoC gets
802
809
  Correct model to use inverter operating losses instead of BMS losses when the battery is above min_soc.
803
810
  Correct exception in Solcast and Solar when a forecast is not available.
804
811
 
805
-
806
812
  2.7.0<br>
807
813
  Allow charge_loss / discharge_loss to be configured for charge_needed().
808
814
  Change 'Force Charge' to 'Battery Hold' in charge times to avoid confusion with Force Charge work mode.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "foxesscloud"
7
- version = "2.7.1"
7
+ version = "2.7.2"
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: 07 November 2024
4
+ Updated: 23 November 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.8.2"
13
+ version = "1.8.3"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -615,6 +615,7 @@ def get_battery(info=1, rated=None, count=None):
615
615
  if get_device() is None:
616
616
  return None
617
617
  output(f"getting battery", 2)
618
+ battery = None
618
619
  params = {'id': device_id}
619
620
  response = signed_get(path="/c/v0/device/battery/info", params=params)
620
621
  if response.status_code != 200:
@@ -625,11 +626,8 @@ def get_battery(info=1, rated=None, count=None):
625
626
  errno = response.json().get('errno')
626
627
  output(f"** get_battery(), no result data, {errno_message(errno)}")
627
628
  return None
628
- saved_info = battery['info'] if battery is not None and battery.get('info') is not None else None
629
629
  battery = result
630
- if saved_info is not None:
631
- battery['info'] = saved_info
632
- elif info == 1:
630
+ if info == 1:
633
631
  response = signed_get(path="/generic/v0/device/battery/list", params=params)
634
632
  if response.status_code != 200:
635
633
  output(f"** get_battery().info got response code {response.status_code}: {response.reason}")
@@ -649,6 +647,9 @@ def get_battery(info=1, rated=None, count=None):
649
647
  battery['residual_handling'] = residual_handling
650
648
  battery['soh'] = None
651
649
  battery['soh_supported'] = False
650
+ if battery.get('status') is None or battery['status'] != 1:
651
+ output(f"** get_battery(): battery status not available")
652
+ return None
652
653
  if battery.get('residual') is not None:
653
654
  battery['residual'] /= 1000
654
655
  if battery['residual_handling'] == 2:
@@ -680,9 +681,9 @@ def get_battery(info=1, rated=None, count=None):
680
681
  battery['charge_loss'] = params['charge_loss']
681
682
  battery['discharge_loss'] = params['discharge_loss']
682
683
  if battery.get('ratedCapacity') is not None and battery.get('capacity') is not None:
683
- battery['soh'] = round(battery['capacity'] * 1000 / battery['ratedCapacity'] * 100, 1)
684
+ battery['soh'] = round(battery['capacity'] * 1000 / battery['ratedCapacity'] * 100, 1) if battery['ratedCapacity'] > 0.0 else None
684
685
  if battery.get('temperature') is not None:
685
- battery['charge_rate'] = params['table'][int((battery['temperature'] - params['offset']) / params['step'])]
686
+ battery['charge_rate'] = interpolate((battery['temperature'] - params['offset']) / params['step'], params['table'])
686
687
  return battery
687
688
 
688
689
  def get_batteries(info=1, rated=None, count=None):
@@ -690,6 +691,7 @@ def get_batteries(info=1, rated=None, count=None):
690
691
  if get_device() is None:
691
692
  return None
692
693
  output(f"getting batteries", 2)
694
+ batteries = None
693
695
  params = {'id': device_id}
694
696
  response = signed_get(path="/generic/v0/device/battery/info", params=params)
695
697
  if response.status_code != 200:
@@ -722,6 +724,9 @@ def get_batteries(info=1, rated=None, count=None):
722
724
  while len(count) < len(batteries):
723
725
  count.append(None)
724
726
  for i,b in enumerate(batteries):
727
+ if b.get('status') is None or b['status'] != 1:
728
+ output(f"** get_batteries(): battery {i+1} status not available")
729
+ continue
725
730
  b['residual_handling'] = residual_handling
726
731
  if b.get('info') is not None:
727
732
  if b['info'].get('slaveBatteries') is not None:
@@ -738,6 +743,8 @@ def get_batteries(info=1, rated=None, count=None):
738
743
  b['soh'] = int(soh) if soh.isnumeric() and int(soh) > 10 else None
739
744
  b['soh_supported'] = b['soh'] is not None
740
745
  for i, b in enumerate(batteries):
746
+ if b.get('status') is None or b['status'] != 1:
747
+ continue
741
748
  if i == 0:
742
749
  residual_handling = b['residual_handling']
743
750
  get_battery(info=0)
@@ -751,13 +758,13 @@ def get_batteries(info=1, rated=None, count=None):
751
758
  residual = b['capacity'] * b['soc'] / 100
752
759
  b['residual'] = round(residual, 3)
753
760
  if b.get('ratedCapacity') is not None and b.get('capacity') is not None:
754
- b['soh'] = round(b['capacity'] * 1000 / b['ratedCapacity'] * 100, 1)
761
+ b['soh'] = round(b['capacity'] * 1000 / b['ratedCapacity'] * 100, 1) if b['ratedCapacity'] > 0.0 else None
755
762
  b['charge_rate'] = None
756
763
  params = battery_params[b['residual_handling']]
757
764
  b['charge_loss'] = params['charge_loss']
758
765
  b['discharge_loss'] = params['discharge_loss']
759
766
  if b.get('temperature') is not None:
760
- b['charge_rate'] = params['table'][int((b['temperature'] - params['offset']) / params['step'])]
767
+ b['charge_rate'] = interpolate((b['temperature'] - params['offset']) / params['step'], params['table'])
761
768
  return batteries
762
769
 
763
770
  ##################################################################################################
@@ -801,7 +808,7 @@ def time_period(t):
801
808
  result += f" Charge from grid" if t['enableGrid'] else f" Battery Hold"
802
809
  return result
803
810
 
804
- def set_charge(ch1=0, st1=0, en1=True, ch2=0, st2=0, en2=True, force=0, enable=1):
811
+ def set_charge(ch1=True, st1=0, en1=0, ch2=True, st2=0, en2=0, force=0, enable=1):
805
812
  global device_sn, battery_settings, debug_setting, messages, schedule
806
813
  if get_device() is None:
807
814
  return None
@@ -3060,7 +3067,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3060
3067
  output(f"full_charge = {full_charge}")
3061
3068
  if test_soc is not None:
3062
3069
  current_soc = test_soc
3063
- capacity = 14.46
3070
+ capacity = 14.43
3064
3071
  residual = test_soc * capacity / 100
3065
3072
  bat_volt = 317.4
3066
3073
  bat_power = 0.0
@@ -3076,7 +3083,6 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3076
3083
  # get device and battery info from inverter
3077
3084
  get_battery()
3078
3085
  if battery is None or battery['status'] != 1:
3079
- output(f"\nBattery status is not available")
3080
3086
  return None
3081
3087
  current_soc = battery['soc']
3082
3088
  bat_volt = battery['volt']
@@ -3192,26 +3198,19 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3192
3198
  consumption_timed = timed_list([consumption * x / daily_sum for x in consumption_by_hour], base_hour, run_time)
3193
3199
  # get Solcast data and produce time line
3194
3200
  solcast_value = None
3195
- solcast_profile = None
3196
3201
  if forecast is None and solcast_api_key is not None and solcast_api_key != 'my.solcast_api_key' and (system_time.hour in forecast_times or run_after == 0):
3197
3202
  fsolcast = Solcast(quiet=True, reload=reload, shading=charge_config.get('shading'), d=base_time)
3198
3203
  if fsolcast is not None and hasattr(fsolcast, 'daily') and fsolcast.daily.get(forecast_day) is not None:
3199
3204
  solcast_value = fsolcast.daily[forecast_day]['kwh']
3200
3205
  solcast_timed = forecast_value_timed(fsolcast, today, tomorrow, base_hour, run_time, time_offset)
3201
- solcast_from = time_hours(fsolcast.daily[today]['from']) if fsolcast.daily[today].get('from') is not None else 0
3202
- output(f"\nSolcast: {tomorrow} {fsolcast.daily[tomorrow]['kwh']:.1f}kWh")
3203
3206
  # get forecast.solar data and produce time line
3204
3207
  solar_value = None
3205
- solar_profile = None
3206
3208
  if forecast is None and solar_arrays is not None and (system_time.hour in forecast_times or run_after == 0):
3207
3209
  fsolar = Solar(quiet=True, shading=charge_config.get('shading'), d=base_time)
3208
3210
  if fsolar is not None and hasattr(fsolar, 'daily') and fsolar.daily.get(forecast_day) is not None:
3209
3211
  solar_value = fsolar.daily[forecast_day]['kwh']
3210
3212
  solar_timed = forecast_value_timed(fsolar, today, tomorrow, base_hour, run_time, 0)
3211
- output(f"\nSolar: {tomorrow} {fsolar.daily[tomorrow]['kwh']:.1f}kWh")
3212
- if solcast_value is None and solar_value is None and debug_setting > 1:
3213
- output(f"\nNo forecasts available at this time")
3214
- # choose expected value and produce generation time line
3213
+ # choose expected value
3215
3214
  quarter = int(today[5:7] if charge_today else tomorrow[5:7]) // 3 % 4
3216
3215
  sun_name = seasonal_sun[quarter]['name']
3217
3216
  sun_profile = seasonal_sun[quarter]['sun']
@@ -3225,9 +3224,11 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3225
3224
  elif solcast_value is not None:
3226
3225
  expected = solcast_value
3227
3226
  generation_timed = solcast_timed
3227
+ output(f"\nSolcast: {tomorrow} {fsolcast.daily[tomorrow]['kwh']:.1f}kWh")
3228
3228
  elif solar_value is not None:
3229
3229
  expected = solar_value
3230
3230
  generation_timed = solar_timed
3231
+ output(f"\nSolar: {tomorrow} {fsolar.daily[tomorrow]['kwh']:.1f}kWh")
3231
3232
  else:
3232
3233
  # no forecast, use generation history
3233
3234
  generation = None
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 07 November 2024
4
+ Updated: 23 November 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.7.1"
13
+ version = "2.7.2"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -582,13 +582,18 @@ def get_battery(info=0, v=None, rated=None, count=None):
582
582
  if v is None:
583
583
  v = battery_vars
584
584
  result = get_real(v)
585
- if battery is None:
586
- battery = {}
585
+ battery = {}
587
586
  for i in range(0, len(battery_vars)):
588
587
  battery[battery_data[i]] = result[i].get('value')
589
588
  battery['residual_handling'] = residual_handling
590
589
  battery['soh'] = None
591
590
  battery['soh_supported'] = False
591
+ if battery.get('status') is None:
592
+ battery['status'] = 0 if battery.get('volt') is None or battery['volt'] <= 0 else 1
593
+ if battery['status'] != 1:
594
+ output(f"** get_battery(): battery status not available")
595
+ return None
596
+ battery['status'] = 1
592
597
  if battery['residual_handling'] == 2:
593
598
  capacity = battery.get('residual')
594
599
  soc = battery.get('soc')
@@ -621,9 +626,9 @@ def get_battery(info=0, v=None, rated=None, count=None):
621
626
  battery['charge_loss'] = params['charge_loss']
622
627
  battery['discharge_loss'] = params['discharge_loss']
623
628
  if battery.get('ratedCapacity') is not None and battery.get('capacity') is not None:
624
- battery['soh'] = round(battery['capacity'] * 1000 / battery['ratedCapacity'] * 100, 1)
629
+ battery['soh'] = round(battery['capacity'] * 1000 / battery['ratedCapacity'] * 100, 1) if battery['ratedCapacity'] > 0.0 else None
625
630
  if battery.get('temperature') is not None:
626
- battery['charge_rate'] = params['table'][int((battery['temperature'] - params['offset']) / params['step'])]
631
+ battery['charge_rate'] = interpolate((battery['temperature'] - params['offset']) / params['step'], params['table'])
627
632
  return battery
628
633
 
629
634
  def get_batteries(info=0, rated=None, count=None):
@@ -674,7 +679,7 @@ def time_period(t, n):
674
679
  result += f" Charge from grid" if enable else f" Battery Hold"
675
680
  return result
676
681
 
677
- def set_charge(ch1=0, st1=0, en1=True, ch2=0, st2=0, en2=True, force = 0, enable=1):
682
+ def set_charge(ch1=True, st1=0, en1=0, ch2=True, st2=0, en2=0, force = 0, enable=1):
678
683
  global device_sn, battery_settings, debug_setting, time_period_vars
679
684
  if get_device() is None:
680
685
  return None
@@ -2722,7 +2727,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2722
2727
  output(f"full_charge = {full_charge}")
2723
2728
  if test_soc is not None:
2724
2729
  current_soc = test_soc
2725
- capacity = 14.46
2730
+ capacity = 14.43
2726
2731
  residual = test_soc * capacity / 100
2727
2732
  bat_volt = 317.4
2728
2733
  bat_power = 0.0
@@ -2738,7 +2743,6 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2738
2743
  # get device and battery info from inverter
2739
2744
  get_battery()
2740
2745
  if battery is None or battery['status'] != 1:
2741
- output(f"\nBattery status is not available")
2742
2746
  return None
2743
2747
  current_soc = battery['soc']
2744
2748
  bat_volt = battery['volt']
@@ -2858,25 +2862,19 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2858
2862
  consumption_timed = timed_list([consumption * x / daily_sum for x in consumption_by_hour], base_hour, run_time)
2859
2863
  # get Solcast data and produce time line
2860
2864
  solcast_value = None
2861
- solcast_profile = None
2862
2865
  if forecast is None and solcast_api_key is not None and solcast_api_key != 'my.solcast_api_key' and (system_time.hour in forecast_times or run_after == 0):
2863
2866
  fsolcast = Solcast(quiet=True, reload=reload, shading=charge_config.get('shading'), d=base_time)
2864
2867
  if fsolcast is not None and hasattr(fsolcast, 'daily') and fsolcast.daily.get(forecast_day) is not None:
2865
2868
  solcast_value = fsolcast.daily[forecast_day]['kwh']
2866
2869
  solcast_timed = forecast_value_timed(fsolcast, today, tomorrow, base_hour, run_time, time_offset)
2867
- output(f"\nSolcast: {tomorrow} {fsolcast.daily[tomorrow]['kwh']:.1f}kWh")
2868
2870
  # get forecast.solar data and produce time line
2869
2871
  solar_value = None
2870
- solar_profile = None
2871
2872
  if forecast is None and solar_arrays is not None and (system_time.hour in forecast_times or run_after == 0):
2872
2873
  fsolar = Solar(quiet=True, shading=charge_config.get('shading'), d=base_time)
2873
2874
  if fsolar is not None and hasattr(fsolar, 'daily') and fsolar.daily.get(forecast_day) is not None:
2874
2875
  solar_value = fsolar.daily[forecast_day]['kwh']
2875
2876
  solar_timed = forecast_value_timed(fsolar, today, tomorrow, base_hour, run_time, 0)
2876
- output(f"\nSolar: {tomorrow} {fsolar.daily[tomorrow]['kwh']:.1f}kWh")
2877
- if solcast_value is None and solar_value is None and debug_setting > 1:
2878
- output(f"\nNo forecasts available at this time")
2879
- # choose expected value and produce generation time line
2877
+ # choose expected value
2880
2878
  quarter = int(today[5:7] if charge_today else tomorrow[5:7]) // 3 % 4
2881
2879
  sun_name = seasonal_sun[quarter]['name']
2882
2880
  sun_profile = seasonal_sun[quarter]['sun']
@@ -2890,9 +2888,11 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2890
2888
  elif solcast_value is not None:
2891
2889
  expected = solcast_value
2892
2890
  generation_timed = solcast_timed
2891
+ output(f"\nSolcast: {tomorrow} {fsolcast.daily[tomorrow]['kwh']:.1f}kWh")
2893
2892
  elif solar_value is not None:
2894
2893
  expected = solar_value
2895
2894
  generation_timed = solar_timed
2895
+ output(f"\nSolar: {tomorrow} {fsolar.daily[tomorrow]['kwh']:.1f}kWh")
2896
2896
  else:
2897
2897
  # no forecast, use generation data
2898
2898
  generation = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.7.1
3
+ Version: 2.7.2
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
@@ -807,6 +807,13 @@ This setting can be:
807
807
 
808
808
  # Version Info
809
809
 
810
+ 2.7.2<br>
811
+ Fix to get_battery() to return error and flag status=0 in f.battery when the cloud is not returning valid data.
812
+ Fix exception calculating SoH if ratedCapacity is returned as 0 when cloud is not returning valid data.
813
+ Update charge_rate in charge_needed() to use a blended charge rate based on battery warming up during charging.
814
+ Fix exception in set_charge() caused by incorrect default parameter values.
815
+ Update charge_needed() to only show forecast that is in use.
816
+
810
817
  2.7.1<br>
811
818
  Update charge_needed() so it only gets generation history if there is no forecast to reduce API calls and save time.
812
819
  Update default parameter values for set_charge() so the other time period is cleared if you only set 1 time.
@@ -816,7 +823,6 @@ Update battery predictions to more accurately reflect what happens when SoC gets
816
823
  Correct model to use inverter operating losses instead of BMS losses when the battery is above min_soc.
817
824
  Correct exception in Solcast and Solar when a forecast is not available.
818
825
 
819
-
820
826
  2.7.0<br>
821
827
  Allow charge_loss / discharge_loss to be configured for charge_needed().
822
828
  Change 'Force Charge' to 'Battery Hold' in charge times to avoid confusion with Force Charge work mode.
File without changes
File without changes