海事信任度:驾驶台能信任这个定位解吗?
对于移动的船舶,kshana receiver-trust可以根据任何接收机已放到总线上的内容(首先是NMEA 0183),为每个位置定位解给出实时的0-100信任度数字,并指出是哪些检查拉低了它。它是位于接收机旁边的软件层。它不是接收机,不能取代接收机,也不会使定位解更精确。
仅供参考的软件。Kshana不是经型式认可的导航设备(IEC 61108、IEC 61162),也未对照任何性能标准评估。其分数和门控只是辅助领航员判断的工具。船舶安全航行的责任,以及是否依赖或停止依赖某一定位解的任何决定,仍由操作人员承担。
本页说明这些检查看什么、各自能抓到什么和抓不到什么、如何实时运行,以及门控做什么。分数的映射和每个默认值见RECEIVER-TRUST.md。
读取的内容#
船舶在会话文件中声明,其限值在运行前已说明:
[platform]
kind = "vessel"
max_speed_kn = 20.0 # includes any current you want to allow for: it limits speed over ground
max_accel_mps2 = 0.2
max_turn_rate_dps = 2.0
antenna_height_m = 18.0 # antenna above the waterline
heading_sensor = true # the stream carries a gyro or compass heading
| 语句 | 读取的内容 |
|---|---|
| GGA | 位置、定位质量(0 = 无定位解)、平均海平面以上高度、大地水准面差距、HDOP |
| RMC | 状态(A有效,V无效)、对地速度和航向、日期、模式 |
| VTG | 对地速度和航向,在RMC没有时使用 |
| HDT、THS | 艏向(THS仅在其模式不是V时) |
| VHW、VBW | 对水速度(VBW仅在其状态为A时) |
| ZDA | 时间 |
| GSV | 每颗卫星的信号强度(dB-Hz)、仰角和方位角(用于几何) |
| GSA | 精度因子和所用卫星(用于几何,在GSV未给出天空时使用;以及保护检查所比较的声明HDOP) |
$PKSOS,<A/F/N> |
接收机报告的OSNMA状态,由读取该接收机的适配器翻译成这条语句:A认证通过,F失败,N无结果 |
接收机自己说明不是卫星定位解的历元(GGA质量0,或6至8:估算、手动、模拟器;RMC状态V,或模式E、M、S、N)不用于位置检查,也不提供任何方向的证据:接收机已经这样说明了。
最初calibration_s秒构成基线,从不评分。每个监测器只看当前和更早的历元,因此在某个历元作出的判定不会因后来的数据到达而改变,实时流给出的分数与把同一文本作为文件读取所得相同。
每项检查能抓到什么、抓不到什么#
定位解之间的方向(转向率检查所用的航迹方位、由两个位置隐含的航向)是它们之间弦的方向,即定位解之间测地线的平均方位角,而不是第一个定位解处的前向方位角;二者相差约为定位解之间子午线收敛角的一半(在纬度59.5度、基线260 m时为0.001度)。
伪造的位置必须与船舶的物理特性以及船舶的其他传感器一致。每项检查把定位解与其中一个比较。检查的独立程度只取决于它的另一个来源:来自GNSS罗经的艏向,或由GNSS推导的“对水速度”,并不独立于被测接收机,也不提供任何额外信息。
| 检查 | 能抓到 | 抓不到 |
|---|---|---|
kinematic |
位置阶跃;所报速度和航向无法解释其移动的航迹;隐含的速度、加速度或转向率超过所声明的船舶限值 | 拖引时所报速度和航向跟随伪造航迹并保持在限值之内(接收机由自己的解算推导它们,所以它们彼此一致);声明得过高的速度限值 |
heading-course |
偏离船首指向的程度超过所声明的偏航角的航迹 | 沿航向的拖引(航向不变);来自同一接收机的艏向或GNSS罗经;超过容差的横向流(虚警);低于min_speed_kn的速度 |
speed-log |
使对地速度相对于对水速度发生变化的拖引 | 由GNSS推导的计程仪;超过允许值的海流(虚警);比允许值更慢的拖引 |
sea-level |
与海面和所声明的天线高度所决定的位置不符的高度 | 使高度保持合理的欺骗干扰源;容差很宽,因为单点垂直误差大;大地水准面差距有记录但不检查,因为引擎中没有大地水准面模型 |
cn0-spread, cn0-rise |
单个发射机的特征:各卫星的C/N0相对于校准基线一同崩塌或一同升高 | 对每颗卫星分别调整功率的欺骗干扰源;基线本身遭到欺骗干扰时的任何情形;慢到跟不上的GSV周期 |
time-consistency |
接收机时间不规则跳变或倒退;相对于本计算机时钟,接收机时间发生漂移(仅限实时流) | 一致且平稳的伪造时间;一开始就错的时钟 |
osnma |
报告的OSNMA认证失败 | 状态是被读取的,没有被验证;抑制该报告的欺骗干扰源看不到 |
cn0-drop, loss-of-lock, agc, jam-ind |
功率被压制和卫星丢失,与静态情形相同 | 它们说明环境是恶劣的,而不是说明定位解有误 |
仅用NMEA的监测器做不到的事。能复现整个星座,并带有自洽的位置、速度、与航向相符的航向、合理的每颗卫星功率和一致的时钟的欺骗干扰源,对上面每项检查都是不可见的:语句中没有任何东西能把它的定位解与真实定位解区分开。对已录制的真实信号的重放同样如此。认证(OSNMA,或带有自身抗欺骗能力的接收机)和独立的导航传感器,才是弥补这一缺口的手段;这一层只能使用它们报告的状态。在基线形成期间就已遭到欺骗干扰的流没有干净的基线。没有告警并不能证明定位解良好。
信任度分数#
校准之后的每个历元得到一个从0到100的分数,以及它不是100的原因:哪些检查扣了分、其统计量相对门限的情况及其扣分。频段(边界在会话中声明):90及以上为nominal,55及以上为degraded,低于此为untrusted。映射、每个权重及其理由见RECEIVER-TRUST.md;它是确定性的,在日志评分之前就已固定,没有学习,也没有拟合任何事件。
每个船舶输出都带有咨询声明:结果JSON和每条实时行的advisory键、CSV的第一行#注释行(读取方可用其注释选项跳过它)、SVG的<desc>和页脚。批量运行的输出:session.result.json(含分数模型和每个历元的扣分)、session.trust.csv(含score和score_reasons列)以及session.trust.svg,对船舶而言,它是按信任度频段着色的接收机报告航迹,其上方为分数随时间的变化。
合成演示#
examples/maritime-trust/中有一份虚构的NMEA日志,记录一艘塔林至赫尔辛基航线上的渡轮(一段50分钟的摘录,约17 kn,1 Hz:GGA、RMC、VTG、陀螺艏向、计程仪、带合理天空的GSV),其中从1500 s起,位置拖引使所报位置偏离船舶的真实位置。接收机全程持续报告有效的定位解;下降的是信任度分数,它穿过降级频段进入不可信频段。session.toml在运行之前就说明了每个门限、船舶的限值和每个分数权重。
kshana receiver-trust examples/maritime-trust/session.toml
kshana receiver-trust live examples/maritime-trust/session.toml \
--file examples/maritime-trust/tallinn-helsinki.nmea --gate --json trust.jsonl > gated.nmea
该日志是由cargo run --example gen_maritime_trust_demo 写入文件的文本:它不对任何无线电信号建模,也不发射任何东西。它不是测量,航线是示意性的,而不是海图。tests/receiver_trust_maritime_demo.rs把该日志和预期输出作为合成数据上的回归防护固定下来;它们对任何监测器在真实干扰下的表现不作任何说明。
该图由tools/gen_readme_assets.py根据本演示的一次真实运行(kshana receiver-trust examples/maritime-trust/session.toml)绘制;分数仅供参考,日志是虚构的。
Studio视图规范#
供构建船舶运行的Studio视图的人参考。本节中的内容在Studio中均尚未实现;引擎一侧(下面的数据)已经实现。
输入。以下任一种:
- 批量运行的结果JSON(
kshana receiver-trust <session.toml>,或在WebAssembly包中通过receiver_trust(toml),日志内联):score_model(频段边界、斜坡、证据保持、每个权重)、monitors_run、states和epochs[],其中每个都带有t_s、state、alarms、score.{score,band,deductions[]}、marine.{position:[lat,lon], stats, ratios},并且自0.36起还有geometry以及每项扣分的class,顶层带有geometry_model和geometry_summary;或者 - 实时JSON行(模式见上),每个历元一个对象,随流运行而追加。每一行都带有接收机报告的
position(模式1.1)。
演示集为examples/maritime-trust/:session.toml、tallinn-helsinki.nmea(日志)和tallinn-helsinki.truth.csv(t_s,true_lat_deg,true_lon_deg:船舶的真实位置,真实日志中不会有这一项)。视图应当运行该会话,并同时读取真值文件。
绘制内容。
- 航迹,上北下南,两轴采用同一比例尺:接收机报告的位置绘成一条线,按该历元所处的分档着色(灰色为校准中,绿色为nominal,琥珀色为degraded,红色为untrusted),标出起点和终点,并附比例尺。对于演示,其下以一条细的中性色线绘出真实航迹,使偏离拖拽表现为两条线逐渐分开;提供真实航迹的开关,以及光标所在时刻的偏移量(单位为米)读数。
- 得分时间线:得分从0到100随时间变化的曲线,分档边界(
nominal_min、degraded_min,取自score_model)以虚线表示,其下方以色带表示所处分档,并以标记标出任何已声明事件的起始。时间轴与航迹共用。 - 原因:针对光标所在的历元,将各项扣分(监测器、相对阈值的比值、扣分数)以横向条形图按从大到小排列,并标出各监测器依据
score_model.weights所占的权重;已运行但未造成扣分的监测器以淡色显示,未运行的监测器(不在monitors_run中)列为“未运行”,绝不当作通过。geometry监测器在score_model.weights中没有条目:其上限为geometry_model.cap_in_force。 - 监测器条带:对
monitors_run中的每个监测器各画一条细条带,显示其比值随时间的变化(在1处告警),使读者可以看出哪项检查最先出现变化。 - 门控状态:在实时视图中,于分档色带之下以条带显示各历元的
gate(off、passed、withheld),并显示最新的$PKSHT。在批处理视图中,门控本会扣留的历元是分档为untrusted且已计入释放保持时间的那些历元。 - 几何通道(0.36):位于分档色带之下的一条条带,按
geometry.status着色(绿色为良好,琥珀色为degraded,灰色斜线填充为未知),所用色系有别于不一致通道和环境通道,并附图例“几何:天空,而非信号”。在原因面板和监测器条带中,扣分按class(几何、环境、不一致)着色,各类别的历元计数(取自geometry_summary)显示在分档计数旁。对于光标所在的历元,显示:geometry.note、n_used、HDOP、PDOP、VDOP、source、age_s,以及在设置了reported_derived_mismatch时的一个标志(此时几何扣分属于不一致)。unknown显示为“几何未知:日志中没有GSV或GSA”,绝不显示为良好。[geometry]的阈值及其来自geometry_model的出处,放在得分设置旁。咨询性说明保持不变。 - 接收机报告的定位解:演示中的接收机全程报告有效定位解(GGA质量1,RMC状态A);应在图例中说明这一点,因为这正是该视图要表达的要点。
交互。所有面板共用一个时间光标(可拖动、方向键移动,以及以1x至60x回放的播放按钮);悬停在航迹或任一条带上会移动该光标。点击某项扣分,会高亮该监测器超过其起始阈值的各个历元。提供分档筛选(仅显示degraded和untrusted的历元),以及跳转到首次扣分和首个untrusted历元。每个监测器名称上的悬停文字给出本页中的一行说明。该会话的阈值和权重以只读方式显示在图表旁,使得得分所用的参数一目了然。
须保留的措辞。页面必须说明:这是仅供参考的软件,不是经型式认可的导航设备;演示是合成文本,其中的偏离拖拽是编造的;没有告警的定位解并不因此就是良好的定位解(见上文的局限)。页面上的任何数字都不应被读作这些检查在真实干扰下表现如何的度量。
通过代码#
对于在内存中持有会话和字节数据的调用方(无文件、无时钟、无套接字;对WebAssembly安全),kshana::receiver_trust::assess提供:
assess_vessel_log(session_toml, log_bytes) -> Result<ReceiverTrustResult, String>:对整份日志按批处理运行打分;其结果与kshana receiver-trust <session.toml>相同,序列化为结果JSON(score_model、monitors_run,以及带有得分和扣分的epochs[])。assess_stream_excerpt(session_toml, excerpt_bytes) -> Result<ExcerptAssessment, String>:对数据流中有限的一段(至多2 MiB和20,000个历元,且长度足以容纳校准窗口)按实时模式的方式打分,并以实时JSON-lines模式返回(epochs[]、最后一个$PKSHT、分档计数摘要、最低分和最终得分、首次untrusted的时间)。接收机的时间不与主机时钟比较。长时间运行的实时模式(套接字、持续追加的文件、门控)由可执行程序提供。
两者都需要含有[platform] kind = "vessel"的会话;其[log]表是可选的。
实时运行#
# stdin: a serial port read by another tool, or a multiplexer's output
some-nmea-source | kshana receiver-trust live session.toml
# a log file that is being appended to (it starts at the beginning; --from-end skips what is there)
kshana receiver-trust live session.toml --file /var/log/nmea.log --follow
# a TCP server (a multiplexer or a terminal server) or UDP datagrams (a bridge network)
kshana receiver-trust live session.toml --tcp 192.0.2.10:10110
kshana receiver-trust live session.toml --udp 10110
该会话与批处理运行使用的session.toml相同,其中有一个声明kind = "vessel"的[platform]表;不需要[log]表。目前尚不直接打开串口:请用你已在使用的工具(stty和cat、终端服务器、多路复用器)读取该端口,再通过管道或转发送入。直接串口输入以后可以提供。
每个完成的历元在标准输出上写出一行JSON:
{"seq":941,"t_s":940.0,"time":"2025-06-14T08:15:40.000Z","state":"degraded","score":60.0,
"deductions":[{"monitor":"heading-course","ratio":1.5,"points":40.0}],
"alarms":["heading-course"],"gate":"off","note":null,
"position":{"lat_deg":59.8123456,"lon_deg":24.9012345,"height_m":39.4}}
state取值为calibrating、nominal、degraded或untrusted(得分所处的分档,边界在会话的[score]表中);校准期间没有score;deductions列出哪些检查扣了分以及扣了多少,按从大到小排列。
JSON-lines模式(版本1.1)#
每个完成的历元输出一个JSON对象,键按此顺序排列:
| 标注 | 类型 | 含义 |
|---|---|---|
seq |
整数 | 已报告的历元计数,从1起 |
t_s |
数值 | 自数据流第一个历元起的秒数 |
time |
字符串或null | 数据流所述的该历元时间(见到RMC之后为带日期的ISO-8601 UTC,否则为hh:mm:ss.mmm UTC (date not in log)) |
state |
字符串 | calibrating、nominal、degraded或untrusted |
score |
数值或null | 0到100,保留一位小数;校准期间,或当note说明无法给出得分的原因时,为null |
deductions |
数组 | {"monitor": string, "ratio": number, "points": number, "class": string},按扣分数从大到小排列;ratio是统计量与阈值之比(达到1或更高即告警);只列出造成扣分的监测器;class (在1.3中新增)取值为geometry、environment或inconsistency |
alarms |
字符串数组 | 达到或超过其阈值的监测器 |
gate |
字符串 | off、passed或withheld |
note |
字符串或null | 未能给出得分的原因(例如已声明的航向传感器缺失) |
position |
对象或null | (在1.1中新增)接收机在该历元报告的位置,{"lat_deg", "lon_deg", "height_m"}(语句给出大地水准面差距时,高度为椭球高,否则为平均海平面以上高度);接收机没有给出位置时为null,包括没有定位解的校准历元 |
advisory |
字符串 | (在1.2中新增)本软件是什么、不是什么:仅供参考,不是经型式认可的导航设备,操作人员仍须负责 |
geometry |
对象 | (在1.3中新增)卫星几何:status(good、degraded、数据流中没有GSV仰角和方位角且没有GSA时为unknown)、reasons(few-satellites、high-pdop、high-hdop、sky-blocked)、source(derived、reported、none)、n_used、hdop、pdop、vdop、open_fraction、max_azimuth_gap_deg、age_s、reported_hdop、reported_derived_mismatch、note、in_view、signal_loss(可见但未跟踪的卫星,而此时可见卫星的DOP良好:几何扣分此时属于environment)、label(good、fair、poor、unknown:始终报告,从不计分;得分只对poor扣分)(见RECEIVER-TRUST.md) |
监测器名称(kebab-case;17个):cn0-drop、agc、jam-ind、loss-of-lock、position-jump、raim、clock、solve-failure、kinematic、heading-course、speed-log、sea-level、cn0-spread、cn0-rise、time-consistency、osnma,以及自1.3起的geometry。后续版本可能新增名称;使用方应将未知的监测器名称当作一个监测器处理。下方的$PKSHT格式在其第一个字段中带有格式版本;本JSON模式为版本1.3:1.1增加了position,1.2增加了advisory,1.3在末尾增加了geometry,并在每项扣分中增加了class;模式只通过在末尾增加键来变更。
认证输入#
osnma监测器通过MarineObs::sat_auth(一个(satellite, status)的列表,状态为Authenticated、Failed或Unavailable)从认证来源获取各卫星的认证状态,同时还有整体的MarineObs::osnma。在NMEA一侧,同样的信息由$PKSOS,<A|F|N>[,<sat>:<A|F|N>...]携带,例如$PKSOS,A,E11:A,E19:F。任何失败,无论是整体的还是某一颗卫星的,都会使该监测器告警并扣除其全部权重(其比值取1.5);成功不构成证据。Kshana在此不验证任何内容:进行验证的来源要对其报告的状态负责。
下一条带时间的语句到达时,一个历元即告完成;速度较慢或不同步的仪器的语句(例如比接收机晚零点几秒的陀螺仪)若在此之前到达,则归入该历元。如果数据流停顿达[live] idle_flush_s(默认1.5秒,应将其设为高于接收机带时间语句之间最长间隔),该历元即按现状完成。数据流的前calibration_s秒构成基线,从不计分,因此一个在首次连接时就已遭受欺骗干扰的数据流没有干净的基线:请在航行之前,或在已知定位良好时启动该层。校准窗口按到达顺序只关闭一次:时间早于已见过的时间的历元(回退、重放数据流开头)无论其时间如何标注,都绝不属于校准;它以完整的time-consistency扣分打分,结果为untrusted。实时引擎对校准历元和数据流最近一分钟左右的数据为每个历元打分,因此每个历元的开销随calibration_s乘以历元速率而增长;在快速接收机上请保持窗口较短。其缓冲区有上限(一连串没有带时间语句的行,每5,000行即告完成,校准和历史数据上限为20,000个历元)。
接收机的时间还会与本计算机的单调时钟进行比较,这只对实时到达的数据流才有意义。以全速经stdin、TCP或UDP送入的已存储日志,必然会触发该检查:请传入--replay。(不带--follow的--file已隐含此项。)--file ... --follow在读到末尾之前按重放处理,此后才启用主机时钟,因此积压的数据不会被当作时钟故障;此后该检查要求校准窗口仍处于打开状态(否则它没有基线,也就不运行)。
数据流的结束与故障:读到末尾的stdin和文件正常结束(退出码0)。对于本应持续下去的数据流,TCP连接被对端关闭并非正常结束:该层在对已收到的全部数据完成打分并写出后以非零状态退出,且不会重新连接,因此应在会重启它的守护程序下运行。将一条语句拆开的UDP数据报会被拼接;stdin、文件和TCP的读取端由有上限的队列支撑,会向发送方施加反压(不会丢失任何数据),而无法施加反压的UDP则会丢弃引擎无法接收的数据报,并在stderr上说明。--udp <port>绑定到环回地址;要在其他地址监听,请给出--udp <addr>:<port>(会打印一条警告)。
$PKSHT:专有语句#
每个历元还会生成一条带有效校验和的NMEA 0183语句,由--pksht <path|->写入文件或标准输出,并由门控插入转发的数据流中:
$PKSHT,1,080140.25,30.0,U,W,heading-course:40.0/cn0-spread:30.0*2F
| 字段 | 含义 |
|---|---|
| 1 | 1:格式版本 |
| 2 | 该历元的UTC时间,hhmmss.ss;数据流未给出时为空 |
| 3 | 信任得分0到100,保留一位小数;校准期间或无法给出时为空 |
| 4 | 分档:C校准中,N nominal,D degraded,U untrusted |
| 5 | 门控:-关闭,P已放行,W已扣留(转发的周期中该定位解被标记为无效) |
| 6 | 至多两项原因,monitor:points,以/连接;没有扣分时为空 |
地址为P + 制造商助记符KSH + T。不识别专有语句的设备会忽略它。Kshana在输入时会忽略自己的$PKSHT。
门控模式#
--gate将Kshana置于接收机与使用其定位解的设备(海图系统、航迹控制系统)之间。NMEA数据流输入后从标准输出送出,并且:
- 当该历元不是untrusted时(以及在校准期间)原样放行,并在每个语句周期之后附上一条
$PKSHT;并且 - 当其为untrusted时标记为无效:GGA质量
0;RMC状态V(以及其模式指示符N和导航状态V,若存在);每个位置中的GNS模式N;GLL状态V和模式N;VTG模式N(若其带有该字段)。位置字段保持原样:关键在于让设备看到接收机自己声明该定位解无效,并自行发出告警。每条被改写的语句都会重新计算校验和。
一旦被扣留,只有在各历元已离开untrusted档达[live] gate_release_s(默认30秒)之后,定位解才会被放行,因此处于分档边缘的得分不会使定位解来回闪烁。JSON行只输出到--json <path>指定的位置(默认哪里都不输出,因为标准输出被数据流占用)。
receiver-nmea-source | kshana receiver-trust live session.toml --gate --json trust.jsonl | downstream-equipment
若要让海图绘图仪直接连接,请通过TCP而不是标准输出提供经门控的数据流:
receiver-nmea-source | kshana receiver-trust live session.toml --gate --listen tcp:10110
--listen tcp:<port>在环回地址上监听;tcp:<addr>:<port>在其他地址监听(会打印一条警告,因为任何能访问该地址的人都能读取该数据流)。可以连接任意数量的客户端;每个客户端从连接的那一刻起获得相同的数据流,且客户端只读。每个客户端都有自己的有上限的队列和写入端,因此跟不上的客户端会被丢弃(连接关闭,并在stderr上输出一行),绝不会拖慢其他客户端或输入。--listen需要--gate;此时标准输出不再承载数据流的任何内容,因此--json -可以使用。
门控不做什么,以及使用之前需要了解什么:
- 它是选择启用的。除非给出
--gate,否则不会有任何定位解被标记为无效。 - 它会增加延迟:一个周期要保持到其历元打分完成,即等到下一条带时间的语句(约一个周期:对1 Hz的接收机为一秒),或在数据流停顿时,在最后一条之后
idle_flush_s。需要在接收机输出后几十毫秒内收到语句的设备,不得放在它的后面。 - 它是链路中的单点:它一旦停止,数据流就随之停止。请在守护下运行,并事先确定下游设备在没有数据流时如何处理。
- 它对自身的故障按失效关闭处理:如果监测器在校准之后无法给出得分(已声明的航向传感器不发送任何数据、校准历元过少),该历元为untrusted,带有
note,并被扣留;释放保持按本计算机的时钟计时,而不是按接收机的时间,因为后者正是欺骗者所能控制的(对于重放,则只有接收机的时间)。 - 它既可能扣留良好的定位解(虚警),也可能放行被欺骗的定位解(漏检)。监测器依据船舶的物理规律及其其他传感器来检查定位解;与这些全部一致的伪造信号是看不出来的(见下文的局限)。
- 它期望的是纯NMEA 0183文本数据流。混入数据流的二进制帧会原样转发,但此时
$PKSHT可能落在某个帧的字节之间。 - 它不是经型式认可的,其中没有任何内容依据IEC 61108或IEC 61162进行过评估。船舶航行的责任仍由操作人员承担。