
简介本资源是一套基于Android平台实现Modbus TCP协议与PLC设备通信的完整工程源码面向工业自动化场景下的移动终端如PDA开发需求特别适合Android新手及具备一定Java基础的工控应用开发者。项目真实源自企业级设备对接实践已亲测可用涵盖16位寄存器读写、浮点数解析与写入等关键功能并集成seroUtils.jar与modbus4j.jar核心依赖。压缩包共1076个文件含20个Java源文件、116个JSON配置、51个XML布局与权限声明、26个JAR库、131个DEX字节码及1个可直接安装的debug APK整体大小为12.01MB结构清晰、模块分离明确。目前已有934人学习下载资源中提炼了实际开发中踩坑总结的连接稳定性处理、异常重试机制与数据类型转换逻辑可作为Modbus Android端开发的可靠起点与二次开发基础。 做Android开发的老哥们有没有遇到过这种需求现场设备是PLC上位机要用安卓平板控制还得读写数据最近我正好捣鼓了一个项目用Modbus4j这个库在Android端直接读写PLC寄存器把运行频率、压力、温度这些参数实时同步到平板上。折腾了几天踩了不少坑今天干脆把这些经验整理出来希望能帮到正要入坑的朋友。这篇内容主要讲清楚三件事一是为什么选Modbus4j而不是其他方案二是怎么在Android Studio里配置和封装一套可复用的Modbus通信工具三是我实际测试中遇到的典型问题和解决办法。适合有Android基础、需要对接PLC设备的开发者参考如果你对工业通信不熟也没关系我会从协议层面简单说明。1. 项目整体设计与技术选型思路1.1 核心需求拆解Android设备如何与PLC通信先说场景。我这边需要让一台安卓平板通过有线或WiFi连接车间里的PLC实时读取设备状态和工艺参数同时还要能下发控制指令比如修改变频器目标频率、启停电机。PLC品牌不一有三菱的、西门子的还有国产汇川的但好在它们基本都支持Modbus协议这就给统一接入提供了可能。Android本身没有直接操作PLC的API所以我们得通过某种工业协议。常见的有Modbus、OPC UA、S7协议等其中Modbus凭借简单、开放、硬件支持广泛成了现场设备通信的事实标准。我们的需求就是找一个成熟稳定的Modbus客户端库集成到Android里封装出好用的读写接口。1.2 为什么选择Modbus4j而不是自己写Socket刚开始我也犹豫过是不是直接用Java Socket自己拼Modbus TCP报文但仔细一想Modbus协议虽然简单但里面有功能码、寄存器地址、字节序、CRC校验RTU模式这些细节处理起来还是挺繁琐的。自己写容易埋坑比如漏掉异常码处理、字节序搞反现场调试时哭都来不及。Modbus4j是一个开源的Java Modbus库支持TCP和RTU两种模式封装了完整的协议栈包括读线圈、读保持寄存器、写单个/多个寄存器等操作。它内部已经有超时重试、异常码解析等机制我们只需要调用几个方法就能完成通信省心得多。而且它是纯Java实现Android可以直接使用不需要JNI部署也很简单。对比过其他库比如Jamod、ModbusIO有的年久失修有的依赖太老Modbus4j的API设计更清晰维护也算活跃所以最终选了它。2. 环境准备与Android工程配置2.1 Android Studio与依赖引入我用的是Android Studio Hedgehog版本Gradle插件8.2应用minSdkVersion 24targetSdkVersion 34。Modbus4j在Maven Central上可以直接用Gradle引入。不过有一点要注意它的老版本依赖了commons-logging可能会导致抱冲突建议用较新版本。在build.gradle模块级里添加dependencies { implementation com.digitalpetri.modbus:modbus:1.1.0 }注意Modbus4j的Maven坐标曾经从com.infiniteautomation迁移到了com.digitalpetri如果你搜索到老的教程用的可能是com.infiniteautomation:modbus4j。我测试下来com.digitalpetri:modbus:1.1.0这个版本稳定API也简洁推荐使用。添加依赖后需要同步一下工程如果下载慢可以更换阿里云镜像。2.2 Android权限配置与网络限制要让Android访问网络必须在AndroidManifest.xml里声明网络权限uses-permission android:nameandroid.permission.INTERNET /如果你的PLC位于局域网内而且Android 9API 28及以上版本默认禁止明文HTTP流量Modbus TCP不是HTTP但同样会走TCP Socket所以需要允许明文网络流量。最简单的方式是在application标签里设置android:usesCleartextTraffictrue更安全的做法是配置网络安全策略但开发调试阶段直接用这个省事。另外如果PLC和设备不在同一个网段还需要设置路由这个要到现场网络里处理代码里做不了什么。3. 核心代码实现封装Modbus通信工具类3.1 建立Modbus TCP连接Modbus4j的API和老的版本不太一样新库主要分ModbusTcpMaster和ModbusRtuMaster。这里以TCP为例。先看一下连接的基本代码import com.digitalpetri.modbus.tcp.ModbusTcpMaster; import com.digitalpetri.modbus.tcp.ModbusTcpMasterConfig; import java.time.Duration; public class ModbusManager { private ModbusTcpMaster master; public boolean connect(String ip, int port, int timeoutSeconds) { try { ModbusTcpMasterConfig config new ModbusTcpMasterConfig.Builder(ip) .setPort(port) .setTimeout(Duration.ofSeconds(timeoutSeconds)) .build(); master new ModbusTcpMaster(config); master.connect(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } }这里有一个关键点ModbusTcpMaster.connect()是同步阻塞的如果PLC地址不通它会一直阻塞到超时。所以建议不要在主线程调用放到子线程或者Coroutine里否则App会ANR。后面我会专门说异步处理。端口默认是502有些PLC可能改成了自定义端口。另外如果PLC支持Modbus TCP over TCP/IPIP就是PLC的以太网口地址。如果你用的是串口转WiFi模块IP就是模块的地址。3.2 读取PLC寄存器的详细实现Modbus协议里读取保持寄存器Holding Register最常见功能码是0x03用来读16位的数据。Modbus4j里提供了readHoldingRegisters方法需要传入从站ID、起始地址和数量。import com.digitalpetri.modbus.requests.ReadHoldingRegistersRequest; import com.digitalpetri.modbus.responses.ReadHoldingRegistersResponse; import com.digitalpetri.modbus.codec.Modbus; public int[] readHoldingRegisters(int unitId, int startAddress, int quantity) { try { ReadHoldingRegistersRequest request new ReadHoldingRegistersRequest(unitId, startAddress, quantity); ReadHoldingRegistersResponse response master.send(request); if (response ! null) { // 返回的是一个字节数组两个字节对应一个寄存器 byte[] data response.getRegisters(); int[] values new int[quantity]; for (int i 0; i quantity; i) { int index i * 2; values[i] ((data[index] 0xFF) 8) | (data[index 1] 0xFF); } return values; } } catch (Exception e) { e.printStackTrace(); } return null; }注意这里我手动处理了字节序Modbus默认是高字节在前Big-Endian。如果你的PLC是低字节在前比如某些国产设备就需要交换高低字节。后面我会写一个工具方法统一处理。另外一个很容易忽略的点起始地址到底从0还是1开始Modbus的地址线是基于0的也就是说PLC侧通常标注的“保持寄存器40001”对应Modbus地址040001-400010如果从40001开始则是40001-400010。但不同PLC的Modbus映射表不一样比如三菱FX系列用D寄存器映射到Modbus地址可能直接就是D编号。所以你在写程序前一定让电气工程师提供一份Modbus地址映射表否则读出来的数据乱七八糟。3.3 写入PLC寄存器的实现与校验写单个保持寄存器的功能码是0x06写多个是0x10。Modbus4j里可以这样写import com.digitalpetri.modbus.requests.WriteSingleRegisterRequest; public boolean writeSingleRegister(int unitId, int address, int value) { try { WriteSingleRegisterRequest request new WriteSingleRegisterRequest(unitId, address, value); master.send(request); return true; } catch (Exception e) { e.printStackTrace(); return false; } }写入多个寄存器可以用WriteMultipleRegistersRequest参数需要字节数组。这个方法用于一次更新多个参数比如同时修改PID的P和I参数。具体实现大同小异。这里要注意写操作前最好先读取当前值确认设备状态避免误操作。尤其在实际生产环境中直接写PLC地址是有风险的需要确认寄存器是否可写有些寄存器是只读的。另外如果你要写32位浮点数比如变频器频率单个寄存器是16位频率精度要求高时就需要拆成两个寄存器然后按IEEE 754浮点数格式组合。Modbus4j没有直接提供浮点写入的API需要自己转换public int[] floatToRegisters(float value) { int intBits Float.floatToIntBits(value); int high (intBits 16) 0xFFFF; int low intBits 0xFFFF; return new int[]{high, low}; }读取时反过来把两个寄存器拼成float。4. 实战对接三菱与西门子PLC的细节4.1 地址映射与数据类型转换的坑以三菱FX系列为例它内部的D寄存器编号是十进制的D0、D1等而Modbus地址通常也是0、1、2这样看起来可以直接对应。但三菱有一些型号Modbus地址会偏移比如D0对应Modbus地址0D200对应Modbus地址200基本是一致的但最好在手册里确认。西门子S7-200/300的Modbus映射则不同通常有固定映射区比如V区地址偏移量需要加上一个基础值。比如要读VB100可能Modbus地址是1000x1000即4096如果没有偏移你可能读错位置。我遇到过这种情况调试了一下午最后发现是地址偏移没加。数据转换方面PLC里的数据有的是无符号整数有的是有符号整数有的是浮点数。Modbus通信只认16位数据转换逻辑必须由上位机处理。我封装了一个工具类public static int byteToInt(byte high, byte low) { return ((high 0xFF) 8) | (low 0xFF); } public static short byteToShort(byte high, byte low) { return (short) (((high 0xFF) 8) | (low 0xFF)); } public static float registersToFloat(int[] registers) { int bits ((registers[0] 0xFFFF) 16) | (registers[1] 0xFFFF); return Float.intBitsToFloat(bits); }4.2 与变频器通信的频率读写示例一个比较典型的应用是读写变频器频率。比如用Modbus控制变频器运行目标频率往往以0.01Hz为单位存成一个16位整数。假设变频器地址是1目标频率寄存器地址是0x2000启动命令寄存器是0x2001。读取当前频率int[] data modbusManager.readHoldingRegisters(1, 0x2000, 1); if (data ! null) { int rawValue data[0]; float frequencyHz rawValue / 100.0f; // 假设精度是0.01Hz }写入目标频率int rawValueToWrite (int)(50.0f * 100); // 50.00Hz modbusManager.writeSingleRegister(1, 0x2000, rawValueToWrite);注意要确认PLC端的寄存器定义是否支持写操作。有些变频器把运行频率设为只读需要通过控制字设置那就要改控制字并触发命令这个就要参照具体变频器的Modbus通信手册了。这个例子可以让读者看到数据转换的实际应用但注意不要提供具体品牌的错误参数要强调以手册为准。5. 常见问题与排查技巧实录5.1 连接超时与重连机制最常见的问题就是连接超时。PLC或者下位机偶尔会断线尤其是WiFi环境信号不稳就断了。Modbus4j内部的超时时间默认比较短需要根据现场调整。我建议把超时设为5秒并且加一个重连机制。我的做法是维护一个连接状态变量在读取异常时自动调用connect方法重连并且采用指数退避策略防止频繁重连private int retryCount 0; public boolean ensureConnected() { if (master ! null master.isConnected()) return true; while (retryCount 3) { if (connect(ip, port, timeout)) { retryCount 0; return true; } try { Thread.sleep(2000L * (retryCount 1)); } catch (InterruptedException e) { e.printStackTrace(); } retryCount; } return false; }5.2 数据不一致与字节序问题这个问题我在现场踩过最深的坑。有时候读数明明不对但协议没错地址也没错最后发现是字节序搞反了。Modbus标准是Big-Endian高字节在前但有些设备却是Little-Endian。比如同样的寄存器值读出来可能是0x1234但PLC里存的却是0x3412。这种情况只能根据设备的Modbus映射手册去判断或者用一个已知值测出来。另外读取多个寄存器时寄存器之间的顺序也可能需要调整。比如32位浮点数A寄存器是高16位还是低16位不同品牌定义可能不同。最好先用PLC软件写一个已知浮点数再读出来分析字节顺序。5.3 主线程卡顿与异步处理Android里直接在主线程调用master.send()会卡死因为网络操作是阻塞的。所以必须封装异步调用。我在项目里用线程池Handler或者用RxJava/协程。最简单的做法是开一个子线程通过回调接口返回数据public interface ModbusCallback { void onSuccess(Object data); void onError(Exception e); } public void readHoldingRegistersAsync(int unitId, int startAddress, int quantity, ModbusCallback callback) { new Thread(() - { try { int[] data readHoldingRegisters(unitId, startAddress, quantity); callback.onSuccess(data); } catch (Exception e) { callback.onError(e); } }).start(); }这样UI不会卡但要注意线程安全问题不要同时发送多个Modbus请求因为底层TCP连接是串行的。如果多个界面同时读取最好加一个队列或锁。5.4 异常码与响应错误排查Modbus返回异常码也能提供线索。比如返回错误码2Illegal Data Address说明你访问的寄存器地址不存在错误码3Illegal Data Value说明写入的值超出范围错误码1Illegal Function则说明功能码不受支持。Modbus4j收到异常会抛出异常我们在catch里把异常信息打印出来就能快速定位。我整理了一个简单的错误对照表异常码含义常见原因0x01Illegal Function功能码不支持PLC型号固件太老0x02Illegal Data Address寄存器地址越界或不存在0x03Illegal Data Value写入的值非法比如超出寄存器范围0x04Server Device FailurePLC内部故障需要检查PLC程序建议把异常码解析封装到工具类里方便现场排查。6. 经验总结与扩展建议到最后我想说几个实际体会。Modbus4j这个库确实让Android读写PLC变得简单但真正难的是理解设备和协议的细节。不同PLC的地址映射不同字节序不同寄存器定义不同这些都必须在开发前跟电气工程师确认清楚。不要相信网上的通用教程每个项目都要拿实际设备先测试。还有一个小技巧开发时可以先在电脑上用Modbus Poll模拟器调试验证库和地址映射是否正确再搬到Android上能省不少时间。Modbus4j也支持模拟主站可以本地起一个从站做测试。不过这个工具要单独安装这里就不展开了。如果后续项目需要支持Modbus RTU串口也可以在此基础上扩展Modbus4j同样有RTU Master实现但需要额外处理串口权限和串口波特率。这块我还没深入研究有兴趣的朋友可以一起交流。以上就是我这个项目的核心经验希望对你有帮助。如果你正在做类似的Android与PLC通信项目欢迎在评论区聊聊你踩过的坑。本文还有配套的精品资源点击获取