使用纯Python和asyncio实现的异步AX.25接口
项目描述
aioax25
:AX.25和APRS库,基于asyncio
该项目的目的是实现一个易于理解的异步AX.25库,基于asyncio
和pyserial
,在纯Python中实现AX.25和APRS堆栈。
自2021-11-12起,需要Python 3.5及更高版本
我曾尝试支持3.4,但以下原因使其变得不可行
- Python 3.8+使
asyncio.coroutine
过时(显然将在3.10中删除)。这意味着我需要某些API函数的coroutine
和async def
版本,以及必要的逻辑来“隐藏”后者以适应Python 3.4。 - 尝试将基于生成器的协程用于单元测试中的“后台”运行证明是一件痛苦的事情。
计划继续支持Python 3.5,直到它也成为不可行(例如,如果需要类型注解)。
哪些功能工作
- 我们可以自动将Kantronics KPC-3 TNC置于KISS模式
- 多端口KISS TNC(已与Direwolf和NWDR 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_size
,send_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_delay
,cts_rand
:在发送/接收传输后等待的秒数,然后我们再发送另一个传输。延迟时间是cts_delay + (random.random() * cts_rand)
,其想法是避免当两个站点尝试传输时加倍。
AX25Interface
是Router
的子类(请参阅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
的子类,因此 bind
、unbind
和 received_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_count
、retransmit_timeout_base
、retransmit_timeout_rand
、retransmit_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_message
和 send_response
send_message(addressee, path=None, oneshot=False, replyack=False)
:此方法将 APRS 消息发送到指定的站点。如果path
为None
,则使用aprs_path
。如果oneshot=True
,则消息不包含消息 ID,不需要 ACK/REJ,不会进行重传,此方法返回None
。否则,返回一个来自aioax25.aprs.message
的APRSMessageHandler
。- 如果
replyack
设置为True
,则消息将向接收者宣传 回复-ACK 功能。并非所有 APRS 实现都支持此功能。 - 如果
replyack
引用了一个本身已将replyack
设置(无论是设置为True
还是设置为以前的消息 ID)的传入消息,则传出消息将在 "ack" 后附加回复-ACK 后缀。 replyack=False
的默认值禁用所有回复-ACK 功能(传入的回复-ACK 消息仍然被视为 ACK)。
- 如果
send_response(message, ack=True)
:当您从另一个站点接收到消息时使用此函数 -- 将消息传递给此函数将向该站点发送ACK
或REJ
消息。
APRSMessageHandler
类
APRSMessageHandler
类实现了 APRS 消息重传逻辑。对象有一个 done
信号,在以下任何事件发生时都会发出
- 消息超时(未收到 ACK/REJ)(
state=HandlerState.TIMEOUT
) - 消息被取消(通过
cancel()
方法)(state=HandlerState.CANCEL
) - 收到 ACK 或 REJ 帧(《state=HandlerState.SUCCESS 或
state=HandlerState.REJECT
)
该信号将调用以下关键字参数的回调函数
handler
:发出信号的APRSMessageHandler
对象state
:APRSMessageHandler
对象的状态。
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
模块可以配置为中继其他别名,如传统的 WIDE
和 RELAY
,或您选择的任何别名。
它能够处理多个接口,但将重复接收到的消息在它们接收的接口上仅重复。(即如果您连接了 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-N
和 TRACEn-N
,在上面的示例中,还将为中继 WIDE
和 GATE
。
防止繁忙网络上的消息循环
如果您有大量的中继台在近距离(比如说大约 6 个)并且有很多流量,您可能会遇到这种情况:要中继的消息在发送队列中等待的时间超过了 28 秒,而其他中继台需要 28 秒来“忘记”该消息。
这会导致网络具有大象般的记忆,因为它几乎永远不会忘记消息,因为中继台是在原始消息 30 秒之后才到达的。
APRSDigipeater
类构造函数可以接受单个参数 digipeater_timeout
,它设置队列中中继消息的超时时间(默认为 5 秒)。如果消息在此超时到期之前未发送,则消息将安静地删除,以防止内存效应。
项目详细信息
aioax25-0.0.11.post0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 3eed9e5811faf615d12e3fba9c9e309e544faa64b8a31136a88d3a87bcfbe221 |
|
MD5 | ce65bb5346b8bb750a372c3c5e58ce32 |
|
BLAKE2b-256 | 9ce2c8df965df97b04d1687a12062a2cd7a4a57dc72457278c69a343a45ee2f7 |