Android 使用modbus协议与可能遇到的问题解决一览
目录
- 前言
- 一、导入模块
- 二、协议相关
- 1. CRC16
- 2. ByteUtil
- 3. ModbusError
- 4. ModbusErrorType
- 5. ModbusFunction
- 6. ModbusRtuMaster
- 7. ByteArrayWriter
- 8. ModbusRtuSerialPortUtil
- 9. ModbusRtuMasterHelp
- 三、使用
- 总结
前言
本篇文章主要演示android的串口通讯功能,其中需要使用serialport模块(下载链接),注意: 串口通讯需要root权限,需要将应用设置成‘android:sharedUserId=“android.uid.system”’即可,如果出现串口通讯无法访问设备,首先看串口名称与波特率是否一致,如果都一致看看是否是打开串口就失败了,如果出现无权限的情况,可能是Android开发板不支持与该设备通讯,可以考虑让嵌入式工程师使用单片机提供通讯访问能力,本篇文章只是演示android使用modbus协议,具体协议理论可以参考其它文章,这里不在讲解。
一、导入模块
implementation project(path: ':serialport')
二、协议相关
1. CRC16
package com.jujiang.fyc.myttftwo.utils.modbus;import java.util.Arrays;public class CRC16 {private static final byte[] crc16_tab_h = { (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80,(byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,(byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00,(byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,(byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,(byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01,(byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80,(byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1,(byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,(byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,(byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0,(byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,(byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81,(byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0,(byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00,(byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80,(byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,(byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01,(byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80,(byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1,(byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00,(byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81,(byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,(byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01,(byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81,(byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1,(byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01,(byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40 };private static final byte[] crc16_tab_l = { (byte) 0x00, (byte) 0xC0, (byte) 0xC1, (byte) 0x01,(byte) 0xC3, (byte) 0x03, (byte) 0x02, (byte) 0xC2, (byte) 0xC6, (byte) 0x06, (byte) 0x07,(byte) 0xC7, (byte) 0x05, (byte) 0xC5, (byte) 0xC4, (byte) 0x04, (byte) 0xCC, (byte) 0x0C,(byte) 0x0D, (byte) 0xCD, (byte) 0x0F, (byte) 0xCF, (byte) 0xCE, (byte) 0x0E, (byte) 0x0A,(byte) 0xCA, (byte) 0xCB, (byte) 0x0B, (byte) 0xC9, (byte) 0x09, (byte) 0x08, (byte) 0xC8,(byte) 0xD8, (byte) 0x18, (byte) 0x19, (byte) 0xD9, (byte) 0x1B, (byte) 0xDB, (byte) 0xDA,(byte) 0x1A, (byte) 0x1E, (byte) 0xDE, (byte) 0xDF, (byte) 0x1F, (byte) 0xDD, (byte) 0x1D,(byte) 0x1C, (byte) 0xDC, (byte) 0x14, (byte) 0xD4, (byte) 0xD5, (byte) 0x15, (byte) 0xD7,(byte) 0x17, (byte) 0x16, (byte) 0xD6, (byte) 0xD2, (byte) 0x12, (byte) 0x13, (byte) 0xD3,(byte) 0x11, (byte) 0xD1, (byte) 0xD0, (byte) 0x10, (byte) 0xF0, (byte) 0x30, (byte) 0x31,(byte) 0xF1, (byte) 0x33, (byte) 0xF3, (byte) 0xF2, (byte) 0x32, (byte) 0x36, (byte) 0xF6,(byte) 0xF7, (byte) 0x37, (byte) 0xF5, (byte) 0x35, (byte) 0x34, (byte) 0xF4, (byte) 0x3C,(byte) 0xFC, (byte) 0xFD, (byte) 0x3D, (byte) 0xFF, (byte) 0x3F, (byte) 0x3E, (byte) 0xFE,(byte) 0xFA, (byte) 0x3A, (byte) 0x3B, (byte) 0xFB, (byte) 0x39, (byte) 0xF9, (byte) 0xF8,(byte) 0x38, (byte) 0x28, (byte) 0xE8, (byte) 0xE9, (byte) 0x29, (byte) 0xEB, (byte) 0x2B,(byte) 0x2A, (byte) 0xEA, (byte) 0xEE, (byte) 0x2E, (byte) 0x2F, (byte) 0xEF, (byte) 0x2D,(byte) 0xED, (byte) 0xEC, (byte) 0x2C, (byte) 0xE4, (byte) 0x24, (byte) 0x25, (byte) 0xE5,(byte) 0x27, (byte) 0xE7, (byte) 0xE6, (byte) 0x26, (byte) 0x22, (byte) 0xE2, (byte) 0xE3,(byte) 0x23, (byte) 0xE1, (byte) 0x21, (byte) 0x20, (byte) 0xE0, (byte) 0xA0, (byte) 0x60,(byte) 0x61, (byte) 0xA1, (byte) 0x63, (byte) 0xA3, (byte) 0xA2, (byte) 0x62, (byte) 0x66,(byte) 0xA6, (byte) 0xA7, (byte) 0x67, (byte) 0xA5, (byte) 0x65, (byte) 0x64, (byte) 0xA4,(byte) 0x6C, (byte) 0xAC, (byte) 0xAD, (byte) 0x6D, (byte) 0xAF, (byte) 0x6F, (byte) 0x6E,(byte) 0xAE, (byte) 0xAA, (byte) 0x6A, (byte) 0x6B, (byte) 0xAB, (byte) 0x69, (byte) 0xA9,(byte) 0xA8, (byte) 0x68, (byte) 0x78, (byte) 0xB8, (byte) 0xB9, (byte) 0x79, (byte) 0xBB,(byte) 0x7B, (byte) 0x7A, (byte) 0xBA, (byte) 0xBE, (byte) 0x7E, (byte) 0x7F, (byte) 0xBF,(byte) 0x7D, (byte) 0xBD, (byte) 0xBC, (byte) 0x7C, (byte) 0xB4, (byte) 0x74, (byte) 0x75,(byte) 0xB5, (byte) 0x77, (byte) 0xB7, (byte) 0xB6, (byte) 0x76, (byte) 0x72, (byte) 0xB2,(byte) 0xB3, (byte) 0x73, (byte) 0xB1, (byte) 0x71, (byte) 0x70, (byte) 0xB0, (byte) 0x50,(byte) 0x90, (byte) 0x91, (byte) 0x51, (byte) 0x93, (byte) 0x53, (byte) 0x52, (byte) 0x92,(byte) 0x96, (byte) 0x56, (byte) 0x57, (byte) 0x97, (byte) 0x55, (byte) 0x95, (byte) 0x94,(byte) 0x54, (byte) 0x9C, (byte) 0x5C, (byte) 0x5D, (byte) 0x9D, (byte) 0x5F, (byte) 0x9F,(byte) 0x9E, (byte) 0x5E, (byte) 0x5A, (byte) 0x9A, (byte) 0x9B, (byte) 0x5B, (byte) 0x99,(byte) 0x59, (byte) 0x58, (byte) 0x98, (byte) 0x88, (byte) 0x48, (byte) 0x49, (byte) 0x89,(byte) 0x4B, (byte) 0x8B, (byte) 0x8A, (byte) 0x4A, (byte) 0x4E, (byte) 0x8E, (byte) 0x8F,(byte) 0x4F, (byte) 0x8D, (byte) 0x4D, (byte) 0x4C, (byte) 0x8C, (byte) 0x44, (byte) 0x84,(byte) 0x85, (byte) 0x45, (byte) 0x87, (byte) 0x47, (byte) 0x46, (byte) 0x86, (byte) 0x82,(byte) 0x42, (byte) 0x43, (byte) 0x83, (byte) 0x41, (byte) 0x81, (byte) 0x80, (byte) 0x40 };/*** 计算CRC16校验** @param data* 需要计算的数组* @return CRC16校验值*/public static int compute(byte[] data) {return compute(data, 0, data.length);}/*** 校验byte数据是否是CRC16数据* @return 是否成功*/public static boolean checkCRC16(byte[] data) {int size = data.length;if (size <= 2) {return false;}byte[] oldCheckArray = new byte[]{ data[size - 2], data[size - 1]};// 将数据拆分成三分byte[] bytes = new byte[size - 2];System.arraycopy(data, 0, bytes, 0, size - 2);// 计算CRC校验码int crc = compute(bytes);ByteArrayWriter request = new ByteArrayWriter();request.writeInt16Reversal(crc);byte[] checkArray = request.toByteArray();return Arrays.equals(oldCheckArray, checkArray);}/*** 计算CRC16校验** @param data* 需要计算的数组* @param offset* 起始位置* @param len* 长度* @return CRC16校验值*/public static int compute(byte[] data, int offset, int len) {return compute(data, offset, len, 0xffff);}/*** 计算CRC16校验** @param data* 需要计算的数组* @param offset* 起始位置* @param len* 长度* @param preval* 之前的校验值* @return CRC16校验值*/public static int compute(byte[] data, int offset, int len, int preval) {int ucCRCHi = (preval & 0xff00) >> 8;int ucCRCLo = preval & 0x00ff;int iIndex;for (int i = 0; i < len; ++i) {iIndex = (ucCRCLo ^ data[offset + i]) & 0x00ff;ucCRCLo = ucCRCHi ^ crc16_tab_h[iIndex];ucCRCHi = crc16_tab_l[iIndex];}int result=((ucCRCHi & 0x00ff) << 8) | (ucCRCLo & 0x00ff) & 0xffff;return result;}
}
2. ByteUtil
package com.jujiang.fyc.myttftwo.utils.modbus;public class ByteUtil {public static String toHexString(byte[] input, String separator) {if (input==null) return null;StringBuilder sb = new StringBuilder();for (int i = 0; i < input.length; i++) {if (separator != null && sb.length() > 0) {sb.append(separator);}String str = Integer.toHexString(input[i] & 0xff);if (str.length() == 1) str = "0" + str;sb.append(str);}return sb.toString();}public static String toHexString(byte[] input) {return toHexString(input, " ");}public static byte[] fromInt32(int input){byte[] result=new byte[4];result[3]=(byte)(input >> 24 & 0xFF);result[2]=(byte)(input >> 16 & 0xFF);result[1]=(byte)(input >> 8 & 0xFF);result[0]=(byte)(input & 0xFF);return result;}public static byte[] fromInt16(int input){byte[] result=new byte[2];result[0]=(byte)(input >> 8 & 0xFF);result[1]=(byte)(input & 0xFF);return result;}public static byte[] fromInt16Reversal(int input){byte[] result=new byte[2];result[1]=(byte)(input>>8&0xFF);result[0]=(byte)(input&0xFF);return result;}
}
3. ModbusError
package com.jujiang.fyc.myttftwo.utils.modbus;import android.text.TextUtils;public class ModbusError extends Exception {private int code;public ModbusError(int code, String message) {super(!TextUtils.isEmpty(message) ? message : "Modbus Error: Exception code = " + code);this.code = code;}public ModbusError(int code) {this(code, null);}public ModbusError(ModbusErrorType type, String message) {super(type.name() + ": " + message);}public ModbusError(String message) {super(message);}public int getCode() {return this.code;}
}
4. ModbusErrorType
package com.jujiang.fyc.myttftwo.utils.modbus;/*** 常见的Modbus通讯错误*/
public enum ModbusErrorType {ModbusError,ModbusFunctionNotSupportedError,ModbusDuplicatedKeyError,ModbusMissingKeyError,ModbusInvalidBlockError,ModbusInvalidArgumentError,ModbusOverlapBlockError,ModbusOutOfBlockError,ModbusInvalidResponseError,ModbusInvalidRequestError,ModbusTimeoutError
}
5. ModbusFunction
package com.jujiang.fyc.myttftwo.utils.modbus;/*** 功能码(十进制显示)*/
public class ModbusFunction {//读线圈寄存器public static final int READ_COILS = 1;//读离散输入寄存器public static final int READ_DISCRETE_INPUTS = 2;//读保持寄存器public static final int READ_HOLDING_REGISTERS = 3;//读输入寄存器public static final int READ_INPUT_REGISTERS = 4;//写单个线圈寄存器public static final int WRITE_SINGLE_COIL = 5;//写单个保持寄存器public static final int WRITE_SINGLE_REGISTER = 6;//写入多个线圈寄存器public static final int WRITE_COILS = 15;//写入多个保持寄存器public static final int WRITE_HOLDING_REGISTERS = 16;
}
6. ModbusRtuMaster
package com.jujiang.fyc.myttftwo.utils.modbus;// 提供协议部分功能
public class ModbusRtuMaster {/*** 组装Modbus RTU消息帧** @param slave 从站地址号* @param function_code 功能码* @param starting_address 读取寄存器起始地址 / 写入寄存器地址 / 写入寄存器起始地址* @param quantity_of_x 读取寄存器个数 / 写入寄存器个数* @param output_value 需要写入单个寄存器的数值* @param output_values 需要写入多个寄存器的数组* @return 将整个消息帧转成byte[]* @throws ModbusError Modbus错误*/synchronized byte[] execute(int slave, int function_code, int starting_address, int quantity_of_x,int output_value, int[] output_values) throws ModbusError {//检查参数是否符合协议规定if (slave < 0 || slave > 0xff) {throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid slave " + slave);}if (starting_address < 0 || starting_address > 0xffff) {throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid starting_address " + starting_address);}if (quantity_of_x < 1 || quantity_of_x > 0xff) {throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid quantity_of_x " + quantity_of_x);}// 构造requestByteArrayWriter request = new ByteArrayWriter();//写入从站地址号request.writeInt8(slave);//根据功能码组装数据区//如果为读取寄存器指令if (function_code == ModbusFunction.READ_COILS || function_code == ModbusFunction.READ_DISCRETE_INPUTS|| function_code == ModbusFunction.READ_INPUT_REGISTERS || function_code == ModbusFunction.READ_HOLDING_REGISTERS) {request.writeInt8(function_code);request.writeInt16(starting_address);request.writeInt16(quantity_of_x);} else if (function_code == ModbusFunction.WRITE_SINGLE_COIL || function_code == ModbusFunction.WRITE_SINGLE_REGISTER) {//写单个寄存器指令if (function_code == ModbusFunction.WRITE_SINGLE_COIL)if (output_value != 0) output_value = 0xff00;//如果为线圈寄存器(写1时为 FF 00,写0时为00 00)request.writeInt8(function_code);request.writeInt16(starting_address);request.writeInt16(output_value);} else if (function_code == ModbusFunction.WRITE_COILS) {//写多个线圈寄存器request.writeInt8(function_code);request.writeInt16(starting_address);request.writeInt16(quantity_of_x);//计算写入字节数int writeByteCount = (quantity_of_x / 8) + 1;/// 满足关系-> (w /8) + 1//写入数量 == 8 ,则写入字节数为1if (quantity_of_x % 8 == 0) {writeByteCount -= 1;}request.writeInt8(writeByteCount);int index = 0;//如果写入数据数量 > 8 ,则需要拆分开来int start = 0;//数组开始位置int end = 7;//数组结束位置int[] splitData = new int[8];//循环写入拆分数组,直到剩下最后一组 元素个数 <= 8 的数据while (writeByteCount > 1) {writeByteCount--;int sIndex = 0;for (index = start; index <= end; index++) {splitData[sIndex++] = output_values[index];}//数据反转 对于是否要反转要看你传过来的数据,如果高低位顺序正确则不用反转splitData = reverseArr(splitData);//写入拆分数组request.writeInt8(toDecimal(splitData));start = index;end += 8;}//写入最后剩下的数据int last = quantity_of_x - index;int[] tData = new int[last];System.arraycopy(output_values, index, tData, 0, last);//数据反转 对于是否要反转要看你传过来的数据,如果高低位顺序正确则不用反转tData = reverseArr(tData);request.writeInt8(toDecimal(tData));} else if (function_code == ModbusFunction.WRITE_HOLDING_REGISTERS) {//写多个保持寄存器request.writeInt8(function_code);request.writeInt16(starting_address);request.writeInt16(quantity_of_x);request.writeInt8(2 * quantity_of_x);//写入数据for (int v : output_values) {request.writeInt16(v);}} else {throw new ModbusError(ModbusErrorType.ModbusFunctionNotSupportedError, "Not support function " + function_code);}byte[] bytes = request.toByteArray();//计算CRC校验码int crc = CRC16.compute(bytes);request.writeInt16Reversal(crc);bytes = request.toByteArray();return bytes;}//将数组反转private int[] reverseArr(int[] arr) {int[] tem = new int[arr.length];for (int i = 0; i < arr.length; i++) {tem[i] = arr[arr.length - 1 - i];}return tem;}//将int[1,0,0,1,1,0]数组转换为十进制数据private int toDecimal(int[] data) {int result = 0;if (data != null) {StringBuilder sData = new StringBuilder();for (int d : data) {sData.append(d);}try {result = Integer.parseInt(sData.toString(), 2);} catch (NumberFormatException e) {result = -1;}}return result;}
}
7. ByteArrayWriter
package com.jujiang.fyc.myttftwo.utils.modbus;import java.io.ByteArrayOutputStream;public class ByteArrayWriter extends ByteArrayOutputStream {public ByteArrayWriter() {super();}public void writeInt8(byte b){this.write(b);}public void writeInt8(int b){this.write((byte)b);}public void writeInt16(int n) {byte[] bytes = ByteUtil.fromInt16(n);this.write(bytes, 0, bytes.length);}public void writeInt16Reversal(int n){byte[] bytes=ByteUtil.fromInt16Reversal(n);this.write(bytes,0,bytes.length);}public void writeInt32(int n) {byte[] bytes = ByteUtil.fromInt32(n);this.write(bytes, 0, bytes.length);}public void writeBytes(byte[] bs,int len){this.write(bs,0,len);}
}
8. ModbusRtuSerialPortUtil
package com.jujiang.fyc.myttftwo.utils.modbusimport android_serialport_api.SerialPort
import com.android.jws.JwsManager
import com.android.jws.JwsSerialPort
import com.jujiang.fyc.myttftwo.utils.LogUtils
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream/*** 帮助Modbus 发送或接受数据*/
class ModbusRtuSerialPortUtil {private var jwp: SerialPort? = nullprivate var inputStream: InputStream? = nullprivate var outputStream: OutputStream? = nullfun open(device: String, baud: Int, dataBits: Int, parity: Int, stopBit: Int) {try {// 普通串口通讯
// jwp = SerialPort(File(device), rate, 0)// 设置验证位等参数jwp = SerialPort(File(device), baud, parity, dataBits, stopBit)inputStream = jwp!!.inputStreamoutputStream = jwp!!.outputStream} catch (e: IOException) {e.printStackTrace()}}// 发送数据fun send(bytes: ByteArray, millisecond: Long, block: (ByteArray, ByteArray) -> Unit) {try {LogUtils.e("bytes = ${bytes.map { String.format("%02x", *arrayOf<Any>(it)) }}")outputStream?.apply {write(bytes)flush()}Thread.sleep(millisecond)// 写入数据val size = inputStream!!.available()val byteArray = ByteArray(size)inputStream!!.read(byteArray)block(bytes, byteArray)} catch (e: IOException) {e.printStackTrace()}}
}
9. ModbusRtuMasterHelp
package com.jujiang.fyc.myttftwo.utils.modbusimport kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume/*** 实际调用类*/
class ModbusRtuMasterHelp(private val serialHelper: ModbusRtuSerialPortUtil) {private val modbusRtu: ModbusRtuMaster = ModbusRtuMaster()/*** 读多个线圈寄存器* @param slave 从站地址* @param startAddress 起始地址* @param numberOfPoints 读取线圈寄存器个数* @throws ModbusError Modbus错误*/fun readCoils(slave: Int,startAddress: Int,numberOfPoints: Int,millisecond: Long = 50,block: (ByteArray, ByteArray) -> Unit) {val sendBytes = modbusRtu.execute(slave,ModbusFunction.READ_COILS,startAddress,numberOfPoints,0,null)this.serialHelper.send(sendBytes, millisecond, block)}//读单个线圈寄存器fun readCoils(slave: Int,startAddress: Int,millisecond: Long = 50,block: (ByteArray, ByteArray) -> Unit) {readCoils(slave, startAddress, 1, millisecond, block)}// 异步读取并返回结果suspend fun readCoilsAsync(slave: Int,startAddress: Int,millisecond: Long = 50): Pair<ByteArray, ByteArray> {return suspendCancellableCoroutine { continuation ->readCoils(slave, startAddress, millisecond) { old, new ->continuation.resume(Pair(old, new))}}}/*** 写单个线圈寄存器* @param slave 从站地址* @param address 写入寄存器地址* @param value 写入值(true/false)* @throws ModbusError Modbus错误*/fun writeSingleCoil(slave: Int, address: Int, value: Boolean, millisecond: Long = 50,block: (ByteArray, ByteArray) -> Unit) {val sendBytes = modbusRtu.execute(slave,ModbusFunction.WRITE_SINGLE_COIL,address,1,if (value) 1 else 0,null)this.serialHelper.send(sendBytes, millisecond, block)}// 异步写并返回结果suspend fun writeSingleCoilAsync(slave: Int, address: Int, value: Boolean, millisecond: Long = 50): Pair<ByteArray, ByteArray> {return suspendCancellableCoroutine { continuation ->writeSingleCoil(slave, address, value, millisecond) { old, new ->continuation.resume(Pair(old, new))}}}
}
三、使用
class MainActivity : BaseBindingActivity<ActivityMainBinding>({ActivityMainBinding.inflate(it)
}) {private val modbusRtuSerialPortUtil by lazy { ModbusRtuSerialPortUtil() }private val modbusRtuSerialPort by lazy { ModbusRtuMasterHelp(modbusRtuSerialPortUtil) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 连接串口, 一定要保证串口是能正常的读写, 否则会导致后续读写操作失效, 如果提示无权限, 只能让嵌入式工程师// 使用单片机来做通讯, Android以普通串口读取方式来使用, 如果能正常读取成功, 可以采用实例方法modbusRtuSerialPortUtil.open("/dev/ttyS3", 19200, 8, 2, 1)// 同步读取线圈, 参数1: 地址; 参数2: 线圈地址; 返回参数1: 表示发送时的byte数组; 参数2: 表示设备回发的数据数组modbusRtuSerialPort.readCoils(0x01, 0x0A) { oldAray, byteArray -> }// 异步读取在协程中操作即可lifecycleScope.launch(Dispatchers.IO) {// 在协程中可以读取的操作方式有很多种, 这里只演示一种, 如果只是需要读一个那可以不需要使用 async 包裹async {val (oldArray, byteArray) = modbusRtuSerialPort.readCoilsAsync(0x01, 0x0A)// CRC16.checkCRC16 用于校验返回数据的后两位是否符合协议验证, 如果不符合则需要联系设备商修改if (CRC16.checkCRC16(byteArray)) {}}.await()}// 同步写数据, 参数1: 地址; 参数2: 线圈地址; 参数3: 需要写入的内容,线圈操作类似于按钮,所以只需要true/false即可;// 返回参数1: 表示发送时的byte数组; 参数2: 表示设备回发的数据数组// 异步写入这里就不在演示了, 目前没涉及到需要异步写入的需求modbusRtuSerialPort.writeSingleCoil(0x01, 0x00, true) { oldAray, byteArray ->// 校验两次数据是否相等,不相等即表示操作失败if (oldAray.contentEquals(byteArray)) {}}}
}
总结
本篇文章主要记录Android使用modbus协议, 其中遇到了按照本篇文章中的方式去读取后会提示无权限的情况或写入数据无返回, 对该情况的推测是串口的打开是成功的但是没有写入数据到设备中, 因为如果串口没打开或打开失败会抛出异常, 通过协调之后得到的结论是设备并不支持Android主板, 解决方案是让嵌入式工程师使用单片机做一个中间层,单片机只负责与设备连接与数据写入操作, Android这边按照modbus协议发送数据给单片机, 单片机将数据发送给设备, 设备回发数据后单片机读取数据后写入数据, Android读取数据拿到实际结果, 该方案可以解决实际问题。
相关文章:
Android 使用modbus协议与可能遇到的问题解决一览
目录 前言一、导入模块二、协议相关1. CRC162. ByteUtil3. ModbusError4. ModbusErrorType5. ModbusFunction6. ModbusRtuMaster7. ByteArrayWriter8. ModbusRtuSerialPortUtil9. ModbusRtuMasterHelp 三、使用总结 前言 本篇文章主要演示android的串口通讯功能,其…...

Virtualbox虚拟机中Ubuntu忘记密码
1、首先重新启动Ubuntu系统,鼠标快速点一下Virtualbox虚拟机窗口获取焦点,然后按住shift键,以调出grub启动菜单。 2、根据提示按下键盘E键进入编辑模式,向下移动光标,将如下"ro quiet splash $vt_handoff"部…...
isPresent()
isPresent() 是 Optional 类的一个方法,用于检查 Optional 对象中是否存在非空值。 Optional 是 Java 8 引入的一个类,用于解决空指针异常的问题。它可以将一个可能为空的值封装成一个对象,并提供了一系列方法来进行安全的操作。 具体来说&…...
DC.js教程_编程入门自学教程_菜鸟教程-免费教程分享
教程简介 DC.js 是一个优秀的 JavaScript 库,用于在浏览器、移动设备中进行数据分析,最终有助于创建数据可视化;DC.js 是一个用于探索大型多维数据集的图表库,它依靠 D3.js 引擎以 CSS 友好的 SVG 格式呈现图表。它允许呈现复杂的…...

Qt应用开发(基础篇)——滑块类 Slider、ScrollBar、Dial
一、前言 滑块类QScrollBar、QSlider和QDial继承于QAbstractSlider,父类主要拥有最大值、最小值、步长、当前值、滑块坐标等信息,滑动的时候触发包含值数据变化、滑块按下、滑块释放等信号。键盘包括左/上和右/下箭头键通过定义的singleStep改变当前值&a…...
iOS的NSUserActivity
NSUserActivity 是 iOS 平台上的一个类,用于支持应用程序之间的交互和继续活动(Continuity)。它主要用于实现 Handoff 功能,使用户可以在不同的 Apple 设备上无缝地继续进行某个任务。NSUserActivity 还可以用于保存和传递应用程序…...

Android HTTP使用(详细版)
前言 在面试过程中,HTTP 被提问的概率还是比较高的。 小林我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是比较大的,通过问答 + 图解的形式由浅入深的方式帮助大家进一步的学习和理解 HTTP 协议。 HTTP 基本概念 Get 与 Post HTTP 特性…...

【雕爷学编程】MicroPython动手做(25)——语音合成与语音识别
知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…...

前端开发:基于cypress的自动化实践
如何在vue中使用cypress如何运行cypress如何编写测试用例如何解决测试数据的问题遇到的元素定位的问题如何看待cypresscypress是否为最佳工具测试怎么办? 如何在vue中使用cypress vue提供了vue-cli 可以快速的创建vue项目。 vue create hello-world在选择安装项里…...

C++类和对象(下部曲)
构造函数 1 构造函数体赋值 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值 虽然对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化 构造函数体中的语句只能将其称为赋初值,而…...

解决eclipse 打开报错 An error has occurred. See the log file null.
解决eclipse 打开报错an error has ocurred. See the log file null 出现原因:安装了高版本的jdk,更换 jdk 版本,版本太高了。 解决方案:更改环境变量 改成 jkd 1.8...
javascript学习
一、数据类型 所有的变量都以var定义 数值 js不区分小数和整数 文本图形音频视频数组 var id_arr [1,2,3,4,5]对象 // 定义对象 var person {name: zhangsan,age: 3,tags: [java,js,php]} // 取对象的值 var person_name person.name...

基于SSM实现个人随笔分享平台:创作心灵,分享自我
项目简介 本文将对项目的功能及部分细节的实现进行介绍。个人随笔分享平台基于 SpringBoot SpringMVC MyBatis 实现。实现了用户的注册与登录、随笔主页、文章查询、个人随笔展示、个人随笔查询、写随笔、草稿箱、随笔修改、随笔删除、访问量及阅读量统计等功能。该项目登录模…...

从零开始学Docker(二):启动第一个Docker容器
宿主机环境:RockyLinux 9 这个章节不小心搞成命令学习了,后面在整理成原理吧 Docker生命周期 拉取并启动Nginx容器 # 查找镜像 例如:nginx [root192 ~]# docker search nginx 我们可以看到,第一个时官方认证构建的nginx # 拉…...
unity 鼠标事件
Input.GetMouseButtonDown(0)点击屏幕Input.mousePosition鼠标的坐标Input.GetKeyDown(KeyCode.Space)点击空格 1.2D游戏中鼠标触发事件 using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEditor; using Un…...
【ChatGPT】相关解读
ChatGPT 背后的“功臣”——RLHF 技术详解 Meta 发布开源可商用模型 Llama 2,实际体验效果如何? Llama 2线上试用地址:replicate.com/a16z-infr…...

【数据中台】DataX源码进行二开插件
参考官方 使用的离线数据同步工具/平台,实现不同数据库等各种异构数据源之间高效的数据同步功能 工具部署 https://github.com/alibaba/DataX/blob/master/userGuid.md 拉取下来的代码,pom.xml里面注释 <!--<module>tsdbreader</module&g…...

【数据结构与算法】基数排序
基数排序 基数排序(Radix Sort)属于“分配式排序”,又称“桶子法”或 bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。基数排序法是属于稳定性…...
Java基础一(队列和堆栈)
//示例 //添加新的元素 stack.push(Element e)queue.add(Element e) //满报IllegalStateException异常 queue.offer(Element e) //满成功true,否则false //删除 stack.pop()queue.remove() //移除头部元素,空报异常 queue.poll() //移除头部元素&…...

使用ansible playbook编写lnmp架构
使用ansible playbook编写lnmp架构 - name: nginx playgather_facts: falsehosts: lnmpremote_user: roottasks: - name: stop firewalldservice: namefirewalld statestopped- name: syslinuxcommand: /usr/sbin/setenforce 0ignore_errors: true- name: nginx.repocopy: src/…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...