aprsd 4.1.2__tar.gz → 4.2.1__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 (181) hide show
  1. {aprsd-4.1.2 → aprsd-4.2.1}/ChangeLog.md +38 -0
  2. {aprsd-4.1.2 → aprsd-4.2.1}/PKG-INFO +48 -48
  3. aprsd-4.2.1/aprsd/client/__init__.py +5 -0
  4. aprsd-4.2.1/aprsd/client/client.py +156 -0
  5. aprsd-4.2.1/aprsd/client/drivers/__init__.py +10 -0
  6. {aprsd-4.1.2/aprsd/client → aprsd-4.2.1/aprsd/client/drivers}/aprsis.py +105 -83
  7. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/client/drivers/fake.py +59 -11
  8. aprsd-4.1.2/aprsd/client/drivers/aprsis.py → aprsd-4.2.1/aprsd/client/drivers/lib/aprslib.py +13 -3
  9. aprsd-4.2.1/aprsd/client/drivers/registry.py +86 -0
  10. aprsd-4.2.1/aprsd/client/drivers/tcpkiss.py +423 -0
  11. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/client/stats.py +2 -2
  12. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/dev.py +6 -4
  13. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/fetch_stats.py +2 -0
  14. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/list_plugins.py +6 -133
  15. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/listen.py +5 -3
  16. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/send_message.py +8 -5
  17. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/server.py +7 -11
  18. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/conf/common.py +7 -1
  19. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/exception.py +7 -0
  20. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/log/log.py +1 -1
  21. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/main.py +0 -7
  22. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/core.py +168 -169
  23. aprsd-4.2.1/aprsd/packets/log.py +171 -0
  24. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugin.py +3 -2
  25. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugin_utils.py +2 -2
  26. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/weather.py +2 -2
  27. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/stats/collector.py +5 -4
  28. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/rx.py +13 -11
  29. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/tx.py +32 -31
  30. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/keepalive_collector.py +7 -5
  31. aprsd-4.2.1/aprsd/utils/package.py +176 -0
  32. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd.egg-info/PKG-INFO +48 -48
  33. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd.egg-info/SOURCES.txt +13 -9
  34. aprsd-4.2.1/aprsd.egg-info/requires.txt +92 -0
  35. aprsd-4.2.1/docker/docker-compose.yml +27 -0
  36. {aprsd-4.1.2 → aprsd-4.2.1}/requirements-dev.in +1 -1
  37. {aprsd-4.1.2 → aprsd-4.2.1}/requirements-dev.txt +24 -24
  38. {aprsd-4.1.2 → aprsd-4.2.1}/requirements.txt +21 -22
  39. aprsd-4.2.1/tests/client/drivers/test_aprsis_driver.py +440 -0
  40. aprsd-4.2.1/tests/client/drivers/test_fake_driver.py +191 -0
  41. aprsd-4.2.1/tests/client/drivers/test_tcpkiss_driver.py +498 -0
  42. aprsd-4.2.1/tests/client/test_registry.py +100 -0
  43. aprsd-4.2.1/tests/cmds/__init__.py +0 -0
  44. aprsd-4.2.1/tests/mock_client_driver.py +76 -0
  45. aprsd-4.2.1/tests/plugins/__init__.py +0 -0
  46. {aprsd-4.1.2 → aprsd-4.2.1}/tests/plugins/test_notify.py +43 -36
  47. aprsd-4.2.1/tests/plugins/test_version.py +64 -0
  48. {aprsd-4.1.2 → aprsd-4.2.1}/tests/test_plugin.py +38 -18
  49. {aprsd-4.1.2 → aprsd-4.2.1}/tox.ini +1 -1
  50. aprsd-4.2.1/uv.lock +1346 -0
  51. aprsd-4.1.2/aprsd/client/__init__.py +0 -13
  52. aprsd-4.1.2/aprsd/client/base.py +0 -156
  53. aprsd-4.1.2/aprsd/client/drivers/kiss.py +0 -144
  54. aprsd-4.1.2/aprsd/client/factory.py +0 -91
  55. aprsd-4.1.2/aprsd/client/fake.py +0 -49
  56. aprsd-4.1.2/aprsd/client/kiss.py +0 -143
  57. aprsd-4.1.2/aprsd/packets/log.py +0 -161
  58. aprsd-4.1.2/aprsd.egg-info/requires.txt +0 -93
  59. aprsd-4.1.2/docker/docker-compose.yml +0 -14
  60. aprsd-4.1.2/tests/client/test_aprsis.py +0 -89
  61. aprsd-4.1.2/tests/client/test_client_base.py +0 -141
  62. aprsd-4.1.2/tests/client/test_factory.py +0 -75
  63. aprsd-4.1.2/tests/plugins/test_version.py +0 -35
  64. aprsd-4.1.2/uv.lock +0 -1340
  65. {aprsd-4.1.2 → aprsd-4.2.1}/.coveragerc +0 -0
  66. {aprsd-4.1.2 → aprsd-4.2.1}/.github/workflows/authors.yml +0 -0
  67. {aprsd-4.1.2 → aprsd-4.2.1}/.github/workflows/codeql.yml +0 -0
  68. {aprsd-4.1.2 → aprsd-4.2.1}/.github/workflows/manual_build.yml +0 -0
  69. {aprsd-4.1.2 → aprsd-4.2.1}/.github/workflows/master-build.yml +0 -0
  70. {aprsd-4.1.2 → aprsd-4.2.1}/.github/workflows/python.yml +0 -0
  71. {aprsd-4.1.2 → aprsd-4.2.1}/.github/workflows/release_build.yml +0 -0
  72. {aprsd-4.1.2 → aprsd-4.2.1}/.mailmap +0 -0
  73. {aprsd-4.1.2 → aprsd-4.2.1}/.pre-commit-config.yaml +0 -0
  74. {aprsd-4.1.2 → aprsd-4.2.1}/.readthedocs.yaml +0 -0
  75. {aprsd-4.1.2 → aprsd-4.2.1}/AUTHORS +0 -0
  76. {aprsd-4.1.2 → aprsd-4.2.1}/CONTRIBUTING.md +0 -0
  77. {aprsd-4.1.2 → aprsd-4.2.1}/INSTALL.txt +0 -0
  78. {aprsd-4.1.2 → aprsd-4.2.1}/LICENSE +0 -0
  79. {aprsd-4.1.2 → aprsd-4.2.1}/MANIFEST.in +0 -0
  80. {aprsd-4.1.2 → aprsd-4.2.1}/Makefile +0 -0
  81. {aprsd-4.1.2 → aprsd-4.2.1}/README.md +0 -0
  82. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/__init__.py +0 -0
  83. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cli_helper.py +0 -0
  84. {aprsd-4.1.2/aprsd/client/drivers → aprsd-4.2.1/aprsd/client/drivers/lib}/__init__.py +0 -0
  85. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/__init__.py +0 -0
  86. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/completion.py +0 -0
  87. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/cmds/healthcheck.py +0 -0
  88. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/conf/__init__.py +0 -0
  89. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/conf/client.py +0 -0
  90. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/conf/log.py +0 -0
  91. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/conf/opts.py +0 -0
  92. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/conf/plugin_common.py +0 -0
  93. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/log/__init__.py +0 -0
  94. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/__init__.py +0 -0
  95. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/collector.py +0 -0
  96. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/filter.py +0 -0
  97. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/filters/__init__.py +0 -0
  98. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/filters/dupe_filter.py +0 -0
  99. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/filters/packet_type.py +0 -0
  100. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/packet_list.py +0 -0
  101. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/seen_list.py +0 -0
  102. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/tracker.py +0 -0
  103. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/packets/watch_list.py +0 -0
  104. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/__init__.py +0 -0
  105. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/fortune.py +0 -0
  106. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/notify.py +0 -0
  107. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/ping.py +0 -0
  108. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/time.py +0 -0
  109. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/plugins/version.py +0 -0
  110. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/stats/__init__.py +0 -0
  111. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/stats/app.py +0 -0
  112. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/__init__.py +0 -0
  113. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/aprsd.py +0 -0
  114. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/keepalive.py +0 -0
  115. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/registry.py +0 -0
  116. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/service.py +0 -0
  117. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/threads/stats.py +0 -0
  118. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/__init__.py +0 -0
  119. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/counter.py +0 -0
  120. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/fuzzyclock.py +0 -0
  121. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/json.py +0 -0
  122. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/objectstore.py +0 -0
  123. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/ring_buffer.py +0 -0
  124. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd/utils/trace.py +0 -0
  125. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd-lnav.json +0 -0
  126. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd.egg-info/dependency_links.txt +0 -0
  127. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd.egg-info/entry_points.txt +0 -0
  128. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd.egg-info/top_level.txt +0 -0
  129. {aprsd-4.1.2 → aprsd-4.2.1}/aprsd_logo.png +0 -0
  130. {aprsd-4.1.2 → aprsd-4.2.1}/docker/Dockerfile +0 -0
  131. {aprsd-4.1.2 → aprsd-4.2.1}/docker/bin/admin.sh +0 -0
  132. {aprsd-4.1.2 → aprsd-4.2.1}/docker/bin/healthcheck.sh +0 -0
  133. {aprsd-4.1.2 → aprsd-4.2.1}/docker/bin/listen.sh +0 -0
  134. {aprsd-4.1.2 → aprsd-4.2.1}/docker/bin/run.sh +0 -0
  135. {aprsd-4.1.2 → aprsd-4.2.1}/docker/bin/setup.sh +0 -0
  136. {aprsd-4.1.2 → aprsd-4.2.1}/docker/build.sh +0 -0
  137. {aprsd-4.1.2 → aprsd-4.2.1}/docs/_static/.keep +0 -0
  138. {aprsd-4.1.2 → aprsd-4.2.1}/docs/_static/aprsd_overview.png +0 -0
  139. {aprsd-4.1.2 → aprsd-4.2.1}/docs/_static/aprsd_overview.svg +0 -0
  140. {aprsd-4.1.2 → aprsd-4.2.1}/docs/_templates/.keep +0 -0
  141. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.client.drivers.rst +0 -0
  142. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.client.rst +0 -0
  143. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.cmds.rst +0 -0
  144. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.conf.rst +0 -0
  145. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.log.rst +0 -0
  146. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.packets.rst +0 -0
  147. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.plugins.rst +0 -0
  148. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.rst +0 -0
  149. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.stats.rst +0 -0
  150. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.threads.rst +0 -0
  151. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/aprsd.utils.rst +0 -0
  152. {aprsd-4.1.2 → aprsd-4.2.1}/docs/apidoc/modules.rst +0 -0
  153. {aprsd-4.1.2 → aprsd-4.2.1}/docs/aprsd.drawio +0 -0
  154. {aprsd-4.1.2 → aprsd-4.2.1}/docs/changelog.rst +0 -0
  155. {aprsd-4.1.2 → aprsd-4.2.1}/docs/clean_docs.py +0 -0
  156. {aprsd-4.1.2 → aprsd-4.2.1}/docs/conf.py +0 -0
  157. {aprsd-4.1.2 → aprsd-4.2.1}/docs/configure.rst +0 -0
  158. {aprsd-4.1.2 → aprsd-4.2.1}/docs/index.rst +0 -0
  159. {aprsd-4.1.2 → aprsd-4.2.1}/docs/install.rst +0 -0
  160. {aprsd-4.1.2 → aprsd-4.2.1}/docs/links.rst +0 -0
  161. {aprsd-4.1.2 → aprsd-4.2.1}/docs/plugin.rst +0 -0
  162. {aprsd-4.1.2 → aprsd-4.2.1}/docs/readme.rst +0 -0
  163. {aprsd-4.1.2 → aprsd-4.2.1}/docs/server.rst +0 -0
  164. {aprsd-4.1.2 → aprsd-4.2.1}/examples/plugins/__init__.py +0 -0
  165. {aprsd-4.1.2 → aprsd-4.2.1}/examples/plugins/example_plugin.py +0 -0
  166. {aprsd-4.1.2 → aprsd-4.2.1}/gray.conf +0 -0
  167. {aprsd-4.1.2 → aprsd-4.2.1}/pyproject.toml +0 -0
  168. {aprsd-4.1.2 → aprsd-4.2.1}/requirements.in +0 -0
  169. {aprsd-4.1.2 → aprsd-4.2.1}/setup.cfg +0 -0
  170. {aprsd-4.1.2 → aprsd-4.2.1}/setup.py +0 -0
  171. {aprsd-4.1.2 → aprsd-4.2.1}/tests/__init__.py +0 -0
  172. {aprsd-4.1.2/tests/cmds → aprsd-4.2.1/tests/client}/__init__.py +0 -0
  173. {aprsd-4.1.2/tests/plugins → aprsd-4.2.1/tests/client/drivers}/__init__.py +0 -0
  174. {aprsd-4.1.2 → aprsd-4.2.1}/tests/cmds/test_send_message.py +0 -0
  175. {aprsd-4.1.2 → aprsd-4.2.1}/tests/fake.py +0 -0
  176. {aprsd-4.1.2 → aprsd-4.2.1}/tests/plugins/test_fortune.py +0 -0
  177. {aprsd-4.1.2 → aprsd-4.2.1}/tests/plugins/test_ping.py +0 -0
  178. {aprsd-4.1.2 → aprsd-4.2.1}/tests/plugins/test_time.py +0 -0
  179. {aprsd-4.1.2 → aprsd-4.2.1}/tests/plugins/test_weather.py +0 -0
  180. {aprsd-4.1.2 → aprsd-4.2.1}/tests/test_packets.py +0 -0
  181. {aprsd-4.1.2 → aprsd-4.2.1}/tools/fast8.sh +0 -0
@@ -4,11 +4,49 @@ 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.1](https://github.com/craigerl/aprsd/compare/4.2.0...4.2.1)
8
+
9
+ > 7 October 2025
10
+
11
+ - Sanity check around decoding packet [`58cb046`](https://github.com/craigerl/aprsd/commit/58cb046b3131f4300255c527dee4c7291a4aeed2)
12
+ - Added CONF.is_digipi [`f3039eb`](https://github.com/craigerl/aprsd/commit/f3039ebfa153d0fbad4a390089fce360e6148854)
13
+ - Fixed stats issue with tcpkiss client. [`556554b`](https://github.com/craigerl/aprsd/commit/556554b1a76743989fa88877e7f694b918f770cf)
14
+ - Fixed missing f string [`8cd61a7`](https://github.com/craigerl/aprsd/commit/8cd61a72c8f16e8651533baf2d677d3345bd3712)
15
+ - Added ThirdPartyPacket decoding in tcpkiss driver [`3961e1d`](https://github.com/craigerl/aprsd/commit/3961e1d1adf428ff0fa66ed5fe0d6192adf164bf)
16
+ - refactored list-plugins [`c7c9a92`](https://github.com/craigerl/aprsd/commit/c7c9a92b153ccc673275dca628d485d1df88db12)
17
+ - Added package [`e15322e`](https://github.com/craigerl/aprsd/commit/e15322ede384d6e463c42e9496502e2c6ea0886c)
18
+ - Some client and driver cleanup. [`328c027`](https://github.com/craigerl/aprsd/commit/328c027ad3db594f3d5622a2cab7cafb21d1cfd6)
19
+ - Fixed some unit tests [`af0feaf`](https://github.com/craigerl/aprsd/commit/af0feaf9c81b8b9f443e58fbe7d23f1ff7ba63d2)
20
+
21
+ #### [4.2.0](https://github.com/craigerl/aprsd/compare/4.1.2...4.2.0)
22
+
23
+ > 12 August 2025
24
+
25
+ - Reworked the entire client and drivers [`#191`](https://github.com/craigerl/aprsd/pull/191)
26
+ - Update the docker-compose.yml [`#193`](https://github.com/craigerl/aprsd/pull/193)
27
+ - Use OpenWeatherMap One Call v3.0 API [`#186`](https://github.com/craigerl/aprsd/pull/186)
28
+ - use OWM onecall v3.0 [`16bfda4`](https://github.com/craigerl/aprsd/commit/16bfda431c54a2ec6fbd011edd710c1d787ae1de)
29
+ - Fixed a problem with WeatherPacket [`5469610`](https://github.com/craigerl/aprsd/commit/54696107797c3d788e5d0b2dad164e1a7cf439f2)
30
+ - Ensure filter is set [`4c53c13`](https://github.com/craigerl/aprsd/commit/4c53c13e793356228f97e26a275d3d4c54b4bf54)
31
+ - Remove flask_enabled [`ce79f01`](https://github.com/craigerl/aprsd/commit/ce79f0112ff0f80db8adfff03c9ec8e62655bcc0)
32
+ - Make some catchall fields non hashable. [`acd639a`](https://github.com/craigerl/aprsd/commit/acd639a9100893a2e1d08d9ba65ef65ce6ed87e6)
33
+ - Fixed setup for AVWXWeatherPlugin [`2fcd574`](https://github.com/craigerl/aprsd/commit/2fcd574f12d48eaec122f96b6b15e9408d395b72)
34
+ - removed old flask_enabled global [`8f471c2`](https://github.com/craigerl/aprsd/commit/8f471c229c1073834c2ac66522fbaf2a561e296a)
35
+ - Updated requirements [`211ac11`](https://github.com/craigerl/aprsd/commit/211ac1132b2f1e2736b1c680936711e114556e90)
36
+ - Don't log an IOError on shutdown [`d41064b`](https://github.com/craigerl/aprsd/commit/d41064ba05faf6167c0d26db2ab525d0bb410bf8)
37
+ - Honor quiet setting for log.setup_logging [`1ae4375`](https://github.com/craigerl/aprsd/commit/1ae437581f753d903c20a6ef54868438eb034800)
38
+ - Make sure packet has addressee field [`034f11b`](https://github.com/craigerl/aprsd/commit/034f11b4f9f3392e003cfbd0e5f8b9fb5f594089)
39
+ - Fix tox failures [`74887af`](https://github.com/craigerl/aprsd/commit/74887af507755cd7ee27f13129b177f4d58ca8e0)
40
+ - log the exception when tx fails. [`fa5d0c6`](https://github.com/craigerl/aprsd/commit/fa5d0c643ae85fc9ad3f615f9d0afc590f422283)
41
+ - Updated requirements for 4.2.0 [`2c476d8`](https://github.com/craigerl/aprsd/commit/2c476d8a04df8cebbaa50780688e58d8b9df2bc9)
42
+ - Updated Changelog for 4.2.0 [`b9fea98`](https://github.com/craigerl/aprsd/commit/b9fea982f977e9cf60c4695ca1892adaeee64298)
43
+
7
44
  #### [4.1.2](https://github.com/craigerl/aprsd/compare/4.1.1...4.1.2)
8
45
 
9
46
  > 6 March 2025
10
47
 
11
48
  - Allow passing in a custom handler to setup_logging [`d262589`](https://github.com/craigerl/aprsd/commit/d2625893134f498748859da3b1684b04d456f790)
49
+ - Changelog for 4.1.2 [`6dba56f`](https://github.com/craigerl/aprsd/commit/6dba56f74d27e418ed2e124e7a3592c274b2da1e)
12
50
 
13
51
  #### [4.1.1](https://github.com/craigerl/aprsd/compare/4.1.0...4.1.1)
14
52
 
@@ -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.1
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,156 @@
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, auto_connect: bool = True):
42
+ self.auto_connect = auto_connect
43
+ self.connected = False
44
+ self.login_status = {
45
+ 'success': False,
46
+ 'message': None,
47
+ }
48
+ self.driver = DriverRegistry().get_driver()
49
+ if self.auto_connect:
50
+ self.connect()
51
+
52
+ def stats(self, serializable=False) -> dict:
53
+ stats = {}
54
+ if self.driver:
55
+ stats = self.driver.stats(serializable=serializable)
56
+ return stats
57
+
58
+ @staticmethod
59
+ def is_enabled():
60
+ for driver in DriverRegistry().drivers:
61
+ if driver.is_enabled():
62
+ return True
63
+ return False
64
+
65
+ @staticmethod
66
+ def is_configured():
67
+ """Check if ANY driver is configured."""
68
+ for driver in DriverRegistry().drivers:
69
+ if driver.is_configured():
70
+ return True
71
+ return False
72
+
73
+ # @property
74
+ # def is_connected(self):
75
+ # if not self.driver:
76
+ # return False
77
+ # return self.driver.is_connected()
78
+
79
+ @property
80
+ def login_success(self):
81
+ if not self.driver:
82
+ return False
83
+ return self.driver.login_success
84
+
85
+ @property
86
+ def login_failure(self):
87
+ if not self.driver:
88
+ return None
89
+ return self.driver.login_failure
90
+
91
+ def set_filter(self, filter):
92
+ self.filter = filter
93
+ if not self.driver:
94
+ return
95
+ self.driver.set_filter(filter)
96
+
97
+ def get_filter(self):
98
+ if not self.driver:
99
+ return None
100
+ return self.driver.filter
101
+
102
+ def is_alive(self):
103
+ return self.driver.is_alive()
104
+
105
+ def connect(self):
106
+ if not self.driver:
107
+ self.driver = DriverRegistry().get_driver()
108
+ self.driver.setup_connection()
109
+
110
+ def close(self):
111
+ if not self.driver:
112
+ return
113
+ self.driver.close()
114
+
115
+ @wrapt.synchronized(lock)
116
+ def reset(self):
117
+ """Call this to force a rebuild/reconnect."""
118
+ LOG.info('Resetting client connection.')
119
+ if self.driver:
120
+ self.driver.close()
121
+ if not self.delay_connect:
122
+ self.driver.setup_connection()
123
+ if self.filter:
124
+ self.driver.set_filter(self.filter)
125
+ else:
126
+ LOG.warning('Client not initialized, nothing to reset.')
127
+
128
+ def send(self, packet: core.Packet) -> bool:
129
+ return self.driver.send(packet)
130
+
131
+ # For the keepalive collector
132
+ def keepalive_check(self):
133
+ # Don't check the first time through.
134
+ if not self.driver.is_alive and self._checks:
135
+ LOG.warning("Resetting client. It's not alive.")
136
+ self.reset()
137
+ self._checks = True
138
+
139
+ # For the keepalive collector
140
+ def keepalive_log(self):
141
+ if ka := self.driver.keepalive:
142
+ keepalive = timeago.format(ka)
143
+ else:
144
+ keepalive = 'N/A'
145
+ LOGU.opt(colors=True).info(f'<green>Client keepalive {keepalive}</green>')
146
+
147
+ def consumer(self, callback: Callable, raw: bool = False):
148
+ return self.driver.consumer(callback=callback, raw=raw)
149
+
150
+ def decode_packet(self, *args, **kwargs) -> core.Packet:
151
+ try:
152
+ packet = self.driver.decode_packet(*args, **kwargs)
153
+ except Exception as e:
154
+ LOG.error(f'Error decoding packet: {e}')
155
+ return None
156
+ return packet
@@ -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,40 @@
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
29
+ connected = False
23
30
 
24
31
  def __init__(self):
25
- max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0}
32
+ max_timeout = {'hours': 0.0, 'minutes': 2, 'seconds': 0}
26
33
  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>")
34
+ self.login_status = {
35
+ 'success': False,
36
+ 'message': None,
37
+ }
63
38
 
64
39
  @staticmethod
65
40
  def is_enabled():
@@ -71,37 +46,31 @@ class APRSISClient(base.APRSClient):
71
46
 
72
47
  @staticmethod
73
48
  def is_configured():
74
- if APRSISClient.is_enabled():
49
+ if APRSISDriver.is_enabled():
75
50
  # Ensure that the config vars are correctly set
76
51
  if not CONF.aprs_network.login:
77
- LOG.error("Config aprs_network.login not set.")
52
+ LOG.error('Config aprs_network.login not set.')
78
53
  raise exception.MissingConfigOptionException(
79
- "aprs_network.login is not set.",
54
+ 'aprs_network.login is not set.',
80
55
  )
81
56
  if not CONF.aprs_network.password:
82
- LOG.error("Config aprs_network.password not set.")
57
+ LOG.error('Config aprs_network.password not set.')
83
58
  raise exception.MissingConfigOptionException(
84
- "aprs_network.password is not set.",
59
+ 'aprs_network.password is not set.',
85
60
  )
86
61
  if not CONF.aprs_network.host:
87
- LOG.error("Config aprs_network.host not set.")
62
+ LOG.error('Config aprs_network.host not set.')
88
63
  raise exception.MissingConfigOptionException(
89
- "aprs_network.host is not set.",
64
+ 'aprs_network.host is not set.',
90
65
  )
91
66
 
92
67
  return True
93
68
  return True
94
69
 
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
-
70
+ @property
102
71
  def is_alive(self):
103
72
  if not self._client:
104
- LOG.warning(f"APRS_CLIENT {self._client} alive? NO!!!")
73
+ LOG.warning(f'APRS_CLIENT {self._client} alive? NO!!!')
105
74
  return False
106
75
  return self._client.is_alive() and not self._is_stale_connection()
107
76
 
@@ -110,13 +79,8 @@ class APRSISClient(base.APRSClient):
110
79
  self._client.stop()
111
80
  self._client.close()
112
81
 
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])
82
+ def send(self, packet: core.Packet) -> bool:
83
+ return self._client.send(packet)
120
84
 
121
85
  def setup_connection(self):
122
86
  user = CONF.aprs_network.login
@@ -125,7 +89,6 @@ class APRSISClient(base.APRSClient):
125
89
  port = CONF.aprs_network.port
126
90
  self.connected = False
127
91
  backoff = 1
128
- aprs_client = None
129
92
  retries = 3
130
93
  retry_count = 0
131
94
  while not self.connected:
@@ -134,27 +97,29 @@ class APRSISClient(base.APRSClient):
134
97
  break
135
98
  try:
136
99
  LOG.info(
137
- f"Creating aprslib client({host}:{port}) and logging in {user}."
100
+ f'Creating aprslib client({host}:{port}) and logging in {user}.'
138
101
  )
139
- aprs_client = aprsis.Aprsdis(
102
+ self._client = APRSLibClient(
140
103
  user, passwd=password, host=host, port=port
141
104
  )
142
105
  # 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
106
+ self._client.logger = LOG
107
+ self._client.connect()
108
+ self.connected = self.login_status['success'] = True
109
+ self.login_status['message'] = self._client.server_string
147
110
  backoff = 1
148
111
  except LoginError as e:
149
112
  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)
113
+ self.connected = self.login_status['success'] = False
114
+ self.login_status['message'] = (
115
+ e.message if hasattr(e, 'message') else str(e)
116
+ )
117
+ LOG.error(self.login_status['message'])
153
118
  time.sleep(backoff)
154
119
  except Exception as e:
155
120
  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
121
+ self.connected = self.login_status['success'] = False
122
+ self.login_status['message'] = getattr(e, 'message', str(e))
158
123
  time.sleep(backoff)
159
124
  # Don't allow the backoff to go to inifinity.
160
125
  if backoff > 5:
@@ -162,16 +127,50 @@ class APRSISClient(base.APRSClient):
162
127
  else:
163
128
  backoff += 1
164
129
  continue
165
- self._client = aprs_client
166
- return aprs_client
167
130
 
168
- def consumer(self, callback, blocking=False, immortal=False, raw=False):
169
- if self._client:
131
+ def set_filter(self, filter):
132
+ self._client.set_filter(filter)
133
+
134
+ def login_success(self) -> bool:
135
+ return self.login_status.get('success', False)
136
+
137
+ def login_failure(self) -> str:
138
+ return self.login_status.get('message', None)
139
+
140
+ @property
141
+ def filter(self):
142
+ return self._client.filter
143
+
144
+ @property
145
+ def server_string(self):
146
+ return self._client.server_string
147
+
148
+ @property
149
+ def keepalive(self):
150
+ return self._client.aprsd_keepalive
151
+
152
+ def _is_stale_connection(self):
153
+ delta = datetime.datetime.now() - self._client.aprsd_keepalive
154
+ if delta > self.max_delta:
155
+ LOG.error(f'Connection is stale, last heard {delta} ago.')
156
+ return True
157
+ return False
158
+
159
+ @staticmethod
160
+ def transport():
161
+ return client.TRANSPORT_APRSIS
162
+
163
+ def decode_packet(self, *args, **kwargs):
164
+ """APRS lib already decodes this."""
165
+ return core.factory(args[0])
166
+
167
+ def consumer(self, callback: Callable, raw: bool = False):
168
+ if self._client and self.connected:
170
169
  try:
171
170
  self._client.consumer(
172
171
  callback,
173
- blocking=blocking,
174
- immortal=immortal,
172
+ blocking=False,
173
+ immortal=False,
175
174
  raw=raw,
176
175
  )
177
176
  except Exception as e:
@@ -179,5 +178,28 @@ class APRSISClient(base.APRSClient):
179
178
  LOG.info(e.__cause__)
180
179
  raise e
181
180
  else:
182
- LOG.warning("client is None, might be resetting.")
183
181
  self.connected = False
182
+
183
+ def stats(self, serializable: bool = 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