@@ -211,6 +211,10 @@ def __init__(
211
211
max_data_age_sec = 10.0 ,
212
212
)
213
213
214
+ self ._cached_metrics : dict [int , InvBatPair | None ] = {
215
+ bat_id : None for bat_id , _ in self ._bat_inv_map .items ()
216
+ }
217
+
214
218
def _create_users_tasks (self ) -> List [asyncio .Task [None ]]:
215
219
"""For each user create a task to wait for request.
216
220
@@ -224,37 +228,39 @@ def _create_users_tasks(self) -> List[asyncio.Task[None]]:
224
228
)
225
229
return tasks
226
230
227
- def _get_upper_bound (self , batteries : Set [int ]) -> float :
231
+ def _get_upper_bound (self , batteries : Set [int ], use_all : bool ) -> float :
228
232
"""Get total upper bound of power to be set for given batteries.
229
233
230
234
Note, output of that function doesn't guarantee that this bound will be
231
235
the same when the request is processed.
232
236
233
237
Args:
234
238
batteries: List of batteries
239
+ use_all: whether all batteries in the power request must be used.
235
240
236
241
Returns:
237
242
Upper bound for `set_power` operation.
238
243
"""
239
- pairs_data : List [InvBatPair ] = self ._get_components_data (batteries )
244
+ pairs_data : List [InvBatPair ] = self ._get_components_data (batteries , use_all )
240
245
return sum (
241
246
min (battery .power_upper_bound , inverter .active_power_upper_bound )
242
247
for battery , inverter in pairs_data
243
248
)
244
249
245
- def _get_lower_bound (self , batteries : Set [int ]) -> float :
250
+ def _get_lower_bound (self , batteries : Set [int ], use_all : bool ) -> float :
246
251
"""Get total lower bound of power to be set for given batteries.
247
252
248
253
Note, output of that function doesn't guarantee that this bound will be
249
254
the same when the request is processed.
250
255
251
256
Args:
252
257
batteries: List of batteries
258
+ use_all: whether all batteries in the power request must be used.
253
259
254
260
Returns:
255
261
Lower bound for `set_power` operation.
256
262
"""
257
- pairs_data : List [InvBatPair ] = self ._get_components_data (batteries )
263
+ pairs_data : List [InvBatPair ] = self ._get_components_data (batteries , use_all )
258
264
return sum (
259
265
max (battery .power_lower_bound , inverter .active_power_lower_bound )
260
266
for battery , inverter in pairs_data
@@ -282,21 +288,19 @@ async def run(self) -> None:
282
288
283
289
try :
284
290
pairs_data : List [InvBatPair ] = self ._get_components_data (
285
- request .batteries
291
+ request .batteries , request . force
286
292
)
287
293
except KeyError as err :
288
294
await user .channel .send (Error (request = request , msg = str (err )))
289
295
continue
290
296
291
- if len (pairs_data ) == 0 :
297
+ if len (pairs_data ) == 0 and request . force is False :
292
298
error_msg = f"No data for the given batteries { str (request .batteries )} "
293
299
await user .channel .send (Error (request = request , msg = str (error_msg )))
294
300
continue
295
301
296
302
try :
297
- distribution = self .distribution_algorithm .distribute_power (
298
- request .power , pairs_data
299
- )
303
+ distribution = self ._get_power_distribution (request , pairs_data )
300
304
except ValueError as err :
301
305
error_msg = f"Couldn't distribute power, error: { str (err )} "
302
306
await user .channel .send (Error (request = request , msg = str (error_msg )))
@@ -379,6 +383,34 @@ async def _set_distributed_power(
379
383
380
384
return self ._parse_result (tasks , distribution .distribution , timeout_sec )
381
385
386
+ def _get_power_distribution (
387
+ self , request : Request , inv_vat_pairs : List [InvBatPair ]
388
+ ) -> DistributionResult :
389
+ """Get power distribution result for the batteries in the request.
390
+
391
+ Args:
392
+ request: the power request to process.
393
+ inv_vat_pairs: the battery and adjacent inverter data pairs.
394
+
395
+ Returns:
396
+ the power distribution result.
397
+ """
398
+ if request .force and len (request .batteries ) != len (inv_vat_pairs ):
399
+ # Distribute power equally for each battery in the request if
400
+ # there are missing components metrics
401
+ power_per_battery = request .power / len (request .batteries )
402
+ return DistributionResult (
403
+ distribution = {
404
+ self ._bat_inv_map [battery_id ]: power_per_battery
405
+ for battery_id in request .batteries
406
+ },
407
+ remaining_power = 0.0 ,
408
+ )
409
+
410
+ return self .distribution_algorithm .distribute_power (
411
+ request .power , inv_vat_pairs
412
+ )
413
+
382
414
def _check_request (self , request : Request ) -> Optional [Result ]:
383
415
"""Check whether the given request if correct.
384
416
@@ -401,11 +433,11 @@ def _check_request(self, request: Request) -> Optional[Result]:
401
433
402
434
if not request .adjust_power :
403
435
if request .power < 0 :
404
- bound = self ._get_lower_bound (request .batteries )
436
+ bound = self ._get_lower_bound (request .batteries , request . force )
405
437
if request .power < bound :
406
438
return OutOfBound (request = request , bound = bound )
407
439
else :
408
- bound = self ._get_upper_bound (request .batteries )
440
+ bound = self ._get_upper_bound (request .batteries , request . force )
409
441
if request .power > bound :
410
442
return OutOfBound (request = request , bound = bound )
411
443
@@ -554,11 +586,14 @@ def _get_components_pairs(
554
586
555
587
return bat_inv_map , inv_bat_map
556
588
557
- def _get_components_data (self , batteries : Set [int ]) -> List [InvBatPair ]:
589
+ def _get_components_data (
590
+ self , batteries : Set [int ], use_all : bool
591
+ ) -> List [InvBatPair ]:
558
592
"""Get data for the given batteries and adjacent inverters.
559
593
560
594
Args:
561
595
batteries: Batteries that needs data.
596
+ use_all: whether all batteries in the power request must be used.
562
597
563
598
Raises:
564
599
KeyError: If any battery in the given list doesn't exists in microgrid.
@@ -568,7 +603,9 @@ def _get_components_data(self, batteries: Set[int]) -> List[InvBatPair]:
568
603
"""
569
604
pairs_data : List [InvBatPair ] = []
570
605
working_batteries = (
571
- self ._all_battery_status .get_working_batteries (batteries ) or batteries
606
+ batteries
607
+ if use_all
608
+ else self ._all_battery_status .get_working_batteries (batteries ) or batteries
572
609
)
573
610
574
611
for battery_id in working_batteries :
@@ -581,6 +618,8 @@ def _get_components_data(self, batteries: Set[int]) -> List[InvBatPair]:
581
618
inverter_id : int = self ._bat_inv_map [battery_id ]
582
619
583
620
data = self ._get_battery_inverter_data (battery_id , inverter_id )
621
+ if data is None and use_all is True :
622
+ data = self ._cached_metrics [battery_id ]
584
623
if data is None :
585
624
_logger .warning (
586
625
"Skipping battery %d because its message isn't correct." ,
@@ -648,7 +687,8 @@ def _get_battery_inverter_data(
648
687
649
688
# If all values are ok then return them.
650
689
if not any (map (isnan , replaceable_metrics )):
651
- return InvBatPair (battery_data , inverter_data )
690
+ self ._cached_metrics [battery_id ] = InvBatPair (battery_data , inverter_data )
691
+ return self ._cached_metrics [battery_id ]
652
692
653
693
# Replace NaN with the corresponding value in the adjacent component.
654
694
# If both metrics are None, return None to ignore this battery.
@@ -670,10 +710,11 @@ def _get_battery_inverter_data(
670
710
elif isnan (inv_bound ):
671
711
inverter_new_metrics [inv_attr ] = bat_bound
672
712
673
- return InvBatPair (
713
+ self . _cached_metrics [ battery_id ] = InvBatPair (
674
714
replace (battery_data , ** battery_new_metrics ),
675
715
replace (inverter_data , ** inverter_new_metrics ),
676
716
)
717
+ return self ._cached_metrics [battery_id ]
677
718
678
719
async def _create_channels (self ) -> None :
679
720
"""Create channels to get data of components in microgrid."""
0 commit comments