diff --git a/app.db b/app.db index 2929be2..14d9b3d 100644 Binary files a/app.db and b/app.db differ 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