最近接觸了一個項目,主要的使用場景是沒有互聯(lián)網(wǎng)的系奉,所以需要App與北斗衛(wèi)星進(jìn)行通信檬贰,包括獲取地理信息,上報信息喜最,解析后臺通過衛(wèi)星下發(fā)的信息偎蘸。北斗海聊官方只提供了PC版的測試軟件,我不知使用了什么方法去查看了他們的源碼瞬内,沒有發(fā)現(xiàn)對底層通信協(xié)議單獨做的封裝迷雪。網(wǎng)上能查到的都沒法用,所以虫蝶,只能自己從0開始了章咧。
硬件設(shè)備
如圖,就是這樣一個燈罩狀的設(shè)備能真,里面插了一張北斗SIM卡赁严。
PC端測試軟件
通過USB口連接設(shè)備,重新選擇端口和波特率粉铐,點擊端口號旁邊的連接疼约,正常情況下就ok了:
此時就可以發(fā)一些指令來測試軟件和硬件是否正常工作了。
我發(fā)送了兩條指令蝙泼,分別是IC讀取和定位申請程剥,即上圖中的紅框1,2。
紅框3是發(fā)送的指令汤踏,紅框4是收到的指令织鲸,具體的協(xié)議我們先跳過,后面再研究溪胶。
USB轉(zhuǎn)串口通信
手機(jī)可以用無線和有線兩種方式與設(shè)備進(jìn)行通信搂擦,我們選擇的有線的方式,所以使用USB轉(zhuǎn)串口通信哗脖。這部分推薦Android usb及串口通信瀑踢,我也是使用博主的工具進(jìn)行調(diào)試的扳还,調(diào)通之后再開始接入自己的項目,進(jìn)行后續(xù)開發(fā)橱夭。我針對北斗海聊做的一些特殊的優(yōu)化在這個項目中普办。
北斗協(xié)議
開發(fā)文檔我先后拿到過三份,都不盡相同徘钥。推薦新手從開發(fā)快速入門手冊開始看,能夠比較快的上手肢娘。我最開始拿到的是頁數(shù)最多的那份呈础,當(dāng)時心里真是...ε=(′ο`*)))。
標(biāo)準(zhǔn):
$IDsss,d1橱健,d2而钞,……,dn*hh<CR><LF>
一些典型的指令:
$CCICA,0,00*7B\r\n
$CCRMO,GGA,2,60*09\r\n
$BDFKI,DWA,Y,Y,0,0000*0C\n
- $
一句指令的開始拘荡。 - ID
這里只是一個標(biāo)識符臼节,不要給后面出現(xiàn)的用戶ID混淆。發(fā)送給設(shè)備的指令為CC珊皿,收到設(shè)備返回的指令為BD网缝。 - sss
這是具體指令的名字。 - ...
一直到*號之前蟋定,這就是具體的指令內(nèi)容了嫩海。 - *號
分割符庇楞,前面是具體指令內(nèi)容,后面兩位就異或校驗。 - <CR><LF>
回車換行符官册,不同平臺有所區(qū)別,Android平臺是\r\n刻剥。這是一條指令的終止符馋记,很重要!K磷省矗愧!
Talk is cheap,show me the code
- IC讀取
/**
* 讀取卡號
*
* @return 讀取卡號命令
*/
public static String getICCmd() {
return "$CCICA,0,00*7B\r\n";
}
- 獲取地理信息
/**
* 獲取位置信息,北斗一代
*
* @return 獲取位置信息命令
*/
public static String getLocationCmdV1() {
return "$CCDWA,0000000,V,1,L,,0,,,0*65\r\n";
}
一代的定位精度低一些迅耘,現(xiàn)在一般都不用了贱枣。
/**
* 獲取位置信息,北斗二代颤专,更加精確纽哥,頻度60s
*
* @return 獲取位置信息命令
*/
public static String getLocationCmd() {
return "$CCRMO,GGA,2,60*09\r\n";
}
public static String getLocationCmd(int freq) {
String s = "CCRMO,GGA,2," + freq;
String check = SerialPortUtil.getBCC(s.getBytes());
return "$" + s + "*" + check + "\r\n";
}
北斗二代獲取地理信息有頻度限制,最高60s/次栖秕。
- 停止輸出
/**
* 停止輸出所有指令
*
* @return
*/
public static String stopOutputCmd() {
return "$CCRMO,,3,*4F" + "\r\n";
}
比如60s/次開始定位后春塌,想通過不斷電的方式讓設(shè)備停止定位,則可以發(fā)送此指令。
- 發(fā)送短報文
/**
* 發(fā)送短報文
*
* @param id 收信方用戶id只壳,必須為7位俏拱,eg:0967760
* @param content 短報文內(nèi)容
* @return 發(fā)送短報文命令,eg:
* $CCTXA,0967760,1,2,A43132335F414243BABAD7D6*77吼句,其內(nèi)容為”123_ABC漢字“
*/
public static String getMsgCmd(String id, String content) {
String contentFlag = "A4";
String start = "CCTXA";
//分別表示通信類別和傳輸方式锅必,這里選擇了普通通信、混合傳輸
String middle = "1,2";
String result = null;
try {
String charsetName = "gb2312";
byte[] contentBytes = content.getBytes(charsetName);
StringBuilder sb = new StringBuilder(start)
.append(",").append(id)
.append(",").append(middle)
.append(",").append(contentFlag);
String hexString = SerialPortUtil.encodeHexString(contentBytes);
sb.append(hexString);
String s = sb.toString();
String check = SerialPortUtil.getBCC(s.getBytes(charsetName));
result = "$" + s + "*" + check + "\r\n";
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
- 解析反饋信息
/**
* 解析反饋信息
*
* @param response 反饋字符串惕艳,eg:$BDFKI,TXA,Y,Y,0,0000*13搞隐,
* $BDFKI,DWA,N,Y,0,0058*16
* @return
*/
public static BeidouBean.Response parseResponse(String response) {
String[] split = response.split(",");
BeidouBean.Response res = new BeidouBean.Response();
res.cmdName = split[1];
res.success = "Y".equals(split[2]);
res.freqSetting = "Y".equals(split[3]);
res.limitStatus = Integer.parseInt(split[4]);
String hourSecond = split[5];
String hour = hourSecond.substring(0, 2);
String second = hourSecond.substring(2, 4);
res.waitSecond = Integer.parseInt(hour) * 60 +
Integer.parseInt(second);
return res;
}
//發(fā)出指令后的反饋信息
public static class Response{
public String cmdName;
//指令是否執(zhí)行成功
public boolean success;
public boolean freqSetting;
//0-發(fā)射抑制解除,大于0則不正常
public int limitStatus;
//當(dāng)用戶設(shè)備發(fā)送入站申請時远搪,若距離上一次入站申請
//的時間間隔小于服務(wù)頻度時劣纲,給出等待時間提示,格式為hhss
public int waitSecond;
}
- 解析地理信息
/**
* 解析位置信息谁鳍,用于北斗一代
*
* @param response 回傳字符串癞季,eg:
* $BDDWR,1,0242407,084936.50,2302.2434,N,11323.6667,E,14,M,-6,M,1,V,V,L*1F
* @return
*/
public static BeidouBean.Location parseLocationV1(String response) {
String[] split = response.split(",");
BeidouBean.Location location = new BeidouBean.Location();
location.customStr = response;
location.userId = split[2];
location.time = split[3];
location.lat = split[4];
location.latDirection = split[5];
location.lon = split[6];
location.lonDirection = split[7];
location.altitude = split[8];
return location;
}
/**
* 解析位置信息
*
* @param response 回傳字符串,eg:
* $GNGGA,063846.00,2914.96875,N,10444.57129,E,1,12,1.07,316.47,M,0,M,,,2.58*6A
* @return
*/
public static BeidouBean.Location parseLocation(String response) {
String[] split = response.split(",");
BeidouBean.Location location = new BeidouBean.Location();
location.customStr = response;
location.time = split[1];
location.lat = split[2];
location.latDirection = split[3];
location.lon = split[4];
location.lonDirection = split[5];
location.altitude = split[9];
return location;
}
我封裝的數(shù)據(jù)模型里并沒有把所有信息都加進(jìn)去倘潜,大家使用的使用可以自己拓展绷柒。另外,以上方法中沒有對回傳的指令進(jìn)行異或校驗窍荧,上生產(chǎn)時應(yīng)該加上辉巡。
- 接收指令
這里有一個坑,一條指令可能會分為2次甚至3次傳送回來蕊退,所以必須自己做處理郊楣。我的解決方案是創(chuàng)建一個buf數(shù)組,每次接收到的指令都往里面放瓤荔,直到讀到終止符\r\n净蚤。
DeviceMeasureController.INSTANCE.measure(usbSerialPort,
new UsbMeasureParameter(UsbPortDeviceType.USB_OTHERS,
19200, 8, 1, 0), new UsbMeasureListener() {
private byte[] buf = new byte[256];
private int index = 0;
@Override
public void measuring(@NotNull UsbSerialPort usbSerialPort, @NotNull byte[] data) {
XLog.d(Arrays.toString(data));
System.arraycopy(data, 0, buf, index, data.length);
// 換行符
if (data[data.length - 1] == (byte) 10) {
String response = new String(buf, 0, index + data.length);
XLog.d(response);
XLog.d(response.length());
String info;
if (response.startsWith("$BDFKI")) {
BeidouBean.Response bResponse = BeidouUtil.parseResponse(response);
if ("DWA".equals(bResponse.cmdName)) {
if (!bResponse.success) {
//todo
}
} else if ("TXA".equals(bResponse.cmdName)) {
if (bResponse.success) {
//todo
} else {
if (bResponse.waitSecond == 0) {
//todo
} else {
//todo
}
XLog.w(String.format("還需等待%ss", bResponse.waitSecond));
}
}
}
info = bResponse.toString();
} else if (response.startsWith("$GNGGA")) {
String location = BeidouUtil.customLocation(response);
//todo
info = location;
}else if(response.startsWith("$BDTXR")){
//下發(fā)
BeidouBean.pushMsg pushMsg = BeidouUtil.parsePushMsg(response);
receiveMsg(pushMsg);
info = pushMsg.toString();
} else {
info = response;
}
XLog.d(info);
XLog.d(Arrays.toString(buf));
buf = new byte[256];
index = 0;
} else {
index += data.length;
}
});
}
@Override
public void write(@NotNull UsbSerialPort usbSerialPort) {
//允許持續(xù)性寫入數(shù)據(jù)
try {
usbSerialPort.write(new byte[]{(byte) 0xff, (byte) 0xff}, 1000);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void measureError(@NotNull String message) {
XLog.e(message);
}
});
以上代碼看起來很長,因為我把解析不同指令的代碼放里面了输硝,簡化版本的核心邏輯就這樣:
DeviceMeasureController.INSTANCE.measure(usbSerialPort,
new UsbMeasureParameter(UsbPortDeviceType.USB_OTHERS,
19200, 8, 1, 0), new UsbMeasureListener() {
private byte[] buf = new byte[256];
private int index = 0;
@Override
public void measuring(@NotNull UsbSerialPort usbSerialPort, @NotNull byte[] data) {
XLog.d(Arrays.toString(data));
System.arraycopy(data, 0, buf, index, data.length);
// 換行符
if (data[data.length - 1] == (byte) 10) {
String response = new String(buf, 0, index + data.length);
//todo
buf = new byte[256];
index = 0;
} else {
index += data.length;
}
});
}
@Override
public void write(@NotNull UsbSerialPort usbSerialPort) {
//允許持續(xù)性寫入數(shù)據(jù)
try {
usbSerialPort.write(new byte[]{(byte) 0xff, (byte) 0xff}, 1000);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void measureError(@NotNull String message) {
XLog.e(message);
}
});
以上是我這次使用北斗短報文實現(xiàn)的一個偽IM今瀑。
代碼在此