跳转至

接口调用使用指南

1.概述

本文档介绍 WECON Android SDK 中的核心接口类及其调用方式。
主要内容涵盖 Beep 控制类SocketCan 通信类SerialHelper 串口类ComBean 数据结构类ByteUtil 工具类的使用方法与示例。

2.Beep 类方法详解

2.1 类简介

Beep 类用于控制设备蜂鸣器的发声行为,提供启用、关闭、设置频率、设置音量及测试功能。
所有方法均通过 JNI 调用底层 scl 库实现,因此调用前需确保本地库已正确加载,并且应用具备系统签名权限。

2.2 类定义

public class Beep {
    static {
        System.loadLibrary("scl"); // 加载本地库
    }
    // 启用蜂鸣器 (PWM)
    public native int enable();
    // 禁用蜂鸣器 (PWM)
    public native int disable();
    // 设置蜂鸣器频率
    public native int setFrequency(int frequency);
    // 设置蜂鸣器音量(占空比)
    public native int setBeeperVolume(float volume);
    // 测试蜂鸣器:响 1 秒然后关闭
    public native int beepTest();
}

2.3 方法说明

enable

定义:public native int enable();

功能说明:启动蜂鸣器,使用 PWM(脉宽调制)方式控制蜂鸣器发声。

参数:无

返回值:0 表示成功,其他值表示错误。

注意事项:调用该方法后,蜂鸣器将进入启用状态,可通过 setFrequency()setBeeperVolume() 调整发声效果。

disable

定义:public native int disable();

功能说明:停止蜂鸣器,关闭 PWM 输出,蜂鸣器不再发声。

参数:无

返回值:0 表示成功,其他值表示错误。

setFrequency

定义:public native int setFrequency(int frequency);

功能说明:设置蜂鸣器的输出频率,单位为 Hz。频率越高,声音越尖锐。
有效范围为 20 Hz ~ 20000 Hz。

参数:【IN】frequency — 整数值,表示期望设置的蜂鸣器发声频率。

返回值:0 表示成功,其他值表示错误。

注意事项:若频率超出有效范围,蜂鸣器可能无声或发出异常噪音。

在调用 enable() 后设置才会生效。

setBeeperVolume

定义:public native int setBeeperVolume(float volume);

功能说明:设置蜂鸣器音量,通过调整 PWM 占空比实现。
音量范围为 0.0 ~ 1.0,值越大,蜂鸣器声音越响。

参数:【IN】volume — 浮点数,表示蜂鸣器音量值。

返回值:0 表示成功,其他值表示错误。

注意事项:建议音量值控制在 0.1 ~ 0.8 之间,避免功放饱和或设备过载。

可结合 setFrequency() 一同使用以达到最佳效果。

beepTest

定义:public native int beepTest();

功能说明: 执行蜂鸣器测试。

方法内部自动设置: 频率:5000 Hz 音量:0.5 持续时间:1 秒
测试结束后蜂鸣器自动关闭。

参数:无

返回值:0 表示成功,其他值表示错误。

用途:用于快速验证蜂鸣器硬件与 JNI 接口的工作状态。

2.4 案例

public class BeepDemo extends AppCompatActivity {

    private static final String TAG = "BeepDemo";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Beep beep = new Beep();

        // 设置蜂鸣器频率(例如 1000 Hz)
        int frequency = 1000;
        int resultFreq = beep.setFrequency(frequency);
        Log.d(TAG, "设置频率 " + frequency + " Hz 结果: " + resultFreq);

        // 设置蜂鸣器音量(例如 0.5)
        float volume = 0.5f;
        int resultVol = beep.setBeeperVolume(volume);
        Log.d(TAG, "设置音量 " + volume + " 结果: " + resultVol);

        // 启用蜂鸣器
        int resultEnable = beep.enable();
        Log.d(TAG, "启用蜂鸣器 结果: " + resultEnable);

        // 2 秒后关闭蜂鸣器
        new android.os.Handler().postDelayed(() -> {
            int resultDisable = beep.disable();
            Log.d(TAG, "停止蜂鸣器 结果: " + resultDisable);
        }, 2000);
    }
}

3.SocketCan 类方法详解

3.1 类简介

SocketCan 类用于在 Android 系统中实现 CAN 总线通信功能。
通过调用 JNI 层的 scl 库接口,该类可完成 CAN 接口的 打开、关闭、波特率设置、数据发送与接收 等操作。
该类适用于 CAN 设备的驱动调试、通信测试与上层业务逻辑开发。

3.2 类定义

public class SocketCan {

    // 新增的 CanFrame 类,用于在 Java 和 Native 之间传递结构化数据
    public static class CanFrame {
        public int id;      // CAN ID (Standard 11-bit or Extended 29-bit, masked)
        public int len;     // Data Length (0-8 for Classic, 0-64 for FD, up to 2048 for XL)
        public byte[] data; // Data payload
        public int type;    // FrameType: 0=CLASSIC, 1=FD, 2=XL
        public int flags;   // Flags: RTR, BRS, ESI, EFF, etc. (mapped from linux/can.h constants)

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("ID=0x").append(Integer.toHexString(id)).append(", ");
            sb.append("Len=").append(len).append(", ");
            sb.append("Type=");
            switch (type) {
                case 0: sb.append("CLASSIC"); break;
                case 1: sb.append("FD"); break;
                case 2: sb.append("XL"); break;
                default: sb.append("UNKNOWN"); break;
            }
            sb.append(", Flags=0x").append(Integer.toHexString(flags)).append(", ");
            sb.append("Data=[");
            if (data != null) {
                for (byte b : data) {
                    sb.append(String.format("%02X ", b));
                }
            }
            sb.append("]");
            return sb.toString();
        }
    }

    // 加载本地库
    static {
        System.loadLibrary("scl"); // 加载本地库
    }

    // 打开 CAN 接口
    public native int openCan(String interfaceName);

    // 关闭 CAN 接口
    public native int closeCan(String interfaceName);

    // 获取 CAN 接口的状态
    public native int getCanStatus(String interfaceName);

    // 设置 CAN 接口的波特率
    public native int setCanBitrate(String interfaceName, int bitrate);

    // 从 CAN 接口读取数据 (保持不变,只触发读取和入队)
    public native int canRead(String interfaceName, int timeoutMs);

    // 获取 CAN 帧数据 (修改返回类型为 CanFrame[])
    public native CanFrame[] getCanFrames();

    // 发送数据到 CAN 接口 (修改输入类型为 CanFrame)
    public native int canWrite(String interfaceName, CanFrame frame);

    // 复位 CAN 接口
    public native int resetCan(String interfaceName);
}

3.3 方法说明

CanFrame

该内部类用于在 Java 和 Native 层之间传输结构化的 CAN 帧数据。

字段名 类型 说明
id int CAN ID (标准或扩展)
len int 数据长度 (经典 CAN 0-8,CAN FD/XL 根据配置)
data byte[] 数据载荷
type int 帧类型:0=CLASSIC, 1=FD, 2=XL
flags int 帧标志位 (如 RTR, BRS, ESI, EFF 等)

openCan

定义:public native int openCan(String interfaceName);

功能说明:打开指定的 CAN 接口,同时初始化与该接口关联的接收与发送 Socket。
打开后,系统将建立全局 CAN 通信上下文。

参数:【IN】interfaceName — 字符串类型,指定 CAN 接口名称(如 "can0")。

返回值:0 表示成功;其他返回值表示错误。

注意事项:调用前应先设置波特率。

若接口已打开,重复调用可能返回错误。

closeCan

定义:public native int closeCan(String interfaceName);

功能说明: 关闭指定的 CAN 接口,并去初始化相关的发送与接收 Socket。
执行此操作会影响全局 CAN 通信状态。

参数:【IN】interfaceName — 字符串类型,指定 CAN 接口名称。

返回值:0 表示成功;其他返回值表示错误。

注意事项:关闭后,所有未读取的数据将被清空。

setCanBitrate

定义:public native int setCanBitrate(String interfaceName, int bitrate);

功能说明:修改指定 CAN 接口的波特率。
必须在 打开接口前 调用;若在接口已打开时调用,可能导致配置失败或通信异常。

参数:【IN】interfaceName — 字符串类型,指定 CAN 接口名称(如 "can0")。

【IN】bitrate — 整数值,设置的波特率(单位:bps),如 250000

返回值: 0 表示成功;其他返回值表示错误。

getCanStatus

定义:public native int getCanStatus(String interfaceName);

功能说明:获取当前指定 CAN 接口的状态。

参数:【IN】interfaceName — 字符串类型,指定 CAN 接口名称。

返回值:0:接口已关闭

1:接口已打开

其他值:表示错误或未知状态

canRead

定义:public native int canRead(String interfaceName, int timeoutMs);

功能说明:从指定的 CAN 接口读取数据帧并存入内部接收队列中。
该方法为阻塞调用,可设置超时时间。

参数:【IN】interfaceName — 字符串类型,指定 CAN 接口名称。

【IN】timeoutMs — 整数值,读取超时时间(单位:毫秒)。

返回值:0 表示读取成功;其他返回值表示错误或超时。

注意事项:读取的数据不会直接返回,而是保存在内部队列中,需通过 getCanFrames() 获取。

getCanFrames

定义:public native CanFrame[] getCanFrames();

说明:从 Native 层的队列中获取所有已读取并排队等待处理的 CAN 帧数据

参数:无

返回值:返回一个 SocketCan.CanFrame 对象的数组,每个对象表示一个完整的 CAN 帧;如果队列为空,返回 null 或空数组

canWrite

定义:public native int canWrite(String interfaceName, CanFrame frame);

说明:发送结构化的 CAN 帧数据至 CAN 接口中

参数:【IN】interfaceName - 字符串类型,指定 can 接口名称

【IN】frame - SocketCan.CanFrame 类型,包含要发送的 ID、数据、长度、类型和标志位等信息

返回值:返回0表示成功,其他返回值表示错误。

resetCan

定义:public native int resetCan(String interfaceName);

说明:复位指定的 can 接口

参数:【IN】interfaceName - 字符串类型,指定 can 接口名称

返回值:返回0表示成功,其他返回值表示错误

3.4 案例

public class CanDemo extends AppCompatActivity {
    private static final String TAG = "CanDemo"; // 日志标签
    private SocketCan socketCan; // CAN接口操作类
    private Thread readThread; // 读取CAN帧的线程
    private volatile boolean isReading = true; // 控制读取线程的标志
    private String canInterface = "can0"; // 使用的CAN接口名称

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 假设这里设置了布局,如果需要
        // setContentView(R.layout.activity_main);
        initCanInterface(); // 初始化CAN接口
    }

    /**
     * 将十六进制字符串转换为字节数组。例如 "123456" -> {0x12, 0x34, 0x56}
     */
    private byte[] hexStringToByteArray(String s) {
        if (s == null || s.isEmpty()) return new byte[0];
        int len = s.length();
        if (len % 2 != 0) {
            // 如果长度不是偶数,可能需要补零,但为了安全起见,这里抛出异常或处理
            Log.w(TAG, "Hex string length is odd. Padding required or error.");
            return new byte[0];
        }
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * 初始化CAN接口
     */
    private void initCanInterface() {
        socketCan = new SocketCan();

        // 如果CAN接口已打开,先关闭以确保干净状态
        if (socketCan.getCanStatus(canInterface) == 0) {
            socketCan.closeCan(canInterface);
            Log.d(TAG, "CAN接口已关闭");
        }

        // 设置CAN波特率
        if (socketCan.setCanBitrate(canInterface, 250000) != 0) {
            Log.e(TAG, "CAN波特率设置失败");
            return;
        }
        Log.d(TAG, "CAN波特率设置成功");

        // 打开CAN接口
        if (socketCan.openCan(canInterface) != 0) {
            Log.e(TAG, "CAN接口打开失败: " + canInterface);
            return;
        }
        Log.d(TAG, "CAN接口打开成功: " + canInterface);

        // 发送测试CAN帧 (ID: 0x123, Data: 0x12, 0x34)
        sendCanFrame("123", "1234");

        // 启动读取线程
        startReading();
    }

    /**
     * 发送CAN帧
     *
     * @param canIdHexStr CAN帧ID (十六进制字符串, 例如 "123")
     * @param dataHexStr  CAN帧数据 (十六进制字符串, 例如 "1234")
     */
    private void sendCanFrame(String canIdHexStr, String dataHexStr) {
        try {
            int id = Integer.parseInt(canIdHexStr, 16);
            byte[] data = hexStringToByteArray(dataHexStr);

            SocketCan.CanFrame frame = new SocketCan.CanFrame();
            frame.id = id;
            frame.len = data.length;
            frame.data = data;
            frame.type = 0; // CLASSIC CAN
            frame.flags = 0; // 默认标志位

            int result = socketCan.canWrite(canInterface, frame);

            if (result != 0) {
                Log.e(TAG, "发送CAN帧失败, 错误码: " + result);
            } else {
                Log.d(TAG, "发送CAN帧成功: ID=0x" + canIdHexStr + ", Data=" + dataHexStr);
            }
        } catch (NumberFormatException e) {
            Log.e(TAG, "发送CAN帧失败: ID或数据格式错误", e);
        }
    }

    /**
     * 启动读取线程,持续读取CAN帧
     */
    private void startReading() {
        isReading = true;
        readThread = new Thread(() -> {
            while (isReading) {
                try {
                    // 1. 读取CAN帧到队列 (Native侧)
                    int readResult = socketCan.canRead(canInterface, 1000);
                    if (readResult < 0) { // Native返回错误码通常为负值
                        Log.e(TAG, "读取CAN帧失败,错误码:" + readResult);
                        Thread.sleep(100); // 避免失败时CPU空转
                        continue;
                    }

                    // 2. 从队列中获取 CANFrame 对象数组
                    SocketCan.CanFrame[] frames = socketCan.getCanFrames();

                    if (frames != null && frames.length > 0) {
                        for (SocketCan.CanFrame frame : frames) {
                            // 利用 CanFrame 自身的 toString 方法打印结构化数据
                            Log.d(TAG, "收到CAN帧: " + frame.toString());
                        }
                    } else {
                        // 如果 readResult >= 0 但 frames 为空,表示本次读取无数据
                        Thread.sleep(10); // 无数据时短暂休眠
                    }
                } catch (Exception e) {
                    // 捕获 InterruptedException 或其他运行时异常
                    if (isReading) {
                        Log.e(TAG, "读取CAN帧错误: " + e.getMessage());
                    }
                }
            }
            Log.d(TAG, "CAN读取线程退出.");
        });
        readThread.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        isReading = false; // 停止读取线程
        if (readThread != null) {
            try {
                // 设置较短超时,防止应用退出卡死
                readThread.join(500);
            } catch (InterruptedException e) {
                Log.e(TAG, "停止读取线程失败");
                Thread.currentThread().interrupt();
            }
        }
        if (socketCan != null) {
            socketCan.closeCan(canInterface); // 关闭CAN接口
            Log.d(TAG, "CAN接口已关闭");
        }
    }
}

4.SerialHelper 类方法详解

4.1 类简介

包名:com.android.scl.SerialPort.tp.xmaihh.serialport.SerialHelper

功能概述: SerialHelper 是底层串口通信的封装类,提供串口的 打开、关闭、发送、接收及配置 等功能。
通过继承该类并重写 onDataReceived() 方法,可实现接收数据的回调处理。
适用于 RS232、RS485 等多种串口通信场景。

4.2 类定义

public abstract class SerialHelper {
    public SerialHelper(String port, int baudRate);
    public void open();
    public void close();
    public void send(byte[] data);
    public void setDataBits(int dataBits);
    public void setStopBits(int stopBits);
    public void setParity(int parity);
    public void setMode(int mode);
    protected abstract void onDataReceived(ComBean comBean);
}

4.3 串口模式接口定义

4.3.1RS-232 模式

逻辑设备路径 通信标准 连接器名称 信号线名称 引脚编号 描述
/dev/ttyS2 RS-232 CON4 RX 2 接收数据 (Receive Data)
/dev/ttyS2 RS-232 CON4 TX 3 发送数据 (Transmit Data)
/dev/ttyS3 RS-232 CON3 RX 2 接收数据 (Receive Data)
/dev/ttyS3 RS-232 CON3 TX 3 发送数据 (Transmit Data)

4.3.2RS-485 模式

逻辑设备路径 通信标准 连接器名称 信号线名称 引脚编号 描述
/dev/ttyS4 RS-485 CON4 A (D+) 1 差分信号 A (Data Positive)
/dev/ttyS4 RS-485 CON4 B (D-) 6 差分信号 B (Data Negative)

4.3.3RS-422/RS-485 复用模式

该接口 /dev/ttyS7 支持 RS-422 或 RS-485 两种模式,通过 CON3 连接器引出。

4.3.3.1RS-422 模式 (全双工)
逻辑设备路径 通信标准 连接器名称 信号线名称 引脚编号 描述
/dev/ttyS7 RS-422 CON3 TA (TX+) 1 发送数据差分信号 A (Transmit Positive)
/dev/ttyS7 RS-422 CON3 TB (TX-) 6 发送数据差分信号 B (Transmit Negative)
/dev/ttyS7 RS-422 CON3 RA (RX+) 9 接收数据差分信号 A (Receive Positive)
/dev/ttyS7 RS-422 CON3 RB (RX-) 8 接收数据差分信号 B (Receive Negative)
4.3.3.2RS-485 模式 (半双工)

在 RS-485 模式下,通常使用 TA 和 TB 引脚作为 A 和 B 信号线,实现发送和接收的共用。

逻辑设备路径 通信标准 连接器名称 信号线名称 引脚编号 描述
/dev/ttyS7 RS-485 CON3 A (D+) 1 (TA) 差分信号 A (数据正)
/dev/ttyS7 RS-485 CON3 B (D-) 6 (TB) 差分信号 B (数据负)

4.4 SerialHelper 类常量(工作模式和物理模式)

常量名称 说明
MODE_ASYNC 0 异步工作模式:默认模式,启动后台读取线程 ReadThread 自动接收数据,通过 onDataReceived 回调。
MODE_SYNC 1 同步工作模式:不启动后台读取线程,需要手动调用 sendAndReceiveSync() 方法进行阻塞式发送和接收。
MODE_RS232 0 物理层模式:RS232 模式。
MODE_RS485 1 物理层模式:RS485 模式。
MODE_RS422 2 物理层模式:RS422 模式。

4.5 方法说明

SerialHelper

定义:SerialHelper(String port, int baudRate, int workMode)

说明: 使用指定串口路径与波特率创建串口助手实例。
创建后可调用 open() 打开串口,并可重写 onDataReceived() 实现数据回调。

参数:【IN】port — 字符串,串口设备路径(如 /dev/ttyS7

【IN】baudRate — 整数值,波特率(如 115200

【IN】workMode - 整数值,指定工作模式。可使用常量 SerialHelper.MODE_ASYNC (异步模式,使用回调) 或 SerialHelper.MODE_SYNC (同步模式,使用 readBlock)

返回值:无

注意事项:实例化后若不调用 open(),串口不会建立通信。

建议在主线程之外进行打开操作,以避免阻塞。

open

定义:public void open()

说明:打开串口,建立读写线程并开始监听接收数据。
必须在配置参数(波特率、数据位、校验位等)设置完成后调用。

参数:无

返回值:无

close

定义:public void close()

说明:关闭串口通信,并释放底层读写线程及相关资源。

参数:无

返回值:无

send

定义:public void send(byte[] data)

说明:向当前打开的串口发送一组字节数据。

参数:【IN】data — 字节数组,待发送的数据内容。

返回值:无

注意事项:串口必须已打开,否则发送无效。

建议在发送前对数据进行校验。

sendHex

定义:public void sendHex(String sHex)

说明:向串口发送十六进制字符串数据。数据会被自动转换为字节数组发送

参数:【IN】sHex - 十六进制字符串(如 "AABBCCDD")

返回值:无

sendTxt

定义:public void sendTxt(String sTxt)

说明:向串口发送文本字符串数据(使用系统默认编码)

参数:【IN】sTxt - 需发送的文本内容

返回值:无

setDataBits

定义:public void setDataBits(int dataBits)

说明:设置串口通信的数据位长度。

参数:【IN】dataBits — 整数值,常用值为 78

返回值:无

setStopBits

定义:public void setStopBits(int stopBits)

说明:设置串口的停止位长度。

参数:【IN】stopBits — 整数值,常用值为 12

返回值: 无

setParity

定义:public void setParity(int parity)

说明:设置串口的校验位类型。

参数:【IN】parity — 整数值:

0 = 无校验

1 = 奇校验

2 = 偶校验

返回值:无

setMode

定义:public void setMode(int mode)

说明:设置串口的工作模式。
常用于区分 RS232 / RS485 通信方式。

参数:【IN】mode — 整数常量,如 SerialHelper.MODE_RS485

返回值:无

onDataReceived

说明: 当串口接收到数据时自动触发回调,可在此方法中处理接收内容。

参数:【IN】comBean — 接收到的数据封装对象(详见 4.2 节)。

返回值:无

readBlock

定义:public byte[] readBlock(int timeoutMs)

说明:同步模式 (MODE_SYNC) 下专用的方法。线程将阻塞等待数据到达,并在发现数据后立即返回当前缓冲区中所有可用的字节数据。

参数:【IN】timeoutMs - 阻塞等待初始数据的最大时间(毫秒)。

返回值:byte[] - 读取到的数据块;如果在超时时间内未收到数据或发生错误则返回 null。

clearInputStream

定义:public void clearInputStream()

说明:串口输入缓冲区中所有当前可用的残留字节数据。在 MODE_SYNC 模式下发送请求前调用,可以避免收到上次通信遗留的“脏数据”。

返回值:无

4.5 案例

package com.example.weconappdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import com.android.scl.SerialPort.tp.xmaihh.serialport.SerialHelper;
import com.android.scl.SerialPort.tp.xmaihh.serialport.bean.ComBean;
import com.android.scl.SerialPort.tp.xmaihh.serialport.utils.ByteUtil;

import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class RS485Demo extends AppCompatActivity {

    private static final String TAG = "RS485Demo";

    // 使用固定线程池处理 Master 的同步阻塞调用和 Slave 的异步响应逻辑
    private final ExecutorService executorService = Executors.newFixedThreadPool(2);
    private Handler mainHandler;

    private SerialHelper masterHelper;
    private SerialHelper slaveHelper;

    private byte[] masterSendData;
    private final AtomicInteger testStep = new AtomicInteger(0);

    // 强制 Slave 在同步测试 (Step 1) 中分批发送时引入的延迟
    private static final int SLAVE_RESPONSE_DELAY_MS = 150;

    // 用于异步模式下,主线程等待回调结果的同步锁 (仅用于测试)
    private final Object asyncLock = new Object();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainHandler = new Handler(Looper.getMainLooper());

        Log.i(TAG, "Starting RS485 Low-Level Sync/Async Test.");
        mainHandler.post(this::startNextTest);
    }

    /**
     * 控制测试流程的步骤
     */
    private void startNextTest() {
        int currentStep = testStep.incrementAndGet();
        Log.d(TAG, "\n==========================================================");
        Log.d(TAG, String.format("======== Starting Test Step %d ========", currentStep));
        Log.d(TAG, "==========================================================");

        closeSerialPorts(); // 确保关闭旧端口

        if (currentStep == 1) {
            // Step 1: 低级同步接口测试 (readAvailableSync) - 验证外部多次读取
            initSerialPorts(SerialHelper.MODE_SYNC, currentStep);
            sendTestDataSync_LowLevel();
        } else if (currentStep == 2) {
            // Step 2: 异步模式测试 (原库功能验证)
            initSerialPorts(SerialHelper.MODE_ASYNC, currentStep);
            sendTestDataAsync();
        } else {
            Log.i(TAG, "All tests finished.");
            executorService.shutdownNow();
        }
    }


    /**
     * 初始化串口:Master 侧配置为同步或异步模式
     * @param masterMode Master的工作模式
     * @param currentTestStep 当前测试步骤 (用于控制 Slave 的行为)
     */
    private void initSerialPorts(int masterMode, int currentTestStep) {
        try {
            String masterPath = "/dev/ttyS7";
            String slavePath = "/dev/ttyS4";
            int baudRate = 115200;

            // ---------- 主站 (Master) ----------
            masterHelper = new SerialHelper(masterPath, baudRate, masterMode) {
                @Override
                protected void onDataReceived(ComBean comBean) {
                    if (getCurrentWorkMode() == MODE_ASYNC) {
                        handleMasterAsyncReceive(comBean.bRec);
                    } else {
                        Log.e(TAG, "Master (SYNC Mode) unexpectedly received callback data!");
                    }
                }
            };
            configSerialHelper(masterHelper);
            masterHelper.open();

            // ---------- 从站 (Slave): 始终使用异步模式,并根据测试步骤实现延迟响应 ----------
            slaveHelper = new SerialHelper(slavePath, baudRate, SerialHelper.MODE_ASYNC) {
                @Override
                protected void onDataReceived(ComBean comBean) {
                    final byte[] data = comBean.bRec;
                    Log.d(TAG, String.format("Slave received request (Len: %d). Preparing response...", data.length));

                    executorService.submit(() -> {
                        try {
                            if (data.length < 10 || currentTestStep != 1) {
                                // Step 2 (ASYNC) 及其它场景:立即回发完整数据,确保粘包助手能正常工作
                                slaveHelper.send(data);
                                Log.d(TAG, "Slave responded (single packet).");
                                return;
                            }

                            // **Step 1 (Low-Level Sync Test): 强制分批延迟发送**
                            int mid = data.length / 2;
                            byte[] part1 = Arrays.copyOfRange(data, 0, mid);
                            byte[] part2 = Arrays.copyOfRange(data, mid, data.length);

                            // 1. 发送第一部分
                            slaveHelper.send(part1);
                            Log.d(TAG, String.format("Slave sent PART 1 (Len: %d).", part1.length));

                            // 2. 强制引入延迟 (150ms)
                            Thread.sleep(SLAVE_RESPONSE_DELAY_MS);

                            // 3. 发送第二部分
                            slaveHelper.send(part2);
                            Log.d(TAG, String.format("Slave sent PART 2 (Len: %d) after %dms delay.", part2.length, SLAVE_RESPONSE_DELAY_MS));

                        } catch (Exception e) {
                            Log.e(TAG, "Slave response failed during send.", e);
                        }
                    });
                }
            };
            configSerialHelper(slaveHelper);
            slaveHelper.open();

            Log.i(TAG, "Serial ports initialized. Master Mode: " + (masterMode == SerialHelper.MODE_SYNC ? "SYNC" : "ASYNC"));

        } catch (Exception e) {
            Log.e(TAG, "Serial port initialization failed.", e);
            mainHandler.postDelayed(this::startNextTest, 100);
        }
    }

    private void configSerialHelper(SerialHelper helper) {
        helper.setDataBits(8);
        helper.setStopBits(1);
        helper.setParity(0);
        helper.setMode(SerialHelper.MODE_RS485);
    }

    // ====================================================================
    // Step 1: 低级同步测试 (readAvailableSync)
    // 验证:外部循环调用 readAvailableSync (原 readBlock) 组装分批数据
    // ====================================================================
    private void sendTestDataSync_LowLevel() {
        executorService.submit(() -> {
            try {
                // 准备长数据,用于分批测试
                String baseData = "L_SYNC_TEST_";
                String padding = new String(new char[60]).replace('\0', 'C');
                masterSendData = (baseData + testStep.get() + padding).getBytes();

                int totalExpectedLength = masterSendData.length;
                int totalTimeoutMs = 800;
                int readChunkTimeout = 200; // 每次 readAvailableSync 阻塞等待的最大时间

                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                long startTime = System.currentTimeMillis();

                Log.d(TAG, String.format("Master (Low-Level): Sending data (Len: %d)", totalExpectedLength));
                masterHelper.send(masterSendData);
                masterHelper.clearInputStream(); // 确保发送后清空缓冲区

                // 核心逻辑:外部循环调用 readBlock
                while (buffer.size() < totalExpectedLength && (System.currentTimeMillis() - startTime) < totalTimeoutMs) {

                    // 调用低级接口:阻塞等待数据块
                    byte[] chunk = masterHelper.readBlock(readChunkTimeout);

                    if (chunk != null && chunk.length > 0) {
                        buffer.write(chunk);
                        Log.d(TAG, String.format("Master (Low-Level): Read chunk (Len: %d). Total read: %d", chunk.length, buffer.size()));
                    } else {
                        // 如果 readBlock 返回 null,说明 readChunkTimeout 到了,但没数据,继续检查总超时
                        Log.d(TAG, "Master (Low-Level): Read chunk timed out, attempting again.");
                    }
                }

                // 3. 处理结果
                byte[] receivedData = buffer.toByteArray();
                long totalTime = System.currentTimeMillis() - startTime;

                if (receivedData.length == totalExpectedLength) {
                    boolean success = compareData(masterSendData, receivedData);
                    Log.i(TAG, success ? "Low-Level Test SUCCESS! (External loop assembly)." : "Low-Level Test FAILED: Data mismatch.");
                } else {
                    Log.e(TAG, String.format("Low-Level Test FAILED: Length mismatch. Received %d/%d bytes. Time: %dms",
                            receivedData.length, totalExpectedLength, totalTime));
                }

            } catch (Exception e) {
                Log.e(TAG, "Master (Low-Level): Test failed.", e);
            } finally {
                mainHandler.postDelayed(this::startNextTest, 500);
            }
        });
    }


    // ====================================================================
    // Step 2: 异步测试 (ASYNC)
    // ====================================================================
    private void sendTestDataAsync() {
        try {
            String dataString = "ASYNC_TEST_REQ_" + testStep.get() + new String(new char[10]).replace('\0', 'B');
            masterSendData = dataString.getBytes();

            Log.d(TAG, "Master (ASYNC): Sending data: " + ByteUtil.ByteArrToHex(masterSendData));

            masterHelper.send(masterSendData);

            // 模拟等待回调的场景(测试代码专有)
            synchronized (asyncLock) {
                try {
                    asyncLock.wait(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    Log.w(TAG, "Slave delay task was interrupted during test teardown. Response aborted.");
                }
            }

        } catch (Exception e) {
            Log.e(TAG, "Master (ASYNC): Send failed.", e);
        } finally {
            mainHandler.post(this::startNextTest);
        }
    }

    /**
     * 处理主站在 ASYNC 模式下的接收回调
     */
    private void handleMasterAsyncReceive(byte[] receivedData) {
        Log.d(TAG, "Master (ASYNC): Received callback data: " + ByteUtil.ByteArrToHex(receivedData));

        // 验证异步模式下,粘包助手是否返回了完整的帧
        boolean success = compareData(masterSendData, receivedData);
        Log.i(TAG, success ? "ASYNC Test SUCCESS!" : "ASYNC Test FAILED: Data mismatch.");

        synchronized (asyncLock) {
            asyncLock.notifyAll();
        }
    }

    // ====================================================================
    // 通用工具方法
    // ====================================================================

    private boolean compareData(byte[] send, byte[] recv) {
        if (send == null || recv == null || send.length != recv.length) {
            Log.e(TAG, String.format("Compare Failed: Length mismatch. Sent: %d, Received: %d",
                    (send != null ? send.length : 0), (recv != null ? recv.length : 0)));
            return false;
        }
        return Arrays.equals(send, recv);
    }

    private void closeSerialPorts() {
        try {
            if (masterHelper != null) masterHelper.close();
            if (slaveHelper != null) slaveHelper.close();
            Log.i(TAG, "Serial ports closed.");
        } catch (Exception e) {
            Log.e(TAG, "Failed to close serial ports.", e);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        closeSerialPorts();
        executorService.shutdownNow();
    }
}

5.ComBean 类方法详解

5.1 类简介

包名:com.android.scl.SerialPort.tp.xmaihh.serialport.bean.ComBean

功能说明: ComBean 是串口接收数据的封装类,用于在 SerialHelper.onDataReceived() 回调中传递接收的数据内容及相关信息。
开发者可以通过该类直接访问接收到的原始字节流,用于进一步解析或显示。

5.2 类定义

public class ComBean {
    public byte[] bRec; // 接收到的原始字节数据
}

5.3 字段说明

字段名 类型 说明
bRec byte[] 接收到的原始字节数据内容,用于保存串口设备实际返回的数据。

5.4 用途

SerialHelperonDataReceived(ComBean comBean) 回调中,可以通过访问 comBean.bRec 获取设备返回的数据;

可结合 ByteUtil.ByteArrToHex() 将字节流转换为可读的十六进制字符串,用于日志打印或调试显示。

5.5 示例代码

@Override
protected void onDataReceived(ComBean comBean) {
    String hexData = ByteUtil.ByteArrToHex(comBean.bRec);
    Log.d("ComBean", "接收到的数据:" + hexData);
}

6.ByteUtil 类方法详解

6.1 类简介

包名:com.android.scl.SerialPort.tp.xmaihh.serialport.utils.ByteUtil

功能说明: ByteUtil 是用于字节数组与十六进制字符串相互转换的工具类。
常用于串口调试、日志输出及通信数据可视化。

6.2 类定义

public class ByteUtil {
    public static String ByteArrToHex(byte[] bytes);
}

6.3 方法说明

ByteArrToHex

定义:public static String ByteArrToHex(byte[] bytes)

说明: 将字节数组转换为十六进制字符串,可直接用于调试日志或数据输出。
转换后的格式为连续的十六进制字符或带空格分隔的可读字符串(如 "01 03 00 00 00 02")。

参数:【IN】bytes — 字节数组,表示需要转换的原始数据。

返回值: 转换后的十六进制字符串。

6.4 示例代码

byte[] rawData = new byte[]{0x01, 0x03, 0x00, 0x00, 0x00, 0x02};
String hexString = ByteUtil.ByteArrToHex(rawData);
Log.d("ByteUtil", "转换结果:" + hexString);

输出示例:

转换结果01 03 00 00 00 02