foxesscloud 2.5.4__py3-none-any.whl → 2.5.5__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: 25 September 2024
4
+ Updated: 26 September 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.6.6"
13
+ version = "1.6.7"
14
14
  print(f"FoxESS-Cloud version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -721,7 +721,7 @@ def set_charge(ch1=None, st1=None, en1=None, ch2=None, st2=None, en2=None, force
721
721
  output(f"success", 2)
722
722
  return battery_settings
723
723
 
724
- def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, target_soc=100, start_soc=10):
724
+ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, end_soc=100, start_soc=10):
725
725
  output(f"\nConfiguring schedule",1)
726
726
  charge = []
727
727
  st1 = time_hours(st1)
@@ -730,7 +730,7 @@ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, target_so
730
730
  en2 = time_hours(en2)
731
731
  span = None
732
732
  if st2 is not None and en2 is not None and st2 != en2:
733
- charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc': target_soc})
733
+ charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc': end_soc})
734
734
  span = {'start': st2, 'end': en2}
735
735
  if st1 is not None and en1 is not None and st1 != en1:
736
736
  charge.append({'start': st1, 'end': en1, 'mode': 'SelfUse', 'min_soc': start_soc})
@@ -2221,6 +2221,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
2221
2221
  if use.get('agile') is not None and use['agile'].get('strategy') is not None:
2222
2222
  base_time_adjust = hours_difference(base_time, use['agile'].get('base_time') )
2223
2223
  for s in use['agile']['strategy']:
2224
+ s['valid_for'] = [int((s['hour'] - base_time_adjust) * steps_per_hour + i) for i in range(0, steps_per_hour // 2)] if s.get('hour') is not None else None
2224
2225
  strategy.append(s)
2225
2226
  if strategy is None or len(strategy) == 0:
2226
2227
  return []
@@ -2230,7 +2231,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
2230
2231
  start = s['start']
2231
2232
  end = s['end']
2232
2233
  if hour_overlap(s, remove):
2233
- output(f" {hours_time(start)}-{hours_time(end)} ** removed ** (overlaps charge period)", 2)
2234
+ output(f" {hours_time(start)}-{hours_time(end)} was removed from strategy", 2)
2234
2235
  continue
2235
2236
  # add segment
2236
2237
  min_soc_now = s['min_soc'] if s.get('min_soc') is not None and s['min_soc'] > 10 else 10
@@ -2239,9 +2240,9 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
2239
2240
  fdsoc = s.get('fdsoc')
2240
2241
  fdpwr = s.get('fdpwr')
2241
2242
  price = s.get('price')
2242
- expires = int((s['valid_to'] - base_time_adjust) * steps_per_hour) if s.get('valid_to') is not None else None
2243
+ valid_for = s.get('valid_for')
2243
2244
  segment = {'start': start, 'end': end, 'mode': mode, 'min_soc': min_soc_now, 'max_soc': max_soc,
2244
- 'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, 'expires': expires}
2245
+ 'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, 'valid_for': valid_for}
2245
2246
  if quiet == 0:
2246
2247
  s = f" {hours_time(start)}-{hours_time(end)} {mode}, min_soc {min_soc_now}%"
2247
2248
  s += f", max_soc {max_soc}%" if max_soc is not None else ""
@@ -2317,21 +2318,22 @@ def get_agile_times(tariff=agile_octopus, d=None):
2317
2318
  # extract times and prices. Times are Zulu (UTC)
2318
2319
  prices = [] # ordered list of 30 minute prices
2319
2320
  for i in range(0, len(results)):
2320
- start = (now.hour + i / 2) % 24
2321
+ hour = i / 2
2322
+ start = (now.hour + hour) % 24
2321
2323
  time_offset = daylight_saving(results[i]['valid_from'][:16]) if daylight_saving is not None else 0
2322
2324
  prices.append({
2323
2325
  'start': start,
2324
2326
  'end': round_time(start + 0.5),
2325
2327
  'time': hours_time(time_hours(results[i]['valid_from'][11:16]) + time_offset + time_shift),
2326
2328
  'price': results[i]['value_inc_vat'],
2327
- 'valid_to': i / 2 + 0.5})
2329
+ 'hour': hour})
2328
2330
  tariff['agile']['base_time'] = period_from.replace('T', ' ')
2329
2331
  tariff['agile']['prices'] = prices
2330
2332
  plunge = []
2331
2333
  plunge_price = tariff_config['plunge_price'] if tariff_config.get('plunge_price') is not None else 2
2332
2334
  plunge_price = [plunge_price] if type(plunge_price) is not list else plunge_price
2333
2335
  plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 6
2334
- for i in range(0, min([48, len(prices)])):
2336
+ for i in range(0, len(prices)):
2335
2337
  # hour relative index into list of plunge prices, starting at 7am
2336
2338
  x = int(((now.hour - 7 + i / 2) % 24) * len(plunge_price) / 24)
2337
2339
  if prices[i] is not None and prices[i]['price'] < plunge_price[x]:
@@ -2591,7 +2593,7 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
2591
2593
  if strategy is not None:
2592
2594
  period['mode'] = 'SelfUse'
2593
2595
  for d in strategy:
2594
- if hour_in(h, d) and (d.get('expires') is None or i < d['expires']):
2596
+ if hour_in(h, d) and (d.get('valid_for') is None or i in d['valid_for']):
2595
2597
  mode = d['mode']
2596
2598
  period['mode'] = mode
2597
2599
  min_soc_now = d['min_soc'] if d.get('min_soc') is not None else min_soc
@@ -3058,18 +3060,16 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3058
3060
  start_residual = interpolate(time_to_start, bat_timed) # residual when charge time starts
3059
3061
  start_soc = int(start_residual / capacity * 100 + 0.5)
3060
3062
  end_residual = interpolate(time_to_end, bat_timed) # residual when charge time ends without charging
3061
- target_soc = charge_config['target_soc'] if charge_config.get('target_soc') is not None else None
3062
- target_kwh = target_soc / 100 * capacity if target_soc is not None else 0
3063
+ target_soc = charge_config.get('target_soc')
3064
+ target_kwh = capacity if full_charge is not None or force_charge == 2 else (target_soc / 100 * capacity) if target_soc is not None else 0
3063
3065
  if target_kwh > (end_residual + kwh_needed):
3064
3066
  kwh_needed = target_kwh - end_residual
3065
- elif full_charge is not None or force_charge == 2:
3066
- kwh_needed = capacity - start_residual
3067
3067
  elif test_charge is not None:
3068
3068
  output(f"\nTest charge of {test_charge}kWh")
3069
3069
  kwh_needed = test_charge
3070
3070
  charge_message = "** test charge **"
3071
3071
  # work out charge needed
3072
- if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh']:
3072
+ if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh'] and test_charge is None:
3073
3073
  output(f"\nNo charging needed:")
3074
3074
  output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
3075
3075
  charge_message = "no charge needed"
@@ -3087,24 +3087,29 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3087
3087
  output(f"\nCharge needed {kwh_needed:.2f}kWh:")
3088
3088
  charge_message = "with charge added"
3089
3089
  output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
3090
- output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(time_to_start, time_line))} ({start_residual:.2f}kWh)")
3091
3090
  # work out time to add kwh_needed to battery
3092
- taper_time = 10/60 if (start_residual + kwh_needed) >= (capacity * 0.95) else 0
3093
- hours = round_time(kwh_needed / (charge_power * charge_loss) + taper_time)
3094
- # charge time exceeded or charge needed exceeds capacity
3095
- if hours > charge_time or (start_residual + kwh_needed) > capacity:
3096
- kwh_needed = capacity - start_residual
3097
- hours = charge_time
3098
- elif hours < charge_config['min_hours']:
3091
+ charge_rate = charge_power * charge_loss
3092
+ hours = round_time(kwh_needed / charge_rate)
3093
+ # check if charge time exceeded or charge needed exceeds capacity
3094
+ hours_to_full = round_time((capacity - start_residual) / (charge_rate) + 10)
3095
+ if hours < charge_config['min_hours']:
3099
3096
  hours = charge_config['min_hours']
3100
- end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
3097
+ elif hours > charge_time:
3098
+ hours = charge_time
3099
+ elif hours > hours_to_full:
3100
+ kwh_shortfall = (hours - hours_to_full) * charge_rate # amount of energy that won't be added
3101
+ required = hours_to_full + charge_time * kwh_shortfall / (end_residual - start_residual) # time to recover energy not added
3102
+ hours = required if required < charge_time else charge_time
3101
3103
  # rework charge and discharge
3102
3104
  charge_period = get_best_charge_period(start_at, hours)
3103
3105
  charge_offset = round_time(charge_period['start'] - start_at) if charge_period is not None else 0
3104
3106
  price = charge_period.get('price') if charge_period is not None else None
3105
3107
  start_timed = time_to_start + charge_offset * steps_per_hour
3106
3108
  end_timed = (start_timed + hours * steps_per_hour) if force_charge == 0 else time_to_end
3107
- output(f" Charge: {hours_time(adjusted_hour(start_timed, time_line))}-{hours_time(adjusted_hour(end_timed, time_line))}" + (f" at {price:5.2f}p/kWh" if price is not None else ""))
3109
+ start_residual = interpolate(start_timed, bat_timed)
3110
+ end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
3111
+ output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(start_timed, time_line))} ({start_residual:.2f}kWh)")
3112
+ output(f" Charge to: {end_soc:.0f} {hours_time(adjusted_hour(start_timed, time_line))}-{hours_time(adjusted_hour(end_timed, time_line))}" + (f" at {price:5.2f}p/kWh" if price is not None else ""))
3108
3113
  for i in range(int(time_to_start), int(end_timed) + 1):
3109
3114
  j = i + 1
3110
3115
  # work out time (fraction of hour) when charging in hour from i to j
@@ -3196,7 +3201,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3196
3201
  end1 = start1 if force_charge == 0 else start2
3197
3202
  end2 = round_time(base_hour + (end_timed if force_charge == 0 else time_to_end) / steps_per_hour)
3198
3203
  if timed_mode > 1:
3199
- periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc, target_soc=end_soc, start_soc=start_soc)
3204
+ periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc, end_soc=end_soc, start_soc=start_soc)
3200
3205
  set_schedule(periods = periods)
3201
3206
  else:
3202
3207
  set_charge(ch1=False, st1=start1, en1=end1, ch2=True, st2=start2, en2=end2, force=1)
foxesscloud/openapi.py CHANGED
@@ -1,7 +1,7 @@
1
1
  ##################################################################################################
2
2
  """
3
3
  Module: Fox ESS Cloud using Open API
4
- Updated: 25 September 2024
4
+ Updated: 26 September 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.4"
13
+ version = "2.5.5"
14
14
  print(f"FoxESS-Cloud Open API version {version}")
15
15
 
16
16
  debug_setting = 1
@@ -669,7 +669,7 @@ def set_charge(ch1=None, st1=None, en1=None, ch2=None, st2=None, en2=None, force
669
669
  output(f"success", 2)
670
670
  return battery_settings
671
671
 
672
- def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, target_soc=100, start_soc=10):
672
+ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, end_soc=100, start_soc=10):
673
673
  output(f"\nConfiguring schedule",1)
674
674
  charge = []
675
675
  st1 = time_hours(st1)
@@ -678,7 +678,7 @@ def charge_periods(st1=None, en1=None, st2=None, en2=None, min_soc=10, target_so
678
678
  en2 = time_hours(en2)
679
679
  span = None
680
680
  if st2 is not None and en2 is not None and st2 != en2:
681
- charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc': target_soc})
681
+ charge.append({'start': st2, 'end': en2, 'mode': 'ForceCharge', 'min_soc': min_soc, 'max_soc': end_soc})
682
682
  span = {'start': st2, 'end': en2}
683
683
  if st1 is not None and en1 is not None and st1 != en1:
684
684
  charge.append({'start': st1, 'end': en1, 'mode': 'SelfUse', 'min_soc': start_soc})
@@ -2085,6 +2085,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
2085
2085
  if use.get('agile') is not None and use['agile'].get('strategy') is not None:
2086
2086
  base_time_adjust = hours_difference(base_time, use['agile'].get('base_time') )
2087
2087
  for s in use['agile']['strategy']:
2088
+ s['valid_for'] = [int((s['hour'] - base_time_adjust) * steps_per_hour + i) for i in range(0, steps_per_hour // 2)] if s.get('hour') is not None else None
2088
2089
  strategy.append(s)
2089
2090
  if strategy is None or len(strategy) == 0:
2090
2091
  return []
@@ -2094,7 +2095,7 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
2094
2095
  start = s['start']
2095
2096
  end = s['end']
2096
2097
  if hour_overlap(s, remove):
2097
- output(f" {hours_time(start)}-{hours_time(end)} ** removed ** (overlaps charge period)", 2)
2098
+ output(f" {hours_time(start)}-{hours_time(end)} was removed from strategy", 2)
2098
2099
  continue
2099
2100
  # add segment
2100
2101
  min_soc_now = s['min_soc'] if s.get('min_soc') is not None and s['min_soc'] > 10 else 10
@@ -2103,9 +2104,9 @@ def get_strategy(use=None, strategy=None, quiet=1, remove=None, reserve=0):
2103
2104
  fdsoc = s.get('fdsoc')
2104
2105
  fdpwr = s.get('fdpwr')
2105
2106
  price = s.get('price')
2106
- expires = int((s['valid_to'] - base_time_adjust) * steps_per_hour) if s.get('valid_to') is not None else None
2107
+ valid_for = s.get('valid_for')
2107
2108
  segment = {'start': start, 'end': end, 'mode': mode, 'min_soc': min_soc_now, 'max_soc': max_soc,
2108
- 'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, 'expires': expires}
2109
+ 'fdsoc': fdsoc, 'fdpwr': fdpwr, 'price': price, 'valid_for': valid_for}
2109
2110
  if quiet == 0:
2110
2111
  s = f" {hours_time(start)}-{hours_time(end)} {mode}, min_soc {min_soc_now}%"
2111
2112
  s += f", max_soc {max_soc}%" if max_soc is not None else ""
@@ -2181,21 +2182,22 @@ def get_agile_times(tariff=agile_octopus, d=None):
2181
2182
  # extract times and prices. Times are Zulu (UTC)
2182
2183
  prices = [] # ordered list of 30 minute prices
2183
2184
  for i in range(0, len(results)):
2184
- start = (now.hour + i / 2) % 24
2185
+ hour = i / 2
2186
+ start = (now.hour + hour) % 24
2185
2187
  time_offset = daylight_saving(results[i]['valid_from'][:16]) if daylight_saving is not None else 0
2186
2188
  prices.append({
2187
2189
  'start': start,
2188
2190
  'end': round_time(start + 0.5),
2189
2191
  'time': hours_time(time_hours(results[i]['valid_from'][11:16]) + time_offset + time_shift),
2190
2192
  'price': results[i]['value_inc_vat'],
2191
- 'valid_to': i / 2 + 0.5})
2193
+ 'hour': hour})
2192
2194
  tariff['agile']['base_time'] = period_from.replace('T', ' ')
2193
2195
  tariff['agile']['prices'] = prices
2194
2196
  plunge = []
2195
2197
  plunge_price = tariff_config['plunge_price'] if tariff_config.get('plunge_price') is not None else 2
2196
2198
  plunge_price = [plunge_price] if type(plunge_price) is not list else plunge_price
2197
2199
  plunge_slots = tariff_config['plunge_slots'] if tariff_config.get('plunge_slots') is not None else 6
2198
- for i in range(0, min([48, len(prices)])):
2200
+ for i in range(0, len(prices)):
2199
2201
  # hour relative index into list of plunge prices, starting at 7am
2200
2202
  x = int(((now.hour - 7 + i / 2) % 24) * len(plunge_price) / 24)
2201
2203
  if prices[i] is not None and prices[i]['price'] < plunge_price[x]:
@@ -2455,7 +2457,7 @@ def strategy_timed(timed_mode, base_hour, run_time, min_soc=10, max_soc=100, cur
2455
2457
  if strategy is not None:
2456
2458
  period['mode'] = 'SelfUse'
2457
2459
  for d in strategy:
2458
- if hour_in(h, d) and (d.get('expires') is None or i < d['expires']):
2460
+ if hour_in(h, d) and (d.get('valid_for') is None or i in d['valid_for']):
2459
2461
  mode = d['mode']
2460
2462
  period['mode'] = mode
2461
2463
  min_soc_now = d['min_soc'] if d.get('min_soc') is not None else min_soc
@@ -2922,18 +2924,16 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2922
2924
  start_residual = interpolate(time_to_start, bat_timed) # residual when charge time starts
2923
2925
  start_soc = int(start_residual / capacity * 100 + 0.5)
2924
2926
  end_residual = interpolate(time_to_end, bat_timed) # residual when charge time ends without charging
2925
- target_soc = charge_config['target_soc'] if charge_config.get('target_soc') is not None else None
2926
- target_kwh = target_soc / 100 * capacity if target_soc is not None else 0
2927
+ target_soc = charge_config.get('target_soc')
2928
+ target_kwh = capacity if full_charge is not None or force_charge == 2 else (target_soc / 100 * capacity) if target_soc is not None else 0
2927
2929
  if target_kwh > (end_residual + kwh_needed):
2928
2930
  kwh_needed = target_kwh - end_residual
2929
- elif full_charge is not None or force_charge == 2:
2930
- kwh_needed = capacity - start_residual
2931
2931
  elif test_charge is not None:
2932
2932
  output(f"\nTest charge of {test_charge}kWh")
2933
2933
  kwh_needed = test_charge
2934
2934
  charge_message = "** test charge **"
2935
2935
  # work out charge needed
2936
- if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh'] and full_charge is None and test_charge is None and force_charge != 2:
2936
+ if kwh_min > (reserve + kwh_contingency) and kwh_needed < charge_config['min_kwh'] and test_charge is None:
2937
2937
  output(f"\nNo charging needed:")
2938
2938
  output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
2939
2939
  charge_message = "no charge needed"
@@ -2951,24 +2951,29 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
2951
2951
  output(f"\nCharge needed {kwh_needed:.2f}kWh:")
2952
2952
  charge_message = "with charge added"
2953
2953
  output(f" SoC now: {current_soc:.0f}% at {hours_time(hour_now)} on {today}")
2954
- output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(time_to_start, time_line))} ({start_residual:.2f}kWh)")
2955
2954
  # work out time to add kwh_needed to battery
2956
- taper_time = 10/60 if (start_residual + kwh_needed) >= (capacity * 0.95) else 0
2957
- hours = round_time(kwh_needed / (charge_power * charge_loss) + taper_time)
2958
- # charge time exceeded or charge needed exceeds capacity
2959
- if hours > charge_time or (start_residual + kwh_needed) > (capacity * 1.01):
2960
- kwh_needed = capacity - start_residual
2961
- hours = charge_time
2962
- elif hours < charge_config['min_hours']:
2955
+ charge_rate = charge_power * charge_loss
2956
+ hours = round_time(kwh_needed / charge_rate)
2957
+ # check if charge time exceeded or charge needed exceeds capacity
2958
+ hours_to_full = round_time((capacity - start_residual) / (charge_rate) + 10)
2959
+ if hours < charge_config['min_hours']:
2963
2960
  hours = charge_config['min_hours']
2964
- end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
2961
+ elif hours > charge_time:
2962
+ hours = charge_time
2963
+ elif hours > hours_to_full:
2964
+ kwh_shortfall = (hours - hours_to_full) * charge_rate # amount of energy that won't be added
2965
+ required = hours_to_full + charge_time * kwh_shortfall / (end_residual - start_residual) # time to recover energy not added
2966
+ hours = required if required < charge_time else charge_time
2965
2967
  # rework charge and discharge
2966
2968
  charge_period = get_best_charge_period(start_at, hours)
2967
2969
  charge_offset = round_time(charge_period['start'] - start_at) if charge_period is not None else 0
2968
2970
  price = charge_period.get('price') if charge_period is not None else None
2969
2971
  start_timed = time_to_start + charge_offset * steps_per_hour
2970
2972
  end_timed = (start_timed + hours * steps_per_hour) if force_charge == 0 else time_to_end
2971
- output(f" Charge: {hours_time(adjusted_hour(start_timed, time_line))}-{hours_time(adjusted_hour(end_timed, time_line))}" + (f" at {price:5.2f}p/kWh" if price is not None else ""))
2973
+ start_residual = interpolate(start_timed, bat_timed)
2974
+ end_soc = min([int((start_residual + kwh_needed) / capacity * 100 + 0.5), 100])
2975
+ output(f" Start SoC: {start_residual / capacity * 100:.0f}% at {hours_time(adjusted_hour(start_timed, time_line))} ({start_residual:.2f}kWh)")
2976
+ output(f" Charge to: {end_soc:.0f}% {hours_time(adjusted_hour(start_timed, time_line))}-{hours_time(adjusted_hour(end_timed, time_line))}" + (f" at {price:5.2f}p/kWh" if price is not None else ""))
2972
2977
  for i in range(int(time_to_start), int(end_timed) + 1):
2973
2978
  j = i + 1
2974
2979
  # work out time (fraction of hour) when charging in hour from i to j
@@ -3060,7 +3065,7 @@ def charge_needed(forecast=None, update_settings=0, timed_mode=None, show_data=N
3060
3065
  end1 = start1 if force_charge == 0 else start2
3061
3066
  end2 = round_time(base_hour + (end_timed if force_charge == 0 else time_to_end) / steps_per_hour)
3062
3067
  if timed_mode > 1:
3063
- periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc, target_soc=end_soc, start_soc=start_soc)
3068
+ periods = charge_periods(st1=start1, en1=end1, st2=start2, en2=end2, min_soc=min_soc, end_soc=end_soc, start_soc=start_soc)
3064
3069
  set_schedule(periods = periods)
3065
3070
  else:
3066
3071
  set_charge(ch1=False, st1=start1, en1=end1, ch2=True, st2=start2, en2=end2, force=1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foxesscloud
3
- Version: 2.5.4
3
+ Version: 2.5.5
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
@@ -783,6 +783,12 @@ This setting can be:
783
783
 
784
784
  # Version Info
785
785
 
786
+ 2.5.5<br>
787
+ Improve validation of plunge price periods so they don't repeat across days.
788
+ Correct start and end soc times and values when charging using best Agile time periods.
789
+ Extend charge times when charge needed exceeds battery capacity.
790
+
791
+
786
792
  2.5.4<br>
787
793
  Remove preset 'weighting' that were not used.
788
794
  Update weighting to apply the requested charge duration correctly.
@@ -0,0 +1,7 @@
1
+ foxesscloud/foxesscloud.py,sha256=biC30gvvQHFTu3CCDhuK6KRGNi2hFbgFxHjSykj-UpE,211062
2
+ foxesscloud/openapi.py,sha256=MEzJPleAV8-2gR17e2qmQezVeotiqkHMzeCBDVJOfsY,204698
3
+ foxesscloud-2.5.5.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
4
+ foxesscloud-2.5.5.dist-info/METADATA,sha256=kvZCDYUyu7v1OkE_dik_f_4V4OXQTaZKwOYWYFTircU,55462
5
+ foxesscloud-2.5.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
+ foxesscloud-2.5.5.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
+ foxesscloud-2.5.5.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- foxesscloud/foxesscloud.py,sha256=elLQc40Zt8QkjmiOhZ2gNe-Wqp5NE38LgnflHpUQMmc,210693
2
- foxesscloud/openapi.py,sha256=aBVMx8eyh5WRbUQTiAFHh6SDNiKeksBplODIO1x3L-k,204407
3
- foxesscloud-2.5.4.dist-info/LICENCE,sha256=-3xv8CElCJV8Bc8PbAsg3iyxMpAK8MoJneM3rXigxqI,1074
4
- foxesscloud-2.5.4.dist-info/METADATA,sha256=KXfCjbfLt8t5jjQbLW_-7UJyoKBvskga5X0sxMkzxmk,55214
5
- foxesscloud-2.5.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
6
- foxesscloud-2.5.4.dist-info/top_level.txt,sha256=IWOrKSNZCLU6IDXSX_b4_bqCfbZoWAT4CC0w0Lg7PuU,12
7
- foxesscloud-2.5.4.dist-info/RECORD,,