From b7bd4dd841c16582431ab13a8cdba2ad671a3041 Mon Sep 17 00:00:00 2001 From: sige Date: Tue, 9 Apr 2024 15:59:42 +0800 Subject: [PATCH] 1 --- app.db | Bin 176128 -> 278528 bytes .../underframework/UfActuatorCmdExecutor.java | 6 +- .../digester/underframework/UfApplication.java | 2 +- .../underframework/connection/UfConnection.java | 6 +- .../connection/UfConnectionBase.java | 2 + .../connection/UfConnectionManager.java | 27 ++-- .../connection/UfModbusRTUOverTCP.java | 172 +++++++++++++++++++++ .../connection/UfZcancmderWebsocket.java | 92 ++++++----- .../underframework/dao/model/UfMdbActuatorCmd.java | 6 + .../underframework/util/UfClassHelper.java | 11 ++ .../web/api/TsApiActuatorCommand.java | 11 +- src/main/resources/application.yml | 6 + web | 2 +- 13 files changed, 289 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/iflytop/digester/underframework/connection/UfModbusRTUOverTCP.java diff --git a/app.db b/app.db index 2929be2d3206045fd5e3e1bb608a3df53ec368a2..14d9b3db7e7963118e5af8fdc29ba790897f14c6 100644 GIT binary patch delta 7070 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 diff --git a/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java b/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java index 0a283c8..cd39c85 100644 --- a/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java +++ b/src/main/java/com/iflytop/digester/underframework/UfActuatorCmdExecutor.java @@ -1,7 +1,9 @@ package com.iflytop.digester.underframework; import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; public class UfActuatorCmdExecutor { - public static void execute(UfMdbActuatorCmd cmd) { - // @TODO : 这里要判断使用那种通讯方式进行执行, 所以待定一下 + // execute cmd + public static String execute(UfMdbActuatorCmd cmd) { + var con = UfApplication.getApp().connections.get(cmd.connectionKey); + return con.execute(cmd); } } diff --git a/src/main/java/com/iflytop/digester/underframework/UfApplication.java b/src/main/java/com/iflytop/digester/underframework/UfApplication.java index 2396038..cb00a8c 100644 --- a/src/main/java/com/iflytop/digester/underframework/UfApplication.java +++ b/src/main/java/com/iflytop/digester/underframework/UfApplication.java @@ -22,7 +22,7 @@ implements ApplicationContextAware, WebMvcConfigurer { private static UfApplication app = null; @Resource - private UfConnectionManager connections; + public UfConnectionManager connections; @Resource private Environment env; diff --git a/src/main/java/com/iflytop/digester/underframework/connection/UfConnection.java b/src/main/java/com/iflytop/digester/underframework/connection/UfConnection.java index 44ea8cc..4042600 100644 --- a/src/main/java/com/iflytop/digester/underframework/connection/UfConnection.java +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfConnection.java @@ -1,12 +1,14 @@ package com.iflytop.digester.underframework.connection; +import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; + import java.util.Map; public interface UfConnection { /** * execute command and return result - * @param command - command text, like "ls -l" + * @param command - command mdele * @return result text */ - String execute( String command ); + String execute( UfMdbActuatorCmd command ); /** * connect to target diff --git a/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionBase.java b/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionBase.java index dcb949d..ba9b606 100644 --- a/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionBase.java +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionBase.java @@ -8,6 +8,8 @@ abstract public class UfConnectionBase implements UfConnection { public static final Logger LOG = LoggerFactory.getLogger("UfConnection"); // name public String name; + // key + public String key; @Override public void setProperties(Map properties) { diff --git a/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionManager.java b/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionManager.java index db9af70..3361dc0 100644 --- a/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionManager.java +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfConnectionManager.java @@ -16,19 +16,24 @@ public class UfConnectionManager { @PostConstruct public void init() { this.connections = new HashMap<>(); -// for ( var option : this.connectionOptionList.connections ) { -// String name = (String)option.get("name"); -// String type = (String)option.get("type"); -// String className = this.getClass().getPackageName() + ".Uf" + type; -// UfConnection con = (UfConnection)UfClassHelper.newInstance(className); -// con.setProperties(option); -// con.connect(); -// this.connections.put(name, con); -// } + for ( var option : this.connectionOptionList.connections ) { + String key = (String)option.get("key"); + String type = (String)option.get("type"); + String className = this.getClass().getPackageName() + ".Uf" + type; + UfConnection con = (UfConnection)UfClassHelper.newInstance(className); + con.setProperties(option); + con.connect(); + this.connections.put(key, con); + } } // get connection by name - public UfConnection get(String name) { - return this.connections.get(name); + public UfConnection get(String key) { + return this.connections.get(key); + } + + // get all connections + public Map getConnections() { + return this.connections; } } diff --git a/src/main/java/com/iflytop/digester/underframework/connection/UfModbusRTUOverTCP.java b/src/main/java/com/iflytop/digester/underframework/connection/UfModbusRTUOverTCP.java new file mode 100644 index 0000000..5562337 --- /dev/null +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfModbusRTUOverTCP.java @@ -0,0 +1,172 @@ +package com.iflytop.digester.underframework.connection; +import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +public class UfModbusRTUOverTCP extends UfConnectionBase { + // connection + private Socket socket; + // writer + private DataOutputStream writer; + // reader + private InputStream reader; + // busy + private Boolean isBusy = false; + + // connections + private static final Map connections = new HashMap<>(); + + // get connection + public static UfModbusRTUOverTCP getConnection(String host, Integer port) { + String key = host + ":" + port; + if ( connections.containsKey(key) ) { + return connections.get(key); + } + + UfModbusRTUOverTCP con = new UfModbusRTUOverTCP(); + con.connect(host, port); + connections.put(key, con); + return con; + } + + @Override + public String execute(UfMdbActuatorCmd command) { + return ""; + } + + @Override + public void connect() { + + } + + // constructor + public UfModbusRTUOverTCP() { + this.isBusy = false; + } + + // connect + public void connect( String host, Integer port ) { + try { + this.socket = new Socket(host, port); + this.writer = new DataOutputStream(this.socket.getOutputStream()); + this.reader = this.socket.getInputStream(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // write holding register + public void writeHoldingRegister( Integer slaveId, Integer address, Integer value ) { + byte[] cmd = new byte[8]; + cmd[0] = (byte)(slaveId & 0xFF); // slave id + cmd[1] = 0x06; // function code + cmd[2] = (byte)(address >> 8); // address high + cmd[3] = (byte)(address & 0xFF); // address low + cmd[4] = (byte)(value >> 8); // value high + cmd[5] = (byte)(value & 0xFF); // value low + this.call(cmd); + } + + // read holding register + public Integer readHoldingRegister( Integer slaveId, Integer address ) { + byte[] cmd = new byte[8]; + cmd[0] = (byte)(slaveId & 0xFF); // slave id + cmd[1] = 0x03; // function code + cmd[2] = (byte)(address >> 8); // address high + cmd[3] = (byte)(address & 0xFF); // address low + cmd[4] = 0x00; // value high + cmd[5] = 0x01; // value low + var response = this.call(cmd); + if (response.isEmpty()) { + throw new RuntimeException("Modbus RTU over TCP: no response"); + } + return response.get(3) << 8 | response.get(4); + } + + // call + private List call(byte[] cmd) { + if ( this.isBusy ) { + synchronized ( this ) { + try { + this.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + this.isBusy = true; + this.writeCmd(cmd); + List response = this.readCmdResponse(); + this.isBusy = false; + synchronized ( this ) { + this.notify(); + } + return response; + } + + // write command + private void writeCmd(byte[] cmd) { + var size = cmd.length; + var crc = this.calculateCRC(cmd, size - 2); + cmd[size-2] = (byte)(crc & 0xFF); + cmd[size-1] = (byte)(crc >> 8); + + try { + this.writer.write(cmd); + this.writer.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + Thread.sleep(300); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + // read command response + private List readCmdResponse ( ) { + try { + List result = new ArrayList<>(); + while ( 0 < this.reader.available() ) { + result.add(this.reader.read()); + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 计算输入数据的CRC16 (Modbus) + * @param data 要计算CRC的字节数组 + * @param length 数组中需要计算的长度 + * @return 计算出的CRC16值 + */ + public int calculateCRC(byte[] data, int length) { + int crc = 0xFFFF; // CRC的初始值 + + for (int pos = 0; pos < length; pos++) { + crc ^= data[pos] & 0xFF; // 将数据与CRC寄存器异或 + for (int i = 8; i != 0; i--) { // 循环处理每个位 + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; // 0xA001是预设的多项式 + } else { + crc >>= 1; + } + } + } + + // 高低位不交换,直接返回 + return crc & 0xFFFF; + } +} 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 ee8c37e..914fe31 100644 --- a/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java +++ b/src/main/java/com/iflytop/digester/underframework/connection/UfZcancmderWebsocket.java @@ -2,6 +2,7 @@ package com.iflytop.digester.underframework.connection; import com.iflytop.digester.underframework.dao.model.TsMdbActuator; import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; import com.iflytop.digester.underframework.util.TsByteBuffer; +import com.iflytop.digester.underframework.util.UfClassHelper; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; @@ -57,42 +58,28 @@ public class UfZcancmderWebsocket extends UfConnectionBase { } @Override - public String execute(String command) { - return null; - } - - // execute command - synchronized public void execute( UfMdbActuatorCmd actuatorCmd ) { - this.executeDeviceCommand(actuatorCmd); - if ( 1 == actuatorCmd.waitForFinish ) { - this.waitForActuatorFinish(actuatorCmd); + synchronized public String execute(UfMdbActuatorCmd command) { + var parts = command.cmdKey.split("_"); + for (int i = 0; i < parts.length; i++) { + parts[i] = parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1); } - } + String methodName = "cmd" + String.join("", parts); + String returnValue = ""; - // wait for actuator finish - private void waitForActuatorFinish(UfMdbActuatorCmd actuatorCmd) { - do { - var waitCmd = new UfMdbActuatorCmd(); - waitCmd.actuatorId = actuatorCmd.actuatorId; - waitCmd.cmdId = "0104"; - waitCmd.cmdKey = "module_get_status"; - waitCmd.parameters = ""; - this.executeDeviceCommand(waitCmd); - int status = this.response.getInt(8); - if ( 0 == status ) { - break; - } + try { + returnValue = (String) UfClassHelper.invokeMethod(this, methodName, List.of(command)); + } catch (NoSuchMethodException e) { + returnValue = this.executeDeviceCommand(command); + } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } while ( true ); + if ( 1 == command.waitForFinish ) { + this.waitForActuatorFinish(command); + } + return returnValue; } // execute device command - private void executeDeviceCommand( UfMdbActuatorCmd actuatorCmd ) { + private String executeDeviceCommand( UfMdbActuatorCmd actuatorCmd ) { this.response = null; this.responseError = null; this.sendCommandRequest(actuatorCmd); @@ -108,6 +95,21 @@ public class UfZcancmderWebsocket extends UfConnectionBase { if ( null != this.responseError ) { throw new RuntimeException(this.responseError); } + + var responseLength = this.response.capacity(); + if ( 8 == responseLength ) { + return ""; + } else if ( 12 == responseLength ) { + int value = this.response.getInt(8); + return String.valueOf(value); + } else if ( 12 < responseLength ) { + byte[] bytes = new byte[this.response.limit() - 8]; + this.response.position(8); + this.response.get(bytes); + return java.util.Base64.getEncoder().encodeToString(bytes); + } else { + throw new RuntimeException("unknown response length: " + this.response.limit()); + } } // send command request @@ -139,7 +141,7 @@ public class UfZcancmderWebsocket extends UfConnectionBase { } TsMdbActuator actuator = TsMdbActuator.findOne(TsMdbActuator.class, actuatorCmd.actuatorId); - int moduleId = 0; //actuator.aid; + int moduleId = Integer.parseInt(actuatorCmd.cmdFlags); int cmdId = Integer.parseInt(actuatorCmd.cmdId, 16); int subCmdId = cmdId & 0xFF; @@ -199,14 +201,32 @@ public class UfZcancmderWebsocket extends UfConnectionBase { // handle on data timeout for binary mode ack message private void handleOnTextAckMessage(ByteBuffer message) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } this.response = message; synchronized (this.callLock) { this.callLock.notifyAll(); } } + + // wait for actuator finish + private void waitForActuatorFinish(UfMdbActuatorCmd actuatorCmd) { + do { + var waitCmd = new UfMdbActuatorCmd(); + waitCmd.actuatorId = actuatorCmd.actuatorId; + waitCmd.cmdId = "0104"; + waitCmd.cmdKey = "module_get_status"; + waitCmd.cmdFlags = actuatorCmd.cmdFlags; + waitCmd.parameters = ""; + String statusText = this.executeDeviceCommand(waitCmd); + int status = Integer.parseInt(statusText); + if ( 0 == status ) { + break; + } + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } while ( true ); + } } diff --git a/src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuatorCmd.java b/src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuatorCmd.java index 9ab1be3..f4a657e 100644 --- a/src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuatorCmd.java +++ b/src/main/java/com/iflytop/digester/underframework/dao/model/UfMdbActuatorCmd.java @@ -6,6 +6,9 @@ public class UfMdbActuatorCmd extends UfActiveRecord { public String actuatorId; @UfActiveRecordField + public String connectionKey; + + @UfActiveRecordField public String name; @UfActiveRecordField @@ -15,6 +18,9 @@ public class UfMdbActuatorCmd extends UfActiveRecord { public String cmdKey; @UfActiveRecordField + public String cmdFlags; + + @UfActiveRecordField public String fixedParameters; @UfActiveRecordField diff --git a/src/main/java/com/iflytop/digester/underframework/util/UfClassHelper.java b/src/main/java/com/iflytop/digester/underframework/util/UfClassHelper.java index f1646f5..565803b 100644 --- a/src/main/java/com/iflytop/digester/underframework/util/UfClassHelper.java +++ b/src/main/java/com/iflytop/digester/underframework/util/UfClassHelper.java @@ -20,6 +20,7 @@ public class UfClassHelper { } } + // new instance public static Object newInstance(String className) { try { return Class.forName(className).getDeclaredConstructor().newInstance(); @@ -28,6 +29,16 @@ public class UfClassHelper { } } + // check method exist + public static Boolean isMethodExist(Object obj, String methodName) { + try { + obj.getClass().getMethod(methodName); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + // invoke method public static Object invokeMethod(Object obj, String methodName, List args) throws NoSuchMethodException { try { diff --git a/src/main/java/com/iflytop/digester/underframework/web/api/TsApiActuatorCommand.java b/src/main/java/com/iflytop/digester/underframework/web/api/TsApiActuatorCommand.java index 7d29a77..8fff3fd 100644 --- a/src/main/java/com/iflytop/digester/underframework/web/api/TsApiActuatorCommand.java +++ b/src/main/java/com/iflytop/digester/underframework/web/api/TsApiActuatorCommand.java @@ -1,4 +1,6 @@ package com.iflytop.digester.underframework.web.api; +import com.iflytop.digester.underframework.UfActuatorCmdExecutor; +import com.iflytop.digester.underframework.UfApplication; import com.iflytop.digester.underframework.dao.record.UfActiveRecord; import com.iflytop.digester.underframework.dao.record.UfActiveRecordCriteria; import com.iflytop.digester.underframework.dao.model.UfMdbActuatorCmd; @@ -40,10 +42,17 @@ public class TsApiActuatorCommand extends UfApiControllerBase { @ResponseBody public UfApiResponse execute(@RequestBody UfMdbActuatorCmd actuatorCmd ) { try { -// UfApplication.getApp().commandExecutor.execute(actuatorCmd); + UfActuatorCmdExecutor.execute(actuatorCmd); } catch ( Exception e ) { return error(e.getMessage()); } return success(); } + + @PostMapping("/api/actuator-cmd/connection-list") + @ResponseBody + public UfApiResponse connectionList() { + var connections = UfApplication.getApp().connections; + return success(connections.getConnections()); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5205a12..37d6fa1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,8 +12,14 @@ device: snippet-execute-log-enable : false connections: - name : zcancmder + key : zcancmder type : ZcancmderWebsocket uri: ws://192.168.8.10:19005 + - name : modbus + key : modbus + type : ModbusRTUOverTCP + host: 182.168.8.10 + port: 502 mqtt-broker: uri: tcp://broker.emqx.io:1883 diff --git a/web b/web index 6bacffe..54eb432 160000 --- a/web +++ b/web @@ -1 +1 @@ -Subproject commit 6bacffe10495394dfa5cf3a793d2aa7a60ef164c +Subproject commit 54eb4327c3a74e6aae926d4752b18439f62091f0