第三次课:netty控制网关设备
分类: springboot 专栏: 物联网项目 标签: netty控制网关设备
2023-05-31 11:10:55 1319浏览
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服务端测试

好博客就要一起分享哦!分享海报
您可能感兴趣的博客
他的专栏
他感兴趣的技术


新业务
springboot学习
ssm框架课
vue学习
【带小白】java基础速成