使用RTL |
您所在的位置:网站首页 › ais软件船舶 › 使用RTL |
文章目录
1. AIS接收1.1 学习开源项目 RTL-AIS1.2 改造RTL-AIS为recv_ais
2. AIS字符串解释2.1 学习开源项目 libais2.2 改造设计虚函数2.3 Excel批量生成代码2.4 生成项目ais2text
3. 把所有功能串起来3.1 USRP项目3.2 rtlsdr项目
4 溜达溜达5. 完整发布包
在业余软件无线电领域,比较入门的玩家也能通过
SDRAngel等成品软件收听广播、接收飞机、船舶的位置。但是,这些软件对具体的算法的封装往往很深,编译起来依赖项巨多,让我们搞不清楚究竟是怎么实现的。很多软件是Python底下又来C++、C,地图还是Web的,从本地Native到浏览器,跨越了好多层工具链和中间件,想庖丁解牛搞清楚这些船的图标是怎么从空中蹦跶到屏幕的,关联学习成本太高(算法、C/C++/Python/后端、JS、前端GIS库)。
很久之前就打算只使用最基础的C/C++语言,从驱动SDR设备到绘制出船舶的图标,完全依赖本博客系列文章的知识全栈实现AIS的接收、绘图。但我家边上没有运河,压根收不到AIS消息,所以,这个尝试一直没做。六月底,去亲戚家避暑,来到浍河北岸,终于搞定。 1. AIS接收AIS 内容是一串经过编码的字符串,一般不长,就几十字节。第一步是从空中捕获这些字符串,并打印出来。 1.1 学习开源项目 RTL-AIS既然是要接收AIS,当然先要学学最基础的代码。RTL-AIS是使用C语言实现的AIS接收解调开源项目,很纯粹。 $ git clone https://github.com/dgiardini/rtl-ais.git克隆到本地,在msys2环境里(本人选择ucrt64)首先安装必要的库(RTL-SDR)。 在这一步,要吐槽非一线城市github根本打不开。这也是为什么我支持国内Git平台的原因,想clone和fetch成功堪比抽大奖。不要说Ladder,这种东西普通人最好不要以身犯险。其实买一个云服务器,一般都能上。 $ pacman -S ucrt64/mingw-w64-ucrt-x86_64-rtl-sdr而后进入克隆的文件夹编译 $ mingw32-make编译好后,插入两百元的RTL-SDR,即可接收AIS: $ ./rtl_ais.exe -? rtl_ais, a simple AIS tuner and generic dual-frequency FM demodulator (probably not a good idea to use with e4000 tuners) Use: rtl_ais [options] [outputfile] [-l left_frequency (default: 161.975M)] [-r right_frequency (default: 162.025M)] left freq return;} pthread_rwlock_wrlock(&ctx->both.rw); //这就是关键,每次收到的样点,都进来了。 for (i=0; iboth.buf[i] = ((int16_t)buf[i]) - 127; pthread_rwlock_unlock(&ctx->both.rw); safe_cond_signal(&ctx->ready, &ctx->ready_m); } static void *rtlsdr_thread_fn(void *arg) { struct rtl_ais_context *ctx = arg; rtlsdr_read_async(ctx->dev, rtlsdr_callback, arg, DEFAULT_ASYNC_BUF_NUMBER, DEFAULT_BUF_LENGTH); ctx->active = 0; return 0; }当RTLSDR被配置为1.6MHz采样率后,在异步函数rtlsdr_thread_fn的驱动下,会源源不断的通过上面的代码回调 rtlsdr_callback,传入样点。更值得注意的是,ctx->both.buf[i]竟然是short的,这非常便于后续把这个程序从对RTLSDR的依赖上剥离开来,嫁接给 USRP B210。 同时,注意到:-n 参数允许把接收结果,也就是AIS 字符串输出到stderr,这使得该程序可以直接不经任何改造就加入为taskBus 业余SDR的模块。 我们替换上述代码为从 stdin 输入,即可剥离对 RTLSDR的依赖并适配给更高阶的业余SDR平台(使用short类型而不是uint8): static void deal_int16(short *buf, uint32_t len, void *arg) { struct rtl_ais_context *ctx = arg; unsigned i; if (!ctx->active) { return;} pthread_rwlock_wrlock(&ctx->both.rw); for (i=0; iboth.buf[i] = buf[i]; pthread_rwlock_unlock(&ctx->both.rw); safe_cond_signal(&ctx->ready, &ctx->ready_m); } static void *rtlsdr_thread_fn(void *arg) { struct rtl_ais_context *ctx = arg; char * recvBuf = malloc(sizeof(short) * DEFAULT_BUF_LENGTH * 2); while(ctx->active) { int red = fread(recvBuf,2,DEFAULT_BUF_LENGTH,stdin); if (red) { deal_int16((short *)recvBuf,red,arg); } } free(recvBuf); ctx->active = 0; return 0; }通过从stdin输入数据,可以采用管道串联传递数据,进而和具体的硬件平台去耦合。相关代码参考代码仓库“recv_ais”。 2. AIS字符串解释经过上一步,输出的是一些看不懂的字符串。这些字符串其实用的是6比特可读字符格式,最初应该是用于一些纯文本的通信线路,比如电传打字机或者老派的网络协议。这些东西究竟是啥意思呢,如何还原里面的经纬度呢?请出万能的AI问一问,结果给我了一个libais的链接。这个库实现了完整的AIS的解析。 2.1 学习开源项目 libais git clone https://github.com/schwehr/libais.git进入文件夹,发现这个东西很容易编译,直接用CMake即可。但编译出来,是一个libais.a的库,不是我们想要的输入一段文本就吐出结果的东西。同时,这个库充分体现了C++作为静态语言的问题,即缺少自省,使得它用起来非常难受。 可以看看 https://github.com/schwehr/libais/blob/master/src/libais/ais_py.cpp 这个文件,好几千行,把每一个字段都枚举了一遍。就是因为缺少动态语言的特性,且没有很好的设计这个库。作者虽然为每个消息类别安排了对应的子类,但没有使用统一的接口来访问这些类。通过消息类型ID,要显式地转换到正确的子类指针获取元素。当然,在运行时也无法获取这个类有几个成员变量,都叫什么名字,因此索性在代码里列举一下。 其实对于这种Protocol Tree,一种MessageType对应好多派生的子类型,可能采用带虚函数的继承树,设计统一的一个Python接口就行了,这样可以用基类的指针来操作所有消息类型,而不是在外部为每个消息类型都手写一遍。 题外话:拜服作者,真爆肝啊!本来以为是机器自动化写的,结果发现几个消息类型的一些笔误和格式的随意性,更像是爆肝的。 2.2 改造设计虚函数搞清楚了类的结构,发现要驱动这个库很简单,但是要自动化的展开各个字段,我可不想爆肝。只能动手术了。首先,维持类树不变,并为顶层祖宗基类给出一个接口: typedef std::map aisdict; class AisMsg { public: virtual aisdict values( aisdict super = aisdict()) const { std::map res(std::move(super)); res["message_id"] = int2string(message_id); res["repeat_indicator"] = int2string(repeat_indicator); res["mmsi"] = int2string(mmsi); return res; }这个接口是把当前协议类里的字段追加到一个字典里。对新的C++,返回复杂类型会触发右值传递,这样做性能问题不大。int2string 是一些简单类型到string的转换,没有使用std::to_string,是出于用WPS/Excel自动化生成代码的考虑。 每个子类,都要重载上述函数,在父类的基础上叠加输出。比如: class Ais1_2_3 : public AisMsg { public: int nav_status; bool rot_over_range; int rot_raw; float rot; float sog; // Knots. int position_accuracy; AisPoint position; float cog; // Degrees. int true_heading; // TODO(schwehr): What about a leap second when timestamp 60 may be valid? int timestamp; int special_manoeuvre; int spare; bool raim; // COMM state SOTDMA msgs 1 and 2 int sync_state; // SOTDMA and ITDMA bool slot_timeout_valid; int slot_timeout; // Based on slot_timeout which ones are valid bool received_stations_valid; int received_stations; bool slot_number_valid; int slot_number; bool utc_valid; int utc_hour; int utc_min; int utc_spare; bool slot_offset_valid; int slot_offset; // ITDMA - msg type 3 bool slot_increment_valid; int slot_increment; bool slots_to_allocate_valid; int slots_to_allocate; bool keep_flag_valid; bool keep_flag; // 3.3.7.3.2 Annex 2 ITDMA. Table 20 Ais1_2_3(const char *nmea_payload, const size_t pad); aisdict values(aisdict super = aisdict()) const override { std::map res(std::move(super)); res["nav_status"]=int2string(nav_status); res["rot_over_range"]=bool2string(rot_over_range); res["rot_raw"]=int2string(rot_raw); res["rot"]=float2string(rot); res["sog"]=float2string(sog); res["position_accuracy"]=int2string(position_accuracy); AisPoint2string(position,"position",res); res["cog"]=float2string(cog); res["true_heading"]=int2string(true_heading); res["timestamp"]=int2string(timestamp); res["special_manoeuvre"]=int2string(special_manoeuvre); res["spare"]=int2string(spare); res["raim"]=bool2string(raim); res["sync_state"]=int2string(sync_state); res["slot_timeout_valid"]=bool2string(slot_timeout_valid); res["slot_timeout"]=int2string(slot_timeout); res["received_stations_valid"]=bool2string(received_stations_valid); res["received_stations"]=int2string(received_stations); res["slot_number_valid"]=bool2string(slot_number_valid); res["slot_number"]=int2string(slot_number); res["utc_valid"]=bool2string(utc_valid); res["utc_hour"]=int2string(utc_hour); res["utc_min"]=int2string(utc_min); res["utc_spare"]=int2string(utc_spare); res["slot_offset_valid"]=bool2string(slot_offset_valid); res["slot_offset"]=int2string(slot_offset); res["slot_increment_valid"]=bool2string(slot_increment_valid); res["slot_increment"]=int2string(slot_increment); res["slots_to_allocate_valid"]=bool2string(slots_to_allocate_valid); res["slots_to_allocate"]=int2string(slots_to_allocate); res["keep_flag_valid"]=bool2string(keep_flag_valid); res["keep_flag"]=bool2string(keep_flag); return AisMsg::values(res); } };等等,有上百个子类,这不也是逐一列举的爆肝笨蛋方法吗? 别慌,由于这些子类的格式都是一样的,我们通过下面的步骤,借助excel一下子搞定。 2.3 Excel批量生成代码要让Excel能够从类的成员变量表自动生成上面的字典赋值代码,需要如下几步: ClangFormat,搞定统一缩进,让百十个类定义的成员变量的缩进一样。cpp另存为CSV,而后wps打开分列,找到定义 int para;这种列,筛选,并保留所有的定义。使用wps公式直接生成上面的代码。拷贝回原始的空白函数位置。Excel: =IF(A1="AisPoint",CONCATENATE(A1,"2string(",B1,",""",B1,"""",",res);"),CONCATENATE("res[""",B1,"""]=",A1,"2string(",B1,");"))
有了上述步骤,用了1个多小时,就搞定了。main函数通过统一的接口,解释了所有的消息, 输出字典到控制台。 当然,还要额外记忆每个船的最新位置和属性,并生成自制地图控件显示所需的语句。 #include #include #include #include #include "ais/ais.h" #include "ais/decode_body.h" using namespace std; int main() { //轮船位置缓存 std::map cache; std::map cache_time; while(1) { std::string str; cin >> str; if (str.size() cout std::map & currData = cache[mmsi]; cache_time[mmsi] = time(0); for (auto p:map_values) currData[p.first] = p.second; if (currData.find("position_lat")!=currData.end() && currData.find("position_lon")!=currData.end()) { fprintf(stderr,"source=ais;destin=geomarker;function=update_icon;icon=ais;name=ais%u;lat=%s;lon=%s;rotate=%f;smooth=1;\n", mmsi, currData["position_lat"].c_str(), currData["position_lon"].c_str(), std::stof(currData["cog"])); fprintf(stderr,"source=ais;destin=geomarker;function=update_props;name=ais%u;LABEL=%u;",mmsi, mmsi); for(auto p:currData) { fprintf(stderr,"%s=%s;",p.first.c_str(),p.second.c_str()); } fprintf(stderr,"\n"); } } } //从缓存和地图删除很久不再出来的船 std::vector delist; for(auto p:cache_time) { if (p.second + 300 |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |