aprsd 4.1.2__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. {aprsd-4.1.2 → aprsd-4.2.0}/ChangeLog.md +23 -0
  2. {aprsd-4.1.2 → aprsd-4.2.0}/PKG-INFO +48 -48
  3. aprsd-4.2.0/aprsd/client/__init__.py +5 -0
  4. aprsd-4.2.0/aprsd/client/client.py +141 -0
  5. aprsd-4.2.0/aprsd/client/drivers/__init__.py +10 -0
  6. {aprsd-4.1.2/aprsd/client → aprsd-4.2.0/aprsd/client/drivers}/aprsis.py +104 -82
  7. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/client/drivers/fake.py +59 -11
  8. aprsd-4.1.2/aprsd/client/drivers/aprsis.py → aprsd-4.2.0/aprsd/client/drivers/lib/aprslib.py +13 -3
  9. aprsd-4.2.0/aprsd/client/drivers/registry.py +86 -0
  10. aprsd-4.2.0/aprsd/client/drivers/tcpkiss.py +408 -0
  11. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/client/stats.py +2 -2
  12. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/dev.py +0 -3
  13. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/listen.py +3 -3
  14. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/send_message.py +4 -4
  15. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/server.py +4 -10
  16. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/log/log.py +1 -1
  17. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/main.py +0 -7
  18. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/core.py +168 -169
  19. aprsd-4.2.0/aprsd/packets/log.py +171 -0
  20. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugin.py +3 -2
  21. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugin_utils.py +2 -2
  22. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/weather.py +2 -2
  23. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/stats/collector.py +5 -4
  24. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/rx.py +12 -10
  25. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/tx.py +32 -31
  26. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/keepalive_collector.py +7 -5
  27. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd.egg-info/PKG-INFO +48 -48
  28. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd.egg-info/SOURCES.txt +12 -9
  29. aprsd-4.2.0/aprsd.egg-info/requires.txt +92 -0
  30. aprsd-4.2.0/docker/docker-compose.yml +27 -0
  31. {aprsd-4.1.2 → aprsd-4.2.0}/requirements-dev.in +1 -1
  32. {aprsd-4.1.2 → aprsd-4.2.0}/requirements-dev.txt +24 -24
  33. {aprsd-4.1.2 → aprsd-4.2.0}/requirements.txt +21 -22
  34. aprsd-4.2.0/tests/client/drivers/test_aprsis_driver.py +440 -0
  35. aprsd-4.2.0/tests/client/drivers/test_fake_driver.py +191 -0
  36. aprsd-4.2.0/tests/client/drivers/test_tcpkiss_driver.py +498 -0
  37. aprsd-4.2.0/tests/client/test_registry.py +100 -0
  38. aprsd-4.2.0/tests/cmds/__init__.py +0 -0
  39. aprsd-4.2.0/tests/mock_client_driver.py +76 -0
  40. aprsd-4.2.0/tests/plugins/__init__.py +0 -0
  41. {aprsd-4.1.2 → aprsd-4.2.0}/tests/plugins/test_notify.py +43 -36
  42. aprsd-4.2.0/tests/plugins/test_version.py +64 -0
  43. {aprsd-4.1.2 → aprsd-4.2.0}/tests/test_plugin.py +38 -18
  44. {aprsd-4.1.2 → aprsd-4.2.0}/tox.ini +1 -1
  45. aprsd-4.2.0/uv.lock +1346 -0
  46. aprsd-4.1.2/aprsd/client/__init__.py +0 -13
  47. aprsd-4.1.2/aprsd/client/base.py +0 -156
  48. aprsd-4.1.2/aprsd/client/drivers/kiss.py +0 -144
  49. aprsd-4.1.2/aprsd/client/factory.py +0 -91
  50. aprsd-4.1.2/aprsd/client/fake.py +0 -49
  51. aprsd-4.1.2/aprsd/client/kiss.py +0 -143
  52. aprsd-4.1.2/aprsd/packets/log.py +0 -161
  53. aprsd-4.1.2/aprsd.egg-info/requires.txt +0 -93
  54. aprsd-4.1.2/docker/docker-compose.yml +0 -14
  55. aprsd-4.1.2/tests/client/test_aprsis.py +0 -89
  56. aprsd-4.1.2/tests/client/test_client_base.py +0 -141
  57. aprsd-4.1.2/tests/client/test_factory.py +0 -75
  58. aprsd-4.1.2/tests/plugins/test_version.py +0 -35
  59. aprsd-4.1.2/uv.lock +0 -1340
  60. {aprsd-4.1.2 → aprsd-4.2.0}/.coveragerc +0 -0
  61. {aprsd-4.1.2 → aprsd-4.2.0}/.github/workflows/authors.yml +0 -0
  62. {aprsd-4.1.2 → aprsd-4.2.0}/.github/workflows/codeql.yml +0 -0
  63. {aprsd-4.1.2 → aprsd-4.2.0}/.github/workflows/manual_build.yml +0 -0
  64. {aprsd-4.1.2 → aprsd-4.2.0}/.github/workflows/master-build.yml +0 -0
  65. {aprsd-4.1.2 → aprsd-4.2.0}/.github/workflows/python.yml +0 -0
  66. {aprsd-4.1.2 → aprsd-4.2.0}/.github/workflows/release_build.yml +0 -0
  67. {aprsd-4.1.2 → aprsd-4.2.0}/.mailmap +0 -0
  68. {aprsd-4.1.2 → aprsd-4.2.0}/.pre-commit-config.yaml +0 -0
  69. {aprsd-4.1.2 → aprsd-4.2.0}/.readthedocs.yaml +0 -0
  70. {aprsd-4.1.2 → aprsd-4.2.0}/AUTHORS +0 -0
  71. {aprsd-4.1.2 → aprsd-4.2.0}/CONTRIBUTING.md +0 -0
  72. {aprsd-4.1.2 → aprsd-4.2.0}/INSTALL.txt +0 -0
  73. {aprsd-4.1.2 → aprsd-4.2.0}/LICENSE +0 -0
  74. {aprsd-4.1.2 → aprsd-4.2.0}/MANIFEST.in +0 -0
  75. {aprsd-4.1.2 → aprsd-4.2.0}/Makefile +0 -0
  76. {aprsd-4.1.2 → aprsd-4.2.0}/README.md +0 -0
  77. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/__init__.py +0 -0
  78. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cli_helper.py +0 -0
  79. {aprsd-4.1.2/aprsd/client/drivers → aprsd-4.2.0/aprsd/client/drivers/lib}/__init__.py +0 -0
  80. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/__init__.py +0 -0
  81. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/completion.py +0 -0
  82. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/fetch_stats.py +0 -0
  83. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/healthcheck.py +0 -0
  84. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/cmds/list_plugins.py +0 -0
  85. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/conf/__init__.py +0 -0
  86. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/conf/client.py +0 -0
  87. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/conf/common.py +0 -0
  88. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/conf/log.py +0 -0
  89. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/conf/opts.py +0 -0
  90. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/conf/plugin_common.py +0 -0
  91. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/exception.py +0 -0
  92. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/log/__init__.py +0 -0
  93. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/__init__.py +0 -0
  94. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/collector.py +0 -0
  95. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/filter.py +0 -0
  96. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/filters/__init__.py +0 -0
  97. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/filters/dupe_filter.py +0 -0
  98. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/filters/packet_type.py +0 -0
  99. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/packet_list.py +0 -0
  100. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/seen_list.py +0 -0
  101. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/tracker.py +0 -0
  102. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/packets/watch_list.py +0 -0
  103. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/__init__.py +0 -0
  104. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/fortune.py +0 -0
  105. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/notify.py +0 -0
  106. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/ping.py +0 -0
  107. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/time.py +0 -0
  108. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/plugins/version.py +0 -0
  109. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/stats/__init__.py +0 -0
  110. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/stats/app.py +0 -0
  111. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/__init__.py +0 -0
  112. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/aprsd.py +0 -0
  113. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/keepalive.py +0 -0
  114. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/registry.py +0 -0
  115. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/service.py +0 -0
  116. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/threads/stats.py +0 -0
  117. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/__init__.py +0 -0
  118. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/counter.py +0 -0
  119. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/fuzzyclock.py +0 -0
  120. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/json.py +0 -0
  121. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/objectstore.py +0 -0
  122. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/ring_buffer.py +0 -0
  123. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd/utils/trace.py +0 -0
  124. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd-lnav.json +0 -0
  125. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd.egg-info/dependency_links.txt +0 -0
  126. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd.egg-info/entry_points.txt +0 -0
  127. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd.egg-info/top_level.txt +0 -0
  128. {aprsd-4.1.2 → aprsd-4.2.0}/aprsd_logo.png +0 -0
  129. {aprsd-4.1.2 → aprsd-4.2.0}/docker/Dockerfile +0 -0
  130. {aprsd-4.1.2 → aprsd-4.2.0}/docker/bin/admin.sh +0 -0
  131. {aprsd-4.1.2 → aprsd-4.2.0}/docker/bin/healthcheck.sh +0 -0
  132. {aprsd-4.1.2 → aprsd-4.2.0}/docker/bin/listen.sh +0 -0
  133. {aprsd-4.1.2 → aprsd-4.2.0}/docker/bin/run.sh +0 -0
  134. {aprsd-4.1.2 → aprsd-4.2.0}/docker/bin/setup.sh +0 -0
  135. {aprsd-4.1.2 → aprsd-4.2.0}/docker/build.sh +0 -0
  136. {aprsd-4.1.2 → aprsd-4.2.0}/docs/_static/.keep +0 -0
  137. {aprsd-4.1.2 → aprsd-4.2.0}/docs/_static/aprsd_overview.png +0 -0
  138. {aprsd-4.1.2 → aprsd-4.2.0}/docs/_static/aprsd_overview.svg +0 -0
  139. {aprsd-4.1.2 → aprsd-4.2.0}/docs/_templates/.keep +0 -0
  140. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.client.drivers.rst +0 -0
  141. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.client.rst +0 -0
  142. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.cmds.rst +0 -0
  143. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.conf.rst +0 -0
  144. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.log.rst +0 -0
  145. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.packets.rst +0 -0
  146. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.plugins.rst +0 -0
  147. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.rst +0 -0
  148. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.stats.rst +0 -0
  149. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.threads.rst +0 -0
  150. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/aprsd.utils.rst +0 -0
  151. {aprsd-4.1.2 → aprsd-4.2.0}/docs/apidoc/modules.rst +0 -0
  152. {aprsd-4.1.2 → aprsd-4.2.0}/docs/aprsd.drawio +0 -0
  153. {aprsd-4.1.2 → aprsd-4.2.0}/docs/changelog.rst +0 -0
  154. {aprsd-4.1.2 → aprsd-4.2.0}/docs/clean_docs.py +0 -0
  155. {aprsd-4.1.2 → aprsd-4.2.0}/docs/conf.py +0 -0
  156. {aprsd-4.1.2 → aprsd-4.2.0}/docs/configure.rst +0 -0
  157. {aprsd-4.1.2 → aprsd-4.2.0}/docs/index.rst +0 -0
  158. {aprsd-4.1.2 → aprsd-4.2.0}/docs/install.rst +0 -0
  159. {aprsd-4.1.2 → aprsd-4.2.0}/docs/links.rst +0 -0
  160. {aprsd-4.1.2 → aprsd-4.2.0}/docs/plugin.rst +0 -0
  161. {aprsd-4.1.2 → aprsd-4.2.0}/docs/readme.rst +0 -0
  162. {aprsd-4.1.2 → aprsd-4.2.0}/docs/server.rst +0 -0
  163. {aprsd-4.1.2 → aprsd-4.2.0}/examples/plugins/__init__.py +0 -0
  164. {aprsd-4.1.2 → aprsd-4.2.0}/examples/plugins/example_plugin.py +0 -0
  165. {aprsd-4.1.2 → aprsd-4.2.0}/gray.conf +0 -0
  166. {aprsd-4.1.2 → aprsd-4.2.0}/pyproject.toml +0 -0
  167. {aprsd-4.1.2 → aprsd-4.2.0}/requirements.in +0 -0
  168. {aprsd-4.1.2 → aprsd-4.2.0}/setup.cfg +0 -0
  169. {aprsd-4.1.2 → aprsd-4.2.0}/setup.py +0 -0
  170. {aprsd-4.1.2 → aprsd-4.2.0}/tests/__init__.py +0 -0
  171. {aprsd-4.1.2/tests/cmds → aprsd-4.2.0/tests/client}/__init__.py +0 -0
  172. {aprsd-4.1.2/tests/plugins → aprsd-4.2.0/tests/client/drivers}/__init__.py +0 -0
  173. {aprsd-4.1.2 → aprsd-4.2.0}/tests/cmds/test_send_message.py +0 -0
  174. {aprsd-4.1.2 → aprsd-4.2.0}/tests/fake.py +0 -0
  175. {aprsd-4.1.2 → aprsd-4.2.0}/tests/plugins/test_fortune.py +0 -0
  176. {aprsd-4.1.2 → aprsd-4.2.0}/tests/plugins/test_ping.py +0 -0
  177. {aprsd-4.1.2 → aprsd-4.2.0}/tests/plugins/test_time.py +0 -0
  178. {aprsd-4.1.2 → aprsd-4.2.0}/tests/plugins/test_weather.py +0 -0
  179. {aprsd-4.1.2 → aprsd-4.2.0}/tests/test_packets.py +0 -0
  180. {aprsd-4.1.2 → aprsd-4.2.0}/tools/fast8.sh +0 -0
@@ -4,11 +4,34 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [4.2.0](https://github.com/craigerl/aprsd/compare/4.1.2...4.2.0)
8
+
9
+ > 12 August 2025
10
+
11
+ - Reworked the entire client and drivers [`#191`](https://github.com/craigerl/aprsd/pull/191)
12
+ - Update the docker-compose.yml [`#193`](https://github.com/craigerl/aprsd/pull/193)
13
+ - Use OpenWeatherMap One Call v3.0 API [`#186`](https://github.com/craigerl/aprsd/pull/186)
14
+ - use OWM onecall v3.0 [`16bfda4`](https://github.com/craigerl/aprsd/commit/16bfda431c54a2ec6fbd011edd710c1d787ae1de)
15
+ - Fixed a problem with WeatherPacket [`5469610`](https://github.com/craigerl/aprsd/commit/54696107797c3d788e5d0b2dad164e1a7cf439f2)
16
+ - Ensure filter is set [`4c53c13`](https://github.com/craigerl/aprsd/commit/4c53c13e793356228f97e26a275d3d4c54b4bf54)
17
+ - Remove flask_enabled [`ce79f01`](https://github.com/craigerl/aprsd/commit/ce79f0112ff0f80db8adfff03c9ec8e62655bcc0)
18
+ - Make some catchall fields non hashable. [`acd639a`](https://github.com/craigerl/aprsd/commit/acd639a9100893a2e1d08d9ba65ef65ce6ed87e6)
19
+ - Fixed setup for AVWXWeatherPlugin [`2fcd574`](https://github.com/craigerl/aprsd/commit/2fcd574f12d48eaec122f96b6b15e9408d395b72)
20
+ - removed old flask_enabled global [`8f471c2`](https://github.com/craigerl/aprsd/commit/8f471c229c1073834c2ac66522fbaf2a561e296a)
21
+ - Updated requirements [`211ac11`](https://github.com/craigerl/aprsd/commit/211ac1132b2f1e2736b1c680936711e114556e90)
22
+ - Don't log an IOError on shutdown [`d41064b`](https://github.com/craigerl/aprsd/commit/d41064ba05faf6167c0d26db2ab525d0bb410bf8)
23
+ - Honor quiet setting for log.setup_logging [`1ae4375`](https://github.com/craigerl/aprsd/commit/1ae437581f753d903c20a6ef54868438eb034800)
24
+ - Make sure packet has addressee field [`034f11b`](https://github.com/craigerl/aprsd/commit/034f11b4f9f3392e003cfbd0e5f8b9fb5f594089)
25
+ - Fix tox failures [`74887af`](https://github.com/craigerl/aprsd/commit/74887af507755cd7ee27f13129b177f4d58ca8e0)
26
+ - log the exception when tx fails. [`fa5d0c6`](https://github.com/craigerl/aprsd/commit/fa5d0c643ae85fc9ad3f615f9d0afc590f422283)
27
+ - Updated requirements for 4.2.0 [`2c476d8`](https://github.com/craigerl/aprsd/commit/2c476d8a04df8cebbaa50780688e58d8b9df2bc9)
28
+
7
29
  #### [4.1.2](https://github.com/craigerl/aprsd/compare/4.1.1...4.1.2)
8
30
 
9
31
  > 6 March 2025
10
32
 
11
33
  - Allow passing in a custom handler to setup_logging [`d262589`](https://github.com/craigerl/aprsd/commit/d2625893134f498748859da3b1684b04d456f790)
34
+ - Changelog for 4.1.2 [`6dba56f`](https://github.com/craigerl/aprsd/commit/6dba56f74d27e418ed2e124e7a3592c274b2da1e)
12
35
 
13
36
  #### [4.1.1](https://github.com/craigerl/aprsd/compare/4.1.0...4.1.1)
14
37
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: aprsd
3
- Version: 4.1.2
3
+ Version: 4.2.0
4
4
  Summary: APRSd is a APRS-IS server that can be used to connect to APRS-IS and send and receive APRS packets.
5
5
  Author-email: Craig Lamparter <craig@craiger.org>, "Walter A. Boring IV" <waboring@hemna.com>, Emre Saglam <emresaglam@gmail.com>, Jason Martin <jhmartin@toger.us>, John <johng42@users.noreply.github.com>, Martiros Shakhzadyan <vrzh@vrzh.net>, Zoe Moore <zoenb@mailbox.org>, ranguli <hello@joshmurphy.ca>
6
6
  Maintainer-email: Craig Lamparter <craig@craiger.org>, "Walter A. Boring IV" <waboring@hemna.com>
@@ -202,84 +202,83 @@ Description-Content-Type: text/markdown
202
202
  License-File: LICENSE
203
203
  License-File: AUTHORS
204
204
  Requires-Dist: aprslib==0.7.2
205
- Requires-Dist: attrs==25.1.0
205
+ Requires-Dist: attrs==25.3.0
206
206
  Requires-Dist: ax253==0.1.5.post1
207
- Requires-Dist: bitarray==3.1.0
208
- Requires-Dist: certifi==2025.1.31
209
- Requires-Dist: charset-normalizer==3.4.1
210
- Requires-Dist: click==8.1.8
207
+ Requires-Dist: bitarray==3.6.1
208
+ Requires-Dist: certifi==2025.8.3
209
+ Requires-Dist: charset-normalizer==3.4.3
210
+ Requires-Dist: click==8.2.1
211
211
  Requires-Dist: dataclasses-json==0.6.7
212
- Requires-Dist: debtcollector==3.0.0
213
212
  Requires-Dist: haversine==2.9.0
214
213
  Requires-Dist: idna==3.10
215
- Requires-Dist: importlib-metadata==8.6.1
214
+ Requires-Dist: importlib-metadata==8.7.0
216
215
  Requires-Dist: kiss3==8.0.0
217
216
  Requires-Dist: loguru==0.7.3
218
- Requires-Dist: markdown-it-py==3.0.0
217
+ Requires-Dist: markdown-it-py==4.0.0
219
218
  Requires-Dist: marshmallow==3.26.1
220
219
  Requires-Dist: mdurl==0.1.2
221
- Requires-Dist: mypy-extensions==1.0.0
220
+ Requires-Dist: mypy-extensions==1.1.0
222
221
  Requires-Dist: netaddr==1.3.0
223
- Requires-Dist: oslo-config==9.7.1
222
+ Requires-Dist: oslo-config==10.0.0
224
223
  Requires-Dist: oslo-i18n==6.5.1
225
- Requires-Dist: packaging==24.2
224
+ Requires-Dist: packaging==25.0
226
225
  Requires-Dist: pbr==6.1.1
227
- Requires-Dist: pluggy==1.5.0
228
- Requires-Dist: pygments==2.19.1
226
+ Requires-Dist: pluggy==1.6.0
227
+ Requires-Dist: pygments==2.19.2
229
228
  Requires-Dist: pyserial==3.5
230
229
  Requires-Dist: pyserial-asyncio==0.6
231
- Requires-Dist: pytz==2025.1
230
+ Requires-Dist: pytz==2025.2
232
231
  Requires-Dist: pyyaml==6.0.2
233
- Requires-Dist: requests==2.32.3
232
+ Requires-Dist: requests==2.32.4
234
233
  Requires-Dist: rfc3986==2.0.0
235
- Requires-Dist: rich==13.9.4
234
+ Requires-Dist: rich==14.1.0
236
235
  Requires-Dist: rush==2021.4.0
237
- Requires-Dist: setuptools==75.8.2
236
+ Requires-Dist: setuptools==80.9.0
238
237
  Requires-Dist: stevedore==5.4.1
239
238
  Requires-Dist: thesmuggler==1.0.1
240
239
  Requires-Dist: timeago==1.0.16
241
- Requires-Dist: typing-extensions==4.12.2
240
+ Requires-Dist: typing-extensions==4.14.1
242
241
  Requires-Dist: typing-inspect==0.9.0
243
- Requires-Dist: tzlocal==5.3
242
+ Requires-Dist: tzlocal==5.3.1
244
243
  Requires-Dist: update-checker==0.18.0
245
- Requires-Dist: urllib3==2.3.0
246
- Requires-Dist: wrapt==1.17.2
247
- Requires-Dist: zipp==3.21.0
244
+ Requires-Dist: urllib3==2.5.0
245
+ Requires-Dist: wrapt==1.17.3
246
+ Requires-Dist: zipp==3.23.0
248
247
  Provides-Extra: dev
249
248
  Requires-Dist: alabaster==1.0.0; extra == "dev"
250
249
  Requires-Dist: babel==2.17.0; extra == "dev"
251
- Requires-Dist: build==1.2.2.post1; extra == "dev"
252
- Requires-Dist: cachetools==5.5.2; extra == "dev"
253
- Requires-Dist: certifi==2025.1.31; extra == "dev"
250
+ Requires-Dist: build==1.3.0; extra == "dev"
251
+ Requires-Dist: cachetools==6.1.0; extra == "dev"
252
+ Requires-Dist: certifi==2025.8.3; extra == "dev"
254
253
  Requires-Dist: cfgv==3.4.0; extra == "dev"
255
254
  Requires-Dist: chardet==5.2.0; extra == "dev"
256
- Requires-Dist: charset-normalizer==3.4.1; extra == "dev"
257
- Requires-Dist: click==8.1.8; extra == "dev"
255
+ Requires-Dist: charset-normalizer==3.4.3; extra == "dev"
256
+ Requires-Dist: click==8.2.1; extra == "dev"
258
257
  Requires-Dist: colorama==0.4.6; extra == "dev"
259
- Requires-Dist: distlib==0.3.9; extra == "dev"
258
+ Requires-Dist: distlib==0.4.0; extra == "dev"
260
259
  Requires-Dist: docutils==0.21.2; extra == "dev"
261
- Requires-Dist: filelock==3.17.0; extra == "dev"
262
- Requires-Dist: identify==2.6.8; extra == "dev"
260
+ Requires-Dist: filelock==3.18.0; extra == "dev"
261
+ Requires-Dist: identify==2.6.13; extra == "dev"
263
262
  Requires-Dist: idna==3.10; extra == "dev"
264
263
  Requires-Dist: imagesize==1.4.1; extra == "dev"
265
- Requires-Dist: jinja2==3.1.5; extra == "dev"
264
+ Requires-Dist: jinja2==3.1.6; extra == "dev"
266
265
  Requires-Dist: m2r==0.3.1; extra == "dev"
267
266
  Requires-Dist: markupsafe==3.0.2; extra == "dev"
268
267
  Requires-Dist: mistune==0.8.4; extra == "dev"
269
268
  Requires-Dist: nodeenv==1.9.1; extra == "dev"
270
- Requires-Dist: packaging==24.2; extra == "dev"
271
- Requires-Dist: pip==25.0.1; extra == "dev"
272
- Requires-Dist: pip-tools==7.4.1; extra == "dev"
273
- Requires-Dist: platformdirs==4.3.6; extra == "dev"
274
- Requires-Dist: pluggy==1.5.0; extra == "dev"
275
- Requires-Dist: pre-commit==4.1.0; extra == "dev"
276
- Requires-Dist: pygments==2.19.1; extra == "dev"
277
- Requires-Dist: pyproject-api==1.9.0; extra == "dev"
269
+ Requires-Dist: packaging==25.0; extra == "dev"
270
+ Requires-Dist: pip==25.2; extra == "dev"
271
+ Requires-Dist: pip-tools==7.5.0; extra == "dev"
272
+ Requires-Dist: platformdirs==4.3.8; extra == "dev"
273
+ Requires-Dist: pluggy==1.6.0; extra == "dev"
274
+ Requires-Dist: pre-commit==4.3.0; extra == "dev"
275
+ Requires-Dist: pygments==2.19.2; extra == "dev"
276
+ Requires-Dist: pyproject-api==1.9.1; extra == "dev"
278
277
  Requires-Dist: pyproject-hooks==1.2.0; extra == "dev"
279
278
  Requires-Dist: pyyaml==6.0.2; extra == "dev"
280
- Requires-Dist: requests==2.32.3; extra == "dev"
281
- Requires-Dist: setuptools==75.8.2; extra == "dev"
282
- Requires-Dist: snowballstemmer==2.2.0; extra == "dev"
279
+ Requires-Dist: requests==2.32.4; extra == "dev"
280
+ Requires-Dist: setuptools==80.9.0; extra == "dev"
281
+ Requires-Dist: snowballstemmer==3.0.1; extra == "dev"
283
282
  Requires-Dist: sphinx==8.1.3; extra == "dev"
284
283
  Requires-Dist: sphinxcontrib-applehelp==2.0.0; extra == "dev"
285
284
  Requires-Dist: sphinxcontrib-devhelp==2.0.0; extra == "dev"
@@ -288,11 +287,12 @@ Requires-Dist: sphinxcontrib-jsmath==1.0.1; extra == "dev"
288
287
  Requires-Dist: sphinxcontrib-qthelp==2.0.0; extra == "dev"
289
288
  Requires-Dist: sphinxcontrib-serializinghtml==2.0.0; extra == "dev"
290
289
  Requires-Dist: tomli==2.2.1; extra == "dev"
291
- Requires-Dist: tox==4.24.1; extra == "dev"
292
- Requires-Dist: typing-extensions==4.12.2; extra == "dev"
293
- Requires-Dist: urllib3==2.3.0; extra == "dev"
294
- Requires-Dist: virtualenv==20.29.2; extra == "dev"
290
+ Requires-Dist: tox==4.28.4; extra == "dev"
291
+ Requires-Dist: typing-extensions==4.14.1; extra == "dev"
292
+ Requires-Dist: urllib3==2.5.0; extra == "dev"
293
+ Requires-Dist: virtualenv==20.33.1; extra == "dev"
295
294
  Requires-Dist: wheel==0.45.1; extra == "dev"
295
+ Dynamic: license-file
296
296
 
297
297
  # APRSD - Ham radio APRS-IS Message platform software
298
298
 
@@ -0,0 +1,5 @@
1
+ # define the client transports here
2
+ TRANSPORT_APRSIS = 'aprsis'
3
+ TRANSPORT_TCPKISS = 'tcpkiss'
4
+ TRANSPORT_SERIALKISS = 'serialkiss'
5
+ TRANSPORT_FAKE = 'fake'
@@ -0,0 +1,141 @@
1
+ import logging
2
+ import threading
3
+ from typing import Callable
4
+
5
+ import timeago
6
+ import wrapt
7
+ from loguru import logger
8
+ from oslo_config import cfg
9
+
10
+ from aprsd.client import drivers # noqa - ensure drivers are registered
11
+ from aprsd.client.drivers.registry import DriverRegistry
12
+ from aprsd.packets import core
13
+ from aprsd.utils import keepalive_collector
14
+
15
+ CONF = cfg.CONF
16
+ LOG = logging.getLogger('APRSD')
17
+ LOGU = logger
18
+
19
+
20
+ class APRSDClient:
21
+ """APRSD client class.
22
+
23
+ This is a singleton class that provides a single instance of the APRSD client.
24
+ It is responsible for connecting to the appropriate APRSD client driver based on
25
+ the configuration.
26
+
27
+ """
28
+
29
+ _instance = None
30
+ driver = None
31
+ lock = threading.Lock()
32
+ filter = None
33
+
34
+ def __new__(cls, *args, **kwargs):
35
+ """This magic turns this into a singleton."""
36
+ if cls._instance is None:
37
+ cls._instance = super().__new__(cls)
38
+ keepalive_collector.KeepAliveCollector().register(cls)
39
+ return cls._instance
40
+
41
+ def __init__(self):
42
+ self.connected = False
43
+ self.login_status = {
44
+ 'success': False,
45
+ 'message': None,
46
+ }
47
+ if not self.driver:
48
+ self.driver = DriverRegistry().get_driver()
49
+ self.driver.setup_connection()
50
+
51
+ def stats(self, serializable=False) -> dict:
52
+ stats = {}
53
+ if self.driver:
54
+ stats = self.driver.stats(serializable=serializable)
55
+ return stats
56
+
57
+ @property
58
+ def is_enabled(self):
59
+ if not self.driver:
60
+ return False
61
+ return self.driver.is_enabled()
62
+
63
+ @property
64
+ def is_configured(self):
65
+ if not self.driver:
66
+ return False
67
+ return self.driver.is_configured()
68
+
69
+ # @property
70
+ # def is_connected(self):
71
+ # if not self.driver:
72
+ # return False
73
+ # return self.driver.is_connected()
74
+
75
+ @property
76
+ def login_success(self):
77
+ if not self.driver:
78
+ return False
79
+ return self.driver.login_success
80
+
81
+ @property
82
+ def login_failure(self):
83
+ if not self.driver:
84
+ return None
85
+ return self.driver.login_failure
86
+
87
+ def set_filter(self, filter):
88
+ self.filter = filter
89
+ if not self.driver:
90
+ return
91
+ self.driver.set_filter(filter)
92
+
93
+ def get_filter(self):
94
+ if not self.driver:
95
+ return None
96
+ return self.driver.filter
97
+
98
+ def is_alive(self):
99
+ return self.driver.is_alive()
100
+
101
+ def close(self):
102
+ if not self.driver:
103
+ return
104
+ self.driver.close()
105
+
106
+ @wrapt.synchronized(lock)
107
+ def reset(self):
108
+ """Call this to force a rebuild/reconnect."""
109
+ LOG.info('Resetting client connection.')
110
+ if self.driver:
111
+ self.driver.close()
112
+ self.driver.setup_connection()
113
+ if self.filter:
114
+ self.driver.set_filter(self.filter)
115
+ else:
116
+ LOG.warning('Client not initialized, nothing to reset.')
117
+
118
+ def send(self, packet: core.Packet) -> bool:
119
+ return self.driver.send(packet)
120
+
121
+ # For the keepalive collector
122
+ def keepalive_check(self):
123
+ # Don't check the first time through.
124
+ if not self.driver.is_alive and self._checks:
125
+ LOG.warning("Resetting client. It's not alive.")
126
+ self.reset()
127
+ self._checks = True
128
+
129
+ # For the keepalive collector
130
+ def keepalive_log(self):
131
+ if ka := self.driver.keepalive:
132
+ keepalive = timeago.format(ka)
133
+ else:
134
+ keepalive = 'N/A'
135
+ LOGU.opt(colors=True).info(f'<green>Client keepalive {keepalive}</green>')
136
+
137
+ def consumer(self, callback: Callable, raw: bool = False):
138
+ return self.driver.consumer(callback=callback, raw=raw)
139
+
140
+ def decode_packet(self, *args, **kwargs) -> core.Packet:
141
+ return self.driver.decode_packet(*args, **kwargs)
@@ -0,0 +1,10 @@
1
+ # All client drivers must be registered here
2
+ from aprsd.client.drivers.aprsis import APRSISDriver
3
+ from aprsd.client.drivers.fake import APRSDFakeDriver
4
+ from aprsd.client.drivers.registry import DriverRegistry
5
+ from aprsd.client.drivers.tcpkiss import TCPKISSDriver
6
+
7
+ driver_registry = DriverRegistry()
8
+ driver_registry.register(APRSDFakeDriver)
9
+ driver_registry.register(APRSISDriver)
10
+ driver_registry.register(TCPKISSDriver)
@@ -1,65 +1,39 @@
1
1
  import datetime
2
2
  import logging
3
3
  import time
4
+ from typing import Callable
4
5
 
5
- import timeago
6
6
  from aprslib.exceptions import LoginError
7
7
  from loguru import logger
8
8
  from oslo_config import cfg
9
9
 
10
10
  from aprsd import client, exception
11
- from aprsd.client import base
12
- from aprsd.client.drivers import aprsis
11
+ from aprsd.client.drivers.lib.aprslib import APRSLibClient
13
12
  from aprsd.packets import core
14
13
 
15
14
  CONF = cfg.CONF
16
- LOG = logging.getLogger("APRSD")
15
+ LOG = logging.getLogger('APRSD')
17
16
  LOGU = logger
18
17
 
19
18
 
20
- class APRSISClient(base.APRSClient):
19
+ # class APRSISDriver(metaclass=trace.TraceWrapperMetaclass):
20
+ class APRSISDriver:
21
+ """This is the APRS-IS driver for the APRSD client.
22
+
23
+ This driver uses our modified aprslib.IS class to connect to the APRS-IS server.
24
+
25
+ """
26
+
21
27
  _client = None
22
28
  _checks = False
23
29
 
24
30
  def __init__(self):
25
- max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
31
+ max_timeout = {'hours': 0.0, 'minutes': 2, 'seconds': 0}
26
32
  self.max_delta = datetime.timedelta(**max_timeout)
27
-
28
- def stats(self, serializable=False) -> dict:
29
- stats = {}
30
- if self.is_configured():
31
- if self._client:
32
- keepalive = self._client.aprsd_keepalive
33
- server_string = self._client.server_string
34
- if serializable:
35
- keepalive = keepalive.isoformat()
36
- else:
37
- keepalive = "None"
38
- server_string = "None"
39
- stats = {
40
- "connected": self.is_connected,
41
- "filter": self.filter,
42
- "login_status": self.login_status,
43
- "connection_keepalive": keepalive,
44
- "server_string": server_string,
45
- "transport": self.transport(),
46
- }
47
-
48
- return stats
49
-
50
- def keepalive_check(self):
51
- # Don't check the first time through.
52
- if not self.is_alive() and self._checks:
53
- LOG.warning("Resetting client. It's not alive.")
54
- self.reset()
55
- self._checks = True
56
-
57
- def keepalive_log(self):
58
- if ka := self._client.aprsd_keepalive:
59
- keepalive = timeago.format(ka)
60
- else:
61
- keepalive = "N/A"
62
- LOGU.opt(colors=True).info(f"<green>Client keepalive {keepalive}</green>")
33
+ self.login_status = {
34
+ 'success': False,
35
+ 'message': None,
36
+ }
63
37
 
64
38
  @staticmethod
65
39
  def is_enabled():
@@ -71,37 +45,31 @@ class APRSISClient(base.APRSClient):
71
45
 
72
46
  @staticmethod
73
47
  def is_configured():
74
- if APRSISClient.is_enabled():
48
+ if APRSISDriver.is_enabled():
75
49
  # Ensure that the config vars are correctly set
76
50
  if not CONF.aprs_network.login:
77
- LOG.error("Config aprs_network.login not set.")
51
+ LOG.error('Config aprs_network.login not set.')
78
52
  raise exception.MissingConfigOptionException(
79
- "aprs_network.login is not set.",
53
+ 'aprs_network.login is not set.',
80
54
  )
81
55
  if not CONF.aprs_network.password:
82
- LOG.error("Config aprs_network.password not set.")
56
+ LOG.error('Config aprs_network.password not set.')
83
57
  raise exception.MissingConfigOptionException(
84
- "aprs_network.password is not set.",
58
+ 'aprs_network.password is not set.',
85
59
  )
86
60
  if not CONF.aprs_network.host:
87
- LOG.error("Config aprs_network.host not set.")
61
+ LOG.error('Config aprs_network.host not set.')
88
62
  raise exception.MissingConfigOptionException(
89
- "aprs_network.host is not set.",
63
+ 'aprs_network.host is not set.',
90
64
  )
91
65
 
92
66
  return True
93
67
  return True
94
68
 
95
- def _is_stale_connection(self):
96
- delta = datetime.datetime.now() - self._client.aprsd_keepalive
97
- if delta > self.max_delta:
98
- LOG.error(f"Connection is stale, last heard {delta} ago.")
99
- return True
100
- return False
101
-
69
+ @property
102
70
  def is_alive(self):
103
71
  if not self._client:
104
- LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
72
+ LOG.warning(f'APRS_CLIENT {self._client} alive? NO!!!')
105
73
  return False
106
74
  return self._client.is_alive() and not self._is_stale_connection()
107
75
 
@@ -110,13 +78,8 @@ class APRSISClient(base.APRSClient):
110
78
  self._client.stop()
111
79
  self._client.close()
112
80
 
113
- @staticmethod
114
- def transport():
115
- return client.TRANSPORT_APRSIS
116
-
117
- def decode_packet(self, *args, **kwargs):
118
- """APRS lib already decodes this."""
119
- return core.factory(args[0])
81
+ def send(self, packet: core.Packet) -> bool:
82
+ return self._client.send(packet)
120
83
 
121
84
  def setup_connection(self):
122
85
  user = CONF.aprs_network.login
@@ -125,7 +88,6 @@ class APRSISClient(base.APRSClient):
125
88
  port = CONF.aprs_network.port
126
89
  self.connected = False
127
90
  backoff = 1
128
- aprs_client = None
129
91
  retries = 3
130
92
  retry_count = 0
131
93
  while not self.connected:
@@ -134,27 +96,29 @@ class APRSISClient(base.APRSClient):
134
96
  break
135
97
  try:
136
98
  LOG.info(
137
- f"Creating aprslib client({host}:{port}) and logging in {user}."
99
+ f'Creating aprslib client({host}:{port}) and logging in {user}.'
138
100
  )
139
- aprs_client = aprsis.Aprsdis(
101
+ self._client = APRSLibClient(
140
102
  user, passwd=password, host=host, port=port
141
103
  )
142
104
  # Force the log to be the same
143
- aprs_client.logger = LOG
144
- aprs_client.connect()
145
- self.connected = self.login_status["success"] = True
146
- self.login_status["message"] = aprs_client.server_string
105
+ self._client.logger = LOG
106
+ self._client.connect()
107
+ self.connected = self.login_status['success'] = True
108
+ self.login_status['message'] = self._client.server_string
147
109
  backoff = 1
148
110
  except LoginError as e:
149
111
  LOG.error(f"Failed to login to APRS-IS Server '{e}'")
150
- self.connected = self.login_status["success"] = False
151
- self.login_status["message"] = e.message
152
- LOG.error(e.message)
112
+ self.connected = self.login_status['success'] = False
113
+ self.login_status['message'] = (
114
+ e.message if hasattr(e, 'message') else str(e)
115
+ )
116
+ LOG.error(self.login_status['message'])
153
117
  time.sleep(backoff)
154
118
  except Exception as e:
155
119
  LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
156
- self.connected = self.login_status["success"] = False
157
- self.login_status["message"] = e.message
120
+ self.connected = self.login_status['success'] = False
121
+ self.login_status['message'] = getattr(e, 'message', str(e))
158
122
  time.sleep(backoff)
159
123
  # Don't allow the backoff to go to inifinity.
160
124
  if backoff > 5:
@@ -162,16 +126,50 @@ class APRSISClient(base.APRSClient):
162
126
  else:
163
127
  backoff += 1
164
128
  continue
165
- self._client = aprs_client
166
- return aprs_client
167
129
 
168
- def consumer(self, callback, blocking=False, immortal=False, raw=False):
130
+ def set_filter(self, filter):
131
+ self._client.set_filter(filter)
132
+
133
+ def login_success(self) -> bool:
134
+ return self.login_status.get('success', False)
135
+
136
+ def login_failure(self) -> str:
137
+ return self.login_status.get('message', None)
138
+
139
+ @property
140
+ def filter(self):
141
+ return self._client.filter
142
+
143
+ @property
144
+ def server_string(self):
145
+ return self._client.server_string
146
+
147
+ @property
148
+ def keepalive(self):
149
+ return self._client.aprsd_keepalive
150
+
151
+ def _is_stale_connection(self):
152
+ delta = datetime.datetime.now() - self._client.aprsd_keepalive
153
+ if delta > self.max_delta:
154
+ LOG.error(f'Connection is stale, last heard {delta} ago.')
155
+ return True
156
+ return False
157
+
158
+ @staticmethod
159
+ def transport():
160
+ return client.TRANSPORT_APRSIS
161
+
162
+ def decode_packet(self, *args, **kwargs):
163
+ """APRS lib already decodes this."""
164
+ return core.factory(args[0])
165
+
166
+ def consumer(self, callback: Callable, raw: bool = False):
169
167
  if self._client:
170
168
  try:
171
169
  self._client.consumer(
172
170
  callback,
173
- blocking=blocking,
174
- immortal=immortal,
171
+ blocking=False,
172
+ immortal=False,
175
173
  raw=raw,
176
174
  )
177
175
  except Exception as e:
@@ -179,5 +177,29 @@ class APRSISClient(base.APRSClient):
179
177
  LOG.info(e.__cause__)
180
178
  raise e
181
179
  else:
182
- LOG.warning("client is None, might be resetting.")
180
+ LOG.warning('client is None, might be resetting.')
183
181
  self.connected = False
182
+
183
+ def stats(self, serializable=False) -> dict:
184
+ stats = {}
185
+ if self.is_configured():
186
+ if self._client:
187
+ keepalive = self._client.aprsd_keepalive
188
+ server_string = self._client.server_string
189
+ if serializable:
190
+ keepalive = keepalive.isoformat()
191
+ filter = self.filter
192
+ else:
193
+ keepalive = 'None'
194
+ server_string = 'None'
195
+ filter = 'None'
196
+ stats = {
197
+ 'connected': self.is_alive,
198
+ 'filter': filter,
199
+ 'login_status': self.login_status,
200
+ 'connection_keepalive': keepalive,
201
+ 'server_string': server_string,
202
+ 'transport': self.transport(),
203
+ }
204
+
205
+ return stats