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