基于Ntrip的实时多线程RTCM数据流接收及解码
Ntrip协议基本框架
NtripSource能够持续提供实时GNSS数据,如原始GNSS观测数据格式:BINEX格式、差分数据格式RTCM。常常由接收机承担,Ntrip系统为每个参考站接收机定义唯一的内部编号,称之为挂载点Mountpoint。
NtripSources, which generate data streams at a specific location.
NtripServers接受来自Source的GNSS数据流。采用既定的NtripCaster的IP地址通过互联网与NtripCaster进行认证并及逆行实时的GNSS数据流传输。
NtripServers upload the data of an NtripSource to an NtripCaster using a “mountpoint” as identification. Several NtripClients can access the data of a desired data stream in parallel by requesting a source by its mountpoint on the NtripCaster.
NtripCaster是Ntrip协议的核心,相当于一个数据中转站。一方面通过身份认证允许既定的NtripSevers接入数据中转站,一方面通过身份认证允许NtripClient获取特定挂载点MountPoint的请求并反馈其不正确操作。
The NtripCaster is a server supporting a subset of HTTP/RTSP request and response
messages and adjusted to low-bandwidth streaming data (from 50 up to 500 Bytes/sec
per stream). The NtripCaster accepts request-messages on a single port from either the NtripServer or the NtripClient. Depending on these messages, the NtripCaster decides whether there is streaming data to receive or to send.
NtripClient通过NtripCaster获取所需要的NtripSource,是GNSS数据流的最终消费者。NtripClient通过互联网请求NtripCaster获得NtripSource的source-table,根据需求获得相关挂载点的信息,通过使用相应挂载点的用户名和密码进行认证,NtripCaster认证通过后会向NtripClient传输源源不断的数据流。
An NtripClient will be accepted by and receive data from an NtripCaster if the NtripClient sends the correct request message. With respect to the message format and status code, the HTTP-based NtripClient to NtripCaster communication is fully compatible to HTTP 1.1, but in this context Ntrip uses only non-persistent connections.
配置文件设置
若整体格式为:
mountPoints=//xxxxxxx:xxxxxxx@ntrip.gnsslab.cn:2101/CUT000AUS0 RTCM_3.0 39.45 328.87 no 1
则:
- 认证信息 :mountPoints=//xxxxxxx:xxxxxxx@
- 主机及其端口 :ntrip.gnsslab.cn:2101
- 协议 :RTCM_3.0
- 测站(挂载点) :CUT000AUS0
可以有多个mountPoints=//xxxxxx格式,多个测站,会有多个线程对应。
解析配置文件信息
在主程序中调用解析文件函数parse_config_info(),gArgs.conf是上述配置文件。
int i,j,n = parse_config_info(gArgs.conf, configs);//主机、端口、协议、路径、认证信息、精度、维度
其中,解析配置文件函数在fileopt.c下
/**
* 从配置文件中解析配置信息
* 返回成功解析的测量站配置信息的数量
* */
int parse_config_info(char *file_name, ConfigInfo *configs[]) {
printf("begin to parse config file: %s\n", file_name);
config_t config = {0};
if(ini_parse(file_name, handler, &config) < 0){
printf("Can't load config file: '%s'\n", file_name);
return 0;
}
//端口
if(gArgs.port == -1 && config.port >= MIN_LSN_PORT && config.port <= MAX_LSN_PORT){
gArgs.port = config.port;
}
//日志水平
if(gArgs.log_level == -1 && config.log_level >= 0 && config.log_level <= 4){
gArgs.log_level = config.log_level;
}
//日志文件
if(gArgs.log_file == NULL && config.log_file != NULL && strcmp(config.log_file, "") != 0){
gArgs.log_file = strdup(config.log_file);
}
//数据记录粒度
if(gArgs.time_unit == -1 && config.time_unit >= 1 && config.time_unit <= 3){
gArgs.time_unit = config.time_unit;
}
//文件过期时间
if(gArgs.expire_days == -1 && config.expire_days >= 0){
gArgs.expire_days = config.expire_days;
}
//数据文件
if(gArgs.data_path == NULL && config.data_file != NULL && strcmp(config.data_file, "") != 0){
gArgs.data_path = strdup(config.data_file);
}
if(gArgs.data_off == -1) gArgs.data_off = config.data_off;
if(gArgs.recv_off == -1) gArgs.recv_off = config.recv_off;
if(gArgs.eph_off == -1) gArgs.eph_off = config.eph_off;
//数据流文件
if(gArgs.recv_path == NULL && config.recv_file != NULL && strcmp(config.recv_file, "") != 0){
gArgs.recv_path = strdup(config.recv_file);
}
//星历文件
if(gArgs.eph_path == NULL && config.eph_file != NULL && strcmp(config.eph_file, "") != 0){
gArgs.eph_path = strdup(config.eph_file);
}
//测站列表
if(config.mount_points == NULL || strcmp(config.mount_points, "") == 0) return 0;
int i=0;
char *token = strtok(config.mount_points, ",");
while(token != NULL) {
//读取这一段配置信息
ConfigInfo *cfg = malloc(sizeof(*cfg));
char protocal[16],url[256];
if(sscanf(token,"%s %s %f %f %s %s",url,protocal,&cfg->f1,&cfg->f2,cfg->s1,cfg->s2) == 6) {
//协议信息
protocal[15] = '\0';
if (strcmp(protocal,"RTCM_3") == 0) cfg->proto = RTCM30;
else if (strcmp(protocal,"RTCM_3.0") == 0) cfg->proto = RTCM30;
else if (strcmp(protocal,"RTCM_3.1") == 0) cfg->proto = RTCM31;
else if (strcmp(protocal,"RTCM_2.3") == 0) cfg->proto = RTCM23;
else if (strcmp(protocal,"RTCM_2.1") == 0) cfg->proto = RTCM21;
else if (strcmp(protocal,"RTCM_2.0") == 0) cfg->proto = RTCM20;
else if (strcmp(protocal,"RTCM_2.2") == 0) cfg->proto = RTCM22;
else if (strcmp(protocal,"BD") == 0) cfg->proto = BD;
else if (strcmp(protocal,"TRIM") == 0) cfg->proto = TRIM;
else if (strcmp(protocal,"BINEX") == 0) cfg->proto = BINEX;
else if (strcmp(protocal,"XIHE_RTCM_3") == 0) cfg->proto = XIHE_RTCM30;
else if (strcmp(protocal,"XIHE_RTCM_3.0") == 0) cfg->proto = XIHE_RTCM30;
else if (strcmp(protocal,"IGMAS_RTCM_3") == 0) cfg->proto = IGMAS_RTCM30;
else if (strcmp(protocal,"IGMAS_RTCM_3.0") == 0) cfg->proto = IGMAS_RTCM30;
else if (strcmp(protocal,"RTCM_3.2") == 0) cfg->proto = RTCM32;
else cfg->proto = NONE;
//服务器信息
struct evhttp_uri *uri = evhttp_uri_parse(url);
cfg->port = evhttp_uri_get_port(uri);
cfg->host = evhttp_uri_get_host(uri);
cfg->path = evhttp_uri_get_path(uri);
//认证信息(找到第一个冒号,冒号前算用户名,冒号后算密码)
const char *userinfo = evhttp_uri_get_userinfo(uri);
if(userinfo != NULL) {
char *spliter = strchr(userinfo,':');
if(spliter != NULL) {
char user[MAX_USER_LEN];
char pass[MAX_PASS_LEN];
int pos = spliter - userinfo;
int len = strlen(userinfo);
if(pos < MAX_USER_LEN && len-pos-1 < MAX_PASS_LEN) {
strncpy(user,userinfo,pos);
user[pos] = '\0';
strncpy(pass,spliter+1,len-pos-1);
pass[len-pos-1] = '\0';
encode(cfg->auth,MAX_AUTH_LEN,user,pass);
configs[i++] = cfg;
} else trace(WARN,"Username and/or password too long: %s\n",userinfo);
} else trace(WARN,"Auth config error, %s\n",userinfo);
} else {
trace(INFO,"No auth info configed.\n");
cfg->auth[0] = '\0';
configs[i++] = cfg;
}
free(uri);
} else {
free(cfg);
trace(FATAL,"Invalid config line: %s\n",token);
}
token = strtok(NULL,",");
if(i >= MAX_STATION){
trace(DEBUG,"Too many config items. Max size is %d\n",MAX_STATION);
break;
}
}
return i;
}
返回到configs[]中,包含所有测站的配置文件里基本信息:有多个主机,多个端口:
typedef struct {
const char *host; //主机
int port; //端口
int proto; //协议,指(RTCM3.1,RTCM3.0,RTCM2.3等)
const char *path; //路径
char auth[MAX_AUTH_LEN]; //认证信息
float f1; //精度
float f2; //维度
char s1[8]; //未知,无用
char s2[8]; //未知,无用
} ConfigInfo;
ConfigInfo *configs[MAX_STATION]; //配置信息
另,该程序有直接从文件流读取数据模块(实时部分暂时不采用):
//从文件流读取数据
if(gArgs.stream != 0) {//默认等于0,采用后续从网络端接收数据流
ObsCtx *ctx = calloc(1,sizeof(*ctx));
ctx->obs_rec = calloc(1,sizeof(*ctx->obs_rec));
ctx->data_fp = fopen("temp.12O","wb"); //数据解析后的文件
FILE *sfile = fopen(gArgs.s_file,"rb");
if(sfile == NULL) {
trace(FATAL,"Stream file %s not found!\n",gArgs.s_file);
exit(1);
}
switch(gArgs.stream) {
case 1:
ctx->rtcm = calloc(1,sizeof(*ctx->rtcm));
input_rtcm3f(ctx,sfile);
break;
case 2:
ctx->rtcm = calloc(1,sizeof(*ctx->rtcm));
input_rtcm2f(ctx,sfile);
break;
case 3:
ctx->bd = calloc(1,sizeof(*ctx->bd));
input_bdf(ctx,sfile);
break;
case 4:
ctx->trim = calloc(1,sizeof(*ctx->trim));
input_trimf(ctx,sfile);
break;
}
exit(1);
}
解析完配置文件的基本信息后,创建服务器监听线程、创建线程池,一个线程对应一个测站及其测站数据。并将配置文件的多个测站基本信息依次存储到测站数组中。
创建多线程、将配置文件信息传给测站数组
首先,ntrip->ctxs[i]指的是某个测站点,ntrip结构如下,包括测站个数、线程总数、活跃线程、所有测站的信息等等:
typedef struct {
int nstats; //测站的个数
int nactive; //活动测站的数量
int total_t; //总共线程数 total thread
int alive_t; //活跃线程数 alive thread
int epoch_type; //广播到客户端的方式
time_t uptime; //系统启动的时间,用于计算系统运行时间
ObsCtx *ctxs[MAX_STATION]; //测站上下文信息
} Ntrip;
Ntrip *ntrip;
Ntrip中观测站的结构体ObsCtx如下,内部包括后续的测站接收到的RTCM数据流RtcmData、最后解析的数据结果ObsRec、配置文件信息、结果文件等等
typedef struct {
int index; //测站对应的坐标
RtcmData *rtcm; //RTCM数据流
BdData *bd; //北斗数据流
TrimData *trim; //[+]@20121209 Trimble数据流
BnxData *bnx; //[+]@20140327 BINEX数据流
ObsRec *obs_rec; //数据包解析结果
ObsQueue *obsq; //观测值队列
/** \brief
*
* \param
* \param
* \return
*
*/
ConfigInfo *config; //配置信息
char marker[8]; //测站的标志
char recv_file[128]; //收到的原始数据包存储的文件名称
FILE *recv_fp; //收到的原始数据包存储的文件描述符
int recv_tag; //当前所写文件的时间标
char data_file[128]; //数据包解析后数据存放的文件名称
FILE *data_fp; //数据包解析后数据存放的文件描述符
int data_tag; //时间标志,最新记录的时间标志。如果是按天,则为年积日,形如 214
//如果按小时,形如:21401,如果按分钟,形如:2140101
//alive表示与该测站的连接是否正常,0表示连接正常
//若大于零表示连接不正常,其值表示重试的次数。
//为避免过于频繁地重试处于死链的测站,第i次重试后
//会间隔2^i秒再进行下一次重试。一旦收到测站的chunk
//数据,就将alive清零。
int alive;
} ObsCtx;
首先:创建多线程:
ntrip = malloc(sizeof(*ntrip));
init_ntrip(ntrip,n);
ntrip->epoch_type = gArgs.epoch_type;
sp_thread_mutex_init(&gnss_mutex,NULL);
//服务器监听线程
sp_thread_t *server_thread = calloc(1,sizeof(sp_thread_t));
if(0 == sp_thread_create(server_thread, NULL, start_caster, &gArgs.port)) {
printf("server listen on port: %d\n",gArgs.port);
}
//创建线程池,一个测站对应一个线程
sp_thread_mutex_init(&gmutex,NULL);
int num_threads = (n <= MAX_STATION) ? n : MAX_STATION;
threadpool *pool = create_threadpool(num_threads);
//创建线程,每一个测站数据对应一个线程
ntrip->total_t = num_threads;
通过循环,处理各个线程。在每个线程中,运用函数init_ctx()将配置文件configs[i]的信息传递给测站ctxs[i],并将测站信息加入到Ntrip中。
for(i=0; i < num_threads; i++) {
printf("tring to connect station: %s:%d%s\n",configs[i]->host,configs[i]->port,configs[i]->path);
//ctx用于回调函数确定收到的数据包属于哪个测试站点
ObsCtx *ctx = malloc(sizeof(*ctx));
init_ctx(ctx,i,configs[i]);
ntrip->ctxs[i] = ctx;
//检查之前的测站名是否有重复的,如果重复用‘#’标记。
for(j=0; j < i; j++) {
if(strncmp(configs[j]->path+1,configs[i]->path+1,4) == 0) {
ntrip->ctxs[i]->marker[5] = '#';
ntrip->ctxs[i]->marker[6] = '\0';
ntrip->ctxs[j]->marker[5] = '#';
ntrip->ctxs[j]->marker[6] = '\0';
}
}
sp_thread_mutex_init(&mutexs[i],NULL);
dispatch_threadpool(pool, create_connection, ctx);
}
根据测站ctx配置文件信息向Caster发送Http的请求‘req’
在dispatch_threadpool()函数中,引用了creat_connection函数,该函数运用了某个已知配置文件信息的ctx作为参数,对该测站进行以下过程:
根据测站的配置文件信息发送Http请求创建连接。
得到Ok回复后进行数据流的接收、解码、储存。
creat_connection函数为:
/**
* 创建连接:尝试连接到测站。
* */
static void create_connection(void* args) {
ObsCtx *ctx = args;
if(ctx == NULL) {
trace(FATAL,"Failed to create this connection, ctx is null.\n");
return;
}
struct event_base* evbase = event_base_new();
evbases[ctx->index] = evbase;
if(evbase == NULL) {
trace(FATAL,"failed to create event base, index = %d\n",ctx->index);
return;
}
//活动线程数+1
sp_thread_mutex_lock( &gmutex );
ntrip->alive_t++;
sp_thread_mutex_unlock( &gmutex );
struct evhttp_connection* conn;
struct evhttp_request *req;
while(1) {
//注:RTCM和BEIDOU获取数据的方式是不一样的,RTCM的方式为发送HTTP请求,然后接收Chunky的HTTP响应。
//北斗测站的方式为直接的到某个目标端口的socket连接
在creat_connection()函数中,根据上述测站ctx配置文件中协议信息ctx->config->proto得到请求req,进行Http链接,返回到evhttp_request_set_chunked_cb(req, chunky_callback)函数中的chunk_callback。这里采用while(1)进行重复循环的原因是没有建立链接会重复发送请求。
else if((strlen(ctx->config->auth) > 0) && (IS_RTCM(ctx->config->proto) || IS_BINEX(ctx->config->proto)) ) {
//创建一个连接
if((conn = evhttp_connection_base_new(evbase, NULL,ctx->config->host, ctx->config->port)) == NULL) {
printf("FATAL!");
break;
}
//构建新请求,请求完成后会回调done_cb,收到chunked数据包回回调chunky_cb
//done_callback是http链接失败后返回,chunky_cb是http请求成功后返回的函数
if((req = evhttp_request_new(done_callback, args)) == NULL) {
printf("ERROR REQ!\n");
break;
}
evhttp_request_set_chunked_cb(req, chunky_callback);
//设置http的请求头(ntrip 2.0)
evhttp_add_header(req->output_headers, "Host", "ntrip.gnsslab.cn");
evhttp_add_header(req->output_headers, "Ntrip-Version", "Ntrip/2.0");
evhttp_add_header(req->output_headers, "User-Agent", "NTRIP NtripRTCM3ToRINEX/1.37");
evhttp_add_header(req->output_headers, "Connection", "close");
//认证信息
char auth[MAX_AUTH_LEN] = "Basic \0";
strcat(auth,ctx->config->auth);
evhttp_add_header(req->output_headers, "Authorization", auth);
//发送请求
evhttp_make_request(conn, req, EVHTTP_REQ_GET, ctx->config->path);
event_base_dispatch(evbase);
evhttp_connection_free(conn);
}
Http请求成功后检查响应并得到原始数据流
采用chunky_callback()检查Caster返回的响应。
响应成功的情况下,添加Ntrip中活跃线程数量,并且得到数据流buffer,用于后续的process()的处理。
/**
* 处理RTCM2.x/RTCM3.x的http响应:
* RTCM的数据是以chunky的http响应播放的。这里检查这个chunky响应是否是正常的,然后
* 将数据传递给process进行处理。
* */
void chunky_callback(struct evhttp_request *req, void *arg) {
//如果收到的响应不是HTTP_OK,则丢弃这个数据包
if (req == NULL || evhttp_request_get_response_code(req) != HTTP_OK) {
trace(FATAL,"bad response, discard.");
return;
}
//添加线程hfx
ObsCtx *ctx = arg;
if(ctx->alive > 0) {
ctx->alive = 0;
sp_thread_mutex_lock( &gmutex );
ntrip->nactive++;
sp_thread_mutex_unlock( &gmutex );
}
//evhttp_request_get_input_buffer为返回的数据流hfx
struct evbuffer *buffer = evhttp_request_get_input_buffer(req);
//process用于解码-hfx
process(ctx,buffer);
}
通过req请求,根据evhttp_request_get_input_buffer()函数得到返回的实时二进制数据流。
将测站ctx接受到的原始数据流进行处理,并将结果返回到该测站信息
运用process()函数实现:
**1、将原始的二进制文件流存储到recv文件中。**
**2、根据测站协议ctx->config->proto的不同分别调用input_rtcm3,input_rtcm2,intput_bd等进行解码。**
其中的evbuffer_remove()函数是将数据流判断buf中有多少个字节。挨个读取这N个字节,按照个协议找到同步码,方便进行后续的解码。
/**
* 处理收到的数据
* 1. 将收到数据直接写入到文件(供以后调试使用)
* 2. 根据协议的不同分别调用input_rtcm3,input_rtcm2,intput_bd,如果返回值为1,表明得到了一个完整的
* 数据。
* 3. 如果解析的数据比队列中上一个数据晚大于1秒,就将上1秒的数据dump到rinex文件中。
**/
void process(ObsCtx *ctx, struct evbuffer *buffer) {
int i,n,tg,ttag,ret=0;
unsigned char buf[4096];
//读取event buffer中的数据,n是读取的字节数量hfx
if((n = evbuffer_remove(buffer,buf,sizeof(buf))) <= 0) return;
//将收到的数据包直接写入文件,供以后调试使用
ttag = get_timetag(time_now());
sp_thread_mutex_lock(&mutexs[ctx->index]);
if(!ctx->recv_fp || time_exceed(ttag,ctx->recv_tag, gArgs.time_unit) || file_exist(ctx->recv_file) == -1) {
if(gArgs.recv_off != 1) set_recvfile(ctx->recv_file, gArgs.recv_path, ctx->marker, ttag, gArgs.time_unit);
//关闭原来文件,打开新的文件
if(ctx->recv_fp) fclose(ctx->recv_fp);
ctx->recv_fp = open_file(ctx->recv_file);
ctx->recv_tag = ttag;
}
fwrite(buf,n,1,ctx->recv_fp);
fflush(ctx->recv_fp);
sp_thread_mutex_unlock(&mutexs[ctx->index]);
ObsRec *top;
ObsQueue *q;
double tt;
//解码n个字节
for(i=0; i<n; i++) {
switch(ctx->config->proto){
case RTCM32:
ret = input_rtcm32(ctx->rtcm,ctx->obs_rec,buf[i]);
break;
case RTCM31:
case RTCM30:
ret = input_rtcm3(ctx->rtcm,ctx->obs_rec,buf[i]);
break;
case XIHE_RTCM30:
ret = input_xihe_rtcm3(ctx->rtcm,ctx->obs_rec,buf[i]);
break;
case IGMAS_RTCM30:
ret = input_igmas_rtcm3(ctx->rtcm,ctx->obs_rec,buf[i]);
break;
case RTCM20:
case RTCM22:
case RTCM23:
case RTCM21:
ret = input_rtcm2(ctx->rtcm,ctx->obs_rec,buf[i]); break;
case BD:
ret = input_bd(ctx->bd,ctx->obs_rec,buf[i]); break;
case TRIM:
ret = input_trim(ctx->trim,ctx->obs_rec,buf[i]); break;
case BINEX:
ret = input_binex(ctx->bnx, ctx->obs_rec, buf[i]); break;
}
//如果是观测值数据,将解析后的结果写入数据文件,这里调用了GPS中心所提供的模块进行数据的压缩
if(ret != 1) continue;
update_time(ctx->obs_rec->obs_time);//更新最新观测时间
q = ctx->obsq;
tg = (q->front >= 0 && q->front < MAX_QUE_SIZE)? q->front : 0;
if(!q->elem[tg]) continue;
if(q->elem[tg]->obs_time.time == 0) update(q->elem[tg],ctx->obs_rec);
top = q->elem[tg];
tt = timediff(ctx->obs_rec->obs_time,top->obs_time);
//如果ctx->obs_rec的时间比队列首元素的时间更晚,就把队列首元素dump到文件
if(tt >= 0.999) {
//如果是RTCM2.x,需要对phase进行调整。
if(ctx->config->proto & (RTCM20 | RTCM21 | RTCM22 | RTCM23)) adjphase(top);
//如果这条记录的时间标志大于ctx中的,则说明需要写入到新的文件中去
time_t t = (top->obs_time.sec > 0.5)?top->obs_time.time + 1 : top->obs_time.time;
ttag = get_timetag(t);
//if(top->obs_time.time%30==0)
{
sp_thread_mutex_lock(&mutexs[ctx->index]);
if(!ctx->data_fp || time_exceed(ttag,ctx->data_tag, gArgs.time_unit) || file_exist(ctx->data_file) == -1) {
if(gArgs.data_off != 1)
{
set_datafile(ctx->data_file, gArgs.data_path, ctx->marker, ttag, gArgs.time_unit);
}
//关闭原来文件,打开新的文件
if(ctx->data_fp) fclose(ctx->data_fp);
ctx->data_fp = open_file(ctx->data_file);
ctx->data_tag = ttag;
//写文件头信息
if(ctx->data_fp && fsize(ctx->data_file) == 0) dump_header(ctx->data_fp,ObsCount,ObsTypes,ctx->marker,top->obs_time);
}
//写数据到文件
if(ctx->data_fp)
dump(ctx->data_fp,top,ObsCount,ObsTypes);
sp_thread_mutex_unlock(&mutexs[ctx->index]);
}
//将数据添加到EPOCH线程的EPOCH队列中去
/** for(j=0; j<MAX_CLIENTS; j++) {
if(clients[j] == NULL || clients[j]->alive == 0 || clients[j]->sync_time != 0) continue;
add2epoch(clients[j],top, ctx->marker);
}*/
//将top移到下一个元素
if(q->elem[tg] != NULL && q->elem[tg]->obs_time.time != 0) {
tg++;
tg = (tg >= q->size || tg >= MAX_QUE_SIZE)?0:tg;
}
q->front = tg;
}
update(q->elem[tg],ctx->obs_rec);
memset(ctx->obs_rec,0,sizeof(ObsRec));
if(ctx->rtcm) ctx->rtcm->nbyte = ctx->rtcm->nbit = ctx->rtcm->obsflag = 0;
if(ctx->bd) ctx->bd->nbyte = 0;
if(ctx->trim) ctx->trim->nbyte = 0;
if(ctx->bnx) ctx->bnx->nbyte = ctx->bnx->step = 0;
}
}
将某一条完整的数据流读取并存到RtcmData* rtcm中
首先,RTCM32的数据流基本格式:
//RTCM32格式
//*+---------- + -------- + ---------- - +-------------------- + ---------- +
//*| preamble | 000000 | length | data message | parity |
//*+---------- + -------- + ---------- - +-------------------- + ---------- +
//*| < -- 8 ---> | <-6 --> | < -- 10 ---> | < -- - length x 8 ---> | <-- 24 --> |
input_rtcm32()函数实现了根据单个字节找到同步码,将一条完整的信息按照一个一个的字节复制到RtcmData* rtcm数据流中,并为将一条完整的数据流传进解码函数decode_rtcm32()中。
extern int input_rtcm32(RtcmData* rtcm,ObsRec* obs_rec,unsigned char c) {
if(rtcm->nbyte == 0) {
if(c != RTCM3PREAMB) return 0; // synchronize frame
rtcm->buff[rtcm->nbyte++] = c;
return 0;
}
rtcm->buff[rtcm->nbyte++] = c;
if(rtcm->nbyte == 3) {
rtcm->len = getbitu(rtcm->buff,14,10)+3; //单位:字节。表示除去最后奇偶校验的中总长度(字节数),+3代表前面的同步码、ID、数据长度三个字节-hfx
}
if (rtcm->nbyte < 3 || rtcm->nbyte<rtcm->len+3) return 0;
rtcm->nbyte=0;
/* check parity */
if (crc24q(rtcm->buff,rtcm->len) != getbitu(rtcm->buff,rtcm->len*8,24)) {
trace(WARN,"rtcm3 parity error: len=%d\n",rtcm->len);
return -1;
}
//解码rtcm32消息
return decode_rtcm32(rtcm,obs_rec);
}
首先导入第一个字节c,初始化rtcm->nbyte == 0。如果不满足该字节为同步字节,即:c != RTCM3PREAMB,直接return 0 退出该函数,继续将该流的下一个字节导入进来,再次判断。
若满足,则将第一个字节赋值为c,即:rtcm->buff[rtcm->nbyte++] = c。根据上述RTCM32格式,读取三个字节到rtcm->buff后,可以根据数据流第三个字节的信息确定整个数据流的长度rtcm->len = getbitu(rtcm->buff,14,10)+3(除去crc),getbitu指的是读取buff中14比特开始读,读10个比特(刚刚是数据长度对应的部分),再转换为二进制的字节数,+3代表前面的同步码、ID、数据长度三个字节。所以rtcm->len为除去奇偶检验的数据字节长度。
当奇偶检验符合时crc24q(rtcm->buff,rtcm->len) = getbitu(rtcm->buff,rtcm->len*8,24),进行decode_rtcm32()解码并存储到某个测站的所有观测数据obs_rec中。
根据协议进行数据流的解码(如Rtcm32)
int decode_rtcm32(RtcmData *rtcm_data, ObsRec *obs_rec) {
int ret = 0;
int type = getbitu(rtcm_data->buff,24,12); //消息类型
obs_rec->msg_type = type;
switch(type) {
case 1001: ret = decode_type_1001(rtcm_data,obs_rec); break;
case 1002: ret = decode_type_1002(rtcm_data,obs_rec); break;
case 1003: ret = decode_type_1003(rtcm_data,obs_rec); break;
case 1004: ret = decode_type_1004(rtcm_data,obs_rec); break; //GPS观测值
case 1009: ret = decode_type_1009(rtcm_data,obs_rec); break;
case 1010: ret = decode_type_1010(rtcm_data,obs_rec); break;
case 1011: ret = decode_type_1011(rtcm_data,obs_rec); break;
case 1012: ret = decode_type_1012(rtcm_data,obs_rec); break; //GLONASS观测值
case 1019: ret = decode_type_1019(rtcm_data); break; //GPS星历数据
case 1020: ret = decode_type_1020(rtcm_data); break; //GLONASS星历数据
case 1071: case 1081: case 1091: case 1101: case 1111: case 1121:
case 1072: case 1082: case 1092: case 1102: case 1112: case 1122:
case 1073: case 1083: case 1093: case 1103: case 1113: case 1123:
case 1074: case 1084: case 1094: case 1104: case 1114: case 1124:
case 1075: case 1085: case 1095: case 1105: case 1115: case 1125:
case 1076: case 1086: case 1096: case 1106: case 1116: case 1126:
case 1077: case 1087: case 1097: case 1107: case 1117: case 1127:
ret =decode_type_multi(rtcm_data,obs_rec); break;
default: trace(DEBUG,"unsupport rtcm3 message type:%d\n",type);
}
return ret;
}
解码星历
/* decode type 1019: gps ephemerides -----------------------------------------*/
static int decode_type_1019(RtcmData *rtcm) {
GpsEph eph = {0};
int sat, prn, week, i = 24+12;
if(i+476 > rtcm->len*8){
trace(WARN, "rtcm3 1019 length error: len=%d\n", rtcm->len);
return -1;
}
prn =getbitu(rtcm->buff,i, 6); i+= 6;
week =getbitu(rtcm->buff,i,10); i+=10;
eph.URAindex =getbitu(rtcm->buff,i, 4); i+= 4;
eph.flags =getbitu(rtcm->buff,i, 2); i+= 2;
eph.IDOT =getbits(rtcm->buff,i,14)*P2_43*SC2RAD; i+=14;
eph.IODE =getbitu(rtcm->buff,i, 8); i+= 8;
eph.TOC =getbitu(rtcm->buff,i,16)*16.0; i+=16;
eph.clock_driftrate =getbits(rtcm->buff,i, 8)*P2_55; i+= 8;
eph.clock_drift =getbits(rtcm->buff,i,16)*P2_43; i+=16;
eph.clock_bias =getbits(rtcm->buff,i,22)*P2_31; i+=22;
eph.IODC =getbitu(rtcm->buff,i,10); i+=10;
eph.Crs =getbits(rtcm->buff,i,16)*P2_5; i+=16;
eph.Delta_n =getbits(rtcm->buff,i,16)*P2_43*SC2RAD; i+=16;
eph.M0 =getbits(rtcm->buff,i,32)*P2_31*SC2RAD; i+=32;
eph.Cuc =getbits(rtcm->buff,i,16)*P2_29; i+=16;
eph.e =getbitu(rtcm->buff,i,32)*P2_33; i+=32;
eph.Cus =getbits(rtcm->buff,i,16)*P2_29; i+=16;
eph.sqrt_A =getbitu(rtcm->buff,i,32)*P2_19; i+=32;
eph.TOE =getbitu(rtcm->buff,i,16)*16.0; i+=16;
eph.Cic =getbits(rtcm->buff,i,16)*P2_29; i+=16;
eph.OMEGA0 =getbits(rtcm->buff,i,32)*P2_31*SC2RAD; i+=32;
eph.Cis =getbits(rtcm->buff,i,16)*P2_29; i+=16;
eph.i0 =getbits(rtcm->buff,i,32)*P2_31*SC2RAD; i+=32;
eph.Crc =getbits(rtcm->buff,i,16)*P2_5; i+=16;
eph.omega =getbits(rtcm->buff,i,32)*P2_31*SC2RAD; i+=32;
eph.OMEGADOT =getbits(rtcm->buff,i,24)*P2_43*SC2RAD; i+=24;
eph.TGD =getbits(rtcm->buff,i, 8)*P2_31; i+= 8;
eph.SVhealth =getbitu(rtcm->buff,i, 6); i+= 6;
i+= 2; //最后两个字段不要了
if (!(sat=satno(SYS_GPS,prn))) {
trace(WARN,"rtcm3 1019 satellite number error: prn=%d\n",prn);
return -1;
}
eph.satellite = sat;
eph.GPSweek = adjgpsweek(week);
//如果此星历数据比老的星历数据更新,则更新之。
return update_gps_eph(prn, eph);
}
解码观测值
/* decode type 1004: extended L1&L2 gps rtk observables ----------------------*/
static int decode_type_1004(RtcmData *rtcm, ObsRec *obs_rec) {
double pr1,cp1,cp2;
int cnr1,cnr2,sat,lock1,lock2,j,nstat,index,i=24+64;
int sync,prn,code1,ppr1,amb,code2,pr21,ppr2;
if((nstat = decode_head_1001(rtcm,obs_rec,&sync)) < 0) return -1;
if(rtcm->obsflag || fabs(timediff(rtcm->obs_time,obs_rec->obs_time)) > 1E-9) {
rtcm->obsflag = obs_rec->sat_num = 0;
}
obs_rec->obs_time = rtcm->obs_time;
for(j=0; j < nstat && obs_rec->sat_num < MAX_OBS && i+125 <= rtcm->len*8; j++) {
prn = getbitu(rtcm->buff,i, 6); i+= 6;
code1=getbitu(rtcm->buff,i, 1); i+= 1;
pr1 =getbitu(rtcm->buff,i,24); i+=24;
ppr1 =getbits(rtcm->buff,i,20); i+=20;
lock1=getbitu(rtcm->buff,i, 7); i+= 7;
amb =getbitu(rtcm->buff,i, 8); i+= 8;
cnr1 =getbitu(rtcm->buff,i, 8); i+= 8;
code2=getbitu(rtcm->buff,i, 2); i+= 2;
pr21 =getbits(rtcm->buff,i,14); i+=14;
ppr2 =getbits(rtcm->buff,i,20); i+=20;
lock2=getbitu(rtcm->buff,i, 7); i+= 7;
cnr2 =getbitu(rtcm->buff,i, 8); i+= 8;
if(!(sat = satno(SYS_GPS,prn))) {
trace(WARN,"rtcm3 1004 satellite number error: sys = %d, prn = %d\n",SYS_GPS,prn);
continue;
}
if((index = obsindex(obs_rec,sat)) < 0) continue;
//解析观测数值 L1
pr1 = pr1*0.02 + amb * PR_UNIT_GPS;
if (ppr1 != 0xFFF80000) {
if(code1) obs_rec->obs[index].P[0] = pr1;
else obs_rec->obs[index].C[0] = pr1;
cp1 = adjustcp(rtcm,sat, 0, ppr1*0.0005 / LAMDA[0]);
obs_rec->obs[index].L[0] = pr1 / LAMDA[0] + cp1;
}
obs_rec->obs[index].LLI[0] = lossoflock(rtcm,(prn<40)?prn:prn-80,0,lock1);
obs_rec->obs[index].S[0] = (unsigned char)cnr1;
//解析观测数值 L2
if (pr21 != 0xFFFFE000) {
if(code2) obs_rec->obs[index].P[1] = pr1 + pr21*0.02;
else obs_rec->obs[index].C[1] = pr1 + pr21*0.02;
}
if (ppr2 != 0xFFF80000) {
cp2 = adjustcp(rtcm, sat, 1, ppr2*0.0005 / LAMDA[1]);
obs_rec->obs[index].L[1] = pr1 / LAMDA[1] + cp2;
}
obs_rec->obs[index].LLI[1] = lossoflock(rtcm,(prn<40)?prn:prn-80,1,lock2);
obs_rec->obs[index].S[1] = (unsigned char)cnr2;
}
rtcm->obsflag = !sync;
return sync?0:1;
}