第三次课:netty控制网关设备

飞一样的编程
飞一样的编程
擅长邻域:Java,MySQL,Linux,nginx,springboot,mongodb,微信小程序,vue

分类: 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服务端测试


好博客就要一起分享哦!分享海报

此处可发布评论

评论(1展开评论

蓝色妖姬 能力:10

2023-06-05 11:30:01

学习中
点击查看更多评论

展开评论

您可能感兴趣的博客

客服QQ 1913284695