使用RTL

您所在的位置:网站首页 ais软件船舶 使用RTL

使用RTL

2024-07-17 00:33:30| 来源: 网络整理| 查看: 265

文章目录 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,");"))

excel 复制类的定义表到Excel,就可以直接拼接出. 这也是我们设计自定义的int2string, float2string, array2string的意义,这样可以尽可能契合原始的类型。

2.4 生成项目ais2text

有了上述步骤,用了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


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭