前言:公司属于北斗通信行业,平时开发时经常要接触到北斗协议。由于北斗通信相对于外界开发人员来说比较冷门,北斗三代协议又是去年才开发民用的,所以在早期开发时想要找到相关资料比较难,因此只好自己看协议慢慢摸索。现在经过了大量相关项目的洗礼之后对北三协议也略知一二就写了个解析工具类。
2024-3-25更新:新增北斗协议解析SDK
协议解析SDK说明:https://blog.csdn.net/lxt1292352578/article/details/137019548
协议解析SDK资源:https://download.csdn.net/download/lxt1292352578/89030621
2024年10月10日更新:之前发的博客也陆陆续续有网友提出问题,最近有网友提醒可以创建一个群聊方便大家简单地交流一下工作中遇见的问题和开发经验,群里主要涉及到的是Iot开发中的通信链路连接和北斗相关(目前只有我)如果你有这方面的问题或者感兴趣的话欢迎加入,初创的群再加上这方面的开发相对冷门所以人数不多,如果有相关问题的话可以在群里留言,大家在工作之余也会根据自身的经验尽力去解答,如果群二维码图片挂了或者过期可以私信一下我哈
一、废话不多说,直接贴代码,复制就能直接使用
package com.example.SecondProject.Utils;
import android.util.Log;
import java.util.Arrays;
// 协议解析/封装工具
public class ProtocolUtil {
public static String TAG = "ProtocolUtil";
private static String data = "";
// 北斗指令拼接:每次接收的都是碎片数据,需要自行拼接为完整的指令,例如:$CCIC A,1595 0044,0,1,33 3211*99
public static void parseData_fragment(String str){
// 拼接数据
data += str;
// 找到 $ 符,如果没有的话,这条数据就丢弃,重置数据,如果有的话就从 $ 开始截取到后面的所有字符
int startIndex = data.indexOf("$");
if (startIndex < 0){
data = "";
return;
}
if (startIndex > 0) {
data = data.substring(startIndex);
}
// 找到 * 符,如果没有代表接收的是中间的数据,退出接收下一串,如果有代表这是这条指令的最后一段 *+校验
int endIndex = data.indexOf("*");
if( endIndex < 1 ) return;
String intactData;
// 先判断一下长度,不然的话长度不足会断掉
if(data.length() < endIndex+3){
intactData = data;
}else {
intactData = data.substring(0,endIndex+3); // 截出 $---*66
data = data.substring(endIndex+3); // 多出来的是下一条指令的开头部分,保留到下一次用
}
// 这里偷懒了,没有对最后两位校验符做验证,有强迫症的话可以自行添加
String XOR_str = intactData.substring(intactData.length() - 2); // 异或校验符
parseData(intactData); // 解析协议
}
// 解析数据
public static void parseData(String intactData){
// 拿到 * 的位置
int xorIndex = intactData.indexOf("*");
if(xorIndex == -1){return;}
String data_str = intactData.substring(0, xorIndex); // 截取到 * 之前,例:$CCICR,0,00
if(!data_str.contains(",")){return;}
String[] values = data_str.split(",", -1); // , 分割,例:["$CCICR","0","00"]
// Log.e(TAG, "正在解析的数据:"+ Arrays.toString(values));
if (data_str.contains("FKI")) { // 反馈信息
BDFKI(values);
}else if (data_str.contains("ICP")) { // IC 信息
BDICP(values);
} else if (data_str.contains("PWI")) { // 波束信息
BDPWI(values);
} else if (data_str.contains("TCI")){ // 北斗三代通信信息
BDTCI(values);
}
// 其余类型的应该都是 RNSS 定位数据
else {
parseRNSS(values);
}
}
public static void BDICP(String[] value){
try {
String cardID = value[1];
String cardFrequency = value[14];
String cardLevel = value[15];
Log.e(TAG, "收到IC信息: 卡号-" + cardID + " 频度-" + cardFrequency + " 等级-" + cardLevel);
}catch (Exception e){
Log.e(TAG, "BDICP: 解析错误" + e.toString());
e.printStackTrace();
return;
}
}
public static void BDPWI(String[] values){
// 尽量用 try 避免线程中断
try {
int rdss2Count1 = Integer.parseInt(values[2]);
int index = 2 + (rdss2Count1*3) + 1;
int rdss3Count = Integer.parseInt(values[index]);
index++;
int s21[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
for (int i = 0 ;i < rdss3Count; i++) {
if(values.length < index+2){
return; // 越界检测
}
int id = Integer.parseInt(values[index]);
if (id > 21 || id <= 0) continue;
int number = Integer.parseInt(values[index+1]);
s21[id-1] = number;
index += 4;
}
Log.e(TAG, "收到信号数据:"+Arrays.toString(s21));
}catch (Exception e){
Log.e(TAG, "BDPWI: 解析错误" + e.toString());
e.printStackTrace();
return;
}
}
// 通信申请后的反馈信息
public static void BDFKI(String[] values){
try {
String type = values[2]; // 反馈的指令类型 :只用 TCQ
String result = values[3]; // 反馈结果 : Y / N
String reason = values[4]; // 失败原因
if(result.equals("Y")){
Log.e(TAG, "收到反馈信息: " + type + "指令发送成功");
} else {
switch ( reason ){
case "1":
Log.e(TAG, "收到反馈信息: " + type + "指令发送失败:频度未到,发射被抑制");
break;
case "2":
Log.e(TAG, "收到反馈信息: " + type + "指令发送失败:接收到系统的抑制指令,发射被抑制");
break;
case "3":
Log.e(TAG, "收到反馈信息: " + type + "指令发送失败:当前设置为无线电静默状态,发射被抑制");
break;
case "4":
Log.e(TAG, "收到反馈信息: " + type + "指令发送失败:功率未锁定");
break;
case "5":
Log.e(TAG, "收到反馈信息: " + type + "指令发送失败:未检测到IC模块信息");
break;
default:
Log.e(TAG, "收到反馈信息: " + type + "指令发送失败,原因码是" + reason);
break;
}
}
}catch (Exception e){
Log.e(TAG, "BDFKI: 解析错误" + e.toString());
e.printStackTrace();
return;
}
}
// 收到了 TCI 通信信息
public static void BDTCI(String[] values){
try {
String fromNumber = values[1]; // 发送发地址
String toNumber = values[2]; // 收信方地址
String messageType = values[5]; // 消息类型:汉字/代码/混合
String timeHour = values[4]; // 消息时间
String content = values[7]; // 消息内容
Log.e(TAG, "收到通信信息: 发送号码-" + fromNumber + " 接收号码-" + toNumber + " 消息类型-" + messageType + " 消息时间-" + timeHour + " 消息内容-" + content );
}catch (Exception e){
Log.e(TAG, "BDTCI: 解析错误" + e.toString());
e.printStackTrace();
return;
}
}
// 解析 RNSS 位置数据,只解析了 GGA 和 GLL ,其余的参考文档自行解析
public static void parseRNSS(String[] values){
try {
// 拆分数据
String head = values[0];
if (head.contains("GGA")){
if (values[6].equals("0")) return; // 0 就是无效定位,不要
String latitude = analysisLonlat(values[2]) + ""; // 纬度
String longitude = analysisLonlat(values[4]) + ""; // 经度
String altitude; // 海拔
if(values[9] != null || !values[9].equals("")){
altitude = values[9];
}else {
altitude = "0";
}
Log.e(TAG, "解析RNSS定位数据(GGA): 纬度-" + latitude + " 经度-" + longitude + " 高度-" + altitude );
}else if (head.contains("GLL")){
if (values[6].equals("V")) return; // V - 无效 A - 有效
String latitude = analysisLonlat(values[1]) + ""; // 纬度
String longitude = analysisLonlat(values[3]) + ""; // 经度
Log.e(TAG, "解析RNSS定位数据(GLL): 纬度-" + latitude + " 经度-" + longitude );
}
else {
return;
}
}catch (Exception e){
Log.e(TAG, "parseRNSS: 解析错误" + e.toString());
e.printStackTrace();
return;
}
}
public static double analysisLonlat(String value){
if(value.contains("N") || value.contains("E")){
return 0.0;
}
if (value.equals("")|| value == null) return 0.0;
double lonlat = Double.valueOf(value);
int dd = (int)lonlat / 100;
int mm = (int)lonlat % 100;
double ms = lonlat - (int)lonlat;
return dd+((mm+ms)/60.0);
}
// 封装 ---------------------------------------------------------------------------------------
// 查询 IC 信息:type - 0检测本机IC信息,1检测本机编组信息,2检测下属用户,3检测IC模块工作模式
public static String CCICR(int type, String info) {
String command = "CCICR," + type + "," + info;
return packaging(command);
}
// 打包,加上 $ 和 * 和 校验和,输出 hex_str
public static String packaging(String tmp){
String hexCommand = DataUtil.string2Hex(tmp);
String hh = getCheckCode0007(hexCommand).toUpperCase(); // 检验和
return "24"+hexCommand+"2A"+DataUtil.string2Hex(hh)+"0D0A";
}
// 计算异或校验和
public static String getCheckCode0007(String strProtocol) {
strProtocol.replace(" ", "");
byte chrCheckCode = 0;
for (int i = 0; i < strProtocol.length(); i += 2) {
char chrTmp ;
chrTmp = strProtocol.charAt(i);
if (chrTmp == ' ') continue;
byte chTmp1 = (byte) (DataUtil.char2HexByte(chrTmp) << 4);
chrTmp = strProtocol.charAt(i + 1);
byte chTmp2 = (byte) (chTmp1 + (DataUtil.char2HexByte(chrTmp) & 15));
chrCheckCode = i == 0 ? chTmp2 : (byte) (chrCheckCode ^ chTmp2);
}
String strHexCheckCode = String.format("%x", Byte.valueOf(chrCheckCode));
if ((strHexCheckCode = strHexCheckCode.toUpperCase()).length() != 2) {
if (strHexCheckCode.length() > 2) {
strHexCheckCode = strHexCheckCode.substring(strHexCheckCode.length() - 2);
} else if (strHexCheckCode.length() < 2 && strHexCheckCode.length() > 0) {
strHexCheckCode = "0" + strHexCheckCode;
}
}
return strHexCheckCode;
}
}
二、简单说明一下
1. 解析
其实协议本身也比较简单,只是开发过程中需要用到各种不同的通信链路例如:蓝牙、串口等,连接北斗设备导致这整个流程显得比较繁琐
上面的解析方法有两个:
一个是:public static void parseData_fragment(String str)
这是拼接碎块化数据用的,适用于蓝牙或串口连接时捕获到的数据比较碎所以需要自行拼接。如果你收到的数据是完整的一条指令就可以直接用另一个方法:public static void parseData(String intactData)
2. 北斗协议
北斗协议的标准格式是:以 “$” 符号开头以 “*” 和 “校验和” “回车换行(/r/n)” 结尾,有了这些特征以后就很好解析了
其实开发中实际需要用到的指令无非就那几条:$BDICP 查询设备信息、$BDPWI 查询信息状况、$BDFKI 查询指令下发状况,这些在代码里面都写好了,在对应的位置增加自己的操作即可,如果需要解析其他指令的话再根据北斗协议自行添加,另附一份精简版北三协议: