java版连接汇川PLC,发送数据,读取数据,保持重新链接,适用安卓

java版连接汇川PLC,发送数据,读取数据,保持重新链接,适用安卓

公共类PlcTcpManager

java 复制代码
import static android.content.ContentValues.TAG;

import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class PlcTcpManager {

    private final String ip;
    private final int port;
    private volatile boolean running = false;
    private volatile boolean connected = false;

    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    private final Object lock = new Object();

    public PlcTcpManager(String ip, int port) {
        //this.context = context.getApplicationContext(); // 保存全局Context,防止内存泄漏
        this.ip = ip;
        this.port = port;
    }

    /* 启动后台通信线程(自动重连机制) */
    public void start() {
        running = true;
        new Thread(this::connectionLoop, "PLC-Connect-Thread").start();
    }

    /* 停止通信并关闭连接 */
    public void stop() {
        running = false;
        disconnect();
    }

    /* 主循环 */
    private void connectionLoop() {
        while (running) {
            if (!connected) {
                Log.i(TAG, "尝试连接PLC...");
                connected = connect();
            }

            if (connected) {
                // 维持心跳
                if (!sendHeartbeat()) {
                    Log.i(TAG,"心跳失败,准备重连...");
                    connected = false;
                    disconnect();
                }
            }

            // 等待3秒再检测
            try {
                Thread.sleep(3000);
            } catch (InterruptedException ignored) {
            }
        }
    }

    /* 尝试连接PLC */
    private boolean connect() {
        try {
            socket = new Socket(ip, port);
            socket.setSoTimeout(2000);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            Log.i(TAG,"PLC连接成功");
            return true;
        } catch (IOException e) {
            Log.e(TAG,"PLC连接失败: " + e.getMessage());
            return false;
        }
    }

    /** 断开连接 */
    private void disconnect() {
        try {
            if (socket != null) socket.close();
        } catch (IOException ignored) {
        }
        socket = null;
        inputStream = null;
        outputStream = null;
    }

    /** 心跳包发送 */
    private boolean sendHeartbeat() {
        try {
            if (outputStream == null) return false;

            // 示例:读D100命令
            byte[] readCmd = new byte[]{
                    0x00, 0x01, 0x00, 0x00, 0x00, 0x06,
                    0x01, 0x03, 0x00, 0x64, 0x00, 0x01
            };
            sendData(readCmd);
            byte[] res = receiveData();
            return res != null && res.length > 0;
        } catch (Exception e) {
            return false;
        }
    }

    /** 发送任意数据 */
    public boolean sendData(byte[] data) {
        synchronized (lock) {
            try {
                if (outputStream == null) return false;
                outputStream.write(data);
                outputStream.flush();
                return true;
            } catch (IOException e) {
                connected = false;
                disconnect();
                return false;
            }
        }
    }

    /** 接收数据 */
    public byte[] receiveData() {
        try {
            if (inputStream == null) return null;
            byte[] buffer = new byte[256];
            int len = inputStream.read(buffer);
            if (len > 0) {
                byte[] result = new byte[len];
                System.arraycopy(buffer, 0, result, 0, len);
                return result;
            }
        } catch (IOException e) {
            connected = false;
            disconnect();
        }
        return null;
    }

    /* 读取多个寄存器(Modbus功能码) */
    public int[] readRegisters(int startAddress, int count) {
        if (!connected || outputStream == null || inputStream == null)
            return null;

        try {
            // 构造Modbus TCP读命令
            byte[] cmd = new byte[]{
                    0x00, 0x01, 0x00, 0x00, 0x00, 0x06,
                    0x01, // 从站地址
                    0x03, // 功能码03:读保持寄存器
                    (byte) (startAddress >> 8), (byte) (startAddress & 0xFF),
                    (byte) (count >> 8), (byte) (count & 0xFF)
            };

            sendData(cmd);
            byte[] res = receiveData();
            if (res == null || res.length < 9) return null;

            int byteCount = res[8] & 0xFF;
            int[] values = new int[byteCount / 2];
            for (int i = 0; i < values.length; i++) {
                values[i] = ((res[9 + 2 * i] & 0xFF) << 8) | (res[10 + 2 * i] & 0xFF);
            }
            return values;
        } catch (Exception e) {
            e.printStackTrace();
            connected = false;
            disconnect();
            return null;
        }
    }




//    public int[] readRegisters(int startAddress, int count) {
//        if (!connected || outputStream == null || inputStream == null)
//            return null;
//
//        try {
//            // 构造读命令
//            byte[] cmd = new byte[]{
//                    0x00, 0x01, 0x00, 0x00, 0x00, 0x06,
//                    0x01,       // 从站地址
//                    0x03,       // 功能码 03 = 读保持寄存器
//                    (byte) (startAddress >> 8), (byte) (startAddress & 0xFF),
//                    (byte) (count >> 8), (byte) (count & 0xFF)
//            };
//
//            sendData(cmd);
//            byte[] res = receiveData();
//
//            if (res == null || res.length < 9) return null;
//
//            int byteCount = res[8] & 0xFF;
//            int[] values = new int[byteCount / 2];
//            for (int i = 0; i < values.length; i++) {
//                values[i] = ((res[9 + 2 * i] & 0xFF) << 8) | (res[10 + 2 * i] & 0xFF);
//            }
//            return values;
//        } catch (Exception e) {
//            e.printStackTrace();
//            connected = false;
//            disconnect();
//            return null;
//        }
//    }


    /** 判断是否连接 */
    public boolean isConnected() {
        return connected;
    }

调用类

java 复制代码
  /* PLC数据更新(每2秒刷新一次) */
    PlcTcpManager plcManager = new PlcTcpManager("192.168.0.10", 502);

    @Override
    protected void onStart() {
        super.onStart();
        plcManager.start(); // 启动带自动重连的通信线程
    }

    @Override
    protected void onStop() {
        super.onStop();
        plcManager.stop();
    }

    //点击按钮读取PLC数据
    private void startPLCData() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 如果PLC已经连接上
                if (plcManager != null && plcManager.isConnected()) {
                    new Thread(() -> {

                        Map<Integer, Integer> data = new HashMap<>();

                        int[] regs = plcManager.readRegisters(0, 4);

                        if (regs != null)
                        {
                            int sensorIndex = 1;
                            for (int reg : regs) {
                                for (int bit = 0; bit < 16; bit++) {
                                    int bitValue = (reg >> bit) & 0x01; // 取出每一位
                                    data.put(sensorIndex++, bitValue);
                                }
                            }

                            // 更新UI层(ViewModel)
                            //runOnUiThread(() -> sensorViewModel.updateAllSensors(data));
                            // 打印日志
                            saveToLog(data);
                        } else {
                            runOnUiThread(() -> Log.e("PLC", "读取PLC失败"));
                        }
                    }).start();
                } else {
                    Log.w("PLC", "PLC未连接,等待重连...");
                }

                // 每2秒更新一次
                handler.postDelayed(this, 2000);
            }
        }, 1000);
    }

//    private void startPLCData() {
//        handler.postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                Map<Integer, Integer> data = new HashMap<>();
//                //Integer status = 0;
//                for (int i = 1; i <= 66; i++) {
//                    int statusType = random.nextInt(3);
//                    //status = statusType == 0 ? "正常" : statusType == 1 ? "报警" : "未知";
//                    data.put(i, statusType);
//
//                }
//                saveToLog(data);
//                sensorViewModel.updateAllSensors(data);
//                handler.postDelayed(this, 2000);
//            }
//        }, 1000);
//    }

    private void saveToLog(Map<Integer, Integer> data) {
        try {
            // ① 获取当前日期作为文件名
            String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
            String fileName = "plc_log_status" + date + ".txt";

            // ② 文件路径:外部存储路径(可见)
            File logFile = new File(getExternalFilesDir(null), fileName);

            // ③ 如果文件不存在,先创建并写入文件头
            if (!logFile.exists()) {
                logFile.createNewFile();
                FileWriter writer = new FileWriter(logFile, true);
                writer.write("=== PLC 日志文件 " + date + " ===\n");
                writer.write("时间\t\t传感器ID\t\t状态\n");
                writer.close();
            }

            // ④ 记录一条新数据
            FileWriter writer = new FileWriter(logFile, true);
            String time = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(new Date());

            for (Map.Entry<Integer, Integer> entry : data.entrySet()) {
                writer.write(time + "\t\t" + entry.getKey() + "\t\t" + entry.getValue() + "\n");
            }
            writer.write("\n");
            writer.flush();
            writer.close();

        } catch (IOException e) {
            Log.e("PLC_LOG", "写入日志失败: " + e.getMessage());
        }
    }



    /** 启动UDP监听线程 **/
    private void startUdpListener(int port) {
        new Thread(() -> {
            try {
                udpSocket = new DatagramSocket(port, InetAddress.getByName("0.0.0.0"));
                udpSocket.setSoTimeout(0); // 永不超时
                Log.i(TAG, "UDP 监听已启动, 端口 " + port);

                byte[] buf = new byte[1024];
                while (isListening) {
                    DatagramPacket packet = new DatagramPacket(buf, buf.length);
                    udpSocket.receive(packet);

                    InetAddress address = packet.getAddress();
                    String senderIP = address.getHostAddress();
                    int length = packet.getLength();
                    byte[] data = new byte[length];
                    System.arraycopy(packet.getData(), 0, data, 0, length);

                    // 尝试转成ASCII字符串
                    String ascii = new String(data, StandardCharsets.UTF_8).trim();
                    String hex = bytesToHex(data);

                    String cardNumber;
                    if (ascii.matches("[0-9A-Za-z]+")) {
                        cardNumber = ascii;
                    } else {
                        cardNumber = hex;
                    }

                    Log.i(TAG, "收到刷卡数据:" + cardNumber + " 来自 " + senderIP);

                    // 更新 ViewModel

                    runOnUiThread(() -> {
                        cardViewModel.setCardNumber(cardNumber);

                        if (!isReadCardVisible) {
                            bottomNav.setSelectedItemId(R.id.nav_read_card);
                            switchFragment(TAG_READ);
                        }
                    });
                }
                udpSocket.close();
            } catch (Exception e) {
                Log.e(TAG, "UDP监听异常", e);
            }
        }).start();
//        new Thread(() -> {
//            try {
//                udpSocket = new DatagramSocket(port, InetAddress.getByName("0.0.0.0"));
//                byte[] buffer = new byte[1024];
//                while (isListening) {
//                    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
//                    udpSocket.receive(packet);
//
//                    String rawData = new String(packet.getData(), 0, packet.getLength()).trim();
//                    String cardNumber = decodeCardData(rawData);
//
//                    runOnUiThread(() -> {
//                        cardViewModel.setCardNumber(cardNumber);
//
//                        // 仅当不在刷卡界面时才跳转
//                        if (!isReadCardVisible) {
//                            bottomNav.setSelectedItemId(R.id.nav_read_card);
//                            switchFragment(TAG_READ);
//                        }
//                    });
//                }
//            } catch (Exception e) {
//                e.printStackTrace();
//            }
//        }).start();
    }
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }
相关推荐
海琴烟Sunshine1 小时前
leetcode 383. 赎金信 python
python·算法·leetcode
2501_916007472 小时前
iOS性能调试工具终极指南,从系统底层到多端协同的全方位优化实践(2025版)
android·ios·小程序·https·uni-app·iphone·webview
2501_915921432 小时前
iOS崩溃日志深度分析与工具组合实战,从符号化到自动化诊断的完整体系
android·ios·小程序·uni-app·自动化·cocoa·iphone
无糖冰可乐213 小时前
IDEA多java版本切换
java·ide·intellij-idea
合作小小程序员小小店3 小时前
web开发,在线%超市销售%管理系统,基于idea,html,jsp,java,ssh,sql server数据库。
java·前端·sqlserver·ssh·intellij-idea
brucelee1863 小时前
IntelliJ IDEA 设置 Local History 永久保留
java·ide·intellij-idea
执念WRD5 小时前
熊海CMS v1.0代码审计实战
android·nginx·安全·web安全·网络安全·系统安全
jllllyuz5 小时前
基于ThinkPHP实现动态ZIP压缩包的生成
android
Pluto_CSND5 小时前
Java中的静态代理与动态代理(Proxy.newProxyInstance)
java·开发语言