foxesscloud 2.6.1__py3-none-any.whl → 2.6.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud
4
- Updated: 09 October 2024
4
+ Updated: 12 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.3"
13
+ version = "1.7.4"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -65,7 +65,7 @@ def plot_show():
65
65
  ##################################################################################################
66
66
  ##################################################################################################
67
67
 
68
- def query_date(d, offset = None):
68
+ def convert_date(d):
69
69
  if d is not None and len(d) < 18:
70
70
  if len(d) == 10:
71
71
  d += ' 00:00:00'
@@ -76,8 +76,12 @@ def query_date(d, offset = None):
76
76
  try:
77
77
  t = datetime.now() if d is None else datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
78
78
  except Exception as e:
79
- output(f"** query_date(): {str(e)}")
79
+ output(f"** convert_date(): {str(e)}")
80
80
  return None
81
+ return t
82
+
83
+ def query_date(d, offset = None):
84
+ t = convert_date(d)
81
85
  if offset is not None:
82
86
  t += timedelta(days = offset)
83
87
  return {'year': t.year, 'month': t.month, 'day': t.day, 'hour': t.hour, 'minute': t.minute, 'second': t.second}
@@ -594,8 +598,8 @@ battery_params = {
594
598
  2: {'table': [ 0, 2, 10, 10, 15, 15, 25, 50, 50, 50, 30, 20, 0],
595
599
  'step': 5,
596
600
  'offset': 5,
597
- 'charge_loss': 1.080,
598
- 'discharge_loss': 0.975},
601
+ 'charge_loss': 1.08,
602
+ 'discharge_loss': 0.85},
599
603
  }
600
604
 
601
605
  def get_battery(info=1):
@@ -630,9 +634,13 @@ def get_battery(info=1):
630
634
  battery['info'] = result['batteries'][0]
631
635
  if battery['info']['masterVersion'] >= '1.014':
632
636
  residual_handling = 2
637
+ battery['residual_handling'] = residual_handling
638
+ battery['rated_capacity'] = None
639
+ battery['soh'] = None
640
+ battery['soh_supported'] = False
633
641
  if battery.get('residual') is not None:
634
642
  battery['residual'] /= 1000
635
- if residual_handling == 2:
643
+ if battery['residual_handling'] == 2:
636
644
  capacity = battery.get('residual')
637
645
  soc = battery.get('soc')
638
646
  residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
@@ -643,7 +651,7 @@ def get_battery(info=1):
643
651
  battery['capacity'] = round(capacity, 3)
644
652
  battery['residual'] = round(residual, 3)
645
653
  battery['charge_rate'] = 50
646
- params = battery_params[residual_handling]
654
+ params = battery_params[battery['residual_handling']]
647
655
  battery['charge_loss'] = params['charge_loss']
648
656
  battery['discharge_loss'] = params['discharge_loss']
649
657
  if battery.get('temperature') is not None:
@@ -680,18 +688,30 @@ def get_batteries(info=1):
680
688
  batteries[i]['info'] = result['batteries'][i]
681
689
  for b in batteries:
682
690
  if b.get('info') is not None and b['info']['masterVersion'] >= '1.014':
683
- residual_handling = 2
684
- capacity = b['ratedCapacity'] / 1000 * int(b['soh']) / 100
685
- soc = b.get('soc')
686
- residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
687
- b['capacity'] = round(capacity, 3)
688
- b['residual'] = round(residual, 3)
689
- b['charge_rate'] = 50
690
- params = battery_params[residual_handling]
691
- b['charge_loss'] = params['charge_loss']
692
- b['discharge_loss'] = params['discharge_loss']
693
- if b.get('temperature') is not None:
694
- b['charge_rate'] = params['table'][int((b['temperature'] - params['offset']) / params['step'])]
691
+ b['residual_handling'] = 2
692
+ if b.get('soh') is not None and b['soh'].isnumeric():
693
+ b['soh_supported'] = True
694
+ else:
695
+ b['rated_capacity'] = None
696
+ b['soh'] = None
697
+ b['soh_supported'] = False
698
+ for b in batteries:
699
+ if b.get('soh_supported') is not None and b['soh_supported']:
700
+ capacity = (b['ratedCapacity'] / 1000 * int(b['soh']) / 100)
701
+ soc = b.get('soc')
702
+ residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
703
+ b['capacity'] = round(capacity, 3)
704
+ b['residual'] = round(residual, 3)
705
+ b['charge_rate'] = 50
706
+ params = battery_params[residual_handling]
707
+ b['charge_loss'] = params['charge_loss']
708
+ b['discharge_loss'] = params['discharge_loss']
709
+ if b.get('temperature') is not None:
710
+ b['charge_rate'] = params['table'][int((b['temperature'] - params['offset']) / params['step'])]
711
+ else:
712
+ get_battery(info=info)
713
+ batteries = [battery]
714
+ break
695
715
  battery = batteries[0]
696
716
  return batteries
697
717
 
@@ -2165,9 +2185,9 @@ def hours_difference(t1, t2):
2165
2185
  if t1 == t2:
2166
2186
  return 0.0
2167
2187
  if type(t1) is str:
2168
- t1 = datetime.strptime(t1, '%Y-%m-%d %H:%M')
2188
+ t1 = convert_date(t1)
2169
2189
  if type(t2) is str:
2170
- t2 = datetime.strptime(t2, '%Y-%m-%d %H:%M')
2190
+ t2 = convert_date(t2)
2171
2191
  return round((t1 - t2).total_seconds() / 3600,1)
2172
2192
 
2173
2193
  ##################################################################################################
@@ -2344,7 +2364,7 @@ def get_agile_times(tariff=agile_octopus, d=None):
2344
2364
  if d is not None and len(d) < 11:
2345
2365
  d += " 18:00"
2346
2366
  # get dates and times
2347
- system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
2367
+ system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else convert_date(d)
2348
2368
  time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
2349
2369
  # adjust system to get local time now
2350
2370
  now = system_time + timedelta(hours=time_offset)
@@ -2815,7 +2835,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2815
2835
  def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2816
2836
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2817
2837
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2818
- global timed_strategy, steps_per_hour, base_time, storage, battery
2838
+ global timed_strategy, steps_per_hour, base_time, storage, battery, charge_rates
2819
2839
  print(f"\n---------------- charge_needed ----------------")
2820
2840
  # validate parameters
2821
2841
  args = locals()
@@ -2843,7 +2863,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2843
2863
  if type(forecast_times) is not list:
2844
2864
  forecast_times = [forecast_times]
2845
2865
  # get dates and times
2846
- system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else datetime.strptime(test_time, '%Y-%m-%d %H:%M')
2866
+ system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else convert_date(test_time)
2847
2867
  time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
2848
2868
  now = system_time + timedelta(hours=time_offset)
2849
2869
  today = datetime.strftime(now, '%Y-%m-%d')
@@ -2916,14 +2936,14 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2916
2936
  output(f"full_charge = {full_charge}")
2917
2937
  if test_soc is not None:
2918
2938
  current_soc = test_soc
2919
- capacity = 14.54
2939
+ capacity = 14.53
2920
2940
  residual = test_soc * capacity / 100
2921
2941
  bat_volt = 317.4
2922
2942
  bat_power = 0.0
2923
2943
  temperature = 30
2924
2944
  bms_charge_current = 15
2925
- charge_loss = 1.080
2926
- discharge_loss = 0.975
2945
+ charge_loss = charge_rates[2]['charge_loss']
2946
+ discharge_loss = charge_rates[2]['discharge_loss']
2927
2947
  bat_current = 0.0
2928
2948
  device_power = 6.0
2929
2949
  device_current = 35
@@ -2971,6 +2991,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2971
2991
  output(f" Temperature: {temperature:.1f}°C")
2972
2992
  output(f" Resistance: {bat_resistance:.2f} ohms")
2973
2993
  output(f" Nominal OCV: {bat_ocv:.1f}V at {nominal_soc}% SoC")
2994
+ output(f" Losses: {charge_loss * 100:.1f}% charge / {discharge_loss * 100:.1f}% discharge")
2974
2995
  # charge current may be derated based on temperature
2975
2996
  charge_current = device_current if charge_config['charge_current'] is None else charge_config['charge_current']
2976
2997
  if charge_current > bms_charge_current:
@@ -3309,10 +3330,10 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3309
3330
 
3310
3331
  def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
3311
3332
  global charge_config, storage
3312
- now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
3333
+ now = convert_date(d)
3313
3334
  yesterday = datetime.strftime(datetime.date(now - timedelta(days=1)), '%Y-%m-%d')
3314
3335
  if save is None and charge_config.get('save') is not None:
3315
- save = charge_config.get('save').replace('###', yesterday)
3336
+ save = charge_config.get('save').replace('###', yesterday if d is None else d[:10])
3316
3337
  if not os.path.exists(storage + save):
3317
3338
  save = None
3318
3339
  if save is None:
@@ -3339,7 +3360,7 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
3339
3360
  base_hour = int(time_hours(base_time[11:16]))
3340
3361
  start_day = base_time[:10]
3341
3362
  print(f"Run at {start_day} {hours_time(hour_now)} with SoC {current_soc:.0f}%")
3342
- now = datetime.strptime(base_time, '%Y-%m-%d %H:%M')
3363
+ now = convert_date(base_time)
3343
3364
  end_day = datetime.strftime(now + timedelta(hours=run_time / steps_per_hour), '%Y-%m-%d')
3344
3365
  if v is None:
3345
3366
  v = ['pvPower', 'loadsPower', 'SoC']
@@ -3372,13 +3393,14 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3, d=None):
3372
3393
  for i in range(0, run_time):
3373
3394
  plots[v][i] = plots[v][i] / count[v][i] if count[v][i] > 0 else None
3374
3395
  if show_data > 0 and plots.get('SoC') is not None:
3375
- data_wrap = charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 6
3376
- s = f"\nBattery Energy kWh:" if show_data == 2 else f"\nBattery SoC:"
3396
+ data_wrap = 1 #charge_config['data_wrap'] if charge_config.get('data_wrap') is not None else 1
3397
+ s = f"\nBattery Energy kWh (predicted / actual):" if show_data == 2 else f"\nBattery SoC (predicted / actual):"
3377
3398
  h = base_hour
3378
3399
  t = 0
3379
3400
  while t < len(time_line) and bat_timed[t] is not None and plots['SoC'][t] is not None:
3380
3401
  col = h % data_wrap
3381
3402
  s += f"\n {hours_time(time_line[t])}" if t == 0 or col == 0 else ""
3403
+ s += f" {bat_timed[t]:5.2f}" if show_data == 2 else f" {bat_timed[t] / capacity * 100:3.0f}%"
3382
3404
  s += f" {plots['SoC'][t]:5.2f}" if show_data == 2 else f" {plots['SoC'][t] / capacity * 100:3.0f}%"
3383
3405
  h += 1
3384
3406
  t += steps_per_hour
@@ -3534,7 +3556,7 @@ def battery_info(log=0, plot=1, count=None, info=1, bat=None):
3534
3556
  output(f"SoH: {bat_soh}%")
3535
3557
  output(f"Current SoC: {current_soc}%")
3536
3558
  output(f"Capacity: {capacity:.2f}kWh")
3537
- output(f"Residual: {residual:.2f}kWh")
3559
+ output(f"Residual: {residual:.2f}kWh" + (" (SoC x Capacity)" if bat['residual_handling'] == 2 else ""))
3538
3560
  output(f"InvBatVolt: {bat_volt:.1f}V")
3539
3561
  output(f"InvBatCurrent: {bat_current:.1f}A")
3540
3562
  output(f"State: {'Charging' if bat_power < 0 else 'Discharging'} ({abs(bat_power):.3f}kW)")
@@ -3944,7 +3966,7 @@ class Solcast :
3944
3966
  # The forecasts and estimated also both include the current time, so the data has to be de-duplicated to get an accurate total for a day
3945
3967
  global debug_setting, solcast_url, solcast_api_key, solcast_save, storage
3946
3968
  self.data = {}
3947
- now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
3969
+ now = convert_date(d)
3948
3970
  self.shading = None if shading is None else shading if shading.get('solcast') is None else shading['solcast']
3949
3971
  self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
3950
3972
  self.quarter = int(self.today[5:7]) // 3 % 4
@@ -4288,7 +4310,7 @@ class Solar :
4288
4310
  def __init__(self, reload=0, quiet=False, shading=None, d=None):
4289
4311
  global solar_arrays, solar_save, solar_total, solar_url, solar_api_key, storage
4290
4312
  self.shading = None if shading is None else shading if shading.get('solar') is None else shading['solar']
4291
- now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
4313
+ now = convert_date(d)
4292
4314
  self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
4293
4315
  self.quarter = int(self.today[5:7]) // 3 % 4
4294
4316
  self.tomorrow = datetime.strftime(datetime.date(now + timedelta(days=1)), '%Y-%m-%d')
foxesscloud/openapi.py CHANGED
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 09 October 2024
4
+ Updated: 12 October 2024
5
5
  By: Tony Matthews
6
6
  """
7
7
  ##################################################################################################
@@ -10,7 +10,7 @@ By: Tony Matthews
10
10
  # ALL RIGHTS ARE RESERVED © Tony Matthews 2024
11
11
  ##################################################################################################
12
12
 
13
- version = "2.6.1"
13
+ version = "2.6.2"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -66,8 +66,7 @@ def plot_show():
66
66
  ##################################################################################################
67
67
  ##################################################################################################
68
68
 
69
- # return query date as a dictionary with year, month, day, hour, minute, second
70
- def query_date(d, offset = None):
69
+ def convert_date(d):
71
70
  if d is not None and len(d) < 18:
72
71
  if len(d) == 10:
73
72
  d += ' 00:00:00'
@@ -78,8 +77,13 @@ def query_date(d, offset = None):
78
77
  try:
79
78
  t = datetime.now() if d is None else datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
80
79
  except Exception as e:
81
- output(f"** query_date(): {str(e)}")
80
+ output(f"** convert_date(): {str(e)}")
82
81
  return None
82
+ return t
83
+
84
+ # return query date as a dictionary with year, month, day, hour, minute, second
85
+ def query_date(d, offset = None):
86
+ t = convert_date(d)
83
87
  if offset is not None:
84
88
  t += timedelta(days = offset)
85
89
  return {'year': t.year, 'month': t.month, 'day': t.day, 'hour': t.hour, 'minute': t.minute, 'second': t.second}
@@ -94,7 +98,7 @@ def query_time(d, time_span):
94
98
  else:
95
99
  d += ':00'
96
100
  try:
97
- t = datetime.now().replace(minute=0, second=0, microsecond=0) if d is None else datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
101
+ t = datetime.now().replace(minute=0, second=0, microsecond=0) if d is None else convert_date(d)
98
102
  except Exception as e:
99
103
  output(f"** query_time(): {str(e)}")
100
104
  return (None, None)
@@ -560,7 +564,7 @@ battery_params = {
560
564
  'step': 5,
561
565
  'offset': 5,
562
566
  'charge_loss': 1.080,
563
- 'discharge_loss': 0.975},
567
+ 'discharge_loss': 0.85},
564
568
  }
565
569
 
566
570
  def get_battery(info=0, v=None):
@@ -575,7 +579,11 @@ def get_battery(info=0, v=None):
575
579
  battery = {}
576
580
  for i in range(0, len(battery_vars)):
577
581
  battery[battery_data[i]] = result[i].get('value')
578
- if residual_handling == 2:
582
+ battery['residual_handling'] = residual_handling
583
+ battery['rated_capacity'] = None
584
+ battery['soh'] = None
585
+ battery['soh_supported'] = False
586
+ if battery['residual_handling'] == 2:
579
587
  capacity = battery.get('residual')
580
588
  soc = battery.get('soc')
581
589
  residual = capacity * soc / 100 if capacity is not None and soc is not None else capacity
@@ -587,7 +595,7 @@ def get_battery(info=0, v=None):
587
595
  battery['residual'] = round(residual, 3)
588
596
  battery['status'] = 1
589
597
  battery['charge_rate'] = 50
590
- params = battery_params[residual_handling]
598
+ params = battery_params[battery['residual_handling']]
591
599
  battery['charge_loss'] = params['charge_loss']
592
600
  battery['discharge_loss'] = params['discharge_loss']
593
601
  if battery.get('temperature') is not None:
@@ -597,8 +605,6 @@ def get_battery(info=0, v=None):
597
605
  def get_batteries(info=0):
598
606
  global battery, batteries
599
607
  get_battery(info=info)
600
- battery['ratedCapacity'] = None
601
- battery['soh'] = None
602
608
  batteries = [battery]
603
609
  return batteries
604
610
 
@@ -1984,9 +1990,9 @@ def hours_difference(t1, t2):
1984
1990
  if t1 == t2:
1985
1991
  return 0.0
1986
1992
  if type(t1) is str:
1987
- t1 = datetime.strptime(t1, '%Y-%m-%d %H:%M')
1993
+ t1 = convert_date(t1)
1988
1994
  if type(t2) is str:
1989
- t2 = datetime.strptime(t2, '%Y-%m-%d %H:%M')
1995
+ t2 = convert_date(t2)
1990
1996
  return round((t1 - t2).total_seconds() / 3600,1)
1991
1997
 
1992
1998
  ##################################################################################################
@@ -2163,7 +2169,7 @@ def get_agile_times(tariff=agile_octopus, d=None):
2163
2169
  if d is not None and len(d) < 11:
2164
2170
  d += " 18:00"
2165
2171
  # get dates and times
2166
- system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
2172
+ system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if d is None else convert_date(d)
2167
2173
  time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
2168
2174
  # adjust system to get local time now
2169
2175
  now = system_time + timedelta(hours=time_offset)
@@ -2634,7 +2640,7 @@ charge_needed_app_key = "awcr5gro2v13oher3v1qu6hwnovp28"
2634
2640
  def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=None, show_plot=None, run_after=None, reload=2,
2635
2641
  forecast_times=None, force_charge=0, test_time=None, test_soc=None, test_charge=None, **settings):
2636
2642
  global device, seasonality, solcast_api_key, debug_setting, tariff, solar_arrays, legend_location, time_shift, charge_needed_app_key
2637
- global timed_strategy, steps_per_hour, base_time, storage, battery
2643
+ global timed_strategy, steps_per_hour, base_time, storage, battery, charge_rates
2638
2644
  print(f"\n---------------- charge_needed ----------------")
2639
2645
  # validate parameters
2640
2646
  args = locals()
@@ -2662,7 +2668,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2662
2668
  if type(forecast_times) is not list:
2663
2669
  forecast_times = [forecast_times]
2664
2670
  # get dates and times
2665
- system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else datetime.strptime(test_time, '%Y-%m-%d %H:%M')
2671
+ system_time = (datetime.now(tz=timezone.utc) + timedelta(hours=time_shift)) if test_time is None else convert_date(test_time)
2666
2672
  time_offset = daylight_saving(system_time) if daylight_saving is not None else 0
2667
2673
  now = system_time + timedelta(hours=time_offset)
2668
2674
  today = datetime.strftime(now, '%Y-%m-%d')
@@ -2741,8 +2747,8 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2741
2747
  bat_power = 0.0
2742
2748
  temperature = 30
2743
2749
  bms_charge_current = 15
2744
- charge_loss = 1.080
2745
- discharge_loss = 0.975
2750
+ charge_loss = charge_rates[2]['charge_loss']
2751
+ discharge_loss = charge_rates[2]['discharge_loss']
2746
2752
  bat_current = 0.0
2747
2753
  device_power = 6.0
2748
2754
  device_current = 35
@@ -3126,10 +3132,10 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3126
3132
 
3127
3133
  def charge_compare(save=None, v=None, show_data=1, show_plot=3):
3128
3134
  global charge_config, storage
3129
- now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
3135
+ now = convert_date(d)
3130
3136
  yesterday = datetime.strftime(datetime.date(now - timedelta(days=1)), '%Y-%m-%d')
3131
3137
  if save is None and charge_config.get('save') is not None:
3132
- save = charge_config.get('save').replace('###', yesterday)
3138
+ save = charge_config.get('save').replace('###', yesterday if d is None else d[:10])
3133
3139
  if not os.path.exists(storage + save):
3134
3140
  save = None
3135
3141
  if save is None:
@@ -3156,7 +3162,7 @@ def charge_compare(save=None, v=None, show_data=1, show_plot=3):
3156
3162
  base_hour = int(time_hours(base_time[11:16]))
3157
3163
  start_day = base_time[:10]
3158
3164
  print(f"Run at {start_day} {hours_time(hour_now)} with SoC {current_soc:.0f}%")
3159
- now = datetime.strptime(base_time, '%Y-%m-%d %H:%M')
3165
+ now = convert_date(base_time)
3160
3166
  end_day = datetime.strftime(now + timedelta(hours=run_time / steps_per_hour), '%Y-%m-%d')
3161
3167
  if v is None:
3162
3168
  v = ['pvPower', 'loadsPower', 'SoC']
@@ -3760,7 +3766,7 @@ class Solcast :
3760
3766
  # The forecasts and estimated also both include the current time, so the data has to be de-duplicated to get an accurate total for a day
3761
3767
  global debug_setting, solcast_url, solcast_api_key, solcast_save, storage
3762
3768
  self.data = {}
3763
- now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
3769
+ now = convert_date(d)
3764
3770
  self.shading = None if shading is None else shading if shading.get('solcast') is None else shading['solcast']
3765
3771
  self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
3766
3772
  self.quarter = int(self.today[5:7]) // 3 % 4
@@ -4104,7 +4110,7 @@ class Solar :
4104
4110
  def __init__(self, reload=0, quiet=False, shading=None, d=None):
4105
4111
  global solar_arrays, solar_save, solar_total, solar_url, solar_api_key, storage
4106
4112
  self.shading = None if shading is None else shading if shading.get('solar') is None else shading['solar']
4107
- now = datetime.now() if d is None else datetime.strptime(d, '%Y-%m-%d %H:%M')
4113
+ now = convert_date(d)
4108
4114
  self.today = datetime.strftime(datetime.date(now), '%Y-%m-%d')
4109
4115
  self.quarter = int(self.today[5:7]) // 3 % 4
4110
4116
  self.tomorrow = datetime.strftime(datetime.date(now + timedelta(days=1)), '%Y-%m-%d')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.6.1
3
+ Version: 2.6.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
@@ -787,6 +787,11 @@ This setting can be:
787
787
 
788
788
  # Version Info
789
789
 
790
+ 2.6.2<br>
791
+ Update battery calibration for charge_needed() when residual_handling is 2.
792
+ Update get_battery() and get_batteries() to include states for ratedCapacity, soh, residual_handling and soh_supported.
793
+ Update charge_compare(), Solcast() and Solar() so date (d) parameter is more flexible.
794
+
790
795
  2.6.1<br>
791
796
  Fix problem where battery discharges below min_soc while waiting for charging to start.
792
797
  Update calibration for Force Charge with BMS 1.014 and later.
@@ -0,0 +1,7 @@
1
+ foxesscloud/foxesscloud.py,sha256=j1-EcJ3vudFCalHBbyBXUTl99s4-foWqq1831YLdHXg,216792
2
+ foxesscloud/openapi.py,sha256=USny4eYMLxG2rg3FsijGp7w_cOT8eMMvy0pVN0DbVSU,207227
3
+ foxesscloud-2.6.2.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
4
+ foxesscloud-2.6.2.dist-info/METADATA,sha256=Y96zL2xTMWPk1yjNnO_QQgulzpPy82Q6IXiEVYHyxxE,57751
5
+ foxesscloud-2.6.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
+ foxesscloud-2.6.2.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
+ foxesscloud-2.6.2.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- foxesscloud/foxesscloud.py,sha256=DHP0NqeFXj6rFIdwyTp7U935YBhJcFG2YMwaHGRBaF4,215964
2
- foxesscloud/openapi.py,sha256=Rp1HdtBLpMt1iK5psUkfnCSzw7vmmABs_SkOZ4G2V0M,207271
3
- foxesscloud-2.6.1.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
4
- foxesscloud-2.6.1.dist-info/METADATA,sha256=aYpWZtJnpnzmEjkB5ip7fCW4Qx9Ra5WOL-5uFCYHemQ,57452
5
- foxesscloud-2.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- foxesscloud-2.6.1.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
- foxesscloud-2.6.1.dist-info/RECORD,,