DTU连接电表传输给云服务器代码思路

  1. 整体思路:DTU是数据透传模式,将服务器的指令通过DTU直接传到电表。返回的电表数据通过DTU的物联网卡直接传给服务器。
  2. 获取电表数据方式:首先正确连接设备(DTU和电表),注意DTU需要安装物联网卡,物联网卡需要查看是否激活。
  3. 初步可以通过测试工具先查看。正确配置测试工具,需要内网穿透工具。本次使用的是【花生壳】。TCP服务调试工具使用的是【wltszs4.3.29.exe】(网络调试助手)。辅助工具【ComMonitor.exe】(串口调试软件)主要用于生成报文里的【帧校验和CS】。
  4. 完成以上配置后,在网络调试助手中发送正确的报文就可以获取到电表数据。以下图片仅供效果参考,我使用的DTU是定制产品,具体报文不具备参考性质。 解析报文协议可以参考:水(CJ/T188)电(DL/T645)抄表数据解析示例_cjt188水表数据解析-CSDN博客国网376.1-2013协议解析 - 哔哩哔哩
5、java代码设计思路:
1、注册端口:定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
3、开始创建独立线程处理socket。
@Component
public class TCPServer {
    public static void startServerMethod() {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("===服务端启动成功===");
            // 1、注册端口
            ServerSocket serverSocket = new ServerSocket(???);
            // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
            while (true) {
                // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
                Socket socket = serverSocket.accept();
                System.out.println(simpleDateFormat.format(new Date())+"["+socket.getRemoteSocketAddress()+ "]它来了,上线了!");
                // 3、开始创建独立线程处理socket
                new ServerReaderThread(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

4、独立线程代码:


/**
 *开始创建独立线程处理socket
 **/
public class ServerReaderThread extends Thread{
    //业务service 手动注入bean
    private xxxService xxxService = BeanContext.getBean(xxxService.class);
    private InputStream inputStream;
    private OutputStream outputStream;
    private Socket socket;
    public ServerReaderThread(Socket socketPara){
        try{
            this.socket=socketPara;
            inputStream=socketPara.getInputStream();
            outputStream=socketPara.getOutputStream();
        }
        catch(IOException e){
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            // 在死循环中处理与客户的交流
            while(true) {
                byte[] buffer = new byte[1024];
                int length = inputStream.read(buffer);
                // 5、按照行读取消息
                String hexString = "";
                for (int i = 0; i < length; i++) {
                    String hex = Integer.toHexString(buffer[i] & 0xFF);
                    if (hex.length() == 1) {
                        hex = "0" + hex;
                    }
                    hexString += " "+hex;
                }
                System.out.println(simpleDateFormat.format(new Date())+"---收到报文:" + hexString);
                String a = hexString.substring(4,6);
                //设备信息
                if(a.equals("32")){
                    //DTU登录确认
                    loginDtu(hexString);
                }else if(a.equals("4a")){
                    //回复心跳
                    sendMsg(LOGIN_CONFIRM_STR);
                }else {
                    //存储报文中的电表数据
                    saveDate(hexString);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
        }
    }

    /**
     * DTU登录确认
     * @param hexString
     */
    private void loginDtu(String hexString) throws IOException {
        //登录确认报文
        LOGIN_CONFIRM_STR
        //登录确认报文(主站 -> 终端)
        sendMsg(LOGIN_CONFIRM_STR);
        //开启定时任务
        startTimeTask();
    }
    /**
     * 给DTU发送请求报文
     * @param msg
     */
    public  void sendMsg(String msg) throws IOException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.format(new Date())+"---发送报文到["+socket.getRemoteSocketAddress()+"]:" + msg);
        // 将16进制字符串转换为字节数组并发送
        byte[] data = new byte[msg.length() / 2];
        for (int i = 0; i < data.length; i++) {
            int index = i * 2;
            int value = Integer.parseInt(msg.substring(index, index + 2), 16);
            data[i] = (byte) value;
        }
        outputStream.write(data);
    }

    /**
     * 定时请求任务
     */
    private void startTimeTask() {
        //启动定时
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                // 这里是要定时执行的代码
                System.out.println("==============定时任务执行...");
                // 执行你的方法
                try {
                    //业务需求 五分整点时间执行请求
                    checkTimeTaskMethod();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        // 定时器每隔1分钟执行一次
        timer.scheduleAtFixedRate(task, 0, 1*60*1000);
    }

    /**
     * 判断是否是五分的整数时间 是的话请求电表数据
     */
    private void checkTimeTaskMethod() throws IOException {
        // 创建一个DateTimeFormatter实例来定义时间的格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println("=============开始判断五分整数时间点");
        LocalDateTime now = LocalDateTime.now();
        Boolean flag = isFiveMinuteTime(now);
        if(flag){
            // 使用formatter格式化LocalTime
            String formattedTime = now.format(formatter);
            System.out.println(formattedTime+":是五分整数时间点,开始请求电表数据。");
            sendMsg(f225_ONE_STR);
        }
    }

    /**
     * 判断是否是五分的整数时间 10:00 10:05 10:10...
     * @param time
     * @return
     */
    public static boolean isFiveMinuteTime(LocalDateTime time) {
        int minuteOfDay = time.getMinute();
        return minuteOfDay % 5 == 0;
    }


}

手动获取bean。因为独立线程不能直接注入service。

/**
 * 手动获取Bean
 * https://blog.csdn.net/tiger0709/article/details/78270768
 * https://blog.csdn.net/u011493599/article/details/78522315
 */
@Component
public class BeanContext implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanContext.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T) applicationContext.getBean(clz);
    }
}

6、报文解析:

        1.报文格式

其中校验和CS就是从控制域到链路用户数据的报文校验总加和(出来的是三位就截取最后两位):

public static String calculateChecksum(String hexString) {
        // 将十六进制字符串转换为字节数组
        byte[] bytes = new byte[hexString.length() / 2];
        System.out.println("字节长度:"+bytes.length);
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(hexString.substring(2 * i, 2 * i + 2), 16);
        }

        // 对字节数组求和
        int checksum = 0;
        for (byte b : bytes) {
            checksum += b & 0xFF;
        }

        // 将总和转换为十六进制字符串
        return Integer.toHexString(checksum).toUpperCase();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值