{ "info": { "author": "Stuart Longland VK4MSL", "author_email": "me@vk4msl.id.au", "bugtrack_url": null, "classifiers": [ "Development Status :: 2 - Pre-Alpha", "Environment :: No Input/Output (Daemon)", "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Operating System :: POSIX", "Programming Language :: Python :: 3 :: Only", "Topic :: Communications :: Ham Radio" ], "description": "# `aioax25`: AX.25 and APRS library in `asyncio`\n\n[![Build Status](https://travis-ci.org/sjlongland/aioax25.svg?branch=master)](https://travis-ci.org/sjlongland/aioax25)\n[![Coverage Status](https://coveralls.io/repos/github/sjlongland/aioax25/badge.svg?branch=master)](https://coveralls.io/github/sjlongland/aioax25?branch=master)\n\nThe aim of this project is to implement a simple-to-understand asynchronous\nAX.25 library built on `asyncio` and `pyserial`, implementing a AX.25 and APRS\nstack in pure Python.\n\n## What works\n\n* We can put a Kantronics KPC-3 TNC into KISS mode automatically\n* Multi-port KISS TNCs (tested with\n [Direwolf](https://github.com/wb2osz/direwolf) and the\n [NWDR UDRC-II](https://nw-digital-radio.groups.io/g/udrc/wiki/home))\n* We can receive AX.25 UI frames\n* We can send AX.25 UI frames\n\n## What doesn't work\n\n* Connecting to AX.25 nodes\n* Accepting connections from AX.25 nodes\n\n## What isn't tested\n\n* Platforms other than GNU/Linux\n\n## Current plans\n\nRight now, I intend to get enough going for APRS operation, as that is my\nimmediate need now. Hence the focus on UI frames.\n\nI intend to write a core class that will take care of some core AX.25 message\nhandling work and provide the basis of what's needed to implement APRS.\n\nAfter that, some things I'd like to tackle in no particular order:\n\n* Connected mode operation\n* NET/ROM support\n\nSupported platforms will be GNU/Linux, and possibly BSD variants. I don't\nhave access to recent Apple hardware (my 2008-era MacBook will not run\ncontemporary MacOS X) so I'm unable to test this software there, but it\n_should_ work nonetheless.\n\nIt might work on Windows -- most probably using Cygwin or Subsystem for Linux.\nWhile I do have a Windows 7 machine handy, life's too short to muck around\nwith an OS that can't decide if it's pretending to be Linux, VMS or CP/M.\nThere's an abundance of AX.25 stacks and tools for that platform, I'll accept\npatches here on the proviso they don't break things or make the code\nunmaintainable.\n\n## Usage\n\nThis is a rough guide regarding how to use `aioax25` in your programs.\n\n### Create a KISS device interface and ports\n\nRight now we only support serial KISS interfaces (patches for TCP-based\ninterfaces are welcome). Import `SerialKISSDevice` from `aioax25.kiss`, then\ncreate an instance as shown:\n\n```python\n kissdev = SerialKISSDevice(\n device='/dev/ttyS4', baudrate=9600,\n log=logging.getLogger('your.kiss.log')\n )\n```\n\nSome optional parameters:\n * `reset_on_close`: When asked to close the device, try to issue a `c0 ff c0`\n reset sequence to the TNC to put it back into CMD mode.\n * `send_block_size`, `send_block_delay`: If a KISS frame is larger than\n this size, break the transmissions out the serial port into chunks of\n the given size, and wait `send_block_delay` seconds between each chunk.\n (If your TNC has a small buffer, this may help.)\n\nThis represents the KISS TNC itself, with its ports accessible using the usual\n`__getitem__` syntax:\n\n```python\n kissport0 = kissdev[0]\n kissport1 = kissdev[1]\n```\n\nThese KISS port interfaces just spit out the content of raw AX.25 frames via\ntheir `received` signals and accept raw AX.25 frames via the `send` method.\nAny object passed to `send` is wrapped in a `bytes` call -- this will\nimplicitly call the `__bytes__` method on the object you pass in.\n\n### Setting up an AX.25 Interface\n\nThe AX.25 interface is a logical routing and queueing layer which decodes the\ndata received from a KISS port and routes it according to the destination\ncall-sign.\n\n`AX25Interface` is found in the `aioax25.interface` package. Import that, then\ndo the following to set up your interface:\n\n```python\n ax25int = AX25Interface(\n kissport=kissdev[0], # or whatever port number you need\n log=logging.getLogger('your.ax25.log')\n )\n```\n\nSome optional parameters:\n * `cts_delay`, `cts_rand`: The number of seconds to wait after making a\n transmission/receiving a transmission, before we send another transmission.\n The delay time is `cts_delay + (random.random() * cts_rand)`, the idea\n being to avoid doubling when two stations attempt transmission.\n\nThe `AX25Interface` is a subclass of `Router` (see `aioax25.router`), which\nexposes the following methods and properties:\n\n * `received_msg`: This is a `Signal` object which is fired for every AX.25\n frame received. Slots are expected to take two keyword arguments:\n `interface` (the interface that received the frame) and `frame` (the\n AX.25 frame itself).\n\n * `bind(callback, callsign, ssid=0, regex=False)`: This method allows you to\n bind a call-back function to receive AX.25 frames whose `destination` field\n is addressed to the call-sign and SSID specified. The call-sign may be a\n regular expression if `regex=True`. This will be compiled and matched\n against all incoming traffic. Regardless of the value of `regex`, the\n `callsign` parameter _must_ be a string.\n\n * `unbind(callback, callsign, ssid=0, regex=False)`: This method un-binds a\n previously bound call-back method from receiving the nominated traffic.\n\nAdditionally, for transmitting frames, `AX25Interface` adds the following:\n\n * `transmit(frame, callback=None)`: This method allows you to transmit\n arbitrary AX.25 frames. They are assumed to be instances of `AX25Frame`\n (from `aioax25.frame`). The `callback`, if given, will be called once the\n frame is sent with the following keyword arguments: `interface` (the\n `AX25Interface` that sent the frame), `frame` (the frame that was sent).\n\n * `cancel_transmit(frame)`: This cancels a pending transmission of a frame.\n If the frame has been sent, this has no effect.\n\n## APRS Traffic handling\n\nThe `AX25Interface` just deals in AX.25 traffic, and does not provide any\nspecial handling of APRS UI frames. For this, one may look at `APRSInterface`.\n\nImport this from `aioax25.aprs`. It too, is a subclass of `Router`, and so\n`bind`, `unbind` and `received_msg` are there -- the messages received will\nbe instances of `APRSFrame` (see `aioax25.aprs.frame`), otherwise the behaviour\nis identical.\n\n```python\n aprsint = APRSInterface(\n ax25int=ax25int, # Your AX25Interface object\n mycall='VK4MSL-9', # Your call-sign and SSID\n log=logging.getLogger('your.aprs.log')\n )\n```\n\nOther optional parameters:\n * `retransmit_count`, `retransmit_timeout_base`, `retransmit_timeout_rand`,\n `retransmit_timeout_scale`: These control the timing of retransmissions\n when sending _confirmable_ APRS messages. Before transmission, a time-out\n is computed as `timeout = retransmit_timeout_base + (random.random() *\n retransmit_timeout_rand)`, and a retry counter is initialised to\n `retransmit_count`. On each re-transmission, the retry counter is\n decremented and the timeout is multiplied by `retransmit_timeout_scale`.\n * `aprs_destination`: This sets the destination call-sign used for APRS\n traffic. Right now, we use the experimental call of `APZAIO` for all\n traffic except direct messages (which instead are sent directly to the\n station addressed).\n * `aprs_path` specifies the digipeater path to use when sending APRS traffic.\n * `listen_destinations` is a list of AX.25 destinations. Behind the scenes,\n these are values passed to `Router.bind`, and thus are given as `dict`s of\n the form: `{callsign: \"CALL\", regex: True/False, ssid: None/int}`.\n * `listen_altnets` is an additional list of AX.25 destinations, given using\n the same scheme as `listen_destinations`.\n * `msgid_modulo` sets the modulo value used when generating a message ID.\n The default value (1000) results in a message ID that starts at 1 and wraps\n around at 999.\n * `deduplication_expiry` sets the number of seconds we store message hashes\n for de-duplication purposes. The default is 28 seconds.\n\nTo send APRS messages, there is `send_message` and `send_response`:\n\n * `send_message(addressee, path=None, oneshot=False, replyack=False)`:\n This sends an APRS message to the addressed station. If `path` is `None`,\n then the `aprs_path` is used. If `oneshot=True`, then the message is sent\n without a message ID, no ACK/REJ is expected and no retransmissions will be\n made, the method returns `None`. Otherwise, a `APRSMessageHandler` (from\n `aioax25.aprs.message`) is returned.\n * If `replyack` is set to `True`, then the message will advertise\n [reply-ack](http://www.aprs.org/aprs11/replyacks.txt) capability to\n the recipient. Not all APRS implementations support this.\n * If `replyack` references an incoming message which itself has `replyack`\n set (either to `True` or to a previous message ID), then the outgoing\n message will have a reply-ack suffix appended to \"ack\" the given message.\n * The default of `replyack=False` disables all reply-ack capability (an\n incoming reply-ack message will still be treated as an ACK however).\n * `send_response(message, ack=True)`: This is used when you have received\n a message from another station -- passing that message to this function\n will send a `ACK` or `REJ` message to that station.\n\n### The `APRSMessageHandler` class\n\nThe `APRSMessageHandler` class implements the APRS message retransmission\nlogic. The objects have a `done` signal which is emitted upon any of the\nfollowing events:\n\n * Message time-out (no ACK/REJ received) (`state=HandlerState.TIMEOUT`)\n * Message was cancelled (via the `cancel()` method)\n (`state=HandlerState.CANCEL`)\n * An ACK or REJ frame was received (`state=HandlerState.SUCCESS` or\n `state=HandlerState.REJECT`)\n\nThe signal will call call-back functions with the following keyword arguments:\n * `handler`: The `APRSMessageHandler` object emitting the signal\n * `state`: The state of the `APRSMessageHandler` object.\n\n### APRS Digipeating\n\n`aioax25` includes a module that implements basic digipeating for APRS\nincluding handling of the `WIDEn-N` SSIDs. The implementation treats `WIDE`\nlike `TRACE`: inserting the station's own call-sign in the path (which I\nbelieve is more compliant with the [Amateur License Conditions\nDetermination](https://www.legislation.gov.au/Details/F2016C00286) in that it\nensures each digipeater \"identifies\" itself).\n\nThe `aioax25.aprs.uidigi` module can be configured to digipeat for other\naliases such as the legacy `WIDE` and `RELAY`, or any alias of your choosing.\n\nIt is capable of handling multiple interfaces, but will repeat incoming\nmessages on the interface they were received from *ONLY*. (i.e. if you connect\na 2m interface and a HF interface, it will *NOT* digipeat from HF to 2m).\n\nSet-up is pretty simple:\n\n```\nfrom aioax25.aprs.uidigi import APRSDigipeater\n\n# Given an APRSInterface class (aprsint)\n# Create a digipeater instance\ndigipeater = APRSDigipeater()\n\n# Connect your interface\ndigipeater.connect(aprsint)\n\n# Optionally add any aliases you want handled\ndigipeater.addaliases('WIDE', 'GATE')\n```\n\nYou're now digipeating. The digipeater will automatically handle `WIDEn-N` and\n`TRACEn-N`, and in the above example, will also digipeat for `WIDE`, `GATE`.\n\n#### Preventing message loops on busy networks\n\nIf you have a *lot* of digipeaters in close proximity (say about 6) and there's\na lot of traffic, you can get the situation where a message queued up to be\ndigipeated sits in the transmit queue longer than the 28 seconds needed for\nother digipeaters to \"forget\" the message.\n\nThis leads to a network with the memory of an elephant, it almost never forgets\na message because the digipeats come more than 30 seconds *after* the original.\n\nThe `APRSDigipeater` class constructor can take a single parameter,\n`digipeater_timeout`, which sets an expiry (default of 5 seconds) on queued\ndigipeat messages. If a message is not sent by the time this timeout expires,\nthe message is quietly dropped, preventing the memory effect.", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/sjlongland/aioax25/", "keywords": "", "license": "GPL-2.0-or-later", "maintainer": "", "maintainer_email": "", "name": "aioax25", "package_url": "https://pypi.org/project/aioax25/", "platform": "", "project_url": "https://pypi.org/project/aioax25/", "project_urls": { "Homepage": "https://github.com/sjlongland/aioax25/" }, "release_url": "https://pypi.org/project/aioax25/0.0.8/", "requires_dist": null, "requires_python": "", "summary": "Asynchronous AX.25 interface in pure Python using asyncio", "version": "0.0.8" }, "last_serial": 5901577, "releases": { "0.0.5": [ { "comment_text": "", "digests": { "md5": "0ff0d0b702f82a848209c21a656246d5", "sha256": "b8befb0f54e52d26fd452f34abb4338cc6d394fb9d11289ea55f3d60d4518859" }, "downloads": -1, "filename": "aioax25-0.0.5.tar.gz", "has_sig": true, "md5_digest": "0ff0d0b702f82a848209c21a656246d5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 48960, "upload_time": "2019-06-29T09:38:45", "url": "https://files.pythonhosted.org/packages/3f/68/fb509ed00871aacd5620cd68a1a77307969bc4cfcb149fa363d4188940c0/aioax25-0.0.5.tar.gz" } ], "0.0.6": [ { "comment_text": "", "digests": { "md5": "656d700bedc72b02566550160bfcb370", "sha256": "fdf8b94c0fe7d83f71751b104d4b1333613987292b27c6b2b873c26480650675" }, "downloads": -1, "filename": "aioax25-0.0.6.tar.gz", "has_sig": true, "md5_digest": "656d700bedc72b02566550160bfcb370", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49825, "upload_time": "2019-07-09T00:02:04", "url": "https://files.pythonhosted.org/packages/b9/41/8f99b7f50d74b0edd313964820647c8235345faae2d736a55cdf2fdfb643/aioax25-0.0.6.tar.gz" } ], "0.0.7": [ { "comment_text": "", "digests": { "md5": "aa5c746f4b29d5537b7b30e799dec47a", "sha256": "1aa6228098e1f81d505fb9cbf72259c387a2e2793654fdd9e99bd9e021bf8632" }, "downloads": -1, "filename": "aioax25-0.0.7.tar.gz", "has_sig": true, "md5_digest": "aa5c746f4b29d5537b7b30e799dec47a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50038, "upload_time": "2019-07-09T02:44:54", "url": "https://files.pythonhosted.org/packages/06/54/c487a9944e8f6c188371221e1d6ce531d1a57ae8709fa5d25a935c152782/aioax25-0.0.7.tar.gz" } ], "0.0.8": [ { "comment_text": "", "digests": { "md5": "9becead5b4968f6daf4c7a66799d08c0", "sha256": "b75c9b6d54935b4204829fa4e7bb5b6abe6ea50f315fcc392e75d87304f2ec36" }, "downloads": -1, "filename": "aioax25-0.0.8.tar.gz", "has_sig": true, "md5_digest": "9becead5b4968f6daf4c7a66799d08c0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51667, "upload_time": "2019-09-29T01:04:49", "url": "https://files.pythonhosted.org/packages/ea/f5/bcdaae0e14843a8837fc832a2961860bf45d4ddf81620fd3c40c019bdaac/aioax25-0.0.8.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "9becead5b4968f6daf4c7a66799d08c0", "sha256": "b75c9b6d54935b4204829fa4e7bb5b6abe6ea50f315fcc392e75d87304f2ec36" }, "downloads": -1, "filename": "aioax25-0.0.8.tar.gz", "has_sig": true, "md5_digest": "9becead5b4968f6daf4c7a66799d08c0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51667, "upload_time": "2019-09-29T01:04:49", "url": "https://files.pythonhosted.org/packages/ea/f5/bcdaae0e14843a8837fc832a2961860bf45d4ddf81620fd3c40c019bdaac/aioax25-0.0.8.tar.gz" } ] }