Ntrip协议
CORS就是网络基准站,通过网络收发GPS差分数据。用户访问CORS后,不用单独架设GPS基准站,即可实现GPS流动站的差分定位。访问CORS系统,就需要网络通讯协议。NTRIP是CORS系统的通讯协议之一,所有的 RTK数据格式(NCT,RTCM,CMR,CMR+等等)都能被传输。
主要流程
拉取挂载点列表数据
连接挂载点
收发差分数据
⼀、拉取挂载点列表
建⽴socket连接后,发送拉取挂载点的命令:
"GET / HTTP/1.1\r\n"
"User-Agent: NTRIP GNSSInternetRadio\r\n"
"Accept: */*\r\n"
"Connection: close\r\n\r\n";
Caster收到命令后,会回复挂载点列表信息:
SOURCETABLE 200 OK
Server: POP_GW_Ntrip_1.0_1622119852/1.0
Via: n20_108
Date: 2021/06/07 20:39:47
Content-Type: text/plain; charset=UTF-8
Content-Length: 475
Connection: close
STR;AUTO;AUTO;RTCM3X;;2;GNSS;POPNet;CHN;0.00;0.00;1;1;POP Platform;none;B;N;500;POP
STR;RTCM30_GG;RTCM30_GG;RTCM3X;1005(10),1004-1012(1),1033(10);2;GNSS;POPNet;CHN;0.00;0.00;1;1;POP Platform;none;B;N;500;POP
STR;RTCM23_GPS;RTCM23_GPS;RTCM2X;1(1),31(1),41(1),3(10),32(30);2;GNSS;POPNet;CHN;0.00;0.00;1;1;POP Platform;none;B;N;500;POP
STR;RTCM32_GGB;RTCM32_GGB;RTCM3X;1005(10),1074-1084-1124(1);2;GNSS;POPNet;CHN;0.00;0.00;1;1;POP Platform;none;B;N;500;POP
ENDSOURCETABLE
STR;AUTO;AUTO;RTCM3X;;2;GNSS;POPNet;CHN;0.00;0.00;1;1;POP Platform;none;B;N;500;POP
这就是一条节点信息,主要是第二条挂载点名字
⼆、连接挂载点
通过拉取到的挂载点信息,需要⽤户名和密码登⼊,才能与其建⽴通信。发送命令格式如下:
GET /RTCM30_GG HTTP/1.0
User-Agent: NTRIP GNSSInternetRadio/1.4.10
Accept: */*
Connection: close
Authorization: Basic cXhuZ3kwMDU6MTAzZTdiZQ==
RTCM30_GG为挂载点名称,cXhuZ3kwMDU6MTAzZTdiZQ==是⼀个base64字符串,它是由⽤户名和密码中间加冒号("UserName:Password")后⽤base64编码⽣成的字符串。如果⽤户名密码及挂载点都正确的话,Ntrip将返回数据:
ICY 200 OK
有些服务器只返回"ICY 200 OK",这个没有影响,说明已经与挂载点建⽴好通信,可以收发数据了。
三、发送差分数据
发送差分数据:
$GPGGA,052114.93,3115.2739300,N,12133.8922600,E,1,00,1.0,-10.643,M,11.353,M,0.0,*5D
如果命令格式错误,Ntrip将不返回任何数据。正确数据将返回数据:
$GNGGA,031202.00,3109.91846,N,12123.97022,E,1,03,18.63,-0.7,M,9.7,M,,*6F
完整代码代码,
import android.util.Base64;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
功能描述:RTK连接处理
* 详细描述:RTK连接处理
*
* @Author: 龚寿生
* @CreateDate: 2021/6/7 17:00
* @UpdateUser: 龚寿生
* @UpdateDate: 2021/6/10 10:00
* @UpdateRemark: 更新连接问题
* @Version: 1.0.1 版本
*/
public class RTKSocketManager {
//TAG
private static final String TAG = "RTKSocketManager";
private static final String TAG_HOST = "host";
private static final String TAG_PORT = "port";
private static final String TAG_USERNAME = "userName";
private static final String TAG_PASSWORD = "password";
//地址账号相关
private String host;
private int port;
private String userName;
private String password;
//连接Ntrip协议服务器socket接口
private final TcpSocket tcpNet;
//线程池,用于网络耗时请求
private final ExecutorService singleThreadExecutor;
//定时发送差分数据,避免过快导致服务器压力过大以及数据太多下发给飞控
private Timer timer;
//差分数据收发开始标志,当前差分数据是否有效
private boolean isStart=false, isFlag=false;
//当前挂载点
private String mountPoint;
//差分数据缓存,一秒钟发一次
private String differentialData;
//数据回调
private Callback callback;
/**
* 采用内部类延单例模式,可以实现线程安全和延迟加载
*/
private static class SingletonHolder {
private static final RTKSocketManager INSTANCE = new RTKSocketManager();
}
/**
* 静态获取当前对象
*
* @return 当前实例对象
*/
public static RTKSocketManager instance() {
return SingletonHolder.INSTANCE;
}
/**
* 实例化对象
*/
private RTKSocketManager() {
host = SharedPreferencesStore.getString(TAG, TAG_HOST);
port = SharedPreferencesStore.getInt(TAG, TAG_PORT, 0);
userName = SharedPreferencesStore.getString(TAG, TAG_USERNAME);
password = SharedPreferencesStore.getString(TAG, TAG_PASSWORD);
tcpNet = new TcpSocket();
singleThreadExecutor = Executors.newSingleThreadExecutor();
}
/**
* 设置地址和端口
*
* @param host 服务器地址
* @param port 端口
*/
public void init(String host, int port) {
this.host = host;
this.port = port;
}
/**
* 连接挂载点
*/
public void queryMountPointList() {
// if (TextUtils.isEmpty(host) || port <= 0) {
// return;
// }
singleThreadExecutor.execute(() -> {
if (tcpNet.connect(host, port)) {
getMountPointList();
} else {
connectMountPointListFail();
}
});
}
/**
* 连接挂载点失败
*/
private void connectMountPointListFail() {
AutelLog.debug_i(TAG, "getMountPointList fail");
if (callback != null) {
MsgPostManager.instance().post(new PostRunnable() {
protected void task() {
callback.getMountPointListFail();
}
});
}
}
/**
* 获取挂载点列表,通过主线程回调传输数据
*/
private void getMountPointList() {
String GET_MOUNT_POINT = "GET / HTTP/1.1\r\n" + "User-Agent: NTRIP GNSSInternetRadio\r\n" + "Accept: */*\r\n" + "Connection: close\r\n\r\n";
tcpNet.write(GET_MOUNT_POINT.getBytes());
byte[] readBuf = tcpNet.read();
if (readBuf.length != 0) {
String reads = new String(readBuf, StandardCharsets.UTF_8);
AutelLog.debug_i(TAG, reads);
String[] strings = reads.split("\n");
ArrayList
for (int i = 8; i < strings.length; i++) {
String[] mount = strings[i].split(";");
if (mount.length == 19 && mount[0].equals("STR") || mount[0].equals("CAS") || mount[0].equals("NET")) {
list.add(mount[1]);
}
}
if (list.size() != 0) {
AutelLog.debug_i(TAG, "list.size:" + list.size());
tcpNet.disConnect();
if (callback != null) {
MsgPostManager.instance().post(new PostRunnable() {
protected void task() {
callback.getMountPointListSuccess(list);
}
});
}
} else {
connectMountPointListFail();
}
} else {
connectMountPointListFail();
}
}
/**
* 设置用户名和密码
*
* @param userName 用户名
* @param password 密码
*/
public void setUserNameAndPassword(String userName, String password) {
this.userName = userName;
this.password = password;
}
/**
* 连接挂载点,需要提前设置用户名和密码
*
* @param mountName 挂载点名称
*/
public void connectMountPoint(String mountName) {
// if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(password) || TextUtils.isEmpty(mountName)) {
// return;
// }
singleThreadExecutor.execute(() -> {
if (tcpNet.connect(host, port)) {
String cmd = getConnectMountPointCmd(mountName);
AutelLog.debug_i(TAG, "connectMountPoint:" + cmd);
tcpNet.write(cmd.getBytes());
byte[] readBuf = tcpNet.read();
if (readBuf.length != 0) {
String reads = new String(readBuf, StandardCharsets.UTF_8);
AutelLog.debug_i(TAG, "read:" + reads);
if (reads.contains("200 OK")) {
mountPoint = mountName;
MsgPostManager.instance().post(new PostRunnable() {
protected void task() {
startRtkDataConnect();
if (callback != null) {
callback.connectMountPointSuccess();
}
}
});
} else {
connectMountPointFail();
}
} else {
connectMountPointFail();
}
} else {
connectMountPointFail();
}
});
}
/**
* 连接挂载点失败
*/
private void connectMountPointFail() {
AutelLog.debug_i(TAG, "connectMountPoint fail");
if (callback != null) {
MsgPostManager.instance().post(new PostRunnable() {
protected void task() {
callback.connectMountPointFail();
}
});
}
}
/**
* 开始和费看看收发RTK差分数据
*/
private void startRtkDataConnect() {
SharedPreferencesStore.saveString(TAG, TAG_HOST, host);
SharedPreferencesStore.saveInt(TAG, TAG_PORT, port);
SharedPreferencesStore.saveString(TAG, TAG_USERNAME, userName);
SharedPreferencesStore.saveString(TAG, TAG_PASSWORD, password);
isStart = true;
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
AutelLog.debug_i(TAG, isStart + ",isStart,isFlag," + isFlag);
if (isStart) {
if (isFlag) {
AutelLog.debug_i(TAG, "write:" + differentialData);
tcpNet.write(differentialData.getBytes());
byte[] bytes = tcpNet.read();
AutelLog.debug_i(TAG, "uploadRTKData:" + Arrays.toString(bytes));
}
} else {
//尝试重连上次登录成功的账号
connectMountPoint(mountPoint);
}
}
}, 0, 1000);
}
/**
* 获取连接挂载点指令
*
* @param mountName 挂载点名称
* @return 字符串
*/
private String getConnectMountPointCmd(String mountName) {
return "GET /" + mountName + " HTTP/1.1\r\n" +
"User-Agent: NTRIP GNSSInternetRadio\r\n" +
"Accept: */*\r\n" +
"Connection: close\r\n" +
"Authorization: Basic " + Base64.encodeToString((userName + ":" + password).getBytes(), Base64.DEFAULT) + "\r\n\r\n";
}
/**
* 检测是否已经连接
*
* @return false: failure true:success
*/
public boolean isConnected() {
return tcpNet.isConnected() && isStart;
}
/**
* 不需要RTK资源的时候,需要销毁资源
*/
public void destory() {
isStart = false;
isFlag = false;
if (timer != null) {
timer.cancel();
}
}
/**
* 接口回调
*/
public interface Callback {
/**
* 获取挂载点成功
*
* @param list 挂载点名称列表
*/
void getMountPointListSuccess(ArrayList
/**
* 获取挂载点失败
*/
void getMountPointListFail();
/**
* 连接挂载点成功
*/
void connectMountPointSuccess();
/**
* 连接挂载点失败
*/
void connectMountPointFail();
}
/**
* 设置回调接口
*
* @param callback 接口
*/
public void setCallback(Callback callback) {
this.callback = callback;
}
/**
* 获取服务器地址
*
* @return String
*/
public String getHost() {
return host;
}
/**
* 获取端口
*
* @return 端口
*/
public int getPort() {
return port;
}
/**
* 获取用户名
*
* @return 用户名
*/
public String getUserName() {
return userName;
}
/**
* 获取密码
*
* @return 密码
*/
public String getPassword() {
return password;
}
/**
* 设置差分数据
*
* @param bytes 差分数据
*/
public void setGPGGA(byte[] bytes) {
differentialData = new String(bytes)+"\r\n";
// differentialData = new String(bytes);
isFlag=true;
AutelLog.debug_i(TAG, "setGPGGA:" + Arrays.toString(bytes));
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import timber.log.Timber;
/**
*
功能描述:TcpSocket工具类
* 详细描述:
*
* @author 龚寿生
* @version V1.0
* @date 2021/1/21 16:13
*/
public class TcpSocket {
private static final String TAG = "gss";
private static final int TIME_OUT = 3000;
private Socket socket = null;
public TcpSocket() {
}
/**
* Function:connect the socket of IpParameters
*
* @return boolean: failure 0:success
*/
public boolean connect(String address, int port) {
if (socket != null && socket.isConnected()) {
return true;
}
try {
socket = new Socket(address, port);
socket.setSoTimeout(TIME_OUT);
} catch (IOException e) {
e.printStackTrace();
}
return socket != null && socket.isConnected();
}
/**
* Function:examine the connection
*
* @return false: failure true:success
*/
public boolean isConnected() {
if (socket == null) {
return false;
}
return socket.isConnected();
}
/**
* set time out
*
* @param time Timeout
* @return false: failure true:success
*/
public boolean setTcpTimeout(int time) {
if (socket == null) {
return false;
}
try {
socket.setSoTimeout(time);
} catch (SocketException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Function:read the data of socket
*
* @return 0: failure ,other:success
*/
public byte[] read() {
int size = 0;
if (socket == null || !socket.isConnected()) {
Timber.i("socket == null || !socket.isConnected()");
return new byte[0];
}
byte[] buf = new byte[1024];
try {
InputStream inputStream = socket.getInputStream();
size = inputStream.read(buf);
} catch (IOException e) {
Timber.tag(TAG).i("socket read IOException:%s", e.getMessage());
disConnect();
e.printStackTrace();
}
if (size > 0) {
byte[] rev_data=new byte[size];
System.arraycopy(buf, 0, rev_data, 0, size);
return rev_data;
}
return new byte[0];
}
/**
* Function:write the data of socket
*
* @param data the data from the client;
*/
public boolean write(byte[] data) {
if (socket == null || !socket.isConnected()) {
Timber.tag(TAG).i("socket == null || !socket.isConnected()");
return false;
}
try {
OutputStream outputStream = socket.getOutputStream();
outputStream.write(data);
} catch (IOException e) {
Timber.tag(TAG).i("socket write IOException:%s", e.getMessage());
e.printStackTrace();
disConnect();
return false;
}
return true;
}
/**
* Function:disConnect the socket
*/
public void disConnect() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
参考链接:
https://www.it610.com/article/1279923609803964416.htm
https://blog.csdn.net/hanford/article/details/53025771
https://www.cnblogs.com/hanford/p/6028156.html