refactor(ft546): register shuttle runtimes from configuration

This commit is contained in:
zgw
2026-08-11 11:58:53 +08:00
parent 11a21e0688
commit 46302c0de1
6 changed files with 263 additions and 50 deletions

View File

@@ -0,0 +1,53 @@
package com.ruoyi.project.ft546.device;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Component
public class Ft546DeviceRuntimeRegistry {
private final ConcurrentMap<String, PlcSrgvRead> shuttleRuntimes =
new ConcurrentHashMap<String, PlcSrgvRead>();
public void register(String deviceId, PlcSrgvRead runtime) {
String id = requireText(deviceId, "deviceId");
PlcSrgvRead value = Objects.requireNonNull(runtime, "runtime");
PlcSrgvRead existing = shuttleRuntimes.putIfAbsent(id, value);
if (existing != null && existing != value) {
throw new IllegalStateException(
"runtime already registered for device " + id);
}
}
public PlcSrgvRead requireSrgv(String deviceId) {
String id = requireText(deviceId, "deviceId");
PlcSrgvRead runtime = shuttleRuntimes.get(id);
if (runtime == null) {
throw new IllegalArgumentException(
"shuttle runtime not found: " + id);
}
return runtime;
}
public Map<String, PlcSrgvRead> allSrgv() {
return Collections.unmodifiableMap(
new LinkedHashMap<String, PlcSrgvRead>(shuttleRuntimes));
}
public void clear() {
shuttleRuntimes.clear();
}
private static String requireText(String value, String field) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException(field + " must not be blank");
}
return value.trim();
}
}

View File

@@ -0,0 +1,53 @@
package com.ruoyi.project.ft546.device.shuttle;
import com.ruoyi.common.domain.PlcConnectInfo;
import com.ruoyi.device.srgv.domain.DeviceSrgv;
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
import org.springframework.stereotype.Component;
@Component
public class Ft546SrgvRuntimeFactory {
public PlcSrgvRead create(DeviceSrgv device,
Ft546DeviceProfile profile) {
if (device == null || device.getDeviceId() == null) {
throw new IllegalArgumentException("device identity is required");
}
if (profile == null
|| !String.valueOf(device.getDeviceId()).equals(
profile.getDeviceId())) {
throw new IllegalArgumentException(
"profile belongs to another device");
}
if (!"SHUTTLE".equals(profile.getDeviceType())
|| !"FT546_LEGACY_SRGV".equals(profile.getAdapterType())) {
throw new IllegalArgumentException(
"profile is not a legacy shuttle profile");
}
String ip = hasText(profile.getIp()) ? profile.getIp().trim()
: device.getIp();
Integer port = profile.getPort() != null ? profile.getPort()
: device.getPort();
if (!hasText(ip) || port == null || port <= 0 || port > 65535) {
throw new IllegalArgumentException(
"device endpoint is not configured");
}
int station = profile.getStation() == null ? 1 : profile.getStation();
return new PlcSrgvRead(device, new PlcConnectInfo(ip, port), station);
}
public PlcSrgvRead create(DeviceSrgv device) {
Ft546DeviceProfile profile = new Ft546DeviceProfile();
profile.setDeviceId(String.valueOf(device.getDeviceId()));
profile.setDeviceType("SHUTTLE");
profile.setAdapterType("FT546_LEGACY_SRGV");
profile.setIp(device.getIp());
profile.setPort(device.getPort());
return create(device, profile);
}
private static boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}
}

View File

@@ -12,6 +12,8 @@ import com.ruoyi.device.ssj.domain.DeviceSsj;
import com.ruoyi.device.ssj.service.IDeviceSsjService;
import com.ruoyi.device.tsj.domain.DeviceTsj;
import com.ruoyi.device.tsj.service.IDeviceTsjService;
import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry;
import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory;
import com.ruoyi.schedule.plcRead.cdz.PlcCdzRead;
import com.ruoyi.schedule.plcRead.ddj.PlcDdjRead;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
@@ -30,6 +32,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@@ -39,9 +42,9 @@ public class ScheduleThread {
public CustomThread<PlcSsjRead> plcSsjReadCustomThread;
public CustomThread<PlcTsjRead> plcTsjReadCustomThread;
public CustomThread<PlcCdzRead> plcCdzReadCustomThread;
public CustomThread<PlcSrgvRead> plcSrgvReadCustomThread1;
public CustomThread<PlcSrgvRead> plcSrgvReadCustomThread2;
public CustomThread<PlcSrgvRead> plcSrgvReadCustomThread3;
public final Map<String, CustomThread<PlcSrgvRead>>
plcSrgvReadCustomThreads =
new ConcurrentHashMap<String, CustomThread<PlcSrgvRead>>();
public CustomThread<MonitoringThread> monitoringCustomThread;
@@ -58,6 +61,10 @@ public class ScheduleThread {
private IDeviceTsjService deviceTsjService;
@Autowired
private IDeviceCdzService deviceCdzService;
@Autowired
private Ft546DeviceRuntimeRegistry deviceRuntimeRegistry;
@Autowired
private Ft546SrgvRuntimeFactory srgvRuntimeFactory;
private boolean isRun = false;
private boolean isRun2 = false;
@@ -128,25 +135,14 @@ public class ScheduleThread {
ConcurrentHashMap<String, DeviceSrgv> srgvList = new ConcurrentHashMap<>();
List<DeviceSrgv> srgvs=deviceSrgvService.selectDeviceSrgvList(new DeviceSrgv());
for(DeviceSrgv srgv:srgvs){
srgvList.put(srgv.getDeviceId().toString(),srgv);
switch (srgv.getDeviceId()){
case 1:
PlcSrgvRead plcSrgvRead1=new PlcSrgvRead(srgv,new PlcConnectInfo(srgv.getIp(),srgv.getPort()));
Thread plcSrgvReadThread1 = new Thread((plcSrgvRead1));
plcSrgvReadCustomThread1=new CustomThread<PlcSrgvRead>(plcSrgvReadThread1,plcSrgvRead1);
break;
case 2:
PlcSrgvRead plcSrgvRead2=new PlcSrgvRead(srgv,new PlcConnectInfo(srgv.getIp(),srgv.getPort()));
Thread plcSrgvReadThread2 = new Thread((plcSrgvRead2));
plcSrgvReadCustomThread2=new CustomThread<PlcSrgvRead>(plcSrgvReadThread2,plcSrgvRead2);
break;
case 3:
PlcSrgvRead plcSrgvRead3=new PlcSrgvRead(srgv,new PlcConnectInfo(srgv.getIp(),srgv.getPort()));
Thread plcSrgvReadThread3 = new Thread((plcSrgvRead3));
plcSrgvReadCustomThread3=new CustomThread<PlcSrgvRead>(plcSrgvReadThread3,plcSrgvRead3);
break;
}
String deviceId = srgv.getDeviceId().toString();
srgvList.put(deviceId,srgv);
PlcSrgvRead plcSrgvRead = srgvRuntimeFactory.create(srgv);
deviceRuntimeRegistry.register(deviceId, plcSrgvRead);
Thread plcSrgvReadThread = new Thread(plcSrgvRead);
plcSrgvReadCustomThreads.put(deviceId,
new CustomThread<PlcSrgvRead>(plcSrgvReadThread,
plcSrgvRead));
}
PlcSrgvRead.deviceSrgvMap=srgvList;
@@ -177,9 +173,10 @@ public class ScheduleThread {
*/
public void startAllThread() {
plcSsjReadCustomThread.getThread().start();
plcSrgvReadCustomThread1.getThread().start();
plcSrgvReadCustomThread2.getThread().start();
plcSrgvReadCustomThread3.getThread().start();
for (CustomThread<PlcSrgvRead> reader
: plcSrgvReadCustomThreads.values()) {
reader.getThread().start();
}
plcTsjReadCustomThread.getThread().start();
plcCdzReadCustomThread.getThread().start();
@@ -205,6 +202,12 @@ public class ScheduleThread {
*/
public void stopAllThread() {
plcSsjReadCustomThread.runnable.isRun = false;
for (CustomThread<PlcSrgvRead> reader
: plcSrgvReadCustomThreads.values()) {
reader.runnable.isRun = false;
}
plcSrgvReadCustomThreads.clear();
deviceRuntimeRegistry.clear();
// plcDdj1ReadCustomThread.runnable.isRun = false;
/* plcDdj2ReadCustomThread.runnable.isRun = false;
plcDdj3ReadCustomThread.runnable.isRun = false;

View File

@@ -29,10 +29,22 @@ public class PlcSrgvRead implements Runnable {
private IByteTransform byteTransform = null;
public PlcSrgvRead(DeviceSrgv deviceSrgv, PlcConnectInfo plcConnectInfo) {
this(deviceSrgv, plcConnectInfo, 1);
}
public PlcSrgvRead(DeviceSrgv deviceSrgv, PlcConnectInfo plcConnectInfo,
int station) {
if (station < 0 || station > 255) {
throw new IllegalArgumentException("station is invalid");
}
this.deviceSrgv = deviceSrgv;
this.ip = plcConnectInfo.getIp();
this.port = plcConnectInfo.getPort();
this.station = (byte) 1;
this.station = (byte) station;
}
public DeviceSrgv getDeviceSrgv() {
return deviceSrgv;
}

View File

@@ -13,7 +13,6 @@ import com.ruoyi.bas.srgvmap.domain.WcsSrgvMap;
import com.ruoyi.bas.srgvmap.service.IWcsSrgvMapService;
import com.ruoyi.bas.srgvmap.service.RoadCacheService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.domain.CustomThread;
import com.ruoyi.common.domain.SendData2;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.device.srgv.domain.DeviceSrgv;
@@ -28,7 +27,7 @@ import com.ruoyi.job.service.impl.JobCallBackServiceImpl;
import com.ruoyi.queue.srgv.domain.WcsSrgvQueue;
import com.ruoyi.queue.srgv.service.IWcsSrgvQueueFinishService;
import com.ruoyi.queue.srgv.service.IWcsSrgvQueueService;
import com.ruoyi.schedule.ScheduleThread;
import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
import com.ruoyi.schedule.plcRead.srgv.roadUtil.PathFinder;
import com.ruoyi.schedule.plcRead.ssj.PlcSsjRead;
@@ -73,10 +72,10 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface {
public void init() {
sRgvCodeList = new HashMap<>();
ArrayList<String> sRgvCodes = new ArrayList<>();
sRgvCodes.add("1");
sRgvCodes.add("2");
sRgvCodes.add("3");
Ft546DeviceRuntimeRegistry runtimeRegistry =
SpringUtils.getBean(Ft546DeviceRuntimeRegistry.class);
ArrayList<String> sRgvCodes = new ArrayList<>(
runtimeRegistry.allSrgv().keySet());
sRgvCodeList.put("srgv", sRgvCodes);
wcsSagvQueueMapper = SpringUtils.getBean(IWcsSrgvQueueService.class);
@@ -1380,26 +1379,18 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface {
private boolean sendCarTask(List<SendData2> sendDataList, DeviceSrgv deviceSrgv) {
boolean b = true;
ScheduleThread scheduleThread = SpringUtils.getBean(ScheduleThread.class);
CustomThread<?> ct = null;
Integer deviceId = deviceSrgv.getDeviceId();
if (deviceId != null) {
switch (deviceId) {
case 1:
ct = scheduleThread.plcSrgvReadCustomThread1;
break;
case 2:
ct = scheduleThread.plcSrgvReadCustomThread2;
break;
case 3:
ct = scheduleThread.plcSrgvReadCustomThread3;
break;
default:
break;
}
if (deviceId == null) {
return false;
}
PlcSrgvRead plcReader = ct != null ? (PlcSrgvRead) ct.getRunnable() : null;
if (plcReader == null) {
Ft546DeviceRuntimeRegistry runtimeRegistry =
SpringUtils.getBean(Ft546DeviceRuntimeRegistry.class);
PlcSrgvRead plcReader;
try {
plcReader = runtimeRegistry.requireSrgv(
String.valueOf(deviceId));
} catch (IllegalArgumentException missingRuntime) {
log.warn("小车 {} 没有已注册的 PLC 运行时,取消本次下发", deviceId);
return false;
}
for (SendData2 sendData : sendDataList) {

View File

@@ -0,0 +1,101 @@
package com.ruoyi.project.ft546.device;
import com.ruoyi.common.domain.PlcConnectInfo;
import com.ruoyi.device.srgv.domain.DeviceSrgv;
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
class Ft546DeviceRuntimeRegistryTest {
@Test
void registersAnyConfiguredNumberOfShuttleRuntimes() {
Ft546DeviceRuntimeRegistry registry = new Ft546DeviceRuntimeRegistry();
PlcSrgvRead first = runtime("1");
PlcSrgvRead second = runtime("2");
PlcSrgvRead ninth = runtime("9");
registry.register("1", first);
registry.register("2", second);
registry.register("9", ninth);
assertEquals(3, registry.allSrgv().size());
assertSame(ninth, registry.requireSrgv("9"));
}
@Test
void duplicateDeviceCannotSilentlyReplaceAnActiveRuntime() {
Ft546DeviceRuntimeRegistry registry = new Ft546DeviceRuntimeRegistry();
registry.register("1", runtime("1"));
assertThrows(IllegalStateException.class,
() -> registry.register("1", runtime("1")));
}
@Test
void factoryRejectsAProfileForAnotherDevice() {
DeviceSrgv device = device("1");
Ft546DeviceProfile profile = new Ft546DeviceProfile();
profile.setDeviceId("2");
profile.setDeviceType("SHUTTLE");
profile.setAdapterType("FT546_LEGACY_SRGV");
assertThrows(IllegalArgumentException.class,
() -> new Ft546SrgvRuntimeFactory().create(device, profile));
}
@Test
void schedulingAndLegacySendPathsDoNotSelectThreeFixedReaders()
throws Exception {
String schedule = readSource(
"com/ruoyi/schedule/ScheduleThread.java");
String legacy = readSource("com/ruoyi/schedule/scheduleTask/srgv/"
+ "BaseSRgvThread.java");
assertFalse(schedule.contains("plcSrgvReadCustomThread1"));
assertFalse(schedule.contains("case 1:"));
assertFalse(legacy.contains("plcSrgvReadCustomThread1"));
assertFalse(legacy.contains("switch (deviceId)"));
}
private static PlcSrgvRead runtime(String id) {
DeviceSrgv device = device(id);
return new PlcSrgvRead(device,
new PlcConnectInfo(device.getIp(), device.getPort()));
}
private static DeviceSrgv device(String id) {
DeviceSrgv device = new DeviceSrgv();
device.setDeviceId(Integer.valueOf(id));
device.setIp("127.0.0." + id);
device.setPort(502);
return device;
}
private static String readSource(String relative) throws Exception {
Path current = Paths.get(System.getProperty(
"maven.multiModuleProjectDirectory", "."))
.toAbsolutePath().normalize();
while (current != null) {
Path source = current.resolve(Paths.get("ruoyi-admin", "src",
"main", "java", relative));
if (Files.isRegularFile(source)) {
return new String(Files.readAllBytes(source),
StandardCharsets.UTF_8);
}
current = current.getParent();
}
throw new java.nio.file.NoSuchFileException(relative);
}
}