[
  {
    "path": ".github/ISSUE_TEMPLATE/提交bug.md",
    "content": "---\nname: 提交BUG\nabout: Create a report to help us improve\ntitle: 建议\nlabels: ''\nassignees: ''\n\n---\n\n## 【警告：请务必按照 issue 模板填写】\n\n## 问题描述\n\n* 框架版本【必填】：XXX\n\n* 问题描述【必填】：XXX\n\n* 复现步骤【必填】：XXX\n\n* 是否必现【必填】：填是/否\n\n* 出现问题机型信息【必填】：请填写出现问题的品牌和机型\n\n* 出现问题的安卓版本【必填】：请填写出现问题的 Android 版本\n\n## 其他\n\n* 提供报错堆栈（如果有报错的话必填，注意不要拿被混淆过的代码堆栈上来）\n\n* 提供截图或视频（根据需要提供，此项不强制）\n\n* 提供解决方案（如果已经解决了的话，此项不强制）\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n"
  },
  {
    "path": "README.md",
    "content": "# Android串口通信框架 SerialPort\n\n[中文](README.md) | [English](README_EN.md)\n\n[![Version](https://img.shields.io/badge/version-5.0.8-blue.svg)](https://github.com/cl-6666/serialPort)\n[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)\n[![License](https://img.shields.io/badge/license-Apache%202-green.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n> 一个灵活、高效并且轻量的Android串口通信框架，让串口操作变得简单易用。支持单串口、多串口、粘包处理、自定义配置等功能。\n\n<img src=\"https://github.com/cl-6666/serialPort/blob/master/img/multiple_images.png\" width=\"650\" height=\"360\" alt=\"演示\"/>  \n\n## 📱 体验演示\n\n想要快速体验串口通信框架的强大功能？直接下载演示 APK 安装到您的 Android 设备上试试吧！\n\n<div align=\"center\">\n\n### 📥 [点击下载演示 APK](https://www.pgyer.com/XNzY)\n\n[![Download APK](https://img.shields.io/badge/Download-APK%20v5.0.8-brightgreen.svg?style=for-the-badge&logo=android)](https://www.pgyer.com/XNzY)\n\n**版本**: v5.0.8 | **大小**: ~7 MB | **API**: 21+ | **架构**: arm64-v8a, armeabi-v7a, x86, x86_64\n\n</div>\n\n### 演示 APK 功能\n\n- ✅ 单串口通信演示\n- ✅ 多串口管理演示\n- ✅ 粘包处理策略切换\n- ✅ 串口参数配置（数据位、校验位、停止位）\n- ✅ 实时数据收发测试\n- ✅ 十六进制/ASCII 数据显示\n- ✅ 性能测试与统计\n\n> **提示**: 演示 APK 需要在具有串口的 Android 设备上运行（如工控设备、开发板等）。如果您的设备没有串口，可以查看源码了解使用方法。\n\n## ⭐ 特性\n\n- 🚀 **简单易用** - 链式调用，一行代码完成配置\n- 🔧 **多串口支持** - 同时管理多个串口，独立配置\n- 📦 **智能粘包处理** - 支持多种粘包策略，可动态切换\n- ⚡ **高性能** - 多线程处理，线程安全设计\n- 🛡️ **稳定可靠** - 完善的错误处理和资源管理\n- 📝 **详细日志** - 丰富的调试信息，方便排查问题\n- 🎯 **灵活配置** - 支持数据位、校验位、停止位等参数配置\n- ✨ **Google Play 认证** - 支持 16KB 页面对齐，完全符合 Google Play 上架要求\n\n## 📖 版本说明\n\n- **当前版本**: 5.0.8 (推荐) - 全新架构，功能强大，支持 Google Play 16KB 页面对齐\n- **历史版本**: [4.1.1版本文档](README4.1.1.md) - 稳定版本\n\n### 5.0.8 版本更新 🔥 (2025-12-25)\n\n- ✅ **16KB 页面对齐**: 完全适配 Google Play 16KB 页面大小要求\n- ✅ **Android 15 支持**: 兼容最新 Android 15 系统\n- ✅ **原生库优化**: arm64-v8a 架构原生库已通过 Google Play 审核标准\n- ✅ **向后兼容**: 完全兼容旧版本 Android 设备，无需修改代码\n\n> **重要提示**: 从 2024 年开始，Google Play 要求所有 arm64-v8a 原生库必须支持 16KB 页面大小。5.0.8 版本已完全适配此要求，可放心上架 Google Play。\n\n\n## 🚀 快速开始\n\n### 依赖集成\n\n在项目的 `build.gradle` 中添加依赖：\n\n```gradle\ndependencies {\n   implementation 'com.github.cl-6666:serialPort:v5.0.8'\n}\n```\n\n在项目根目录的 `build.gradle` 中添加：\n\n```gradle\nallprojects {\n    repositories {\n        maven { url 'https://jitpack.io' }\n    }\n}\n```\n\n### 权限配置\n\n在 `AndroidManifest.xml` 中添加必要权限：\n\n```xml\n<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n```\n\n## 📚 使用指南\n\n### 1️⃣ 单串口使用 - 基础示例\n\n#### 最简单的使用方式\n\n```java\npublic class MainActivity extends AppCompatActivity {\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        \n        // 一行代码打开串口并接收数据\n        SimpleSerialPortManager.getInstance()\n            .openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n                String receivedData = new String(data);\n                Log.i(\"Serial\", \"收到数据: \" + receivedData);\n                // 处理接收到的数据\n            });\n    }\n    \n    // 发送数据\n    private void sendData() {\n        SimpleSerialPortManager.getInstance().sendData(\"Hello World\");\n    }\n    \n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // 关闭串口\n        SimpleSerialPortManager.getInstance().closeSerialPort();\n    }\n}\n```\n\n#### 完整配置示例\n\n```java\npublic class App extends Application {\n    \n    @Override\n    public void onCreate() {\n        super.onCreate();\n        \n        // 全局配置（可选）\n        new SimpleSerialPortManager.QuickConfig()\n            .setIntervalSleep(50)                    // 读取间隔50ms\n            .setEnableLog(true)                      // 启用日志\n            .setLogTag(\"SerialPortApp\")              // 设置日志标签\n            .setDatabits(8)                          // 数据位8\n            .setParity(0)                            // 无校验\n            .setStopbits(1)                          // 停止位1\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING)\n            .apply(this);\n    }\n}\n```\n\n### 2️⃣ 数据位、校验位、停止位配置\n\n```java\npublic class SerialConfigExample {\n    \n    public void configureSerialParams() {\n        SimpleSerialPortManager manager = SimpleSerialPortManager.getInstance();\n        \n        // 方式1：使用QuickConfig配置\n        new SimpleSerialPortManager.QuickConfig()\n            .setDatabits(8)        // 数据位：5, 6, 7, 8\n            .setParity(0)          // 校验位：0=无校验, 1=奇校验, 2=偶校验\n            .setStopbits(1)        // 停止位：1 或 2\n            .setFlags(0)           // 标志位\n            .apply(getApplication());\n        \n        // 方式2：动态设置\n        manager.setDatabits(8)     // 设置数据位\n               .setParity(2)       // 设置偶校验\n               .setStopbits(1)     // 设置停止位1\n               .setFlags(0);       // 设置标志位\n        \n        // 打开串口\n        manager.openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n            Log.i(\"Serial\", \"数据: \" + new String(data));\n        });\n    }\n    \n    // 常用配置组合\n    public void commonConfigurations() {\n        SimpleSerialPortManager manager = SimpleSerialPortManager.getInstance();\n        \n        // 标准配置 8N1 (8数据位, 无校验, 1停止位)\n        manager.setDatabits(8).setParity(0).setStopbits(1);\n        \n        // Modbus RTU 8E1 (8数据位, 偶校验, 1停止位) \n        manager.setDatabits(8).setParity(2).setStopbits(1);\n        \n        // 老式设备 7E2 (7数据位, 偶校验, 2停止位)\n        manager.setDatabits(7).setParity(2).setStopbits(2);\n    }\n}\n```\n\n### 3️⃣ 粘包处理详解\n\n粘包是串口通信中常见的问题，5.0.0版本提供了多种处理策略：\n\n```java\npublic class StickyPacketExample {\n    \n    public void noProcessing() {\n        // 策略1：不处理粘包 - 适用于简单数据流\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING)\n            .apply(this);\n    }\n    \n    public void delimiterBased() {\n        // 策略2：基于分隔符 - 适用于文本协议\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.DELIMITER_BASED)\n            .apply(this);\n        \n        // 自定义分隔符\n        SimpleSerialPortManager.getInstance()\n            .configureStickyPacket(SimpleSerialPortManager.StickyPacketStrategy.DELIMITER_BASED);\n    }\n    \n    public void fixedLength() {\n        // 策略3：固定长度 - 适用于固定长度协议\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.FIXED_LENGTH)\n            .apply(this);\n    }\n    \n    public void variableLength() {\n        // 策略4：可变长度 - 适用于带长度字段的协议\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.VARIABLE_LENGTH)\n            .apply(this);\n    }\n}\n```\n\n### 4️⃣ 多串口管理 - 强大功能\n\n```java\npublic class MultiSerialExample {\n    \n    public void basicMultiSerial() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // 串口1：GPS模块，不需要粘包处理\n        manager.openSerialPort(\"GPS\", \"/dev/ttyS1\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0)\n                .setStopbits(1)\n                .setStickyPacketHelpers(new BaseStickPackageHelper()) // 不处理粘包\n                .build(),\n            // 状态回调\n            (serialId, success, status) -> {\n                Log.i(\"GPS\", \"状态: \" + (success ? \"成功\" : \"失败\"));\n            },\n            // 数据回调\n            (serialId, data) -> {\n                String gpsData = new String(data);\n                Log.i(\"GPS\", \"数据: \" + gpsData);\n                handleGpsData(gpsData);\n            });\n        \n        // 串口2：传感器模块，需要换行符分包\n        manager.openSerialPort(\"SENSOR\", \"/dev/ttyS2\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0) \n                .setStopbits(1)\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\")) // 换行符分包\n                .build(),\n            null, // 不需要状态回调\n            (serialId, data) -> {\n                String sensorData = new String(data).trim();\n                Log.i(\"SENSOR\", \"数据: \" + sensorData);\n                handleSensorData(sensorData);\n            });\n        \n        // 发送数据到不同串口\n        manager.sendData(\"GPS\", \"AT+GPS?\\r\\n\");\n        manager.sendData(\"SENSOR\", \"READ_TEMP\\n\");\n    }\n    \n    // 动态管理串口\n    public void dynamicManagement() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // 查看串口状态\n        List<String> openedPorts = manager.getOpenedSerialPorts();\n        boolean isOpened = manager.isSerialPortOpened(\"GPS\");\n        manager.printAllSerialStatus();\n        \n        // 动态更新粘包策略\n        manager.updateStickyPacketHelpers(\"GPS\", \n            new AbsStickPackageHelper[]{new SpecifiedStickPackageHelper(\"\\r\\n\")});\n        \n        // 关闭特定串口\n        manager.closeSerialPort(\"GPS\");\n        \n        // 关闭所有串口\n        manager.closeAllSerialPorts();\n    }\n}\n```\n\n## 🎯 实际应用场景\n\n### 工业控制场景\n```java\npublic class IndustrialControlExample {\n    \n    public void setupIndustrialPorts() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // PLC通信 - Modbus RTU\n        manager.openSerialPort(\"PLC\", \"/dev/ttyS1\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8).setParity(2).setStopbits(1) // 8E1\n                .setStickyPacketHelpers(new StaticLenStickPackageHelper(8))\n                .build(),\n            null, this::handlePlcData);\n        \n        // 传感器数据采集 - 文本协议\n        manager.openSerialPort(\"SENSORS\", \"/dev/ttyS3\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(7).setParity(2).setStopbits(1) // 7E1\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null, this::handleSensorData);\n    }\n}\n```\n\n### 通信网关场景\n```java\npublic class GatewayExample {\n    \n    public void setupGateway() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // 上行通信（与服务器）\n        manager.openSerialPort(\"UPLINK\", \"/dev/ttyS1\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\"))\n                .build(),\n            null, this::handleUplinkData);\n        \n        // 下行设备1 - GPS\n        manager.openSerialPort(\"GPS\", \"/dev/ttyS2\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null, data -> forwardToUplink(\"GPS\", data));\n    }\n    \n    private void forwardToUplink(String deviceId, byte[] data) {\n        String message = String.format(\"[%s]%s\\n\", deviceId, new String(data));\n        SimpleSerialPortManager.multi().sendData(\"UPLINK\", message);\n    }\n}\n```\n\n## 🔧 高级功能\n\n### 日志系统\n```java\n// 启用详细日志\nSerialPortLogUtil.setDebugEnabled(true);\n\n// 自定义日志输出\nSerialPortLogUtil.i(\"MyTag\", \"自定义日志信息\");\nSerialPortLogUtil.printData(\"发送\", data); // 十六进制+ASCII显示\nSerialPortLogUtil.printSerialConfig(\"MySerial\", 8, 0, 1, 0); // 配置信息\n```\n\n### 错误处理\n```java\nmanager.openSerialPort(\"TEST\", \"/dev/ttyS1\", 9600,\n    (serialId, success, status) -> {\n        if (!success) {\n            switch (status) {\n                case NO_READ_WRITE_PERMISSION:\n                    Log.e(\"Serial\", \"权限不足\");\n                    break;\n                case OPEN_FAIL:\n                    Log.e(\"Serial\", \"打开失败\");\n                    break;\n            }\n        }\n    },\n    dataCallback);\n```\n\n## 🛠️ 故障排查\n\n### 常见问题\n\n1. **串口打开失败**\n   ```java\n   // 检查设备路径\n   String[] devices = new SerialPortFinder().getAllDevicesPath();\n   \n   // 检查权限\n   File deviceFile = new File(\"/dev/ttyS4\");\n   boolean canRead = deviceFile.canRead();\n   boolean canWrite = deviceFile.canWrite();\n   ```\n\n2. **数据接收不完整**\n   ```java\n   // 启用日志查看原始数据\n   SerialPortLogUtil.setDebugEnabled(true);\n   \n   // 尝试不同的粘包策略\n   manager.configureStickyPacket(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING);\n   ```\n\n## 📖 API参考\n\n### SimpleSerialPortManager (单串口)\n| 方法 | 说明 |\n|------|------|\n| `getInstance()` | 获取单例实例 |\n| `openSerialPort(path, baudRate, callback)` | 打开串口 |\n| `sendData(data)` | 发送数据 |\n| `closeSerialPort()` | 关闭串口 |\n| `setDatabits(databits)` | 设置数据位 |\n| `setParity(parity)` | 设置校验位 |\n| `setStopbits(stopbits)` | 设置停止位 |\n\n### MultiSerialPortManager (多串口)\n| 方法 | 说明 |\n|------|------|\n| `getInstance()` | 获取实例 |\n| `openSerialPort(id, path, baudRate, config, statusCallback, dataCallback)` | 打开串口 |\n| `sendData(serialId, data)` | 发送数据到指定串口 |\n| `closeSerialPort(serialId)` | 关闭指定串口 |\n| `closeAllSerialPorts()` | 关闭所有串口 |\n| `isSerialPortOpened(serialId)` | 检查串口状态 |\n\n## 🎯 版本迁移\n\n### 从4.1.1迁移到5.0.0\n\n**旧版本 (4.1.1)**:\n```java\n// 在Application中初始化\nSerialUtils.getInstance().init(this, true, \"TAG\", 50, 8, 0, 1);\n\n// 使用\nSerialUtils.getInstance().setmSerialPortDirectorListens(...);\nSerialUtils.getInstance().manyOpenSerialPort(list);\n```\n\n**新版本 (5.0.0)**:\n```java\n// 简化的初始化（可选）\nnew SimpleSerialPortManager.QuickConfig()\n    .setDatabits(8).setParity(0).setStopbits(1)\n    .apply(this);\n\n// 直接使用\nSimpleSerialPortManager.getInstance()\n    .openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n        // 处理数据\n    });\n```\n\n## 📞 联系我们\n\n- **QQ群**: 458173716\n- **博客**: https://blog.csdn.net/a214024475/article/details/113735085\n- **GitHub**: https://github.com/cl-6666/serialPort\n\n\n### PC端串口调试助手\n<img src=\"https://github.com/cl-6666/serialPort/blob/master/img/pc_ck.jpg\" width=\"440\" height=\"320\" alt=\"PC调试助手\"/>\n\n**下载链接**: https://pan.baidu.com/s/1DL2TOHz9bl9RIKIG3oCSWw?pwd=f7sh  \n\n### QQ技术交流群\n<img src=\"https://github.com/cl-6666/serialPort/blob/master/img/qq2.jpg\" width=\"350\" height=\"560\" alt=\"QQ群\"/>\n\n**QQ群号**: 458173716\n\n## 🔬 技术说明\n\n### 16KB 页面对齐适配 (v5.0.8)\n\n从 2024 年开始，Google Play 要求所有使用原生库（.so 文件）的应用必须支持 16KB 页面大小，以适配最新的 Android 设备。本库已完全适配此要求。\n\n#### 技术实现\n\n我们在 CMake 构建配置中针对 arm64-v8a 架构添加了以下链接器标志：\n\n```cmake\n# CMakeLists.txt\nif(ANDROID_ABI STREQUAL \"arm64-v8a\")\n    target_compile_options(SerialPort PRIVATE -fno-emulated-tls)\n    target_link_options(SerialPort PRIVATE \n        \"LINKER:-z,max-page-size=16384\"\n        \"LINKER:-z,common-page-size=16384\")\nendif()\n```\n\n#### 兼容性说明\n\n- ✅ **完全兼容**: 支持所有 Android 5.0+ (API 21+) 设备\n- ✅ **无需修改**: 开发者无需修改任何代码，直接升级即可\n- ✅ **性能优化**: 16KB 页面对齐可提升部分设备的内存管理效率\n- ✅ **Google Play 认证**: 已通过 Google Play 的 16KB 页面对齐检测\n\n#### 验证方法\n\n使用 Android Studio 的 APK Analyzer 工具可以验证原生库是否支持 16KB 页面对齐：\n\n1. 构建 APK 或 AAB 文件\n2. 在 Android Studio 中选择 `Build` → `Analyze APK...`\n3. 查看 `lib/arm64-v8a/libSerialPort.so` 的 `Alignment` 列\n4. 显示 `16 KB` 表示已正确配置\n\n#### 相关资源\n\n- [Google Play 16KB 页面大小要求](https://developer.android.com/guide/practices/page-sizes)\n- [CMake 链接器选项文档](https://cmake.org/cmake/help/latest/command/target_link_options.html)\n\n---\n"
  },
  {
    "path": "README4.1.1.md",
    "content": "# Android串口通信框架 SerialPort v4.1.1\n\n[![Version](https://img.shields.io/badge/version-4.1.1-orange.svg)](https://github.com/cl-6666/serialPort)\n[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)\n\n> 这是串口通信框架的4.1.1稳定版本文档。如果你是新用户，建议使用最新的 [5.0.0版本](README.md)，它提供了更简单的API和更强大的功能。\n\n⚠️ **注意**: 4.1.1版本为历史版本，仅用于维护现有项目。新项目请使用 [5.0.0版本](README.md)。\n\n## 📖 版本说明\n\n- **当前版本**: 4.1.1 (稳定维护版本)\n- **推荐版本**: [5.0.0版本](README.md) - 功能更强大，API更简单\n\n## 🚀 快速开始\n\n### 依赖集成\n\n在项目的 `build.gradle` 中添加依赖：\n\n```gradle\ndependencies {\n    implementation 'com.github.cl-6666:serialPort:4.1.1'\n}\n```\n\n在项目根目录的 `build.gradle` 中添加：\n\n```gradle\nallprojects {\n           repositories {\n\t\t\tmaven { url 'https://jitpack.io' }\n             }\n\t}\n```\n\n### 权限配置\n\n在 `AndroidManifest.xml` 中添加必要权限：\n\n```xml\n<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n```\n\n## 📚 使用指南\n\n### 1. Application中初始化\n\n```java\n   public class App extends Application {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        \n        // 方式1：使用XLogConfig配置日志\n        XLogConfig logConfig = new XLogConfig.Builder()\n                .logSwitch(true)        // 开启日志\n                .tag(\"SerialPort\")      // 设置tag\n                .build();\n        \n        SerialConfig serialConfig = new SerialConfig.Builder()\n                .setXLogConfig(logConfig)    // 配置日志参数\n                .setIntervalSleep(50)        // 设置读取间隔\n                .setSerialPortReconnection(false)  // 是否开启串口重连\n                .setFlags(0)             // 标志位\n                .setDatabits(8)          // 数据位\n                .setStopbits(1)          // 停止位\n                .setParity(0)            // 校验位：0无校验，1奇校验，2偶校验\n                .build();\n        \n        SerialUtils.getInstance().init(this, serialConfig);\n        \n        // 方式2：简化初始化\n        SerialUtils.getInstance().init(this, true, \"SerialPort\", 50, 8, 0, 1);\n    }\n}\n```\n\n### 2. 单串口使用\n\n```java\npublic class MainActivity extends AppCompatActivity {\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        \n        // 设置串口监听\n        SerialUtils.getInstance().setmSerialPortDirectorListens(new SerialPortDirectorListens() {\n            @Override\n            public void onSerialPortOpenSuccess(File device, SerialPortEnum serialPortEnum) {\n                Log.i(\"Serial\", \"串口打开成功: \" + device.getPath());\n            }\n            \n            @Override\n            public void onSerialPortOpenFail(File device, SerialStatus status) {\n                Log.e(\"Serial\", \"串口打开失败: \" + status);\n            }\n            \n            @Override\n            public void onDataReceive(byte[] bytes, SerialPortEnum serialPortEnum) {\n                String data = new String(bytes);\n                Log.i(\"Serial\", \"接收数据: \" + data);\n                // 处理接收到的数据\n            }\n            \n            @Override\n            public void onDataSend(byte[] bytes, SerialPortEnum serialPortEnum) {\n                Log.i(\"Serial\", \"发送数据: \" + new String(bytes));\n            }\n        });\n        \n        // 打开串口\n        List<Device> deviceList = new ArrayList<>();\n        deviceList.add(new Device(\"/dev/ttyS4\", \"115200\", new File(\"/dev/ttyS4\")));\n        SerialUtils.getInstance().manyOpenSerialPort(deviceList);\n    }\n    \n    // 发送数据\n    private void sendData() {\n        String data = \"Hello World\";\n        SerialUtils.getInstance().sendData(SerialPortEnum.SERIAL_ONE, data.getBytes());\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // 关闭串口\n        SerialUtils.getInstance().serialPortClose();\n    }\n}\n```\n\n### 3. 多串口使用\n\n```java\npublic class MultiSerialActivity extends AppCompatActivity {\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        \n        // 设置粘包处理\n        SerialUtils.getInstance().setStickPackageHelper(\n            new BaseStickPackageHelper(),              // 串口1：不处理粘包\n            new SpecifiedStickPackageHelper(\"\\n\"),     // 串口2：换行符分包\n            new StaticLenStickPackageHelper(8)         // 串口3：固定8字节\n        );\n        \n        // 设置监听\n        SerialUtils.getInstance().setmSerialPortDirectorListens(new SerialPortDirectorListens() {\n            @Override\n            public void onSerialPortOpenSuccess(File device, SerialPortEnum serialPortEnum) {\n                Log.i(\"Serial\", \"串口[\" + serialPortEnum + \"]打开成功: \" + device.getPath());\n            }\n            \n            @Override\n            public void onSerialPortOpenFail(File device, SerialStatus status) {\n                Log.e(\"Serial\", \"串口打开失败: \" + status);\n            }\n            \n            @Override\n            public void onDataReceive(byte[] bytes, SerialPortEnum serialPortEnum) {\n                String data = new String(bytes);\n                Log.i(\"Serial\", \"串口[\" + serialPortEnum + \"]收到: \" + data);\n                \n                // 根据串口类型处理数据\n                switch (serialPortEnum) {\n                    case SERIAL_ONE:\n                        handleGpsData(data);\n                        break;\n                    case SERIAL_TWO:\n                        handleSensorData(data);\n                        break;\n                    case SERIAL_THREE:\n                        handleModbusData(bytes);\n                        break;\n                }\n            }\n            \n            @Override\n            public void onDataSend(byte[] bytes, SerialPortEnum serialPortEnum) {\n                Log.i(\"Serial\", \"串口[\" + serialPortEnum + \"]发送: \" + new String(bytes));\n            }\n        });\n        \n        // 打开多个串口\n        List<Device> deviceList = new ArrayList<>();\n        deviceList.add(new Device(\"/dev/ttyS1\", \"9600\", new File(\"/dev/ttyS1\")));    // GPS\n        deviceList.add(new Device(\"/dev/ttyS2\", \"115200\", new File(\"/dev/ttyS2\")));  // 传感器\n        deviceList.add(new Device(\"/dev/ttyS3\", \"9600\", new File(\"/dev/ttyS3\")));    // Modbus\n        \n        SerialUtils.getInstance().manyOpenSerialPort(deviceList);\n    }\n    \n    // 向不同串口发送数据\n    private void sendToSerial() {\n        // 向串口1发送GPS命令\n        SerialUtils.getInstance().sendData(SerialPortEnum.SERIAL_ONE, \"AT+GPS?\\r\\n\".getBytes());\n        \n        // 向串口2发送传感器命令\n        SerialUtils.getInstance().sendData(SerialPortEnum.SERIAL_TWO, \"READ_TEMP\\n\".getBytes());\n        \n        // 向串口3发送Modbus命令\n        byte[] modbusCmd = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, (byte)0x84, 0x0A};\n        SerialUtils.getInstance().sendData(SerialPortEnum.SERIAL_THREE, modbusCmd);\n    }\n    \n    private void handleGpsData(String data) {\n        // 处理GPS数据\n    }\n    \n    private void handleSensorData(String data) {\n        // 处理传感器数据\n    }\n    \n    private void handleModbusData(byte[] data) {\n        // 处理Modbus数据\n    }\n}\n```\n\n### 4. 粘包处理配置\n\n```java\npublic class StickyPacketConfig {\n    \n    public void configureStickyPacket() {\n        // 1. 不处理粘包（默认）\n        SerialUtils.getInstance().setStickPackageHelper(new BaseStickPackageHelper());\n        \n        // 2. 按分隔符分包\n        SerialUtils.getInstance().setStickPackageHelper(\n            new SpecifiedStickPackageHelper(\"\\r\\n\".getBytes())  // 按\\r\\n分包\n        );\n        \n        // 3. 固定长度分包\n        SerialUtils.getInstance().setStickPackageHelper(\n            new StaticLenStickPackageHelper(16)  // 固定16字节\n        );\n        \n        // 4. 可变长度分包\n        SerialUtils.getInstance().setStickPackageHelper(\n            new VariableLenStickPackageHelper(\n                java.nio.ByteOrder.BIG_ENDIAN,  // 字节序\n                2,    // 长度字段大小\n                2,    // 长度字段位置\n                12    // 包头长度\n            )\n        );\n        \n        // 5. 多串口不同策略\n        SerialUtils.getInstance().setStickPackageHelper(\n            new BaseStickPackageHelper(),                    // 串口1：不处理\n            new SpecifiedStickPackageHelper(\"\\n\"),           // 串口2：换行符\n            new StaticLenStickPackageHelper(8),              // 串口3：固定长度\n            new VariableLenStickPackageHelper(               // 串口4：可变长度\n                java.nio.ByteOrder.BIG_ENDIAN, 2, 2, 12)\n        );\n    }\n}\n```  \n\n### 5. 串口参数配置\n\n```java\npublic class SerialParamConfig {\n    \n    public void configureParams() {\n        SerialConfig serialConfig = new SerialConfig.Builder()\n            .setIntervalSleep(50)           // 读取间隔50ms\n            .setDatabits(8)                 // 数据位8\n            .setStopbits(1)                 // 停止位1\n            .setParity(0)                   // 校验位：0=无校验\n            .setFlags(0)                    // 标志位\n            .setSerialPortReconnection(false)  // 是否重连\n            .build();\n        \n        SerialUtils.getInstance().init(getApplication(), serialConfig);\n    }\n    \n    // 常用配置\n    public void commonConfigs() {\n        // 标准配置 8N1\n        SerialConfig config8N1 = new SerialConfig.Builder()\n            .setDatabits(8).setParity(0).setStopbits(1)\n            .build();\n        \n        // Modbus RTU 8E1\n        SerialConfig configModbus = new SerialConfig.Builder()\n            .setDatabits(8).setParity(2).setStopbits(1)  // 偶校验\n            .build();\n        \n        // 老式设备 7E2\n        SerialConfig configOld = new SerialConfig.Builder()\n            .setDatabits(7).setParity(2).setStopbits(2)\n            .build();\n    }\n}\n```\n\n## 📖 API参考\n\n### SerialUtils 主要方法\n| 方法 | 说明 |\n|------|------|\n| `init(Application, SerialConfig)` | 初始化串口框架 |\n| `init(Application, boolean, String, int, int, int, int)` | 简化初始化 |\n| `setmSerialPortDirectorListens(SerialPortDirectorListens)` | 设置串口监听 |\n| `setStickPackageHelper(AbsStickPackageHelper...)` | 设置粘包处理 |\n| `manyOpenSerialPort(List<Device>)` | 打开多个串口 |\n| `sendData(SerialPortEnum, byte[])` | 发送数据 |\n| `serialPortClose()` | 关闭串口 |\n\n### SerialConfig 配置项\n| 参数 | 说明 | 默认值 |\n|------|------|--------|\n| `intervalSleep` | 读取间隔(ms) | 50 |\n| `databits` | 数据位 | 8 |\n| `stopbits` | 停止位 | 1 |\n| `parity` | 校验位 | 0 |\n| `flags` | 标志位 | 0 |\n| `serialPortReconnection` | 是否重连 | false |\n\n### 粘包处理器\n| 类型 | 说明 | 适用场景 |\n|------|------|----------|\n| `BaseStickPackageHelper` | 不处理粘包 | 简单数据流 |\n| `SpecifiedStickPackageHelper` | 分隔符分包 | 文本协议 |\n| `StaticLenStickPackageHelper` | 固定长度分包 | 固定格式协议 |\n| `VariableLenStickPackageHelper` | 可变长度分包 | 复杂二进制协议 |\n\n## 🛠️ 故障排查\n\n### 常见问题\n\n1. **串口打开失败**\n   ```java\n   // 检查设备路径\n   String[] devices = new SerialPortFinder().getAllDevicesPath();\n   \n   // 检查权限\n   File deviceFile = new File(\"/dev/ttyS4\");\n   if (!deviceFile.canRead() || !deviceFile.canWrite()) {\n       Log.e(\"Serial\", \"设备权限不足\");\n   }\n   ```\n\n2. **数据接收不完整**\n   ```java\n   // 尝试不处理粘包\n   SerialUtils.getInstance().setStickPackageHelper(new BaseStickPackageHelper());\n   \n   // 或者调整读取间隔\n   SerialConfig config = new SerialConfig.Builder()\n       .setIntervalSleep(20)  // 减少到20ms\n       .build();\n   ```\n\n3. **日志输出问题**\n   ```java\n   // 确保启用日志\n   XLogConfig logConfig = new XLogConfig.Builder()\n       .logSwitch(true)\n       .tag(\"SerialPort\")\n       .build();\n   ```\n\n## 🎯 升级到5.0.0\n\n如果你想升级到最新的5.0.0版本，以下是主要的变化：\n\n### 4.1.1版本\n```java\n// 初始化\nSerialUtils.getInstance().init(this, true, \"TAG\", 50, 8, 0, 1);\n\n// 使用\nSerialUtils.getInstance().setmSerialPortDirectorListens(...);\nSerialUtils.getInstance().manyOpenSerialPort(deviceList);\nSerialUtils.getInstance().sendData(SerialPortEnum.SERIAL_ONE, data);\n```\n\n### 5.0.0版本 (推荐)\n```java\n// 简化初始化\nnew SimpleSerialPortManager.QuickConfig()\n    .setDatabits(8).setParity(0).setStopbits(1)\n    .apply(this);\n\n// 简化使用\nSimpleSerialPortManager.getInstance()\n    .openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n        // 处理数据\n    });\n\n// 多串口\nMultiSerialPortManager manager = SimpleSerialPortManager.multi();\nmanager.openSerialPort(\"GPS\", \"/dev/ttyS1\", 9600, config, statusCallback, dataCallback);\n```\n\n**升级优势**：\n- API更简单易用\n- 支持真正的多串口管理\n- 更好的错误处理\n- 增强的日志系统\n- 更高的性能\n\n详细的5.0.0版本使用请查看 [最新文档](README.md)。\n\n## 📞 联系我们\n\n- **QQ群**: 458173716\n- **博客**: https://blog.csdn.net/a214024475/article/details/113735085\n- **GitHub**: https://github.com/cl-6666/serialPort\n\n## 📄 许可证\n\n```\nLicensed under the Apache License, Version 2.0\n```\n\n---\n\n⚠️ **再次提醒**: 4.1.1版本为历史版本，新项目建议使用 [5.0.0版本](README.md)，功能更强大，使用更简单！"
  },
  {
    "path": "README_EN.md",
    "content": "# Android Serial Communication Framework SerialPort\n\n[English](README_EN.md) | [中文](README.md)\n\n[![Version](https://img.shields.io/badge/version-5.0.8-blue.svg)](https://github.com/cl-6666/serialPort)\n[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)\n[![License](https://img.shields.io/badge/license-Apache%202-green.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n> A flexible, efficient, and lightweight Android serial communication framework that makes serial port operations simple. Supports single-port, multi-port, sticky-packet handling, custom configuration, and more.\n\n<img src=\"https://github.com/cl-6666/serialPort/blob/master/img/multiple_images.png\" width=\"650\" height=\"360\" alt=\"Demo\"/>  \n\n## 📱 Demo APK\n\nWant to try it quickly? Download the demo APK and install it on your Android device.\n\n<div align=\"center\">\n\n### 📥 [Download Demo APK](https://www.pgyer.com/XNzY)\n\n[![Download APK](https://img.shields.io/badge/Download-APK%20v5.0.8-brightgreen.svg?style=for-the-badge&logo=android)](https://www.pgyer.com/XNzY)\n\n**Version**: v5.0.8 | **Size**: ~7 MB | **API**: 21+ | **ABIs**: arm64-v8a, armeabi-v7a, x86, x86_64\n\n</div>\n\n### Demo Features\n\n- ✅ Single serial port demo\n- ✅ Multi-serial management demo\n- ✅ Sticky-packet strategy switching\n- ✅ Serial params configuration (data bits, parity, stop bits)\n- ✅ Real-time send/receive test\n- ✅ Hex/ASCII display\n- ✅ Performance test & statistics\n\n> Note: the demo APK must run on an Android device with serial ports (industrial devices, development boards, etc.). If your device has no serial port, check the source code for usage.\n\n## ⭐ Features\n\n- 🚀 **Easy to use** - fluent APIs, configure with one line\n- 🔧 **Multi-serial support** - manage multiple ports with independent configs\n- 📦 **Smart sticky-packet handling** - multiple strategies, switch at runtime\n- ⚡ **High performance** - multithreaded, thread-safe design\n- 🛡️ **Stable & reliable** - solid error handling and resource management\n- 📝 **Detailed logs** - rich debug information for troubleshooting\n- 🎯 **Flexible config** - data bits, parity, stop bits, etc.\n- ✨ **Google Play ready** - supports 16 KB page alignment and passes Play requirements\n\n## 📖 Versions\n\n- **Current**: 5.0.8 (recommended) - new architecture, powerful features, supports Google Play 16 KB page alignment\n- **Legacy**: [4.1.1 docs](README4.1.1.md) - stable legacy version\n\n### 5.0.8 Changes 🔥 (2025-12-25)\n\n- ✅ **16 KB page alignment**: fully compatible with Google Play 16 KB page size requirements\n- ✅ **Android 15 support**: compatible with Android 15\n- ✅ **Native library optimization**: `arm64-v8a` native lib meets Google Play checks\n- ✅ **Backward compatible**: works on older Android devices without code changes\n\n> Important: since 2024, Google Play requires all `arm64-v8a` native libraries to support 16 KB page size. v5.0.8 fully meets this requirement.\n\n### 5.0.0 Major Update 🎉\n\n- ✅ **Architecture refactor**: removed `SerialUtils` dependency, clearer design\n- ✅ **Simplified API**: introduced `SimpleSerialPortManager`, easier usage\n- ✅ **Multi-serial management**: new `MultiSerialPortManager` for complex scenarios\n- ✅ **Enhanced logging**: built-in logging system for better debugging\n- ✅ **Independent config**: each port can use its own sticky-packet strategy\n- ✅ **Performance improvements**: reduced ~30% redundant code\n\n## 🚀 Quick Start\n\n### Dependency\n\nAdd dependency in your module `build.gradle`:\n\n```gradle\ndependencies {\n   implementation 'com.github.cl-6666:serialPort:v5.0.8'\n}\n```\n\nAdd JitPack in the root `build.gradle`:\n\n```gradle\nallprojects {\n    repositories {\n        maven { url 'https://jitpack.io' }\n    }\n}\n```\n\n### Permissions\n\nAdd required permissions in `AndroidManifest.xml`:\n\n```xml\n<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n```\n\n## 📚 Usage Guide\n\n### 1️⃣ Single Port - Basic Example\n\n#### Minimal usage\n\n```java\npublic class MainActivity extends AppCompatActivity {\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        \n        // Open serial port and receive data with one line\n        SimpleSerialPortManager.getInstance()\n            .openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n                String receivedData = new String(data);\n                Log.i(\"Serial\", \"Received: \" + receivedData);\n                // Handle received data\n            });\n    }\n    \n    // Send data\n    private void sendData() {\n        SimpleSerialPortManager.getInstance().sendData(\"Hello World\");\n    }\n    \n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // Close serial port\n        SimpleSerialPortManager.getInstance().closeSerialPort();\n    }\n}\n```\n\n#### Full configuration example\n\n```java\npublic class App extends Application {\n    \n    @Override\n    public void onCreate() {\n        super.onCreate();\n        \n        // Global config (optional)\n        new SimpleSerialPortManager.QuickConfig()\n            .setIntervalSleep(50)                    // Read interval: 50ms\n            .setEnableLog(true)                      // Enable logs\n            .setLogTag(\"SerialPortApp\")              // Log tag\n            .setDatabits(8)                          // Data bits: 8\n            .setParity(0)                            // Parity: none\n            .setStopbits(1)                          // Stop bits: 1\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING)\n            .apply(this);\n    }\n}\n```\n\n### 2️⃣ Data Bits / Parity / Stop Bits\n\n```java\npublic class SerialConfigExample {\n    \n    public void configureSerialParams() {\n        SimpleSerialPortManager manager = SimpleSerialPortManager.getInstance();\n        \n        // Option 1: use QuickConfig\n        new SimpleSerialPortManager.QuickConfig()\n            .setDatabits(8)        // Data bits: 5, 6, 7, 8\n            .setParity(0)          // Parity: 0=none, 1=odd, 2=even\n            .setStopbits(1)        // Stop bits: 1 or 2\n            .setFlags(0)           // Flags\n            .apply(getApplication());\n        \n        // Option 2: set dynamically\n        manager.setDatabits(8)     // Set data bits\n               .setParity(2)       // Set even parity\n               .setStopbits(1)     // Set stop bits to 1\n               .setFlags(0);       // Set flags\n        \n        // Open serial port\n        manager.openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n            Log.i(\"Serial\", \"Data: \" + new String(data));\n        });\n    }\n    \n    // Common configurations\n    public void commonConfigurations() {\n        SimpleSerialPortManager manager = SimpleSerialPortManager.getInstance();\n        \n        // Standard 8N1 (8 data bits, no parity, 1 stop bit)\n        manager.setDatabits(8).setParity(0).setStopbits(1);\n        \n        // Modbus RTU 8E1 (8 data bits, even parity, 1 stop bit) \n        manager.setDatabits(8).setParity(2).setStopbits(1);\n        \n        // Legacy devices 7E2 (7 data bits, even parity, 2 stop bits)\n        manager.setDatabits(7).setParity(2).setStopbits(2);\n    }\n}\n```\n\n### 3️⃣ Sticky-Packet Handling\n\nSticky packets are common in serial communication. Since v5.0.0, multiple strategies are provided:\n\n```java\npublic class StickyPacketExample {\n    \n    public void noProcessing() {\n        // Strategy 1: no processing - good for simple streams\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING)\n            .apply(this);\n    }\n    \n    public void delimiterBased() {\n        // Strategy 2: delimiter-based - good for text protocols\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.DELIMITER_BASED)\n            .apply(this);\n        \n        // Custom delimiter\n        SimpleSerialPortManager.getInstance()\n            .configureStickyPacket(SimpleSerialPortManager.StickyPacketStrategy.DELIMITER_BASED);\n    }\n    \n    public void fixedLength() {\n        // Strategy 3: fixed-length - good for fixed-length protocols\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.FIXED_LENGTH)\n            .apply(this);\n    }\n    \n    public void variableLength() {\n        // Strategy 4: variable-length - good for protocols with length fields\n        new SimpleSerialPortManager.QuickConfig()\n            .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.VARIABLE_LENGTH)\n            .apply(this);\n    }\n}\n```\n\n### 4️⃣ Multi-Serial Management\n\n```java\npublic class MultiSerialExample {\n    \n    public void basicMultiSerial() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // Port 1: GPS module, no sticky-packet processing\n        manager.openSerialPort(\"GPS\", \"/dev/ttyS1\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0)\n                .setStopbits(1)\n                .setStickyPacketHelpers(new BaseStickPackageHelper()) // No processing\n                .build(),\n            // Status callback\n            (serialId, success, status) -> {\n                Log.i(\"GPS\", \"Status: \" + (success ? \"Success\" : \"Failed\"));\n            },\n            // Data callback\n            (serialId, data) -> {\n                String gpsData = new String(data);\n                Log.i(\"GPS\", \"Data: \" + gpsData);\n                handleGpsData(gpsData);\n            });\n        \n        // Port 2: sensor module, split by newline\n        manager.openSerialPort(\"SENSOR\", \"/dev/ttyS2\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0) \n                .setStopbits(1)\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\")) // Newline delimiter\n                .build(),\n            null, // No status callback needed\n            (serialId, data) -> {\n                String sensorData = new String(data).trim();\n                Log.i(\"SENSOR\", \"Data: \" + sensorData);\n                handleSensorData(sensorData);\n            });\n        \n        // Send to different ports\n        manager.sendData(\"GPS\", \"AT+GPS?\\r\\n\");\n        manager.sendData(\"SENSOR\", \"READ_TEMP\\n\");\n    }\n    \n    // Dynamic management\n    public void dynamicManagement() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // Query status\n        List<String> openedPorts = manager.getOpenedSerialPorts();\n        boolean isOpened = manager.isSerialPortOpened(\"GPS\");\n        manager.printAllSerialStatus();\n        \n        // Update sticky-packet strategy dynamically\n        manager.updateStickyPacketHelpers(\"GPS\", \n            new AbsStickPackageHelper[]{new SpecifiedStickPackageHelper(\"\\r\\n\")});\n        \n        // Close one port\n        manager.closeSerialPort(\"GPS\");\n        \n        // Close all ports\n        manager.closeAllSerialPorts();\n    }\n}\n```\n\n## 🎯 Real-world Scenarios\n\n### Industrial control\n```java\npublic class IndustrialControlExample {\n    \n    public void setupIndustrialPorts() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // PLC - Modbus RTU\n        manager.openSerialPort(\"PLC\", \"/dev/ttyS1\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8).setParity(2).setStopbits(1) // 8E1\n                .setStickyPacketHelpers(new StaticLenStickPackageHelper(8))\n                .build(),\n            null, this::handlePlcData);\n        \n        // Sensor acquisition - text protocol\n        manager.openSerialPort(\"SENSORS\", \"/dev/ttyS3\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(7).setParity(2).setStopbits(1) // 7E1\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null, this::handleSensorData);\n    }\n}\n```\n\n### Communication gateway\n```java\npublic class GatewayExample {\n    \n    public void setupGateway() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // Uplink (to server)\n        manager.openSerialPort(\"UPLINK\", \"/dev/ttyS1\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\"))\n                .build(),\n            null, this::handleUplinkData);\n        \n        // Downlink device 1 - GPS\n        manager.openSerialPort(\"GPS\", \"/dev/ttyS2\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null, data -> forwardToUplink(\"GPS\", data));\n    }\n    \n    private void forwardToUplink(String deviceId, byte[] data) {\n        String message = String.format(\"[%s]%s\\n\", deviceId, new String(data));\n        SimpleSerialPortManager.multi().sendData(\"UPLINK\", message);\n    }\n}\n```\n\n## 🔧 Advanced\n\n### Logging\n```java\n// Enable verbose logs\nSerialPortLogUtil.setDebugEnabled(true);\n\n// Custom output\nSerialPortLogUtil.i(\"MyTag\", \"Custom log message\");\nSerialPortLogUtil.printData(\"Send\", data); // Hex + ASCII\nSerialPortLogUtil.printSerialConfig(\"MySerial\", 8, 0, 1, 0); // Config info\n```\n\n### Error handling\n```java\nmanager.openSerialPort(\"TEST\", \"/dev/ttyS1\", 9600,\n    (serialId, success, status) -> {\n        if (!success) {\n            switch (status) {\n                case NO_READ_WRITE_PERMISSION:\n                    Log.e(\"Serial\", \"No permission\");\n                    break;\n                case OPEN_FAIL:\n                    Log.e(\"Serial\", \"Open failed\");\n                    break;\n            }\n        }\n    },\n    dataCallback);\n```\n\n## 🛠️ Troubleshooting\n\n### Common issues\n\n1. **Failed to open serial port**\n   ```java\n   // Check device paths\n   String[] devices = new SerialPortFinder().getAllDevicesPath();\n   \n   // Check permission\n   File deviceFile = new File(\"/dev/ttyS4\");\n   boolean canRead = deviceFile.canRead();\n   boolean canWrite = deviceFile.canWrite();\n   ```\n\n2. **Incomplete received data**\n   ```java\n   // Enable logs to inspect raw data\n   SerialPortLogUtil.setDebugEnabled(true);\n   \n   // Try different sticky-packet strategies\n   manager.configureStickyPacket(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING);\n   ```\n\n## 📖 API Reference\n\n### SimpleSerialPortManager (single port)\n| Method | Description |\n|------|------|\n| `getInstance()` | Get singleton instance |\n| `openSerialPort(path, baudRate, callback)` | Open serial port |\n| `sendData(data)` | Send data |\n| `closeSerialPort()` | Close serial port |\n| `setDatabits(databits)` | Set data bits |\n| `setParity(parity)` | Set parity |\n| `setStopbits(stopbits)` | Set stop bits |\n\n### MultiSerialPortManager (multi port)\n| Method | Description |\n|------|------|\n| `getInstance()` | Get instance |\n| `openSerialPort(id, path, baudRate, config, statusCallback, dataCallback)` | Open serial port |\n| `sendData(serialId, data)` | Send data to a port |\n| `closeSerialPort(serialId)` | Close a port |\n| `closeAllSerialPorts()` | Close all ports |\n| `isSerialPortOpened(serialId)` | Check port status |\n\n## 🎯 Migration\n\n### From 4.1.1 to 5.0.0\n\n**Old (4.1.1)**:\n```java\n// Init in Application\nSerialUtils.getInstance().init(this, true, \"TAG\", 50, 8, 0, 1);\n\n// Usage\nSerialUtils.getInstance().setmSerialPortDirectorListens(...);\nSerialUtils.getInstance().manyOpenSerialPort(list);\n```\n\n**New (5.0.0)**:\n```java\n// Simplified init (optional)\nnew SimpleSerialPortManager.QuickConfig()\n    .setDatabits(8).setParity(0).setStopbits(1)\n    .apply(this);\n\n// Direct usage\nSimpleSerialPortManager.getInstance()\n    .openSerialPort(\"/dev/ttyS4\", 115200, data -> {\n        // Handle data\n    });\n```\n\n## 📞 Contact\n\n- **QQ group**: 458173716\n- **Blog**: https://blog.csdn.net/a214024475/article/details/113735085\n- **GitHub**: https://github.com/cl-6666/serialPort\n\n### PC serial debugging assistant\n<img src=\"https://github.com/cl-6666/serialPort/blob/master/img/pc_ck.jpg\" width=\"440\" height=\"320\" alt=\"PC tool\"/>\n\n**Download**: https://pan.baidu.com/s/1DL2TOHz9bl9RIKIG3oCSWw?pwd=f7sh  \n\n### QQ technical group\n<img src=\"https://github.com/cl-6666/serialPort/blob/master/img/qq2.jpg\" width=\"350\" height=\"560\" alt=\"QQ group\"/>\n\n**Group ID**: 458173716\n\n## 🔬 Technical Notes\n\n### 16 KB Page Alignment (v5.0.8)\n\nSince 2024, Google Play requires apps that ship native libraries (`.so`) to support 16 KB page size, to be compatible with newer Android devices. This library fully meets the requirement.\n\n#### Implementation\n\nIn CMake configuration, the following linker flags are added for `arm64-v8a`:\n\n```cmake\n# CMakeLists.txt\nif(ANDROID_ABI STREQUAL \"arm64-v8a\")\n    target_compile_options(SerialPort PRIVATE -fno-emulated-tls)\n    target_link_options(SerialPort PRIVATE \n        \"LINKER:-z,max-page-size=16384\"\n        \"LINKER:-z,common-page-size=16384\")\nendif()\n```\n\n#### Compatibility\n\n- ✅ **Fully compatible**: supports Android 5.0+ (API 21+)\n- ✅ **No code change**: upgrade and use directly\n- ✅ **Optimized**: 16 KB alignment can improve memory management on some devices\n- ✅ **Google Play ready**: passes 16 KB alignment checks\n\n#### Verification\n\nYou can verify alignment with Android Studio APK Analyzer:\n\n1. Build an APK or AAB\n2. In Android Studio: `Build` → `Analyze APK...`\n3. Check the `Alignment` column for `lib/arm64-v8a/libSerialPort.so`\n4. `16 KB` means it is configured correctly\n\n#### Resources\n\n- [Google Play 16 KB page size requirements](https://developer.android.com/guide/practices/page-sizes)\n- [CMake `target_link_options` docs](https://cmake.org/cmake/help/latest/command/target_link_options.html)\n\n---\n"
  },
  {
    "path": "app/build.gradle",
    "content": "plugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.kotlin.android)\n}\n\nandroid {\n    namespace 'com.cl.myapplication'\n    compileSdk 36\n\n    defaultConfig {\n        applicationId \"com.cl.myapplication\"\n        minSdk 24\n        targetSdk 36\n        versionCode 508\n        versionName \"5.0.8\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_11\n        targetCompatibility JavaVersion.VERSION_11\n    }\n    kotlinOptions {\n        jvmTarget = '11'\n    }\n    buildFeatures{\n        dataBinding=true\n    }\n}\n\ndependencies {\n\n    implementation libs.androidx.core.ktx\n    implementation libs.androidx.appcompat\n    implementation libs.material\n    implementation 'androidx.cardview:cardview:1.0.0'\n    testImplementation libs.junit\n    androidTestImplementation libs.androidx.junit\n    androidTestImplementation libs.androidx.espresso.core\n    implementation 'org.greenrobot:eventbus:3.2.0'\n    implementation 'com.github.getActivity:ToastUtils:9.6'\n    implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'\n    implementation 'com.google.code.gson:gson:2.8.6'\n//    implementation project(path: ':serial_lib')\n    implementation 'com.github.cl-6666:serialPort:v5.0.8'\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.cl.myapplication\">\n\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\" />\n\n\n    <application\n        android:name=\".App\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.MyApplication\">\n\n        <meta-data\n            android:name=\"design_width_in_dp\"\n            android:value=\"1080\" />\n        <meta-data\n            android:name=\"design_height_in_dp\"\n            android:value=\"1920\" />\n\n        <activity android:name=\".MultiSerialPortActivity\"\n            android:exported=\"true\"\n            android:screenOrientation=\"landscape\">\n\n<!--            <intent-filter>-->\n<!--                <action android:name=\"android.intent.action.MAIN\" />-->\n\n<!--                <category android:name=\"android.intent.category.LAUNCHER\" />-->\n<!--            </intent-filter>-->\n\n        </activity>\n\n        <activity\n            android:name=\".SingleSerialPortActivity\"\n            android:exported=\"true\"\n            android:screenOrientation=\"landscape\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n\n        <!--查看哪些串口是否可用-->\n        <activity android:name=\".SelectSerialPortActivity\">\n<!--            <intent-filter>-->\n<!--                <action android:name=\"android.intent.action.MAIN\" />-->\n\n<!--                <category android:name=\"android.intent.category.LAUNCHER\" />-->\n<!--            </intent-filter>-->\n        </activity>\n        <activity\n            android:name=\".MainActivity\"\n            android:windowSoftInputMode=\"stateHidden|stateUnchanged\"></activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/App.java",
    "content": "package com.cl.myapplication;\n\nimport android.app.Application;\n\nimport com.hjq.toast.ToastUtils;\nimport com.cl.serialportlibrary.SimpleSerialPortManager;\n\n/**\n * 项目：serialPort\n * 作者：Arry\n * 创建日期：2021/10/20\n * 描述：\n * 修订历史：\n */\npublic class App extends Application {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        // 初始化 Toast 框架\n        ToastUtils.init(this);\n        \n        // 使用新的SimpleSerialPortManager进行全局初始化\n        new SimpleSerialPortManager.QuickConfig()\n                .setIntervalSleep(50)                    // 读取间隔50ms\n                .setEnableLog(true)                      // 启用日志\n                .setLogTag(\"SerialPortApp\")              // 设置日志标签\n                .setDatabits(8)                          // 数据位8\n                .setParity(0)                            // 无校验\n                .setStopbits(1)                          // 停止位1\n                .setStickyPacketStrategy(SimpleSerialPortManager.StickyPacketStrategy.NO_PROCESSING) // 不处理黏包\n                .setMaxPacketSize(1024)                  // 最大包大小1KB\n                .apply(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/MainActivity.kt",
    "content": "package com.cl.myapplication\n\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.Log\nimport android.view.View\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.databinding.DataBindingUtil\nimport com.cl.myapplication.databinding.ActivityMainBinding\nimport com.cl.serialportlibrary.Device\nimport com.cl.serialportlibrary.SimpleSerialPortManager\n\nclass MainActivity : AppCompatActivity(){\n\n    private val TAG = MainActivity::class.java.simpleName\n    val DEVICE = \"device\"\n    private var isSerialPortOpened = false\n    private var mToast: Toast? = null\n    private lateinit var binding: ActivityMainBinding\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)\n        val device = intent.getSerializableExtra(DEVICE) as Device?\n        Log.i(TAG, \"onCreate: device = $device\")\n        if (null == device) {\n            finish()\n            return\n        }\n        \n        // 使用SimpleSerialPortManager打开串口\n        val devicePath = device.name\n        val baudRate = device.root.toInt()\n        SimpleSerialPortManager.getInstance().openSerialPort(devicePath, baudRate) { data ->\n            runOnUiThread {\n                val receivedData = String(data)\n                Log.i(TAG, \"接收到数据: $receivedData\")\n//                binding.tvReceiveContent.text = receivedData\n            }\n        }\n        isSerialPortOpened = true\n    }\n\n\n    fun onSend(view: View) {\n        val editTextSendContent = binding.etSendContent.text.toString()\n        if (TextUtils.isEmpty(editTextSendContent)) {\n            Log.i(TAG, \"onSend: 发送内容为 null\")\n            return\n        }\n        val sendContentBytes = editTextSendContent.toByteArray()\n        val sendBytes = SimpleSerialPortManager.getInstance().sendData(sendContentBytes)\n        Log.i(TAG, \"onSend: sendBytes = $sendBytes\")\n        showToast(if (sendBytes) \"发送成功\" else \"发送失败\")\n    }\n\n\n    fun onDestroy(view: View) {\n        if (isSerialPortOpened) {\n            SimpleSerialPortManager.getInstance().closeSerialPort()\n            isSerialPortOpened = false\n        }\n        finish()\n    }\n    \n    override fun onDestroy() {\n        super.onDestroy()\n        if (isSerialPortOpened) {\n            SimpleSerialPortManager.getInstance().closeSerialPort()\n        }\n    }\n\n\n    /**\n     * Toast\n     *\n     * @param content content\n     */\n    private fun showToast(content: String) {\n        if (null == mToast) {\n            mToast = Toast.makeText(applicationContext, null, Toast.LENGTH_SHORT)\n        }\n        mToast?.setText(content)\n        mToast?.show()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/MultiSerialPortActivity.java",
    "content": "package com.cl.myapplication;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\nimport android.widget.Button;\nimport android.widget.Spinner;\nimport android.widget.TextView;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.databinding.DataBindingUtil;\n\nimport com.cl.myapplication.adapter.SpAdapter;\nimport com.cl.myapplication.databinding.ActivityMultiSerialBinding;\nimport com.cl.serialportlibrary.MultiSerialPortManager;\nimport com.cl.serialportlibrary.SerialPortFinder;\nimport com.cl.serialportlibrary.SimpleSerialPortManager;\nimport com.cl.serialportlibrary.enumerate.SerialStatus;\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\nimport com.cl.serialportlibrary.stick.CompositeStickPackageHelper;\nimport com.cl.serialportlibrary.stick.SpecifiedStickPackageHelper;\nimport com.cl.serialportlibrary.stick.StaticLenStickPackageHelper;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Locale;\n\n/**\n * 多串口演示Activity\n * 展示如何同时管理多个串口，每个串口使用不同的粘包策略\n */\npublic class MultiSerialPortActivity extends AppCompatActivity {\n    \n    private static final String TAG = \"MultiSerialPortActivity\";\n    \n    private ActivityMultiSerialBinding binding;\n    private TextView statusText;\n    private TextView dataText;\n\n    private final SimpleDateFormat timeFormatter = new SimpleDateFormat(\"HH:mm:ss.SSS\", Locale.getDefault());\n    \n    // 串口设备列表\n    private String[] mDevices;\n    private String[] mBaudrates = {\"9600\", \"19200\", \"38400\", \"57600\", \"115200\", \"230400\", \"460800\", \"921600\"};\n    \n    // 全局串口参数\n    private String[] mDatabits = {\"8\", \"7\", \"6\", \"5\"};\n    private String[] mParitys = {\"NONE\", \"ODD\", \"EVEN\", \"SPACE\", \"MARK\"};\n    private String[] mStopbits = {\"1\", \"2\"};\n    \n    private int globalDatabits = 8;\n    private int globalParity = 0;\n    private int globalStopbits = 1;\n    \n    // 各串口的配置\n    private SerialPortConfig gpsConfig = new SerialPortConfig();\n    private SerialPortConfig sensorConfig = new SerialPortConfig();\n    private SerialPortConfig modbusConfig = new SerialPortConfig();\n    private SerialPortConfig customConfig = new SerialPortConfig();\n    \n    // 串口状态\n    private boolean gpsOpened = false;\n    private boolean sensorOpened = false;\n    private boolean modbusOpened = false;\n    private boolean customOpened = false;\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        binding = DataBindingUtil.setContentView(this, R.layout.activity_multi_serial);\n        \n        initViews();\n        initDevices();\n        setupGlobalParamSpinners();\n        setupSpinners();\n        setupMultiSerial();\n    }\n    \n    private void initViews() {\n        statusText = binding.tvStatus;\n        dataText = binding.tvData;\n        \n        // 设置按钮点击事件\n        binding.btnRefreshPorts.setOnClickListener(v -> refreshSerialPorts());\n        binding.btnOpenAll.setOnClickListener(v -> openAllSerialPorts());\n        binding.btnCloseAll.setOnClickListener(v -> closeAllSerialPorts());\n        binding.btnSendTest.setOnClickListener(v -> sendTestData());\n        binding.btnClearLog.setOnClickListener(v -> clearLog());\n        \n        // 各串口的开关按钮\n        binding.btnGpsToggle.setOnClickListener(v -> toggleGpsPort());\n        binding.btnSensorToggle.setOnClickListener(v -> toggleSensorPort());\n        binding.btnModbusToggle.setOnClickListener(v -> toggleModbusPort());\n        binding.btnCustomToggle.setOnClickListener(v -> toggleCustomPort());\n    }\n    \n    private void setupMultiSerial() {\n        updateStatus(\"多串口管理器初始化完成\");\n    }\n    \n    \n    /**\n     * 发送测试数据到所有串口\n     */\n    private void sendTestData() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        int sentCount = 0;\n\n        // 向GPS发送AT命令\n        if (gpsOpened) {\n            boolean ok = manager.sendData(\"GPS\", \"AT+GPS?\\r\\n\");\n            if (!ok) {\n                appendData(\"TX_FAIL [GPS] AT+GPS?\\\\r\\\\n\");\n            }\n            if (ok) {\n                sentCount++;\n            }\n        }\n\n        // 向传感器发送读取命令\n        if (sensorOpened) {\n            boolean ok = manager.sendData(\"SENSOR\", \"READ_TEMP\\n\");\n            if (!ok) {\n                appendData(\"TX_FAIL [SENSOR] READ_TEMP\\\\n\");\n            }\n            if (ok) {\n                sentCount++;\n            }\n        }\n\n        // 向Modbus设备发送读取寄存器命令\n        if (modbusOpened) {\n            byte[] modbusCmd = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, (byte)0x84, 0x0A};\n            boolean ok = manager.sendData(\"MODBUS\", modbusCmd);\n            if (!ok) {\n                appendData(\"TX_FAIL [MODBUS] \" + bytesToHex(modbusCmd));\n            }\n            if (ok) {\n                sentCount++;\n            }\n        }\n\n        // 向自定义协议设备发送命令\n        if (customOpened) {\n            String payload = \"$$START$$GET_STATUS$$END$$\";\n            boolean ok = manager.sendData(\"CUSTOM\", payload);\n            if (!ok) {\n                appendData(\"TX_FAIL [CUSTOM] \" + payload);\n            }\n            if (ok) {\n                sentCount++;\n            }\n        }\n\n        updateStatus(\"测试数据发送请求已提交到 \" + sentCount + \" 个串口\");\n    }\n    \n    /**\n     * 更新状态显示\n     */\n    private void updateStatus(String message) {\n        appendLog(statusText, message, 20);\n        Log.i(TAG, message);\n    }\n\n    private void appendData(String message) {\n        appendLog(dataText, message, 200);\n    }\n\n    private void appendLog(TextView target, String message, int maxLines) {\n        if (target == null) {\n            return;\n        }\n        String time = timeFormatter.format(new Date());\n        String currentText = target.getText().toString();\n        String newText = time + \" \" + message + \"\\n\" + currentText;\n        String[] lines = newText.split(\"\\n\");\n        if (lines.length > maxLines) {\n            StringBuilder sb = new StringBuilder();\n            for (int i = 0; i < maxLines; i++) {\n                sb.append(lines[i]).append(\"\\n\");\n            }\n            newText = sb.toString();\n        }\n        target.setText(newText);\n    }\n\n    private String sanitizeForSingleLine(String text) {\n        if (text == null) {\n            return \"\";\n        }\n        return text.replace(\"\\r\", \"\\\\r\").replace(\"\\n\", \"\\\\n\");\n    }\n    \n    /**\n     * 字节数组转十六进制字符串\n     */\n    private String bytesToHex(byte[] bytes) {\n        StringBuilder sb = new StringBuilder();\n        for (byte b : bytes) {\n            sb.append(String.format(\"%02X \", b));\n        }\n        return sb.toString().trim();\n    }\n    \n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        // 清理资源\n        closeAllSerialPorts();\n    }\n    \n    /**\n     * 演示动态更新粘包处理器\n     */\n    public void updateStickyPacketExample(View view) {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // 动态更新GPS串口的粘包处理器，改为按回车换行分包\n        boolean success = manager.updateStickyPacketHelpers(\"GPS\", \n            new AbsStickPackageHelper[]{new SpecifiedStickPackageHelper(\"\\r\\n\")});\n        \n        if (success) {\n            updateStatus(\"GPS串口粘包处理器已更新为\\\\r\\\\n分包\");\n        } else {\n            updateStatus(\"GPS串口粘包处理器更新失败\");\n        }\n    }\n    \n    /**\n     * 展示串口数据路由功能\n     */\n    public void serialRoutingExample() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        \n        // 主控制串口：接收外部命令并路由到其他串口\n        manager.openSerialPort(\"MAIN_CTRL\", \"/dev/ttyS0\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null,\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String command = new String(data).trim();\n                    Log.i(TAG, \"主控制命令: \" + command);\n                    \n                    // 根据命令前缀路由到不同串口\n                    if (command.startsWith(\"GPS:\")) {\n                        String gpsCmd = command.substring(4);\n                        manager.sendData(\"GPS\", gpsCmd + \"\\r\\n\");\n                        updateStatus(\"路由命令到GPS: \" + gpsCmd);\n                    } else if (command.startsWith(\"SENSOR:\")) {\n                        String sensorCmd = command.substring(7);\n                        manager.sendData(\"SENSOR\", sensorCmd + \"\\n\");\n                        updateStatus(\"路由命令到传感器: \" + sensorCmd);\n                    } else if (command.startsWith(\"MODBUS:\")) {\n                        // 这里可以解析十六进制字符串并发送到Modbus\n                        updateStatus(\"路由命令到Modbus: \" + command);\n                    }\n                }\n            });\n        \n        updateStatus(\"串口路由功能已启用，可通过主控制串口发送命令\");\n    }\n    \n    /**\n     * 初始化设备列表\n     */\n    private void initDevices() {\n        SerialPortFinder serialPortFinder = new SerialPortFinder();\n        mDevices = serialPortFinder.getAllDevicesPath();\n        if (mDevices.length == 0) {\n            mDevices = new String[]{\"没有找到串口设备\"};\n        }\n        \n        // 初始化默认配置\n        gpsConfig.device = mDevices.length > 0 ? mDevices[0] : \"\";\n        gpsConfig.baudrate = \"9600\";\n        \n        sensorConfig.device = mDevices.length > 1 ? mDevices[1] : (mDevices.length > 0 ? mDevices[0] : \"\");\n        sensorConfig.baudrate = \"115200\";\n        \n        modbusConfig.device = mDevices.length > 2 ? mDevices[2] : (mDevices.length > 0 ? mDevices[0] : \"\");\n        modbusConfig.baudrate = \"9600\";\n        \n        customConfig.device = mDevices.length > 3 ? mDevices[3] : (mDevices.length > 0 ? mDevices[0] : \"\");\n        customConfig.baudrate = \"115200\";\n    }\n    \n    /**\n     * 设置全局参数下拉框\n     */\n    private void setupGlobalParamSpinners() {\n        // 数据位配置\n        setupSpinner(binding.spinnerDatabits, mDatabits, 0, (position) -> {\n            globalDatabits = Integer.parseInt(mDatabits[position]);\n            updateStatus(\"全局数据位已设置为: \" + globalDatabits);\n            closeAllOpenedPorts();\n        });\n        \n        // 校验位配置\n        setupSpinner(binding.spinnerParity, mParitys, 0, (position) -> {\n            globalParity = position;\n            updateStatus(\"全局校验位已设置为: \" + mParitys[position]);\n            closeAllOpenedPorts();\n        });\n        \n        // 停止位配置\n        setupSpinner(binding.spinnerStopbits, mStopbits, 0, (position) -> {\n            globalStopbits = Integer.parseInt(mStopbits[position]);\n            updateStatus(\"全局停止位已设置为: \" + globalStopbits);\n            closeAllOpenedPorts();\n        });\n    }\n    \n    /**\n     * 关闭所有已打开的串口（参数变更时使用）\n     */\n    private void closeAllOpenedPorts() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        boolean hasOpenPorts = false;\n        \n        if (gpsOpened) {\n            manager.closeSerialPort(\"GPS\");\n            gpsOpened = false;\n            hasOpenPorts = true;\n        }\n        if (sensorOpened) {\n            manager.closeSerialPort(\"SENSOR\");\n            sensorOpened = false;\n            hasOpenPorts = true;\n        }\n        if (modbusOpened) {\n            manager.closeSerialPort(\"MODBUS\");\n            modbusOpened = false;\n            hasOpenPorts = true;\n        }\n        if (customOpened) {\n            manager.closeSerialPort(\"CUSTOM\");\n            customOpened = false;\n            hasOpenPorts = true;\n        }\n        \n        if (hasOpenPorts) {\n            updateButtonStates();\n            updateStatus(\"参数变更，已关闭所有串口，请重新打开\");\n        }\n    }\n    \n    /**\n     * 设置下拉框\n     */\n    private void setupSpinners() {\n        // GPS串口配置\n        setupSpinner(binding.spinnerGpsDevice, mDevices, 0, (position) -> {\n            gpsConfig.device = mDevices[position];\n            if (gpsOpened) {\n                updateStatus(\"GPS串口设备已更改，请重新打开串口\");\n                closeGpsPort();\n            }\n        });\n        \n        setupSpinner(binding.spinnerGpsBaudrate, mBaudrates, 0, (position) -> {\n            gpsConfig.baudrate = mBaudrates[position];\n            if (gpsOpened) {\n                updateStatus(\"GPS串口波特率已更改，请重新打开串口\");\n                closeGpsPort();\n            }\n        });\n        \n        // 传感器串口配置\n        setupSpinner(binding.spinnerSensorDevice, mDevices, mDevices.length > 1 ? 1 : 0, (position) -> {\n            sensorConfig.device = mDevices[position];\n            if (sensorOpened) {\n                updateStatus(\"传感器串口设备已更改，请重新打开串口\");\n                closeSensorPort();\n            }\n        });\n        \n        setupSpinner(binding.spinnerSensorBaudrate, mBaudrates, 4, (position) -> {\n            sensorConfig.baudrate = mBaudrates[position];\n            if (sensorOpened) {\n                updateStatus(\"传感器串口波特率已更改，请重新打开串口\");\n                closeSensorPort();\n            }\n        });\n        \n        // Modbus串口配置\n        setupSpinner(binding.spinnerModbusDevice, mDevices, mDevices.length > 2 ? 2 : 0, (position) -> {\n            modbusConfig.device = mDevices[position];\n            if (modbusOpened) {\n                updateStatus(\"Modbus串口设备已更改，请重新打开串口\");\n                closeModbusPort();\n            }\n        });\n        \n        setupSpinner(binding.spinnerModbusBaudrate, mBaudrates, 0, (position) -> {\n            modbusConfig.baudrate = mBaudrates[position];\n            if (modbusOpened) {\n                updateStatus(\"Modbus串口波特率已更改，请重新打开串口\");\n                closeModbusPort();\n            }\n        });\n        \n        // 自定义串口配置\n        setupSpinner(binding.spinnerCustomDevice, mDevices, mDevices.length > 3 ? 3 : 0, (position) -> {\n            customConfig.device = mDevices[position];\n            if (customOpened) {\n                updateStatus(\"自定义串口设备已更改，请重新打开串口\");\n                closeCustomPort();\n            }\n        });\n        \n        setupSpinner(binding.spinnerCustomBaudrate, mBaudrates, 4, (position) -> {\n            customConfig.baudrate = mBaudrates[position];\n            if (customOpened) {\n                updateStatus(\"自定义串口波特率已更改，请重新打开串口\");\n                closeCustomPort();\n            }\n        });\n    }\n    \n    /**\n     * 设置单个下拉框\n     */\n    private void setupSpinner(Spinner spinner, String[] data, int defaultSelection, OnSpinnerItemSelectedListener listener) {\n        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.spinner_default_item, data);\n        adapter.setDropDownViewResource(R.layout.spinner_item);\n        spinner.setAdapter(adapter);\n        spinner.setSelection(defaultSelection);\n        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                listener.onItemSelected(position);\n            }\n            \n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n            }\n        });\n    }\n    \n    /**\n     * 刷新串口列表\n     */\n    private void refreshSerialPorts() {\n        initDevices();\n        setupSpinners();\n        updateStatus(\"串口列表已刷新，共找到 \" + mDevices.length + \" 个设备\");\n    }\n    \n    /**\n     * 打开所有串口\n     */\n    private void openAllSerialPorts() {\n        updateStatus(\"开始打开所有串口...\");\n        if (!gpsOpened) toggleGpsPort();\n        if (!sensorOpened) toggleSensorPort();\n        if (!modbusOpened) toggleModbusPort();\n        if (!customOpened) toggleCustomPort();\n    }\n    \n    /**\n     * 关闭所有串口\n     */\n    private void closeAllSerialPorts() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        manager.closeAllSerialPorts();\n        \n        gpsOpened = false;\n        sensorOpened = false;\n        modbusOpened = false;\n        customOpened = false;\n        \n        updateButtonStates();\n        updateStatus(\"所有串口已关闭\");\n    }\n    \n    /**\n     * GPS串口开关\n     */\n    private void toggleGpsPort() {\n        if (gpsOpened) {\n            closeGpsPort();\n        } else {\n            openGpsPort();\n        }\n    }\n    \n    private void openGpsPort() {\n        if (gpsConfig.device.equals(\"没有找到串口设备\")) {\n            updateStatus(\"GPS串口：没有可用设备\");\n            return;\n        }\n\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        int baudrate = Integer.parseInt(gpsConfig.baudrate);\n\n        manager.openSerialPort(\"GPS\", gpsConfig.device, baudrate,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(globalDatabits)\n                .setParity(globalParity)\n                .setStopbits(globalStopbits)\n                .setStickyPacketHelpers(new BaseStickPackageHelper())\n                .build(),\n            (serialId, success, status) -> {\n                gpsOpened = success;\n                runOnUiThread(() -> {\n                    updateButtonStates();\n                    updateStatus(String.format(\"GPS串口[%s]: %s\", \n                        gpsConfig.device, success ? \"打开成功\" : \"打开失败 - \" + status));\n                });\n            },\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String text = sanitizeForSingleLine(new String(data));\n                    String hex = bytesToHex(data);\n                    Log.i(TAG, \"GPS数据: \" + text);\n                    runOnUiThread(() -> appendData(\"RX [\" + serialId + \"] len=\" + data.length + \" text=\" + text + \" hex=\" + hex));\n                }\n\n                @Override\n                public void onDataSent(String serialId, byte[] data) {\n                    String text = sanitizeForSingleLine(new String(data));\n                    String hex = bytesToHex(data);\n                    runOnUiThread(() -> appendData(\"TX [\" + serialId + \"] len=\" + data.length + \" text=\" + text + \" hex=\" + hex));\n                }\n            });\n    }\n    \n    private void closeGpsPort() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        manager.closeSerialPort(\"GPS\");\n        gpsOpened = false;\n        updateButtonStates();\n        updateStatus(\"GPS串口已关闭\");\n    }\n    \n    /**\n     * 传感器串口开关\n     */\n    private void toggleSensorPort() {\n        if (sensorOpened) {\n            closeSensorPort();\n        } else {\n            openSensorPort();\n        }\n    }\n    \n    private void openSensorPort() {\n        if (sensorConfig.device.equals(\"没有找到串口设备\")) {\n            updateStatus(\"传感器串口：没有可用设备\");\n            return;\n        }\n\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        int baudrate = Integer.parseInt(sensorConfig.baudrate);\n\n        manager.openSerialPort(\"SENSOR\", sensorConfig.device, baudrate,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(globalDatabits)\n                .setParity(globalParity)\n                .setStopbits(globalStopbits)\n                .setStickyPacketHelpers(new CompositeStickPackageHelper(\n                    new SpecifiedStickPackageHelper(\"\\n\"),\n                    new BaseStickPackageHelper()))\n                .build(),\n            (serialId, success, status) -> {\n                sensorOpened = success;\n                runOnUiThread(() -> {\n                    updateButtonStates();\n                    updateStatus(String.format(\"传感器串口[%s]: %s\", \n                        sensorConfig.device, success ? \"打开成功\" : \"打开失败 - \" + status));\n                });\n            },\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String text = sanitizeForSingleLine(new String(data).trim());\n                    String hex = bytesToHex(data);\n                    Log.i(TAG, \"传感器数据: \" + text);\n                    runOnUiThread(() -> appendData(\"RX [\" + serialId + \"] len=\" + data.length + \" text=\" + text + \" hex=\" + hex));\n                }\n\n                @Override\n                public void onDataSent(String serialId, byte[] data) {\n                    String text = sanitizeForSingleLine(new String(data));\n                    String hex = bytesToHex(data);\n                    runOnUiThread(() -> appendData(\"TX [\" + serialId + \"] len=\" + data.length + \" text=\" + text + \" hex=\" + hex));\n                }\n            });\n    }\n    \n    private void closeSensorPort() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        manager.closeSerialPort(\"SENSOR\");\n        sensorOpened = false;\n        updateButtonStates();\n        updateStatus(\"传感器串口已关闭\");\n    }\n    \n    /**\n     * Modbus串口开关\n     */\n    private void toggleModbusPort() {\n        if (modbusOpened) {\n            closeModbusPort();\n        } else {\n            openModbusPort();\n        }\n    }\n    \n    private void openModbusPort() {\n        if (modbusConfig.device.equals(\"没有找到串口设备\")) {\n            updateStatus(\"Modbus串口：没有可用设备\");\n            return;\n        }\n\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        int baudrate = Integer.parseInt(modbusConfig.baudrate);\n\n        manager.openSerialPort(\"MODBUS\", modbusConfig.device, baudrate,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(globalDatabits)\n                .setParity(globalParity)\n                .setStopbits(globalStopbits)\n                .setStickyPacketHelpers(new CompositeStickPackageHelper(\n                    new StaticLenStickPackageHelper(8),\n                    new BaseStickPackageHelper()))\n                .build(),\n            (serialId, success, status) -> {\n                modbusOpened = success;\n                runOnUiThread(() -> {\n                    updateButtonStates();\n                    updateStatus(String.format(\"Modbus串口[%s]: %s\", \n                        modbusConfig.device, success ? \"打开成功\" : \"打开失败 - \" + status));\n                });\n            },\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String modbusData = bytesToHex(data);\n                    Log.i(TAG, \"Modbus数据: \" + modbusData);\n                    runOnUiThread(() -> appendData(\"RX [\" + serialId + \"] len=\" + data.length + \" hex=\" + modbusData));\n                }\n\n                @Override\n                public void onDataSent(String serialId, byte[] data) {\n                    String hex = bytesToHex(data);\n                    runOnUiThread(() -> appendData(\"TX [\" + serialId + \"] len=\" + data.length + \" hex=\" + hex));\n                }\n            });\n    }\n    \n    private void closeModbusPort() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        manager.closeSerialPort(\"MODBUS\");\n        modbusOpened = false;\n        updateButtonStates();\n        updateStatus(\"Modbus串口已关闭\");\n    }\n    \n    /**\n     * 自定义串口开关\n     */\n    private void toggleCustomPort() {\n        if (customOpened) {\n            closeCustomPort();\n        } else {\n            openCustomPort();\n        }\n    }\n    \n    private void openCustomPort() {\n        if (customConfig.device.equals(\"没有找到串口设备\")) {\n            updateStatus(\"自定义串口：没有可用设备\");\n            return;\n        }\n\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        int baudrate = Integer.parseInt(customConfig.baudrate);\n\n        manager.openSerialPort(\"CUSTOM\", customConfig.device, baudrate,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(globalDatabits)\n                .setParity(globalParity)\n                .setStopbits(globalStopbits)\n                .setStickyPacketHelpers(new CompositeStickPackageHelper(\n                    new SpecifiedStickPackageHelper(\"$$START$$\", \"$$END$$\"),\n                    new BaseStickPackageHelper()))\n                .build(),\n            (serialId, success, status) -> {\n                customOpened = success;\n                runOnUiThread(() -> {\n                    updateButtonStates();\n                    updateStatus(String.format(\"自定义串口[%s]: %s\", \n                        customConfig.device, success ? \"打开成功\" : \"打开失败 - \" + status));\n                });\n            },\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String text = sanitizeForSingleLine(new String(data));\n                    String hex = bytesToHex(data);\n                    Log.i(TAG, \"自定义协议数据: \" + text);\n                    runOnUiThread(() -> appendData(\"RX [\" + serialId + \"] len=\" + data.length + \" text=\" + text + \" hex=\" + hex));\n                }\n\n                @Override\n                public void onDataSent(String serialId, byte[] data) {\n                    String text = sanitizeForSingleLine(new String(data));\n                    String hex = bytesToHex(data);\n                    runOnUiThread(() -> appendData(\"TX [\" + serialId + \"] len=\" + data.length + \" text=\" + text + \" hex=\" + hex));\n                }\n            });\n    }\n    \n    private void closeCustomPort() {\n        MultiSerialPortManager manager = SimpleSerialPortManager.multi();\n        manager.closeSerialPort(\"CUSTOM\");\n        customOpened = false;\n        updateButtonStates();\n        updateStatus(\"自定义串口已关闭\");\n    }\n    \n    /**\n     * 更新按钮状态\n     */\n    private void updateButtonStates() {\n        binding.btnGpsToggle.setText(gpsOpened ? \"关闭\" : \"打开\");\n        binding.btnGpsToggle.setBackgroundColor(getResources().getColor(\n            gpsOpened ? R.color.secondary_text : R.color.colorAccent));\n        \n        binding.btnSensorToggle.setText(sensorOpened ? \"关闭\" : \"打开\");\n        binding.btnSensorToggle.setBackgroundColor(getResources().getColor(\n            sensorOpened ? R.color.secondary_text : R.color.colorAccent));\n        \n        binding.btnModbusToggle.setText(modbusOpened ? \"关闭\" : \"打开\");\n        binding.btnModbusToggle.setBackgroundColor(getResources().getColor(\n            modbusOpened ? R.color.secondary_text : R.color.colorAccent));\n        \n        binding.btnCustomToggle.setText(customOpened ? \"关闭\" : \"打开\");\n        binding.btnCustomToggle.setBackgroundColor(getResources().getColor(\n            customOpened ? R.color.secondary_text : R.color.colorAccent));\n    }\n    \n    /**\n     * 清空日志\n     */\n    private void clearLog() {\n        if (statusText != null) {\n            statusText.setText(\"日志已清空\");\n        }\n        if (dataText != null) {\n            dataText.setText(\"数据已清空\");\n        }\n    }\n    \n    /**\n     * 串口配置类\n     */\n    private static class SerialPortConfig {\n        String device = \"\";\n        String baudrate = \"9600\";\n    }\n    \n    /**\n     * 下拉框选择监听器\n     */\n    private interface OnSpinnerItemSelectedListener {\n        void onItemSelected(int position);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/SelectSerialPortActivity.kt",
    "content": "package com.cl.myapplication\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.AdapterView\nimport android.widget.AdapterView.OnItemClickListener\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.databinding.DataBindingUtil\nimport com.cl.myapplication.adapter.DeviceAdapter\nimport com.cl.myapplication.databinding.ActivitySelectSerialPortBinding\nimport com.cl.serialportlibrary.SerialPortFinder\n\nclass SelectSerialPortActivity : AppCompatActivity(), OnItemClickListener {\n\n\n    private var mDeviceAdapter: DeviceAdapter? = null\n    val DEVICE = \"device\"\n    private lateinit var binding: ActivitySelectSerialPortBinding\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = DataBindingUtil.setContentView(this, R.layout.activity_select_serial_port)\n\n        val serialPortFinder = SerialPortFinder()\n        val devices = serialPortFinder.devices\n        if (binding.lvDevices != null) {\n            binding.lvDevices.emptyView = binding.tvEmpty\n            mDeviceAdapter = DeviceAdapter(applicationContext, devices)\n            binding.lvDevices.adapter = mDeviceAdapter\n            binding.lvDevices.onItemClickListener = this\n        }\n\n    }\n\n    override fun onItemClick(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {\n        val device = mDeviceAdapter!!.getItem(position)\n        val intent = Intent(this, MainActivity::class.java)\n        intent.putExtra(DEVICE, device)\n        startActivity(intent)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/SingleSerialPortActivity.java",
    "content": "package com.cl.myapplication;\n\nimport android.app.Activity;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.AdapterView;\nimport android.widget.ArrayAdapter;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.core.app.ActivityCompat;\nimport androidx.databinding.DataBindingUtil;\nimport androidx.fragment.app.FragmentManager;\n\nimport com.cl.myapplication.adapter.SpAdapter;\nimport com.cl.myapplication.constant.PreferenceKeys;\nimport com.cl.myapplication.databinding.ActivityMainJavaBinding;\nimport com.cl.myapplication.fragment.LogFragment;\nimport com.cl.myapplication.message.ConversionNoticeEvent;\nimport com.cl.myapplication.message.IMessage;\nimport com.cl.myapplication.message.LogManager;\nimport com.cl.myapplication.message.RecvMessage;\nimport com.cl.myapplication.message.SendMessage;\nimport com.cl.myapplication.util.PrefHelper;\nimport com.hjq.toast.ToastUtils;\nimport com.cl.serialportlibrary.Device;\nimport com.cl.serialportlibrary.SerialPortFinder;\nimport com.cl.serialportlibrary.SimpleSerialPortManager;\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\n\nimport org.greenrobot.eventbus.EventBus;\nimport org.greenrobot.eventbus.Subscribe;\nimport org.greenrobot.eventbus.ThreadMode;\n\nimport java.util.Arrays;\n\n/**\n * 单串口演示\n */\npublic class SingleSerialPortActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {\n\n    private ActivityMainJavaBinding binding;\n    private Device mDevice;\n\n    private String[] mDevices;\n    private String[] mBaudrates;\n    private int mDeviceIndex;\n    private int mBaudrateIndex;\n    private boolean mOpened = false;\n    private boolean mConversionNotice = true;\n    private LogFragment mLogFragment;\n\n    final String[] databits = new String[]{\"8\", \"7\", \"6\", \"5\"};\n    final String[] paritys = new String[]{\"NONE\", \"ODD\", \"EVEN\", \"SPACE\", \"MARK\"};\n    final String[] stopbits = new String[]{\"1\", \"2\"};\n\n    //先定义\n    private static final int REQUEST_EXTERNAL_STORAGE = 1;\n\n    private static String[] PERMISSIONS_STORAGE = {\n            \"android.permission.READ_EXTERNAL_STORAGE\",\n            \"android.permission.WRITE_EXTERNAL_STORAGE\"};\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        binding = DataBindingUtil.setContentView(this, R.layout.activity_main_java);\n        verifyStoragePermissions(this);\n        initFragment();\n        initDevice();\n        initSpinners();\n\n        //设置数据位\n        SpAdapter spAdapter1 = new SpAdapter(this);\n        spAdapter1.setDatas(databits);\n        binding.spDatabits.setAdapter(spAdapter1);\n        binding.spDatabits.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                closeSerialPort();\n                int dataBitValue = Integer.parseInt(databits[position]);\n                SimpleSerialPortManager.getInstance().setDatabits(dataBitValue);\n                SerialPortLogUtil.i(\"MainJavaActivity\", \"设置数据位: \" + dataBitValue);\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n\n            }\n        });\n\n        //设置数据位\n        SpAdapter spAdapter2 = new SpAdapter(this);\n        spAdapter2.setDatas(paritys);\n        binding.spParity.setAdapter(spAdapter2);\n        binding.spParity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                closeSerialPort();\n                SimpleSerialPortManager.getInstance().setParity(position);\n                SerialPortLogUtil.i(\"MainJavaActivity\", \"设置校验位: \" + paritys[position] + \" (值: \" + position + \")\");\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n\n            }\n        });\n\n        //设置停止位\n        SpAdapter spAdapter3 = new SpAdapter(this);\n        spAdapter3.setDatas(stopbits);\n        binding.spStopbits.setAdapter(spAdapter3);\n        binding.spStopbits.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {\n            @Override\n            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n                closeSerialPort();\n                int stopBitValue = Integer.parseInt(stopbits[position]);\n                SimpleSerialPortManager.getInstance().setStopbits(stopBitValue);\n                SerialPortLogUtil.i(\"MainJavaActivity\", \"设置停止位: \" + stopBitValue);\n            }\n\n            @Override\n            public void onNothingSelected(AdapterView<?> parent) {\n\n            }\n        });\n\n        binding.btnOpenDevice.setOnClickListener(v -> {\n            if (mOpened) {\n                closeSerialPort();\n            } else {\n                openSerialPort();\n            }\n        });\n\n        binding.btnSendData.setOnClickListener((view) -> {\n            onSend();\n        });\n    }\n    \n    /**\n     * 打开串口\n     */\n    private void openSerialPort() {\n        String devicePath = mDevice.getName();\n        int baudRate = Integer.parseInt(mDevice.getRoot());\n        \n        SerialPortLogUtil.i(\"MainJavaActivity\", \"打开的串口为：\" + devicePath + \"----\" + baudRate);\n        \n        // 使用SimpleSerialPortManager打开串口\n        boolean success = SimpleSerialPortManager.getInstance()\n                .openSerialPort(devicePath, baudRate,\n                        // 打开状态回调\n                        (isSuccess, status) -> {\n                            runOnUiThread(() -> {\n                                switch (status) {\n                                    case SUCCESS_OPENED:\n                                        ToastUtils.show(\"串口打开成功\");\n                                        mOpened = true;\n                                        updateViewState(true);\n                                        break;\n                                    case NO_READ_WRITE_PERMISSION:\n                                        ToastUtils.show(\"没有读写权限\");\n                                        updateViewState(false);\n                                        break;\n                                    case OPEN_FAIL:\n                                        ToastUtils.show(\"串口打开失败\");\n                                        updateViewState(false);\n                                        break;\n                                }\n                            });\n                        },\n                        // 数据接收回调\n                        new SimpleSerialPortManager.OnDataReceivedCallback() {\n                            @Override\n                            public void onDataReceived(byte[] data) {\n                                SerialPortLogUtil.i(\"MainJavaActivity\", \"onDataReceived [ byte[] ]: \" + Arrays.toString(data));\n                                SerialPortLogUtil.i(\"MainJavaActivity\", \"onDataReceived [ String ]: \" + new String(data));\n                                \n                                runOnUiThread(() -> {\n                                    if (mConversionNotice) {\n                                        LogManager.instance().post(new RecvMessage(bytesToHex(data)));\n                                    } else {\n                                        LogManager.instance().post(new RecvMessage(Arrays.toString(data)));\n                                    }\n                                });\n                            }\n                            \n                            @Override\n                            public void onDataSent(byte[] data) {\n                                SerialPortLogUtil.i(\"MainJavaActivity\", \"onDataSent [ byte[] ]: \" + Arrays.toString(data));\n                                SerialPortLogUtil.i(\"MainJavaActivity\", \"onDataSent [ String ]: \" + new String(data));\n                                \n                                runOnUiThread(() -> {\n                                    if (mConversionNotice) {\n                                        LogManager.instance().post(new SendMessage(bytesToHex(data)));\n                                    } else {\n                                        LogManager.instance().post(new SendMessage(Arrays.toString(data)));\n                                    }\n                                });\n                            }\n                        });\n    }\n    \n    private void closeSerialPort() {\n        SimpleSerialPortManager.getInstance().closeSerialPort();\n        mOpened = false;\n        updateViewState(mOpened);\n    }\n\n    public static void verifyStoragePermissions(Activity activity) {\n        try {\n            //检测是否有写的权限\n            int permission = ActivityCompat.checkSelfPermission(activity,\n                    \"android.permission.WRITE_EXTERNAL_STORAGE\");\n            if (permission != PackageManager.PERMISSION_GRANTED) {\n                // 没有写的权限，去申请写的权限，会弹出对话框\n                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 更新视图状态\n     *\n     * @param isSerialPortOpened\n     */\n    private void updateViewState(boolean isSerialPortOpened) {\n        int stringRes = isSerialPortOpened ? R.string.close_serial_port : R.string.open_serial_port;\n        binding.btnOpenDevice.setText(stringRes);\n        binding.spinnerDevices.setEnabled(!isSerialPortOpened);\n        binding.spinnerBaudrate.setEnabled(!isSerialPortOpened);\n        binding.btnSendData.setEnabled(isSerialPortOpened);\n        binding.btnLoadList.setEnabled(isSerialPortOpened);\n    }\n\n    /**\n     * 初始化设备列表\n     */\n    private void initDevice() {\n        PrefHelper.initDefault(this);\n        SerialPortFinder serialPortFinder = new SerialPortFinder();\n        // 设备\n        mDevices = serialPortFinder.getAllDevicesPath();\n        if (mDevices.length == 0) {\n            mDevices = new String[]{\n                    getString(R.string.no_serial_device)\n            };\n        }\n        // 波特率\n        mBaudrates = getResources().getStringArray(R.array.baudrates);\n\n        mDeviceIndex = PrefHelper.getDefault().getInt(PreferenceKeys.SERIAL_PORT_DEVICES, 0);\n        mDeviceIndex = mDeviceIndex >= mDevices.length ? mDevices.length - 1 : mDeviceIndex;\n        mBaudrateIndex = PrefHelper.getDefault().getInt(PreferenceKeys.BAUD_RATE, 0);\n\n        mDevice = new Device(mDevices[mDeviceIndex], mBaudrates[mBaudrateIndex], null);\n    }\n\n\n    /**\n     * 初始化下拉选项\n     */\n    private void initSpinners() {\n        ArrayAdapter<String> deviceAdapter =\n                new ArrayAdapter<String>(this, R.layout.spinner_default_item, mDevices);\n        deviceAdapter.setDropDownViewResource(R.layout.spinner_item);\n        binding.spinnerDevices.setAdapter(deviceAdapter);\n        binding.spinnerDevices.setOnItemSelectedListener(this);\n\n        ArrayAdapter<String> baudrateAdapter =\n                new ArrayAdapter<String>(this, R.layout.spinner_default_item, mBaudrates);\n        baudrateAdapter.setDropDownViewResource(R.layout.spinner_item);\n        binding.spinnerBaudrate.setAdapter(baudrateAdapter);\n        binding.spinnerBaudrate.setOnItemSelectedListener(this);\n\n        binding.spinnerDevices.setSelection(mDeviceIndex);\n        binding.spinnerBaudrate.setSelection(mBaudrateIndex);\n    }\n\n\n    /**\n     * 发送数据\n     */\n    public void onSend() {\n        String sendContent = binding.etData.getText().toString().trim();\n        if (TextUtils.isEmpty(sendContent)) {\n            SerialPortLogUtil.i(\"MainJavaActivity\", \"onSend: 发送内容为 null\");\n            return;\n        }\n        byte[] sendContentBytes = sendContent.getBytes();\n        // 使用SimpleSerialPortManager发送数据\n        boolean sendBytes = SimpleSerialPortManager.getInstance().sendData(sendContentBytes);\n        SerialPortLogUtil.i(\"MainJavaActivity\", \"onSend: sendBytes = \" + sendBytes);\n    }\n\n    @Override\n    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {\n        // Spinner 选择监听\n        int parentId = parent.getId();\n        if (parentId == R.id.spinner_devices) {\n            mDeviceIndex = position;\n            mDevice.setName(mDevices[mDeviceIndex]);\n        } else if (parentId == R.id.spinner_baudrate) {\n            mBaudrateIndex = position;\n            mDevice.setRoot(mBaudrates[mBaudrateIndex]);\n        }\n    }\n\n    @Override\n    public void onNothingSelected(AdapterView<?> parent) {\n\n    }\n\n    @Override\n    protected void onDestroy() {\n        SimpleSerialPortManager.getInstance().closeSerialPort();\n        super.onDestroy();\n    }\n\n    @Override\n    public void onStart() {\n        super.onStart();\n        EventBus.getDefault().register(this);\n    }\n\n    @Override\n    public void onStop() {\n        super.onStop();\n        EventBus.getDefault().unregister(this);\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        refreshLogList();\n    }\n\n    /**\n     * 初始化日志Fragment\n     */\n    protected void initFragment() {\n        FragmentManager fragmentManager = getSupportFragmentManager();\n        mLogFragment = (LogFragment) fragmentManager.findFragmentById(R.id.log_fragment);\n    }\n\n\n    /**\n     * 刷新日志列表\n     */\n    protected void refreshLogList() {\n        mLogFragment.updateAutoEndButton();\n        mLogFragment.updateList();\n    }\n\n    @Subscribe(threadMode = ThreadMode.MAIN)\n    public void onMessageEvent(IMessage message) {\n        // 收到时间，刷新界面\n        mLogFragment.add(message);\n    }\n\n    @Subscribe(threadMode = ThreadMode.MAIN)\n    public void onConversionNotice(ConversionNoticeEvent messageEvent) {\n        if (messageEvent.getMessage().equals(\"1\")) {\n            mConversionNotice = false;\n        } else {\n            mConversionNotice = true;\n        }\n\n    }\n\n\n    /**\n     * 字节数组转16进制\n     *\n     * @param bytes 需要转换的byte数组\n     * @return 转换后的Hex字符串\n     */\n    public static String bytesToHex(byte[] bytes) {\n        StringBuffer sb = new StringBuffer();\n        for (int i = 0; i < bytes.length; i++) {\n            String hex = Integer.toHexString(bytes[i] & 0xFF);\n            if (hex.length() < 2) {\n                sb.append(0);\n            }\n            sb.append(hex);\n        }\n        return sb.toString();\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/adapter/DeviceAdapter.java",
    "content": "package com.cl.myapplication.adapter;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\n\nimport androidx.databinding.DataBindingUtil;\n\nimport com.cl.myapplication.R;\nimport com.cl.myapplication.databinding.ItemDeviceBinding;\nimport com.cl.serialportlibrary.Device;\n\nimport java.io.File;\nimport java.util.ArrayList;\n\n/**\n * 串口列表适配器\n */\npublic class DeviceAdapter extends BaseAdapter {\n\n    private LayoutInflater mInflater;\n    private ArrayList<Device> devices;\n\n    public DeviceAdapter(Context context, ArrayList<Device> devices) {\n        this.mInflater = LayoutInflater.from(context);\n        this.devices = devices;\n    }\n\n    @Override\n    public int getCount() {\n        return devices.size();\n    }\n\n    @Override\n    public Device getItem(int position) {\n        return devices.get(position);\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        ItemDeviceBinding binding;\n        if (null == convertView) {\n            binding = DataBindingUtil.inflate(mInflater, R.layout.item_device, parent, false);\n            convertView = binding.getRoot();\n            convertView.setTag(binding);\n        } else {\n            binding = (ItemDeviceBinding) convertView.getTag();\n        }\n\n        String deviceName = devices.get(position).getName();\n        String driverName = devices.get(position).getRoot();\n        File file = devices.get(position).getFile();\n        boolean canRead = file.canRead();\n        boolean canWrite = file.canWrite();\n        boolean canExecute = file.canExecute();\n        String path = file.getAbsolutePath();\n\n        StringBuffer permission = new StringBuffer();\n        permission.append(\"\\t权限[\");\n        permission.append(canRead ? \" 可读 \" : \" 不可读 \");\n        permission.append(canWrite ? \" 可写 \" : \" 不可写 \");\n        permission.append(canExecute ? \" 可执行 \" : \" 不可执行 \");\n        permission.append(\"]\");\n\n        binding.tvDevice.setText(String.format(\"%s [%s] (%s)  %s\", deviceName, driverName, path, permission));\n\n        return convertView;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/adapter/SpAdapter.java",
    "content": "package com.cl.myapplication.adapter;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\n\nimport androidx.databinding.DataBindingUtil;\n\nimport com.cl.myapplication.R;\nimport com.cl.myapplication.databinding.ItemDeviceBinding;\n\n\npublic class SpAdapter extends BaseAdapter {\n\n    String[] datas;\n    Context mContext;\n\n    public SpAdapter(Context context) {\n        this.mContext = context;\n    }\n\n    public void setDatas(String[] datas) {\n        this.datas = datas;\n        notifyDataSetChanged();\n    }\n\n    @Override\n    public int getCount() {\n        return datas == null ? 0 : datas.length;\n    }\n\n    @Override\n    public Object getItem(int position) {\n        return datas == null ? null : datas[position];\n    }\n\n    @Override\n    public long getItemId(int position) {\n        return position;\n    }\n\n    @Override\n    public View getView(int position, View convertView, ViewGroup parent) {\n        ItemDeviceBinding binding;\n        if (convertView == null) {\n            binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.item_device, parent, false);\n            convertView = binding.getRoot();\n            convertView.setTag(binding);\n        } else {\n            binding = (ItemDeviceBinding) convertView.getTag();\n        }\n\n        binding.tvDevice.setText(datas[position]);\n\n        return convertView;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/constant/PreferenceKeys.java",
    "content": "package com.cl.myapplication.constant;\n\n\npublic class PreferenceKeys {\n\n    /**\n     * 串口设备\n     */\n    public static String SERIAL_PORT_DEVICES = \"serial_port_devices\";\n    /**\n     * 波特率\n     */\n    public static String BAUD_RATE = \"baud_rate\";\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/fragment/LogFragment.java",
    "content": "package com.cl.myapplication.fragment;\n\nimport android.os.Bundle;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\n\nimport androidx.annotation.Nullable;\nimport androidx.databinding.DataBindingUtil;\nimport androidx.fragment.app.Fragment;\n\nimport com.cl.myapplication.R;\nimport com.cl.myapplication.databinding.FragmentLogBinding;\nimport com.cl.myapplication.message.ConversionNoticeEvent;\nimport com.cl.myapplication.message.IMessage;\nimport com.cl.myapplication.message.LogManager;\nimport com.cl.myapplication.util.ListViewHolder;\n\nimport org.greenrobot.eventbus.EventBus;\n\n\npublic class LogFragment extends Fragment {\n\n    private LogAdapter mAdapter;\n    private boolean mConversionNotice = true;\n    private FragmentLogBinding binding;\n\n    @Nullable\n    @Override\n    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n                             @Nullable Bundle savedInstanceState) {\n\n        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_log, container, false);\n\n        binding.btnClearLog.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                // 清空列表\n                LogManager.instance().clear();\n                updateList();\n            }\n        });\n        \n        binding.btnAutoEnd.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                LogManager.instance().changAutoEnd();\n                updateAutoEndButton();\n            }\n        });\n        \n        binding.btnWhetherHexadecimal.setOnClickListener((view1) -> {\n            if (mConversionNotice){\n                EventBus.getDefault().post(new ConversionNoticeEvent(\"1\"));\n                mConversionNotice=false;\n            }else {\n                EventBus.getDefault().post(new ConversionNoticeEvent(\"2\"));\n                mConversionNotice=true;\n            }\n        });\n\n        mAdapter = new LogAdapter();\n        binding.lvLogs.setAdapter(mAdapter);\n\n        updateAutoEndButton();\n        return binding.getRoot();\n    }\n\n    public void updateAutoEndButton() {\n        if (binding != null) {\n            if (LogManager.instance().isAutoEnd()) {\n                binding.btnAutoEnd.setText(\"禁止自动显示最新日志\");\n                binding.lvLogs.setSelection(mAdapter.getCount() - 1);\n            } else {\n                binding.btnAutoEnd.setText(\"自动显示最新日志\");\n            }\n        }\n    }\n\n    private static class LogAdapter extends BaseAdapter {\n\n        @Override\n        public int getCount() {\n            return LogManager.instance().messages.size();\n        }\n\n        @Override\n        public IMessage getItem(int position) {\n            return LogManager.instance().messages.get(position);\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n\n            IMessage message = getItem(position);\n\n            ListViewHolder holder;\n            if (convertView == null) {\n                holder = new ListViewHolder(R.layout.item_log, parent);\n                convertView = holder.getItemView();\n            } else {\n                holder = (ListViewHolder) convertView.getTag();\n            }\n\n            TextView tvLog = holder.getText(R.id.tv_log);\n            TextView tvNum = holder.getText(R.id.tv_num);\n\n            tvLog.setText(message.getMessage());\n            tvLog.setEnabled(message.isToSend());\n\n            tvNum.setText(String.valueOf(position + 1));\n\n            return convertView;\n        }\n    }\n\n    public void add(IMessage message) {\n        LogManager.instance().add(message);\n        updateList();\n    }\n\n    public void updateList() {\n        if (binding != null) {\n            mAdapter.notifyDataSetChanged();\n            if (LogManager.instance().isAutoEnd()) {\n                binding.lvLogs.setSelection(mAdapter.getCount() - 1);\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/message/ConversionNoticeEvent.java",
    "content": "package com.cl.myapplication.message;\n\n/**\n * 项目：serialPort\n * 作者：Arry\n * 创建日期：2021/10/20\n * 描述：\n * 修订历史：\n */\npublic class ConversionNoticeEvent {\n\n    private String message;\n\n    public ConversionNoticeEvent(String message) {\n        this.message = message;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/message/IMessage.java",
    "content": "package com.cl.myapplication.message;\n\n/**\n * 日志消息数据接口\n */\n\npublic interface IMessage {\n    /**\n     * 消息文本\n     *\n     * @return\n     */\n    String getMessage();\n\n    /**\n     * 是否发送的消息\n     *\n     * @return\n     */\n    boolean isToSend();\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/message/LogManager.java",
    "content": "package com.cl.myapplication.message;\n\nimport org.greenrobot.eventbus.EventBus;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * log管理类\n */\n\npublic class LogManager {\n\n    public final List<IMessage> messages;\n    private boolean mAutoEnd = true;\n\n    public LogManager() {\n        messages = new ArrayList<>();\n    }\n\n    private static class InstanceHolder {\n\n        public static LogManager sManager = new LogManager();\n    }\n\n    public static LogManager instance() {\n        return InstanceHolder.sManager;\n    }\n\n    public void add(IMessage message) {\n        messages.add(message);\n    }\n\n    public void post(IMessage message) {\n        EventBus.getDefault().post(message);\n    }\n\n    public void clear() {\n        messages.clear();\n    }\n\n    public boolean isAutoEnd() {\n        return mAutoEnd;\n    }\n\n    public void setAutoEnd(boolean autoEnd) {\n        mAutoEnd = autoEnd;\n    }\n\n    public void changAutoEnd() {\n        mAutoEnd = !mAutoEnd;\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/message/RecvMessage.java",
    "content": "package com.cl.myapplication.message;\n\n\nimport com.cl.myapplication.util.TimeUtil;\n\n/**\n * 收到的日志\n */\n\npublic class RecvMessage implements IMessage {\n    \n    private String command;\n    private String message;\n\n    public RecvMessage(String command) {\n        this.command = command;\n        this.message = TimeUtil.currentTime() + \"    收到命令：\" + command;\n    }\n\n    @Override\n    public String getMessage() {\n        return message;\n    }\n\n    @Override\n    public boolean isToSend() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/message/SendMessage.java",
    "content": "package com.cl.myapplication.message;\n\n\nimport com.cl.myapplication.util.TimeUtil;\n\n/**\n * 发送的日志\n */\n\npublic class SendMessage implements IMessage {\n\n    private String command;\n    private String message;\n\n    public SendMessage(String command) {\n        this.command = command;\n        this.message = TimeUtil.currentTime() + \"    发送命令：\" + command;\n    }\n\n    @Override\n    public String getMessage() {\n        return message;\n    }\n\n    @Override\n    public boolean isToSend() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/util/ByteUtil.java",
    "content": "package com.cl.myapplication.util;\n\n/**\n * name：cl\n * date：2022/12/27\n * desc：Byte工具类\n */\npublic class ByteUtil {\n\n\n    /**\n     * 字节数组转16进制\n     *\n     * @param bytes 需要转换的byte数组\n     * @return 转换后的Hex字符串\n     */\n    public static String bytesToHex(byte[] bytes) {\n        StringBuffer sb = new StringBuffer();\n        for (int i = 0; i < bytes.length; i++) {\n            String hex = Integer.toHexString(bytes[i] & 0xFF);\n            if (hex.length() < 2) {\n                sb.append(0);\n            }\n            sb.append(hex);\n        }\n        return sb.toString();\n    }\n\n\n    public static String trim(String s) {\n        int i = s.length();// 字符串最后一个字符的位置\n        int j = 0;// 字符串第一个字符\n        int k = 0;// 中间变量\n        char[] arrayOfChar = s.toCharArray();// 将字符串转换成字符数组\n        while ((j < i) && (arrayOfChar[(k + j)] <= ' '))\n            ++j;// 确定字符串前面的空格数\n        while ((j < i) && (arrayOfChar[(k + i - 1)] <= ' '))\n            --i;// 确定字符串后面的空格数\n        return (((j > 0) || (i < s.length())) ? s.substring(j, i) : s);// 返回去除空格后的字符串\n    }\n\n    public static String toChineseHex(String s) {\n        String ss = s;\n        byte[] bt = new byte[0];\n\n        try {\n            bt = ss.getBytes(\"UTF-8\");\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        String s1 = \"\";\n        for (int i = 0; i < bt.length; i++) {\n            String tempStr = Integer.toHexString(bt[i]);\n            if (tempStr.length() > 2)\n                tempStr = tempStr.substring(tempStr.length() - 2);\n            s1 = s1 + tempStr + \"\";\n        }\n        return s1.toUpperCase();\n    }\n\n\n    /**\n     * 异或校验，返回一个字节\n     */\n    public static byte orVerification(byte[] bytes) {\n        int nAll = 0;\n        for (int i = 0; i < bytes.length; i++) {\n            int nTemp = bytes[i];\n            nAll = nAll ^ nTemp;\n        }\n        return (byte) nAll;\n    }\n\n    public static byte complement(byte[] bytes) {\n        int iSum = 0;\n        for (int i = 0; i < bytes.length; i++) {\n            iSum += bytes[i];\n        }\n        iSum = 256 - iSum;\n        return (byte) iSum;\n    }\n\n\n    /**\n     * 多个byte数组合并\n     */\n    public static byte[] byteMergerAll(byte[]... values) {\n        int length_byte = 0;\n        for (int i = 0; i < values.length; i++) {\n            length_byte += values[i].length;\n        }\n        byte[] all_byte = new byte[length_byte];\n        int countLength = 0;\n        for (int i = 0; i < values.length; i++) {\n            byte[] b = values[i];\n            System.arraycopy(b, 0, all_byte, countLength, b.length);\n            countLength += b.length;\n        }\n        return all_byte;\n    }\n\n\n    public static byte[] hex2Byte(String hex) {\n        String[] parts = hex.split(\" \");\n        byte[] bytes = new byte[parts.length];\n        for (int i = 0; i < parts.length; i++) {\n            bytes[i] = (byte) Integer.parseInt(parts[i], 16);\n        }\n        return bytes;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/util/ListViewHolder.java",
    "content": "package com.cl.myapplication.util;\n\nimport android.util.SparseArray;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\npublic class ListViewHolder {\n\n    private SparseArray<View> mViewArray;\n    public View itemView;\n    public int position;\n\n    public ListViewHolder(View itemView) {\n        this.itemView = itemView;\n        mViewArray = new SparseArray<>();\n        this.itemView.setTag(this);\n    }\n\n    public ListViewHolder(int layoutId, ViewGroup parent) {\n        View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);\n        this.itemView = view;\n        mViewArray = new SparseArray<>();\n        this.itemView.setTag(this);\n    }\n\n    public View getItemView() {\n        return itemView;\n    }\n\n    public void bindPosition(int position) {\n        this.position = position;\n    }\n\n    public int getPosition() {\n        return position;\n    }\n\n    public <V extends View> V getView(int resId) {\n        View view = mViewArray.get(resId);\n        if (view == null) {\n            view = itemView.findViewById(resId);\n            mViewArray.put(resId, view);\n        }\n        return (V) view;\n    }\n\n    public void setText(int resId, CharSequence text) {\n        TextView textView = getView(resId);\n        textView.setText(text);\n    }\n\n    public TextView getText(int id) {\n        return getView(id);\n    }\n\n    public ImageView getImage(int id) {\n        return getView(id);\n    }\n}\n    \n    \n\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/util/PrefHelper.java",
    "content": "package com.cl.myapplication.util;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.preference.PreferenceManager;\n\n\npublic class PrefHelper {\n\n    private static PrefHelper sInstance;\n\n    private SharedPreferences mPreferences;\n\n    public static void initDefault(Context context) {\n        sInstance = new PrefHelper(PreferenceManager.getDefaultSharedPreferences(context));\n    }\n\n    public static PrefHelper getDefault() {\n        return sInstance;\n    }\n\n    public static PrefHelper get(Context context, String name) {\n        return new PrefHelper(context, name);\n    }\n\n    private PrefHelper(SharedPreferences preferences) {\n        mPreferences = preferences;\n    }\n\n    private PrefHelper(Context context, String name) {\n        mPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE);\n    }\n\n    public SharedPreferences.Editor edit() {\n        return mPreferences.edit();\n    }\n\n    public SharedPreferences.Editor putInt(String key, int value) {\n        return edit().putInt(key, value);\n    }\n\n    public void saveInt(String key, int value) {\n        putInt(key, value).apply();\n    }\n\n    public int getInt(String key, int defValue) {\n        return mPreferences.getInt(key, defValue);\n    }\n\n    public SharedPreferences.Editor putFloat(String key, float value) {\n        return edit().putFloat(key, value);\n    }\n\n    public void saveFloat(String key, float value) {\n        putFloat(key, value).apply();\n    }\n\n    public float getFloat(String key, float defValue) {\n        return mPreferences.getFloat(key, defValue);\n    }\n\n    public SharedPreferences.Editor putBoolean(String key, boolean value) {\n        return edit().putBoolean(key, value);\n    }\n\n    public void saveBoolean(String key, boolean value) {\n        putBoolean(key, value).apply();\n    }\n\n    public boolean getBoolean(String key, boolean defValue) {\n        return mPreferences.getBoolean(key, defValue);\n    }\n\n    public SharedPreferences.Editor putLong(String key, long value) {\n        return edit().putLong(key, value);\n    }\n\n    public void saveLong(String key, long value) {\n        putLong(key, value).apply();\n    }\n\n    public long getLong(String key, long defValue) {\n        return mPreferences.getLong(key, defValue);\n    }\n\n    public SharedPreferences.Editor putString(String key, String value) {\n        return edit().putString(key, value);\n    }\n\n    public void saveString(String key, String value) {\n        putString(key, value).apply();\n    }\n\n    public String getString(String key, String defValue) {\n        return mPreferences.getString(key, defValue);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/cl/myapplication/util/TimeUtil.java",
    "content": "package com.cl.myapplication.util;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\n\npublic class TimeUtil {\n\n    public static final SimpleDateFormat DEFAULT_FORMAT =\n            new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n    public static String currentTime() {\n        Date date = new Date();\n        return DEFAULT_FORMAT.format(date);\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/color/selector_log_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"#e93838\" android:state_enabled=\"false\" />\n    <item android:color=\"#519b49\" /> \n</selector>"
  },
  {
    "path": "app/src/main/res/color/selector_spinner_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"@android:color/darker_gray\" android:state_enabled=\"false\" />\n    <item android:color=\"@color/colorPrimary\" /> \n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:id=\"@+id/linearLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\">\n\n            <EditText\n                android:id=\"@+id/et_send_content\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:ems=\"10\"\n                android:gravity=\"top\"\n                android:hint=\"请输入发送内容，内容转 byte[] 发送\"\n                android:minLines=\"5\" />\n\n            <Button\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:onClick=\"onSend\"\n                android:text=\"发送\" />\n\n\n            <Button\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:onClick=\"onDestroy\"\n                android:text=\"注销\" />\n\n        </LinearLayout>\n\n\n    </RelativeLayout>\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main_java.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n    <LinearLayout\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/colorLightBlue\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"50dp\"\n            android:background=\"@color/colorPrimary\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginRight=\"10dp\"\n                android:text=\"@string/select_serial_port\"\n                android:textColor=\"@color/white\"\n                android:textSize=\"17dp\"\n                android:textStyle=\"bold\" />\n\n            <Spinner\n                android:id=\"@+id/spinner_devices\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"8dp\"\n                android:layout_weight=\"1\"\n                android:background=\"@color/colorLightBlue\"\n                android:textSize=\"17dp\"\n                tools:entries=\"@array/baudrates\" />\n\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"10dp\"\n                android:layout_marginRight=\"10dp\"\n                android:text=\"@string/select_baud_rate\"\n                android:textColor=\"@color/white\"\n                android:textSize=\"17dp\"\n                android:textStyle=\"bold\" />\n\n            <Spinner\n                android:id=\"@+id/spinner_baudrate\"\n                android:layout_width=\"200dp\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@color/colorLightBlue\"\n                android:textSize=\"17dp\"\n                tools:entries=\"@array/baudrates\" />\n\n            <Button\n                android:id=\"@+id/btn_open_device\"\n                android:layout_width=\"90dp\"\n                android:layout_height=\"50dp\"\n                android:layout_marginLeft=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/open_serial_port\"\n                android:textSize=\"17dp\" />\n\n\n        </LinearLayout>\n\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"horizontal\">\n\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n\n\n                <EditText\n                    android:id=\"@+id/et_data\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"50dp\"\n                    android:digits=\"0123456789abcdefABCDEF\"\n                    android:hint=\"@string/input_data\"\n                    android:inputType=\"textCapCharacters\"\n                    android:singleLine=\"true\"\n                    android:textSize=\"17dp\" />\n\n                <Button\n                    android:id=\"@+id/btn_send_data\"\n                    style=\"@style/ButtonStyle\"\n                    android:text=\"@string/send_data\" />\n\n                <Button\n                    android:id=\"@+id/btn_load_list\"\n                    style=\"@style/ButtonStyle\"\n                    android:text=\"@string/load_cmd_list\" />\n\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"5dp\"\n                    android:orientation=\"horizontal\">\n\n                    <TextView\n                        android:id=\"@+id/tv_databits\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:gravity=\"center_vertical\"\n                        android:text=\"@string/title_data_bits\"\n                        android:textColor=\"#000\" />\n\n                    <Spinner\n                        android:id=\"@+id/sp_databits\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\" />\n                </LinearLayout>\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"5dp\"\n                    android:orientation=\"horizontal\">\n\n                    <TextView\n                        android:id=\"@+id/tv_parity\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:gravity=\"center_vertical\"\n                        android:text=\"@string/title_parity\"\n                        android:textColor=\"#000\" />\n\n                    <Spinner\n                        android:id=\"@+id/sp_parity\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\" />\n                </LinearLayout>\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"5dp\"\n                    android:orientation=\"horizontal\">\n\n                    <TextView\n                        android:id=\"@+id/tv_stopbits\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:gravity=\"center_vertical\"\n                        android:text=\"@string/title_stop_bits\"\n                        android:textColor=\"#000\" />\n\n                    <Spinner\n                        android:id=\"@+id/sp_stopbits\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\" />\n                </LinearLayout>\n\n            </LinearLayout>\n\n            <include\n                layout=\"@layout/include_fragment_container\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\" />\n\n        </LinearLayout>\n    </LinearLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_multi_serial.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/colorLightBlue\"\n        android:orientation=\"vertical\">\n\n        <!-- 顶部串口选择区域 -->\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:background=\"@color/colorPrimary\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingLeft=\"16dp\"\n            android:paddingRight=\"16dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginRight=\"12dp\"\n                android:text=\"多串口管理\"\n                android:textColor=\"@color/white\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <View\n                android:layout_width=\"0dp\"\n                android:layout_height=\"1dp\"\n                android:layout_weight=\"1\" />\n\n            <Button\n                android:id=\"@+id/btn_refresh_ports\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"40dp\"\n                android:background=\"@color/colorAccent\"\n                android:paddingLeft=\"16dp\"\n                android:paddingRight=\"16dp\"\n                android:text=\"刷新串口\"\n                android:textColor=\"@color/white\"\n                android:textSize=\"14sp\" />\n\n        </LinearLayout>\n\n        <!-- 主内容区域 -->\n        <ScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:padding=\"16dp\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <!-- 全局串口参数配置卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"16dp\"\n                            android:text=\"全局串口参数\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"horizontal\">\n\n                            <LinearLayout\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:orientation=\"vertical\">\n\n                                <TextView\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:text=\"数据位:\"\n                                    android:textColor=\"@color/black\"\n                                    android:textSize=\"14sp\" />\n\n                                <Spinner\n                                    android:id=\"@+id/spinner_databits\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_marginTop=\"4dp\"\n                                    android:background=\"@color/colorLightBlue\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginLeft=\"12dp\"\n                                android:layout_weight=\"1\"\n                                android:orientation=\"vertical\">\n\n                                <TextView\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:text=\"校验位:\"\n                                    android:textColor=\"@color/black\"\n                                    android:textSize=\"14sp\" />\n\n                                <Spinner\n                                    android:id=\"@+id/spinner_parity\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_marginTop=\"4dp\"\n                                    android:background=\"@color/colorLightBlue\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginLeft=\"12dp\"\n                                android:layout_weight=\"1\"\n                                android:orientation=\"vertical\">\n\n                                <TextView\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:text=\"停止位:\"\n                                    android:textColor=\"@color/black\"\n                                    android:textSize=\"14sp\" />\n\n                                <Spinner\n                                    android:id=\"@+id/spinner_stopbits\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_marginTop=\"4dp\"\n                                    android:background=\"@color/colorLightBlue\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginTop=\"8dp\"\n                            android:text=\"注意：修改全局参数后，需要重新打开串口才能生效\"\n                            android:textColor=\"@color/secondary_text\"\n                            android:textSize=\"12sp\" />\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n                <!-- 串口配置卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"16dp\"\n                            android:text=\"串口设备配置\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <!-- 串口1配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口1 (GPS模块)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_gps_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_gps_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_gps_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <!-- 分割线 -->\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"1dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:background=\"@color/divider\" />\n\n                        <!-- 串口2配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口2 (传感器模块)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_sensor_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_sensor_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_sensor_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <!-- 分割线 -->\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"1dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:background=\"@color/divider\" />\n\n                        <!-- 串口3配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口3 (Modbus设备)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_modbus_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_modbus_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_modbus_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <!-- 分割线 -->\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"1dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:background=\"@color/divider\" />\n\n                        <!-- 串口4配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口4 (自定义协议)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_custom_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_custom_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_custom_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n                <!-- 操作按钮卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"16dp\"\n                            android:text=\"批量操作\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"horizontal\">\n\n                            <Button\n                                android:id=\"@+id/btn_open_all\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"48dp\"\n                                android:layout_marginEnd=\"8dp\"\n                                android:layout_weight=\"1\"\n                                android:background=\"@color/colorPrimary\"\n                                android:text=\"打开所有串口\"\n                                android:textColor=\"@color/white\"\n                                android:textSize=\"14sp\" />\n\n                            <Button\n                                android:id=\"@+id/btn_close_all\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"48dp\"\n                                android:layout_marginStart=\"8dp\"\n                                android:layout_weight=\"1\"\n                                android:background=\"@color/secondary_text\"\n                                android:text=\"关闭所有串口\"\n                                android:textColor=\"@color/white\"\n                                android:textSize=\"14sp\" />\n\n                        </LinearLayout>\n\n                        <Button\n                            android:id=\"@+id/btn_send_test\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"48dp\"\n                            android:layout_marginTop=\"12dp\"\n                            android:background=\"@color/colorAccent\"\n                            android:text=\"发送测试数据\"\n                            android:textColor=\"@color/white\"\n                            android:textSize=\"14sp\" />\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n                <!-- 状态日志卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"horizontal\">\n\n                            <TextView\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:text=\"状态日志\"\n                                android:textColor=\"@color/colorPrimary\"\n                                android:textSize=\"18sp\"\n                                android:textStyle=\"bold\" />\n\n                            <Button\n                                android:id=\"@+id/btn_clear_log\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"32dp\"\n                                android:background=\"@color/secondary_text\"\n                                android:paddingLeft=\"12dp\"\n                                android:paddingRight=\"12dp\"\n                                android:text=\"清空日志\"\n                                android:textColor=\"@color/white\"\n                                android:textSize=\"12sp\" />\n\n                        </LinearLayout>\n\n                        <ScrollView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"200dp\">\n\n                            <TextView\n                                android:id=\"@+id/tv_status\"\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:background=\"@color/log_bg\"\n                                android:fontFamily=\"monospace\"\n                                android:padding=\"12dp\"\n                                android:text=\"等待操作...\"\n                                android:textSize=\"12sp\" />\n\n                        </ScrollView>\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginTop=\"16dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:text=\"收发数据\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <ScrollView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"220dp\">\n\n                            <TextView\n                                android:id=\"@+id/tv_data\"\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:background=\"@color/log_bg\"\n                                android:fontFamily=\"monospace\"\n                                android:padding=\"12dp\"\n                                android:text=\"等待数据...\"\n                                android:textSize=\"12sp\" />\n\n                        </ScrollView>\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n            </LinearLayout>\n\n        </ScrollView>\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_multi_serial_new.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/colorLightBlue\"\n        android:orientation=\"vertical\">\n\n        <!-- 顶部串口选择区域 -->\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:background=\"@color/colorPrimary\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingLeft=\"16dp\"\n            android:paddingRight=\"16dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginRight=\"12dp\"\n                android:text=\"多串口管理\"\n                android:textColor=\"@color/white\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <View\n                android:layout_width=\"0dp\"\n                android:layout_height=\"1dp\"\n                android:layout_weight=\"1\" />\n\n            <Button\n                android:id=\"@+id/btn_refresh_ports\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"40dp\"\n                android:background=\"@color/colorAccent\"\n                android:paddingLeft=\"16dp\"\n                android:paddingRight=\"16dp\"\n                android:text=\"刷新串口\"\n                android:textColor=\"@color/white\"\n                android:textSize=\"14sp\" />\n\n        </LinearLayout>\n\n        <!-- 主内容区域 -->\n        <ScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:padding=\"16dp\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <!-- 全局串口参数配置卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"16dp\"\n                            android:text=\"全局串口参数\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"horizontal\">\n\n                            <LinearLayout\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:orientation=\"vertical\">\n\n                                <TextView\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:text=\"数据位:\"\n                                    android:textColor=\"@color/black\"\n                                    android:textSize=\"14sp\" />\n\n                                <Spinner\n                                    android:id=\"@+id/spinner_databits\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_marginTop=\"4dp\"\n                                    android:background=\"@color/colorLightBlue\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginLeft=\"12dp\"\n                                android:layout_weight=\"1\"\n                                android:orientation=\"vertical\">\n\n                                <TextView\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:text=\"校验位:\"\n                                    android:textColor=\"@color/black\"\n                                    android:textSize=\"14sp\" />\n\n                                <Spinner\n                                    android:id=\"@+id/spinner_parity\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_marginTop=\"4dp\"\n                                    android:background=\"@color/colorLightBlue\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginLeft=\"12dp\"\n                                android:layout_weight=\"1\"\n                                android:orientation=\"vertical\">\n\n                                <TextView\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:text=\"停止位:\"\n                                    android:textColor=\"@color/black\"\n                                    android:textSize=\"14sp\" />\n\n                                <Spinner\n                                    android:id=\"@+id/spinner_stopbits\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_marginTop=\"4dp\"\n                                    android:background=\"@color/colorLightBlue\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginTop=\"8dp\"\n                            android:text=\"注意：修改全局参数后，需要重新打开串口才能生效\"\n                            android:textColor=\"@color/secondary_text\"\n                            android:textSize=\"12sp\" />\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n                <!-- 串口配置卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"16dp\"\n                            android:text=\"串口设备配置\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <!-- 串口1配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口1 (GPS模块)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_gps_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_gps_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_gps_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <!-- 分割线 -->\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"1dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:background=\"@color/divider\" />\n\n                        <!-- 串口2配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口2 (传感器模块)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_sensor_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_sensor_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_sensor_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <!-- 分割线 -->\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"1dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:background=\"@color/divider\" />\n\n                        <!-- 串口3配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口3 (Modbus设备)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_modbus_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_modbus_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_modbus_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <!-- 分割线 -->\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"1dp\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:background=\"@color/divider\" />\n\n                        <!-- 串口4配置 -->\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"vertical\">\n\n                            <TextView\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"8dp\"\n                                android:text=\"串口4 (自定义协议)\"\n                                android:textColor=\"@color/black\"\n                                android:textSize=\"16sp\"\n                                android:textStyle=\"bold\" />\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:orientation=\"horizontal\">\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"设备:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_custom_device\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <LinearLayout\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:layout_weight=\"1\"\n                                    android:orientation=\"vertical\">\n\n                                    <TextView\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"wrap_content\"\n                                        android:text=\"波特率:\"\n                                        android:textColor=\"@color/black\"\n                                        android:textSize=\"14sp\" />\n\n                                    <Spinner\n                                        android:id=\"@+id/spinner_custom_baudrate\"\n                                        android:layout_width=\"match_parent\"\n                                        android:layout_height=\"40dp\"\n                                        android:layout_marginTop=\"4dp\"\n                                        android:background=\"@color/colorLightBlue\" />\n\n                                </LinearLayout>\n\n                                <Button\n                                    android:id=\"@+id/btn_custom_toggle\"\n                                    android:layout_width=\"80dp\"\n                                    android:layout_height=\"40dp\"\n                                    android:layout_gravity=\"bottom\"\n                                    android:layout_marginLeft=\"12dp\"\n                                    android:background=\"@color/colorAccent\"\n                                    android:text=\"打开\"\n                                    android:textColor=\"@color/white\"\n                                    android:textSize=\"12sp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n                <!-- 操作按钮卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"16dp\"\n                            android:text=\"批量操作\"\n                            android:textColor=\"@color/colorPrimary\"\n                            android:textSize=\"18sp\"\n                            android:textStyle=\"bold\" />\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"horizontal\">\n\n                            <Button\n                                android:id=\"@+id/btn_open_all\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"48dp\"\n                                android:layout_marginEnd=\"8dp\"\n                                android:layout_weight=\"1\"\n                                android:background=\"@color/colorPrimary\"\n                                android:text=\"打开所有串口\"\n                                android:textColor=\"@color/white\"\n                                android:textSize=\"14sp\" />\n\n                            <Button\n                                android:id=\"@+id/btn_close_all\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"48dp\"\n                                android:layout_marginStart=\"8dp\"\n                                android:layout_weight=\"1\"\n                                android:background=\"@color/secondary_text\"\n                                android:text=\"关闭所有串口\"\n                                android:textColor=\"@color/white\"\n                                android:textSize=\"14sp\" />\n\n                        </LinearLayout>\n\n                        <Button\n                            android:id=\"@+id/btn_send_test\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"48dp\"\n                            android:layout_marginTop=\"12dp\"\n                            android:background=\"@color/colorAccent\"\n                            android:text=\"发送测试数据\"\n                            android:textColor=\"@color/white\"\n                            android:textSize=\"14sp\" />\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n                <!-- 状态日志卡片 -->\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    app:cardCornerRadius=\"8dp\"\n                    app:cardElevation=\"4dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"12dp\"\n                            android:orientation=\"horizontal\">\n\n                            <TextView\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:text=\"状态日志\"\n                                android:textColor=\"@color/colorPrimary\"\n                                android:textSize=\"18sp\"\n                                android:textStyle=\"bold\" />\n\n                            <Button\n                                android:id=\"@+id/btn_clear_log\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"32dp\"\n                                android:background=\"@color/secondary_text\"\n                                android:paddingLeft=\"12dp\"\n                                android:paddingRight=\"12dp\"\n                                android:text=\"清空日志\"\n                                android:textColor=\"@color/white\"\n                                android:textSize=\"12sp\" />\n\n                        </LinearLayout>\n\n                        <ScrollView\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"200dp\">\n\n                            <TextView\n                                android:id=\"@+id/tv_status\"\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:background=\"@color/log_bg\"\n                                android:fontFamily=\"monospace\"\n                                android:padding=\"12dp\"\n                                android:text=\"等待操作...\"\n                                android:textSize=\"12sp\" />\n\n                        </ScrollView>\n\n                    </LinearLayout>\n\n                </androidx.cardview.widget.CardView>\n\n            </LinearLayout>\n\n        </ScrollView>\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_select_serial_port.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <ListView\n            android:id=\"@+id/lv_devices\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n        <TextView\n            android:id=\"@+id/tv_empty\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"没有可用串口\"\n            android:textColor=\"#FF000000\"\n            android:textSize=\"30sp\" />\n\n    </RelativeLayout>\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#00000000\"\n        android:orientation=\"vertical\">\n\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"50dp\"\n            android:orientation=\"horizontal\">\n\n\n            <Button\n                android:id=\"@+id/btn_clear_log\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:text=\"清除日志\"\n                android:textSize=\"14dp\" />\n\n            <Button\n                android:id=\"@+id/btn_auto_end\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:text=\"自动显示最新日志\"\n                android:textSize=\"14dp\" />\n\n            <Button\n                android:id=\"@+id/btn_whether_hexadecimal\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:textSize=\"14dp\"\n                android:text=\"Hex-byte转换\"\n                android:layout_height=\"wrap_content\"/>\n\n\n        </LinearLayout>\n\n        <ListView\n            android:id=\"@+id/lv_logs\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:fastScrollEnabled=\"true\" />\n\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/include_fragment_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<fragment\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/log_fragment\"\n    android:name=\"com.cl.myapplication.fragment.LogFragment\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />"
  },
  {
    "path": "app/src/main/res/layout/item_device.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <TextView\n            android:id=\"@+id/tv_device\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"20dp\"\n            android:textColor=\"#FF000000\"\n            tools:text=\"串口号 [root] (路径)\" />\n\n    </RelativeLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_num\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingLeft=\"10dp\"\n            android:text=\"1\"\n            android:textColor=\"#ccae17\"\n            android:textSize=\"17dp\"\n            android:textStyle=\"bold\" />\n\n\n        <TextView\n            android:id=\"@+id/tv_log\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\"\n            android:textColor=\"@color/selector_log_text\"\n            android:textSize=\"17dp\"\n            tools:text=\"abadsfadsfdasfasdjfljadlsfjlasjdflja;lsdjf;ljasdl;fj;lasdjf;lajsd;fljasdfa\" />\n\n    </LinearLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/spinner_default_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        style=\"?android:attr/spinnerItemStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"marquee\"\n        android:padding=\"8dp\"\n        android:textAlignment=\"inherit\"\n        android:textColor=\"@color/black\"\n        android:textSize=\"17dp\"\n        tools:text=\"/dev/ttyS2\" />\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/spinner_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        style=\"?android:attr/spinnerItemStyle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"marquee\"\n        android:padding=\"8dp\"\n        android:textAlignment=\"inherit\"\n        android:textColor=\"@color/black\"\n        android:textSize=\"17dp\"\n        tools:text=\"/dev/ttyS2\" />\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n\n\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n    <color name=\"colorItemGrey\">#e4e4e4</color>\n    <color name=\"colorVeryLightBlue\">#55a0a7cc</color>\n    <color name=\"colorLightBlue\">#ffa0a7cc</color>\n\n    <color name=\"log_bg\">#e5e5e5</color>\n\n    <color name=\"transparent\">#00000000</color>\n    <color name=\"on_pressed\">#30bfbfbf</color>\n    <color name=\"secondary_text\">#727272</color>\n    <color name=\"divider\">#B6B6B6</color>\n\n    <color name=\"color_888888\">#888888</color>\n    <color name=\"color_20ffffff\">#20ffffff</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/string_arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string-array name=\"baudrates\">\n        <item>110</item>\n        <item>300</item>\n        <item>600</item>\n        <item>1200</item>\n        <item>2400</item>\n        <item>4800</item>\n        <item>9600</item>\n        <item>14400</item>\n        <item>19200</item>\n        <item>38400</item>\n        <item>56000</item>\n        <item>57600</item>\n        <item>115200</item>\n        <item>128000</item>\n        <item>256000</item>\n    </string-array>\n\n\n    <string-array name=\"channels\">\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n        <item>4</item>\n        <item>5</item>\n        <item>6</item>\n        <item>7</item>\n        <item>8</item>\n        <item>9</item>\n        <item>10</item>\n    </string-array>\n\n    <string-array name=\"pulses\">\n        <item>0：设备默认</item>\n        <item>1：快 20ms</item>\n        <item>2：中 40ms</item>\n        <item>3：慢 60ms</item>\n    </string-array>\n\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n\n    <string name=\"app_name\">串口调试助手</string>\n    <string name=\"select_serial_port\">串口设备:</string>\n    <string name=\"select_baud_rate\">波特率</string>\n    <string name=\"open_serial_port\">打开串口</string>\n    <string name=\"close_serial_port\">关闭串口</string>\n    <string name=\"no_serial_device\">找不到串口设备</string>\n    <string name=\"send_data\">发送命令</string>\n    <string name=\"input_data\">输入命令</string>\n    <string name=\"load_cmd_list\">加载命令列表</string>\n    <string name=\"title_data_bits\">数据位:</string>\n    <string name=\"title_parity\">校验位:</string>\n    <string name=\"title_stop_bits\">停止位:</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.MyApplication\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_500</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorOnPrimary\">@color/white</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_700</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">?attr/colorPrimaryVariant</item>\n        <!-- Customize your theme here. -->\n    </style>\n\n\n    <style name=\"ButtonStyle\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">50dp</item>\n        <item name=\"android:textSize\">17dp</item>\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.MyApplication\" parent=\"Theme.MaterialComponents.DayNight.DarkActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_200</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorOnPrimary\">@color/black</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_200</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">?attr/colorPrimaryVariant</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.kotlin.android) apply false\n}"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nagp = \"8.12.2\"\nkotlin = \"2.0.21\"\ncoreKtx = \"1.10.1\"\njunit = \"4.13.2\"\njunitVersion = \"1.1.5\"\nespressoCore = \"3.5.1\"\nappcompat = \"1.6.1\"\nmaterial = \"1.10.0\"\n\n[libraries]\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitVersion\" }\nandroidx-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"espressoCore\" }\nandroidx-appcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"appcompat\" }\nmaterial = { group = \"com.google.android.material\", name = \"material\", version.ref = \"material\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nkotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\n\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sun Sep 28 20:08:42 CST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.13-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "serial_lib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "serial_lib/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n    id 'maven-publish'\n}\n\nandroid {\n    namespace \"com.cl.serialportlibrary\"\n    compileSdk 34\n\n    defaultConfig {\n        minSdk 21\n        targetSdk 34\n        versionCode 508\n        versionName \"5.0.8\"\n        consumerProguardFiles \"consumer-rules.pro\"\n        \n        externalNativeBuild {\n            cmake {\n                // 设置 Android 平台和 STL\n                arguments \"-DANDROID_PLATFORM=android-21\",\n                          \"-DANDROID_STL=c++_shared\"\n            }\n        }\n        ndk {\n            abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'\n        }\n    }\n    \n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    externalNativeBuild {\n        cmake {\n            path \"src/main/cpp/CMakeLists.txt\"\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: \"libs\", include: [\"*.jar\"])\n    implementation 'androidx.appcompat:appcompat:1.6.1'\n    testImplementation 'junit:junit:4.13.2'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.5'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'\n}\n\nafterEvaluate {\n    publishing {\n        publications {\n            release(MavenPublication) {\n                groupId = 'com.github.cl-6666'\n                artifactId = 'serialPort'\n                version = '5.0.6'\n                \n                artifact bundleReleaseAar\n                \n                pom {\n                    name = 'SerialPort Library'\n                    description = 'Android Serial Port Library with JNI support'\n                    url = 'https://github.com/cl-6666/serialPort'\n                    \n                    licenses {\n                        license {\n                            name = 'The Apache License, Version 2.0'\n                            url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                        }\n                    }\n                    \n                    developers {\n                        developer {\n                            id = 'cl-6666'\n                            name = 'cl-6666'\n                        }\n                    }\n                    \n                    scm {\n                        connection = 'scm:git:git://github.com/cl-6666/serialPort.git'\n                        developerConnection = 'scm:git:ssh://github.com:cl-6666/serialPort.git'\n                        url = 'https://github.com/cl-6666/serialPort/tree/master'\n                    }\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "serial_lib/consumer-rules.pro",
    "content": "# Consumer proguard rules for serial_lib\n\n# Keep all public APIs\n-keep public class com.cl.serialportlibrary.** { *; }\n\n# Keep native methods\n-keepclasseswithmembernames class * {\n    native <methods>;\n}\n\n# Keep enums\n-keepclassmembers enum * {\n    public static **[] values();\n    public static ** valueOf(java.lang.String);\n}\n\n# Keep Serializable classes\n-keepclassmembers class * implements java.io.Serializable {\n    static final long serialVersionUID;\n    private static final java.io.ObjectStreamField[] serialPersistentFields;\n    private void writeObject(java.io.ObjectOutputStream);\n    private void readObject(java.io.ObjectInputStream);\n    java.lang.Object writeReplace();\n    java.lang.Object readResolve();\n}\n"
  },
  {
    "path": "serial_lib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in D:\\Android\\sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "serial_lib/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application android:allowBackup=\"true\" android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\">\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "serial_lib/src/main/cpp/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.18.1)\n\nproject(SerialPort C)\n\nadd_library(SerialPort SHARED\n            SerialPort.c)\n\n# 添加 16KB 页面对齐支持 (arm64-v8a)\nif(ANDROID_ABI STREQUAL \"arm64-v8a\")\n    target_compile_options(SerialPort PRIVATE -fno-emulated-tls)\n    target_link_options(SerialPort PRIVATE \n        \"LINKER:-z,max-page-size=16384\"\n        \"LINKER:-z,common-page-size=16384\")\nendif()\n\n# Include libraries needed for libserial_port lib\ntarget_link_libraries(SerialPort\n                      android\n                      log)"
  },
  {
    "path": "serial_lib/src/main/cpp/SerialPort.c",
    "content": "\n\n/*\n * Copyright 2009-2011 Cedric Priscal\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <termios.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <string.h>\n#include <jni.h>\n\n#include \"SerialPort.h\"\n\n#include \"android/log.h\"\nstatic const char *TAG=\"serial_port\";\n#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)\n#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)\n#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)\n\nstatic speed_t getBaudrate(jint baudrate)\n{\n    switch(baudrate) {\n        case 0: return B0;\n        case 50: return B50;\n        case 75: return B75;\n        case 110: return B110;\n        case 134: return B134;\n        case 150: return B150;\n        case 200: return B200;\n        case 300: return B300;\n        case 600: return B600;\n        case 1200: return B1200;\n        case 1800: return B1800;\n        case 2400: return B2400;\n        case 4800: return B4800;\n        case 9600: return B9600;\n        case 19200: return B19200;\n        case 38400: return B38400;\n        case 57600: return B57600;\n        case 115200: return B115200;\n        case 230400: return B230400;\n        case 460800: return B460800;\n        case 500000: return B500000;\n        case 576000: return B576000;\n        case 921600: return B921600;\n        case 1000000: return B1000000;\n        case 1152000: return B1152000;\n        case 1500000: return B1500000;\n        case 2000000: return B2000000;\n        case 2500000: return B2500000;\n        case 3000000: return B3000000;\n        case 3500000: return B3500000;\n        case 4000000: return B4000000;\n        default: return -1;\n    }\n}\n\n/*\n * Class:     android_serialport_SerialPort\n * Method:    open\n * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;\n */\nJNIEXPORT jobject JNICALL Java_com_cl_serialportlibrary_SerialPort_open\n        (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags, jint databits, jint stopbits, jint parity)\n{\n    int fd;\n    speed_t speed;\n    jobject mFileDescriptor;\n\n    /* Check arguments */\n    {\n        speed = getBaudrate(baudrate);\n        if (speed == -1) {\n            LOGE(\"Invalid baudrate\");\n            return NULL;\n        }\n    }\n\n    /* Opening device */\n    {\n        jboolean iscopy;\n        const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);\n        LOGD(\"Opening serial port %s with flags 0x%x\", path_utf, O_RDWR | flags);\n        fd = open(path_utf, O_RDWR | flags);\n        LOGD(\"open() fd = %d\", fd);\n        (*env)->ReleaseStringUTFChars(env, path, path_utf);\n        if (fd == -1) {\n            LOGE(\"Cannot open port\");\n            return NULL;\n        }\n    }\n\n    /* Configure device */\n    {\n        struct termios cfg;\n        LOGD(\"Configuring serial port\");\n        if (tcgetattr(fd, &cfg)) {\n            LOGE(\"tcgetattr() failed\");\n            close(fd);\n            return NULL;\n        }\n\n        // Initialize termios struct\n        cfmakeraw(&cfg);\n\n        // Set data bits\n        cfg.c_cflag &= ~CSIZE;\n        switch (databits) {\n            case 5:\n                cfg.c_cflag |= CS5;\n                break;\n            case 6:\n                cfg.c_cflag |= CS6;\n                break;\n            case 7:\n                cfg.c_cflag |= CS7;\n                break;\n            case 8:\n                cfg.c_cflag |= CS8;\n                break;\n            default:\n                LOGE(\"Invalid data bits\");\n                close(fd);\n                return NULL;\n        }\n\n        // Set stop bits\n        switch (stopbits) {\n            case 1:\n                cfg.c_cflag &= ~CSTOPB;\n                break;\n            case 2:\n                cfg.c_cflag |= CSTOPB;\n                break;\n            default:\n                LOGE(\"Invalid stop bits\");\n                close(fd);\n                return NULL;\n        }\n\n        switch (parity) {\n            case 0:\n                cfg.c_cflag &= ~PARENB;    //PARITY OFF\n                break;\n            case 1:\n                cfg.c_cflag |= (PARODD | PARENB);   //PARITY ODD\n                cfg.c_iflag &= ~IGNPAR;\n                cfg.c_iflag |= PARMRK;\n                cfg.c_iflag |= INPCK;\n                break;\n            case 2:\n                cfg.c_iflag &= ~(IGNPAR | PARMRK); //PARITY EVEN\n                cfg.c_iflag |= INPCK;\n                cfg.c_cflag |= PARENB;\n                cfg.c_cflag &= ~PARODD;\n                break;\n            case 3:\n                //  PARITY SPACE\n                cfg.c_iflag &= ~IGNPAR;             //  Make sure wrong parity is not ignored\n                cfg.c_iflag |= PARMRK;              //  Marks parity error, parity error\n                //  is given as three char sequence\n                cfg.c_iflag |= INPCK;               //  Enable input parity checking\n                cfg.c_cflag |= PARENB | CMSPAR;     //  Enable parity and set space parity\n                cfg.c_cflag &= ~PARODD;             //\n                break;\n            case 4:\n                //  PARITY MARK\n                cfg.c_iflag &= ~IGNPAR;             //  Make sure wrong parity is not ignored\n                cfg.c_iflag |= PARMRK;              //  Marks parity error, parity error\n                //  is given as three char sequence\n                cfg.c_iflag |= INPCK;               //  Enable input parity checking\n                cfg.c_cflag |= PARENB | CMSPAR | PARODD;\n                break;\n            default:\n                cfg.c_cflag &= ~PARENB;\n                break;\n        }\n\n        // Set baud rate\n        cfsetispeed(&cfg, speed);\n        cfsetospeed(&cfg, speed);\n\n        if (tcsetattr(fd, TCSANOW, &cfg)) {\n            LOGE(\"tcsetattr() failed\");\n            close(fd);\n            return NULL;\n        }\n    }\n\n    /* Create a corresponding file descriptor */\n    {\n        jclass cFileDescriptor = (*env)->FindClass(env, \"java/io/FileDescriptor\");\n        jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, \"<init>\", \"()V\");\n        jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, \"descriptor\", \"I\");\n        mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);\n        (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);\n    }\n\n    return mFileDescriptor;\n}\n\nJNIEXPORT void JNICALL Java_com_cl_serialportlibrary_SerialPort_close\n        (JNIEnv *env, jobject thiz)\n{\n    jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);\n    jclass FileDescriptorClass = (*env)->FindClass(env, \"java/io/FileDescriptor\");\n\n    jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, \"mFd\", \"Ljava/io/FileDescriptor;\");\n    jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, \"descriptor\", \"I\");\n\n    jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);\n    jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);\n\n    LOGD(\"close(fd = %d)\", descriptor);\n    close(descriptor);\n}\n"
  },
  {
    "path": "serial_lib/src/main/cpp/SerialPort.h",
    "content": "/* DO NOT EDIT THIS FILE - it is machine generated */\n#include <jni.h>\n/* Header for class android_serialport_api_SerialPort */\n\n#ifndef _Included_qingwei_kong_serialportlibrary_SerialPort\n#define _Included_qingwei_kong_serialportlibrary_SerialPort\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n * Class:     android_serialport_api_SerialPort\n * Method:    open\n * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;\n */\nJNIEXPORT jobject JNICALL Java_com_cl_serialportlibrary_SerialPort_open\n  (JNIEnv *, jclass, jstring, jint, jint,jint,jint,jint);\n\n/*\n * Class:     android_serialport_api_SerialPort\n * Method:    close\n * Signature: ()V\n */\nJNIEXPORT void JNICALL Java_com_cl_serialportlibrary_SerialPort_close\n  (JNIEnv *, jobject);\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/Device.java",
    "content": "package com.cl.serialportlibrary;\n\nimport java.io.File;\nimport java.io.Serializable;\n\n\npublic class Device implements Serializable{\n\n    private static final String TAG = Device.class.getSimpleName();\n    private String name;\n    private String root;\n    private File file;\n\n    public Device(String name, String root, File file) {\n        this.name = name;\n        this.root = root;\n        this.file = file;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getRoot() {\n        return root;\n    }\n\n    public void setRoot(String root) {\n        this.root = root;\n    }\n\n    public File getFile() {\n        return file;\n    }\n\n    public void setFile(File path) {\n        this.file = file;\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/Driver.java",
    "content": "package com.cl.serialportlibrary;\n\nimport java.io.File;\nimport java.util.ArrayList;\n\n/**\n * 串口驱动信息类\n * 简化版本，仅用于SerialPortFinder\n */\npublic class Driver {\n    \n    private String mDriverName;\n    private String mDeviceRoot;\n    \n    public Driver(String name, String root) {\n        mDriverName = name;\n        mDeviceRoot = root;\n    }\n    \n    /**\n     * 获取驱动名称\n     */\n    public String getName() {\n        return mDriverName;\n    }\n    \n    /**\n     * 获取设备根路径\n     */\n    public String getRoot() {\n        return mDeviceRoot;\n    }\n    \n    /**\n     * 获取该驱动下的所有设备\n     */\n    public ArrayList<File> getDevices() {\n        ArrayList<File> devices = new ArrayList<>();\n        File dev = new File(\"/dev\");\n        File[] files = dev.listFiles();\n        \n        if (files != null) {\n            for (File file : files) {\n                if (file.getAbsolutePath().startsWith(mDeviceRoot)) {\n                    devices.add(file);\n                }\n            }\n        }\n        \n        return devices;\n    }\n    \n    @Override\n    public String toString() {\n        return \"Driver{\" +\n                \"name='\" + mDriverName + '\\'' +\n                \", root='\" + mDeviceRoot + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/MultiSerialPortManager.java",
    "content": "package com.cl.serialportlibrary;\n\nimport android.app.Application;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.cl.serialportlibrary.enumerate.SerialPortEnum;\nimport com.cl.serialportlibrary.enumerate.SerialStatus;\nimport com.cl.serialportlibrary.listener.OnOpenSerialPortListener;\nimport com.cl.serialportlibrary.listener.OnSerialPortDataListener;\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 多串口管理器\n * 支持同时管理多个串口，每个串口可以有独立的配置\n * Author: cl\n * Date: 2023/10/26\n */\npublic class MultiSerialPortManager {\n    \n    private static final String TAG = \"MultiSerialPortManager\";\n    private static MultiSerialPortManager instance;\n    private Handler handler = new Handler(Looper.getMainLooper());\n    \n    // 串口管理器映射 <串口ID, SerialPortManager>\n    private final Map<String, SerialPortManager> serialPortManagers = new ConcurrentHashMap<>();\n    \n    // 串口配置映射 <串口ID, SerialConfig>\n    private final Map<String, SerialConfig> serialConfigs = new ConcurrentHashMap<>();\n    \n    // 回调映射 <串口ID, 回调接口>\n    private final Map<String, OnSerialPortStatusCallback> statusCallbacks = new ConcurrentHashMap<>();\n    private final Map<String, OnSerialPortDataCallback> dataCallbacks = new ConcurrentHashMap<>();\n    \n    // 串口枚举映射 <串口ID, SerialPortEnum>\n    private final Map<String, SerialPortEnum> serialPortEnums = new ConcurrentHashMap<>();\n    \n    private MultiSerialPortManager() {}\n    \n    /**\n     * 获取单例实例\n     */\n    public static MultiSerialPortManager getInstance() {\n        if (instance == null) {\n            synchronized (MultiSerialPortManager.class) {\n                if (instance == null) {\n                    instance = new MultiSerialPortManager();\n                }\n            }\n        }\n        return instance;\n    }\n    \n    /**\n     * 配置并打开串口\n     * @param serialId 串口ID（自定义标识）\n     * @param devicePath 设备路径\n     * @param baudRate 波特率\n     * @param config 串口配置\n     * @param statusCallback 状态回调\n     * @param dataCallback 数据回调\n     * @return 是否打开成功\n     */\n    public boolean openSerialPort(String serialId, String devicePath, int baudRate, \n                                 SerialPortConfig config, OnSerialPortStatusCallback statusCallback, \n                                 OnSerialPortDataCallback dataCallback) {\n        \n        SerialPortLogUtil.printSeparator(TAG, \"打开串口 \" + serialId);\n        SerialPortLogUtil.i(TAG, String.format(\"串口[%s] - 设备: %s, 波特率: %d\", serialId, devicePath, baudRate));\n        \n        // 检查串口是否已经打开\n        if (serialPortManagers.containsKey(serialId)) {\n            SerialPortLogUtil.w(TAG, \"串口[\" + serialId + \"]已经打开，先关闭旧连接\");\n            closeSerialPort(serialId);\n        }\n        \n        // 创建串口配置\n        SerialConfig serialConfig = new SerialConfig.Builder()\n                .setEnableLogging(config.enableLogging)\n                .setIntervalSleep(config.intervalSleep)\n                .setDatabits(config.databits)\n                .setParity(config.parity)\n                .setStopbits(config.stopbits)\n                .setFlags(config.flags)\n                .setEnableStickyPacketProcessing(config.stickyPacketHelpers != null && config.stickyPacketHelpers.length > 0)\n                .setStickyPacketHelpers(config.stickyPacketHelpers != null ? config.stickyPacketHelpers : new AbsStickPackageHelper[]{new BaseStickPackageHelper()})\n                .build();\n        \n        // 保存配置和回调\n        serialConfigs.put(serialId, serialConfig);\n        if (statusCallback != null) statusCallbacks.put(serialId, statusCallback);\n        if (dataCallback != null) dataCallbacks.put(serialId, dataCallback);\n        \n        // 分配串口枚举\n        SerialPortEnum serialPortEnum = getAvailableSerialPortEnum();\n        serialPortEnums.put(serialId, serialPortEnum);\n        \n        SerialPortLogUtil.printSerialConfig(TAG + \"_\" + serialId, config.databits, config.parity, config.stopbits, config.flags);\n        if (config.stickyPacketHelpers != null) {\n            SerialPortLogUtil.i(TAG, String.format(\"串口[%s] 配置了 %d 个粘包处理器\", serialId, config.stickyPacketHelpers.length));\n        }\n        \n        // 创建SerialPortManager\n        SerialPortManager serialPortManager = new SerialPortManager(serialPortEnum);\n        serialPortManager.setSerialConfig(serialConfig);\n        \n        // 设置监听器\n        serialPortManager.setOnOpenSerialPortListener(new OnOpenSerialPortListener() {\n            @Override\n            public void openState(SerialPortEnum serialPortEnum, File device, SerialStatus status) {\n                String logMessage = String.format(\"串口[%s] 状态变化: %s - %s\", serialId, device.getPath(), status);\n                if (status == SerialStatus.SUCCESS_OPENED) {\n                    SerialPortLogUtil.i(TAG, logMessage);\n                } else {\n                    SerialPortLogUtil.e(TAG, logMessage);\n                }\n                \n                handler.post(() -> {\n                    OnSerialPortStatusCallback callback = statusCallbacks.get(serialId);\n                    if (callback != null) {\n                        callback.onStatusChanged(serialId, status == SerialStatus.SUCCESS_OPENED, status);\n                    }\n                });\n            }\n        });\n        \n        serialPortManager.setOnSerialPortDataListener(new OnSerialPortDataListener() {\n            @Override\n            public void onDataReceived(byte[] data, SerialPortEnum serialPortEnum) {\n                SerialPortLogUtil.printData(TAG + \"_\" + serialId, \"接收数据\", data);\n                handler.post(() -> {\n                    OnSerialPortDataCallback callback = dataCallbacks.get(serialId);\n                    if (callback != null) {\n                        callback.onDataReceived(serialId, data);\n                    }\n                });\n            }\n            \n            @Override\n            public void onDataSent(byte[] data, SerialPortEnum serialPortEnum) {\n                SerialPortLogUtil.printData(TAG + \"_\" + serialId, \"发送数据\", data);\n                handler.post(() -> {\n                    OnSerialPortDataCallback callback = dataCallbacks.get(serialId);\n                    if (callback != null) {\n                        callback.onDataSent(serialId, data);\n                    }\n                });\n            }\n        });\n        \n        // 打开串口\n        boolean success = serialPortManager.openSerialPort(devicePath, baudRate);\n        if (success) {\n            serialPortManagers.put(serialId, serialPortManager);\n            SerialPortLogUtil.i(TAG, \"串口[\" + serialId + \"] 打开成功\");\n        } else {\n            // 清理资源\n            serialConfigs.remove(serialId);\n            statusCallbacks.remove(serialId);\n            dataCallbacks.remove(serialId);\n            serialPortEnums.remove(serialId);\n            SerialPortLogUtil.e(TAG, \"串口[\" + serialId + \"] 打开失败\");\n        }\n        \n        return success;\n    }\n    \n    /**\n     * 简化的打开串口方法\n     */\n    public boolean openSerialPort(String serialId, String devicePath, int baudRate, \n                                 OnSerialPortDataCallback dataCallback) {\n        SerialPortConfig config = new SerialPortConfig.Builder().build();\n        return openSerialPort(serialId, devicePath, baudRate, config, null, dataCallback);\n    }\n    \n    /**\n     * 发送数据到指定串口\n     * @param serialId 串口ID\n     * @param data 数据\n     * @return 是否发送成功\n     */\n    public boolean sendData(String serialId, byte[] data) {\n        SerialPortManager manager = serialPortManagers.get(serialId);\n        if (manager == null) {\n            SerialPortLogUtil.e(TAG, \"串口[\" + serialId + \"] 未打开，无法发送数据\");\n            return false;\n        }\n        \n        if (data == null || data.length == 0) {\n            SerialPortLogUtil.w(TAG, \"串口[\" + serialId + \"] 尝试发送空数据\");\n            return false;\n        }\n        \n        long startTime = System.currentTimeMillis();\n        SerialPortLogUtil.printData(TAG + \"_\" + serialId, \"准备发送\", data);\n        \n        boolean result = manager.sendBytes(data);\n        SerialPortLogUtil.printPerformance(TAG + \"_\" + serialId, \"发送数据\", startTime);\n        \n        if (!result) {\n            SerialPortLogUtil.e(TAG, \"串口[\" + serialId + \"] 数据发送失败\");\n        }\n        \n        return result;\n    }\n    \n    /**\n     * 发送字符串数据到指定串口\n     */\n    public boolean sendData(String serialId, String data) {\n        return sendData(serialId, data.getBytes());\n    }\n    \n    /**\n     * 关闭指定串口\n     * @param serialId 串口ID\n     */\n    public void closeSerialPort(String serialId) {\n        SerialPortManager manager = serialPortManagers.remove(serialId);\n        if (manager != null) {\n            SerialPortLogUtil.i(TAG, \"关闭串口[\" + serialId + \"]\");\n            manager.closeSerialPort();\n        }\n        \n        // 清理相关资源\n        serialConfigs.remove(serialId);\n        statusCallbacks.remove(serialId);\n        dataCallbacks.remove(serialId);\n        serialPortEnums.remove(serialId);\n    }\n    \n    /**\n     * 关闭所有串口\n     */\n    public void closeAllSerialPorts() {\n        SerialPortLogUtil.printSeparator(TAG, \"关闭所有串口\");\n        List<String> serialIds = new ArrayList<>(serialPortManagers.keySet());\n        for (String serialId : serialIds) {\n            closeSerialPort(serialId);\n        }\n    }\n    \n    /**\n     * 检查指定串口是否已打开\n     */\n    public boolean isSerialPortOpened(String serialId) {\n        SerialPortManager manager = serialPortManagers.get(serialId);\n        return manager != null && manager.isOpen();\n    }\n    \n    /**\n     * 获取所有已打开的串口ID\n     */\n    public List<String> getOpenedSerialPorts() {\n        List<String> openedPorts = new ArrayList<>();\n        for (Map.Entry<String, SerialPortManager> entry : serialPortManagers.entrySet()) {\n            if (entry.getValue().isOpen()) {\n                openedPorts.add(entry.getKey());\n            }\n        }\n        return openedPorts;\n    }\n    \n    /**\n     * 获取指定串口的配置\n     */\n    public SerialConfig getSerialConfig(String serialId) {\n        return serialConfigs.get(serialId);\n    }\n    \n    /**\n     * 更新指定串口的粘包处理器\n     */\n    public boolean updateStickyPacketHelpers(String serialId, AbsStickPackageHelper[] helpers) {\n        SerialPortManager manager = serialPortManagers.get(serialId);\n        SerialConfig config = serialConfigs.get(serialId);\n        \n        if (manager == null || config == null) {\n            SerialPortLogUtil.e(TAG, \"串口[\" + serialId + \"] 未打开，无法更新粘包处理器\");\n            return false;\n        }\n        \n        config.setStickyPacketHelpers(helpers);\n        List<AbsStickPackageHelper> helperList = new ArrayList<>();\n        for (AbsStickPackageHelper helper : helpers) {\n            helperList.add(helper);\n        }\n        manager.setStickPackageHelpers(helperList);\n        \n        SerialPortLogUtil.i(TAG, String.format(\"串口[%s] 更新粘包处理器，数量: %d\", serialId, helpers.length));\n        return true;\n    }\n    \n    /**\n     * 打印所有串口状态\n     */\n    public void printAllSerialStatus() {\n        SerialPortLogUtil.printSeparator(TAG, \"所有串口状态\");\n        if (serialPortManagers.isEmpty()) {\n            SerialPortLogUtil.i(TAG, \"当前没有打开的串口\");\n            return;\n        }\n        \n        for (Map.Entry<String, SerialPortManager> entry : serialPortManagers.entrySet()) {\n            String serialId = entry.getKey();\n            SerialPortManager manager = entry.getValue();\n            SerialConfig config = serialConfigs.get(serialId);\n            \n            SerialPortLogUtil.i(TAG, String.format(\"串口[%s] - 状态: %s\", \n                serialId, manager.isOpen() ? \"已打开\" : \"已关闭\"));\n            \n            if (config != null) {\n                SerialPortLogUtil.printSerialConfig(TAG + \"_\" + serialId, \n                    config.getDatabits(), config.getParity(), config.getStopbits(), config.getFlags());\n            }\n        }\n    }\n    \n    /**\n     * 获取可用的串口枚举\n     */\n    private SerialPortEnum getAvailableSerialPortEnum() {\n        // 简单的策略：按顺序分配，最多支持6个串口\n        SerialPortEnum[] enums = {\n            SerialPortEnum.SERIAL_ONE,\n            SerialPortEnum.SERIAL_TWO,\n            SerialPortEnum.SERIAL_THREE,\n            SerialPortEnum.SERIAL_FOUR,\n            SerialPortEnum.SERIAL_FIVE,\n            SerialPortEnum.SERIAL_SIX\n        };\n        \n        for (SerialPortEnum serialPortEnum : enums) {\n            boolean isUsed = false;\n            for (SerialPortEnum usedEnum : serialPortEnums.values()) {\n                if (usedEnum == serialPortEnum) {\n                    isUsed = true;\n                    break;\n                }\n            }\n            if (!isUsed) {\n                return serialPortEnum;\n            }\n        }\n        \n        // 如果所有枚举都被使用，返回第一个（可能会有冲突，但至少不会崩溃）\n        SerialPortLogUtil.w(TAG, \"所有串口枚举都已被使用，可能会有冲突\");\n        return SerialPortEnum.SERIAL_ONE;\n    }\n    \n    /**\n     * 串口状态回调接口\n     */\n    public interface OnSerialPortStatusCallback {\n        /**\n         * 串口状态变化\n         * @param serialId 串口ID\n         * @param success 是否成功\n         * @param status 状态\n         */\n        void onStatusChanged(String serialId, boolean success, SerialStatus status);\n    }\n    \n    /**\n     * 串口数据回调接口\n     */\n    public interface OnSerialPortDataCallback {\n        /**\n         * 接收到数据\n         * @param serialId 串口ID\n         * @param data 数据\n         */\n        void onDataReceived(String serialId, byte[] data);\n        \n        /**\n         * 数据发送完成\n         * @param serialId 串口ID\n         * @param data 数据\n         */\n        default void onDataSent(String serialId, byte[] data) {\n            // 默认空实现\n        }\n    }\n    \n    /**\n     * 串口配置类\n     */\n    public static class SerialPortConfig {\n        private boolean enableLogging = true;\n        private int intervalSleep = 50;\n        private int databits = 8;\n        private int parity = 0;\n        private int stopbits = 1;\n        private int flags = 0;\n        private AbsStickPackageHelper[] stickyPacketHelpers;\n        \n        private SerialPortConfig(Builder builder) {\n            this.enableLogging = builder.enableLogging;\n            this.intervalSleep = builder.intervalSleep;\n            this.databits = builder.databits;\n            this.parity = builder.parity;\n            this.stopbits = builder.stopbits;\n            this.flags = builder.flags;\n            this.stickyPacketHelpers = builder.stickyPacketHelpers;\n        }\n        \n        public static class Builder {\n            private boolean enableLogging = true;\n            private int intervalSleep = 50;\n            private int databits = 8;\n            private int parity = 0;\n            private int stopbits = 1;\n            private int flags = 0;\n            private AbsStickPackageHelper[] stickyPacketHelpers;\n            \n            public Builder setEnableLogging(boolean enableLogging) {\n                this.enableLogging = enableLogging;\n                return this;\n            }\n            \n            public Builder setIntervalSleep(int intervalSleep) {\n                this.intervalSleep = intervalSleep;\n                return this;\n            }\n            \n            public Builder setDatabits(int databits) {\n                this.databits = databits;\n                return this;\n            }\n            \n            public Builder setParity(int parity) {\n                this.parity = parity;\n                return this;\n            }\n            \n            public Builder setStopbits(int stopbits) {\n                this.stopbits = stopbits;\n                return this;\n            }\n            \n            public Builder setFlags(int flags) {\n                this.flags = flags;\n                return this;\n            }\n            \n            public Builder setStickyPacketHelpers(AbsStickPackageHelper... helpers) {\n                this.stickyPacketHelpers = helpers;\n                return this;\n            }\n            \n            public SerialPortConfig build() {\n                return new SerialPortConfig(this);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/SerialConfig.java",
    "content": "package com.cl.serialportlibrary;\n\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\n\n/**\n * name：cl\n * date：2023/10/26\n * desc：串口配置类\n */\npublic class SerialConfig {\n\n    //配置日志相关参数\n    private boolean enableLogging;\n    //串口接收间隔时间\n    private int intervalSleep;\n    //串口重连机制\n    private boolean serialPortReconnection;\n    int flags;\n    int databits;\n    int stopbits;\n    int parity;\n    \n    // 黏包处理相关配置\n    private boolean enableStickyPacketProcessing;\n    private int maxPacketSize;\n    private int packetTimeout;\n    private AbsStickPackageHelper[] stickyPacketHelpers;\n    private boolean autoReconnect;\n    private int reconnectInterval;\n    private int maxReconnectAttempts;\n\n\n    public SerialConfig(Builder builder) {\n        this.enableLogging=builder.enableLogging;\n        this.intervalSleep=builder.intervalSleep;\n        this.serialPortReconnection=builder.serialPortReconnection;\n        this.flags=builder.flags;\n        this.databits=builder.databits;\n        this.stopbits=builder.stopbits;\n        this.parity=builder.parity;\n        this.enableStickyPacketProcessing=builder.enableStickyPacketProcessing;\n        this.maxPacketSize=builder.maxPacketSize;\n        this.packetTimeout=builder.packetTimeout;\n        this.stickyPacketHelpers=builder.stickyPacketHelpers;\n        this.autoReconnect=builder.autoReconnect;\n        this.reconnectInterval=builder.reconnectInterval;\n        this.maxReconnectAttempts=builder.maxReconnectAttempts;\n    }\n\n\n    public boolean isEnableLogging() {\n        return enableLogging;\n    }\n    \n    public void setEnableLogging(boolean enableLogging) {\n        this.enableLogging = enableLogging;\n    }\n\n    public int getIntervalSleep() {\n        return intervalSleep;\n    }\n\n    public boolean isSerialPortReconnection() {\n        return serialPortReconnection;\n    }\n\n    public void setIntervalSleep(int intervalSleep) {\n        this.intervalSleep = intervalSleep;\n    }\n\n    public int getFlags() {\n        return flags;\n    }\n\n    public int getDatabits() {\n        return databits;\n    }\n\n    public int getStopbits() {\n        return stopbits;\n    }\n\n    public int getParity() {\n        return parity;\n    }\n\n    public void setFlags(int flags) {\n        this.flags = flags;\n    }\n\n    public void setDatabits(int databits) {\n        this.databits = databits;\n    }\n\n    public void setStopbits(int stopbits) {\n        this.stopbits = stopbits;\n    }\n\n    public void setParity(int parity) {\n        this.parity = parity;\n    }\n\n    public boolean isEnableStickyPacketProcessing() {\n        return enableStickyPacketProcessing;\n    }\n\n    public void setEnableStickyPacketProcessing(boolean enableStickyPacketProcessing) {\n        this.enableStickyPacketProcessing = enableStickyPacketProcessing;\n    }\n\n    public int getMaxPacketSize() {\n        return maxPacketSize;\n    }\n\n    public void setMaxPacketSize(int maxPacketSize) {\n        this.maxPacketSize = maxPacketSize;\n    }\n\n    public int getPacketTimeout() {\n        return packetTimeout;\n    }\n\n    public void setPacketTimeout(int packetTimeout) {\n        this.packetTimeout = packetTimeout;\n    }\n\n    public AbsStickPackageHelper[] getStickyPacketHelpers() {\n        return stickyPacketHelpers;\n    }\n\n    public void setStickyPacketHelpers(AbsStickPackageHelper[] stickyPacketHelpers) {\n        this.stickyPacketHelpers = stickyPacketHelpers;\n    }\n\n    public boolean isAutoReconnect() {\n        return autoReconnect;\n    }\n\n    public void setAutoReconnect(boolean autoReconnect) {\n        this.autoReconnect = autoReconnect;\n    }\n\n    public int getReconnectInterval() {\n        return reconnectInterval;\n    }\n\n    public void setReconnectInterval(int reconnectInterval) {\n        this.reconnectInterval = reconnectInterval;\n    }\n\n    public int getMaxReconnectAttempts() {\n        return maxReconnectAttempts;\n    }\n\n    public void setMaxReconnectAttempts(int maxReconnectAttempts) {\n        this.maxReconnectAttempts = maxReconnectAttempts;\n    }\n\n    public static class Builder {\n\n        //配置日志相关参数\n        private boolean enableLogging = true;\n        //串口接收间隔时间\n        private int intervalSleep = 50;\n        //串口重连机制\n        private boolean serialPortReconnection = false;\n        // 标志位\n        int flags = 0;\n        // 数据位\n        int databits = 8;\n        // 停止位\n        int stopbits = 1;\n        // 校验位：0 表示无校验位，1 表示奇校验，2 表示偶校验\n        int parity = 0;\n        \n        // 黏包处理相关配置\n        private boolean enableStickyPacketProcessing = true;\n        private int maxPacketSize = 1024;\n        private int packetTimeout = 1000;\n        private AbsStickPackageHelper[] stickyPacketHelpers = {new BaseStickPackageHelper()};\n        private boolean autoReconnect = false;\n        private int reconnectInterval = 5000;\n        private int maxReconnectAttempts = 3;\n\n        public Builder setEnableLogging(boolean enableLogging) {\n            this.enableLogging = enableLogging;\n            return this;\n        }\n\n        public Builder setIntervalSleep(int sleep) {\n            intervalSleep = sleep;\n            return this;\n        }\n\n        public Builder setSerialPortReconnection(boolean serialReconnection) {\n            serialPortReconnection = serialReconnection;\n            return this;\n        }\n\n        public Builder setFlags(int flags) {\n            this.flags = flags;\n            return this;\n        }\n\n        public Builder setDatabits(int databits) {\n            this.databits = databits;\n            return this;\n        }\n\n        public Builder setStopbits(int stopbits) {\n            this.stopbits = stopbits;\n            return this;\n        }\n\n        public Builder setParity(int parity) {\n            this.parity = parity;\n            return this;\n        }\n\n        public Builder setEnableStickyPacketProcessing(boolean enableStickyPacketProcessing) {\n            this.enableStickyPacketProcessing = enableStickyPacketProcessing;\n            return this;\n        }\n\n        public Builder setMaxPacketSize(int maxPacketSize) {\n            this.maxPacketSize = maxPacketSize;\n            return this;\n        }\n\n        public Builder setPacketTimeout(int packetTimeout) {\n            this.packetTimeout = packetTimeout;\n            return this;\n        }\n\n        public Builder setStickyPacketHelpers(AbsStickPackageHelper... stickyPacketHelpers) {\n            this.stickyPacketHelpers = stickyPacketHelpers;\n            return this;\n        }\n\n        public Builder setAutoReconnect(boolean autoReconnect) {\n            this.autoReconnect = autoReconnect;\n            return this;\n        }\n\n        public Builder setReconnectInterval(int reconnectInterval) {\n            this.reconnectInterval = reconnectInterval;\n            return this;\n        }\n\n        public Builder setMaxReconnectAttempts(int maxReconnectAttempts) {\n            this.maxReconnectAttempts = maxReconnectAttempts;\n            return this;\n        }\n\n        public SerialConfig build() {\n            return new SerialConfig(this);\n        }\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/SerialPort.java",
    "content": "package com.cl.serialportlibrary;\n\nimport java.io.File;\nimport java.io.FileDescriptor;\nimport java.io.IOException;\n\npublic class SerialPort {\n\n    static {\n        System.loadLibrary(\"SerialPort\");\n    }\n\n    private static final String TAG = SerialPort.class.getSimpleName();\n\n    /**\n     * 文件设置最高权限 777 可读 可写 可执行\n     *\n     * @param file 文件\n     * @return 权限修改是否成功\n     */\n    boolean chmod777(File file) {\n        if (null == file || !file.exists()) {\n            // 文件不存在\n            return false;\n        }\n        try {\n            // 获取ROOT权限\n            Process su = Runtime.getRuntime().exec(\"/system/bin/su\");\n            // 修改文件属性为 [可读 可写 可执行]\n            String cmd = \"chmod 777 \" + file.getAbsolutePath() + \"\\n\" + \"exit\\n\";\n            su.getOutputStream().write(cmd.getBytes());\n            if (0 == su.waitFor() && file.canRead() && file.canWrite() && file.canExecute()) {\n                return true;\n            }\n        } catch (IOException | InterruptedException e) {\n            // 没有ROOT权限\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    // 打开串口\n    protected native FileDescriptor open(String path, int baudrate, int flags, int databits, int stopbits, int parity);\n\n    // 关闭串口\n    protected native void close();\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/SerialPortFinder.java",
    "content": "package com.cl.serialportlibrary;\n\n\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\n\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.io.LineNumberReader;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.Vector;\n\npublic class SerialPortFinder {\n\n    private static final String TAG = SerialPortFinder.class.getSimpleName();\n    private static final String DRIVERS_PATH = \"/proc/tty/drivers\";\n    private static final String SERIAL_FIELD = \"serial\";\n\n    public SerialPortFinder() {\n        File file = new File(DRIVERS_PATH);\n        boolean b = file.canRead();\n        SerialPortLogUtil.i(TAG, \"SerialPortFinder: file.canRead() = \" + b);\n    }\n\n    /**\n     * 获取 Drivers\n     *\n     * @return Drivers\n     * @throws IOException IOException\n     */\n    private ArrayList<Driver> getDrivers() throws IOException {\n        ArrayList<Driver> drivers = new ArrayList<>();\n        LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(DRIVERS_PATH));\n        String readLine;\n        while ((readLine = lineNumberReader.readLine()) != null) {\n            String driverName = readLine.substring(0, 0x15).trim();\n            String[] fields = readLine.split(\" +\");\n            if ((fields.length >= 5) && (fields[fields.length - 1].equals(SERIAL_FIELD))) {\n                SerialPortLogUtil.d(TAG, \"Found new driver \" + driverName + \" on \" + fields[fields.length - 4]);\n                drivers.add(new Driver(driverName, fields[fields.length - 4]));\n            }\n        }\n        return drivers;\n    }\n\n    /**\n     * 获取串口\n     *\n     * @return 串口\n     */\n    public ArrayList<Device> getDevices() {\n        ArrayList<Device> devices = new ArrayList<>();\n        try {\n            ArrayList<Driver> drivers = getDrivers();\n            for (Driver driver : drivers) {\n                String driverName = driver.getName();\n                ArrayList<File> driverDevices = driver.getDevices();\n                for (File file : driverDevices) {\n                    String devicesName = file.getName();\n                    devices.add(new Device(devicesName, driverName, file));\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return devices;\n    }\n\n\n    public String[] getAllDevicesPath() {\n        Vector<String> devices = new Vector<String>();\n        // Parse each driver\n        Iterator<Driver> itdriv;\n        try {\n            itdriv = getDrivers().iterator();\n            while (itdriv.hasNext()) {\n                Driver driver = itdriv.next();\n                Iterator<File> itdev = driver.getDevices().iterator();\n                while (itdev.hasNext()) {\n                    String device = itdev.next().getAbsolutePath();\n                    devices.add(device);\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return devices.toArray(new String[devices.size()]);\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/SerialPortManager.java",
    "content": "package com.cl.serialportlibrary;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Message;\n\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\nimport com.cl.serialportlibrary.enumerate.SerialPortEnum;\nimport com.cl.serialportlibrary.enumerate.SerialStatus;\nimport com.cl.serialportlibrary.listener.OnOpenSerialPortListener;\nimport com.cl.serialportlibrary.listener.OnSerialPortDataListener;\nimport com.cl.serialportlibrary.thread.SerialPortReadThread;\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.FileDescriptor;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\n\npublic class SerialPortManager extends SerialPort {\n\n    private static final String TAG = SerialPortManager.class.getSimpleName();\n    private FileInputStream mFileInputStream;\n    private FileOutputStream mFileOutputStream;\n    private FileDescriptor mFd;\n    private OnOpenSerialPortListener mOnOpenSerialPortListener;\n    private OnSerialPortDataListener mOnSerialPortDataListener;\n    private HandlerThread mSendingHandlerThread;\n    private Handler mSendingHandler;\n    private SerialPortReadThread mSerialPortReadThread;\n    //串口类型\n    private final SerialPortEnum mSerialPortEnum;\n    //串口配置\n    private SerialConfig mSerialConfig;\n    //粘包处理器\n    private List<AbsStickPackageHelper> mStickPackageHelpers;\n\n    public SerialPortManager() {\n        this(SerialPortEnum.SERIAL_ONE);\n    }\n\n    public SerialPortManager(SerialPortEnum mSerialPortEnum) {\n        this.mSerialPortEnum = mSerialPortEnum;\n        this.mStickPackageHelpers = new ArrayList<>();\n        this.mStickPackageHelpers.add(new BaseStickPackageHelper());\n    }\n    \n    /**\n     * 设置串口配置\n     */\n    public void setSerialConfig(SerialConfig config) {\n        this.mSerialConfig = config;\n        if (config.getStickyPacketHelpers() != null && config.getStickyPacketHelpers().length > 0) {\n            this.mStickPackageHelpers = Arrays.asList(config.getStickyPacketHelpers());\n        }\n    }\n    \n    /**\n     * 设置粘包处理器\n     */\n    public void setStickPackageHelpers(List<AbsStickPackageHelper> helpers) {\n        if (helpers != null) {\n            this.mStickPackageHelpers = helpers;\n        }\n    }\n\n    /**\n     * 打开串口\n     *\n     * @param devicePath 串口号\n     * @param baudRate   波特率\n     */\n    public boolean openSerialPort(String devicePath, int baudRate) {\n        closeSerialPort();\n        SerialPortLogUtil.i(TAG, \"openSerialPort: \" + String.format(\"打开串口 %s  波特率 %s\", devicePath, baudRate));\n        // 校验串口权限\n        if (!checkSerialPortPermission(devicePath)) {\n            return false;\n        }\n        try {\n            // 使用配置参数或默认值\n            int flags = mSerialConfig != null ? mSerialConfig.getFlags() : 0;\n            int databits = mSerialConfig != null ? mSerialConfig.getDatabits() : 8;\n            int stopbits = mSerialConfig != null ? mSerialConfig.getStopbits() : 1;\n            int parity = mSerialConfig != null ? mSerialConfig.getParity() : 0;\n            \n            SerialPortLogUtil.d(TAG, \"串口参数 - flags: \" + flags + \", databits: \" + databits + \", stopbits: \" + stopbits + \", parity: \" + parity);\n            mFd = open(devicePath, baudRate, flags, databits, stopbits, parity);\n            mFileInputStream = new FileInputStream(mFd);\n            mFileOutputStream = new FileOutputStream(mFd);\n            SerialPortLogUtil.i(TAG, \"openSerialPort: 串口已经打开 \" + mFd);\n            notifySerialPortOpened(new File(devicePath), SerialStatus.SUCCESS_OPENED);\n            // 开启发送消息的线程\n            startSendThread();\n            // 开启接收消息的线程\n            startReadThread();\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            notifySerialPortOpened(new File(devicePath), SerialStatus.OPEN_FAIL);\n            return false;\n        }\n    }\n\n    /**\n     * 检查串口权限\n     *\n     * @param devicePath 串口号\n     * @return 是否有权限\n     */\n    private boolean checkSerialPortPermission(String devicePath) {\n        File deviceFile = new File(devicePath);\n        if (!deviceFile.canRead() || !deviceFile.canWrite()) {\n            boolean chmod777 = chmod777(deviceFile);\n            if (!chmod777) {\n                SerialPortLogUtil.e(TAG, \"串口权限不足: \" + devicePath);\n                notifySerialPortOpened(deviceFile, SerialStatus.NO_READ_WRITE_PERMISSION);\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 通知串口打开状态\n     *\n     * @param deviceFile 串口文件\n     * @param status     打开状态\n     */\n    private void notifySerialPortOpened(File deviceFile, SerialStatus status) {\n        if (null != mOnOpenSerialPortListener) {\n            mOnOpenSerialPortListener.openState(mSerialPortEnum, deviceFile, status);\n        }\n    }\n\n    /**\n     * 检查串口是否已打开\n     */\n    public boolean isOpen() {\n        return mFd != null && mFileInputStream != null && mFileOutputStream != null;\n    }\n    \n    /**\n     * 关闭串口\n     */\n    public void closeSerialPort() {\n        if (null != mFd) {\n            close();\n            mFd = null;\n        }\n        // 停止发送消息的线程\n        stopSendThread();\n        // 停止接收消息的线程\n        stopReadThread();\n        closeStream(mFileInputStream);\n        closeStream(mFileOutputStream);\n    }\n\n    /**\n     * 关闭流\n     *\n     * @param stream 流对象\n     */\n    private void closeStream(Closeable stream) {\n        if (null != stream) {\n            try {\n                stream.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    /**\n     * 添加打开串口监听\n     *\n     * @param listener listener\n     * @return SerialPortManager\n     */\n    public SerialPortManager setOnOpenSerialPortListener(OnOpenSerialPortListener listener) {\n        mOnOpenSerialPortListener = listener;\n        return this;\n    }\n\n    /**\n     * 添加数据通信监听\n     *\n     * @param listener listener\n     * @return SerialPortManager\n     */\n    public SerialPortManager setOnSerialPortDataListener(OnSerialPortDataListener listener) {\n        mOnSerialPortDataListener = listener;\n        return this;\n    }\n\n    /**\n     * 开启发送消息的线程\n     */\n    private void startSendThread() {\n        // 开启发送消息的线程\n        mSendingHandlerThread = new HandlerThread(\"mSendingHandlerThread\" + mSerialPortEnum.name());\n        mSendingHandlerThread.start();\n        // Handler\n        mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) {\n            @Override\n            public void handleMessage(Message msg) {\n                byte[] sendBytes = (byte[]) msg.obj;\n\n                if (null != mFileOutputStream && null != sendBytes && 0 < sendBytes.length) {\n                    try {\n                        mFileOutputStream.write(sendBytes);\n                        if (null != mOnSerialPortDataListener) {\n                            mOnSerialPortDataListener.onDataSent(sendBytes, mSerialPortEnum);\n                        }\n                    } catch (IOException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n        };\n    }\n\n    /**\n     * 停止发送消息线程\n     */\n    private void stopSendThread() {\n        mSendingHandler = null;\n        if (null != mSendingHandlerThread) {\n            mSendingHandlerThread.interrupt();\n            mSendingHandlerThread.quit();\n            mSendingHandlerThread = null;\n        }\n    }\n\n    /**\n     * 开启接收消息的线程\n     */\n    private void startReadThread() {\n        mSerialPortReadThread = new SerialPortReadThread(mFileInputStream, mSerialPortEnum, mStickPackageHelpers) {\n            @Override\n            public void onDataReceived(byte[] bytes) {\n                if (null != mOnSerialPortDataListener) {\n                    mOnSerialPortDataListener.onDataReceived(bytes, mSerialPortEnum);\n                }\n            }\n        };\n        mSerialPortReadThread.start();\n        SerialPortLogUtil.d(TAG, \"启动数据接收线程\");\n    }\n\n    /**\n     * 停止接收消息的线程\n     */\n    private void stopReadThread() {\n        if (null != mSerialPortReadThread) {\n            mSerialPortReadThread.release();\n        }\n    }\n\n    /**\n     * 发送数据\n     *\n     * @param sendBytes 发送数据\n     * @return 发送是否成功\n     */\n    public boolean sendBytes(byte[] sendBytes) {\n        if (null != mFd && null != mFileInputStream && null != mFileOutputStream) {\n            if (null != mSendingHandler) {\n                Message message = Message.obtain();\n                message.obj = sendBytes;\n                return mSendingHandler.sendMessage(message);\n            }\n        }\n        return false;\n    }\n\n}"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/SimpleSerialPortManager.java",
    "content": "package com.cl.serialportlibrary;\n\nimport android.app.Application;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport com.cl.serialportlibrary.enumerate.SerialPortEnum;\nimport com.cl.serialportlibrary.enumerate.SerialStatus;\nimport com.cl.serialportlibrary.listener.OnOpenSerialPortListener;\nimport com.cl.serialportlibrary.listener.OnSerialPortDataListener;\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\nimport com.cl.serialportlibrary.stick.SpecifiedStickPackageHelper;\nimport com.cl.serialportlibrary.stick.StaticLenStickPackageHelper;\nimport com.cl.serialportlibrary.stick.VariableLenStickPackageHelper;\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 简化的串口管理器，提供更简单的API供外部使用\n * 完全独立，不依赖SerialUtils\n * Author: cl\n * Date: 2023/10/26\n */\npublic class SimpleSerialPortManager {\n    \n    private static final String TAG = \"SimpleSerialPortManager\";\n    private static SimpleSerialPortManager instance;\n    private OnOpenSerialPortCallback openCallback;\n    private OnDataReceivedCallback dataCallback;\n    \n    // 串口管理器\n    private SerialPortManager serialPortManager;\n    private SerialConfig serialConfig;\n    private Handler handler = new Handler(Looper.getMainLooper());\n    private List<AbsStickPackageHelper> stickPackageHelpers = new ArrayList<>();\n    private boolean isInitialized = false;\n    \n    // 串口参数配置\n    private int databits = 8;      // 数据位，默认8\n    private int parity = 0;        // 校验位，默认0（无校验）\n    private int stopbits = 1;      // 停止位，默认1\n    private int flags = 0;         // 标志位，默认0\n    \n    private SimpleSerialPortManager() {\n        // 默认添加基础粘包处理器\n        stickPackageHelpers.add(new BaseStickPackageHelper());\n    }\n    \n    /**\n     * 获取单例实例\n     */\n    public static SimpleSerialPortManager getInstance() {\n        if (instance == null) {\n            synchronized (SimpleSerialPortManager.class) {\n                if (instance == null) {\n                    instance = new SimpleSerialPortManager();\n                }\n            }\n        }\n        return instance;\n    }\n    \n    /**\n     * 简单初始化 - 最基本的配置\n     * @param application 应用程序上下文\n     */\n    public SimpleSerialPortManager init(Application application) {\n        return init(application, true, \"SerialPort\", 50);\n    }\n    \n    /**\n     * 初始化串口管理器 - 基础配置\n     * @param application 应用程序上下文\n     * @param enableLog 是否启用日志\n     * @param logTag 日志标签\n     * @param intervalSleep 读取间隔时间(ms)\n     */\n    public SimpleSerialPortManager init(Application application, boolean enableLog, String logTag, int intervalSleep) {\n        SerialPortLogUtil.setDebugEnabled(enableLog);\n        SerialPortLogUtil.i(TAG, \"初始化SimpleSerialPortManager - enableLog: \" + enableLog + \", tag: \" + logTag + \", interval: \" + intervalSleep);\n        \n        // 创建默认配置\n        serialConfig = new SerialConfig.Builder()\n                .setEnableLogging(enableLog)\n                .setIntervalSleep(intervalSleep)\n                .setDatabits(databits)\n                .setParity(parity)\n                .setStopbits(stopbits)\n                .setFlags(flags)\n                .setEnableStickyPacketProcessing(true)\n                .setStickyPacketHelpers(stickPackageHelpers.toArray(new AbsStickPackageHelper[0]))\n                .build();\n                \n        isInitialized = true;\n        SerialPortLogUtil.i(TAG, \"SimpleSerialPortManager初始化完成\");\n        return this;\n    }\n    \n    /**\n     * 初始化串口管理器 - 使用SerialConfig配置\n     * @param application 应用程序上下文\n     * @param config 串口配置\n     */\n    public SimpleSerialPortManager init(Application application, SerialConfig config) {\n        this.serialConfig = config;\n        SerialPortLogUtil.setDebugEnabled(config.isEnableLogging());\n        SerialPortLogUtil.i(TAG, \"使用SerialConfig初始化SimpleSerialPortManager\");\n        \n        // 同步串口参数\n        this.databits = config.getDatabits();\n        this.parity = config.getParity();\n        this.stopbits = config.getStopbits();\n        this.flags = config.getFlags();\n        \n        isInitialized = true;\n        SerialPortLogUtil.i(TAG, \"SimpleSerialPortManager初始化完成\");\n        return this;\n    }\n    \n    /**\n     * 配置粘包处理策略\n     */\n    public SimpleSerialPortManager configureStickyPacket(StickyPacketStrategy strategy) {\n        stickPackageHelpers.clear();\n        \n        AbsStickPackageHelper helper;\n        switch (strategy) {\n            case DELIMITER_BASED:\n                helper = new SpecifiedStickPackageHelper(\"\\n\");\n                SerialPortLogUtil.i(TAG, \"配置粘包处理策略: 分隔符模式\");\n                break;\n            case FIXED_LENGTH:\n                helper = new StaticLenStickPackageHelper();\n                SerialPortLogUtil.i(TAG, \"配置粘包处理策略: 固定长度模式\");\n                break;\n            case VARIABLE_LENGTH:\n                helper = new VariableLenStickPackageHelper(java.nio.ByteOrder.BIG_ENDIAN, 2, 2, 12);\n                SerialPortLogUtil.i(TAG, \"配置粘包处理策略: 可变长度模式\");\n                break;\n            case NO_PROCESSING:\n            default:\n                helper = new BaseStickPackageHelper();\n                SerialPortLogUtil.i(TAG, \"配置粘包处理策略: 无处理模式\");\n                break;\n        }\n        \n        stickPackageHelpers.add(helper);\n        \n        // 如果已经初始化，更新配置\n        if (serialConfig != null) {\n            serialConfig.setStickyPacketHelpers(stickPackageHelpers.toArray(new AbsStickPackageHelper[0]));\n        }\n        \n        return this;\n    }\n    \n    /**\n     * 设置自定义粘包处理器\n     */\n    public SimpleSerialPortManager setStickyPacketHelpers(AbsStickPackageHelper... helpers) {\n        stickPackageHelpers.clear();\n        for (AbsStickPackageHelper helper : helpers) {\n            stickPackageHelpers.add(helper);\n        }\n        \n        // 如果已经初始化，更新配置\n        if (serialConfig != null) {\n            serialConfig.setStickyPacketHelpers(helpers);\n        }\n        \n        SerialPortLogUtil.i(TAG, \"设置自定义粘包处理器，数量: \" + helpers.length);\n        return this;\n    }\n    \n    /**\n     * 打开串口\n     * @param devicePath 设备路径\n     * @param baudRate 波特率\n     * @param callback 数据接收回调\n     * @return 是否打开成功\n     */\n    public boolean openSerialPort(String devicePath, int baudRate, OnDataReceivedCallback callback) {\n        return openSerialPort(devicePath, baudRate, null, callback);\n    }\n    \n    /**\n     * 打开串口\n     * @param devicePath 设备路径\n     * @param baudRate 波特率\n     * @param openCallback 打开状态回调\n     * @param dataCallback 数据接收回调\n     * @return 是否打开成功\n     */\n    public boolean openSerialPort(String devicePath, int baudRate, OnOpenSerialPortCallback openCallback, OnDataReceivedCallback dataCallback) {\n        if (!isInitialized) {\n            SerialPortLogUtil.e(TAG, \"SimpleSerialPortManager未初始化，请先调用init()方法\");\n            return false;\n        }\n        \n        this.openCallback = openCallback;\n        this.dataCallback = dataCallback;\n        \n        SerialPortLogUtil.i(TAG, \"尝试打开串口: \" + devicePath + \", 波特率: \" + baudRate);\n        SerialPortLogUtil.printSerialConfig(TAG, databits, parity, stopbits, flags);\n        \n        // 更新配置中的串口参数\n        updateSerialConfig();\n        \n        // 创建SerialPortManager并设置配置\n        serialPortManager = new SerialPortManager();\n        serialPortManager.setSerialConfig(serialConfig);\n        serialPortManager.setStickPackageHelpers(stickPackageHelpers);\n        \n        // 设置监听器\n        serialPortManager.setOnOpenSerialPortListener(new OnOpenSerialPortListener() {\n            @Override\n            public void openState(SerialPortEnum serialPortEnum, File device, SerialStatus status) {\n                if (status == SerialStatus.SUCCESS_OPENED) {\n                    SerialPortLogUtil.i(TAG, \"串口打开成功: \" + device.getPath());\n                    handler.post(() -> {\n                        if (openCallback != null) {\n                            openCallback.onStatusChanged(true, status);\n                        }\n                    });\n                } else {\n                    SerialPortLogUtil.e(TAG, \"串口打开失败: \" + device.getPath() + \", 状态: \" + status);\n                    handler.post(() -> {\n                        if (openCallback != null) {\n                            openCallback.onStatusChanged(false, status);\n                        }\n                    });\n                }\n            }\n        });\n        \n        serialPortManager.setOnSerialPortDataListener(new OnSerialPortDataListener() {\n            @Override\n            public void onDataReceived(byte[] data, SerialPortEnum serialPortEnum) {\n                SerialPortLogUtil.printData(TAG, \"接收数据\", data);\n                handler.post(() -> {\n                    if (dataCallback != null) {\n                        dataCallback.onDataReceived(data);\n                    }\n                });\n            }\n            \n            @Override\n            public void onDataSent(byte[] data, SerialPortEnum serialPortEnum) {\n                SerialPortLogUtil.printData(TAG, \"发送数据\", data);\n                handler.post(() -> {\n                    if (dataCallback != null) {\n                        dataCallback.onDataSent(data);\n                    }\n                });\n            }\n        });\n        \n        // 打开串口\n        return serialPortManager.openSerialPort(devicePath, baudRate);\n    }\n    \n    /**\n     * 更新串口配置参数\n     */\n    private void updateSerialConfig() {\n        if (serialConfig != null) {\n            serialConfig.setDatabits(this.databits);\n            serialConfig.setParity(this.parity);\n            serialConfig.setStopbits(this.stopbits);\n            serialConfig.setFlags(this.flags);\n            SerialPortLogUtil.d(TAG, \"更新串口配置 - 数据位: \" + databits + \", 校验位: \" + parity + \", 停止位: \" + stopbits);\n        }\n    }\n    \n    /**\n     * 发送数据\n     * @param data 要发送的数据\n     * @return 是否发送成功\n     */\n    public boolean sendData(byte[] data) {\n        if (serialPortManager == null) {\n            SerialPortLogUtil.e(TAG, \"串口未打开，无法发送数据\");\n            return false;\n        }\n        \n        if (data == null || data.length == 0) {\n            SerialPortLogUtil.w(TAG, \"尝试发送空数据\");\n            return false;\n        }\n        \n        long startTime = System.currentTimeMillis();\n        SerialPortLogUtil.printData(TAG, \"准备发送\", data);\n        \n        boolean result = serialPortManager.sendBytes(data);\n        SerialPortLogUtil.printPerformance(TAG, \"发送数据\", startTime);\n        \n        if (!result) {\n            SerialPortLogUtil.e(TAG, \"数据发送失败\");\n        }\n        \n        return result;\n    }\n    \n    /**\n     * 发送字符串数据\n     * @param data 要发送的字符串\n     * @return 是否发送成功\n     */\n    public boolean sendData(String data) {\n        return sendData(data.getBytes());\n    }\n    \n    /**\n     * 关闭串口\n     */\n    public void closeSerialPort() {\n        if (serialPortManager != null) {\n            SerialPortLogUtil.i(TAG, \"关闭串口\");\n            serialPortManager.closeSerialPort();\n            serialPortManager = null;\n        }\n        openCallback = null;\n        dataCallback = null;\n    }\n    \n    /**\n     * 检查串口是否已打开\n     */\n    public boolean isSerialPortOpened() {\n        return serialPortManager != null && serialPortManager.isOpen();\n    }\n    \n    // Getter和Setter方法\n    public SimpleSerialPortManager setDatabits(int databits) {\n        this.databits = databits;\n        SerialPortLogUtil.d(TAG, \"设置数据位: \" + databits);\n        return this;\n    }\n    \n    public SimpleSerialPortManager setParity(int parity) {\n        this.parity = parity;\n        SerialPortLogUtil.d(TAG, \"设置校验位: \" + parity);\n        return this;\n    }\n    \n    public SimpleSerialPortManager setStopbits(int stopbits) {\n        this.stopbits = stopbits;\n        SerialPortLogUtil.d(TAG, \"设置停止位: \" + stopbits);\n        return this;\n    }\n    \n    public SimpleSerialPortManager setFlags(int flags) {\n        this.flags = flags;\n        SerialPortLogUtil.d(TAG, \"设置标志位: \" + flags);\n        return this;\n    }\n    \n    public int getDatabits() {\n        return databits;\n    }\n    \n    public int getParity() {\n        return parity;\n    }\n    \n    public int getStopbits() {\n        return stopbits;\n    }\n    \n    public int getFlags() {\n        return flags;\n    }\n    \n    public SerialConfig getSerialConfig() {\n        return serialConfig;\n    }\n    \n    /**\n     * 粘包处理策略枚举\n     */\n    public enum StickyPacketStrategy {\n        NO_PROCESSING,      // 不处理\n        DELIMITER_BASED,    // 基于分隔符\n        FIXED_LENGTH,       // 固定长度\n        VARIABLE_LENGTH     // 可变长度\n    }\n    \n    /**\n     * 串口打开状态回调接口\n     */\n    public interface OnOpenSerialPortCallback {\n        /**\n         * 串口状态变化回调\n         * @param success 是否成功\n         * @param status 状态\n         */\n        void onStatusChanged(boolean success, SerialStatus status);\n    }\n    \n    /**\n     * 数据接收回调接口\n     */\n    public interface OnDataReceivedCallback {\n        /**\n         * 接收到数据\n         * @param data 接收到的数据\n         */\n        void onDataReceived(byte[] data);\n        \n        /**\n         * 数据发送完成\n         * @param data 发送的数据\n         */\n        default void onDataSent(byte[] data) {\n            // 默认空实现\n        }\n    }\n    \n    /**\n     * 多串口管理 - 获取多串口管理器实例\n     * @return MultiSerialPortManager实例\n     */\n    public static MultiSerialPortManager multi() {\n        return MultiSerialPortManager.getInstance();\n    }\n    \n    /**\n     * 快速配置构建器\n     */\n    public static class QuickConfig {\n        private int intervalSleep = 50;\n        private boolean enableLog = true;\n        private String logTag = \"SerialPort\";\n        private StickyPacketStrategy stickyPacketStrategy = StickyPacketStrategy.NO_PROCESSING;\n        private int maxPacketSize = 1024;\n        private boolean autoReconnect = false;\n        private int databits = 8;\n        private int parity = 0;\n        private int stopbits = 1;\n        private int flags = 0;\n        \n        public QuickConfig setIntervalSleep(int intervalSleep) {\n            this.intervalSleep = intervalSleep;\n            return this;\n        }\n        \n        public QuickConfig setEnableLog(boolean enableLog) {\n            this.enableLog = enableLog;\n            return this;\n        }\n        \n        public QuickConfig setLogTag(String logTag) {\n            this.logTag = logTag;\n            return this;\n        }\n        \n        public QuickConfig setStickyPacketStrategy(StickyPacketStrategy strategy) {\n            this.stickyPacketStrategy = strategy;\n            return this;\n        }\n        \n        public QuickConfig setMaxPacketSize(int maxPacketSize) {\n            this.maxPacketSize = maxPacketSize;\n            return this;\n        }\n        \n        public QuickConfig setAutoReconnect(boolean autoReconnect) {\n            this.autoReconnect = autoReconnect;\n            return this;\n        }\n        \n        public QuickConfig setDatabits(int databits) {\n            this.databits = databits;\n            return this;\n        }\n        \n        public QuickConfig setParity(int parity) {\n            this.parity = parity;\n            return this;\n        }\n        \n        public QuickConfig setStopbits(int stopbits) {\n            this.stopbits = stopbits;\n            return this;\n        }\n        \n        public QuickConfig setFlags(int flags) {\n            this.flags = flags;\n            return this;\n        }\n        \n        /**\n         * 应用配置并返回SimpleSerialPortManager实例\n         */\n        public SimpleSerialPortManager apply(Application application) {\n            SerialConfig config = new SerialConfig.Builder()\n                    .setIntervalSleep(intervalSleep)\n                    .setEnableLogging(enableLog)\n                    .setEnableStickyPacketProcessing(stickyPacketStrategy != StickyPacketStrategy.NO_PROCESSING)\n                    .setMaxPacketSize(maxPacketSize)\n                    .setAutoReconnect(autoReconnect)\n                    .setDatabits(databits)\n                    .setParity(parity)\n                    .setStopbits(stopbits)\n                    .setFlags(flags)\n                    .build();\n\n            SimpleSerialPortManager manager = SimpleSerialPortManager.getInstance()\n                    .init(application, config)\n                    .configureStickyPacket(stickyPacketStrategy);\n\n            // 设置串口参数\n            manager.setDatabits(databits)\n                   .setParity(parity)\n                   .setStopbits(stopbits)\n                   .setFlags(flags);\n\n            SerialPortLogUtil.i(\"QuickConfig\", \"快速配置完成 - 间隔: \" + intervalSleep + \"ms, 日志: \" + enableLog + \", 策略: \" + stickyPacketStrategy);\n            return manager;\n        }\n    }\n}"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/enumerate/SerialPortEnum.java",
    "content": "package com.cl.serialportlibrary.enumerate;\n\n/**\n * name：cl\n * date：2023/2/20\n * desc：串口枚举类型\n */\npublic enum SerialPortEnum {\n    //串口1\n    SERIAL_ONE,\n    //串口2\n    SERIAL_TWO,\n    //串口3\n    SERIAL_THREE,\n    //串口4\n    SERIAL_FOUR,\n    //串口5\n    SERIAL_FIVE,\n    //串口6\n    SERIAL_SIX\n\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/enumerate/SerialStatus.java",
    "content": "package com.cl.serialportlibrary.enumerate;\n\n/**\n * name：cl\n * date：2023/2/20\n * desc：\n */\npublic enum SerialStatus {\n    NO_READ_WRITE_PERMISSION,\n    OPEN_FAIL,\n    SUCCESS_OPENED\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/example/MultiSerialPortExample.java",
    "content": "package com.cl.serialportlibrary.example;\n\nimport android.app.Application;\nimport android.util.Log;\n\nimport com.cl.serialportlibrary.MultiSerialPortManager;\nimport com.cl.serialportlibrary.enumerate.SerialStatus;\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\nimport com.cl.serialportlibrary.stick.SpecifiedStickPackageHelper;\nimport com.cl.serialportlibrary.stick.StaticLenStickPackageHelper;\nimport com.cl.serialportlibrary.stick.StickyPacketHelperFactory;\n\n/**\n * 多串口使用示例\n * 展示如何同时管理多个串口，每个串口使用不同的粘包处理策略\n */\npublic class MultiSerialPortExample {\n    \n    private static final String TAG = \"MultiSerialPortExample\";\n    \n    /**\n     * 示例1：基础多串口使用\n     */\n    public void basicMultiSerialExample(Application application) {\n        MultiSerialPortManager manager = MultiSerialPortManager.getInstance();\n        \n        // 串口1：不需要粘包处理，用于简单的数据传输\n        manager.openSerialPort(\"GPS\", \"/dev/ttyS1\", 9600, \n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0)\n                .setStopbits(1)\n                .setStickyPacketHelpers(new BaseStickPackageHelper()) // 不处理粘包\n                .build(),\n            // 状态回调\n            (serialId, success, status) -> {\n                Log.i(TAG, String.format(\"串口[%s] 状态: %s\", serialId, success ? \"打开成功\" : \"打开失败\"));\n            },\n            // 数据回调\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String gpsData = new String(data);\n                    Log.i(TAG, \"GPS数据: \" + gpsData);\n                    // 处理GPS数据\n                }\n            });\n        \n        // 串口2：需要按换行符分包，用于文本协议\n        manager.openSerialPort(\"SENSOR\", \"/dev/ttyS2\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0)\n                .setStopbits(1)\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\")) // 按换行符分包\n                .build(),\n            null, // 不需要状态回调\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String sensorData = new String(data).trim();\n                    Log.i(TAG, \"传感器数据: \" + sensorData);\n                    // 处理传感器数据\n                }\n            });\n        \n        // 发送数据到不同串口\n        manager.sendData(\"GPS\", \"AT+GPS\\r\\n\");\n        manager.sendData(\"SENSOR\", \"READ_TEMP\\n\");\n    }\n    \n    /**\n     * 示例2：复杂的多串口场景\n     */\n    public void advancedMultiSerialExample(Application application) {\n        MultiSerialPortManager manager = MultiSerialPortManager.getInstance();\n        \n        // 串口1：Modbus RTU协议，固定长度包\n        manager.openSerialPort(\"MODBUS\", \"/dev/ttyS3\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(2) // 偶校验\n                .setStopbits(1)\n                .setStickyPacketHelpers(new StaticLenStickPackageHelper(8)) // 固定8字节\n                .build(),\n            (serialId, success, status) -> {\n                if (success) {\n                    Log.i(TAG, \"Modbus串口打开成功\");\n                    // 发送读取寄存器命令\n                    byte[] readCmd = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, (byte)0x84, 0x0A};\n                    manager.sendData(\"MODBUS\", readCmd);\n                }\n            },\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    Log.i(TAG, \"Modbus响应: \" + bytesToHex(data));\n                    // 解析Modbus响应\n                    parseModbusResponse(data);\n                }\n            });\n        \n        // 串口2：自定义协议，可变长度包\n        manager.openSerialPort(\"CUSTOM\", \"/dev/ttyS4\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0)\n                .setStopbits(1)\n                .setStickyPacketHelpers(\n                    StickyPacketHelperFactory.createVariableLength(\n                        java.nio.ByteOrder.BIG_ENDIAN, 2, 2, 12)) // 可变长度包\n                .build(),\n            null,\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    Log.i(TAG, \"自定义协议数据: \" + bytesToHex(data));\n                    // 处理自定义协议数据\n                    parseCustomProtocol(data);\n                }\n            });\n        \n        // 串口3：AT命令，多种分隔符\n        manager.openSerialPort(\"MODEM\", \"/dev/ttyS5\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setDatabits(8)\n                .setParity(0)\n                .setStopbits(1)\n                .setStickyPacketHelpers(\n                    StickyPacketHelperFactory.Common.createATCommand()) // AT命令分包\n                .build(),\n            null,\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String response = new String(data).trim();\n                    Log.i(TAG, \"AT响应: \" + response);\n                    // 处理AT命令响应\n                    parseATResponse(response);\n                }\n            });\n    }\n    \n    /**\n     * 示例3：动态管理串口\n     */\n    public void dynamicSerialManagement() {\n        MultiSerialPortManager manager = MultiSerialPortManager.getInstance();\n        \n        // 创建一个通用的数据回调\n        MultiSerialPortManager.OnSerialPortDataCallback commonCallback = \n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    Log.i(TAG, String.format(\"串口[%s] 收到数据: %s\", serialId, new String(data)));\n                }\n                \n                @Override\n                public void onDataSent(String serialId, byte[] data) {\n                    Log.d(TAG, String.format(\"串口[%s] 发送数据: %s\", serialId, new String(data)));\n                }\n            };\n        \n        // 批量打开串口\n        String[] devices = {\"/dev/ttyS1\", \"/dev/ttyS2\", \"/dev/ttyS3\"};\n        int[] baudRates = {9600, 115200, 57600};\n        \n        for (int i = 0; i < devices.length; i++) {\n            String serialId = \"SERIAL_\" + (i + 1);\n            \n            // 根据不同需求配置不同的粘包处理\n            MultiSerialPortManager.SerialPortConfig config;\n            switch (i) {\n                case 0: // 第一个串口不需要粘包处理\n                    config = new MultiSerialPortManager.SerialPortConfig.Builder()\n                        .setStickyPacketHelpers(new BaseStickPackageHelper())\n                        .build();\n                    break;\n                case 1: // 第二个串口需要换行符分包\n                    config = new MultiSerialPortManager.SerialPortConfig.Builder()\n                        .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\"))\n                        .build();\n                    break;\n                case 2: // 第三个串口需要固定长度分包\n                    config = new MultiSerialPortManager.SerialPortConfig.Builder()\n                        .setStickyPacketHelpers(new StaticLenStickPackageHelper(16))\n                        .build();\n                    break;\n                default:\n                    config = new MultiSerialPortManager.SerialPortConfig.Builder().build();\n                    break;\n            }\n            \n            manager.openSerialPort(serialId, devices[i], baudRates[i], config, null, commonCallback);\n        }\n        \n        // 打印所有串口状态\n        manager.printAllSerialStatus();\n        \n        // 向所有串口发送测试数据\n        for (String serialId : manager.getOpenedSerialPorts()) {\n            manager.sendData(serialId, \"Hello from \" + serialId + \"\\n\");\n        }\n        \n        // 动态更新某个串口的粘包处理器\n        manager.updateStickyPacketHelpers(\"SERIAL_1\", \n            new AbsStickPackageHelper[]{new SpecifiedStickPackageHelper(\"END\")});\n    }\n    \n    /**\n     * 示例4：串口数据路由\n     */\n    public void serialDataRouting() {\n        MultiSerialPortManager manager = MultiSerialPortManager.getInstance();\n        \n        // 主控制串口：接收外部命令\n        manager.openSerialPort(\"MAIN_CTRL\", \"/dev/ttyS1\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null,\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String command = new String(data).trim();\n                    Log.i(TAG, \"主控制命令: \" + command);\n                    \n                    // 根据命令路由到不同的串口\n                    if (command.startsWith(\"GPS:\")) {\n                        String gpsCmd = command.substring(4);\n                        manager.sendData(\"GPS_MODULE\", gpsCmd + \"\\r\\n\");\n                    } else if (command.startsWith(\"SENSOR:\")) {\n                        String sensorCmd = command.substring(7);\n                        manager.sendData(\"SENSOR_MODULE\", sensorCmd + \"\\n\");\n                    }\n                }\n            });\n        \n        // GPS模块串口\n        manager.openSerialPort(\"GPS_MODULE\", \"/dev/ttyS2\", 9600,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\r\\n\"))\n                .build(),\n            null,\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String gpsResponse = new String(data).trim();\n                    Log.i(TAG, \"GPS响应: \" + gpsResponse);\n                    // 将GPS响应转发到主控制串口\n                    manager.sendData(\"MAIN_CTRL\", \"GPS_RESP:\" + gpsResponse + \"\\r\\n\");\n                }\n            });\n        \n        // 传感器模块串口\n        manager.openSerialPort(\"SENSOR_MODULE\", \"/dev/ttyS3\", 115200,\n            new MultiSerialPortManager.SerialPortConfig.Builder()\n                .setStickyPacketHelpers(new SpecifiedStickPackageHelper(\"\\n\"))\n                .build(),\n            null,\n            new MultiSerialPortManager.OnSerialPortDataCallback() {\n                @Override\n                public void onDataReceived(String serialId, byte[] data) {\n                    String sensorResponse = new String(data).trim();\n                    Log.i(TAG, \"传感器响应: \" + sensorResponse);\n                    // 将传感器响应转发到主控制串口\n                    manager.sendData(\"MAIN_CTRL\", \"SENSOR_RESP:\" + sensorResponse + \"\\r\\n\");\n                }\n            });\n    }\n    \n    /**\n     * 清理资源\n     */\n    public void cleanup() {\n        // 关闭所有串口\n        MultiSerialPortManager.getInstance().closeAllSerialPorts();\n        Log.i(TAG, \"所有串口已关闭\");\n    }\n    \n    // 辅助方法\n    private String bytesToHex(byte[] bytes) {\n        StringBuilder sb = new StringBuilder();\n        for (byte b : bytes) {\n            sb.append(String.format(\"%02X \", b));\n        }\n        return sb.toString().trim();\n    }\n    \n    private void parseModbusResponse(byte[] data) {\n        // Modbus响应解析逻辑\n        if (data.length >= 3) {\n            int slaveId = data[0] & 0xFF;\n            int functionCode = data[1] & 0xFF;\n            Log.i(TAG, String.format(\"Modbus - 从机ID: %d, 功能码: %d\", slaveId, functionCode));\n        }\n    }\n    \n    private void parseCustomProtocol(byte[] data) {\n        // 自定义协议解析逻辑\n        Log.i(TAG, \"解析自定义协议数据，长度: \" + data.length);\n    }\n    \n    private void parseATResponse(String response) {\n        // AT命令响应解析逻辑\n        if (response.equals(\"OK\")) {\n            Log.i(TAG, \"AT命令执行成功\");\n        } else if (response.equals(\"ERROR\")) {\n            Log.e(TAG, \"AT命令执行失败\");\n        } else if (response.startsWith(\"+\")) {\n            Log.i(TAG, \"AT命令数据响应: \" + response);\n        }\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/listener/OnOpenSerialPortListener.java",
    "content": "package com.cl.serialportlibrary.listener;\n\nimport com.cl.serialportlibrary.enumerate.SerialPortEnum;\nimport com.cl.serialportlibrary.enumerate.SerialStatus;\n\nimport java.io.File;\n\n/**\n * 打开串口监听\n */\npublic interface OnOpenSerialPortListener {\n\n    void openState(SerialPortEnum serialPortEnum, File device, SerialStatus status);\n\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/listener/OnSerialPortDataListener.java",
    "content": "package com.cl.serialportlibrary.listener;\n\nimport com.cl.serialportlibrary.enumerate.SerialPortEnum;\n\n/**\n * 串口消息监听\n */\npublic interface OnSerialPortDataListener {\n\n    /**\n     * 数据接收\n     *\n     * @param bytes 接收到的数据\n     */\n    void onDataReceived(byte[] bytes, SerialPortEnum serialPortEnum);\n\n    /**\n     * 数据发送\n     *\n     * @param bytes 发送的数据\n     */\n    void onDataSent(byte[] bytes,SerialPortEnum serialPortEnum);\n\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/AbsStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport java.io.InputStream;\n\n/**\n * Accept the message, the helper of the sticky packet processing,\n * return the final data through the inputstream,\n * manually process the sticky packet, and the returned byte[] is the complete data we expect\n * Note: This method will be called repeatedly until a complete piece of data is parsed.\n * This method is synchronous, try not to do time-consuming operations, otherwise it will block reading data\n */\npublic interface AbsStickPackageHelper {\n    byte[] execute(InputStream is);\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/BaseStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport android.os.SystemClock;\n\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * The simplest thing to do is not to deal with sticky packets,\n * read directly and return as much as InputStream.available() reads\n */\npublic class BaseStickPackageHelper implements AbsStickPackageHelper {\n    public BaseStickPackageHelper() {\n    }\n\n    @Override\n    public byte[] execute(InputStream is) {\n        try {\n            int available = is.available();\n            if (available > 0) {\n                byte[] buffer = new byte[available];\n                int size = is.read(buffer);\n                if (size > 0) {\n                    return buffer;\n                }\n                SerialPortLogUtil.d(\"BaseStickPackageHelper\", \"原始数据长度: \" + buffer.length);\n            } else {\n                SystemClock.sleep(50); // 默认50ms间隔\n            }\n\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/CompositeStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 组合式黏包处理器\n * 首先尝试使用主要处理器，如果失败则使用备用处理器\n * Author: cl\n * Date: 2023/10/26\n */\npublic class CompositeStickPackageHelper implements AbsStickPackageHelper {\n    \n    private final AbsStickPackageHelper primaryHelper;\n    private final AbsStickPackageHelper fallbackHelper;\n    private final List<Byte> buffer = new ArrayList<>();\n    \n    public CompositeStickPackageHelper(AbsStickPackageHelper primaryHelper, AbsStickPackageHelper fallbackHelper) {\n        this.primaryHelper = primaryHelper;\n        this.fallbackHelper = fallbackHelper;\n    }\n    \n    @Override\n    public byte[] execute(InputStream is) {\n        // 先尝试读取一些数据到缓冲区\n        try {\n            int available = is.available();\n            if (available > 0) {\n                byte[] tempBuffer = new byte[available];\n                int readBytes = is.read(tempBuffer);\n                if (readBytes > 0) {\n                    for (int i = 0; i < readBytes; i++) {\n                        buffer.add(tempBuffer[i]);\n                    }\n                }\n            }\n            \n            if (buffer.isEmpty()) {\n                return null;\n            }\n            \n            // 将缓冲区数据转换为字节数组\n            byte[] bufferData = new byte[buffer.size()];\n            for (int i = 0; i < buffer.size(); i++) {\n                bufferData[i] = buffer.get(i);\n            }\n            \n            // 尝试使用主要处理器\n            ByteArrayInputStream primaryStream = new ByteArrayInputStream(bufferData);\n            byte[] primaryResult = primaryHelper.execute(primaryStream);\n            \n            if (primaryResult != null && primaryResult.length > 0) {\n                // 主要处理器成功，清除已处理的数据\n                if (primaryResult.length <= buffer.size()) {\n                    for (int i = 0; i < primaryResult.length; i++) {\n                        buffer.remove(0);\n                    }\n                }\n                return primaryResult;\n            }\n            \n            // 主要处理器失败，尝试备用处理器\n            ByteArrayInputStream fallbackStream = new ByteArrayInputStream(bufferData);\n            byte[] fallbackResult = fallbackHelper.execute(fallbackStream);\n            \n            if (fallbackResult != null && fallbackResult.length > 0) {\n                // 备用处理器成功，清除已处理的数据\n                if (fallbackResult.length <= buffer.size()) {\n                    for (int i = 0; i < fallbackResult.length; i++) {\n                        buffer.remove(0);\n                    }\n                }\n                return fallbackResult;\n            }\n            \n            // 两个处理器都失败，保持缓冲区数据等待更多数据\n            return null;\n            \n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/SpecifiedStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * The sticky packet processing of specific characters,\n * one Byte[] at the beginning and the end, cannot be empty at the same time,\n * if one of them is empty, then the non-empty is used as the split marker\n * Example: The protocol is formulated as ^+data+$, starting with ^ and ending with $\n */\npublic class SpecifiedStickPackageHelper implements AbsStickPackageHelper {\n    private final byte[] head;\n    private final byte[] tail;\n    private final List<Byte> bytes;\n    private final int headLen;\n    private final int tailLen;\n\n    public SpecifiedStickPackageHelper(byte[] head, byte[] tail) {\n        this.head = head;\n        this.tail = tail;\n        if (head == null || tail == null) {\n            throw new IllegalStateException(\" head or tail ==null\");\n        }\n        if (head.length == 0 && tail.length == 0) {\n            throw new IllegalStateException(\" head and tail length==0\");\n        }\n        headLen = head.length;\n        tailLen = tail.length;\n        bytes = new ArrayList<>();\n    }\n    \n    /**\n     * 构造函数 - 只使用结束标识\n     * @param tail 结束标识\n     */\n    public SpecifiedStickPackageHelper(byte[] tail) {\n        this(new byte[0], tail);\n    }\n    \n    /**\n     * 构造函数 - 字符串版本\n     * @param head 开始标识字符串\n     * @param tail 结束标识字符串\n     */\n    public SpecifiedStickPackageHelper(String head, String tail) {\n        this(head != null ? head.getBytes() : new byte[0], \n             tail != null ? tail.getBytes() : new byte[0]);\n    }\n    \n    /**\n     * 构造函数 - 只使用结束标识字符串\n     * @param tail 结束标识字符串\n     */\n    public SpecifiedStickPackageHelper(String tail) {\n        this(new byte[0], tail != null ? tail.getBytes() : new byte[0]);\n    }\n\n    private boolean endWith(Byte[] src, byte[] target) {\n        if (src.length < target.length) {\n            return false;\n        }\n        for (int i = 0; i < target.length; i++) {\n            if (target[target.length - i - 1] != src[src.length - i - 1]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private byte[] getRangeBytes(List<Byte> list, int start, int end) {\n        Byte[] temps = Arrays.copyOfRange(list.toArray(new Byte[0]), start, end);\n        byte[] result = new byte[temps.length];\n        for (int i = 0; i < result.length; i++) {\n            result[i] = temps[i];\n        }\n        return result;\n    }\n\n    @Override\n    public byte[] execute(InputStream is) {\n        bytes.clear();\n        int len = -1;\n        byte temp;\n        int startIndex = -1;\n        byte[] result = null;\n        boolean isFindStart = false, isFindEnd = false;\n        try {\n            while ((len = is.read()) != -1) {\n                temp = (byte) len;\n                bytes.add(temp);\n                Byte[] byteArray = bytes.toArray(new Byte[]{});\n                if (headLen == 0 || tailLen == 0) {//Only head or tail markers\n                    if (endWith(byteArray, head) || endWith(byteArray, tail)) {\n                        if (startIndex == -1) {\n                            startIndex = bytes.size() - headLen;\n                        } else {\n                            result = getRangeBytes(bytes, startIndex, bytes.size());\n                            break;\n                        }\n                    }\n                } else {\n                    if (!isFindStart) {\n                        if (endWith(byteArray, head)) {\n                            startIndex = bytes.size() - headLen;\n                            isFindStart = true;\n                        }\n                    } else if (!isFindEnd) {\n                        if (endWith(byteArray, tail)) {\n                            if (startIndex + headLen <= bytes.size() - tailLen) {\n                                isFindEnd = true;\n                                result = getRangeBytes(bytes, startIndex, bytes.size());\n                                break;\n                            }\n                        }\n                    }\n\n                }\n            }\n            if (len == -1) {\n                return null;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/StaticLenStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Fixed-length adhesive package treatment\n * Example: The protocol stipulates that the length of each packet is 16\n */\npublic class StaticLenStickPackageHelper implements AbsStickPackageHelper {\n    private int stackLen = 16;\n\n    public StaticLenStickPackageHelper(int stackLen) {\n        this.stackLen = stackLen;\n    }\n    \n    /**\n     * 默认构造函数，使用16字节固定长度\n     */\n    public StaticLenStickPackageHelper() {\n        this.stackLen = 16;\n    }\n\n    @Override\n    public byte[] execute(InputStream is) {\n        int count = 0;\n        int len = -1;\n        byte temp;\n        byte[] result = new byte[stackLen];\n        try {\n            while (count < stackLen && (len = is.read()) != -1) {\n                temp = (byte) len;\n                result[count] = temp;\n                count++;\n            }\n            if (len == -1) {\n                return null;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/StickyPacketHelperFactory.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport java.nio.charset.StandardCharsets;\n\n/**\n * 黏包处理器工厂类\n * 提供常用的黏包处理器配置\n * Author: cl\n * Date: 2023/10/26\n */\npublic class StickyPacketHelperFactory {\n    \n    /**\n     * 创建无黏包处理的处理器\n     */\n    public static AbsStickPackageHelper createNoProcessing() {\n        return new BaseStickPackageHelper();\n    }\n    \n    /**\n     * 创建固定长度的黏包处理器\n     * @param length 数据包固定长度\n     */\n    public static AbsStickPackageHelper createFixedLength(int length) {\n        return new StaticLenStickPackageHelper(length);\n    }\n    \n    /**\n     * 创建基于分隔符的黏包处理器\n     * @param delimiter 分隔符字符串\n     */\n    public static AbsStickPackageHelper createDelimiterBased(String delimiter) {\n        byte[] delimiterBytes = delimiter.getBytes(StandardCharsets.UTF_8);\n        return new SpecifiedStickPackageHelper(new byte[0], delimiterBytes);\n    }\n    \n    /**\n     * 创建基于开始和结束标识的黏包处理器\n     * @param startMarker 开始标识\n     * @param endMarker 结束标识\n     */\n    public static AbsStickPackageHelper createMarkerBased(String startMarker, String endMarker) {\n        byte[] startBytes = startMarker.getBytes(StandardCharsets.UTF_8);\n        byte[] endBytes = endMarker.getBytes(StandardCharsets.UTF_8);\n        return new SpecifiedStickPackageHelper(startBytes, endBytes);\n    }\n    \n    /**\n     * 创建基于开始和结束标识的黏包处理器 (字节版本)\n     * @param startMarker 开始标识字节数组\n     * @param endMarker 结束标识字节数组\n     */\n    public static AbsStickPackageHelper createMarkerBased(byte[] startMarker, byte[] endMarker) {\n        return new SpecifiedStickPackageHelper(startMarker, endMarker);\n    }\n    \n    /**\n     * 创建变长黏包处理器\n     * @param byteOrder 字节序\n     * @param lenSize 长度字段大小\n     * @param lenIndex 长度字段位置\n     * @param offset 偏移量\n     */\n    public static AbsStickPackageHelper createVariableLength(java.nio.ByteOrder byteOrder, int lenSize, int lenIndex, int offset) {\n        return new VariableLenStickPackageHelper(byteOrder, lenSize, lenIndex, offset);\n    }\n    \n    /**\n     * 创建默认变长黏包处理器\n     * 默认配置：大端字节序，2字节长度字段，位置在索引2，偏移12字节\n     */\n    public static AbsStickPackageHelper createVariableLength() {\n        return new VariableLenStickPackageHelper(java.nio.ByteOrder.BIG_ENDIAN, 2, 2, 12);\n    }\n    \n    /**\n     * 创建自定义超时的基础处理器\n     * @param timeout 超时时间（毫秒）\n     */\n    public static AbsStickPackageHelper createTimeoutBased(int timeout) {\n        return new TimeoutStickPackageHelper(timeout);\n    }\n    \n    /**\n     * 创建组合式黏包处理器\n     * @param primaryHelper 主要处理器\n     * @param fallbackHelper 备用处理器\n     */\n    public static AbsStickPackageHelper createComposite(AbsStickPackageHelper primaryHelper, \n                                                       AbsStickPackageHelper fallbackHelper) {\n        return new CompositeStickPackageHelper(primaryHelper, fallbackHelper);\n    }\n    \n    /**\n     * 常用协议的快速创建方法\n     */\n    public static class Common {\n        \n        /**\n         * AT指令协议（以\\r\\n结尾）\n         */\n        public static AbsStickPackageHelper createATCommand() {\n            return createDelimiterBased(\"\\r\\n\");\n        }\n        \n        /**\n         * JSON协议（以换行符结尾）\n         */\n        public static AbsStickPackageHelper createJsonLine() {\n            return createDelimiterBased(\"\\n\");\n        }\n        \n        /**\n         * Modbus RTU协议（固定长度，通常8字节）\n         */\n        public static AbsStickPackageHelper createModbusRTU() {\n            return createFixedLength(8);\n        }\n        \n        /**\n         * 自定义协议（STX/ETX包围）\n         */\n        public static AbsStickPackageHelper createSTXETX() {\n            return createMarkerBased(new byte[]{0x02}, new byte[]{0x03}); // STX, ETX\n        }\n        \n        /**\n         * 自定义协议（SOH/EOT包围）\n         */\n        public static AbsStickPackageHelper createSOHEOT() {\n            return createMarkerBased(new byte[]{0x01}, new byte[]{0x04}); // SOH, EOT\n        }\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/TimeoutStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\nimport android.os.SystemClock;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 基于超时的黏包处理器\n * 在指定时间内没有新数据到达时，认为是一个完整的数据包\n * Author: cl\n * Date: 2023/10/26\n */\npublic class TimeoutStickPackageHelper implements AbsStickPackageHelper {\n    \n    private final int timeout; // 超时时间（毫秒）\n    private final List<Byte> buffer = new ArrayList<>();\n    \n    public TimeoutStickPackageHelper(int timeout) {\n        this.timeout = timeout;\n    }\n    \n    @Override\n    public byte[] execute(InputStream is) {\n        buffer.clear();\n        long lastDataTime = System.currentTimeMillis();\n        \n        try {\n            while (true) {\n                int available = is.available();\n                if (available > 0) {\n                    // 有数据可读\n                    byte[] tempBuffer = new byte[available];\n                    int readBytes = is.read(tempBuffer);\n                    if (readBytes > 0) {\n                        for (int i = 0; i < readBytes; i++) {\n                            buffer.add(tempBuffer[i]);\n                        }\n                        lastDataTime = System.currentTimeMillis();\n                    }\n                } else {\n                    // 没有数据，检查超时\n                    if (!buffer.isEmpty() && (System.currentTimeMillis() - lastDataTime) >= timeout) {\n                        // 超时且缓冲区有数据，返回数据包\n                        byte[] result = new byte[buffer.size()];\n                        for (int i = 0; i < buffer.size(); i++) {\n                            result[i] = buffer.get(i);\n                        }\n                        return result;\n                    }\n                    \n                    // 短暂休眠，避免CPU过度占用\n                    SystemClock.sleep(10);\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/stick/VariableLenStickPackageHelper.java",
    "content": "package com.cl.serialportlibrary.stick;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteOrder;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Variable-length sticky packet processing, used in the protocol with a length field\n * Example: The protocol is: type+dataLen+data+md5\n * type: Named type, two bytes\n * dataLen: The length of the data field, two bytes\n * data: Data field, variable length, length dataLen\n * md5: md5 field, 8 bytes\n * Use: 1.byteOrder: first determine the big and small ends, ByteOrder.BIG_ENDIAN or ByteOrder.LITTLE_ENDIAN;\n * 2.lenSize: The length of the len field, 2 in this example\n * 3.lenIndex: The position of the len field, 2 in this example, because the len field is preceded by type, and its length is 2\n * 4.offset: the length of the entire package -len, this example is the length of the three fields of type+dataLen+md5, that is, 2+2+8=12\n */\npublic class VariableLenStickPackageHelper implements AbsStickPackageHelper {\n    private int offset = 0;\n    private int lenIndex = 0;\n    private int lenSize = 2;\n    private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;\n    private final List<Byte> mBytes;\n    private final int lenStartIndex;\n    private final int lenEndIndex;\n\n    public VariableLenStickPackageHelper(ByteOrder byteOrder, int lenSize, int lenIndex, int offset) {\n        this.byteOrder = byteOrder;\n        this.lenSize = lenSize;\n        this.offset = offset;\n        this.lenIndex = lenIndex;\n        mBytes = new ArrayList<>();\n        lenStartIndex = lenIndex;\n        lenEndIndex = lenIndex + lenSize - 1;\n        if (lenStartIndex > lenEndIndex) {\n            throw new IllegalStateException(\"lenStartIndex>lenEndIndex\");\n        }\n    }\n\n    private int getLen(byte[] src, ByteOrder order) {\n        int re = 0;\n        if (order == ByteOrder.BIG_ENDIAN) {\n            for (byte b : src) {\n                re = (re << 8) | (b & 0xff);\n            }\n        } else {\n            for (int i = src.length - 1; i >= 0; i--) {\n                re = (re << 8) | (src[i] & 0xff);\n            }\n        }\n        return re;\n    }\n\n    @Override\n    public byte[] execute(InputStream is) {\n        mBytes.clear();\n        int count = 0;\n        int len = -1;\n        byte temp;\n        byte[] result;\n        int msgLen = -1;\n        byte[] lenField = new byte[lenSize];\n        try {\n            while ((len = is.read()) != -1) {\n                temp = (byte) len;\n                if (count >= lenStartIndex && count <= lenEndIndex) {\n                    lenField[count - lenStartIndex] = temp;\n                    if (count == lenEndIndex) {\n                        msgLen = getLen(lenField, byteOrder);\n                    }\n                }\n                count++;\n                mBytes.add(temp);\n                if (msgLen != -1) {\n                    if (count == msgLen + offset) {\n                        break;\n                    } else if (count > msgLen + offset) {\n                        len = -1;\n                        break;\n                    }\n                }\n            }\n            if (len == -1) {\n                return null;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n            return null;\n        }\n        result = new byte[mBytes.size()];\n        for (int i = 0; i < result.length; i++) {\n            result[i] = mBytes.get(i);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/thread/SerialPortReadThread.java",
    "content": "package com.cl.serialportlibrary.thread;\n\nimport com.cl.serialportlibrary.utils.SerialPortLogUtil;\nimport com.cl.serialportlibrary.enumerate.SerialPortEnum;\nimport com.cl.serialportlibrary.stick.AbsStickPackageHelper;\nimport com.cl.serialportlibrary.stick.BaseStickPackageHelper;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\n\n/**\n * 串口消息读取线程\n */\npublic abstract class SerialPortReadThread extends Thread {\n\n    public abstract void onDataReceived(byte[] bytes);\n\n    private InputStream mInputStream;\n    private SerialPortEnum mSerialPortEnum;\n    private List<AbsStickPackageHelper> mStickPackageHelpers;\n\n    public SerialPortReadThread(InputStream inputStream, SerialPortEnum mSerialPortEnum, List<AbsStickPackageHelper> stickPackageHelpers) {\n        mInputStream = inputStream;\n        this.mSerialPortEnum = mSerialPortEnum;\n        this.mStickPackageHelpers = stickPackageHelpers;\n        \n        // 如果没有提供粘包处理器，使用默认的\n        if (this.mStickPackageHelpers == null || this.mStickPackageHelpers.isEmpty()) {\n            this.mStickPackageHelpers = new java.util.ArrayList<>();\n            this.mStickPackageHelpers.add(new BaseStickPackageHelper());\n        }\n    }\n\n    @Override\n    public void run() {\n        if (mInputStream == null) return;\n        while (!Thread.currentThread().isInterrupted()) {\n            try {\n                if (mStickPackageHelpers.size() > mSerialPortEnum.ordinal()) {\n                    AbsStickPackageHelper helper = mStickPackageHelpers.get(mSerialPortEnum.ordinal());\n                    byte[] buffer = helper.execute(mInputStream);\n                    if (buffer != null && buffer.length > 0) {\n                        SerialPortLogUtil.d(\"SerialPortReadThread\", \"接收数据，长度: \" + buffer.length);\n                        onDataReceived(buffer);\n                    }\n                } else {\n                    // 使用第一个处理器作为默认\n                    if (!mStickPackageHelpers.isEmpty()) {\n                        AbsStickPackageHelper helper = mStickPackageHelpers.get(0);\n                        byte[] buffer = helper.execute(mInputStream);\n                        if (buffer != null && buffer.length > 0) {\n                            SerialPortLogUtil.d(\"SerialPortReadThread\", \"接收数据(默认处理器)，长度: \" + buffer.length);\n                            onDataReceived(buffer);\n                        }\n                    } else {\n                        SerialPortLogUtil.e(\"SerialPortReadThread\", \"没有可用的粘包处理器\");\n                        break;\n                    }\n                }\n            } catch (Exception e) {\n                SerialPortLogUtil.e(\"SerialPortReadThread\", \"读取数据异常: \" + e.getMessage());\n                e.printStackTrace();\n                break;\n            }\n        }\n    }\n\n    /**\n     * 关闭线程，释放资源\n     */\n    public void release() {\n        interrupt();\n        if (mInputStream != null) {\n            try {\n                mInputStream.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n            mInputStream = null;\n        }\n    }\n}"
  },
  {
    "path": "serial_lib/src/main/java/com/cl/serialportlibrary/utils/SerialPortLogUtil.java",
    "content": "package com.cl.serialportlibrary.utils;\n\nimport android.util.Log;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Locale;\n\n/**\n * 串口日志工具类\n * 替代XLog的增强实现，包含更详细的调试信息\n * 提供时间戳、调用位置、数据格式化等功能\n */\npublic class SerialPortLogUtil {\n    \n    private static final String DEFAULT_TAG = \"SerialPort\";\n    private static boolean isDebugEnabled = true;\n    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(\"HH:mm:ss.SSS\", Locale.getDefault());\n    \n    /**\n     * 设置是否启用调试日志\n     * @param enabled 是否启用\n     */\n    public static void setDebugEnabled(boolean enabled) {\n        isDebugEnabled = enabled;\n        if (enabled) {\n            Log.i(DEFAULT_TAG, \"================== 串口日志系统已启用 ==================\");\n        }\n    }\n    \n    /**\n     * 获取是否启用调试日志\n     */\n    public static boolean isDebugEnabled() {\n        return isDebugEnabled;\n    }\n    \n    /**\n     * 获取当前时间戳\n     */\n    private static String getTimeStamp() {\n        return DATE_FORMAT.format(new Date());\n    }\n    \n    /**\n     * 获取调用者信息\n     */\n    private static String getCallerInfo() {\n        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();\n        // 跳过前面的调用栈，找到真正的调用者\n        for (int i = 4; i < stackTrace.length; i++) {\n            StackTraceElement element = stackTrace[i];\n            String className = element.getClassName();\n            if (!className.equals(SerialPortLogUtil.class.getName())) {\n                String simpleClassName = className.substring(className.lastIndexOf('.') + 1);\n                return String.format(\"[%s.%s:%d]\", simpleClassName, element.getMethodName(), element.getLineNumber());\n            }\n        }\n        return \"[Unknown]\";\n    }\n    \n    /**\n     * 格式化消息\n     */\n    private static String formatMessage(String message) {\n        return String.format(\"%s %s %s\", getTimeStamp(), getCallerInfo(), message);\n    }\n    \n    /**\n     * 输出调试日志\n     * @param tag 标签\n     * @param message 消息\n     */\n    public static void d(String tag, String message) {\n        if (isDebugEnabled) {\n            Log.d(tag != null ? tag : DEFAULT_TAG, formatMessage(message));\n        }\n    }\n    \n    /**\n     * 输出调试日志（使用默认标签）\n     * @param message 消息\n     */\n    public static void d(String message) {\n        d(DEFAULT_TAG, message);\n    }\n    \n    /**\n     * 输出信息日志\n     * @param tag 标签\n     * @param message 消息\n     */\n    public static void i(String tag, String message) {\n        if (isDebugEnabled) {\n            Log.i(tag != null ? tag : DEFAULT_TAG, formatMessage(message));\n        }\n    }\n    \n    /**\n     * 输出信息日志（使用默认标签）\n     * @param message 消息\n     */\n    public static void i(String message) {\n        i(DEFAULT_TAG, message);\n    }\n    \n    /**\n     * 输出警告日志\n     * @param tag 标签\n     * @param message 消息\n     */\n    public static void w(String tag, String message) {\n        if (isDebugEnabled) {\n            Log.w(tag != null ? tag : DEFAULT_TAG, formatMessage(message));\n        }\n    }\n    \n    /**\n     * 输出警告日志（使用默认标签）\n     * @param message 消息\n     */\n    public static void w(String message) {\n        w(DEFAULT_TAG, message);\n    }\n    \n    /**\n     * 输出错误日志\n     * @param tag 标签\n     * @param message 消息\n     */\n    public static void e(String tag, String message) {\n        // 错误日志始终输出，不受isDebugEnabled控制\n        Log.e(tag != null ? tag : DEFAULT_TAG, formatMessage(message));\n    }\n    \n    /**\n     * 输出错误日志（使用默认标签）\n     * @param message 消息\n     */\n    public static void e(String message) {\n        e(DEFAULT_TAG, message);\n    }\n    \n    /**\n     * 输出错误日志（带异常）\n     * @param tag 标签\n     * @param message 消息\n     * @param throwable 异常\n     */\n    public static void e(String tag, String message, Throwable throwable) {\n        // 错误日志始终输出，不受isDebugEnabled控制\n        Log.e(tag != null ? tag : DEFAULT_TAG, formatMessage(message), throwable);\n    }\n    \n    /**\n     * 输出错误日志（带异常，使用默认标签）\n     * @param message 消息\n     * @param throwable 异常\n     */\n    public static void e(String message, Throwable throwable) {\n        e(DEFAULT_TAG, message, throwable);\n    }\n    \n    /**\n     * 打印数据（专门用于串口数据调试）\n     * @param tag 标签\n     * @param prefix 前缀\n     * @param data 数据\n     */\n    public static void printData(String tag, String prefix, byte[] data) {\n        if (!isDebugEnabled || data == null) return;\n        \n        StringBuilder sb = new StringBuilder();\n        sb.append(prefix).append(\" [\").append(data.length).append(\" bytes]: \");\n        \n        // 十六进制格式\n        sb.append(\"HEX[\");\n        for (int i = 0; i < Math.min(data.length, 32); i++) { // 最多显示32字节\n            if (i > 0) sb.append(\" \");\n            sb.append(String.format(\"%02X\", data[i] & 0xFF));\n        }\n        if (data.length > 32) {\n            sb.append(\"...\");\n        }\n        sb.append(\"] \");\n        \n        // ASCII格式（可打印字符）\n        sb.append(\"ASCII[\");\n        for (int i = 0; i < Math.min(data.length, 32); i++) {\n            byte b = data[i];\n            if (b >= 32 && b < 127) {\n                sb.append((char) b);\n            } else {\n                sb.append('.');\n            }\n        }\n        if (data.length > 32) {\n            sb.append(\"...\");\n        }\n        sb.append(\"]\");\n        \n        d(tag, sb.toString());\n    }\n    \n    /**\n     * 打印数据（使用默认标签）\n     * @param prefix 前缀\n     * @param data 数据\n     */\n    public static void printData(String prefix, byte[] data) {\n        printData(DEFAULT_TAG, prefix, data);\n    }\n    \n    /**\n     * 打印串口状态信息\n     * @param tag 标签\n     * @param devicePath 设备路径\n     * @param baudRate 波特率\n     * @param isOpen 是否打开\n     */\n    public static void printSerialStatus(String tag, String devicePath, int baudRate, boolean isOpen) {\n        i(tag, String.format(\"串口状态 - 设备: %s, 波特率: %d, 状态: %s\", \n            devicePath, baudRate, isOpen ? \"已打开\" : \"已关闭\"));\n    }\n    \n    /**\n     * 打印串口配置信息\n     * @param tag 标签\n     * @param databits 数据位\n     * @param parity 校验位\n     * @param stopbits 停止位\n     * @param flags 标志位\n     */\n    public static void printSerialConfig(String tag, int databits, int parity, int stopbits, int flags) {\n        String parityStr;\n        switch (parity) {\n            case 0: parityStr = \"无校验\"; break;\n            case 1: parityStr = \"奇校验\"; break;\n            case 2: parityStr = \"偶校验\"; break;\n            default: parityStr = \"未知(\" + parity + \")\"; break;\n        }\n        \n        i(tag, String.format(\"串口配置 - 数据位: %d, 校验位: %s, 停止位: %d, 标志位: 0x%X\", \n            databits, parityStr, stopbits, flags));\n    }\n    \n    /**\n     * 打印性能信息\n     * @param tag 标签\n     * @param operation 操作名称\n     * @param startTime 开始时间\n     */\n    public static void printPerformance(String tag, String operation, long startTime) {\n        long duration = System.currentTimeMillis() - startTime;\n        d(tag, String.format(\"性能统计 - %s 耗时: %dms\", operation, duration));\n    }\n    \n    /**\n     * 打印分隔线\n     * @param tag 标签\n     * @param title 标题\n     */\n    public static void printSeparator(String tag, String title) {\n        if (isDebugEnabled) {\n            String separator = \"==================== \" + title + \" ====================\";\n            i(tag, separator);\n        }\n    }\n    \n    /**\n     * 打印分隔线（使用默认标签）\n     * @param title 标题\n     */\n    public static void printSeparator(String title) {\n        printSeparator(DEFAULT_TAG, title);\n    }\n}"
  },
  {
    "path": "serial_lib/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">SerialPortLibrary</string>\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n        maven { url 'https://jitpack.io' }\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven { url 'https://jitpack.io' }\n    }\n}\n\nrootProject.name = \"serialPort\"\ninclude ':serial_lib'\ninclude ':app'"
  }
]