基于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
则:

  1. 认证信息 :mountPoints=//xxxxxxx:xxxxxxx@
  2. 主机及其端口 :ntrip.gnsslab.cn:2101
  3. 协议 :RTCM_3.0
  4. 测站(挂载点) :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;
}
  • 10
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
### 回答1: 最新版的ntrip协议是2021年的rtcm。这个协议是专门用于实时差分定位的。Ntrip是一种网络传输技术,它可以将基准站实时的差分数据通过互联网传输到移动设备上,从而实现实时定位。这个协议是由RTCM推出的,它包含了实时流式传输协议(RTSP)和实时传输协议(TCP)等技术,使得用户可以快速、准确地获得差分数据。Ntrip-RTCM协议包括四部分:差分信号传输部分、数据压缩和编码部分、数据解码和还原部分、以及数据传输和接收部分。通过这些组件,用户可以获得高精度的定位信息。 Ntrip协议具有一些重要的特点,例如高速和高精度的数据传输、安全可靠的数据传输、大量的基准站和客户端设备支持、易于操作的软件界面等。Ntrip协议具有广泛的应用范围,例如地理测量、地图制作、卫星导航、遥感图像处理等领域。同时,Ntrip协议在航空、航海、水利、气象等领域也得到了应用。总之,Ntrip协议是一种高科技、高性能的协议,它将为各行各业提供更准确、更可靠的定位服务。 ### 回答2: 最新版的NTRIP协议是2021年推出的,它是实时运输协议(RTCP)中用于差分GPS校正的一种协议。NTRIP协议可以让多台收发器同时接收卫星信号进行定位,同时通过网络上传差分信号,从而提高定位的精度和可靠性。 该协议使用的是实时传输协议(RTP)来传输GPS差分数据,通过网络连接将数据传输到使用者,具有较高的实时性和稳定性。NTRIP协议可以在不同的网络环境下使用,包括Internet、局域网甚至是无线局域网,可以与不同的GPS接收器进行通信,兼容多种数据格式,具有很好的通用性。 NTRIP协议的使用可以有效提高GPS的定位精度,尤其是在建筑物、森林等高影响区域的精度提高非常显著。此外,NTRIP协议的推出也开启了不同领域间的通信,使得GPS信息可以更广泛地用于农业、港口、城市规划等各个行业。 总之,NTRIP协议是一种高效的GPS差分校正协议,其不断更新的版本也使其与更多的设备和网络环境兼容,并不断提升其实时性和安全性。随着技术的不断发展,相信NTRIP协议将继续为我们带来更加丰富、高效的定位技术。 ### 回答3: 最新版的NTRIP协议是指2021年发布的RTCM(无线电技术委员会)协议,它被用于在全球范围内的差分GNSS(全球卫星导航系统)定位服务中。NTRIP是一种实时差分技术,它通过基站网络传输GNSS信号到用户设备,使用户可以获得高精度的位置信息。 与较早版本不同,2021年的RTCM协议提供了更加实用和强大的功能。其中一个新功能是多GNSS系统支持。这意味着用户现在可以同时使用多个卫星系统的数据,例如GPS、GLONASS、Galileo和Beidou等。这使得位置数据更加精确和可靠。另外,RTCM 2021增强了对RTCM 3.3版本的支持,包括支持“要素”消息和新的扩展解决方案。 除了新的功能外,最新版的NTRIP协议提供了更好的兼容性和可靠性。它保证了不同设备和网络之间的兼容性,并且可以更好地处理错误和跳过不必要的数据包。这大大提高了用户获取位置数据的成功率和精度。 总的来说,最新版的NTRIP协议提供了更强大的功能和更好的兼容性和可靠性,这些都是全球差分GNSS定位服务中至关重要的特征。它的应用将可以更好地满足不同行业对高精度位置数据的需求,例如地球物理勘查、建筑和农业等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值