feat(wcs): coordinate conveyor ports and charging

This commit is contained in:
zgw
2026-08-11 10:51:23 +08:00
parent 6030deb227
commit ef3cac9e3e
10 changed files with 438 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
package com.ruoyi.wcs.application.device;
import com.ruoyi.wcs.domain.equipment.AvailableChargers;
import com.ruoyi.wcs.domain.equipment.ChargingPolicyDecision;
import com.ruoyi.wcs.domain.equipment.ConveyorPortState;
import com.ruoyi.wcs.domain.equipment.DeviceCapability;
import com.ruoyi.wcs.domain.equipment.DeviceDescriptor;
import com.ruoyi.wcs.domain.equipment.DeviceType;
import com.ruoyi.wcs.domain.equipment.PendingWorkload;
import com.ruoyi.wcs.domain.equipment.PortMode;
import com.ruoyi.wcs.domain.equipment.VehicleSnapshot;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ConveyorAndChargingCoordinatorTest {
@Test
void locksPortAgainstNewOutboundAssignmentDuringInboundSwitchPermission() {
ConveyorCoordinator coordinator = new ConveyorCoordinator();
coordinator.register(new ConveyorPortState("PORT-1", PortMode.OUTBOUND));
coordinator.grantModeSwitch("PORT-1", PortMode.INBOUND);
assertFalse(coordinator.canAssignOutbound("PORT-1"));
assertTrue(coordinator.isSwitchLocked("PORT-1"));
}
@Test
void refusesModeSwitchWhileJobsRemainBoundToThePort() {
ConveyorCoordinator coordinator = new ConveyorCoordinator();
ConveyorPortState port = new ConveyorPortState("PORT-1", PortMode.OUTBOUND);
port.bindJob("JOB-1");
coordinator.register(port);
assertFalse(coordinator.requestModeSwitch("PORT-1", PortMode.INBOUND));
assertTrue(coordinator.canAssignOutbound("PORT-1"));
}
@Test
void forcedChargeWinsOverOrdinaryReadyTask() {
ChargingCoordinator charging = new ChargingCoordinator(20, 40);
ChargingPolicyDecision decision = charging.decide(vehicle(15,
VehicleSnapshot.OccupationState.IDLE),
new PendingWorkload(0, 3, Duration.ofMinutes(2)), chargers());
assertEquals(ChargingPolicyDecision.Type.FORCED_CHARGE,
decision.getType());
assertEquals("CHARGER-NEAR", decision.getChargerId());
}
@Test
void batteryThresholdNeverInterruptsAnActivePhysicalTask() {
ChargingCoordinator charging = new ChargingCoordinator(20, 40);
ChargingPolicyDecision decision = charging.decide(vehicle(10,
VehicleSnapshot.OccupationState.BUSY),
new PendingWorkload(0, 0, Duration.ZERO), chargers());
assertEquals(ChargingPolicyDecision.Type.NOT_ELIGIBLE,
decision.getType());
}
private static AvailableChargers chargers() {
return new AvailableChargers(Arrays.asList(
new AvailableChargers.Charger("CHARGER-FAR", 8),
new AvailableChargers.Charger("CHARGER-NEAR", 2)));
}
private static VehicleSnapshot vehicle(
int battery, VehicleSnapshot.OccupationState occupation) {
DeviceDescriptor descriptor = new DeviceDescriptor("V1",
DeviceType.FOUR_WAY_SHUTTLE, "F1",
Collections.singleton(DeviceCapability.CHARGING));
return new VehicleSnapshot(descriptor,
VehicleSnapshot.ConnectionState.ONLINE,
VehicleSnapshot.OperatingMode.AUTO,
VehicleSnapshot.HealthState.NORMAL, occupation,
"A", null, battery, occupation == VehicleSnapshot.OccupationState.BUSY
? "CMD-1" : null, "F1", Instant.EPOCH);
}
}