ModbusTCP Slave for Android
最近项目中 Socket 通讯使用到 ModbusTCP协议,记录下从调试到使用过程。
客户提出Modbus协议,一开始也是有点懵逼的,找遍各种资料,Github开源项目,很少有关于Android端作为从机的文章跟项目参考,时间不等人,只能自己下载源码分析到底怎么回事。最终决定使用JLibModbus开源库
Modbus 一般分主机poll(Client)跟从机slave(Server),网上很多都是以Android端作为主机端的案例,本文主要介绍以Android端作为从机slave。
主机端大家可以参考:https://www.cnblogs.com/ioufev/p/10831289.html
使用工具+资源jar
ModbusPoll:主机Poll端
ModbusSlave:从机Slave端
具体使用后面介绍,下载安装就不在这里多说,参考:https://www.cnblogs.com/hieroly/p/9063710.html
jlibmodbus-1.2.9.7.jar
Android 端从机Slave创建
导入jar包后,下面是我自己创建从机的部分代码,应该是可以直接使用的
public class ModBusManager {
private static final String TAG = "ModBusManager";
private static ModBusManager modbusManager = null;
private static ExecutorService singleThreadPool;
private Context context;
private ModbusSlave modbusSlave;
private TcpParameters tcpParameters;
private MyDataHolder dataHolder;
public static ModBusManager getInstance(Context context) {
if (null == modbusManager) {
synchronized (ModBusManager.class) {
if (null == modbusManager) {
modbusManager = new ModBusManager(context);
}
}
}
return modbusManager;
}
private ModBusManager(Context context) {
this.context = context;
singleThreadPool = Executors.newSingleThreadExecutor();
}
/**
* 启动 从机 salve (server)
*/
public void startSalve() {
singleThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Log.d(TAG, "run: startSalve");
//填写自己的端口号
tcpParameters = getParameters(Constant.SOCKET_PORT);
dataHolder = new MyDataHolder(context);
// 创建一个从机
modbusSlave = new ModbusSlaveTCP(tcpParameters);
// 设置控制台输出主机和从机命令交互日志
Modbus.setLogLevel(Modbus.LogLevel.LEVEL_DEBUG);
modbusSlave.setDataHolder(dataHolder);
modbusSlave.setReadTimeout(10000);
//填写自己的 Slave ID
modbusSlave.setServerAddress(Constant.SERVER_ID);
modbusSlave.listen();
} catch (ModbusIOException e) {
e.printStackTrace();
}
}
});
}
/**
* 配置 参数
*
* @param port
* @return
*/
private TcpParameters getParameters(int port) {
TcpParameters parameters = new TcpParameters();
try {
//此处设置Android 端 IP地址
InetAddress address = InetAddress.getByName("192.168.30.211");
parameters.setHost(address);
// 设置从机TCP的是否长连接
parameters.setKeepAlive(true);
// 设置从机TCP的端口
parameters.setPort(port);
Log.d(TAG, "getParameters: address = " + address + " port = " + port);
} catch (UnknownHostException e) {
e.printStackTrace();
}
return parameters;
}
public MyDataHolder getDataHolder() {
return dataHolder;
}
public void release() {
try {
if (null != modbusSlave) {
modbusSlave.shutdown();
modbusSlave = null;
}
if (null != dataHolder) {
dataHolder.release();
dataHolder = null;
}
if (null != tcpParameters) {
tcpParameters = null;
}
} catch (ModbusIOException e) {
e.printStackTrace();
}
}
}
主从机如何交互,前期为了弄明白这些,包括工具的使用,花了很大的功夫,大概讲述下
个人理解:主从机交互,从机(Salve)也就Socket的Server端,会创建一个连续地址存储空间,主机(Poll)Client端会对这个连续的地址写入值或者从这里面读取值。也就是主从机都可以读写操作这个连续地址存储获取或者修改数据。此处欢迎大佬详细给我介绍
数据读写
public class MyDataHolder extends DataHolder {
private static final String TAG = "MyDataHolder";
private Context context;
MyDataHolder(Context context) {
this.context = context;
//很关键
setHoldingRegisters(new ModbusHoldingRegisters(Modbus.MAX_START_ADDRESS));
}
//06
@Override
public void writeHoldingRegister(int offset, int value) throws IllegalDataAddressException, IllegalDataValueException {
Log.d(TAG, "writeHoldingRegister:写单个寄存器 offset = " + offset + " value = " + value);
//转2进制
Log.d(TAG, "writeHoldingRegister: value_2 = " + Integer.toBinaryString(value));
//转16进制
Log.d(TAG, "writeHoldingRegister: value_16 = " + Integer.toHexString(value));
super.writeHoldingRegister(offset, value);
}
//10
@Override
public void writeHoldingRegisterRange(int offset, int[] range) throws IllegalDataAddressException, IllegalDataValueException {
Log.d(TAG, "writeHoldingRegisterRange:写多个寄存器 offset = " + offset);
super.writeHoldingRegisterRange(offset, range);
}
//03 读寄存器
@Override
public int[] readHoldingRegisterRange(int offset, int quantity) throws IllegalDataAddressException {
Log.d(TAG, "readHoldingRegisterRange: 读取信息 offset = " + offset);
return super.readHoldingRegisterRange(offset, quantity);
}
}
数据读写关键在 DataHolder ,大家可以自己看源码,对应很多功能码,代码只展示三种,主要我目前只用到这三种,就不额外举例。
注意:ModbusPoll 填写的 地址 和功能码都是 16进制 但是jlibModbus 读取出来会转换成10进制 所以 10对应的十进制16
Modbus Poll 使用
打开后,点击Setup - read/Write Definition...(设置功能码) - 出来如上图所示画面
File - new 可以创建多个窗口,停留在哪个窗口点击Setup就是针对的具体设置,可同时开启多个
Slave ID :
对应代码设置 modbusSlave.setServerAddress(0xD4)
两边设置的值要一致(Modbus Poll 是16进制的值,代码设置的是10进制的值)举例:代码设置0xD4 那么在工具中填应该是212
Function:
功能编码,对应上图 01 - 15 介绍
Address :
连续存储的地址
对应代码 MyDataHolder 中三个方法的 offset 参数(也是10 进制 对应 16进制)
Quantity:
地址数量,图示 03,读取 从地址0开始的10个寄存器地址的值
06 写单个寄存器,值只能为1,写入一个值
16 写多个寄存器,给连续的地址写入多个值,起始地址是Address
举例写多个寄存器:
对ID 为 212 的从机写入地址从50开始连续10个寄存器(如上图所示)点击OK后,可以对数据的进制进行设置。
DisPlay中可以对填写数据的进制进行设置 hex - 16进制 Binary - 2进制 (不管工具端设置什么进制的数,Android 端读取都会转换成10进制,所以获取的数据,处理要进行对应的进制转换)
//转换部分 源码
public void setValue(int value) throws ModbusNumberException {
if (!Modbus.checkRegisterValue(value)) {
throw new ModbusNumberException("Register value out of range", value);
}
this.value = ((short) value) & 0xffff;//转10进制
}
使用:开启+关闭
ModBusManager.getInstance(this).startSalve();
ModBusManager.getInstance(this).release();
开启从机监听,启动ModbusPoll 点击Connection填写对应的IP地址+port端口号,点击OK,就会连续发送数据到Android
注意:连接不上 注意排查 1,IP地址 2,端口号 3,Salve ID 4,有可能电脑的防火墙也需要关闭下
主机端写入数据时,从 MyDataHolder 处拦截,获取数据。
Android端也可以调用MyDataHolder.write......写单个寄存器/多个寄存器
写入成功等待主机端读取,读取的数据就是写入的数据,完成主从机的交互。
(关键 记得MyDataHolder中 setHoldingRegisters(new ModbusHoldingRegisters(Modbus.MAX_START_ADDRESS));)
(写入寄存器的值 转换成10进制写入)
我反编译jar源码提取了部分使用的,以上两个类应该不影响直接使用jar包
未完待续....................