@@ -211,6 +211,10 @@ def __init__(
211211 max_data_age_sec = 10.0 ,
212212 )
213213
214+ self ._cached_metrics : dict [int , InvBatPair | None ] = {
215+ bat_id : None for bat_id , _ in self ._bat_inv_map .items ()
216+ }
217+
214218 def _create_users_tasks (self ) -> List [asyncio .Task [None ]]:
215219 """For each user create a task to wait for request.
216220
@@ -224,37 +228,39 @@ def _create_users_tasks(self) -> List[asyncio.Task[None]]:
224228 )
225229 return tasks
226230
227- def _get_upper_bound (self , batteries : Set [int ]) -> float :
231+ def _get_upper_bound (self , batteries : Set [int ], use_all : bool ) -> float :
228232 """Get total upper bound of power to be set for given batteries.
229233
230234 Note, output of that function doesn't guarantee that this bound will be
231235 the same when the request is processed.
232236
233237 Args:
234238 batteries: List of batteries
239+ use_all: whether all batteries in the power request must be used.
235240
236241 Returns:
237242 Upper bound for `set_power` operation.
238243 """
239- pairs_data : List [InvBatPair ] = self ._get_components_data (batteries )
244+ pairs_data : List [InvBatPair ] = self ._get_components_data (batteries , use_all )
240245 return sum (
241246 min (battery .power_upper_bound , inverter .active_power_upper_bound )
242247 for battery , inverter in pairs_data
243248 )
244249
245- def _get_lower_bound (self , batteries : Set [int ]) -> float :
250+ def _get_lower_bound (self , batteries : Set [int ], use_all : bool ) -> float :
246251 """Get total lower bound of power to be set for given batteries.
247252
248253 Note, output of that function doesn't guarantee that this bound will be
249254 the same when the request is processed.
250255
251256 Args:
252257 batteries: List of batteries
258+ use_all: whether all batteries in the power request must be used.
253259
254260 Returns:
255261 Lower bound for `set_power` operation.
256262 """
257- pairs_data : List [InvBatPair ] = self ._get_components_data (batteries )
263+ pairs_data : List [InvBatPair ] = self ._get_components_data (batteries , use_all )
258264 return sum (
259265 max (battery .power_lower_bound , inverter .active_power_lower_bound )
260266 for battery , inverter in pairs_data
@@ -282,21 +288,19 @@ async def run(self) -> None:
282288
283289 try :
284290 pairs_data : List [InvBatPair ] = self ._get_components_data (
285- request .batteries
291+ request .batteries , request . force
286292 )
287293 except KeyError as err :
288294 await user .channel .send (Error (request = request , msg = str (err )))
289295 continue
290296
291- if len (pairs_data ) == 0 :
297+ if len (pairs_data ) == 0 and request . force is False :
292298 error_msg = f"No data for the given batteries { str (request .batteries )} "
293299 await user .channel .send (Error (request = request , msg = str (error_msg )))
294300 continue
295301
296302 try :
297- distribution = self .distribution_algorithm .distribute_power (
298- request .power , pairs_data
299- )
303+ distribution = self ._get_power_distribution (request , pairs_data )
300304 except ValueError as err :
301305 error_msg = f"Couldn't distribute power, error: { str (err )} "
302306 await user .channel .send (Error (request = request , msg = str (error_msg )))
@@ -379,6 +383,34 @@ async def _set_distributed_power(
379383
380384 return self ._parse_result (tasks , distribution .distribution , timeout_sec )
381385
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+
382414 def _check_request (self , request : Request ) -> Optional [Result ]:
383415 """Check whether the given request if correct.
384416
@@ -401,11 +433,11 @@ def _check_request(self, request: Request) -> Optional[Result]:
401433
402434 if not request .adjust_power :
403435 if request .power < 0 :
404- bound = self ._get_lower_bound (request .batteries )
436+ bound = self ._get_lower_bound (request .batteries , request . force )
405437 if request .power < bound :
406438 return OutOfBound (request = request , bound = bound )
407439 else :
408- bound = self ._get_upper_bound (request .batteries )
440+ bound = self ._get_upper_bound (request .batteries , request . force )
409441 if request .power > bound :
410442 return OutOfBound (request = request , bound = bound )
411443
@@ -554,11 +586,14 @@ def _get_components_pairs(
554586
555587 return bat_inv_map , inv_bat_map
556588
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 ]:
558592 """Get data for the given batteries and adjacent inverters.
559593
560594 Args:
561595 batteries: Batteries that needs data.
596+ use_all: whether all batteries in the power request must be used.
562597
563598 Raises:
564599 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]:
568603 """
569604 pairs_data : List [InvBatPair ] = []
570605 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
572609 )
573610
574611 for battery_id in working_batteries :
@@ -581,6 +618,8 @@ def _get_components_data(self, batteries: Set[int]) -> List[InvBatPair]:
581618 inverter_id : int = self ._bat_inv_map [battery_id ]
582619
583620 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 ]
584623 if data is None :
585624 _logger .warning (
586625 "Skipping battery %d because its message isn't correct." ,
@@ -648,7 +687,8 @@ def _get_battery_inverter_data(
648687
649688 # If all values are ok then return them.
650689 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 ]
652692
653693 # Replace NaN with the corresponding value in the adjacent component.
654694 # If both metrics are None, return None to ignore this battery.
@@ -670,10 +710,11 @@ def _get_battery_inverter_data(
670710 elif isnan (inv_bound ):
671711 inverter_new_metrics [inv_attr ] = bat_bound
672712
673- return InvBatPair (
713+ self . _cached_metrics [ battery_id ] = InvBatPair (
674714 replace (battery_data , ** battery_new_metrics ),
675715 replace (inverter_data , ** inverter_new_metrics ),
676716 )
717+ return self ._cached_metrics [battery_id ]
677718
678719 async def _create_channels (self ) -> None :
679720 """Create channels to get data of components in microgrid."""
0 commit comments