diff --git a/app.db b/app.db index 5fed908..6bc7619 100644 Binary files a/app.db and b/app.db differ diff --git a/doc/MQTT.pdf b/doc/MQTT.pdf new file mode 100644 index 0000000..097da21 Binary files /dev/null and b/doc/MQTT.pdf differ diff --git a/emergency-stop.txt b/emergency-stop.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/emergency-stop.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java b/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java index c40697e..0e811df 100644 --- a/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java +++ b/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java @@ -1,11 +1,36 @@ package com.iflytop.digester.underframework; import com.iflytop.digester.underframework.dao.model.UfMdbActuator; import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; +import com.iflytop.digester.underframework.dao.model.UfMdbNotification; import com.iflytop.digester.underframework.dao.record.UfActiveRecord; +import com.iflytop.digester.underframework.util.UfCommon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; +import java.util.TimerTask; + public class UfActuatorCmdExecutor { + // logger + public static final Logger LOG = LoggerFactory.getLogger(UfActuatorCmdExecutor.class); + + // emergency stop restore lock + private static final Object emergencyStopRestoreLock = new Object(); + + // execute command + public static String emergencyExecute(UfMdbActuatorCmd cmd) { + var con = UfApplication.getApp().connections.get(cmd.connectionKey); + return con.execute(cmd); + } + // execute cmd public static String execute(UfMdbActuatorCmd cmd) { + if ( UfActuatorCmdExecutor.hasEmergencyStopTriggered() ) { + throw new RuntimeException("触发紧急停止"); + } var con = UfApplication.getApp().connections.get(cmd.connectionKey); return con.execute(cmd); } @@ -30,4 +55,59 @@ public class UfActuatorCmdExecutor { public static String execute(String actuatorKey, String cmdKey) { return execute(actuatorKey, cmdKey, null); } + + // check if emergency stop triggered + private static Boolean hasEmergencyStopTriggered() { + String filePath = UfApplication.getApp().getEnv().getProperty("app.emergency-stop-file-path"); + if ( null == filePath ) { + return false; + } + + // read all content from file as string + try { + String state = Files.readString(Path.of(filePath)); + if ( "1".equals(state) ) { + return false; // not triggered + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + // triggered + LOG.warn("急停触发"); + UfMdbNotification.action("EmergencyStop", new HashMap<>()); + UfCmdSnippetExecutor.execute("EmergencyStop"); + var restoreCheckThread = new Thread(UfActuatorCmdExecutor::emergencyStopRestoreCheck); + restoreCheckThread.start(); + synchronized ( UfActuatorCmdExecutor.emergencyStopRestoreLock ) { + try { + UfActuatorCmdExecutor.emergencyStopRestoreLock.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + UfCmdSnippetExecutor.execute("EmergencyStopRestore"); + return true; + } + + // 急停恢复 + public static void emergencyStopRestoreCheck() { + do { + UfCommon.delay(1000); + String filePath = UfApplication.getApp().getEnv().getProperty("app.emergency-stop-file-path"); + assert filePath != null; + try { + String state = Files.readString(Path.of(filePath)); + if ( "1".equals(state) ) { + break; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } while ( true ); + LOG.warn("急停恢复"); + synchronized ( UfActuatorCmdExecutor.emergencyStopRestoreLock ) { + UfActuatorCmdExecutor.emergencyStopRestoreLock.notify(); + } + } } diff --git a/src/main/java/com/iflytop/digester/underframework/UfCmdSnippetExecutor.java b/src/main/java/com/iflytop/digester/underframework/UfCmdSnippetExecutor.java index 92edf69..666ef8c 100644 --- a/src/main/java/com/iflytop/digester/underframework/UfCmdSnippetExecutor.java +++ b/src/main/java/com/iflytop/digester/underframework/UfCmdSnippetExecutor.java @@ -26,6 +26,25 @@ public class UfCmdSnippetExecutor { private final UfMdbSnippet snippet; // params private final Map params; + // emergency mode state + private Boolean emergencyModeEnable = false; + + // 紧急模式运行 + static public void emergencyExecute( String snippetKey ) { + Boolean logEnabled = UfMdbOption.getBoolean("DeviceCommandSnippetLogEnable", true); + if ( Boolean.TRUE.equals(logEnabled) ) { + LOG.info("*EMERGENCY* [Snippet Execute] : {}", snippetKey); + } + + var snippet = UfActiveRecord.findOne(UfMdbSnippet.class, Map.of("key", snippetKey)); + if (null == snippet) { + throw new RuntimeException("无效的片段KEY : " + snippetKey); + } + + var executor = new UfCmdSnippetExecutor(snippet, new HashMap<>()); + executor.setEmergencyModeEnable(true); + executor.run(); + } /** * execute snippet @@ -61,6 +80,11 @@ public class UfCmdSnippetExecutor { this.params = params; } + // set emergency mode + public void setEmergencyModeEnable( Boolean enable ) { + this.emergencyModeEnable = enable; + } + // execute snippet public void run() { ObjectMapper jsonMapper = new ObjectMapper(); diff --git a/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java b/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java index 684de69..f491e06 100644 --- a/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java @@ -119,15 +119,15 @@ public class UfZcancmderWebsocket extends UfConnectionBase { } } + if ( null != this.responseError ) { + throw new RuntimeException(this.responseError); + } + if ( null == this.response ) { var actuator = UfActiveRecord.findOne(UfMdbActuator.class, actuatorCmd.actuatorId); throw new RuntimeException(String.format("设备 [%s] 响应超时: %s", actuator.name, actuatorCmd.cmdKey)); } - if ( null != this.responseError ) { - throw new RuntimeException(this.responseError); - } - var responseLength = this.response.capacity(); if ( 8 == responseLength ) { return ""; @@ -284,8 +284,9 @@ public class UfZcancmderWebsocket extends UfConnectionBase { private void handleOnTextErrorMessage(ByteBuffer message) { Integer errorCode = message.getInt(8); String errorMessage = switch (errorCode) { - case 3 -> "操作不支持"; - case 14 -> "参数数量不匹配"; + case 3 -> "操作不支持(koperation_not_support)"; + case 11 -> "子设备超时(ksubdevice_overtime)"; + case 14 -> "参数数量不匹配(kcmd_param_num_error)"; default -> "未知"; }; diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0874f30..ce538da 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -14,11 +14,11 @@ device: key : zcancmder enable : true type : ZcancmderWebsocket -# uri: ws://192.168.8.10:19005 - uri : ws://127.0.0.1:19005 + uri: ws://192.168.8.10:19005 +# uri : ws://127.0.0.1:19005 - name : modbus key : modbus - enable : false + enable : true type : ModbusRTUOverTCP host: 192.168.8.10 port: 20000 @@ -30,3 +30,4 @@ app : opencv-library-path: D:/ProgramFiles/OpenCV/opencv/build/java/x64/opencv_java490.dll pylon-library-path: D:/ProgramFiles/Pylon5/Runtime/x64/PylonC_v5_2.dll pylon-wrapper-path: D:/Sige5193/digester/src/main/java/com/iflytop/digester/camera/x64/Debug/baslerCamera.dll + emergency-stop-file-path : D:/Sige5193/digester/emergency-stop.txt diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 3bb7c52..6421297 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -28,4 +28,5 @@ app : bucketVolumeCheckEnable: false opencv-library-path: /userdata/rootfs_overlay/usr/lib/jni/libopencv_java420.so pylon-library-path: /opt/pylon/lib/libpylonc.so.7.2 - pylon-wrapper-path: /app/basler-camera-java-wrapper/libpylonc-wrapper.so \ No newline at end of file + pylon-wrapper-path: /app/basler-camera-java-wrapper/libpylonc-wrapper.so + emergency-stop-file-path: /sys/class/gpio/gpio102/value \ No newline at end of file