diff --git a/src/pom.xml b/src/pom.xml index 9dab566..a54e55a 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -29,6 +29,11 @@ jSerialComm 2.6.2 + + org.java-websocket + Java-WebSocket + 1.5.4 + diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiDevice.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiDevice.java index 8a0baa6..aa0968b 100644 --- a/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiDevice.java +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/DiDevice.java @@ -1,11 +1,11 @@ package com.my.graphiteDigesterBg.diframe; +import com.my.graphiteDigesterBg.diframe.util.DiByteBuffer; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import org.yaml.snakeyaml.Yaml; - import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; @@ -15,7 +15,6 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; import java.util.Map; - @Component public class DiDevice { @Resource @@ -76,7 +75,7 @@ public class DiDevice { // setup connection private void setupConnection() { - String connectionClassName = this.env.getProperty("device.connection.class"); + String connectionClassName = this.getConfig("connection.class"); Class connectionClass = null; try { connectionClass = Class.forName(connectionClassName); @@ -175,6 +174,8 @@ public class DiDevice { request = this.buildCommandRequestForTextMode(cmd, mid, args); } else if ("binary".equals(cmdMode) ) { request = this.buildCommandRequestForBinaryMode(cmd, mid, args); + } else if ("hex".equals(cmdMode)) { + request = this.buildCommandRequestForHexMode(cmd, mid, args); } else { throw new RuntimeException("Unknown command mode: " + cmdMode); } @@ -239,4 +240,13 @@ public class DiDevice { return request; } + + // build command buffer for hex mode + private DiCommandRequest buildCommandRequestForHexMode(DiCommand cmd, Integer mid, Object... args) { + DiCommandRequest request = this.buildCommandRequestForBinaryMode(cmd, mid, args); + String hex = DiByteBuffer.toHex(request.parameter); + hex = hex.replace(" ",""); + request.parameter = ByteBuffer.wrap(hex.getBytes()); + return request; + } } \ No newline at end of file diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/connection/DiConWebsocket.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/connection/DiConWebsocket.java new file mode 100644 index 0000000..039876c --- /dev/null +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/connection/DiConWebsocket.java @@ -0,0 +1,144 @@ +package com.my.graphiteDigesterBg.diframe.connection; +import com.my.graphiteDigesterBg.diframe.DiCommandRequest; +import com.my.graphiteDigesterBg.diframe.DiDevice; +import com.my.graphiteDigesterBg.diframe.DiDeviceConnection; +import com.my.graphiteDigesterBg.diframe.util.DiByteBuffer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +public class DiConWebsocket implements DiDeviceConnection { + // logger + public static final Logger LOG = LoggerFactory.getLogger(DiConWebsocket.class); + // device + private DiDevice device; + // client + private DiConWebsocketClient client; + // requests + private final List requests; + + // constructor + public DiConWebsocket() { + this.requests = new ArrayList<>(); + } + + @Override + public void setDevice(DiDevice device) { + this.device = device; + } + + @Override + public void connect() { + String wsUri = this.device.getConfig("connection.uri"); + if ( null == wsUri ) { + throw new RuntimeException("device option 'device.connection.uri' is required."); + } + URI uri = null; + try { + uri = new URI(wsUri); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + this.client = new DiConWebsocketClient(uri); + this.client.connect(); + this.client.setHolder(this); + } + + @Override + public void call(DiCommandRequest request) { + this.requests.add(request); + String cmd = DiByteBuffer.toHex(request.parameter); + LOG.info("Command => {} : [{}]", request.parameterText, cmd); + cmd = cmd.replace(" ",""); + this.client.send(cmd); + this.setupRequestTimeoutTimer(request); + synchronized ( request ) { + try { + request.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + LOG.info("Command <= {}", DiByteBuffer.toHex(request.response)); + } + + // setup request timeout timer + private void setupRequestTimeoutTimer(DiCommandRequest request) { + TimerTask callTimeoutTimerTask = new TimerTask() { + @Override + public void run() { + DiConWebsocket.this.handleCallTimeout(request); + } + }; + request.timeoutTimer = new Timer(); + Integer callTimeout = this.device.getConfig("connection.callTimeout", Integer.class); + request.timeoutTimer.schedule(callTimeoutTimerTask, callTimeout); + } + + // handle call timeout + private void handleCallTimeout(DiCommandRequest request) { + if ( request.isResponseReceived ) { + return ; + } + + request.timeoutCount ++; + if ( request.timeoutCount > 3 ) { + synchronized ( request ) { + request.timeoutTimer = null; + request.errorCode = 8000; + request.notify(); + } + return ; + } + + String cmd = DiByteBuffer.toHex(request.parameter); + LOG.info("Command (Retry:{}) => {} : [{}]", request.timeoutCount, request.parameterText, cmd); + cmd = cmd.replace(" ",""); + this.client.send(cmd); + this.setupRequestTimeoutTimer(request); + } + + // handle on data + public void handleOnText(String text) { + ByteBuffer message = DiByteBuffer.fromHex(text); + message.order(ByteOrder.LITTLE_ENDIAN); + byte messageType = message.get(5); + if ( 0x01 == messageType ) { // ack message + this.handleOnTextAckMessage(message); + } else if ( 0x02 == messageType ) { // error message + throw new RuntimeException("error message received: " + text); + } else { + throw new RuntimeException("unknown message type : " + messageType + " <= " + text); + } + } + + // handle on data timeout for binary mode ack message + private void handleOnTextAckMessage(ByteBuffer message) { + short messageId = message.getShort(0); + DiCommandRequest request = null; + for ( DiCommandRequest requestItem : this.requests ) { + if ( requestItem.id == messageId ) { + request = requestItem; + break; + } + } + if ( null == request ) { + return ; // 可能是超时了, 已经被处理掉了 ~~~ + } + request.response = message; + request.isResponseReceived = true; + if ( null != request.timeoutTimer ) { + request.timeoutTimer.cancel(); + request.timeoutTimer = null; + } + synchronized ( request ) { + request.notify(); + } + } +} diff --git a/src/src/main/java/com/my/graphiteDigesterBg/diframe/connection/DiConWebsocketClient.java b/src/src/main/java/com/my/graphiteDigesterBg/diframe/connection/DiConWebsocketClient.java new file mode 100644 index 0000000..9718240 --- /dev/null +++ b/src/src/main/java/com/my/graphiteDigesterBg/diframe/connection/DiConWebsocketClient.java @@ -0,0 +1,49 @@ +package com.my.graphiteDigesterBg.diframe.connection; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.net.URI; +public class DiConWebsocketClient extends WebSocketClient { + // logger + public static final Logger LOG = LoggerFactory.getLogger(DiConWebsocketClient.class); + + // holder + private DiConWebsocket holder; + + // constructor + public DiConWebsocketClient(URI uri) { + super(uri); + } + + // set holder + public void setHolder(DiConWebsocket holder) { + this.holder = holder; + } + + /** + * event handler for receiving text message from device + * @param text message from device + */ + @Override + public void onMessage(String text) { + this.holder.handleOnText(text); + } + + @Override + public void onOpen(ServerHandshake serverHandshake) { + LOG.info("device connected : {}", this.uri); + } + + @Override + public void onClose(int i, String s, boolean b) { + LOG.info("close"); + throw new RuntimeException("device connection closed"); + } + + @Override + public void onError(Exception e) { + LOG.error("error", e); + throw new RuntimeException(e); + } +} diff --git a/src/src/main/resources/application.yml b/src/src/main/resources/application.yml index fbc315c..d3184d9 100644 --- a/src/src/main/resources/application.yml +++ b/src/src/main/resources/application.yml @@ -8,11 +8,3 @@ spring: #mybatis: # configuration: # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - -device : - connection : - class : com.my.graphiteDigesterBg.diframe.connection.DiConSerialPort - path : COM1 - baudRate : 921600 - frameTimeout : 1000 - mode : binary # text | hex | binary \ No newline at end of file diff --git a/src/src/main/resources/device.yml b/src/src/main/resources/device.yml index 7eb612e..a700727 100644 --- a/src/src/main/resources/device.yml +++ b/src/src/main/resources/device.yml @@ -1,10 +1,14 @@ connection : - class : com.my.graphiteDigesterBg.diframe.connection.DiConSerialPort - path : COM1 - baudRate : 921600 - frameTimeout : 100 - callTimeout : 5000 - mode : binary # text | hex | binary +# class : com.my.graphiteDigesterBg.diframe.connection.DiConSerialPort +# path : COM1 +# baudRate : 921600 +# frameTimeout : 100 +# callTimeout : 5000 +# mode : binary # text | hex | binary + class : com.my.graphiteDigesterBg.diframe.connection.DiConWebsocket + uri : ws://127.0.0.1:8899/device + mode : hex + callTimeout : 3000 # device registers registers : # 加液电机