通过NettySocket向硬件发送控制指令,并接收数据解析
准备工作
1,搭建netty服务端
@Slf4j
@Component
public class NettyServer {
@Autowired
private NettyServerProperties nettyServerProperties;
//编写run方法,处理客户端的请求
public void run() throws Exception {
//创建两个线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//获取到pipeline
ChannelPipeline pipeline = ch.pipeline();
//向pipeline加入解码器,可以自己定义
pipeline.addLast("decoder", new StringDecoder());
//向pipeline加入编码器,可以自己定义
//pipeline.addLast("encoder", new MyEncoder());
pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
//加入自己的业务处理handler
pipeline.addLast(new NettyServerHandler());
}
});
log.info("netty服务器启动成功(port:" + nettyServerProperties.getPort() + ")......");
ChannelFuture channelFuture = b.bind(nettyServerProperties.getPort()).sync();
//监听关闭
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
2,NettyServerProperties 配置
/**
* 读取YML中的服务配置
*
*/
@Configuration
@ConfigurationProperties(prefix = NettyServerProperties.PREFIX)
@Data
public class NettyServerProperties {
public static final String PREFIX = "netty.server";
/**
* 服务器端口
*/
private Integer port;
}
3,pom配置
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
4,yml配置
#Netty暴露端口
netty.server.port=9021
5,编解码处理程序可以自己定义,也可以用原生的(根据自己需要)(此段出处找不到了,对原作者说声抱歉)
/**
* @Author: LQY
* @CreateTime: 2022-09-14 0e:53
* @Description: 编码器
*/
@Component
public class MyEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception {
//将16进制字符串转为数组
byteBuf.writeBytes(hexString2Bytes(s));
}
/**
* 功能描述: 16进制字符串转字节数组
*
* @param src 16进制字符串
* @return byte[]
* @Author keLe
* @Date 2022/8/26
*/
public static byte[] hexString2Bytes(String src) {
int l = src.length() / 2;
byte[] ret = new byte[l];
for (int i = 0; i < l; i++) {
ret[i] = (byte) Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
}
return ret;
}
public static String convertStringToHex(String str) {
char[] chars = str.toCharArray();
StringBuffer hex = new StringBuffer();
for (int i = 0; i < chars.length; i++) {
hex.append(Integer.toHexString((int) chars[i]));
}
return hex.toString();
}
//16进制转换10进制
public static int hex16To10(String strHex) {
BigInteger lngNum = new BigInteger(strHex, 16);
return lngNum.intValue();
}
}
处理程序
1,编写服务端处理程序
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
private static final NettyChannelMng NETTY_CHANNEL_MNG;
private static final ServiceProperties PROPERTIES;
static {
PROPERTIES = SpringUtil.getBean("serviceProperties");
NETTY_CHANNEL_MNG = SpringUtil.getBean("nettyChannelMng");
}
/**
* 有客户端与服务器发生连接时执行此方法
* 1.打印提示信息
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
log.info("有新的客户端与服务器发生连接。客户端地址:" + channel.remoteAddress());
}
/**
* 当有客户端与服务器断开连接时执行此方法,此时会自动将此客户端从 channelGroup 中移除
* 1.打印提示信息
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
log.info("有客户端与服务器断开连接。客户端地址:" + channel.remoteAddress());
//设备断联,停止定时任务
// ExperimentRemoveScheduledTaskCmd cmd = new ExperimentRemoveScheduledTaskCmd();
// cmd.setJobName(linkedHashMap.get(channel.remoteAddress().toString()).toString());
// cmd.setGroupName(QuartzGroupEnum.EXPERIMENT.getValue());
// ExperimentScheduledTaskService experimentScheduledTaskService = SpringUtil.getBean("experimentScheduledTaskService");
// experimentScheduledTaskService.removeExperimentScheduledTask(cmd);
//更新数表中的数据状态
HashMap<String, Object> param = new HashMap<>();
param.put("id", NETTY_CHANNEL_MNG.getDeviceId(channel));
param.put("deviceRoute", StringUtils.EMPTY);
param.put("deviceStatus", "0");
String postResult = ExperimentHttpUtil.postMap(PROPERTIES.getUpdateExperimentDeviceSchool(), param);
log.info("结果:{}", postResult);
}
/**
* 表示channel 处于活动状态
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info(ctx.channel().remoteAddress() + " 处于活动状态");
}
/**
* 表示channel 处于不活动状态
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
log.info(channel.remoteAddress() + " 处于不活动状态,即将断开连接。");
HashMap<String, Object> param = new HashMap<>();
param.put("id", NETTY_CHANNEL_MNG.getDeviceId(channel));
param.put("deviceRoute", StringUtils.EMPTY);
param.put("deviceStatus", "0");
String postResult = ExperimentHttpUtil.postMap(PROPERTIES.getUpdateExperimentDeviceSchool(), param);
log.info("结果:{}", postResult);
}
private void regConversation(Channel channel, String msg) {
if (msg.length() != 10) {
return;
}
// 主动建立连接,消息长度是10
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("deviceMark", msg);
String result = ExperimentHttpUtil.postMap(PROPERTIES.getGetExperimentDeviceSchool(), paramMap);
if (ObjectUtil.isEmpty(result)) {
return;
}
JSONObject jsonObject = new JSONObject(result);
String deviceId = jsonObject.getByPath("$.data.id", String.class);
if(StringUtils.isEmpty(deviceId)) {
log.info("设备连接失败,接收到deviceMark:{}, 取到的deviceId:{}", msg, deviceId);
return;
}
//数据填入到linkedHashMap
NETTY_CHANNEL_MNG.addActiveDeviceChannel(deviceId, channel);
HashMap<String,Object> connectParam=new HashMap<>();
connectParam.put("deviceId",deviceId);
//添加连接日志
String s = ExperimentHttpUtil.postMap(PROPERTIES.getAddDeviceConnectLog(), connectParam);
HashMap<String, Object> param = new HashMap<>();
param.put("id", deviceId);
param.put("deviceRoute", channel.remoteAddress().toString());
param.put("deviceStatus", "1");
//更新数据表状态
String postResult = ExperimentHttpUtil.postMap(PROPERTIES.getUpdateExperimentDeviceSchool(), param);
log.info("更新数据表结果:{}", postResult);
//查询当前设备所绑定的实验是否还在继续
HashMap<String, Object> exParamMap = new HashMap<>();
exParamMap.put("deviceId", NETTY_CHANNEL_MNG.getDeviceId(channel));
String exStatus = ExperimentHttpUtil.postMap(PROPERTIES.getGetExperimentTask(), exParamMap);
JSONObject exObject = new JSONObject(exStatus);
if (!Optional.ofNullable(exObject.getByPath("$.success", Boolean.class)).orElse(false)) {
log.info("实验已经结束");
return;
}
log.info("实验在继续中,定时任务修改连接参数");
//在继续就获取当前定时一个定时采集任务
JobDataMap map = new JobDataMap();
//map.put("address", channel.remoteAddress().toString());
map.put("deviceId", NETTY_CHANNEL_MNG.getDeviceId(channel));
map.put("deviceNumber", msg);
ExperimentScheduledTaskAddCmd cmd = new ExperimentScheduledTaskAddCmd();
cmd.setJobName(NETTY_CHANNEL_MNG.getDeviceId(channel));
cmd.setGroupName(QuartzGroupEnum.EXPERIMENT.getValue());
cmd.setJobClass(CollectDataJob.class);
//cmd.setCron(FrequencyEnum.FIVE_MINUTE.getCron());
cmd.setCron(FrequencyEnum.TEN_SECOND.getCron());
cmd.setJobDataMap(map);
log.info("开始采集");
ExperimentScheduledTaskAddCmdExe experimentScheduledTaskAddCmdExe = SpringUtil.getBean("experimentScheduledTaskAddCmdExe");
experimentScheduledTaskAddCmdExe.execute(cmd);
}
private void commonConversation(Channel channel, String msg) {
if (msg.length() == 10) {
return;
}
// 发控制或采集指令,返回数据
// LQY TODO 2022/9/27 21:17 =>查询实验是否结束,结束则停止收集
HashMap<String, Object> exParamMap = new HashMap<>();
exParamMap.put("deviceId", NETTY_CHANNEL_MNG.getDeviceId(channel));
String exStatus = ExperimentHttpUtil.postMap(PROPERTIES.getGetExperimentTask(), exParamMap);
JSONObject exObject = new JSONObject(exStatus);
if (!Optional.ofNullable(exObject.getByPath("$.success", Boolean.class)).orElse(false)) {
ExperimentRemoveScheduledTaskCmd cmd = new ExperimentRemoveScheduledTaskCmd();
cmd.setJobName(NETTY_CHANNEL_MNG.getDeviceId(channel));
cmd.setGroupName(QuartzGroupEnum.EXPERIMENT.getValue());
ExperimentScheduledTaskService experimentScheduledTaskService = SpringUtil.getBean("experimentScheduledTaskService");
//移除定时任务
experimentScheduledTaskService.removeExperimentScheduledTask(cmd);
} else {
//实验未停止,继续传输采集数据|发送控制指令
DataAnalysis dataAnalysis = new DataAnalysis();
Map<String, String> dataMap = dataAnalysis.dataAnalysis(channel.remoteAddress(), msg);
if (ObjectUtil.isEmpty(dataMap)) {
log.info("控制指令------------------");
return;
}
//上传数据
//数据转为Map集合
HashMap<String, Object> param = new HashMap<>();
param.put("deviceId", NETTY_CHANNEL_MNG.getDeviceId(channel));
param.put("startTime", LocalDateTime.now());
param.put("dataTypeSAndDataValue", dataMap);
log.info("传输的数据{}", new JSONObject(param).toJSONString(0));
String postResult = ExperimentHttpUtil.postMap(PROPERTIES.getAddExperimentCollectTask(), param);
log.info("数据传输结果:{}", postResult);
}
}
/**
* 读取到客户端发来的数据数据
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws IOException {
//获取到当前channel
Channel channel = ctx.channel();
log.info("接收到客户端(硬件)发来的数据,地址为:" + channel.remoteAddress() + ",数据为:" + msg);
//记录当前连接的设备数据
this.regConversation(channel, msg);
this.commonConversation(channel, msg);
}
/**
* 处理异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("发生异常。异常信息:{}", cause.getMessage());
//关闭通道
ctx.close();
}
/**
* 心跳机制
*/
//(本项目所连接的硬件设备,会在断连后,自动重连,所以心跳机制可以不加)
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
// log.info("心跳事件时触发");
// if (evt instanceof IdleStateEvent) {
// IdleStateEvent event = (IdleStateEvent) evt;
// // 当我们长时间没有给服务器发消息时,发送ping消息,告诉服务器我们还活跃
// if (event.state().equals(IdleState.WRITER_IDLE)) {
// log.debug("发送心跳");
// ctx.writeAndFlush("ping");
// }
// } else {
// super.userEventTriggered(ctx, evt);
// }
}
}
注解:
NettyChannelMng (通过当前的通道,拿到当前硬件设备配置的数据<此项目中配置的是deviceId>)
@Component
@Slf4j
public class NettyChannelMng {
//定义一个channle 组,管理所有的channel
//GlobalEventExecutor.INSTANCE) 是全局的事件执行器,是一个单例
private ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
// 活动的设备id和channelId映射
private Map<String, ChannelId> activeDeviceIdChannelIdMapping = new ConcurrentHashMap<>();
private Map<ChannelId, String> channelIdDeviceMapping = new ConcurrentHashMap<>();
synchronized public void addActiveDeviceChannel(String deviceId, Channel channel) {
boolean added = this.channelGroup.add(channel);
if(added) {
this.activeDeviceIdChannelIdMapping.put(deviceId, channel.id());
this.channelIdDeviceMapping.put(channel.id(), deviceId);
}
}
synchronized public void unconnectDevice(String deviceId) {
this.activeDeviceIdChannelIdMapping.remove(deviceId);
Iterator<Map.Entry<ChannelId, String>> iterator = this.channelIdDeviceMapping.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<ChannelId, String> entry = iterator.next();
if(Objects.equals(entry.getValue(), deviceId)) {
iterator.remove();
}
}
}
synchronized public Channel getActiveDeviceChannel(String deviceId) {
ChannelId channelId = this.activeDeviceIdChannelIdMapping.getOrDefault(deviceId, null);
if(Objects.isNull(channelId)) {
return null;
}
Channel channel = this.channelGroup.find(channelId);
return channel;
}
synchronized public String getDeviceId(Channel channel) {
String deviceId = this.channelIdDeviceMapping.getOrDefault(channel.id(), null);
return deviceId;
}
public List<String> listAddress() {
List<String> addresses = this.channelGroup.stream().map(e->e.remoteAddress().toString()).collect(Collectors.toList());
return addresses;
}
}
数据处理
1,commonConversation(Channel channel, String msg)
此接口处理客户端(硬件)推送过来的数据(第一次是连接发送设备的编号,长度为10,后续发送的则为控制指令结果,和数据采集结果),自定义定时任务不再解释,后续会贴出链接。
private void commonConversation(Channel channel, String msg) {
if (msg.length() == 10) {
return;
}
// 发控制或采集指令,返回数据
// LQY TODO 2022/9/27 21:17 =>查询实验是否结束,结束则停止收集
HashMap<String, Object> exParamMap = new HashMap<>();
exParamMap.put("deviceId", NETTY_CHANNEL_MNG.getDeviceId(channel));
String exStatus = ExperimentHttpUtil.postMap(PROPERTIES.getGetExperimentTask(), exParamMap);
JSONObject exObject = new JSONObject(exStatus);
if (!Optional.ofNullable(exObject.getByPath("$.success", Boolean.class)).orElse(false)) {
ExperimentRemoveScheduledTaskCmd cmd = new ExperimentRemoveScheduledTaskCmd();
cmd.setJobName(NETTY_CHANNEL_MNG.getDeviceId(channel));
cmd.setGroupName(QuartzGroupEnum.EXPERIMENT.getValue());
ExperimentScheduledTaskService experimentScheduledTaskService = SpringUtil.getBean("experimentScheduledTaskService");
//移除定时任务
experimentScheduledTaskService.removeExperimentScheduledTask(cmd);
} else {
//实验未停止,继续传输采集数据|发送控制指令
DataAnalysis dataAnalysis = new DataAnalysis();
Map<String, String> dataMap = dataAnalysis.dataAnalysis(channel.remoteAddress(), msg);
if (ObjectUtil.isEmpty(dataMap)) {
log.info("控制指令------------------");
return;
}
//上传数据
//数据转为Map集合
HashMap<String, Object> param = new HashMap<>();
param.put("deviceId", NETTY_CHANNEL_MNG.getDeviceId(channel));
param.put("startTime", LocalDateTime.now());
param.put("dataTypeSAndDataValue", dataMap);
log.info("传输的数据{}", new JSONObject(param).toJSONString(0));
String postResult = ExperimentHttpUtil.postMap(PROPERTIES.getAddExperimentCollectTask(), param);
log.info("数据传输结果:{}", postResult);
}
}
2,数据分析(DataAnalysis)
**
* @Author: LQY
* @CreateTime: 2022-09-22 20:00
* @Description: 数据解析
*/
@Slf4j
public class DataAnalysis {
public Map<String, String> dataAnalysis(SocketAddress remoteAddress, String msg) {
Map<String, String> dataMap = new HashMap<>();
//表示客户端回传的数据为硬件采集/控制数据
String ip = remoteAddress.toString();
String crc = msg.substring(msg.length() - 4);
//数据校验
String newCRC = InstructionTransformation.getCRC16(msg.substring(0, msg.length() - 4));
if(!StringUtils.equals(crc, newCRC)) {
log.error("校验值不正确");
MemoryCacheUtil.put(ip, false);
return dataMap;
}
String deviceNum = msg.substring(0, 6);
String code = msg.substring(6, 8);
switch (code) {
case "03"://流速传感器数据、土壤温湿度数据、角度传感器数据
String mark = msg.substring(8, 10);
switch (mark) {
case "30":
double flowMeter_1 = MyEncoder.hex16To10(msg.substring(10, 14)) / 10.0;//1号流量计
double flowMeter_2 = MyEncoder.hex16To10(msg.substring(14, 18)) / 10.0;//2号流量计
double flowMeter_3 = MyEncoder.hex16To10(msg.substring(18, 22)) / 10.0;//3号流量计
double flowMeter_4 = MyEncoder.hex16To10(msg.substring(22, 26)) / 10.0;//4号流量计
double totalFlow_1 = MyEncoder.hex16To10(msg.substring(26, 30));//1号流量计总流量
double totalFlow_2 = MyEncoder.hex16To10(msg.substring(30, 34));//2号流量计总流量
double totalFlow_3 = MyEncoder.hex16To10(msg.substring(34, 38));//3号流量计总流量
double totalFlow_4 = MyEncoder.hex16To10(msg.substring(38, 42));//4号流量计总流量
double tem_1 = MyEncoder.hex16To10(msg.substring(42, 46)) / 100.0;//1号温度
double hum_1 = MyEncoder.hex16To10(msg.substring(46, 50)) / 100.0;//1号湿度
double ec_1 = MyEncoder.hex16To10(msg.substring(50, 54));//1号盐度
double tem_2 = MyEncoder.hex16To10(msg.substring(54, 58)) / 100.0;//2号温度
double hum_2 = MyEncoder.hex16To10(msg.substring(58, 62)) / 100.0;//2号湿度
double ec_2 = MyEncoder.hex16To10(msg.substring(62, 66));//2号盐度
double tem_3 = MyEncoder.hex16To10(msg.substring(66, 70)) / 100.0;//3号温度
double hum_3 = MyEncoder.hex16To10(msg.substring(70, 74)) / 100.0;//3号湿度
double ec_3 = MyEncoder.hex16To10(msg.substring(74, 78));//3号盐度
Integer proximitySwitch_0 = MyEncoder.hex16To10(msg.substring(78, 82));//0度传感器是否触发,1触发0未触发
Integer proximitySwitch_2 = MyEncoder.hex16To10(msg.substring(82, 86));//2度传感器是否触发,1触发0未触发
Integer proximitySwitch_4 = MyEncoder.hex16To10(msg.substring(86, 90));//4度传感器是否触发,1触发0未触发
Integer proximitySwitch_6 = MyEncoder.hex16To10(msg.substring(90, 94));//6度传感器是否触发,1触发0未触发
Integer proximitySwitch_8 = MyEncoder.hex16To10(msg.substring(94, 98));//8度传感器是否触发,1触发0未触发
Integer proximitySwitch_10 = MyEncoder.hex16To10(msg.substring(98, 102));//10度传感器是否触发,1触发0未触发
Integer bathHeater_1 = MyEncoder.hex16To10(msg.substring(105, 106));//1号浴霸状态,1开0关
Integer bathHeater_2 = MyEncoder.hex16To10(msg.substring(104, 105));//2号浴霸状态,1开0关
Integer bathHeater_3 = MyEncoder.hex16To10(msg.substring(103, 104));//3号浴霸状态,1开0关
log.info("1号流量计:" + flowMeter_1 + ",2号流量计:" + flowMeter_2 + ",3号流量计:" + flowMeter_3 + ",4号流量计:" + flowMeter_4);
log.info("1号流量计总流量:" + totalFlow_1 + ",2号流量计总流量:" + totalFlow_2 + ",3号流量计总流量:" + totalFlow_3 + ",4号流量计总流量:" + totalFlow_4);
log.info("1号温度:" + tem_1 + ",1号湿度:" + hum_1 + ",1号盐度:" + ec_1);
log.info("2号温度:" + tem_2 + ",2号湿度:" + hum_2 + ",2号盐度:" + ec_2);
log.info("3号温度:" + tem_3 + ",3号湿度:" + hum_3 + ",3号盐度:" + ec_3);
log.info("1号浴霸状态:" + bathHeater_1 + ",2号浴霸状态:" + bathHeater_2 + ",3号浴霸状态:" + bathHeater_3);
log.info("0度传感器是否触发:" + proximitySwitch_0 + ",2度传感器是否触发:" + proximitySwitch_2 + ",4度传感器是否触发:" + proximitySwitch_4
+ ",6度传感器是否触发:" + proximitySwitch_6 + ",8度传感器是否触发:" + proximitySwitch_8 + ",10度传感器是否触发:" + proximitySwitch_10);
dataMap.put("flowMeter_1", String.valueOf(flowMeter_1));
dataMap.put("flowMeter_2", String.valueOf(flowMeter_2));
dataMap.put("flowMeter_3", String.valueOf(flowMeter_3));
dataMap.put("flowMeter_4", String.valueOf(flowMeter_4));
dataMap.put("totalFlow_1", String.valueOf(totalFlow_1));
dataMap.put("totalFlow_2", String.valueOf(totalFlow_2));
dataMap.put("totalFlow_3", String.valueOf(totalFlow_3));
dataMap.put("totalFlow_4", String.valueOf(totalFlow_4));
dataMap.put("tem_1", String.valueOf(tem_1));
dataMap.put("hum_1", String.valueOf(hum_1));
dataMap.put("ec_1", String.valueOf(ec_1));
dataMap.put("tem_2", String.valueOf(tem_2));
dataMap.put("hum_2", String.valueOf(hum_2));
dataMap.put("ec_2", String.valueOf(ec_2));
dataMap.put("tem_3", String.valueOf(tem_3));
dataMap.put("hum_3", String.valueOf(hum_3));
dataMap.put("ec_3", String.valueOf(ec_3));
// MemoryCacheUtil.put(ip, dataMap.isEmpty());
break;
}
break;
case "10":
mark = msg.substring(10, 12);
switch (mark) {
case "19":
log.info("控制坡度成功");
break;
case "1A":
log.info("控制浴霸1、 2、 3成功");
break;
case "15": // L.D. TODO 2022/9/27 15:03 : 这个暂时没用到
log.info("清空流量计1、2、3、4累计流量成功");
break;
}
break;
}
return dataMap;
}
}
3,数据校验(InstructionTransformation)
**
* @Author: LQY
* @CreateTime: 2022-09-21 16:35
* @Description: 指令转换,转为hex
*/
public class InstructionTransformation {
public static String getCRC16(String data) {
data = data.replace(" ", "");
int len = data.length();
if (!(len % 2 == 0)) {
return "0000";
}
int num = len / 2;
byte[] para = new byte[num];
for (int i = 0; i < num; i++) {
int value = Integer.valueOf(data.substring(i * 2, 2 * (i + 1)), 16);
para[i] = (byte) value;
}
return getCRC16(para);
}
/**
* 计算CRC16校验码
*
* @param bytes 字节数组
* @return 校验码
*/
public static String getCRC16(byte[] bytes) {
// CRC寄存器全为1
int CRC = 0x0000ffff;
// 多项式校验值
int POLYNOMIAL = 0x0000a001;
int i, j;
for (i = 0; i < bytes.length; i++) {
CRC ^= ((int) bytes[i] & 0x000000ff);
for (j = 0; j < 8; j++) {
if ((CRC & 0x00000001) != 0) {
CRC >>= 1;
CRC ^= POLYNOMIAL;
} else {
CRC >>= 1;
}
}
}
// 结果转换为16进制
String result = Integer.toHexString(CRC).toUpperCase();
if (result.length() != 4) {
StringBuffer sb = new StringBuffer("0000");
result = sb.replace(4 - result.length(), 4, result).toString();
}
//高位在前地位在后
//String str = result.substring(0, 2) + result.substring(2, 4);
// 交换高低位,低位在前高位在后
String str = result.substring(2, 4) + result.substring(0, 2);
return str;
}
}
控制层
1,nettyController
/**
* @Author: LQY
* @CreateTime: 2022-09-23 09:38
* @Description: 硬件控制
*/
@RestController
@RequestMapping("/control")
@Api(tags = "硬件控制类", produces = "application/json", consumes = "application/json")
@Slf4j
public class NettyController {
@Autowired
private DataCollectionService dataCollectionService;
@Autowired
private CommandControlService commandControlService;
@Autowired
private RemoveFlowService removeFlowService;
/**
* @Description: 数据采集(单次)
* @Author LQY
* @Date 2022/9/24 9:50
* @Return com.alibaba.cola.dto.Response
*/
@PostMapping("/dataCollect")
@ApiOperation(value = "数据收集")
public Response dataCollect(@RequestBody @Validated NettyRequestDto nettyRequestDto) {
return dataCollectionService.dataCollection(nettyRequestDto);
}
/**
* @Description: 数据采集(定时) // L.D. TODO 2022/9/27 14:37 : 开始实验调这个接口
* @Author LQY
* @Date 2022/9/25 13:40
* @Return com.alibaba.cola.dto.Response
*/
@PostMapping("/dataCollectTiming")
@ApiOperation(value = "数据收集(定时)")
public Response dataCollectTiming(@RequestBody @Validated NettyRequestDto nettyRequestDto) {
JobDataMap map = new JobDataMap();
//map.put("address", nettyRequestDto.getAddress());
map.put("deviceId", nettyRequestDto.getDeviceId());
map.put("deviceNumber", nettyRequestDto.getDeviceNumber());
ExperimentScheduledTaskAddCmd cmd = new ExperimentScheduledTaskAddCmd();
cmd.setJobName(nettyRequestDto.getDeviceId());
cmd.setGroupName(QuartzGroupEnum.EXPERIMENT.getValue());
cmd.setJobClass(CollectDataJob.class);
//默认5分钟采集一次
String cron = FrequencyEnum.getValueByName(nettyRequestDto.getFrequency());
if(StringUtils.isEmpty(cron)) {
//cron = FrequencyEnum.FIVE_MINUTE.getCron();
// LQY TODO 2022/11/9 16:55 =>演示用
cron = FrequencyEnum.TEN_SECOND.getCron();
}
cmd.setCron(cron);
cmd.setJobDataMap(map);
log.info("开始采集");
return dataCollectionService.dataCollectTiming(cmd);
}
@PostMapping("/stopCollect")
@ApiOperation(value = "实验停止停止收集数据")
public Response stopCollect(@RequestBody @Validated NettyRequestDto nettyRequestDto) {
// log.error("测试测试测试测试");
ExperimentRemoveScheduledTaskCmd cmd = new ExperimentRemoveScheduledTaskCmd();
cmd.setJobName(nettyRequestDto.getDeviceId());
cmd.setGroupName(QuartzGroupEnum.EXPERIMENT.getValue());
ExperimentScheduledTaskService experimentScheduledTaskService = SpringUtil.getBean("experimentScheduledTaskService");
return experimentScheduledTaskService.removeExperimentScheduledTask(cmd);
}
/**
* @Description: 控制指令
* @Author LQY
* @Date 2022/9/24 9:50
* @Return com.alibaba.cola.dto.Response
*/
@PostMapping("/sendInstructionToDevice")
@ApiOperation(value = "发送控制指令到设备")
public Response sendInstructionToDevice(@RequestBody @Validated NettyRequestDto nettyRequestDto) throws InterruptedException {
Response result = commandControlService.commandControl(nettyRequestDto);
// Boolean sendResult = MemoryCacheUtil.get(nettyRequestDto.getAddress());
// if (Objects.nonNull(sendResult) && !sendResult) {
// MemoryCacheUtil.remove(nettyRequestDto.getAddress());
// return Response.buildFailure(ErrorCode.ERROR_SERVICE.getErrCode(), "操作失败");
// }
return result;
}
/**
* @Description: 清除累计流量
* @Author LQY
* @Date 2022/9/24 9:49
* @Return com.alibaba.cola.dto.Response
*/
@PostMapping("/clearAccumulatedFlow")
@ApiOperation(value = "清除累计流量")
public Response clearAccumulatedFlow(@RequestBody @Validated NettyRequestDto nettyRequestDto) {
return removeFlowService.removeFlow(nettyRequestDto);
}
}
2,重点(sendInstructionToDevice)(发送控制指令到指定设备)
之前是靠前端传输过来的具体设备的路由地址(通道地址channel),使用循环查询是否存在该设备,存在就能发送控制指令,后续改为设备连接后,会通过全局存储当前连接的设备数据(包含接口查询数据表的相关数据),前端传参只需要传设备的deviceMark与deviceId,传入控制参数即可操控硬件设备。
**
* @Author: LQY
* @CreateTime: 2022-09-23 09:16
* @Description: 控制指令
*/
@Data
public class NettyRequestDto {
private String address;
/**
* 设备编号
*/
private String deviceNumber;
/**
* 操作类型
*/
private String type;
/** 采集频率 */
private String frequency;
/** 设备id */
private String deviceId;
}
/**
* @Description: 控制指令
* @Author LQY
* @Date 2022/9/24 9:50
* @Return com.alibaba.cola.dto.Response
*/
@PostMapping("/sendInstructionToDevice")
@ApiOperation(value = "发送控制指令到设备")
public Response sendInstructionToDevice(@RequestBody @Validated NettyRequestDto nettyRequestDto) throws InterruptedException {
Response result = commandControlService.commandControl(nettyRequestDto);
// Boolean sendResult = MemoryCacheUtil.get(nettyRequestDto.getAddress());
// if (Objects.nonNull(sendResult) && !sendResult) {
// MemoryCacheUtil.remove(nettyRequestDto.getAddress());
// return Response.buildFailure(ErrorCode.ERROR_SERVICE.getErrCode(), "操作失败");
// }
return result;
}
3,控制指令
/**
* @Author: LQY
* @CreateTime: 2022-09-24 09:31
* @Description:指令控制操作
*/
@Component
@Slf4j
public class CommandControlCmdExe {
@Resource
NettyChannelMng nettyChannelMng;
/**
* @Description: 发控制指令
* @Author LQY
* @Date 2022/9/24 9:50
* @Return com.alibaba.cola.dto.Response
*/
public Response execute(NettyRequestDto nettyRequestDto) {
Channel channel = nettyChannelMng.getActiveDeviceChannel(nettyRequestDto.getDeviceId());
if(Objects.isNull(channel)) {
return Response.buildFailure(ErrorCode.ERROR_SERVICE.getErrCode(), "设备断开连接,请稍后再试");
}
String deviceNum = nettyRequestDto.getDeviceNumber().substring(4);
switch (nettyRequestDto.getType()) {
case "1"://获取流速传感器数据、土壤温湿度数据、角度传感器数据
String msg = deviceNum + "0300000018";
String CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "2"://控制坡度为0度
msg = deviceNum + "1000190001020000";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "3"://控制坡度为2度
msg = deviceNum + "1000190001020001";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "4"://控制坡度为4度
msg = deviceNum + "1000190001020002";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "5"://控制坡度为6度
msg = deviceNum + "1000190001020003";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "15"://控制坡度为8度
msg = deviceNum + "1000190001020004";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "16"://控制坡度为10度
msg = deviceNum + "1000190001020005";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
case "6"://控制浴霸1开
//msg = deviceNum + "10001A000306000000000000";
msg = deviceNum + "10001A0001020001";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
log.error("1开指令:{}",msg);
channel.writeAndFlush(msg);
break;
case "7"://控制浴霸1关
//msg = deviceNum + "10001A000306000100000000";
msg = deviceNum + "10001A0001020000";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
log.error("1关指令:{}",msg);
channel.writeAndFlush(msg);
break;
case "8"://控制浴霸2开
//msg = deviceNum + "10001A000306000100010000";
msg = deviceNum + "10001B0001020001";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
log.error("2开指令:{}",msg);
channel.writeAndFlush(msg);
break;
case "9"://控制浴霸2关
//msg = deviceNum + "10001A000306000100010001";
msg = deviceNum + "10001B0001020000";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
log.error("2关指令:{}",msg);
channel.writeAndFlush(msg);
break;
case "10"://控制浴霸3开
//msg = deviceNum + "10001A000306000000010000";
msg = deviceNum + "10001C0001020001";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
log.error("3开指令:{}",msg);
channel.writeAndFlush(msg);
break;
case "11"://控制浴霸3关
//msg = deviceNum + "10001A000306000000010001";
msg = deviceNum + "10001C0001020000";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
log.error("3关指令:{}",msg);
channel.writeAndFlush(msg);
break;
//case "12"://控制浴霸1关2关3开
// msg = deviceNum + "10001A000306000000000001";
// CRC = InstructionTransformation.getCRC16(msg);
// msg = msg + CRC;
// channel.writeAndFlush(msg);
// break;
//case "13"://控制浴霸1开2关3开
// msg = deviceNum + "10001A000306000100000001";
// CRC = InstructionTransformation.getCRC16(msg);
// msg = msg + CRC;
// channel.writeAndFlush(msg);
// break;
case "14"://清空流量计1、2、3、4累计流量cc
msg = deviceNum + "1000180001021111";
CRC = InstructionTransformation.getCRC16(msg);
msg = msg + CRC;
channel.writeAndFlush(msg);
break;
}
return Response.buildSuccess();
}
}