diff --git a/nl-task/pom.xml b/nl-task/pom.xml new file mode 100644 index 0000000..a8760c4 --- /dev/null +++ b/nl-task/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.nl + nl-tool-platform + 3.0.0 + + + nl-task + + + 17 + 17 + UTF-8 + + + + + org.projectlombok + lombok + true + + + jakarta.annotation + jakarta.annotation-api + + + org.nl + nl-common + + + + diff --git a/nl-task/src/main/java/org/nl/task/modular/core/schedule/DeviceSignalSchedule.java b/nl-task/src/main/java/org/nl/task/modular/core/schedule/DeviceSignalSchedule.java new file mode 100644 index 0000000..60c1d14 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/core/schedule/DeviceSignalSchedule.java @@ -0,0 +1,60 @@ +package org.nl.task.modular.core.schedule; + +import lombok.extern.slf4j.Slf4j; +import org.nl.task.modular.task.handler.AbstractDeviceHandler; +import org.nl.task.modular.task.manage.DeviceManager; +import org.nl.task.modular.task.model.SignalEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.BlockingQueue; + +@Slf4j +@Component +public class DeviceSignalSchedule { + + @Autowired + private DeviceManager deviceManager; + + /** + * 定时消费所有设备事件队列 + */ + @Scheduled(fixedDelay = 3000) + public void processSignalQueue() { + Map> handlers = deviceManager.getHandlers(); + for (AbstractDeviceHandler handler : handlers.values()) { + BlockingQueue queue = handler.getEngine().getQueue(); + consumeQueue(handler, queue); + } + } + + private void consumeQueue(AbstractDeviceHandler handler, BlockingQueue queue) { + SignalEvent event; + while ((event = queue.peek()) != null) { + try { + handleEvent(event); + queue.poll(); + removeEventIndex(handler, event); + log.info("处理信号成功,设备号:{}, 信号:{}, 信号值:{}", event.getDeviceCode(), event.getAlias(), event.getValue()); + } catch (Exception e) { + log.error("处理信号事件失败, deviceCode={}, alias={}, value={}", event.getDeviceCode(), event.getAlias(), event.getValue(), e); + break; + } + } + } + + /** + * 处理信号事件 + */ + private void handleEvent(SignalEvent event) { + + } + + private void removeEventIndex(AbstractDeviceHandler handler, SignalEvent event) { + String key = event.getDeviceCode() + ":" + event.getAlias(); + handler.getEngine().getEventIndex().remove(key, event); + } + +} diff --git a/nl-task/src/main/java/org/nl/task/modular/task/annotation/DeviceType.java b/nl-task/src/main/java/org/nl/task/modular/task/annotation/DeviceType.java new file mode 100644 index 0000000..73095ae --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/annotation/DeviceType.java @@ -0,0 +1,10 @@ +package org.nl.task.modular.task.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DeviceType { + String value(); +} diff --git a/nl-task/src/main/java/org/nl/task/modular/task/annotation/EnqueueWhen.java b/nl-task/src/main/java/org/nl/task/modular/task/annotation/EnqueueWhen.java new file mode 100644 index 0000000..be98846 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/annotation/EnqueueWhen.java @@ -0,0 +1,12 @@ +package org.nl.task.modular.task.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EnqueueWhen { + String[] values(); + + boolean change() default true; +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/annotation/SignalAlias.java b/nl-task/src/main/java/org/nl/task/modular/task/annotation/SignalAlias.java new file mode 100644 index 0000000..163ec78 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/annotation/SignalAlias.java @@ -0,0 +1,10 @@ +package org.nl.task.modular.task.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SignalAlias { + String value(); +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/config/DeviceRegisterConfig.java b/nl-task/src/main/java/org/nl/task/modular/task/config/DeviceRegisterConfig.java new file mode 100644 index 0000000..c01a734 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/config/DeviceRegisterConfig.java @@ -0,0 +1,29 @@ +package org.nl.task.modular.task.config; + +import jakarta.annotation.PostConstruct; +import org.nl.task.modular.task.annotation.DeviceType; +import org.nl.task.modular.task.handler.AbstractDeviceHandler; +import org.nl.task.modular.task.manage.DeviceManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class DeviceRegisterConfig { + + @Autowired(required = false) + private DeviceManager deviceManager; + @Autowired + private List> handlers; + + @PostConstruct + public void registerHandlers() { + for (AbstractDeviceHandler handler : handlers) { + DeviceType deviceType = handler.getClass().getAnnotation(DeviceType.class); + if (deviceType != null) { + deviceManager.register(deviceType.value(), handler); + } + } + } +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/engine/SignalEngine.java b/nl-task/src/main/java/org/nl/task/modular/task/engine/SignalEngine.java new file mode 100644 index 0000000..6932edb --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/engine/SignalEngine.java @@ -0,0 +1,45 @@ +package org.nl.task.modular.task.engine; + +import lombok.Getter; +import org.nl.task.modular.task.model.SignalEvent; + +import java.util.Map; +import java.util.concurrent.*; + +@Getter +public class SignalEngine { + + /** + * key 设备号 + * value 设备信号对象 + */ + private final Map deviceSignalMap = new ConcurrentHashMap<>(); + + /** + * 设备对应的触发事件 + */ + private final BlockingQueue queue = new LinkedBlockingQueue<>(1000); + + /** + * 去重, 防止同一设备同一信号值多次投递到队列中 + */ + private final Map eventIndex = new ConcurrentHashMap<>(); + + + + public void enqueue(String deviceCode, String alias, Object value) { + String key = deviceCode + ":" + alias; + SignalEvent newEvent = SignalEvent + .builder() + .deviceCode(deviceCode) + .alias(alias) + .value(value) + .timestamp(System.currentTimeMillis()) + .build(); + SignalEvent oldEvent = eventIndex.put(key, newEvent); + if (oldEvent != null) { + queue.remove(oldEvent); + } + queue.offer(newEvent); + } +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/handler/AbstractDeviceHandler.java b/nl-task/src/main/java/org/nl/task/modular/task/handler/AbstractDeviceHandler.java new file mode 100644 index 0000000..dfc0361 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/handler/AbstractDeviceHandler.java @@ -0,0 +1,112 @@ +package org.nl.task.modular.task.handler; + +import lombok.Getter; +import org.nl.task.modular.task.annotation.EnqueueWhen; +import org.nl.task.modular.task.annotation.SignalAlias; +import org.nl.task.modular.task.engine.SignalEngine; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.BiConsumer; + +@Getter +public abstract class AbstractDeviceHandler { + + protected final SignalEngine engine = new SignalEngine<>(); + + private final Class signalClass; + + private final Map fieldMap = new HashMap<>(); + + private final Map> setterMap = new HashMap<>(); + + private final Map ruleMap = new HashMap<>(); + + protected AbstractDeviceHandler(Class signalClass) { + this.signalClass = signalClass; + initMetadata(); + } + + private void initMetadata() { + for (Field field : signalClass.getDeclaredFields()) { + field.setAccessible(true); + SignalAlias aliasAnn = field.getAnnotation(SignalAlias.class); + String alias; + if (aliasAnn == null || aliasAnn.value().isEmpty()) { + alias = field.getName(); + } else { + alias = aliasAnn.value(); + } + fieldMap.put(alias, field); + setterMap.put(alias, (obj, val) -> { + try { + field.set(obj, val); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + EnqueueWhen rule = field.getAnnotation(EnqueueWhen.class); + if (rule != null) { + ruleMap.put(alias, rule); + } + } + } + + public void syncSignals(String deviceCode, Map signals) { + T plcSignal = engine.getDeviceSignalMap().computeIfAbsent(deviceCode, k -> newInstance()); + signals.forEach((alias, value) -> { + BiConsumer setter = setterMap.get(alias); + if (setter == null) { + return; + } + Object oldValue = getOldValue(plcSignal, alias); + boolean changed = !Objects.equals(oldValue, value); + if (changed) { + setter.accept(plcSignal, value); + } + //TODO 需要判断一下,如果当前变化值,不在EnqueueWhen注解范围内 + // 就需要从队列里将之前的给移出队列 + EnqueueWhen rule = ruleMap.get(alias); + boolean enqueue = false; + if (rule != null) { + if (rule.values().length == 0) { + enqueue = !rule.change() || changed; + } else { + for (String v : rule.values()) { + if (Objects.equals(String.valueOf(value), v)) { + enqueue = !rule.change() || changed; + if (enqueue) { + break; + } + } + } + } + } + if (enqueue) { + engine.enqueue(deviceCode, alias, value); + } + }); + } + + private Object getOldValue(T plcSignal, String alias) { + try { + Field field = fieldMap.get(alias); + if (field == null) { + return null; + } + return field.get(plcSignal); + } catch (Exception e) { + return null; + } + } + + private T newInstance() { + try { + return signalClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/nl-task/src/main/java/org/nl/task/modular/task/handler/ConveyorHandler.java b/nl-task/src/main/java/org/nl/task/modular/task/handler/ConveyorHandler.java new file mode 100644 index 0000000..71f0385 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/handler/ConveyorHandler.java @@ -0,0 +1,34 @@ +package org.nl.task.modular.task.handler; + +import lombok.Data; +import org.nl.task.modular.task.annotation.DeviceType; +import org.nl.task.modular.task.annotation.EnqueueWhen; +import org.springframework.stereotype.Component; + +@DeviceType("CONVEYOR") +@Component +public class ConveyorHandler extends AbstractDeviceHandler { + + public ConveyorHandler() { + super(PlcSignal.class); + } + + @Data + public static class PlcSignal { + + @EnqueueWhen(values = {"5","2","6"}) + private int mode; + + @EnqueueWhen(values = {"0"}) + private int move; + + private int action; + + private int error; + + private int task; + + private String barcode; + } + +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/handler/StackerHandler.java b/nl-task/src/main/java/org/nl/task/modular/task/handler/StackerHandler.java new file mode 100644 index 0000000..b8d17cd --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/handler/StackerHandler.java @@ -0,0 +1,31 @@ +package org.nl.task.modular.task.handler; + +import lombok.Data; +import org.nl.task.modular.task.annotation.DeviceType; +import org.nl.task.modular.task.annotation.EnqueueWhen; +import org.springframework.stereotype.Component; + +@DeviceType("STACKER") +@Component +public class StackerHandler extends AbstractDeviceHandler { + + public StackerHandler() { + super(PlcSignal.class); + } + + @Data + public static class PlcSignal { + + @EnqueueWhen(values = {"5"}) + private int mode; + + private int move; + + private int action; + + private int error; + + private int task; + } + +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/manage/DeviceManager.java b/nl-task/src/main/java/org/nl/task/modular/task/manage/DeviceManager.java new file mode 100644 index 0000000..4de69a2 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/manage/DeviceManager.java @@ -0,0 +1,68 @@ +package org.nl.task.modular.task.manage; + +import lombok.extern.slf4j.Slf4j; +import org.nl.task.modular.task.handler.AbstractDeviceHandler; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Slf4j +public class DeviceManager { + + private final Map> handlerMap = new ConcurrentHashMap<>(); + + private final Map> deviceIndex = new ConcurrentHashMap<>(); + + public void register(String deviceType, AbstractDeviceHandler handler) { + handlerMap.put(deviceType, handler); + } + + public AbstractDeviceHandler getHandler(String deviceType) { + return handlerMap.get(deviceType); + } + + public Map> getHandlers() { + return handlerMap; + } + + public void registerDevice(String deviceType, String deviceCode) { + AbstractDeviceHandler handler = handlerMap.get(deviceType); + if (handler == null) { + return; + } + deviceIndex.put(deviceCode, handler); + } + + public void registerDevice(String deviceType, List deviceCodeList) { + AbstractDeviceHandler handler = handlerMap.get(deviceType); + if (handler == null) { + log.warn("未找到对应设备类型{}的Handler", deviceType); + return; + } + deviceCodeList.stream().forEach(deviceCode -> { + deviceIndex.put(deviceCode, handler); + }); + log.info("注册设备信息:设备类型:{}, 设备号:{}", deviceType, deviceCodeList); + } + + public void setSignal(String deviceCode, Map signals) { + AbstractDeviceHandler handler = deviceIndex.get(deviceCode); + if (handler == null) { + log.warn("未找到对应设备{}的Handler", deviceCode); + return; + } + handler.syncSignals(deviceCode, signals); + } + + public T getSignal(String deviceCode) { + AbstractDeviceHandler handler = (AbstractDeviceHandler) deviceIndex.get(deviceCode); + if (handler == null) { + return null; + } + return handler.getEngine().getDeviceSignalMap().get(deviceCode); + } + +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/model/SignalEvent.java b/nl-task/src/main/java/org/nl/task/modular/task/model/SignalEvent.java new file mode 100644 index 0000000..d5829f2 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/model/SignalEvent.java @@ -0,0 +1,18 @@ +package org.nl.task.modular.task.model; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class SignalEvent { + + String deviceCode; + + String alias; + + Object value; + + long timestamp; + +} \ No newline at end of file diff --git a/nl-task/src/main/java/org/nl/task/modular/task/test/TestController.java b/nl-task/src/main/java/org/nl/task/modular/task/test/TestController.java new file mode 100644 index 0000000..5dfbcd2 --- /dev/null +++ b/nl-task/src/main/java/org/nl/task/modular/task/test/TestController.java @@ -0,0 +1,51 @@ +package org.nl.task.modular.task.test; + +import cn.dev33.satoken.annotation.SaIgnore; +import org.nl.task.modular.task.handler.ConveyorHandler; +import org.nl.task.modular.task.handler.StackerHandler; +import org.nl.task.modular.task.manage.DeviceManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/test") +@Validated +public class TestController { + + @Autowired + private DeviceManager deviceManager; + + @PostMapping("/register") + @SaIgnore + public void register(@RequestBody Map map) { + deviceManager.registerDevice("CONVEYOR", Arrays.asList("CONVEYOR1", "CONVEYOR2")); + deviceManager.registerDevice("STACKER", Arrays.asList("STACKER1")); + Map conveyor1 = new HashMap<>(); + conveyor1.put("mode", 5); + conveyor1.put("move", 0); + conveyor1.put("action", 0); + conveyor1.put("barcode","T000000001"); + deviceManager.setSignal("CONVEYOR1", conveyor1); + Map conveyor2 = new HashMap<>(); + conveyor2.put("mode", 2); + conveyor2.put("move", 1); + conveyor2.put("action", 1); + deviceManager.setSignal("CONVEYOR2", conveyor2); + Map stacker1 = new HashMap<>(); + stacker1.put("mode", 5); + stacker1.put("move", 1); + stacker1.put("action", 1); + deviceManager.setSignal("STACKER1", stacker1); + ConveyorHandler.PlcSignal signal = deviceManager.getSignal("CONVEYOR1"); + System.out.println(signal.toString()); + signal = deviceManager.getSignal("CONVEYOR2"); + System.out.println(signal.toString()); + StackerHandler.PlcSignal signal2 = deviceManager.getSignal("STACKER1"); + System.out.println(signal2); + } +} diff --git a/nl-web-app/pom.xml b/nl-web-app/pom.xml index 9c90f8a..875fa77 100644 --- a/nl-web-app/pom.xml +++ b/nl-web-app/pom.xml @@ -60,6 +60,12 @@ 0.0.1-SNAPSHOT + + org.nl + nl-task + 3.0.0 + + org.nl nl-iot diff --git a/nl-web-app/src/main/java/org/nl/Application.java b/nl-web-app/src/main/java/org/nl/Application.java index 7506117..df5d3f1 100644 --- a/nl-web-app/src/main/java/org/nl/Application.java +++ b/nl-web-app/src/main/java/org/nl/Application.java @@ -19,6 +19,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,6 +32,7 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @SpringBootApplication +@EnableScheduling public class Application { /* 解决druid 日志报错:discard long time none received connection:xxx */ diff --git a/pom.xml b/pom.xml index 1d0b73f..e5fcb3c 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ nl-plugin-tool nl-plugin-tool-api nl-iot + nl-task