跳转到主要内容

使用纯Python和asyncio实现的异步AX.25接口

项目描述

aioax25:AX.25和APRS库,基于asyncio

Test Status 3.5 Test Status 3.9 Test Status 3.10 Coverage Status

该项目的目的是实现一个易于理解的异步AX.25库,基于asynciopyserial,在纯Python中实现AX.25和APRS堆栈。

自2021-11-12起,需要Python 3.5及更高版本

我曾尝试支持3.4,但以下原因使其变得不可行

  • Python 3.8+使asyncio.coroutine过时(显然将在3.10中删除)。这意味着我需要某些API函数的coroutineasync def版本,以及必要的逻辑来“隐藏”后者以适应Python 3.4。
  • 尝试将基于生成器的协程用于单元测试中的“后台”运行证明是一件痛苦的事情。

计划继续支持Python 3.5,直到它也成为不可行(例如,如果需要类型注解)。

哪些功能工作

  • 我们可以自动将Kantronics KPC-3 TNC置于KISS模式
  • 多端口KISS TNC(已与DirewolfNWDR UDRC-II测试)
  • 我们可以接收AX.25 UI帧
  • 我们可以发送AX.25 UI帧

哪些功能不工作

  • 连接到AX.25节点
  • 接受来自AX.25节点的连接

未测试的内容

  • 除了GNU/Linux以外的平台

当前计划

目前,我的目标是满足APRS操作的需求,因为这是我现在的紧迫需求。因此,焦点放在UI框架上。

我打算编写一个核心类,它将负责一些核心的AX.25消息处理工作,并为实现APRS提供基础。

之后,我将按照无特定顺序解决以下问题

  • 连接模式操作
  • NET/ROM支持

支持的平台将是GNU/Linux,以及可能的BSD变种。我无法访问最新的苹果硬件(我的2008年款MacBook无法运行当代的MacOS X),因此我无法在该软件上进行测试,但它应该可以工作。

它可能在Windows上工作--很可能是使用Cygwin或Linux子系统。虽然我有一台Windows 7电脑,但生命太短暂了,不能在不能决定是假装自己是Linux、VMS还是CP/M的操作系统上浪费时间。该平台有大量的AX.25堆栈和工具,我将在假设补丁不会破坏事物或使代码不可维护的情况下接受补丁。

用法

这是一份关于如何在您的程序中使用aioax25的粗略指南。

创建KISS设备接口和端口

目前我们仅支持串行KISS接口(欢迎基于TCP的接口的补丁)。从aioax25.kiss导入make_device,然后创建一个实例,如下所示

    kissdev = make_device(
        type='serial', device='/dev/ttyS4', baudrate=9600,
        log=logging.getLogger('your.kiss.log')
    )

或者对于TCP连接的KISS接口

    kissdev = make_device(
        type='tcp', host='kissdevice.example.com', port=12345,
        log=logging.getLogger('your.kiss.log')
    )

(注意:如果kissdevice.example.com通过互联网传输,我建议通过VPN路由或通过ssl参数提供一个ssl.SSLContext,以便您的客户端通过服务器进行身份验证。)

或者对于子进程

    kissdev = make_device(
        type='subproc', command=['/path/to/your/command', 'arg1', 'arg2'],
        log=logging.getLogger('your.kiss.log')
    )

一些可选参数

  • reset_on_close:当请求关闭设备时,尝试向TNC发送c0 ff c0重置序列,将其放回CMD模式。
  • send_block_sizesend_block_delay:如果KISS帧的大小大于此大小,则将传输拆分为给定大小的块,并在每个块之间等待send_block_delay秒。(如果您的TNC有小的缓冲区,这可能有所帮助。)

这代表KISS TNC本身,其端口可以通过使用通常的__getitem__语法访问。

    kissport0 = kissdev[0]
    kissport1 = kissdev[1]

这些KISS端口接口只是通过它们的received信号输出原始AX.25帧内容,并通过send方法接受原始AX.25帧。传递给send的任何对象都会被bytes调用包装--这会隐式调用传递给的对象的__bytes__方法。

设置AX.25接口

AX.25接口是一个逻辑路由和队列层,它解码从KISS端口接收到的数据,并根据目的呼叫号进行路由。

AX25Interface位于aioax25.interface包中。导入它,然后按照以下步骤设置您的接口

   ax25int = AX25Interface(
       kissport=kissdev[0],     # or whatever port number you need
       log=logging.getLogger('your.ax25.log')
   )

一些可选参数

  • cts_delaycts_rand:在发送/接收传输后等待的秒数,然后我们再发送另一个传输。延迟时间是cts_delay + (random.random() * cts_rand),其想法是避免当两个站点尝试传输时加倍。

AX25InterfaceRouter的子类(请参阅aioax25.router),它公开了以下方法和属性

  • received_msg:这是一个Signal对象,对于接收到的每个AX.25帧都会触发。期望槽接收两个关键字参数:interface(接收帧的接口)和frame(AX.25帧本身)。

  • bind(callback, callsign, ssid=0, regex=False):此方法允许您将回调函数绑定到接收目的字段指向指定的呼叫号和SSID的AX.25帧。如果regex=True,则呼叫号可以是正则表达式。这将编译并匹配所有传入流量。无论regex的值如何,callsign参数必须是字符串。

  • unbind(callback, callsign, ssid=0, regex=False):此方法用于解除之前绑定的事件处理函数,使其不再接收指定的流量。

此外,对于发送帧,AX25Interface 还增加了以下功能

  • transmit(frame, callback=None):此方法允许您发送任意的 AX.25 帧数据。这些帧假定是来自 AX25Frame(位于 aioax25.frame)的实例。如果提供了 callback,则在帧发送后,将调用该函数,并传递以下关键字参数:interface(发送帧的 AX25Interface)、frame(已发送的帧)。

  • cancel_transmit(frame):此方法取消一个待发送帧的传输。如果帧已发送,则此操作无效果。

APRS 流量处理

AX25Interface 仅处理 AX.25 流量,并不对 APRS UI 帧进行特殊处理。对于此功能,可以查看 APRSInterface

aioax25.aprs 导入此功能。它也是一个 Router 的子类,因此 bindunbindreceived_msg 都存在 —— 接收到的消息将是 APRSFrame 的实例(请参阅 aioax25.aprs.frame),否则行为相同。

   aprsint = APRSInterface(
       ax25int=ax25int,         # Your AX25Interface object
       mycall='VK4MSL-9',       # Your call-sign and SSID
       log=logging.getLogger('your.aprs.log')
   )

其他可选参数

  • retransmit_countretransmit_timeout_baseretransmit_timeout_randretransmit_timeout_scale:这些参数控制发送 可确认 的 APRS 消息时重传的定时。在传输之前,计算超时时间为 timeout = retransmit_timeout_base + (random.random() * retransmit_timeout_rand),并将重试计数器初始化为 retransmit_count。在每次重传时,重试计数器递减,超时时间乘以 retransmit_timeout_scale
  • aprs_destination:此参数设置用于 APRS 流量的目标呼号。目前,我们使用实验性呼号 APZAIO 用于所有流量(直接消息除外,这些消息直接发送到指定的站点)。
  • aprs_path 指定发送 APRS 流量时使用的中继器路径。
  • listen_destinations 是 AX.25 目标列表。在幕后,这些值传递给 Router.bind,因此它们是以下形式的 dict{callsign: "CALL", regex: True/False, ssid: None/int}设置此参数可能会破坏 MICe 数据包的接收!
  • listen_altnets 是另一个 AX.25 目标列表,使用与 listen_destinations 相同的方案。 设置此参数可能会破坏 MICe 数据包的接收!
  • msgid_modulo 设置生成消息 ID 时使用的模数。默认值(1000)导致消息 ID 从 1 开始,并在 999 处回绕。
  • deduplication_expiry 设置存储消息哈希以进行去重处理的秒数。默认为 28 秒。

要发送 APRS 消息,有 send_messagesend_response

  • send_message(addressee, path=None, oneshot=False, replyack=False):此方法将 APRS 消息发送到指定的站点。如果 pathNone,则使用 aprs_path。如果 oneshot=True,则消息不包含消息 ID,不需要 ACK/REJ,不会进行重传,此方法返回 None。否则,返回一个来自 aioax25.aprs.messageAPRSMessageHandler
    • 如果 replyack 设置为 True,则消息将向接收者宣传 回复-ACK 功能。并非所有 APRS 实现都支持此功能。
    • 如果 replyack 引用了一个本身已将 replyack 设置(无论是设置为 True 还是设置为以前的消息 ID)的传入消息,则传出消息将在 "ack" 后附加回复-ACK 后缀。
    • replyack=False 的默认值禁用所有回复-ACK 功能(传入的回复-ACK 消息仍然被视为 ACK)。
  • send_response(message, ack=True):当您从另一个站点接收到消息时使用此函数 -- 将消息传递给此函数将向该站点发送 ACKREJ 消息。

APRSMessageHandler

APRSMessageHandler 类实现了 APRS 消息重传逻辑。对象有一个 done 信号,在以下任何事件发生时都会发出

  • 消息超时(未收到 ACK/REJ)(state=HandlerState.TIMEOUT)
  • 消息被取消(通过 cancel() 方法)(state=HandlerState.CANCEL)
  • 收到 ACK 或 REJ 帧(《state=HandlerState.SUCCESS 或 state=HandlerState.REJECT

该信号将调用以下关键字参数的回调函数

  • handler:发出信号的 APRSMessageHandler 对象
  • stateAPRSMessageHandler 对象的状态。

TAPR TNC2 数据包格式

有时,您需要 TAPR TNC2 格式的传入数据包,特别是在 APRS-IS 交互中。在 aioax25 中,这有点实验性,因为没有人对“TNC2 格式”有明确的定义。

所有 AX25Frame 实例都实现了 tnc2 属性,该属性以期望与 TNC2 兼容的格式返回帧。对于可能以多种不同格式编码的 UI 帧,还有一个 get_tnc2 方法,该方法接受传递给 bytes.decode() 的参数;默认情况下,以 ISO-8859-1 编码解耦有效载荷,因为这样可以无损地保留字节值。

APRS 分段中继

aioax25 包含一个模块,实现了包括处理 WIDEn-N SSID 基本分段中继的 APRS。该实现将 WIDE 视同 TRACE:在路径中插入站点的呼号(我相信这更符合 业余许可证条件决定,因为它确保每个中继台“识别”自己)。

aioax25.aprs.uidigi 模块可以配置为中继其他别名,如传统的 WIDERELAY,或您选择的任何别名。

它能够处理多个接口,但将重复接收到的消息在它们接收的接口上重复。(即如果您连接了 2m 接口和 HF 接口,它不会从 HF 中继到 2m)。

设置非常简单

from aioax25.aprs.uidigi import APRSDigipeater

# Given an APRSInterface class (aprsint)
# Create a digipeater instance
digipeater = APRSDigipeater()

# Connect your interface
digipeater.connect(aprsint)

# Optionally add any aliases you want handled
digipeater.addaliases('WIDE', 'GATE')

现在您正在进行中继。中继台将自动处理 WIDEn-NTRACEn-N,在上面的示例中,还将为中继 WIDEGATE

防止繁忙网络上的消息循环

如果您有大量的中继台在近距离(比如说大约 6 个)并且有很多流量,您可能会遇到这种情况:要中继的消息在发送队列中等待的时间超过了 28 秒,而其他中继台需要 28 秒来“忘记”该消息。

这会导致网络具有大象般的记忆,因为它几乎永远不会忘记消息,因为中继台是在原始消息 30 秒之后才到达的。

APRSDigipeater 类构造函数可以接受单个参数 digipeater_timeout,它设置队列中中继消息的超时时间(默认为 5 秒)。如果消息在此超时到期之前未发送,则消息将安静地删除,以防止内存效应。

项目详细信息


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源代码分发

aioax25-0.0.11.post0.tar.gz (83.1 kB 查看哈希值)

上传时间 源代码

由以下组织支持