aprsd 1.0.0__py3-none-any.whl → 3.4.1__py3-none-any.whl

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 (141) hide show
  1. aprsd/__init__.py +6 -4
  2. aprsd/cli_helper.py +151 -0
  3. aprsd/client/__init__.py +13 -0
  4. aprsd/client/aprsis.py +132 -0
  5. aprsd/client/base.py +105 -0
  6. aprsd/client/drivers/__init__.py +0 -0
  7. aprsd/client/drivers/aprsis.py +224 -0
  8. aprsd/client/drivers/fake.py +73 -0
  9. aprsd/client/drivers/kiss.py +119 -0
  10. aprsd/client/factory.py +88 -0
  11. aprsd/client/fake.py +48 -0
  12. aprsd/client/kiss.py +103 -0
  13. aprsd/client/stats.py +38 -0
  14. aprsd/cmds/__init__.py +0 -0
  15. aprsd/cmds/completion.py +22 -0
  16. aprsd/cmds/dev.py +162 -0
  17. aprsd/cmds/fetch_stats.py +156 -0
  18. aprsd/cmds/healthcheck.py +86 -0
  19. aprsd/cmds/list_plugins.py +319 -0
  20. aprsd/cmds/listen.py +230 -0
  21. aprsd/cmds/send_message.py +174 -0
  22. aprsd/cmds/server.py +142 -0
  23. aprsd/cmds/webchat.py +681 -0
  24. aprsd/conf/__init__.py +56 -0
  25. aprsd/conf/client.py +131 -0
  26. aprsd/conf/common.py +302 -0
  27. aprsd/conf/log.py +65 -0
  28. aprsd/conf/opts.py +80 -0
  29. aprsd/conf/plugin_common.py +191 -0
  30. aprsd/conf/plugin_email.py +105 -0
  31. aprsd/exception.py +13 -0
  32. aprsd/log/__init__.py +0 -0
  33. aprsd/log/log.py +138 -0
  34. aprsd/main.py +104 -867
  35. aprsd/messaging.py +4 -0
  36. aprsd/packets/__init__.py +12 -0
  37. aprsd/packets/collector.py +56 -0
  38. aprsd/packets/core.py +823 -0
  39. aprsd/packets/log.py +143 -0
  40. aprsd/packets/packet_list.py +116 -0
  41. aprsd/packets/seen_list.py +54 -0
  42. aprsd/packets/tracker.py +109 -0
  43. aprsd/packets/watch_list.py +122 -0
  44. aprsd/plugin.py +475 -284
  45. aprsd/plugin_utils.py +86 -0
  46. aprsd/plugins/__init__.py +0 -0
  47. aprsd/plugins/email.py +709 -0
  48. aprsd/plugins/fortune.py +61 -0
  49. aprsd/plugins/location.py +179 -0
  50. aprsd/plugins/notify.py +61 -0
  51. aprsd/plugins/ping.py +31 -0
  52. aprsd/plugins/time.py +115 -0
  53. aprsd/plugins/version.py +31 -0
  54. aprsd/plugins/weather.py +405 -0
  55. aprsd/stats/__init__.py +20 -0
  56. aprsd/stats/app.py +49 -0
  57. aprsd/stats/collector.py +38 -0
  58. aprsd/threads/__init__.py +11 -0
  59. aprsd/threads/aprsd.py +119 -0
  60. aprsd/threads/keep_alive.py +124 -0
  61. aprsd/threads/log_monitor.py +121 -0
  62. aprsd/threads/registry.py +56 -0
  63. aprsd/threads/rx.py +354 -0
  64. aprsd/threads/stats.py +44 -0
  65. aprsd/threads/tx.py +255 -0
  66. aprsd/utils/__init__.py +163 -0
  67. aprsd/utils/counter.py +51 -0
  68. aprsd/utils/json.py +80 -0
  69. aprsd/utils/objectstore.py +123 -0
  70. aprsd/utils/ring_buffer.py +40 -0
  71. aprsd/utils/trace.py +180 -0
  72. aprsd/web/__init__.py +0 -0
  73. aprsd/web/admin/__init__.py +0 -0
  74. aprsd/web/admin/static/css/index.css +84 -0
  75. aprsd/web/admin/static/css/prism.css +4 -0
  76. aprsd/web/admin/static/css/tabs.css +35 -0
  77. aprsd/web/admin/static/images/Untitled.png +0 -0
  78. aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
  79. aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
  80. aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
  81. aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
  82. aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
  83. aprsd/web/admin/static/js/charts.js +235 -0
  84. aprsd/web/admin/static/js/echarts.js +465 -0
  85. aprsd/web/admin/static/js/logs.js +26 -0
  86. aprsd/web/admin/static/js/main.js +231 -0
  87. aprsd/web/admin/static/js/prism.js +12 -0
  88. aprsd/web/admin/static/js/send-message.js +114 -0
  89. aprsd/web/admin/static/js/tabs.js +28 -0
  90. aprsd/web/admin/templates/index.html +196 -0
  91. aprsd/web/chat/static/css/chat.css +115 -0
  92. aprsd/web/chat/static/css/index.css +66 -0
  93. aprsd/web/chat/static/css/style.css.map +1 -0
  94. aprsd/web/chat/static/css/tabs.css +41 -0
  95. aprsd/web/chat/static/css/upstream/bootstrap.min.css +6 -0
  96. aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
  97. aprsd/web/chat/static/css/upstream/google-fonts.css +23 -0
  98. aprsd/web/chat/static/css/upstream/jquery-ui.css +1311 -0
  99. aprsd/web/chat/static/css/upstream/jquery.toast.css +28 -0
  100. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
  101. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
  102. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
  103. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
  104. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
  105. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
  106. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
  107. aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
  108. aprsd/web/chat/static/images/Untitled.png +0 -0
  109. aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
  110. aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
  111. aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
  112. aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
  113. aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
  114. aprsd/web/chat/static/images/globe.svg +3 -0
  115. aprsd/web/chat/static/js/gps.js +84 -0
  116. aprsd/web/chat/static/js/main.js +45 -0
  117. aprsd/web/chat/static/js/send-message.js +585 -0
  118. aprsd/web/chat/static/js/tabs.js +28 -0
  119. aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +7 -0
  120. aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +2 -0
  121. aprsd/web/chat/static/js/upstream/jquery-ui.min.js +13 -0
  122. aprsd/web/chat/static/js/upstream/jquery.toast.js +374 -0
  123. aprsd/web/chat/static/js/upstream/semantic.min.js +11 -0
  124. aprsd/web/chat/static/js/upstream/socket.io.min.js +7 -0
  125. aprsd/web/chat/templates/index.html +139 -0
  126. aprsd/wsgi.py +315 -0
  127. aprsd-3.4.1.dist-info/AUTHORS +13 -0
  128. aprsd-3.4.1.dist-info/LICENSE +175 -0
  129. aprsd-3.4.1.dist-info/METADATA +799 -0
  130. aprsd-3.4.1.dist-info/RECORD +134 -0
  131. {aprsd-1.0.0.dist-info → aprsd-3.4.1.dist-info}/WHEEL +1 -1
  132. aprsd-3.4.1.dist-info/entry_points.txt +8 -0
  133. aprsd/fake_aprs.py +0 -83
  134. aprsd/utils.py +0 -166
  135. aprsd-1.0.0.dist-info/AUTHORS +0 -6
  136. aprsd-1.0.0.dist-info/METADATA +0 -181
  137. aprsd-1.0.0.dist-info/RECORD +0 -13
  138. aprsd-1.0.0.dist-info/entry_points.txt +0 -4
  139. aprsd-1.0.0.dist-info/pbr.json +0 -1
  140. /aprsd/{fuzzyclock.py → utils/fuzzyclock.py} +0 -0
  141. {aprsd-1.0.0.dist-info → aprsd-3.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,61 @@
1
+ import logging
2
+ import shutil
3
+ import subprocess
4
+
5
+ from aprsd import packets, plugin
6
+ from aprsd.utils import trace
7
+
8
+
9
+ LOG = logging.getLogger("APRSD")
10
+
11
+ DEFAULT_FORTUNE_PATH = '/usr/games/fortune'
12
+
13
+
14
+ class FortunePlugin(plugin.APRSDRegexCommandPluginBase):
15
+ """Fortune."""
16
+
17
+ command_regex = r"^([f]|[f]\s|fortune)"
18
+ command_name = "fortune"
19
+ short_description = "Give me a fortune"
20
+
21
+ fortune_path = None
22
+
23
+ def setup(self):
24
+ self.fortune_path = shutil.which(DEFAULT_FORTUNE_PATH)
25
+ LOG.info(f"Fortune path {self.fortune_path}")
26
+ if not self.fortune_path:
27
+ self.enabled = False
28
+ else:
29
+ self.enabled = True
30
+
31
+ @trace.trace
32
+ def process(self, packet: packets.MessagePacket):
33
+ LOG.info("FortunePlugin")
34
+
35
+ # fromcall = packet.get("from")
36
+ # message = packet.get("message_text", None)
37
+ # ack = packet.get("msgNo", "0")
38
+
39
+ reply = None
40
+
41
+ try:
42
+ cmnd = [self.fortune_path, "-s", "-n 60"]
43
+ command = " ".join(cmnd)
44
+ output = subprocess.check_output(
45
+ command,
46
+ shell=True,
47
+ timeout=3,
48
+ universal_newlines=True,
49
+ )
50
+ output = (
51
+ output.replace("\r", "")
52
+ .replace("\n", "")
53
+ .replace(" ", "")
54
+ .replace("\t", " ")
55
+ )
56
+ except subprocess.CalledProcessError as ex:
57
+ reply = f"Fortune command failed '{ex.output}'"
58
+ else:
59
+ reply = output
60
+
61
+ return reply
@@ -0,0 +1,179 @@
1
+ import logging
2
+ import re
3
+ import time
4
+
5
+ from geopy.geocoders import ArcGIS, AzureMaps, Baidu, Bing, GoogleV3
6
+ from geopy.geocoders import HereV7, Nominatim, OpenCage, TomTom, What3WordsV3, Woosmap
7
+ from oslo_config import cfg
8
+
9
+ from aprsd import packets, plugin, plugin_utils
10
+ from aprsd.utils import trace
11
+
12
+
13
+ CONF = cfg.CONF
14
+ LOG = logging.getLogger("APRSD")
15
+
16
+
17
+ class UsLocation:
18
+ raw = {}
19
+
20
+ def __init__(self, info):
21
+ self.info = info
22
+
23
+ def __str__(self):
24
+ return self.info
25
+
26
+
27
+ class USGov:
28
+ """US Government geocoder that uses the geopy API.
29
+
30
+ This is a dummy class the implements the geopy reverse API,
31
+ so the factory can return an object that conforms to the API.
32
+ """
33
+ def reverse(self, coordinates):
34
+ """Reverse geocode a coordinate."""
35
+ LOG.info(f"USGov reverse geocode {coordinates}")
36
+ coords = coordinates.split(",")
37
+ lat = float(coords[0])
38
+ lon = float(coords[1])
39
+ result = plugin_utils.get_weather_gov_for_gps(lat, lon)
40
+ # LOG.info(f"WEATHER: {result}")
41
+ # LOG.info(f"area description {result['location']['areaDescription']}")
42
+ if 'location' in result:
43
+ loc = UsLocation(result['location']['areaDescription'])
44
+ else:
45
+ loc = UsLocation("Unknown Location")
46
+
47
+ LOG.info(f"USGov reverse geocode LOC {loc}")
48
+ return loc
49
+
50
+
51
+ def geopy_factory():
52
+ """Factory function for geopy geocoders."""
53
+ geocoder = CONF.location_plugin.geopy_geocoder
54
+ LOG.info(f"Using geocoder: {geocoder}")
55
+ user_agent = CONF.location_plugin.user_agent
56
+ LOG.info(f"Using user_agent: {user_agent}")
57
+
58
+ if geocoder == "Nominatim":
59
+ return Nominatim(user_agent=user_agent)
60
+ elif geocoder == "USGov":
61
+ return USGov()
62
+ elif geocoder == "ArcGIS":
63
+ return ArcGIS(
64
+ username=CONF.location_plugin.arcgis_username,
65
+ password=CONF.location_plugin.arcgis_password,
66
+ user_agent=user_agent,
67
+ )
68
+ elif geocoder == "AzureMaps":
69
+ return AzureMaps(
70
+ user_agent=user_agent,
71
+ subscription_key=CONF.location_plugin.azuremaps_subscription_key,
72
+ )
73
+ elif geocoder == "Baidu":
74
+ return Baidu(user_agent=user_agent, api_key=CONF.location_plugin.baidu_api_key)
75
+ elif geocoder == "Bing":
76
+ return Bing(user_agent=user_agent, api_key=CONF.location_plugin.bing_api_key)
77
+ elif geocoder == "GoogleV3":
78
+ return GoogleV3(user_agent=user_agent, api_key=CONF.location_plugin.google_api_key)
79
+ elif geocoder == "HERE":
80
+ return HereV7(user_agent=user_agent, api_key=CONF.location_plugin.here_api_key)
81
+ elif geocoder == "OpenCage":
82
+ return OpenCage(user_agent=user_agent, api_key=CONF.location_plugin.opencage_api_key)
83
+ elif geocoder == "TomTom":
84
+ return TomTom(user_agent=user_agent, api_key=CONF.location_plugin.tomtom_api_key)
85
+ elif geocoder == "What3Words":
86
+ return What3WordsV3(user_agent=user_agent, api_key=CONF.location_plugin.what3words_api_key)
87
+ elif geocoder == "Woosmap":
88
+ return Woosmap(user_agent=user_agent, api_key=CONF.location_plugin.woosmap_api_key)
89
+ else:
90
+ raise ValueError(f"Unknown geocoder: {geocoder}")
91
+
92
+
93
+ class LocationPlugin(plugin.APRSDRegexCommandPluginBase, plugin.APRSFIKEYMixin):
94
+ """Location!"""
95
+
96
+ command_regex = r"^([l]|[l]\s|location)"
97
+ command_name = "location"
98
+ short_description = "Where in the world is a CALLSIGN's last GPS beacon?"
99
+
100
+ def setup(self):
101
+ self.ensure_aprs_fi_key()
102
+
103
+ @trace.trace
104
+ def process(self, packet: packets.MessagePacket):
105
+ LOG.info("Location Plugin")
106
+ fromcall = packet.from_call
107
+ message = packet.get("message_text", None)
108
+
109
+ api_key = CONF.aprs_fi.apiKey
110
+
111
+ # optional second argument is a callsign to search
112
+ a = re.search(r"^.*\s+(.*)", message)
113
+ if a is not None:
114
+ searchcall = a.group(1)
115
+ searchcall = searchcall.upper()
116
+ else:
117
+ # if no second argument, search for calling station
118
+ searchcall = fromcall
119
+
120
+ try:
121
+ aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
122
+ except Exception as ex:
123
+ LOG.error(f"Failed to fetch aprs.fi '{ex}'")
124
+ return "Failed to fetch aprs.fi location"
125
+
126
+ LOG.debug(f"LocationPlugin: aprs_data = {aprs_data}")
127
+ if not len(aprs_data["entries"]):
128
+ LOG.error("Didn't get any entries from aprs.fi")
129
+ return "Failed to fetch aprs.fi location"
130
+
131
+ lat = float(aprs_data["entries"][0]["lat"])
132
+ lon = float(aprs_data["entries"][0]["lng"])
133
+
134
+ # Get some information about their location
135
+ try:
136
+ tic = time.perf_counter()
137
+ geolocator = geopy_factory()
138
+ LOG.info(f"Using GEOLOCATOR: {geolocator}")
139
+ coordinates = f"{lat:0.6f}, {lon:0.6f}"
140
+ location = geolocator.reverse(coordinates)
141
+ address = location.raw.get("address")
142
+ LOG.debug(f"GEOLOCATOR address: {address}")
143
+ toc = time.perf_counter()
144
+ if address:
145
+ LOG.info(f"Geopy address {address} took {toc - tic:0.4f}")
146
+ if address.get("country_code") == "us":
147
+ area_info = f"{address.get('county')}, {address.get('state')}"
148
+ else:
149
+ # what to do for address for non US?
150
+ area_info = f"{address.get('country'), 'Unknown'}"
151
+ else:
152
+ area_info = str(location)
153
+ except Exception as ex:
154
+ LOG.error(ex)
155
+ LOG.error(f"Failed to fetch Geopy address {ex}")
156
+ area_info = "Unknown Location"
157
+
158
+ try: # altitude not always provided
159
+ alt = float(aprs_data["entries"][0]["altitude"])
160
+ except Exception:
161
+ alt = 0
162
+ altfeet = int(alt * 3.28084)
163
+ aprs_lasttime_seconds = aprs_data["entries"][0]["lasttime"]
164
+ # aprs_lasttime_seconds = aprs_lasttime_seconds.encode(
165
+ # "ascii", errors="ignore"
166
+ # ) # unicode to ascii
167
+ delta_seconds = time.time() - int(aprs_lasttime_seconds)
168
+ delta_hours = delta_seconds / 60 / 60
169
+
170
+ reply = "{}: {} {}' {},{} {}h ago".format(
171
+ searchcall,
172
+ area_info,
173
+ str(altfeet),
174
+ f"{lat:0.2f}",
175
+ f"{lon:0.2f}",
176
+ str("%.1f" % round(delta_hours, 1)),
177
+ ).rstrip()
178
+
179
+ return reply
@@ -0,0 +1,61 @@
1
+ import logging
2
+
3
+ from oslo_config import cfg
4
+
5
+ from aprsd import packets, plugin
6
+
7
+
8
+ CONF = cfg.CONF
9
+ LOG = logging.getLogger("APRSD")
10
+
11
+
12
+ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
13
+ """Notification plugin to send seen message for callsign.
14
+
15
+
16
+ This plugin will track callsigns in the watch list and report
17
+ when a callsign has been seen when the last time they were
18
+ seen was older than the configured age limit.
19
+ """
20
+
21
+ short_description = "Notify me when a CALLSIGN is recently seen on APRS-IS"
22
+
23
+ def process(self, packet: packets.MessagePacket):
24
+ LOG.info("NotifySeenPlugin")
25
+
26
+ notify_callsign = CONF.watch_list.alert_callsign
27
+ fromcall = packet.from_call
28
+
29
+ wl = packets.WatchList()
30
+ age = wl.age(fromcall)
31
+
32
+ if fromcall != notify_callsign:
33
+ if wl.is_old(fromcall):
34
+ LOG.info(
35
+ "NOTIFY {} last seen {} max age={}".format(
36
+ fromcall,
37
+ age,
38
+ wl.max_delta(),
39
+ ),
40
+ )
41
+ packet_type = packet.__class__.__name__
42
+ # we shouldn't notify the alert user that they are online.
43
+ pkt = packets.MessagePacket(
44
+ from_call=CONF.callsign,
45
+ to_call=notify_callsign,
46
+ message_text=(
47
+ f"{fromcall} was just seen by type:'{packet_type}'"
48
+ ),
49
+ allow_delay=False,
50
+ )
51
+ pkt.allow_delay = False
52
+ return pkt
53
+ else:
54
+ LOG.debug(
55
+ "Not old enough to notify on callsign "
56
+ f"'{fromcall}' : {age} < {wl.max_delta()}",
57
+ )
58
+ return packets.NULL_MESSAGE
59
+ else:
60
+ LOG.debug("fromcall and notify_callsign are the same, ignoring")
61
+ return packets.NULL_MESSAGE
aprsd/plugins/ping.py ADDED
@@ -0,0 +1,31 @@
1
+ import logging
2
+ import time
3
+
4
+ from aprsd import plugin
5
+ from aprsd.utils import trace
6
+
7
+
8
+ LOG = logging.getLogger("APRSD")
9
+
10
+
11
+ class PingPlugin(plugin.APRSDRegexCommandPluginBase):
12
+ """Ping."""
13
+
14
+ command_regex = r"^([p]|[p]\s|ping)"
15
+ command_name = "ping"
16
+ short_description = "reply with a Pong!"
17
+
18
+ @trace.trace
19
+ def process(self, packet):
20
+ LOG.info("PingPlugin")
21
+ # fromcall = packet.get("from")
22
+ # message = packet.get("message_text", None)
23
+ # ack = packet.get("msgNo", "0")
24
+ stm = time.localtime()
25
+ h = stm.tm_hour
26
+ m = stm.tm_min
27
+ s = stm.tm_sec
28
+ reply = (
29
+ "Pong! " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2)
30
+ )
31
+ return reply.rstrip()
aprsd/plugins/time.py ADDED
@@ -0,0 +1,115 @@
1
+ import logging
2
+ import re
3
+
4
+ from oslo_config import cfg
5
+ import pytz
6
+ from tzlocal import get_localzone
7
+
8
+ from aprsd import packets, plugin, plugin_utils
9
+ from aprsd.utils import fuzzy, trace
10
+
11
+
12
+ CONF = cfg.CONF
13
+ LOG = logging.getLogger("APRSD")
14
+
15
+
16
+ class TimePlugin(plugin.APRSDRegexCommandPluginBase):
17
+ """Time command."""
18
+
19
+ # Look for t or t<space> or T<space> or time
20
+ command_regex = r"^([t]|[t]\s|time)"
21
+ command_name = "time"
22
+ short_description = "What is the current local time."
23
+
24
+ def _get_local_tz(self):
25
+ lz = get_localzone()
26
+ return pytz.timezone(str(lz))
27
+
28
+ def _get_utcnow(self):
29
+ return pytz.datetime.datetime.utcnow()
30
+
31
+ def build_date_str(self, localzone):
32
+ utcnow = self._get_utcnow()
33
+ gmt_t = pytz.utc.localize(utcnow)
34
+ local_t = gmt_t.astimezone(localzone)
35
+
36
+ local_short_str = local_t.strftime("%H:%M %Z")
37
+ local_hour = local_t.strftime("%H")
38
+ local_min = local_t.strftime("%M")
39
+ cur_time = fuzzy(int(local_hour), int(local_min), 1)
40
+
41
+ reply = "{} ({})".format(
42
+ cur_time,
43
+ local_short_str,
44
+ )
45
+
46
+ return reply
47
+
48
+ @trace.trace
49
+ def process(self, packet: packets.Packet):
50
+ LOG.info("TIME COMMAND")
51
+ # So we can mock this in unit tests
52
+ localzone = self._get_local_tz()
53
+ return self.build_date_str(localzone)
54
+
55
+
56
+ class TimeOWMPlugin(TimePlugin, plugin.APRSFIKEYMixin):
57
+ """OpenWeatherMap based timezone fetching."""
58
+
59
+ command_regex = r"^([t]|[t]\s|time)"
60
+ command_name = "time"
61
+ short_description = "Current time of GPS beacon's timezone. Uses OpenWeatherMap"
62
+
63
+ def setup(self):
64
+ self.ensure_aprs_fi_key()
65
+
66
+ @trace.trace
67
+ def process(self, packet: packets.MessagePacket):
68
+ fromcall = packet.from_call
69
+ message = packet.message_text
70
+ # ack = packet.get("msgNo", "0")
71
+
72
+ # optional second argument is a callsign to search
73
+ a = re.search(r"^.*\s+(.*)", message)
74
+ if a is not None:
75
+ searchcall = a.group(1)
76
+ searchcall = searchcall.upper()
77
+ else:
78
+ # if no second argument, search for calling station
79
+ searchcall = fromcall
80
+
81
+ api_key = CONF.aprs_fi.apiKey
82
+ try:
83
+ aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
84
+ except Exception as ex:
85
+ LOG.error(f"Failed to fetch aprs.fi data {ex}")
86
+ return "Failed to fetch location"
87
+
88
+ LOG.debug(f"LocationPlugin: aprs_data = {aprs_data}")
89
+ if not len(aprs_data["entries"]):
90
+ LOG.error("Didn't get any entries from aprs.fi")
91
+ return "Failed to fetch aprs.fi location"
92
+
93
+ lat = aprs_data["entries"][0]["lat"]
94
+ lon = aprs_data["entries"][0]["lng"]
95
+
96
+ try:
97
+ self.config.exists(
98
+ ["services", "openweathermap", "apiKey"],
99
+ )
100
+ except Exception as ex:
101
+ LOG.error(f"Failed to find config openweathermap:apiKey {ex}")
102
+ return "No openweathermap apiKey found"
103
+
104
+ api_key = self.config["services"]["openweathermap"]["apiKey"]
105
+ try:
106
+ results = plugin_utils.fetch_openweathermap(api_key, lat, lon)
107
+ except Exception as ex:
108
+ LOG.error(f"Couldn't fetch openweathermap api '{ex}'")
109
+ # default to UTC
110
+ localzone = pytz.timezone("UTC")
111
+ else:
112
+ tzone = results["timezone"]
113
+ localzone = pytz.timezone(tzone)
114
+
115
+ return self.build_date_str(localzone)
@@ -0,0 +1,31 @@
1
+ import logging
2
+
3
+ import aprsd
4
+ from aprsd import plugin
5
+ from aprsd.stats import collector
6
+
7
+
8
+ LOG = logging.getLogger("APRSD")
9
+
10
+
11
+ class VersionPlugin(plugin.APRSDRegexCommandPluginBase):
12
+ """Version of APRSD Plugin."""
13
+
14
+ command_regex = r"^([v]|[v]\s|version)"
15
+ command_name = "version"
16
+ short_description = "What is the APRSD Version"
17
+
18
+ # message_number:time combos so we don't resend the same email in
19
+ # five mins {int:int}
20
+ email_sent_dict = {}
21
+
22
+ def process(self, packet):
23
+ LOG.info("Version COMMAND")
24
+ # fromcall = packet.get("from")
25
+ # message = packet.get("message_text", None)
26
+ # ack = packet.get("msgNo", "0")
27
+ s = collector.Collector().collect()
28
+ return "APRSD ver:{} uptime:{}".format(
29
+ aprsd.__version__,
30
+ s["APRSDStats"]["uptime"],
31
+ )