Git Product home page Git Product logo

aioax25's Introduction

aioax25: AX.25 and APRS library in asyncio

Test Status 3.5 Test Status 3.9 Test Status 3.10 Coverage Status

The aim of this project is to implement a simple-to-understand asynchronous AX.25 library built on asyncio and pyserial, implementing a AX.25 and APRS stack in pure Python.

Python 3.5+ and above is required as of 2021-11-12

I did try to support 3.4, but this proved to be infeasible for the following reasons:

  • Python 3.8+ makes asyncio.coroutine deprecated (apparently will be removed in 3.10). This meant I needed coroutine and async def versions of some API functions, and the necessary logic to "hide" the latter from Python 3.4.
  • Trying to coax generator-based coroutines to run "in the background" for unit test purposes proved to be a pain in the arse.

Python 3.5 support is planned to continue until it too, becomes infeasible (e.g. if type annotations become required).

What works

  • We can put a Kantronics KPC-3 TNC into KISS mode automatically
  • Multi-port KISS TNCs (tested with Direwolf and the NWDR UDRC-II)
  • We can receive AX.25 UI frames
  • We can send AX.25 UI frames

What doesn't work

  • Connecting to AX.25 nodes
  • Accepting connections from AX.25 nodes

What isn't tested

  • Platforms other than GNU/Linux

Current plans

Right now, I intend to get enough going for APRS operation, as that is my immediate need now. Hence the focus on UI frames.

I intend to write a core class that will take care of some core AX.25 message handling work and provide the basis of what's needed to implement APRS.

After that, some things I'd like to tackle in no particular order:

  • Connected mode operation
  • NET/ROM support

Supported platforms will be GNU/Linux, and possibly BSD variants. I don't have access to recent Apple hardware (my 2008-era MacBook will not run contemporary MacOS X) so I'm unable to test this software there, but it should work nonetheless.

It might work on Windows -- most probably using Cygwin or Subsystem for Linux. While I do have a Windows 7 machine handy, life's too short to muck around with an OS that can't decide if it's pretending to be Linux, VMS or CP/M. There's an abundance of AX.25 stacks and tools for that platform, I'll accept patches here on the proviso they don't break things or make the code unmaintainable.

Usage

This is a rough guide regarding how to use aioax25 in your programs.

Create a KISS device interface and ports

Right now we only support serial KISS interfaces (patches for TCP-based interfaces are welcome). Import make_device from aioax25.kiss, then create an instance as shown:

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

Or for a TCP-connected KISS interface:

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

(Note: if kissdevice.example.com is going over the Internet, I suggest either routing via a VPN or supplying a ssl.SSLContext via the ssl parameter so that your client is authenticated with the server.)

Or for a subprocess:

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

Some optional parameters:

  • reset_on_close: When asked to close the device, try to issue a c0 ff c0 reset sequence to the TNC to put it back into CMD mode.
  • send_block_size, send_block_delay: If a KISS frame is larger than this size, break the transmissions out the serial port into chunks of the given size, and wait send_block_delay seconds between each chunk. (If your TNC has a small buffer, this may help.)

This represents the KISS TNC itself, with its ports accessible using the usual __getitem__ syntax:

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

These KISS port interfaces just spit out the content of raw AX.25 frames via their received signals and accept raw AX.25 frames via the send method. Any object passed to send is wrapped in a bytes call -- this will implicitly call the __bytes__ method on the object you pass in.

Setting up an AX.25 Interface

The AX.25 interface is a logical routing and queueing layer which decodes the data received from a KISS port and routes it according to the destination call-sign.

AX25Interface is found in the aioax25.interface package. Import that, then do the following to set up your interface:

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

Some optional parameters:

  • cts_delay, cts_rand: The number of seconds to wait after making a transmission/receiving a transmission, before we send another transmission. The delay time is cts_delay + (random.random() * cts_rand), the idea being to avoid doubling when two stations attempt transmission.

The AX25Interface is a subclass of Router (see aioax25.router), which exposes the following methods and properties:

  • received_msg: This is a Signal object which is fired for every AX.25 frame received. Slots are expected to take two keyword arguments: interface (the interface that received the frame) and frame (the AX.25 frame itself).

  • bind(callback, callsign, ssid=0, regex=False): This method allows you to bind a call-back function to receive AX.25 frames whose destination field is addressed to the call-sign and SSID specified. The call-sign may be a regular expression if regex=True. This will be compiled and matched against all incoming traffic. Regardless of the value of regex, the callsign parameter must be a string.

  • unbind(callback, callsign, ssid=0, regex=False): This method un-binds a previously bound call-back method from receiving the nominated traffic.

Additionally, for transmitting frames, AX25Interface adds the following:

  • transmit(frame, callback=None): This method allows you to transmit arbitrary AX.25 frames. They are assumed to be instances of AX25Frame (from aioax25.frame). The callback, if given, will be called once the frame is sent with the following keyword arguments: interface (the AX25Interface that sent the frame), frame (the frame that was sent).

  • cancel_transmit(frame): This cancels a pending transmission of a frame. If the frame has been sent, this has no effect.

APRS Traffic handling

The AX25Interface just deals in AX.25 traffic, and does not provide any special handling of APRS UI frames. For this, one may look at APRSInterface.

Import this from aioax25.aprs. It too, is a subclass of Router, and so bind, unbind and received_msg are there -- the messages received will be instances of APRSFrame (see aioax25.aprs.frame), otherwise the behaviour is identical.

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

Other optional parameters:

  • retransmit_count, retransmit_timeout_base, retransmit_timeout_rand, retransmit_timeout_scale: These control the timing of retransmissions when sending confirmable APRS messages. Before transmission, a time-out is computed as timeout = retransmit_timeout_base + (random.random() * retransmit_timeout_rand), and a retry counter is initialised to retransmit_count. On each re-transmission, the retry counter is decremented and the timeout is multiplied by retransmit_timeout_scale.
  • aprs_destination: This sets the destination call-sign used for APRS traffic. Right now, we use the experimental call of APZAIO for all traffic except direct messages (which instead are sent directly to the station addressed).
  • aprs_path specifies the digipeater path to use when sending APRS traffic.
  • listen_destinations is a list of AX.25 destinations. Behind the scenes, these are values passed to Router.bind, and thus are given as dicts of the form: {callsign: "CALL", regex: True/False, ssid: None/int}. Setting this may break reception of MICe packets!
  • listen_altnets is an additional list of AX.25 destinations, given using the same scheme as listen_destinations. Setting this may break reception of MICe packets!
  • msgid_modulo sets the modulo value used when generating a message ID. The default value (1000) results in a message ID that starts at 1 and wraps around at 999.
  • deduplication_expiry sets the number of seconds we store message hashes for de-duplication purposes. The default is 28 seconds.

To send APRS messages, there is send_message and send_response:

  • send_message(addressee, path=None, oneshot=False, replyack=False): This sends an APRS message to the addressed station. If path is None, then the aprs_path is used. If oneshot=True, then the message is sent without a message ID, no ACK/REJ is expected and no retransmissions will be made, the method returns None. Otherwise, a APRSMessageHandler (from aioax25.aprs.message) is returned.
    • If replyack is set to True, then the message will advertise reply-ack capability to the recipient. Not all APRS implementations support this.
    • If replyack references an incoming message which itself has replyack set (either to True or to a previous message ID), then the outgoing message will have a reply-ack suffix appended to "ack" the given message.
    • The default of replyack=False disables all reply-ack capability (an incoming reply-ack message will still be treated as an ACK however).
  • send_response(message, ack=True): This is used when you have received a message from another station -- passing that message to this function will send a ACK or REJ message to that station.

The APRSMessageHandler class

The APRSMessageHandler class implements the APRS message retransmission logic. The objects have a done signal which is emitted upon any of the following events:

  • Message time-out (no ACK/REJ received) (state=HandlerState.TIMEOUT)
  • Message was cancelled (via the cancel() method) (state=HandlerState.CANCEL)
  • An ACK or REJ frame was received (state=HandlerState.SUCCESS or state=HandlerState.REJECT)

The signal will call call-back functions with the following keyword arguments:

  • handler: The APRSMessageHandler object emitting the signal
  • state: The state of the APRSMessageHandler object.

TAPR TNC2 packet format

Sometimes, you need the incoming packet in TAPR TNC2 format, notably for APRS-IS interaction. This is somewhat experimental in aioax25 as no one seems to have a definition of what "TNC2 format" is.

All AX25Frame instances implement tnc2 property, which returns the frame in a hopefully TNC2-compatible format. For UI frames, which may be encoded in a number of different formats, there is also a get_tnc2 method, which accepts arguments that are passed to bytes.decode(); the default is to decode the payload as ISO-8859-1 since this preserves the byte values losslessly.

APRS Digipeating

aioax25 includes a module that implements basic digipeating for APRS including handling of the WIDEn-N SSIDs. The implementation treats WIDE like TRACE: inserting the station's own call-sign in the path (which I believe is more compliant with the Amateur License Conditions Determination in that it ensures each digipeater "identifies" itself).

The aioax25.aprs.uidigi module can be configured to digipeat for other aliases such as the legacy WIDE and RELAY, or any alias of your choosing.

It is capable of handling multiple interfaces, but will repeat incoming messages on the interface they were received from ONLY. (i.e. if you connect a 2m interface and a HF interface, it will NOT digipeat from HF to 2m).

Set-up is pretty simple:

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')

You're now digipeating. The digipeater will automatically handle WIDEn-N and TRACEn-N, and in the above example, will also digipeat for WIDE, GATE.

Preventing message loops on busy networks

If you have a lot of digipeaters in close proximity (say about 6) and there's a lot of traffic, you can get the situation where a message queued up to be digipeated sits in the transmit queue longer than the 28 seconds needed for other digipeaters to "forget" the message.

This leads to a network with the memory of an elephant, it almost never forgets a message because the digipeats come more than 30 seconds after the original.

The APRSDigipeater class constructor can take a single parameter, digipeater_timeout, which sets an expiry (default of 5 seconds) on queued digipeat messages. If a message is not sent by the time this timeout expires, the message is quietly dropped, preventing the memory effect.

aioax25's People

Contributors

hemna avatar sjlongland avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

aioax25's Issues

Error on create

Hey man,
I am working on a webchat version of aprsd and am using the latest from github for aioax25. Every time I startup APRSInterface I get an exception from asyncio

Exception in callback <Task pending name='Task-1' coro=<TCPKISSDevice._open_connection() running at /Users/i530566/devel/mine/hamradio/aioax25/aioax25/kiss.py:540> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_write_done(9)(), <TaskWakeupMethWrapper object at 0x104d8a7f0>()]>>()
handle: <Handle <Task pending name='Task-1' coro=<TCPKISSDevice._open_connection() running at /Users/i530566/devel/mine/hamradio/aioax25/aioax25/kiss.py:540> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_write_done(9)(), <TaskWakeupMethWrapper object at 0x104d8a7f0>()]>>()>
Traceback (most recent call last):
  File "/Users/i530566/.pyenv/versions/3.8.7/lib/python3.8/asyncio/events.py", line 81, in _run
    self._context.run(self._callback, *self._args)
TypeError: '_asyncio.Task' object is not callable

Here is how I launch APRSInterface:
https://github.com/craigerl/aprsd/blob/small_refactor/aprsd/clients/kiss.py#L46-L64

Digipeating should wait a randomised delay before relaying

If there are official digipeaters in the area, it is undesirable to try and relay on their behalf.
Moreover, sending immediately risks doubling, which leads to packet loss.

Wait a randomised delay before digipeating a message. If we hear a downlink frame for that same message, discard and cancel our scheduled transmission.

Exception handling help?

Hey there,

Thanks for making this project. I'm trying to write a little chat app over AX.25 and was trying to catch to errors.

The first I figured out, if direwolf isn't running the device never transitons to OPEN, so I can catch that:

while self.device.state != KISSDeviceState.OPEN:
            msg = f"Waiting for direwolf connection... attempt {i}/{max_attempts}"
            print(msg, end="\r")  # noqa: T201
            if i > max_attempts:
                msg = "Cannot connect to direwolf. Is it running?"
                LOGGER.critical(msg)
                sys.exit(1)

            await asyncio.sleep(1)
            i += 1

The second, which I cannot figure out, is if direwolf disappears after the device was open.

raw_frame = AX25RawFrame(
                destination=dest,
                source=self.callsign,
                control=0,
                payload=payload,
            )
interface.transmit(raw_frame)

In this case if direwolf is no longer running I get the following:

Unhandled exception in event loop:
  File "/usr/lib64/python3.12/asyncio/events.py", line 84, in _run
    self._context.run(self._callback, *self._args)
  File "/home/bthornto/github/ax25/venv/lib64/python3.12/site-packages/aioax25/kiss.py", line 321, in _send_data
    self._send_raw_data(data)
  File "/home/bthornto/github/ax25/venv/lib64/python3.12/site-packages/aioax25/kiss.py", line 448, in _send_raw_data
    self._transport.write(data)
    ^^^^^^^^^^^^^^^^^^^^^

Exception 'NoneType' object has no attribute 'write'

Any suggestions or pointers on how to catch this one? I would like to gracefully exit the app when it happens.

Thanks for any info/suggestions,

Brad

APRS module does not de-duplicate messages

In cases where digipeaters are in use, we should be acting on only the first copy of a message we see and ignoring the others.

Confirmable messages have a message ID for that exact purpose.

`None` being appended to APRS message frames

Payload content: ac966884ae9260ac966884ae92fcae92888a6240e2ae92888a64406303f03a564b344257492020203a4c23342043523a65323030363030383133303830323335303630306439366120543a3232353133335a7b33387d4e6f6e65

4e6f6e65 is None… that's a Pythonism that crept in on the packet network.

queued messages never go out sometimes

hey thanks for the update for version 0.0.11.

I'm seeing some weird behavior at times. I am connecting to direwolf over tcpkiss and there are times
when I send a message to aioax25, and I the debugging output shows that the packet is added to the send queue.
Then it never goes out
When I then send a message to direwolf from my radio, kiss gets that message and then sends it to aprsd, and then I see
the queued up messages in aioax25 go out to direwolf and then out to RF finally.

Is there some reason you can think of that the messages get stuck in the queue and don't go to direwolf?

09/25/2022 08:09:42 PM TXPKT-3-hey f        DEBUG    Send 'b':WB4BOR   :hey from webchat{3'' TO KISS                                                       kiss.py:147
09/25/2022 08:09:42 PM TXPKT-3-hey f        INFO     Sending WB4BOR-12>WB4BOR,WIDE1-1,WIDE2-1: PID=0xf0 Payload=b':WB4BOR   :hey from webchat{3'           aprs.py:330
09/25/2022 08:09:42 PM TXPKT-3-hey f        DEBUG    Adding to queue: WB4BOR-12>WB4BOR,WIDE1-1,WIDE2-1: PID=0xf0 Payload=b':WB4BOR   :hey from         interface.py:62
                                                     webchat{3'

then 2 minutes later since aprsd didn't get an ack, it sends it again. It doesn't actually go out to direwolf and RF until direwolf gets a packet from RF to kiss to aprsd.

working on TCPKISSDevice

I'm trying to wrap my head around how this package works.
I'm trying to use this to connect to direwolf's TCP KISS interface to recieve aprs messages.

I haven't quite figured out how the KISSPort maps to a TCP based kissdevice, but my test script looks like this.

kissdev = kiss.TCPKISSDevice("192.168.1.7", 8001, log=LOG)
kissdev.open()

kissport0 = kissdev[0]

ax25int = interface.AX25Interface(
    kissport=kissport0,
    log=LOG
)

aprsint = APRSInterface(
    ax25int=ax25int,
    mycall='WB4BOR',
    log=LOG
)

But it just just and exits. How do I get this to loop forever waiting for packets to come in?

[02/22/2021 07:57:44 PM][MainThread  ][DEBUG] [/home/waboring/devel/aioax25/aioax25/kiss.py.__init__:446] Created
[02/22/2021 07:57:44 PM][MainThread  ][DEBUG] [/home/waboring/devel/aioax25/aioax25/kiss.py.open:386] Opening device
[02/22/2021 07:57:44 PM][MainThread  ][DEBUG] [/home/waboring/devel/aioax25/aioax25/kiss.py._open:449] Call open
[02/22/2021 07:57:44 PM][MainThread  ][DEBUG] [/home/waboring/devel/aioax25/aioax25/kiss.py._open:455] Call open Done
[02/22/2021 07:57:44 PM][MainThread  ][DEBUG] [/home/waboring/devel/aioax25/aioax25/kiss.py.__getitem__:373] OPEN new port 0
[02/22/2021 07:57:44 PM][MainThread  ][DEBUG] [/home/waboring/devel/aioax25/aioax25/kiss.py.__getitem__:375] <aioax25.kiss.KISSPort object at 0x7f82202bbcd0>

My TCPKISSDevice class looks like this:

class TCPKISSDevice(BaseKISSDevice):

    _interface = None
    READ_BYTES = 1000

    def __init__(self, host: str, port: int, *args, **kwargs):
        super(TCPKISSDevice, self).__init__(*args, **kwargs)
        self.address = (host, port)
        self._log.debug('Created')

    def _open(self):
        self._log.debug('Call open')
        self._interface = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._interface.connect(self.address)
        self._state = KISSDeviceState.OPEN
        self._loop.add_reader(self._interface, self._on_recv_ready)
        self._loop.call_soon(self._init_kiss)
        self._log.debug('Call open Done')

    def _on_recv_ready(self):
        self._log.debug('Called')
        try:
            read_data = self._interface.recv(self.READ_BYTES, timeout=30)
            self._receive(read_data)
        except:
            self._log.exception('Failed to read from serial device')

    def _send_raw_data(self, data):
        self._serial.write(data)

    def _close(self):
        self._loop.remove_reader(self._interface)
        socket.close(self._interface)
        self._interface = None
        self._state = KISSDeviceState.CLOSED
       

Sending message packet with msg number

I am seeing a problem with sending a message that includes a message number. It looks like the message number is being stripped off the packet, resulting in incorrect acks coming back.

Here is an example from aprsd.

08/02/2022 12:43:33 PM TXPKT-4-ping         INFO                                                                                  messaging.py:584
                                                     Sending Message _______________(TX:1)
                                                       Raw     : WB4BOR-12>APZ100::WB4BOR-11:ping{4

                                                       To      : WB4BOR-11
                                                       Message : ping
                                                       Msg #   : 4
                                                     Sending Message _______________ Complete
08/02/2022 12:43:33 PM TXPKT-4-ping         DEBUG    GET client tcpkiss                                                              client.py:226
08/02/2022 12:43:34 PM TXPKT-4-ping         DEBUG    Send WB4BOR-12>APZ100::WB4BOR-11:ping{4                                           kiss.py:108
                                                      TO KISS
08/02/2022 12:43:34 PM TXPKT-4-ping         INFO     Send one-shot to WB4BOR-11: ping                                                  aprs.py:160
08/02/2022 12:43:34 PM TXPKT-4-ping         INFO     Sending WB4BOR-12>WB4BOR-11,WIDE1-1,WIDE2-1: PID=0xf0 Payload=b':WB4BOR-11:ping'  aprs.py:330
08/02/2022 12:43:34 PM TXPKT-4-ping         DEBUG    Adding to queue: WB4BOR-12>WB4BOR-11,WIDE1-1,WIDE2-1: PID=0xf0                interface.py:62
                                                     Payload=b':WB4BOR-11:ping'
08/02/2022 12:43:34 PM TXPKT-4-ping         DEBUG    Scheduling next transmission ASAP                                            interface.py:113
08/02/2022 12:43:34 PM RX_MSG               DEBUG    Transmitting WB4BOR-12>WB4BOR-11,WIDE1-1,WIDE2-1: PID=0xf0                   interface.py:141
                                                     Payload=b':WB4BOR-11:ping'
08/02/2022 12:43:34 PM RX_MSG               DEBUG    XMIT AX.25 WB4BOR-12>WB4BOR-11,WIDE1-1,WIDE2-1: PID=0xf0                          kiss.py:619
                                                     Payload=b':WB4BOR-11:ping'
08/02/2022 12:43:34 PM RX_MSG               DEBUG    XMIT FRAME '00ae8468849ea4f6ae8468849ea478ae92888a624062ae92888a64406303f03a57423 kiss.py:226
                                                     4424f522d31313a70696e67'
                                                     

The direwolf log

[0L] WB4BOR-12>WB4BOR-11,WIDE1-1,WIDE2-1::WB4BOR-11:ping

The recieving APRSD instance view of the packet

today at 12:42:45 PM                                                     Received Message _______________                                                                                                                   
today at 12:42:45 PM                                                       Raw     : WB4BOR-12>WB4BOR-11,K4CQ-4,WIDE1*,WIDE2-1,qAR,APPOMX::WB4BOR-11:ping                                                                   
today at 12:42:45 PM                                                       From    : WB4BOR-12                                                                                                                              
today at 12:42:45 PM                                                       Message : ping                                                                                                                                   
today at 12:42:45 PM                                                       Msg #   : 0                                                                                                                                      
today at 12:42:45 PM                                                     Received Message _______________ Complete                                                                                                          
today at 12:42:45 PM08/02/2022 12:42:45 PM RXPKT-WB4BOR-12>     DEBUG    Send ACK(WB4BOR-12:0) to radio.

As you can see the original message was sent to tcpkiss with a message number of 4, so I can get acks for message 4 back from the destination.

The destination didn't get a message number as part of the packet as it was stripped out, so it sent an ack with message number of 0.

Handle garbage data at the end of message IDs in APRS messages

Kenwood in their TH-F7E for unknown reasons appends a carriage return in their message ID.
Xastir, appends a }, which may be down to the ack-reply in newer variants of APRS.

I managed to kludge my way around what Kenwood is doing, but Xastir down-right confuses aioax25. This information needs to be stripped.

sender header callsign gets a * causes aprslib to fail to decode packet

Hello,
I am very close to getting aprsd working with aioax25 with direwolf. There is an odd behavior that I am seeing when getting aprs packets from aioax25, which is the sender's callsign gets a * after it, which causes aprslib to fail to decode the packet.

These lines of code are where the * is being added to the sender's callsign.
https://github.com/sjlongland/aioax25/blob/master/aioax25/frame.py#L713-L714

Here is a sample packet sent from my Kenwood TH-d74, which direwolf picks up and sends to aioax25

[DEBUG] Got an APRS Frame 'WB4BOR*>APK004,WIDE1-1,WIDE2-1: PID=0xf0 Payload=b':WB4BOR-12:ping{27'' - [/Users/i530566/devel/mine/aprsd/aprsd/threads.py:373]
[09/01/2021 02:15:27 PM] [KISSRX_MSG  ] [DEBUG] Decoding WB4BOR*>APK004,WIDE1-1,WIDE2-1::WB4BOR-12:ping{27 - [/Users/i530566/devel/mine/aprsd/aprsd/threads.py:377]

The direwolf log entry for the packet

WB4BOR audio level = 8(4/2)   [NONE]   ___|||||_                                                                                                 │
[0.5] WB4BOR>APK004,WIDE1-1,WIDE2-1::WB4BOR-12:ping{27<0x0d>                                                                                     │
[0H] WB4BOR>APK004,APPOMX*,WIDE2-1::WB4BOR-12:ping{27<0x0d>

You can see the fail to decode here:

Exception in callback KISSRXThread.process_packet(interface=<aioax25.aprs...t 0x1040a7970>, frame=<aioax25.aprs...t 0x1040b3160>)()
handle: <Handle KISSRXThread.process_packet(interface=<aioax25.aprs...t 0x1040a7970>, frame=<aioax25.aprs...t 0x1040b3160>)()>
Traceback (most recent call last):
  File "/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aprslib/parsing/__init__.py", line 101, in parse
    parsed.update(parse_header(head))
  File "/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aprslib/parsing/common.py", line 45, in parse_header
    raise ParseError("fromcallsign is invalid")
aprslib.exceptions.ParseError: fromcallsign is invalid

I've never seen an aprs frame where the fromcallsign gets a * put after it. If I comment those lines 712 and 714 out of the frame.py, then everything works and is happy.

README is out of date?

I'm struggling with trying to get aioax25 to send me frames from a kiss interface. The README mentions that you can set
listen_destinations in the APRSInterface to specify which callsign/ssid to look for. Yet, when passing in listen_destinations dict to APRSInterface I get a failure.

  File "/Users/i530566/devel/mine/aprsd/aprsd/kissclient.py", line 108, in setup
    self.aprsint = APRSInterface(
TypeError: __init__() got an unexpected keyword argument 'listen_destinations'

I grepped the repo

└─> fgrep -rni listen_destination *
README.md:173: * `listen_destinations` is a list of AX.25 destinations.  Behind the scenes,
README.md:177:   the same scheme as `listen_destinations`.

I am also calling bind() on the APRSInterface and passing in my callsign and setting regex to false (assuming this means look for an exact match?), but my callback is not getting called when I send an APRS packet.

My callsign is WB4BOR-12 for this instance of aioax25 connected to direwolf. I am using a kenwood TH-D74 to send an aprs message to WB4BOR-12. You can see from the log below that aioax25 gets it, says the frame is for us, but yet then never sends it on to my registered callback from the aprsinterface.bind() call.

[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] Processing incoming message WB4BOR*>APK004,WIDE1-1,WIDE2-1: PID=0xf0 Payload=b':WB4BOR-12:ping{93' (type APRSMessageFrame) - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/aprs/aprs.py:247]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] Addressee? WB4BOR-12 - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/aprs/aprs.py:249]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] Frame is for us! - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/aprs/aprs.py:261]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] This is a real message for us! - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/aprs/aprs.py:276]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] Handling incoming frame WB4BOR*>APK004,WIDE1-1,WIDE2-1: PID=0xf0 Payload=b':WB4BOR-12:ping{93' - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/router.py:98]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] Dest: WB4BOR-12 callsign: WB4BOR - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/router.py:108]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] RXers = dict_values([]) - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/router.py:123]
[08/31/2021 01:44:13 PM] [KISSRX_MSG  ] [DEBUG] Dispatching frame to 0 receivers - [/Users/i530566/devel/mine/aprsd/.venv/lib/python3.8/site-packages/aioax25/router.py:139]

SABM and SABME swapped in frame.py

In the file frame.py, SABM and SABME classes have their modifiers swapped. The correct values should be :-

Class AX25SetAsyncBalancedModeExtendedFrame should have a modifier of MODIFIER = 0b01101111
Class AX25SetAsyncBalancedModeFrame should have a modifier of MODIFIER = 0b00101111

It also appears that the P/F flag needs more investigation, and should possibly be set. The basis for this is that my PicoPacket TNC with 1996 firmware has the P/F bit set. In my case, the picopacket is using 0x3f as the value for SABM.

I suspect that existing code uses a mask to ignore the p/f bit.

Following your code, you could ADD these MODIFIER = 0b01101111 and MODIFIER = 0b00101111 respectively for SABME and SABM.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.