14 changed files with 604 additions and 21 deletions
-
32src/main/java/com/qyft/ms/app/common/command/CommandFuture.java
-
39src/main/java/com/qyft/ms/app/common/command/CommandWaitControl.java
-
32src/main/java/com/qyft/ms/app/common/command/CurrentSendCmdMapInstance.java
-
39src/main/java/com/qyft/ms/app/common/command/CyclicNumberGenerator.java
-
25src/main/java/com/qyft/ms/app/common/command/FrontCommandAck.java
-
109src/main/java/com/qyft/ms/app/common/command/FrontCommandGenerator.java
-
12src/main/java/com/qyft/ms/app/common/constant/CommandStatus.java
-
190src/main/java/com/qyft/ms/app/controller/FrontCmdController.java
-
30src/main/java/com/qyft/ms/app/model/bo/CMDToDevice.java
-
24src/main/java/com/qyft/ms/app/model/form/CMDFormV2.java
-
60src/main/java/com/qyft/ms/device/handler/DeviceMessageHandler.java
-
25src/main/java/com/qyft/ms/device/service/DeviceTcpCMDServiceV2.java
-
2src/main/java/com/qyft/ms/system/config/WebConfig.java
-
6src/main/resources/application.yml
@ -0,0 +1,32 @@ |
|||
package com.qyft.ms.app.common.command; |
|||
|
|||
import cn.hutool.json.JSONObject; |
|||
import com.qyft.ms.app.model.bo.CMDToDevice; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class CommandFuture { |
|||
|
|||
private CommandWaitControl commandWaitController = new CommandWaitControl(); |
|||
|
|||
/** |
|||
* 发送给设备的指令 |
|||
*/ |
|||
private CMDToDevice cmdToDevice; |
|||
/** |
|||
* 设备返回的结果 |
|||
*/ |
|||
private JSONObject callbackResult; |
|||
/** |
|||
* 已收到 |
|||
*/ |
|||
private boolean isReceived = false; |
|||
|
|||
public void waitForContinue() { |
|||
commandWaitController.commandWait(); |
|||
} |
|||
|
|||
public void commandContinue() { |
|||
commandWaitController.commandContinue(); |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
package com.qyft.ms.app.common.command; |
|||
|
|||
public class CommandWaitControl { |
|||
|
|||
public String commandCallbackJsonStringHolder = ""; |
|||
|
|||
private boolean shouldWait = true; |
|||
public synchronized void commandWait() { |
|||
while (shouldWait) { |
|||
try { |
|||
wait(); |
|||
} catch (InterruptedException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
// 重置标志,以便下次可以再次等待 |
|||
shouldWait = true; |
|||
} |
|||
public synchronized void commandContinue() { |
|||
shouldWait = false; |
|||
notify(); |
|||
} |
|||
|
|||
/** |
|||
* 获取命令回调值 |
|||
* @return |
|||
*/ |
|||
public String getCommandCallbackValue() { |
|||
return commandCallbackJsonStringHolder; |
|||
} |
|||
/** |
|||
* 设置命令回调值 |
|||
* @param commandCallbackJsonString |
|||
*/ |
|||
public void setCommandCallbackValue(String commandCallbackJsonString) { |
|||
this.commandCallbackJsonStringHolder = commandCallbackJsonString; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,32 @@ |
|||
package com.qyft.ms.app.common.command; |
|||
|
|||
import io.swagger.v3.oas.models.security.SecurityScheme; |
|||
|
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
|
|||
public class CurrentSendCmdMapInstance { |
|||
private static CurrentSendCmdMapInstance instance; |
|||
|
|||
|
|||
ConcurrentHashMap<Integer, CommandFuture> commandMap = new ConcurrentHashMap<>(); |
|||
|
|||
|
|||
public static synchronized CurrentSendCmdMapInstance getInstance() { |
|||
if (instance == null) { |
|||
instance = new CurrentSendCmdMapInstance(); |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
public void putCommand(Integer key, CommandFuture commandFuture) { |
|||
commandMap.put(key, commandFuture); |
|||
} |
|||
|
|||
public CommandFuture removeCommand(Integer key) { |
|||
return commandMap.remove(key); |
|||
} |
|||
|
|||
public CommandFuture getCommand(Integer key) { |
|||
return commandMap.get(key); |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
package com.qyft.ms.app.common.command; |
|||
|
|||
public class CyclicNumberGenerator { |
|||
// 饿汉式单例,在类加载时就创建实例 |
|||
private static final CyclicNumberGenerator INSTANCE = new CyclicNumberGenerator(); |
|||
// 当前生成的数字,初始值为 1 |
|||
private int currentNumber = 1; |
|||
|
|||
// 私有构造函数,防止外部实例化 |
|||
private CyclicNumberGenerator() {} |
|||
|
|||
// 提供全局访问点获取单例实例 |
|||
public static CyclicNumberGenerator getInstance() { |
|||
return INSTANCE; |
|||
} |
|||
|
|||
/** |
|||
* 生成 1 到 255 之间的循环整数 |
|||
* @return 生成的整数 |
|||
*/ |
|||
public synchronized int generateNumber() { |
|||
int result = currentNumber; |
|||
// 每次生成后将当前数字加 1 |
|||
currentNumber++; |
|||
// 如果当前数字超过 255,将其重置为 1 |
|||
if (currentNumber > 255) { |
|||
currentNumber = 1; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
public static void main(String[] args) { |
|||
CyclicNumberGenerator generator = CyclicNumberGenerator.getInstance(); |
|||
// 测试生成 260 个数字 |
|||
for (int i = 0; i < 260; i++) { |
|||
System.out.println(generator.generateNumber()); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
package com.qyft.ms.app.common.command; |
|||
|
|||
import cn.hutool.json.JSONObject; |
|||
|
|||
public class FrontCommandAck { |
|||
|
|||
public static String backstageAck(String cmdId, String cmdName,String status,String info) { |
|||
JSONObject jsonObject = new JSONObject(); |
|||
jsonObject.set("cmdId", cmdId); |
|||
jsonObject.set("cmdName", cmdName); |
|||
jsonObject.set("status", status); |
|||
jsonObject.set("info", info); |
|||
return jsonObject.toString(); |
|||
} |
|||
|
|||
public static String deviceAck(String cmdId, String cmdName) { |
|||
JSONObject jsonObject = new JSONObject(); |
|||
jsonObject.set("cmdId", cmdId); |
|||
jsonObject.set("cmdName", cmdName); |
|||
jsonObject.set("status", "receive"); |
|||
return jsonObject.toString(); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,109 @@ |
|||
package com.qyft.ms.app.common.command; |
|||
|
|||
|
|||
import com.qyft.ms.app.model.bo.CMDToDevice; |
|||
|
|||
/** |
|||
* 生成给设备发送的指令 |
|||
*/ |
|||
public class FrontCommandGenerator { |
|||
|
|||
/** |
|||
* { |
|||
* cmdName:'getInfoCmd' |
|||
* cmdId:'', |
|||
* device:'device', |
|||
* action:'get' |
|||
* } |
|||
*/ |
|||
public static CMDToDevice device_status_get() { |
|||
int cmdId = CyclicNumberGenerator.getInstance().generateNumber(); |
|||
CMDToDevice cmdToDevice = new CMDToDevice(); |
|||
cmdToDevice.setCmdId(cmdId); |
|||
cmdToDevice.setCmdName("device_status_get"); |
|||
cmdToDevice.setDevice("device"); |
|||
cmdToDevice.setAction("get"); |
|||
return cmdToDevice; |
|||
} |
|||
|
|||
/** |
|||
* 获取设备当前湿度 |
|||
* { |
|||
* cmdName:'getInfoCmd' |
|||
* cmdId:'', |
|||
* device:'temperature', |
|||
* action:'get' |
|||
* } |
|||
*/ |
|||
public static CMDToDevice temperature_get() { |
|||
int cmdId = CyclicNumberGenerator.getInstance().generateNumber(); |
|||
CMDToDevice cmdToDevice = new CMDToDevice(); |
|||
cmdToDevice.setCmdId(cmdId); |
|||
cmdToDevice.setCmdName("getInfoCmd"); |
|||
cmdToDevice.setDevice("temperature"); |
|||
cmdToDevice.setAction("get"); |
|||
return cmdToDevice; |
|||
} |
|||
|
|||
/** |
|||
* 开启除湿阀 |
|||
* { |
|||
* cmdName:'controlCmd' |
|||
* cmdId:'', |
|||
* device:'dehumidifier_valve', |
|||
* action:'open|close' |
|||
* } |
|||
*/ |
|||
public static CMDToDevice dehumidifier_start() { |
|||
int cmdId = CyclicNumberGenerator.getInstance().generateNumber(); |
|||
CMDToDevice cmdToDevice = new CMDToDevice(); |
|||
cmdToDevice.setCmdId(cmdId); |
|||
cmdToDevice.setCmdName("controlCmd"); |
|||
cmdToDevice.setDevice("dehumidifier_valve"); |
|||
cmdToDevice.setAction("open"); |
|||
return cmdToDevice; |
|||
} |
|||
|
|||
|
|||
public static String matrix_prefill() { |
|||
return "matrix_prefill"; |
|||
} |
|||
|
|||
public static String spray_pipeline_wash() { |
|||
return "spray_pipeline_wash"; |
|||
} |
|||
|
|||
public static String syringe_pipeline_wash() { |
|||
return "syringe_pipeline_wash"; |
|||
} |
|||
|
|||
public static String matrix_spray_start() { |
|||
return "matrix_spray_start"; |
|||
} |
|||
|
|||
public static String matrix_spray_change_param() { |
|||
return "matrix_spray_change_param"; |
|||
} |
|||
|
|||
public static String matrix_spray_stop() { |
|||
return "matrix_spray_stop"; |
|||
} |
|||
|
|||
public static String matrix_spray_pause() { |
|||
return "matrix_spray_pause"; |
|||
} |
|||
|
|||
public static String matrix_spray_continue() { |
|||
return "matrix_spray_continue"; |
|||
} |
|||
|
|||
public static String slide_tray_in() { |
|||
return "slide_tray_in"; |
|||
} |
|||
|
|||
public static String slide_tray_out() { |
|||
return "slide_tray_out"; |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,12 @@ |
|||
package com.qyft.ms.app.common.constant; |
|||
|
|||
public class CommandStatus { |
|||
/** |
|||
* 已收到前端指令 |
|||
*/ |
|||
public static final String RECEIVE = "receive"; |
|||
/** |
|||
* 指令执行发生错误 |
|||
*/ |
|||
public static final String ERROR = "error"; |
|||
} |
@ -0,0 +1,190 @@ |
|||
package com.qyft.ms.app.controller; |
|||
|
|||
import cn.hutool.json.JSONObject; |
|||
import cn.hutool.json.JSONUtil; |
|||
import com.qyft.ms.app.common.command.CommandFuture; |
|||
import com.qyft.ms.app.common.command.CurrentSendCmdMapInstance; |
|||
import com.qyft.ms.app.common.command.FrontCommandAck; |
|||
import com.qyft.ms.app.common.command.FrontCommandGenerator; |
|||
import com.qyft.ms.app.common.constant.CommandStatus; |
|||
import com.qyft.ms.app.model.bo.CMDToDevice; |
|||
import com.qyft.ms.app.model.form.CMDFormV2; |
|||
import com.qyft.ms.device.service.DeviceTcpCMDServiceV2; |
|||
import io.swagger.v3.oas.annotations.Operation; |
|||
import io.swagger.v3.oas.annotations.tags.Tag; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Map; |
|||
|
|||
@Tag(name = "前端调用指令") |
|||
@RestController |
|||
@RequestMapping("/api/device/front") |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class FrontCmdController { |
|||
|
|||
private final DeviceTcpCMDServiceV2 deviceTcpCMDServiceV2; |
|||
|
|||
@Operation(summary = "前端统一调用一个接口") |
|||
@PostMapping("/control") |
|||
public ResponseBodyEmitter controlMethod(@RequestBody CMDFormV2 cmdForm) { |
|||
ResponseBodyEmitter emitter = new ResponseBodyEmitter(); |
|||
String frontCmdId = cmdForm.getCmdId(); |
|||
String frontCmdName = cmdForm.getCmdName(); |
|||
Map<String, Object> param = cmdForm.getParam(); |
|||
|
|||
if ("device_status_get".equals(frontCmdName)) { |
|||
|
|||
new Thread(() -> { |
|||
|
|||
try { |
|||
//向前端发送接收到指令 |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.RECEIVE, "后台已收到指令"), MediaType.TEXT_PLAIN); |
|||
} catch (IOException e) { |
|||
emitter.completeWithError(e); |
|||
} |
|||
|
|||
//生成发给设备的指令 |
|||
CMDToDevice cmdToDevice = FrontCommandGenerator.device_status_get(); |
|||
CommandFuture commandFuture = new CommandFuture(); |
|||
commandFuture.setCmdToDevice(cmdToDevice); |
|||
|
|||
Integer toDeviceCmdId = cmdToDevice.getCmdId(); |
|||
|
|||
//将指令放入map,等待设备返回,然后取出,在调用commandContinue |
|||
CurrentSendCmdMapInstance.getInstance().putCommand(toDeviceCmdId, commandFuture); |
|||
|
|||
//发送指令给设备 |
|||
deviceTcpCMDServiceV2.send(JSONUtil.toJsonStr(cmdToDevice)); |
|||
|
|||
commandFuture.waitForContinue(); |
|||
|
|||
if (commandFuture.isReceived()) { |
|||
try { |
|||
emitter.send(FrontCommandAck.deviceAck(frontCmdId, frontCmdName), MediaType.TEXT_PLAIN); |
|||
} catch (IOException e) { |
|||
emitter.completeWithError(e); |
|||
} |
|||
} |
|||
commandFuture.waitForContinue(); |
|||
|
|||
// JSONObject callbackResult = commandFuture.getCallbackResult(); |
|||
|
|||
|
|||
//对返回结果处理 |
|||
|
|||
|
|||
emitter.complete(); |
|||
|
|||
}).start(); |
|||
|
|||
} else if ("dehumidifier_start".equals(frontCmdName)) { |
|||
/* |
|||
除湿,该方法接收Double类型参数 humidity |
|||
*/ |
|||
new Thread(() -> { |
|||
try { |
|||
//向前端发送接收到指令 |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.RECEIVE, "后台已收到指令"), MediaType.TEXT_PLAIN); |
|||
//判断参数是否合法 |
|||
Integer humidity = (Integer) param.get("humidity"); |
|||
if (humidity == null) { |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.ERROR, "参数 humidity 必填"), MediaType.TEXT_PLAIN); |
|||
emitter.complete(); |
|||
return; |
|||
} |
|||
//1:先判断当前湿度是否大于设置值 |
|||
CMDToDevice temperatureCmdToDevice = FrontCommandGenerator.temperature_get();//生成指令 获取当前湿度 |
|||
CommandFuture temperatureFuture = new CommandFuture(); |
|||
temperatureFuture.setCmdToDevice(temperatureCmdToDevice); |
|||
Integer toDeviceCmdId = temperatureCmdToDevice.getCmdId(); |
|||
CurrentSendCmdMapInstance.getInstance().putCommand(toDeviceCmdId, temperatureFuture);//将指令放入map |
|||
deviceTcpCMDServiceV2.send(temperatureCmdToDevice); //发送指令给设备 |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.RECEIVE, "已向设备发送了指令:" + JSONUtil.toJsonStr(temperatureCmdToDevice)), MediaType.TEXT_PLAIN); |
|||
temperatureFuture.waitForContinue();//等待设备的反馈 |
|||
JSONObject deviceResult = temperatureFuture.getCallbackResult();//拿到设备返回结果 |
|||
if(temperatureFuture.isReceived()){//直接携带数据的反馈其实不需要判断isReceived,直接判断deviceResult == null |
|||
CurrentSendCmdMapInstance.getInstance().removeCommand(toDeviceCmdId);//将指令从map中删除 |
|||
//设备已经收到指令并且执行成功 |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.RECEIVE, "设备指令反馈:" + JSONUtil.toJsonStr(deviceResult)), MediaType.TEXT_PLAIN); |
|||
} |
|||
if (deviceResult == null) { |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.ERROR, "未读取到湿度传感器数值"), MediaType.TEXT_PLAIN); |
|||
emitter.complete(); |
|||
return; |
|||
} |
|||
Double temperature = deviceResult.getDouble("temperature");//拿到设备返回的湿度数值 |
|||
if (humidity < temperature) { |
|||
//2:如果小于设置值,提醒用户不用除湿 |
|||
emitter.send(FrontCommandAck.backstageAck(frontCmdId, frontCmdName, CommandStatus.ERROR, "设定湿度小于当前湿度,无需除湿。"), MediaType.TEXT_PLAIN); |
|||
emitter.complete(); |
|||
return; |
|||
} |
|||
//3:如果需要除湿开始调用底层指令" |
|||
CMDToDevice cmdToDeviceHumidity = FrontCommandGenerator.dehumidifier_start();//生成指令 开启除湿阀 |
|||
CommandFuture commandFutureHumidity = new CommandFuture(); |
|||
// |
|||
// |
|||
// //生成发给设备的指令 |
|||
// CMDToDevice cmdToDevice = FrontCommandGenerator.device_status_get(); |
|||
// CommandFuture commandFuture = new CommandFuture(); |
|||
// commandFuture.setCmdToDevice(cmdToDevice); |
|||
// |
|||
// String toDeviceCmdId = cmdToDevice.getCmdId(); |
|||
// //将指令放入map,等待设备返回,然后取出,在调用commandContinue |
|||
// CurrentSendCmdMapInstance.getInstance().putCommand(toDeviceCmdId, commandFuture); |
|||
// |
|||
// //发送指令给设备 |
|||
// deviceTcpCMDServiceV2.send(JSONUtil.toJsonStr(cmdToDevice)); |
|||
// commandFuture.waitForContinue(); |
|||
// if (commandFuture.isReceived()) { |
|||
// |
|||
// emitter.send(FrontCommandAck.deviceAck(frontCmdId, frontCmdName), MediaType.TEXT_PLAIN); |
|||
// |
|||
// } |
|||
// commandFuture.waitForContinue(); |
|||
// String callbackResult = commandFuture.getCallbackResult(); |
|||
|
|||
|
|||
|
|||
//对返回结果处理 |
|||
emitter.complete(); |
|||
|
|||
} catch (IOException e) { |
|||
emitter.completeWithError(e); |
|||
} |
|||
|
|||
}).start(); |
|||
} else if ("matrix_prefill".equals(frontCmdName)) { |
|||
|
|||
} else if ("spray_pipeline_wash".equals(frontCmdName)) { |
|||
|
|||
} else if ("syringe_pipeline_wash".equals(frontCmdName)) { |
|||
|
|||
} else if ("matrix_spray_start".equals(frontCmdName)) { |
|||
|
|||
} else if ("matrix_spray_change_param".equals(frontCmdName)) { |
|||
|
|||
} else if ("matrix_spray_stop".equals(frontCmdName)) { |
|||
|
|||
} else if ("matrix_spray_pause".equals(frontCmdName)) { |
|||
|
|||
} else if ("matrix_spray_continue".equals(frontCmdName)) { |
|||
|
|||
} else if ("slide_tray_in".equals(frontCmdName)) { |
|||
|
|||
} else if ("slide_tray_out".equals(frontCmdName)) { |
|||
|
|||
} |
|||
|
|||
|
|||
return emitter; |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,30 @@ |
|||
package com.qyft.ms.app.model.bo; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import jakarta.validation.constraints.NotBlank; |
|||
import lombok.Data; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Schema(description = "指令") |
|||
@Data |
|||
public class CMDToDevice { |
|||
@Schema(description = "指令id,不指定后台会自动生成uuid") |
|||
private Integer cmdId; |
|||
|
|||
@NotBlank() |
|||
@Schema(description = "指令名字", example = "device_status_get") |
|||
private String cmdName; |
|||
|
|||
@NotBlank() |
|||
@Schema(description = "子设备", example = "three_way_valve") |
|||
private String device; |
|||
|
|||
@NotBlank() |
|||
@Schema(description = "行为", example = "open") |
|||
private String action; |
|||
|
|||
@Schema(description = "参数") |
|||
private Map<String, Object> param; |
|||
|
|||
} |
@ -0,0 +1,24 @@ |
|||
package com.qyft.ms.app.model.form; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import jakarta.validation.constraints.NotBlank; |
|||
import lombok.Data; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Schema(description = "指令") |
|||
@Data |
|||
public class CMDFormV2 { |
|||
|
|||
@NotBlank() |
|||
@Schema(description = "指令id,前端生成唯一UUID") |
|||
private String cmdId; |
|||
|
|||
@NotBlank() |
|||
@Schema(description = "指令名字", example = "device_status_get") |
|||
private String cmdName; |
|||
|
|||
@Schema(description = "参数") |
|||
private Map<String, Object> param; |
|||
|
|||
} |
@ -0,0 +1,25 @@ |
|||
package com.qyft.ms.device.service; |
|||
|
|||
import cn.hutool.json.JSONUtil; |
|||
import com.qyft.ms.device.client.TcpClient; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
/** |
|||
* 设备tcp指令发送服务 |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
@RequiredArgsConstructor |
|||
public class DeviceTcpCMDServiceV2 { |
|||
|
|||
private final TcpClient tcpClient; |
|||
|
|||
|
|||
public boolean send(Object object) { |
|||
return tcpClient.send(JSONUtil.toJsonStr(object)); |
|||
} |
|||
|
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue