Android基于modbus TCP/IP的通讯

最近因为项目的需求,需要用到modbus TCP/IP协议,听到这个名字感觉很熟悉,毕竟我们常用的http协议也是基于TCP/IP封装的。带着这种既熟悉又陌生的感觉,开始在网上查找一些资料,发现网上关于这方面的资料不是很多,而且也不是特别全。所以趁现在有时间就整理一下项目中这个模块的指示,方便自己以后查看。

首先我们简单了解一下什么是modbus TCP/IP协议。modbus是由Modicon(现为施耐德电气公司的一个品牌)在1979年发明的,是全球第一个真正用于工业现场的总线协议。modbus协议是应用于电子控制器上的一种通用语言。通过这个协议,控制器相互之间,控制器经由网络(例如以太网)和其它设备之间可以通信。modbus本身就是一个通信协议,可以基于串口,也可以基于网口。基于串口的由RTU,基于网口的由TCP,默认端口号502。通常我们把服务器端作为主站,将带有modbus模块的设备作为从站处理。利用modbus从寄存器中读取和写数据。

功能码 名称 作用
01 读取线圈状态 取得一组逻辑线圈的当前状态(ON/OFF)
02 读取输入状态 取得一组开关输入的当前状态(ON/OFF)
03 读取保持寄存器 在一个或多个保持寄存器中取得当前的二进制值
04 读取输入寄存器 在一个或多个输入寄存器中取得当前的二进制值
05 强置单线圈 强置一个逻辑线圈的通断状态
06 预置单寄存器 把具体二进值装入一个保持寄存器
07 读取异常状态 取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定
08 回送诊断校验 把诊断校验报文送从机,以对通信处理进行评鉴
09 编程(只用于484) 使主机模拟编程器作用,修改PC从机逻辑
10 控询(只用于484) 可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送
11 读取事件计数 可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时
12 读取通信事件记录 可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误
13 编程(184/384 484 584) 可使主机模拟编程器功能修改PC从机逻辑
14 探询(184/384 484 584) 可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送
15 强置多线圈 强置一串连续逻辑线圈的通断
16 预置多寄存器 把具体的二进制值装入一串连续的保持寄存器
17 报告从机标识 可使主机判断编址从机的类型及该从机运行指示灯的状态
18 (884和MICRO 84) 可使主机模拟编程功能,修改PC状态逻辑
19 重置通信链路 发生非可修改错误后,是从机复位于已知状态,可重置顺序字节
20 读取通用参数(584L) 显示扩展存储器文件中的数据信息
21 写入通用参数(584L) 把通用参数写入扩展存储文件,或修改之
22~64 保留作扩展功能备用
65~72 保留以备用户功能所用 留作用户功能的扩展编码
73~119 非法功能
120~127 保留 留作内部作用
128~255 保留 用于异常应答

在Android的开发中用到的可能就是读取和写入功能。接下来我们就开始代码开发。

首先我们需要导入相关的jar包,主要有两个modbus4j.jar,serroUtils.jar。

导入了需要的jar包之后,我们就开始和modbus建立联系了。代码如下:

  1. import java.util.Date;

  2. import com.serotonin.modbus4j.ModbusFactory;

  3. import com.serotonin.modbus4j.ModbusMaster;

  4. import com.serotonin.modbus4j.exception.ModbusInitException;

  5. import com.serotonin.modbus4j.exception.ModbusTransportException;

  6. import com.serotonin.modbus4j.ip.IpParameters;

  7. import com.serotonin.modbus4j.msg.ModbusRequest;

  8. import com.serotonin.modbus4j.msg.ModbusResponse;

  9. import com.serotonin.modbus4j.msg.ReadHoldingRegistersRequest;

  10. import com.serotonin.modbus4j.msg.WriteRegistersRequest;

  11. import com.serotonin.modbus4j.msg.WriteRegistersResponse;

  12. import com.serotonin.util.queue.ByteQueue;

  13. public class ReadAWriteUtil {

  14. public static void modbusWTCP(String ip, int port, int slaveId, int start, short[] values) {

  15. ModbusFactory modbusFactory = new ModbusFactory();

  16. // 设备ModbusTCP的Ip与端口,如果不设定端口则默认为502

  17. IpParameters params = new IpParameters();

  18. params.setHost(ip);

  19. if (502 != port) {

  20. params.setPort(port);

  21. }// 设置端口,默认502

  22. ModbusMaster tcpMaster = null;

  23. // 参数1:IP和端口信息 参数2:保持连接激活

  24. tcpMaster = modbusFactory.createTcpMaster(params, true);

  25. try {

  26. tcpMaster.init();

  27. System.out.println("===============" + 1111111);

  28. } catch (ModbusInitException e) {

  29. // System.out.println("11111111111111=="+"此处出现问题了啊!");

  30. // 如果出现了通信异常信息,则保存到数据库中

  31. //CommunityExceptionRecord cer = new CommunityExceptionRecord();

  32. //cer.setDate(new Date());

  33. //cer.setIp(ip);

  34. // cer.setRemark(bgName+"出现连接异常");

  35. // batteryGroupRecordService.saveCommunityException(cer);

  36. }

  37. try {

  38. WriteRegistersRequest request = new WriteRegistersRequest(slaveId, start, values);

  39. WriteRegistersResponse response = (WriteRegistersResponse) tcpMaster.send(request);

  40. if (response.isException())

  41. System.out.println("Exception response: message=" + response.getExceptionMessage());

  42. else

  43. System.out.println("Success");

  44. } catch (ModbusTransportException e) {

  45. e.printStackTrace();

  46. }

  47. }

  48.  
  49. public static ByteQueue modbusTCP(String ip, int port, int start,int readLenth) {

  50. ModbusFactory modbusFactory = new ModbusFactory();

  51. // 设备ModbusTCP的Ip与端口,如果不设定端口则默认为502

  52. IpParameters params = new IpParameters();

  53. params.setHost(ip);

  54. if(502!=port){params.setPort(port);}//设置端口,默认502

  55. ModbusMaster tcpMaster = null;

  56. tcpMaster = modbusFactory.createTcpMaster(params, true);

  57. try {

  58. tcpMaster.init();

  59. System.out.println("==============="+1111111);

  60. } catch (ModbusInitException e) {

  61. return null;

  62. }

  63. ModbusRequest modbusRequest=null;

  64. try {

  65. modbusRequest = new ReadHoldingRegistersRequest(1, start, readLenth);//功能码03

  66. } catch (ModbusTransportException e) {

  67. e.printStackTrace();

  68. }

  69. ModbusResponse modbusResponse=null;

  70. try {

  71. modbusResponse = tcpMaster.send(modbusRequest);

  72. } catch (ModbusTransportException e) {

  73. e.printStackTrace();

  74. }

  75. ByteQueue byteQueue= new ByteQueue(12);

  76. modbusResponse.write(byteQueue);

  77. System.out.println("功能码:"+modbusRequest.getFunctionCode());

  78. System.out.println("从站地址:"+modbusRequest.getSlaveId());

  79. System.out.println("收到的响应信息大小:"+byteQueue.size());

  80. System.out.println("收到的响应信息值:"+byteQueue);

  81. return byteQueue;

  82. }

  83. }

通过运行以上代码,就可以看到我们已经和modbus建立起了相关的联系,并且可以写入和读取数据了。但着才仅仅只是个开始。对于习惯了返回数据为json字符串的我们,当看到控制台打印出的byte数组时一定是一脸的懵逼,what is it?所以接下来我们就讲一讲modbus tcp的数据含义。在modbus开发的时候,Android和plc之间会定义好一写地址,并标明地址的意义和相关数据的含义。主要分为两类地址,一种是写入类型地址,一种是读取类型地址。如下:

写入数据地址:

268 %MX 402.5  BOOL 1 bit APP_DISLIGHT_DI       表示的含义

402.5是地址,Bool是数据类型,1bit是数据所占的位数,%MX表示以字节为单位

在plc中,整形数据都是short类型,占2个字节。而我们在通讯过程中传入的起始地址参数是int类型的,占4个字节。所以我们在看到402.5(代表第402个字节的第5位)这个地址的时候需要做一个除以2的处理。至于原因,应该是其转换为2进制是一样的。所以我们传入的startadr应该是201+偏移量(这个值是plc设定好的已知量),至于第五位就是我们需要传入的value值,那我们需要传入多少呢,因为这个数据类型是bool,所以是由0或者1两种值,如果传入0的话就直接传0就可以了,如果传入1的话就需要传入2的5次方,转换为二进制之后你就能看到在第五位的值是1了。

 

再来看看读取地址:

11 %MX 287.2  BOOL 1 bit APP_CHECK_WATERIN_FINISHED 表示含义      

287.2是地址,Bool是数据类型,1bit是数据所占的位数,%MX表示以字节为单位

读取的起始地址同上需要做除以2的处理,第二个参数为读取的长度,是以short类型为单位的,就说如果读取长度为1,那就是读取一个short,2个字节的数据。在读取到数据之后,我们还需要把数据转换为二进制,然后根据相关位上的数据来判断具体的含义。这里还有一个问题就是转换出来的二进制和本身含义是相反的,就是说二进制的第一位应该对应从起始地址开始的最后一位的含义。

 

 

你可能感兴趣的:(工作总结)