读取电表数据并展示

目录

硬件及相关知识准备

1:电表,透传工具(目前使用的塔石家的),负载设备。

2:进行测试,看电脑是否能与电表进行通讯,不知道如何测试可以找厂家客服。
3:了解协议(我的电表是2007版DLT645-2007),详细的话找篇文章。我下面简单说一下:

java 复制代码
指令格式:68  地址 68 控制码  长度  数据域 校验码 16
实际发送指令。读取电压:
68 55 00 09 09 21 20 68 11 04 35 34 34 33 5D 16
解释:
68 给电表表示开始
55 00 09 09 21 20  这个是你电表的地址采用倒叙(一般贴在电表上的),实际上是 202109090055
68 表示开始(起到个分割作用)
11 控制码,具体看下图,读数据是1001 16进制就是11。
04 长度,表示后面4组是我的执行代码,如果是返回的指令,则是他给你返回的值。
35 34 34 33 表示读取A相电压 具体如下下图。二进制代码是02 01 01 00 他这边数据域的数据都需要加 33 所以变成了 35 34 34 33 具体原因我也不知道了
5D 是校验码,你前面的指令进行计算后得出的。计算公式:从第一个帧起始符开始到校验码之前的所有各字节的模 256 的和,即各字节二进制算术和,不计超过 256 的溢出值。(我已经代码实现,可以看后面的代码部分)
16 结束指令,表示指令到这里结束了。

接收的指令:
68 55 00 09 09 21 20 68 91 08 35 34 34 33 33 33 33 33 AD 16
前面不说了直接控制码开始
91 从16转换成2进制:10010001
然后根据下图指令
1 从站发出的
0 从站正确应答
0 无后续数据帧
10001 表示读取的数据
08 表示后面8组都是返回的数据。注意这些都是16进制!
我们假设返回的是B54C3333: 
B5-33(16制计算)=82(16进制)
4C-33=19
33-33=0
33-33=0
由于之前说了是倒叙,所以实际读数是 001982。
回到我们的实际数据 35 34 34 33 33 33 33 33
这需要分成两部分,35 34 34 33 表示收到的指令 和我们发送的一样没问题
33 33 33 33 才是我们收到的指令,很显然是0000
然后根据下下图的数据格式000.0v(记得倒叙)【由于我是没接负载的所以数据都是0】

4:根据需求,找到协议中的数据标识和编码表如上图。我的需求是读电压,电流,电源。

后端代码准备

1.校验码生成代码。

根据上面说到公式生成即可,较为简单,直接出代码:

java 复制代码
public class DLT645ChecksumCalculator {

    /**
     * 计算十六进制字符串(表示字节序列)的校验和。
     * 校验和计算规则:将字符串表示的所有字节值相加,取和的低8位(模256)。
     *
     * @param hexString 十六进制字符串(包含空格,如 "68 55 00 09 09 21 20 68 11 04 35 34 34 33")
     * @return 校验和字节的十六进制字符串形式(大写,两位)
     * @throws IllegalArgumentException 如果输入字符串包含非十六进制字符或长度不是偶数
     */
    public static String calculateChecksum(String hexString) {
        // 移除所有空格,确保处理的是纯十六进制字符串
        String cleanHex = hexString.replaceAll("\\s", "");

        // 检查长度是否为偶数(每个字节由两个十六进制字符表示)
        if (cleanHex.length() % 2 != 0) {
            throw new IllegalArgumentException("输入必须包含完整的字节(偶数个十六进制字符)");
        }

        int sum = 0;
        int length = cleanHex.length();

        // 每两个字符解析一个字节,并累加
        for (int i = 0; i < length; i += 2) {
            // 提取两个字符组成一个字节的十六进制表示
            String byteStr = cleanHex.substring(i, i + 2);
            // 将十六进制字符串转换为字节值
            byte b = (byte) Integer.parseInt(byteStr, 16);
            // 将字节值作为无符号整数累加到 sum 中
            sum += b & 0xFF;
        }

        // 取累加和的低8位作为校验码
        byte checksumByte = (byte) (sum & 0xFF);
        // 将校验码字节转换为两位十六进制字符串(大写)
        return String.format("%02X", checksumByte);
    }

    /**
     * 生成完整的 DLT645-2007 帧。
     *
     * @param partialFrame 部分帧的十六进制字符串(不包含校验码和结束符 0x16)
     * @return 完整的帧字符串,包含校验码和结束符 0x16
     */
    public static String generateCompleteFrame(String partialFrame) {
        String checksum = calculateChecksum(partialFrame);
        // 将校验码和结束符 0x16 附加到部分帧后面
        return partialFrame + " " + checksum + " 16";
    }

    /**
     * 主方法 - 示例用法
     */
    public static void main(String[] args) {
        // 示例输入:部分帧数据(不包含校验码和结束符)
        String partialFrame = "68 55 00 09 09 21 20 68 91 08 35 34 34 33 33 33 33 33";

        try {
            String completeFrame = generateCompleteFrame(partialFrame);
            System.out.println("输入的部分帧: " + partialFrame);
            System.out.println("生成的完整帧: " + completeFrame);
            // 预期输出应类似于: 68 55 00 09 09 21 20 68 11 04 35 34 34 33 50 16
        } catch (IllegalArgumentException e) {
            System.err.println("错误: " + e.getMessage());
        }
    }
}

2.创建后端环境,搭建springboot项目。(这个自行解决)

3.按需求创建数据库。(自行解决)

4.完成电脑与硬件通讯的代码,并且以接口的形式完成。

建议首先是,自己写一个demo进行测试,demo能进行通讯,再移到后端接口之中。代码仅供参考

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiClientHexServer {
    // 线程安全的客户端连接列表
    private static final CopyOnWriteArrayList<ClientHandler> clientHandlers = new CopyOnWriteArrayList<>();
    // 线程池,用于处理客户端连接
    private static final ExecutorService executorService = Executors.newCachedThreadPool();
    // 服务器端口
    private static final int PORT = 8080;

    public static void main(String[] args) {
        try {
            // 创建服务器套接字
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服务器已启动,正在监听端口 " + PORT + "...");

            // 启动一个线程用于控制台输入(发送指令)
            Thread consoleThread = new Thread(MultiClientHexServer::handleConsoleInput);
            consoleThread.setDaemon(true); // 设置为守护线程,当主线程结束时自动结束
            consoleThread.start();

            // 持续接受客户端连接
            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("客户端已连接: " + clientSocket.getInetAddress().getHostAddress());

                // 为每个客户端创建一个处理器并添加到列表
                ClientHandler clientHandler = new ClientHandler(clientSocket);
                clientHandlers.add(clientHandler);

                // 在线程池中执行客户端处理任务
                executorService.execute(clientHandler);
            }
        } catch (IOException e) {
            System.err.println("服务器异常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 处理控制台输入,用于向所有客户端发送指令
     */
    private static void handleConsoleInput() {
        try {
            java.io.BufferedReader consoleReader =
                    new java.io.BufferedReader(new java.io.InputStreamReader(System.in));

            String input;
            while ((input = consoleReader.readLine()) != null) {
                if ("exit".equalsIgnoreCase(input)) {
                    System.out.println("正在关闭服务器...");
                    System.exit(0);
                }

                try {
                    // 将输入的十六进制字符串转换为字节数组
                    byte[] command = hexStringToByteArray(input.replaceAll("\\s+", ""));

                    // 向所有客户端发送指令
                    broadcastToAllClients(command);
                    System.out.println("已向所有客户端发送指令: " + input);
                } catch (IllegalArgumentException e) {
                    System.err.println("输入格式错误,请输入有效的十六进制指令: " + e.getMessage());
                }
            }
        } catch (IOException e) {
            System.err.println("控制台输入错误: " + e.getMessage());
        }
    }

    /**
     * 向所有连接的客户端广播消息
     * @param data 要发送的数据字节数组
     */
    private static void broadcastToAllClients(byte[] data) {
        // 遍历所有客户端处理器并发送数据
        for (ClientHandler handler : clientHandlers) {
            try {
                handler.sendData(data);
            } catch (IOException e) {
                System.err.println("向客户端发送数据失败: " + e.getMessage());
                // 发送失败可能是由于连接已关闭,从列表中移除
                clientHandlers.remove(handler);
            }
        }
    }

    /**
     * 将十六进制字符串转换为字节数组
     * @param hexString 十六进制字符串(不含空格)
     * @return 字节数组
     * @throws IllegalArgumentException 如果输入格式无效
     */
    private static byte[] hexStringToByteArray(String hexString) {
        if (hexString.length() % 2 != 0) {
            throw new IllegalArgumentException("十六进制字符串长度必须是偶数");
        }

        int len = hexString.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            String byteStr = hexString.substring(i, i + 2);
            try {
                data[i / 2] = (byte) Integer.parseInt(byteStr, 16);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("无效的十六进制字符: " + byteStr);
            }
        }
        return data;
    }

    /**
     * 将字节数组转换为十六进制字符串表示形式
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex).append(" ");
        }
        return hexString.toString().toUpperCase();
    }

    /**
     * 客户端处理器类,每个客户端连接对应一个实例
     */
    private static class ClientHandler implements Runnable {
        private final Socket clientSocket;
        private OutputStream outputStream;
        private InputStream inputStream;

        public ClientHandler(Socket socket) {
            this.clientSocket = socket;
        }

        @Override
        public void run() {
            try {
                // 获取输入输出流
                outputStream = clientSocket.getOutputStream();
                inputStream = clientSocket.getInputStream();

                System.out.println("开始处理客户端: " + clientSocket.getInetAddress().getHostAddress());

                // 持续接收客户端数据
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    // 处理接收到的数据
                    byte[] receivedData = new byte[bytesRead];
                    System.arraycopy(buffer, 0, receivedData, 0, bytesRead);

                    // 打印接收到的数据
                    System.out.println("从客户端 " + clientSocket.getInetAddress().getHostAddress() +
                            " 接收到数据: " + bytesToHex(receivedData));

                    // 这里可以添加对接收数据的处理逻辑
                    // 例如解析协议、回复客户端等
                }
            } catch (IOException e) {
                System.err.println("与客户端通信错误: " + e.getMessage());
            } finally {
                // 关闭连接并清理资源
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    System.err.println("关闭客户端连接时出错: " + e.getMessage());
                }

                // 从客户端列表中移除
                clientHandlers.remove(this);
                System.out.println("客户端断开连接: " + clientSocket.getInetAddress().getHostAddress());
            }
        }

        /**
         * 向客户端发送数据
         * @param data 要发送的数据字节数组
         * @throws IOException 如果发送失败
         */
        public void sendData(byte[] data) throws IOException {
            if (outputStream != null) {
                outputStream.write(data);
                outputStream.flush();
            }
        }
    }
}

5:设计思路

相关推荐
数据狐(Datafox)44 分钟前
一文读懂京东商品详情API:返回字段解读与 JSON 样例
java·开发语言·数据库·windows·json
emfuture1 小时前
龙芯2K300 PLC 在拉丝机收线系统改造中的应用:硬件选型、张力控制与国产化替代实践
java·开发语言·智能制造·plc·国产化替代
Doker 多克2 小时前
Spring Integration AMQP 和 Spring AMQP详解
java·后端·spring
NE_STOP2 小时前
Spring AI Alibaba -- 文生图、文生音及向量化
java
知行EDI2 小时前
eMudhra CA 私钥安全事件:Java 运行环境(JRE)风险评估与修复指南
java·emudhra
旋律4142 小时前
什么是抽象类与接口
java
旋律4142 小时前
String、StringBuilder、StringBuffer:三个"字符串",到底区别在哪?
java·后端
一条泥憨鱼3 小时前
【Redis | 黑马点评】基于Redis的消息队列 | stream消息队列 源码解读
java·数据库·redis·缓存·lua
aibo8603083 小时前
基于 Spring Boot + Vue3 的博物馆藏品管理系统设计与实现
java·spring boot·后端