第三次课:netty控制网关设备
分类: springboot 专栏: 物联网项目 标签: netty控制网关设备
2023-05-31 11:10:55 1046浏览
netty控制网关设备
配置网关
- 配置好路由器,wifi设置好
默认设置的WiFi密码1q2w3e4r,WiFi名称是tenda开头的。
- 用网线连上网关
- 自己的电脑连上WiFi(tenda开头的那个)
- 转接头连接网关和电脑
- 设置网关的ip和端口
- 设置网关(看成是客户端)的对应服务端的ip和端口。
通过485协议调试,设置完后最好是重启下网关
设置基础数据
- 校园信息
- 空间位置
- 网关配置(跟上面的步骤设置的要一致,注意网关的ip和端口)
- 设备管理(注意电表设备编号)
开发后端核心代码
比如是对某个电表执行拉闸操作,那么把这个设备id传到后端controller,然后根据设备id查出该设备归哪个网关gateway管,并且查出该设备的设备编号deviceCode
String address = ElectricityMeterUtil.deviceAddressConvert(deviceCode); String ip = "/"+gatewayVO.getGatewayIp()+":"+gatewayVO.getGatewayPort(); String instruct = ElectricityMeterUtil.getCompleteInstruct(address, InstructConstant.ELECTRICITY_METER_PULL_INSTRUCT); //获取发送指令 sendInstructsService.sendInstructs(instruct,ip,request,deviceCode);
ChannelGroup channelGroup= ServerHandler.getChannels(); Iterator channelIterator = channelGroup.iterator(); while(channelIterator.hasNext()){ Channel channel = (Channel)channelIterator.next(); InetSocketAddress insocket = (InetSocketAddress)channel.remoteAddress(); //给指定客户端ip发送消息 if(ip.equals(insocket.toString())){ byte[] b = ElectricityMeterUtil.fromHexString(instruction); channel.writeAndFlush(Unpooled.copiedBuffer(b)); channel.flush(); } }
至于netty服务端代码和处理器代码怎么写,回顾上次课内容。注意自定义编码器和自定义解码器
public class NettyMessageEncoder extends MessageToByteEncoder<String> { @Override protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception { byteBuf.writeBytes(ElectricityMeterUtil.fromHexString(s)); } }
@Slf4j public class NettyMessageDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception { log.info("开始解码:"); byte[] b = new byte[byteBuf.readableBytes()]; //复制内容到字节数组b byteBuf.readBytes(b); //字节数组转字符串 String str = new String(b); String content = ElectricityMeterUtil.getBufHexStr(b); out.add(content); log.info("解码结束!"); } }
厂家提供的辅助类
/** * 电表工具类 */ public class ElectricityMeterUtil { /** * 获取完整指令字符串 * @param hexAddress 设备地址 * @param constant 校验前的指令内容 InstructConstant类中获取 * @return */ public static String getCompleteInstruct(String hexAddress,String constant){ String instructTxt = constant.replaceAll("FE","").trim(); String addressInstaruct = MessageFormat.format(instructTxt,hexAddress); //获取校验码 String ten = getCSCheck(addressInstaruct); return MessageFormat.format(constant,hexAddress)+" "+ten+" 16"; } /** * 设备地址的转换 * 转化前 000000920245 * 转换后 45 02 92 00 00 00 * @param deviceNo * @return */ public static String deviceAddressConvert(String deviceNo){ String resultDeviceNo = null; if(deviceNo!=null){ deviceNo=deviceNo.replace("", " "); int num = deviceNo.length()/4; LinkedList<String> items = new LinkedList<String>(); String newStr; for(int i=0;i<num;i++){ newStr=""; newStr = deviceNo.substring(0, 4); deviceNo = deviceNo.substring(4); items.addFirst(newStr.replaceAll(" ", "")); } resultDeviceNo = StringUtils.join(items," "); } return resultDeviceNo; } /** * 获取指令中的校验码 * @param hexStr * @return */ public static String getCSCheck(String hexStr) { byte[] bytes = fromHexString(hexStr); int ck = 0; for(byte b : bytes){ ck = ck+b; } //去除FE后,从帧起始符68H到数据域 全部字符的字节相加取模256, //保留一直字节,超过256会有多个字节所以要取模 Integer a = ck%0x100; System.out.println("检验码的和是:"+ toHex(ck)); String b = toHex(a); if(b.length()<2){ b="0"+b; } if(b.length()>2){ b= b.substring(b.length()-2); } return b; } /** * 转换为十六进制数据 * @param num * @return */ public static String toHex(int num) { char[] map = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; if (num == 0) return "0"; String result = ""; while (num != 0) { int x = num & 0xF; result = map[(x)] + result; num = (num >>> 4); } return result; } /** * 16进制数据转字节数组 * * @param hexStr * @return */ public static byte[] fromHexString(String hexStr) { if (hexStr.length() < 1) return null; hexStr = hexStr.replaceAll(" ", ""); byte[] result = new byte[hexStr.length() / 2]; for (int i = 0; i < hexStr.length() / 2; i++) { int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16); int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16); result[i] = (byte) (high * 16 + low); } return result; } /** * 将字节转换成16进制字符串 * @param raw * @return */ public static String getBufHexStr(byte[] raw) { String HEXES = "0123456789ABCDEF"; if (raw == null) { return null; } final StringBuilder hex = new StringBuilder(2 * raw.length); for (final byte b : raw) { hex.append(HEXES.charAt((b & 0xF0) >> 4)) .append(HEXES.charAt((b & 0x0F))); } return hex.toString(); } }
/** * 水电表指令常量类 */ public class InstructConstant { //唤醒码 public static String FE_1 = "FE "; public static String FE_2 = "FE FE "; public static String FE_3 = "FE FE FE "; public static String FE_4 = "FE FE FE FE "; public static String START_CHARACTER_68 = "68"; /** * 电表指令描述 * 1. FE位唤醒码一般有0-4个 【FE FE】 68 AA AA AA AA AA AA 68 01 02 65 F3 27 16 * 2. 68 是帧起始符 FE FE 【68】 AA AA AA AA AA AA 【68】 01 02 65 F3 27 16 * 3. 两个68之间的是设备的地址,一般是指设备编号 FE FE 68 【AA AA AA AA AA AA】 68 01 02 65 F3 27 16 * 4. 01是操作码 01-表示读,04-表示写 FE FE 68 AA AA AA AA AA AA 68 【01】 02 65 F3 27 16 * 5. 表示数据字节长度 FE FE 68 AA AA AA AA AA AA 68 01 【02】 65 F3 27 16 * 6. 【65 F3】表示数据 FE FE 68 AA AA AA AA AA AA 68 01 02 【65 F3】 27 16 * 7. 27是校验码,是27前面全部数据字节和取模256的结果(不包含FE) FE FE 68 AA AA AA AA AA AA 68 01 02 65 F3 27 16 * 8. 16是结束符固定值 */ /**获取电表号指令*/ public static String ELECTRICITY_METER_INSTRUCT="FE FE 68 AA AA AA AA AA AA 68 01 02 65 F3 27 16"; /**查电表用量指令 {0}-设备号*/ public static String ELECTRICITY_METER_USED_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 01 02 43 C3"; /**拉总闸 {0}-设备号 */ public static String ELECTRICITY_METER_PULL_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 08 5B F3 33 33 33 33 67 45"; /**合总闸 {0}-设备号*/ public static String ELECTRICITY_METER_ON_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 08 5B F3 33 33 33 33 AB 89"; /**A拉闸 {0}-设备号*/ public static String ELECTRICITY_METER_A_PULL_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 08 5B F3 33 33 33 33 88 66"; /**A合闸 {0}-设备号*/ public static String ELECTRICITY_METER_A_NO_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 08 5B F3 33 33 33 33 99 CC"; /**B拉闸 {0}-设备号*/ public static String ELECTRICITY_METER_B_PULL_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 08 5B F3 33 33 33 33 55 44"; /**B合闸 {0}-设备号*/ public static String ELECTRICITY_METER_B_NO_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 08 5B F3 33 33 33 33 BB AA"; /**电表的 总电压(AB路)、功率、电流、频率,A路*/ public static String ELECTRICITY_METER_SUMMARY_INFO_INSTRUCT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 01 02 B3 E9"; /**电表 A路功率设置*/ public static String ELECTRICITY_A_POWER_SETUP =FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 04 09 85 FF 33 33 33 33 {1}"; /**电表 B路功率设置*/ public static String ELECTRICITY_B_POWER_SETUP =FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 04 09 85 FF 33 33 33 33 {1}"; /**电表 A路周休特征字*/ public static String ELECTRICITY_WEEY_A_SETUP = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 04 08 55 F3 33 33 33 33 B2 33"; /**电表 B路周休特征字*/ public static String ELECTRICITY_WEEY_B_SETUP = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 04 08 55 F3 33 33 33 33 B2 B2"; /**电表 A路工作日 时段控制设置*/ public static String ELECTRICITY_A_TIME_WORK_SETUP = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 24 72 F6 33 33 33 33 {1} 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33"; /**电表 A路休息日 时段控制设置*/ public static String ELECTRICITY_A_TIME_REST_SETUP = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 24 82 F6 33 33 33 33 {1} 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33"; /**电表 B路工作日 时段控制设置*/ public static String ELECTRICITY_B_TIME_WORK_SETUP = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 24 92 F6 33 33 33 33 {1} 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33"; /**电表 B路休息日 时段控制设置*/ public static String ELECTRICITY_B_TIME_REST_SETUP = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 +" 04 24 A2 F6 33 33 33 33 {1} 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33 44 33 33"; /**查询电表余额*/ public static String ELECTRICITY_BALANCE=FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 01 02 4A 1C"; /**电费充值 {1} 购电次数 {2} 购电金额 {3} 购电类型 33-开户 34-充值 */ public static String ELECTRICITY_RECHARGE = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 04 0F 48 1C 33 33 33 33 {1} 32 32 {2} 34"; /**电费开户 {1} 购电次数 {2} 购电金额 {3} 购电类型 33-开户 34-充值*/ public static String ELECTRICITY_OPEN_ACCOUNT = FE_2 + START_CHARACTER_68 +" {0} "+ START_CHARACTER_68 + " 04 0F 48 1C 33 33 33 33 {1} 32 32 {2} 33"; /**查询水表信息指令*/ public static String WATER_METER_READ_DATA = FE_2 + START_CHARACTER_68 +" 10 {0} " + "01 03 90 1F 00"; /**开阀水表信息指令*/ public static String WATER_METER_NO = FE_2 + START_CHARACTER_68 +" 10 {0} " + "2A 04 A0 17 00 55"; /**关阀水表信息指令*/ public static String WATER_METER_OFF = FE_2 + START_CHARACTER_68 +" 10 {0} " + "2A 04 A0 17 00 99"; }
/** * 指令类型 */ public enum InstructTypeConstant { E_MAIN_GATE_NO("1","电-合总闸"), E_MAIN_GATE_OFF("2","电-拉总闸"), E_A_GATE_NO("3","电-A合总闸"), E_A_GATE_OFF("4","电-A拉总闸"), E_B_GATE_NO("5","电-B合总闸"), E_B_GATE_OFF("6","电-B拉总闸"), E_MAIN_PARSM_INFO("7","获取电表参数信息"), E_A_POWER_SETUP("8","下发A路功率设置"), E_A_WEEK_SETUP("9","A路周休特征字设置-一周"), E_B_WEEK_SETUP("10","B路周休特征字设置-一周"), E_A_TIME_WORK_SETUPA("11","A路工作日时段控制设置"), E_A_TIME_REST_SETUPA("12","A路休息日时段控制设置"), E_B_TIME_WORK_SETUPA("13","B路工作日时段控制设置"), E_B_TIME_REST_SETUPA("14","B路休息日时段控制设置"), E_RECHARGE_SETUPA("15","电表充值"), E_BALANCE_SETUPA("16","查询电表金额"), E_OPEN_ACCOUNT("17","电表重新开户"), W_GATE_NO("100","水-开阀"), W_GATE_OFF("101","水-关阀"), W_SEARCH_INFO("102","水-查询水表信息"); private String key; private String value; InstructTypeConstant(String key, String value) { this.key = key; this.value=value; } public String getKey() { return key; } public void setKey(String key) { this.key=key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public static InstructTypeConstant getByKey(String key) { if (null == key) { return null; } for (InstructTypeConstant status : InstructTypeConstant.values()) { if (status.getKey().equals(key)) { return status; } } return null; } public static InstructTypeConstant getByValue(String value) { for (InstructTypeConstant status : InstructTypeConstant.values()) { if (status.getValue().equals(value)) { return status; } } return null; } }
保存操作记录
像这种拉闸合闸都属于敏感操作,所以最好是把这种操作记录保存到数据库。(用面向切面的方式,建议后置增强)
参考之前的笔记:https://www.jf3q.com/article/detail/4130
参考属性
/** * 操作日志id */ @TableId(type = IdType.AUTO) private Integer id; /** * 设备编号 */ private String deviceCode; private String operationType;//操作类型:比如:电-合总闸 电-拉总闸 /** * 操作内容 */ private String operationContext;//发送指令 /** * 发送IP地址 */ private String sendIp; /** * 操作用户 */ private String operationUser; /** * 创建时间 */ private Date iotCreate; /** * 修改时间 */ private Date iotModify;
@Aspect @Component @EnableAspectJAutoProxy public class OperationLogAspect { @Resource IotOperationLogMapper operationLogMapper; @Pointcut("execution( * com.jf3q.iot.service.impl.SendInstructsServiceImpl.sendInstructs(..))") public void point(){ } //后置增强 @AfterReturning(value = "point()") public void afterReturning(JoinPoint jp){ Object [] args = jp.getArgs(); //取出参数 String ip = (String) args[0];//客户端ip String instructStr =(String) args[1];//操作指令 String deviceCode = (String) args[2];//设备编码 String operation = (String) args[3];//操作类型——拉闸或者合闸等 //添加日志记录 IotOperationLog log = new IotOperationLog(); log.setOperationContext(instructStr); log.setOperationType(operation); log.setOperationUser("admin");//谁操作的设置成谁 log.setDeviceCode(deviceCode); log.setSendIp(ip); operationLogMapper.insert(log); } }
补充-用单边机调试助手充当netty服务端测试
好博客就要一起分享哦!分享海报
您可能感兴趣的博客
他的专栏
他感兴趣的技术