@@ -549,6 +549,180 @@ async def test_two_batteries_three_inverters(self, mocker: MockerFixture) -> Non
549549 assert result .excess_power .isclose (Power .from_watts (200.0 ))
550550 assert result .request == request
551551
552+ async def test_two_batteries_one_inverter_different_exclusion_bounds_2 (
553+ self , mocker : MockerFixture
554+ ) -> None :
555+ """Test power distribution with two batteries having different exclusion bounds.
556+
557+ Test if power distribution works with two batteries connected to one
558+ inverter, each having different exclusion bounds.
559+ """
560+ gen = GraphGenerator ()
561+ batteries = gen .components (* [ComponentCategory .BATTERY ] * 2 )
562+ inverter = gen .component (ComponentCategory .INVERTER )
563+
564+ graph = gen .to_graph (
565+ (
566+ ComponentCategory .METER ,
567+ (ComponentCategory .METER , [(inverter , [* batteries ])]),
568+ )
569+ )
570+
571+ mockgrid = MockMicrogrid (graph = graph )
572+ await mockgrid .start (mocker )
573+ await self .init_component_data (mockgrid )
574+ await mockgrid .mock_client .send (
575+ inverter_msg (
576+ inverter .component_id ,
577+ power = PowerBounds (- 1000 , - 500 , 500 , 1000 ),
578+ )
579+ )
580+ await mockgrid .mock_client .send (
581+ battery_msg (
582+ batteries [0 ].component_id ,
583+ soc = Metric (40 , Bound (20 , 80 )),
584+ capacity = Metric (10_000 ),
585+ power = PowerBounds (- 1000 , - 200 , 200 , 1000 ),
586+ )
587+ )
588+ await mockgrid .mock_client .send (
589+ battery_msg (
590+ batteries [1 ].component_id ,
591+ soc = Metric (40 , Bound (20 , 80 )),
592+ capacity = Metric (10_000 ),
593+ power = PowerBounds (- 1000 , - 100 , 100 , 1000 ),
594+ )
595+ )
596+
597+ channel = Broadcast [Request ]("power_distributor" )
598+ channel_registry = ChannelRegistry (name = "power_distributor" )
599+
600+ request = Request (
601+ namespace = self ._namespace ,
602+ power = Power .from_watts (300.0 ),
603+ batteries = {batteries [0 ].component_id , batteries [1 ].component_id },
604+ request_timeout = SAFETY_TIMEOUT ,
605+ )
606+
607+ attrs = {"get_working_batteries.return_value" : request .batteries }
608+
609+ mocker .patch (
610+ "frequenz.sdk.actor.power_distributing.power_distributing.BatteryPoolStatus" ,
611+ return_value = MagicMock (spec = BatteryPoolStatus , ** attrs ),
612+ )
613+
614+ mocker .patch ("asyncio.sleep" , new_callable = AsyncMock )
615+ battery_status_channel = Broadcast [BatteryStatus ]("battery_status" )
616+
617+ async with PowerDistributingActor (
618+ requests_receiver = channel .new_receiver (),
619+ channel_registry = channel_registry ,
620+ battery_status_sender = battery_status_channel .new_sender (),
621+ ):
622+ await channel .new_sender ().send (request )
623+ result_rx = channel_registry .new_receiver (self ._namespace )
624+
625+ done , pending = await asyncio .wait (
626+ [asyncio .create_task (result_rx .receive ())],
627+ timeout = SAFETY_TIMEOUT .total_seconds (),
628+ )
629+
630+ assert len (pending ) == 0
631+ assert len (done ) == 1
632+
633+ result : Result = done .pop ().result ()
634+ assert isinstance (result , OutOfBounds )
635+ assert result .request == request
636+ assert result .bounds == PowerBounds (- 1000 , - 500 , 500 , 1000 )
637+
638+ async def test_two_batteries_one_inverter_different_exclusion_bounds (
639+ self , mocker : MockerFixture
640+ ) -> None :
641+ """Test power distribution with two batteries having different exclusion bounds.
642+
643+ Test if power distribution works with two batteries connected to one
644+ inverter, each having different exclusion bounds.
645+ """
646+ gen = GraphGenerator ()
647+ batteries = gen .components (* [ComponentCategory .BATTERY ] * 2 )
648+
649+ graph = gen .to_graph (
650+ (
651+ ComponentCategory .METER ,
652+ (
653+ ComponentCategory .METER ,
654+ [
655+ (
656+ ComponentCategory .INVERTER ,
657+ [* batteries ],
658+ ),
659+ ],
660+ ),
661+ )
662+ )
663+
664+ mockgrid = MockMicrogrid (graph = graph )
665+ await mockgrid .start (mocker )
666+ await self .init_component_data (mockgrid )
667+ await mockgrid .mock_client .send (
668+ battery_msg (
669+ batteries [0 ].component_id ,
670+ soc = Metric (40 , Bound (20 , 80 )),
671+ capacity = Metric (10_000 ),
672+ power = PowerBounds (- 1000 , - 200 , 200 , 1000 ),
673+ )
674+ )
675+ await mockgrid .mock_client .send (
676+ battery_msg (
677+ batteries [1 ].component_id ,
678+ soc = Metric (40 , Bound (20 , 80 )),
679+ capacity = Metric (10_000 ),
680+ power = PowerBounds (- 1000 , - 100 , 100 , 1000 ),
681+ )
682+ )
683+
684+ channel = Broadcast [Request ]("power_distributor" )
685+ channel_registry = ChannelRegistry (name = "power_distributor" )
686+
687+ request = Request (
688+ namespace = self ._namespace ,
689+ power = Power .from_watts (300.0 ),
690+ batteries = {batteries [0 ].component_id , batteries [1 ].component_id },
691+ request_timeout = SAFETY_TIMEOUT ,
692+ )
693+
694+ attrs = {"get_working_batteries.return_value" : request .batteries }
695+
696+ mocker .patch (
697+ "frequenz.sdk.actor.power_distributing.power_distributing.BatteryPoolStatus" ,
698+ return_value = MagicMock (spec = BatteryPoolStatus , ** attrs ),
699+ )
700+
701+ mocker .patch ("asyncio.sleep" , new_callable = AsyncMock )
702+ battery_status_channel = Broadcast [BatteryStatus ]("battery_status" )
703+
704+ async with PowerDistributingActor (
705+ requests_receiver = channel .new_receiver (),
706+ channel_registry = channel_registry ,
707+ battery_status_sender = battery_status_channel .new_sender (),
708+ ):
709+ await channel .new_sender ().send (request )
710+ result_rx = channel_registry .new_receiver (self ._namespace )
711+
712+ done , pending = await asyncio .wait (
713+ [asyncio .create_task (result_rx .receive ())],
714+ timeout = SAFETY_TIMEOUT .total_seconds (),
715+ )
716+
717+ assert len (pending ) == 0
718+ assert len (done ) == 1
719+
720+ result : Result = done .pop ().result ()
721+ assert isinstance (result , OutOfBounds )
722+ assert result .request == request
723+ # each inverter is bounded at 500
724+ assert result .bounds == PowerBounds (- 500 , - 400 , 400 , 500 )
725+
552726 async def test_connected_but_not_requested_batteries (
553727 self , mocker : MockerFixture
554728 ) -> None :
0 commit comments