aprsd 3.4.4__py3-none-any.whl → 4.0.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.
- aprsd/cli_helper.py +12 -5
- aprsd/client/aprsis.py +31 -9
- aprsd/client/base.py +13 -2
- aprsd/client/drivers/aprsis.py +6 -3
- aprsd/client/drivers/fake.py +15 -20
- aprsd/client/factory.py +0 -2
- aprsd/client/fake.py +0 -2
- aprsd/client/kiss.py +17 -2
- aprsd/client/stats.py +1 -3
- aprsd/cmds/completion.py +7 -4
- aprsd/cmds/dev.py +38 -42
- aprsd/cmds/fetch_stats.py +140 -143
- aprsd/cmds/healthcheck.py +5 -3
- aprsd/cmds/list_plugins.py +140 -134
- aprsd/cmds/listen.py +13 -9
- aprsd/cmds/server.py +53 -27
- aprsd/conf/__init__.py +1 -2
- aprsd/conf/client.py +3 -4
- aprsd/conf/common.py +19 -93
- aprsd/conf/log.py +2 -4
- aprsd/conf/opts.py +5 -4
- aprsd/conf/plugin_common.py +11 -121
- aprsd/exception.py +2 -0
- aprsd/log/log.py +7 -46
- aprsd/main.py +10 -4
- aprsd/packets/__init__.py +14 -4
- aprsd/packets/core.py +57 -67
- aprsd/packets/log.py +8 -8
- aprsd/packets/packet_list.py +9 -6
- aprsd/plugin.py +22 -11
- aprsd/plugins/notify.py +1 -4
- aprsd/plugins/weather.py +10 -8
- aprsd/stats/collector.py +5 -2
- aprsd/threads/__init__.py +3 -2
- aprsd/threads/aprsd.py +12 -7
- aprsd/threads/{keep_alive.py → keepalive.py} +14 -45
- aprsd/threads/registry.py +3 -3
- aprsd/threads/rx.py +9 -6
- aprsd/threads/stats.py +2 -2
- aprsd/threads/tx.py +3 -4
- aprsd/utils/__init__.py +42 -10
- aprsd/utils/json.py +9 -4
- aprsd/utils/keepalive_collector.py +55 -0
- aprsd/utils/trace.py +0 -2
- aprsd-4.0.1.dist-info/AUTHORS +1 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/METADATA +307 -408
- aprsd-4.0.1.dist-info/RECORD +74 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/WHEEL +1 -1
- aprsd/cmds/admin.py +0 -57
- aprsd/cmds/webchat.py +0 -662
- aprsd/conf/plugin_email.py +0 -105
- aprsd/plugins/email.py +0 -715
- aprsd/plugins/location.py +0 -181
- aprsd/threads/log_monitor.py +0 -121
- aprsd/web/__init__.py +0 -0
- aprsd/web/admin/__init__.py +0 -0
- aprsd/web/admin/static/css/index.css +0 -84
- aprsd/web/admin/static/css/prism.css +0 -4
- aprsd/web/admin/static/css/tabs.css +0 -35
- aprsd/web/admin/static/images/Untitled.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/admin/static/js/charts.js +0 -235
- aprsd/web/admin/static/js/echarts.js +0 -465
- aprsd/web/admin/static/js/logs.js +0 -26
- aprsd/web/admin/static/js/main.js +0 -231
- aprsd/web/admin/static/js/prism.js +0 -12
- aprsd/web/admin/static/js/send-message.js +0 -114
- aprsd/web/admin/static/js/tabs.js +0 -28
- aprsd/web/admin/templates/index.html +0 -196
- aprsd/web/chat/static/css/chat.css +0 -115
- aprsd/web/chat/static/css/index.css +0 -66
- aprsd/web/chat/static/css/style.css.map +0 -1
- aprsd/web/chat/static/css/tabs.css +0 -41
- aprsd/web/chat/static/css/upstream/bootstrap.min.css +0 -6
- aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/google-fonts.css +0 -23
- aprsd/web/chat/static/css/upstream/jquery-ui.css +0 -1311
- aprsd/web/chat/static/css/upstream/jquery.toast.css +0 -28
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
- aprsd/web/chat/static/images/Untitled.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/chat/static/images/globe.svg +0 -3
- aprsd/web/chat/static/js/gps.js +0 -84
- aprsd/web/chat/static/js/main.js +0 -45
- aprsd/web/chat/static/js/send-message.js +0 -612
- aprsd/web/chat/static/js/tabs.js +0 -28
- aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +0 -7
- aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +0 -2
- aprsd/web/chat/static/js/upstream/jquery-ui.min.js +0 -13
- aprsd/web/chat/static/js/upstream/jquery.toast.js +0 -374
- aprsd/web/chat/static/js/upstream/semantic.min.js +0 -11
- aprsd/web/chat/static/js/upstream/socket.io.min.js +0 -7
- aprsd/web/chat/templates/index.html +0 -139
- aprsd/wsgi.py +0 -322
- aprsd-3.4.4.dist-info/AUTHORS +0 -13
- aprsd-3.4.4.dist-info/RECORD +0 -134
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/LICENSE +0 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/entry_points.txt +0 -0
- {aprsd-3.4.4.dist-info → aprsd-4.0.1.dist-info}/top_level.txt +0 -0
aprsd/cmds/webchat.py
DELETED
@@ -1,662 +0,0 @@
|
|
1
|
-
import datetime
|
2
|
-
import json
|
3
|
-
import logging
|
4
|
-
import signal
|
5
|
-
import sys
|
6
|
-
import threading
|
7
|
-
import time
|
8
|
-
|
9
|
-
import click
|
10
|
-
import flask
|
11
|
-
from flask import request
|
12
|
-
from flask_httpauth import HTTPBasicAuth
|
13
|
-
from flask_socketio import Namespace, SocketIO
|
14
|
-
from geopy.distance import geodesic
|
15
|
-
from oslo_config import cfg
|
16
|
-
import timeago
|
17
|
-
from werkzeug.security import check_password_hash, generate_password_hash
|
18
|
-
import wrapt
|
19
|
-
|
20
|
-
import aprsd
|
21
|
-
from aprsd import cli_helper, client, packets, plugin_utils, stats, threads
|
22
|
-
from aprsd import utils
|
23
|
-
from aprsd import utils as aprsd_utils
|
24
|
-
from aprsd.client import client_factory, kiss
|
25
|
-
from aprsd.main import cli
|
26
|
-
from aprsd.threads import aprsd as aprsd_threads
|
27
|
-
from aprsd.threads import keep_alive, rx
|
28
|
-
from aprsd.threads import stats as stats_thread
|
29
|
-
from aprsd.threads import tx
|
30
|
-
from aprsd.utils import trace
|
31
|
-
|
32
|
-
|
33
|
-
CONF = cfg.CONF
|
34
|
-
LOG = logging.getLogger()
|
35
|
-
auth = HTTPBasicAuth()
|
36
|
-
users = {}
|
37
|
-
socketio = None
|
38
|
-
|
39
|
-
# List of callsigns that we don't want to track/fetch their location
|
40
|
-
callsign_no_track = [
|
41
|
-
"APDW16", "BLN0", "BLN1", "BLN2",
|
42
|
-
"BLN3", "BLN4", "BLN5", "BLN6", "BLN7", "BLN8", "BLN9",
|
43
|
-
]
|
44
|
-
|
45
|
-
# Callsign location information
|
46
|
-
# callsign: {lat: 0.0, long: 0.0, last_update: datetime}
|
47
|
-
callsign_locations = {}
|
48
|
-
|
49
|
-
flask_app = flask.Flask(
|
50
|
-
"aprsd",
|
51
|
-
static_url_path="/static",
|
52
|
-
static_folder="web/chat/static",
|
53
|
-
template_folder="web/chat/templates",
|
54
|
-
)
|
55
|
-
|
56
|
-
|
57
|
-
def signal_handler(sig, frame):
|
58
|
-
|
59
|
-
click.echo("signal_handler: called")
|
60
|
-
LOG.info(
|
61
|
-
f"Ctrl+C, Sending all threads({len(threads.APRSDThreadList())}) exit! "
|
62
|
-
f"Can take up to 10 seconds {datetime.datetime.now()}",
|
63
|
-
)
|
64
|
-
threads.APRSDThreadList().stop_all()
|
65
|
-
if "subprocess" not in str(frame):
|
66
|
-
time.sleep(1.5)
|
67
|
-
stats.stats_collector.collect()
|
68
|
-
LOG.info("Telling flask to bail.")
|
69
|
-
signal.signal(signal.SIGTERM, sys.exit(0))
|
70
|
-
|
71
|
-
|
72
|
-
class SentMessages:
|
73
|
-
|
74
|
-
_instance = None
|
75
|
-
lock = threading.Lock()
|
76
|
-
|
77
|
-
data = {}
|
78
|
-
|
79
|
-
def __new__(cls, *args, **kwargs):
|
80
|
-
"""This magic turns this into a singleton."""
|
81
|
-
if cls._instance is None:
|
82
|
-
cls._instance = super().__new__(cls)
|
83
|
-
return cls._instance
|
84
|
-
|
85
|
-
def is_initialized(self):
|
86
|
-
return True
|
87
|
-
|
88
|
-
@wrapt.synchronized(lock)
|
89
|
-
def add(self, msg):
|
90
|
-
self.data[msg.msgNo] = msg.__dict__
|
91
|
-
|
92
|
-
@wrapt.synchronized(lock)
|
93
|
-
def __len__(self):
|
94
|
-
return len(self.data.keys())
|
95
|
-
|
96
|
-
@wrapt.synchronized(lock)
|
97
|
-
def get(self, id):
|
98
|
-
if id in self.data:
|
99
|
-
return self.data[id]
|
100
|
-
|
101
|
-
@wrapt.synchronized(lock)
|
102
|
-
def get_all(self):
|
103
|
-
return self.data
|
104
|
-
|
105
|
-
@wrapt.synchronized(lock)
|
106
|
-
def set_status(self, id, status):
|
107
|
-
if id in self.data:
|
108
|
-
self.data[id]["last_update"] = str(datetime.datetime.now())
|
109
|
-
self.data[id]["status"] = status
|
110
|
-
|
111
|
-
@wrapt.synchronized(lock)
|
112
|
-
def ack(self, id):
|
113
|
-
"""The message got an ack!"""
|
114
|
-
if id in self.data:
|
115
|
-
self.data[id]["last_update"] = str(datetime.datetime.now())
|
116
|
-
self.data[id]["ack"] = True
|
117
|
-
|
118
|
-
@wrapt.synchronized(lock)
|
119
|
-
def reply(self, id, packet):
|
120
|
-
"""We got a packet back from the sent message."""
|
121
|
-
if id in self.data:
|
122
|
-
self.data[id]["reply"] = packet
|
123
|
-
|
124
|
-
|
125
|
-
# HTTPBasicAuth doesn't work on a class method.
|
126
|
-
# This has to be out here. Rely on the APRSDFlask
|
127
|
-
# class to initialize the users from the config
|
128
|
-
@auth.verify_password
|
129
|
-
def verify_password(username, password):
|
130
|
-
global users
|
131
|
-
|
132
|
-
if username in users and check_password_hash(users[username], password):
|
133
|
-
return username
|
134
|
-
|
135
|
-
|
136
|
-
def _build_location_from_repeat(message):
|
137
|
-
# This is a location message Format is
|
138
|
-
# ^ld^callsign:latitude,longitude,altitude,course,speed,timestamp
|
139
|
-
a = message.split(":")
|
140
|
-
LOG.warning(a)
|
141
|
-
if len(a) == 2:
|
142
|
-
callsign = a[0].replace("^ld^", "")
|
143
|
-
b = a[1].split(",")
|
144
|
-
LOG.warning(b)
|
145
|
-
if len(b) == 6:
|
146
|
-
lat = float(b[0])
|
147
|
-
lon = float(b[1])
|
148
|
-
alt = float(b[2])
|
149
|
-
course = float(b[3])
|
150
|
-
speed = float(b[4])
|
151
|
-
time = int(b[5])
|
152
|
-
compass_bearing = aprsd_utils.degrees_to_cardinal(course)
|
153
|
-
data = {
|
154
|
-
"callsign": callsign,
|
155
|
-
"lat": lat,
|
156
|
-
"lon": lon,
|
157
|
-
"altitude": alt,
|
158
|
-
"course": course,
|
159
|
-
"compass_bearing": compass_bearing,
|
160
|
-
"speed": speed,
|
161
|
-
"lasttime": time,
|
162
|
-
"timeago": timeago.format(time),
|
163
|
-
}
|
164
|
-
LOG.debug(f"Location data from REPEAT {data}")
|
165
|
-
return data
|
166
|
-
|
167
|
-
|
168
|
-
def _calculate_location_data(location_data):
|
169
|
-
"""Calculate all of the location data from data from aprs.fi or REPEAT."""
|
170
|
-
lat = location_data["lat"]
|
171
|
-
lon = location_data["lon"]
|
172
|
-
alt = location_data["altitude"]
|
173
|
-
speed = location_data["speed"]
|
174
|
-
lasttime = location_data["lasttime"]
|
175
|
-
timeago_str = location_data.get(
|
176
|
-
"timeago",
|
177
|
-
timeago.format(lasttime),
|
178
|
-
)
|
179
|
-
# now calculate distance from our own location
|
180
|
-
distance = 0
|
181
|
-
if CONF.webchat.latitude and CONF.webchat.longitude:
|
182
|
-
our_lat = float(CONF.webchat.latitude)
|
183
|
-
our_lon = float(CONF.webchat.longitude)
|
184
|
-
distance = geodesic((our_lat, our_lon), (lat, lon)).kilometers
|
185
|
-
bearing = aprsd_utils.calculate_initial_compass_bearing(
|
186
|
-
(our_lat, our_lon),
|
187
|
-
(lat, lon),
|
188
|
-
)
|
189
|
-
compass_bearing = aprsd_utils.degrees_to_cardinal(bearing)
|
190
|
-
return {
|
191
|
-
"callsign": location_data["callsign"],
|
192
|
-
"lat": lat,
|
193
|
-
"lon": lon,
|
194
|
-
"altitude": alt,
|
195
|
-
"course": f"{bearing:0.1f}",
|
196
|
-
"compass_bearing": compass_bearing,
|
197
|
-
"speed": speed,
|
198
|
-
"lasttime": lasttime,
|
199
|
-
"timeago": timeago_str,
|
200
|
-
"distance": f"{distance:0.1f}",
|
201
|
-
}
|
202
|
-
|
203
|
-
|
204
|
-
def send_location_data_to_browser(location_data):
|
205
|
-
global socketio
|
206
|
-
callsign = location_data["callsign"]
|
207
|
-
LOG.info(f"Got location for {callsign} {callsign_locations[callsign]}")
|
208
|
-
socketio.emit(
|
209
|
-
"callsign_location", callsign_locations[callsign],
|
210
|
-
namespace="/sendmsg",
|
211
|
-
)
|
212
|
-
|
213
|
-
|
214
|
-
def populate_callsign_location(callsign, data=None):
|
215
|
-
"""Populate the location for the callsign.
|
216
|
-
|
217
|
-
if data is passed in, then we have the location already from
|
218
|
-
an APRS packet. If data is None, then we need to fetch the
|
219
|
-
location from aprs.fi or REPEAT.
|
220
|
-
"""
|
221
|
-
global socketio
|
222
|
-
"""Fetch the location for the callsign."""
|
223
|
-
LOG.debug(f"populate_callsign_location {callsign}")
|
224
|
-
if data:
|
225
|
-
location_data = _calculate_location_data(data)
|
226
|
-
callsign_locations[callsign] = location_data
|
227
|
-
send_location_data_to_browser(location_data)
|
228
|
-
return
|
229
|
-
|
230
|
-
# First we are going to try to get the location from aprs.fi
|
231
|
-
# if there is no internets, then this will fail and we will
|
232
|
-
# fallback to calling REPEAT for the location for the callsign.
|
233
|
-
fallback = False
|
234
|
-
if not CONF.aprs_fi.apiKey:
|
235
|
-
LOG.warning(
|
236
|
-
"Config aprs_fi.apiKey is not set. Can't get location from aprs.fi "
|
237
|
-
" falling back to sending REPEAT to get location.",
|
238
|
-
)
|
239
|
-
fallback = True
|
240
|
-
else:
|
241
|
-
try:
|
242
|
-
aprs_data = plugin_utils.get_aprs_fi(CONF.aprs_fi.apiKey, callsign)
|
243
|
-
if not len(aprs_data["entries"]):
|
244
|
-
LOG.error("Didn't get any entries from aprs.fi")
|
245
|
-
return
|
246
|
-
lat = float(aprs_data["entries"][0]["lat"])
|
247
|
-
lon = float(aprs_data["entries"][0]["lng"])
|
248
|
-
try: # altitude not always provided
|
249
|
-
alt = float(aprs_data["entries"][0]["altitude"])
|
250
|
-
except Exception:
|
251
|
-
alt = 0
|
252
|
-
location_data = {
|
253
|
-
"callsign": callsign,
|
254
|
-
"lat": lat,
|
255
|
-
"lon": lon,
|
256
|
-
"altitude": alt,
|
257
|
-
"lasttime": int(aprs_data["entries"][0]["lasttime"]),
|
258
|
-
"course": float(aprs_data["entries"][0].get("course", 0)),
|
259
|
-
"speed": float(aprs_data["entries"][0].get("speed", 0)),
|
260
|
-
}
|
261
|
-
location_data = _calculate_location_data(location_data)
|
262
|
-
callsign_locations[callsign] = location_data
|
263
|
-
send_location_data_to_browser(location_data)
|
264
|
-
return
|
265
|
-
except Exception as ex:
|
266
|
-
LOG.error(f"Failed to fetch aprs.fi '{ex}'")
|
267
|
-
LOG.error(ex)
|
268
|
-
fallback = True
|
269
|
-
|
270
|
-
if fallback:
|
271
|
-
# We don't have the location data
|
272
|
-
# and we can't get it from aprs.fi
|
273
|
-
# Send a special message to REPEAT to get the location data
|
274
|
-
LOG.info(f"Sending REPEAT to get location for callsign {callsign}.")
|
275
|
-
tx.send(
|
276
|
-
packets.MessagePacket(
|
277
|
-
from_call=CONF.callsign,
|
278
|
-
to_call="REPEAT",
|
279
|
-
message_text=f"ld {callsign}",
|
280
|
-
),
|
281
|
-
)
|
282
|
-
|
283
|
-
|
284
|
-
class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
|
285
|
-
"""Class that handles packets being sent to us."""
|
286
|
-
|
287
|
-
def __init__(self, packet_queue, socketio):
|
288
|
-
self.socketio = socketio
|
289
|
-
self.connected = False
|
290
|
-
super().__init__(packet_queue)
|
291
|
-
|
292
|
-
def process_ack_packet(self, packet: packets.AckPacket):
|
293
|
-
super().process_ack_packet(packet)
|
294
|
-
ack_num = packet.get("msgNo")
|
295
|
-
SentMessages().ack(ack_num)
|
296
|
-
msg = SentMessages().get(ack_num)
|
297
|
-
if msg:
|
298
|
-
self.socketio.emit(
|
299
|
-
"ack", msg,
|
300
|
-
namespace="/sendmsg",
|
301
|
-
)
|
302
|
-
self.got_ack = True
|
303
|
-
|
304
|
-
def process_our_message_packet(self, packet: packets.MessagePacket):
|
305
|
-
global callsign_locations
|
306
|
-
# ok lets see if we have the location for the
|
307
|
-
# person we just sent a message to.
|
308
|
-
from_call = packet.get("from_call").upper()
|
309
|
-
if from_call == "REPEAT":
|
310
|
-
# We got a message from REPEAT. Is this a location message?
|
311
|
-
message = packet.get("message_text")
|
312
|
-
if message.startswith("^ld^"):
|
313
|
-
location_data = _build_location_from_repeat(message)
|
314
|
-
callsign = location_data["callsign"]
|
315
|
-
location_data = _calculate_location_data(location_data)
|
316
|
-
callsign_locations[callsign] = location_data
|
317
|
-
send_location_data_to_browser(location_data)
|
318
|
-
return
|
319
|
-
elif (
|
320
|
-
from_call not in callsign_locations
|
321
|
-
and from_call not in callsign_no_track
|
322
|
-
and client_factory.create().transport() in [client.TRANSPORT_APRSIS, client.TRANSPORT_FAKE]
|
323
|
-
):
|
324
|
-
# We have to ask aprs for the location for the callsign
|
325
|
-
# We send a message packet to wb4bor-11 asking for location.
|
326
|
-
populate_callsign_location(from_call)
|
327
|
-
# Send the packet to the browser.
|
328
|
-
self.socketio.emit(
|
329
|
-
"new", packet.__dict__,
|
330
|
-
namespace="/sendmsg",
|
331
|
-
)
|
332
|
-
|
333
|
-
|
334
|
-
class LocationProcessingThread(aprsd_threads.APRSDThread):
|
335
|
-
"""Class to handle the location processing."""
|
336
|
-
def __init__(self):
|
337
|
-
super().__init__("LocationProcessingThread")
|
338
|
-
|
339
|
-
def loop(self):
|
340
|
-
pass
|
341
|
-
|
342
|
-
|
343
|
-
def set_config():
|
344
|
-
global users
|
345
|
-
|
346
|
-
|
347
|
-
def _get_transport(stats):
|
348
|
-
if CONF.aprs_network.enabled:
|
349
|
-
transport = "aprs-is"
|
350
|
-
aprs_connection = (
|
351
|
-
"APRS-IS Server: <a href='http://status.aprs2.net' >"
|
352
|
-
"{}</a>".format(stats["APRSClientStats"]["server_string"])
|
353
|
-
)
|
354
|
-
elif kiss.KISSClient.is_enabled():
|
355
|
-
transport = kiss.KISSClient.transport()
|
356
|
-
if transport == client.TRANSPORT_TCPKISS:
|
357
|
-
aprs_connection = (
|
358
|
-
"TCPKISS://{}:{}".format(
|
359
|
-
CONF.kiss_tcp.host,
|
360
|
-
CONF.kiss_tcp.port,
|
361
|
-
)
|
362
|
-
)
|
363
|
-
elif transport == client.TRANSPORT_SERIALKISS:
|
364
|
-
# for pep8 violation
|
365
|
-
aprs_connection = (
|
366
|
-
"SerialKISS://{}@{} baud".format(
|
367
|
-
CONF.kiss_serial.device,
|
368
|
-
CONF.kiss_serial.baudrate,
|
369
|
-
),
|
370
|
-
)
|
371
|
-
elif CONF.fake_client.enabled:
|
372
|
-
transport = client.TRANSPORT_FAKE
|
373
|
-
aprs_connection = "Fake Client"
|
374
|
-
|
375
|
-
return transport, aprs_connection
|
376
|
-
|
377
|
-
|
378
|
-
@flask_app.route("/location/<callsign>", methods=["POST"])
|
379
|
-
def location(callsign):
|
380
|
-
LOG.debug(f"Fetch location for callsign {callsign}")
|
381
|
-
if not callsign in callsign_no_track:
|
382
|
-
populate_callsign_location(callsign)
|
383
|
-
|
384
|
-
|
385
|
-
@auth.login_required
|
386
|
-
@flask_app.route("/")
|
387
|
-
def index():
|
388
|
-
stats = _stats()
|
389
|
-
|
390
|
-
# For development
|
391
|
-
html_template = "index.html"
|
392
|
-
LOG.debug(f"Template {html_template}")
|
393
|
-
|
394
|
-
transport, aprs_connection = _get_transport(stats["stats"])
|
395
|
-
LOG.debug(f"transport {transport} aprs_connection {aprs_connection}")
|
396
|
-
|
397
|
-
stats["transport"] = transport
|
398
|
-
stats["aprs_connection"] = aprs_connection
|
399
|
-
LOG.debug(f"initial stats = {stats}")
|
400
|
-
latitude = CONF.webchat.latitude
|
401
|
-
if latitude:
|
402
|
-
latitude = float(CONF.webchat.latitude)
|
403
|
-
|
404
|
-
longitude = CONF.webchat.longitude
|
405
|
-
if longitude:
|
406
|
-
longitude = float(longitude)
|
407
|
-
|
408
|
-
return flask.render_template(
|
409
|
-
html_template,
|
410
|
-
initial_stats=stats,
|
411
|
-
aprs_connection=aprs_connection,
|
412
|
-
callsign=CONF.callsign,
|
413
|
-
version=aprsd.__version__,
|
414
|
-
latitude=latitude,
|
415
|
-
longitude=longitude,
|
416
|
-
)
|
417
|
-
|
418
|
-
|
419
|
-
@auth.login_required
|
420
|
-
@flask_app.route("/send-message-status")
|
421
|
-
def send_message_status():
|
422
|
-
LOG.debug(request)
|
423
|
-
msgs = SentMessages()
|
424
|
-
info = msgs.get_all()
|
425
|
-
return json.dumps(info)
|
426
|
-
|
427
|
-
|
428
|
-
def _stats():
|
429
|
-
now = datetime.datetime.now()
|
430
|
-
|
431
|
-
time_format = "%m-%d-%Y %H:%M:%S"
|
432
|
-
stats_dict = stats.stats_collector.collect(serializable=True)
|
433
|
-
# Webchat doesnt need these
|
434
|
-
if "WatchList" in stats_dict:
|
435
|
-
del stats_dict["WatchList"]
|
436
|
-
if "SeenList" in stats_dict:
|
437
|
-
del stats_dict["SeenList"]
|
438
|
-
if "APRSDThreadList" in stats_dict:
|
439
|
-
del stats_dict["APRSDThreadList"]
|
440
|
-
if "PacketList" in stats_dict:
|
441
|
-
del stats_dict["PacketList"]
|
442
|
-
if "EmailStats" in stats_dict:
|
443
|
-
del stats_dict["EmailStats"]
|
444
|
-
if "PluginManager" in stats_dict:
|
445
|
-
del stats_dict["PluginManager"]
|
446
|
-
|
447
|
-
result = {
|
448
|
-
"time": now.strftime(time_format),
|
449
|
-
"stats": stats_dict,
|
450
|
-
}
|
451
|
-
return result
|
452
|
-
|
453
|
-
|
454
|
-
@flask_app.route("/stats")
|
455
|
-
def get_stats():
|
456
|
-
return json.dumps(_stats())
|
457
|
-
|
458
|
-
|
459
|
-
class SendMessageNamespace(Namespace):
|
460
|
-
"""Class to handle the socketio interactions."""
|
461
|
-
got_ack = False
|
462
|
-
reply_sent = False
|
463
|
-
msg = None
|
464
|
-
request = None
|
465
|
-
|
466
|
-
def __init__(self, namespace=None, config=None):
|
467
|
-
super().__init__(namespace)
|
468
|
-
|
469
|
-
def on_connect(self):
|
470
|
-
global socketio
|
471
|
-
LOG.debug("Web socket connected")
|
472
|
-
socketio.emit(
|
473
|
-
"connected", {"data": "/sendmsg Connected"},
|
474
|
-
namespace="/sendmsg",
|
475
|
-
)
|
476
|
-
|
477
|
-
def on_disconnect(self):
|
478
|
-
LOG.debug("WS Disconnected")
|
479
|
-
|
480
|
-
def on_send(self, data):
|
481
|
-
global socketio
|
482
|
-
LOG.debug(f"WS: on_send {data}")
|
483
|
-
self.request = data
|
484
|
-
data["from"] = CONF.callsign
|
485
|
-
path = data.get("path", None)
|
486
|
-
if not path:
|
487
|
-
path = []
|
488
|
-
elif "," in path:
|
489
|
-
path_opts = path.split(",")
|
490
|
-
path = [x.strip() for x in path_opts]
|
491
|
-
else:
|
492
|
-
path = [path]
|
493
|
-
|
494
|
-
pkt = packets.MessagePacket(
|
495
|
-
from_call=data["from"],
|
496
|
-
to_call=data["to"].upper(),
|
497
|
-
message_text=data["message"],
|
498
|
-
path=path,
|
499
|
-
)
|
500
|
-
pkt.prepare()
|
501
|
-
self.msg = pkt
|
502
|
-
msgs = SentMessages()
|
503
|
-
tx.send(pkt)
|
504
|
-
msgs.add(pkt)
|
505
|
-
msgs.set_status(pkt.msgNo, "Sending")
|
506
|
-
obj = msgs.get(pkt.msgNo)
|
507
|
-
socketio.emit(
|
508
|
-
"sent", obj,
|
509
|
-
namespace="/sendmsg",
|
510
|
-
)
|
511
|
-
|
512
|
-
def on_gps(self, data):
|
513
|
-
LOG.debug(f"WS on_GPS: {data}")
|
514
|
-
lat = data["latitude"]
|
515
|
-
long = data["longitude"]
|
516
|
-
LOG.debug(f"Lat {lat}")
|
517
|
-
LOG.debug(f"Long {long}")
|
518
|
-
path = data.get("path", None)
|
519
|
-
if not path:
|
520
|
-
path = []
|
521
|
-
elif "," in path:
|
522
|
-
path_opts = path.split(",")
|
523
|
-
path = [x.strip() for x in path_opts]
|
524
|
-
else:
|
525
|
-
path = [path]
|
526
|
-
|
527
|
-
tx.send(
|
528
|
-
packets.BeaconPacket(
|
529
|
-
from_call=CONF.callsign,
|
530
|
-
to_call="APDW16",
|
531
|
-
latitude=lat,
|
532
|
-
longitude=long,
|
533
|
-
comment="APRSD WebChat Beacon",
|
534
|
-
path=path,
|
535
|
-
),
|
536
|
-
direct=True,
|
537
|
-
)
|
538
|
-
|
539
|
-
def handle_message(self, data):
|
540
|
-
LOG.debug(f"WS Data {data}")
|
541
|
-
|
542
|
-
def handle_json(self, data):
|
543
|
-
LOG.debug(f"WS json {data}")
|
544
|
-
|
545
|
-
def on_get_callsign_location(self, data):
|
546
|
-
LOG.debug(f"on_callsign_location {data}")
|
547
|
-
if data["callsign"] not in callsign_no_track:
|
548
|
-
populate_callsign_location(data["callsign"])
|
549
|
-
|
550
|
-
|
551
|
-
@trace.trace
|
552
|
-
def init_flask(loglevel, quiet):
|
553
|
-
global socketio, flask_app
|
554
|
-
|
555
|
-
socketio = SocketIO(
|
556
|
-
flask_app, logger=False, engineio_logger=False,
|
557
|
-
async_mode="threading",
|
558
|
-
)
|
559
|
-
|
560
|
-
socketio.on_namespace(
|
561
|
-
SendMessageNamespace(
|
562
|
-
"/sendmsg",
|
563
|
-
),
|
564
|
-
)
|
565
|
-
return socketio
|
566
|
-
|
567
|
-
|
568
|
-
# main() ###
|
569
|
-
@cli.command()
|
570
|
-
@cli_helper.add_options(cli_helper.common_options)
|
571
|
-
@click.option(
|
572
|
-
"-f",
|
573
|
-
"--flush",
|
574
|
-
"flush",
|
575
|
-
is_flag=True,
|
576
|
-
show_default=True,
|
577
|
-
default=False,
|
578
|
-
help="Flush out all old aged messages on disk.",
|
579
|
-
)
|
580
|
-
@click.option(
|
581
|
-
"-p",
|
582
|
-
"--port",
|
583
|
-
"port",
|
584
|
-
show_default=True,
|
585
|
-
default=None,
|
586
|
-
help="Port to listen to web requests. This overrides the config.webchat.web_port setting.",
|
587
|
-
)
|
588
|
-
@click.pass_context
|
589
|
-
@cli_helper.process_standard_options
|
590
|
-
def webchat(ctx, flush, port):
|
591
|
-
"""Web based HAM Radio chat program!"""
|
592
|
-
loglevel = ctx.obj["loglevel"]
|
593
|
-
quiet = ctx.obj["quiet"]
|
594
|
-
|
595
|
-
signal.signal(signal.SIGINT, signal_handler)
|
596
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
597
|
-
|
598
|
-
level, msg = utils._check_version()
|
599
|
-
if level:
|
600
|
-
LOG.warning(msg)
|
601
|
-
else:
|
602
|
-
LOG.info(msg)
|
603
|
-
LOG.info(f"APRSD Started version: {aprsd.__version__}")
|
604
|
-
|
605
|
-
CONF.log_opt_values(logging.getLogger(), logging.DEBUG)
|
606
|
-
user = CONF.admin.user
|
607
|
-
users[user] = generate_password_hash(CONF.admin.password)
|
608
|
-
if not port:
|
609
|
-
port = CONF.webchat.web_port
|
610
|
-
|
611
|
-
# Initialize the client factory and create
|
612
|
-
# The correct client object ready for use
|
613
|
-
# Make sure we have 1 client transport enabled
|
614
|
-
if not client_factory.is_client_enabled():
|
615
|
-
LOG.error("No Clients are enabled in config.")
|
616
|
-
sys.exit(-1)
|
617
|
-
|
618
|
-
if not client_factory.is_client_configured():
|
619
|
-
LOG.error("APRS client is not properly configured in config file.")
|
620
|
-
sys.exit(-1)
|
621
|
-
|
622
|
-
# Creates the client object
|
623
|
-
LOG.info("Creating client connection")
|
624
|
-
aprs_client = client_factory.create()
|
625
|
-
LOG.info(aprs_client)
|
626
|
-
if not aprs_client.login_success:
|
627
|
-
# We failed to login, will just quit!
|
628
|
-
msg = f"Login Failure: {aprs_client.login_failure}"
|
629
|
-
LOG.error(msg)
|
630
|
-
print(msg)
|
631
|
-
sys.exit(-1)
|
632
|
-
|
633
|
-
keepalive = keep_alive.KeepAliveThread()
|
634
|
-
LOG.info("Start KeepAliveThread")
|
635
|
-
keepalive.start()
|
636
|
-
|
637
|
-
stats_store_thread = stats_thread.APRSDStatsStoreThread()
|
638
|
-
stats_store_thread.start()
|
639
|
-
|
640
|
-
socketio = init_flask(loglevel, quiet)
|
641
|
-
rx_thread = rx.APRSDPluginRXThread(
|
642
|
-
packet_queue=threads.packet_queue,
|
643
|
-
)
|
644
|
-
rx_thread.start()
|
645
|
-
process_thread = WebChatProcessPacketThread(
|
646
|
-
packet_queue=threads.packet_queue,
|
647
|
-
socketio=socketio,
|
648
|
-
)
|
649
|
-
process_thread.start()
|
650
|
-
|
651
|
-
LOG.info("Start socketio.run()")
|
652
|
-
socketio.run(
|
653
|
-
flask_app,
|
654
|
-
# This is broken for now after removing cryptography
|
655
|
-
# and pyopenssl
|
656
|
-
# ssl_context="adhoc",
|
657
|
-
host=CONF.webchat.web_ip,
|
658
|
-
port=port,
|
659
|
-
allow_unsafe_werkzeug=True,
|
660
|
-
)
|
661
|
-
|
662
|
-
LOG.info("WebChat exiting!!!! Bye.")
|