1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-02-03 09:44:15 -05:00

Merge pull request #139 from craigerl/walt-test

Walt test
This commit is contained in:
Walter A. Boring IV 2023-11-17 13:47:05 -05:00 committed by GitHub
commit b73373db3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 709 additions and 423 deletions

View File

@ -145,6 +145,8 @@ def sample_config(ctx):
if not sys.argv[1:]: if not sys.argv[1:]:
raise SystemExit raise SystemExit
raise raise
LOG.warning(conf.namespace)
return
generator.generate(conf) generator.generate(conf)

View File

@ -1,7 +1,6 @@
import abc import abc
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
import datetime from datetime import datetime
import json
import logging import logging
import re import re
import time import time
@ -9,9 +8,9 @@ import time
from typing import List from typing import List
import dacite import dacite
from dataclasses_json import dataclass_json
from aprsd.utils import counter from aprsd.utils import counter
from aprsd.utils import json as aprsd_json
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -28,12 +27,20 @@ PACKET_TYPE_BEACON = "beacon"
PACKET_TYPE_THIRDPARTY = "thirdparty" PACKET_TYPE_THIRDPARTY = "thirdparty"
PACKET_TYPE_UNCOMPRESSED = "uncompressed" PACKET_TYPE_UNCOMPRESSED = "uncompressed"
NO_DATE = datetime(1900, 10, 24)
def _init_timestamp(): def _init_timestamp():
"""Build a unix style timestamp integer""" """Build a unix style timestamp integer"""
return int(round(time.time())) return int(round(time.time()))
def _init_send_time():
# We have to use a datetime here, or the json encoder
# Fails on a NoneType.
return NO_DATE
def _init_msgNo(): # noqa: N802 def _init_msgNo(): # noqa: N802
"""For some reason __post__init doesn't get called. """For some reason __post__init doesn't get called.
@ -45,6 +52,20 @@ def _init_msgNo(): # noqa: N802
return c.value return c.value
def factory_from_dict(packet_dict):
pkt_type = get_packet_type(packet_dict)
if pkt_type:
cls = TYPE_LOOKUP[pkt_type]
return cls.from_dict(packet_dict)
def factory_from_json(packet_dict):
pkt_type = get_packet_type(packet_dict)
if pkt_type:
return TYPE_LOOKUP[pkt_type].from_json(packet_dict)
@dataclass_json
@dataclass(unsafe_hash=True) @dataclass(unsafe_hash=True)
class Packet(metaclass=abc.ABCMeta): class Packet(metaclass=abc.ABCMeta):
from_call: str = field(default=None) from_call: str = field(default=None)
@ -64,7 +85,19 @@ class Packet(metaclass=abc.ABCMeta):
# Fields related to sending packets out # Fields related to sending packets out
send_count: int = field(repr=False, default=0, compare=False, hash=False) send_count: int = field(repr=False, default=0, compare=False, hash=False)
retry_count: int = field(repr=False, default=3, compare=False, hash=False) retry_count: int = field(repr=False, default=3, compare=False, hash=False)
last_send_time: datetime.timedelta = field(repr=False, default=None, compare=False, hash=False) # last_send_time: datetime = field(
# metadata=dc_json_config(
# encoder=datetime.isoformat,
# decoder=datetime.fromisoformat,
# ),
# repr=True,
# default_factory=_init_send_time,
# compare=False,
# hash=False
# )
last_send_time: float = field(repr=False, default=0, compare=False, hash=False)
last_send_attempt: int = field(repr=False, default=0, compare=False, hash=False)
# Do we allow this packet to be saved to send later? # Do we allow this packet to be saved to send later?
allow_delay: bool = field(repr=False, default=True, compare=False, hash=False) allow_delay: bool = field(repr=False, default=True, compare=False, hash=False)
path: List[str] = field(default_factory=list, compare=False, hash=False) path: List[str] = field(default_factory=list, compare=False, hash=False)
@ -73,16 +106,12 @@ class Packet(metaclass=abc.ABCMeta):
def __post__init__(self): def __post__init__(self):
LOG.warning(f"POST INIT {self}") LOG.warning(f"POST INIT {self}")
@property
def __dict__(self):
return asdict(self)
@property @property
def json(self): def json(self):
""" """
get the json formated string get the json formated string
""" """
return json.dumps(self.__dict__, cls=aprsd_json.EnhancedJSONEncoder) return self.to_json()
def get(self, key, default=None): def get(self, key, default=None):
"""Emulate a getter on a dict.""" """Emulate a getter on a dict."""
@ -289,6 +318,7 @@ class RejectPacket(Packet):
self.payload = f":{self.to_call.ljust(9)} :rej{self.msgNo}" self.payload = f":{self.to_call.ljust(9)} :rej{self.msgNo}"
@dataclass_json
@dataclass(unsafe_hash=True) @dataclass(unsafe_hash=True)
class MessagePacket(Packet): class MessagePacket(Packet):
message_text: str = field(default=None) message_text: str = field(default=None)
@ -406,12 +436,12 @@ class GPSPacket(Packet):
def _build_time_zulu(self): def _build_time_zulu(self):
"""Build the timestamp in UTC/zulu.""" """Build the timestamp in UTC/zulu."""
if self.timestamp: if self.timestamp:
local_dt = datetime.datetime.fromtimestamp(self.timestamp) local_dt = datetime.fromtimestamp(self.timestamp)
else: else:
local_dt = datetime.datetime.now() local_dt = datetime.now()
self.timestamp = datetime.datetime.timestamp(local_dt) self.timestamp = datetime.timestamp(local_dt)
utc_offset_timedelta = datetime.datetime.utcnow() - local_dt utc_offset_timedelta = datetime.utcnow() - local_dt
result_utc_datetime = local_dt + utc_offset_timedelta result_utc_datetime = local_dt + utc_offset_timedelta
time_zulu = result_utc_datetime.strftime("%d%H%M") time_zulu = result_utc_datetime.strftime("%d%H%M")
return time_zulu return time_zulu
@ -567,7 +597,7 @@ class WeatherPacket(GPSPacket):
class ThirdParty(Packet): class ThirdParty(Packet):
# Holds the encapsulated packet # Holds the encapsulated packet
subpacket: Packet = None subpacket: Packet = field(default=None, compare=True, hash=False)
def __repr__(self): def __repr__(self):
"""Build the repr version of the packet.""" """Build the repr version of the packet."""
@ -600,7 +630,7 @@ def get_packet_type(packet: dict):
pkt_format = packet.get("format", None) pkt_format = packet.get("format", None)
msg_response = packet.get("response", None) msg_response = packet.get("response", None)
packet_type = "unknown" packet_type = PACKET_TYPE_UNKNOWN
if pkt_format == "message" and msg_response == "ack": if pkt_format == "message" and msg_response == "ack":
packet_type = PACKET_TYPE_ACK packet_type = PACKET_TYPE_ACK
elif pkt_format == "message" and msg_response == "rej": elif pkt_format == "message" and msg_response == "rej":
@ -620,6 +650,10 @@ def get_packet_type(packet: dict):
packet_type = PACKET_TYPE_WX packet_type = PACKET_TYPE_WX
elif pkt_format == PACKET_TYPE_THIRDPARTY: elif pkt_format == PACKET_TYPE_THIRDPARTY:
packet_type = PACKET_TYPE_THIRDPARTY packet_type = PACKET_TYPE_THIRDPARTY
if packet_type == PACKET_TYPE_UNKNOWN:
if "latitude" in packet:
packet_type = PACKET_TYPE_BEACON
return packet_type return packet_type

View File

@ -19,11 +19,12 @@ class PacketList(MutableMapping):
lock = threading.Lock() lock = threading.Lock()
_total_rx: int = 0 _total_rx: int = 0
_total_tx: int = 0 _total_tx: int = 0
types = {}
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._maxlen = 1000 cls._maxlen = 100
cls.d = OrderedDict() cls.d = OrderedDict()
return cls._instance return cls._instance
@ -32,6 +33,10 @@ class PacketList(MutableMapping):
"""Add a packet that was received.""" """Add a packet that was received."""
self._total_rx += 1 self._total_rx += 1
self._add(packet) self._add(packet)
ptype = packet.__class__.__name__
if not ptype in self.types:
self.types[ptype] = {"tx": 0, "rx": 0}
self.types[ptype]["rx"] += 1
seen_list.SeenList().update_seen(packet) seen_list.SeenList().update_seen(packet)
stats.APRSDStats().rx(packet) stats.APRSDStats().rx(packet)
@ -40,6 +45,10 @@ class PacketList(MutableMapping):
"""Add a packet that was received.""" """Add a packet that was received."""
self._total_tx += 1 self._total_tx += 1
self._add(packet) self._add(packet)
ptype = packet.__class__.__name__
if not ptype in self.types:
self.types[ptype] = {"tx": 0, "rx": 0}
self.types[ptype]["tx"] += 1
seen_list.SeenList().update_seen(packet) seen_list.SeenList().update_seen(packet)
stats.APRSDStats().tx(packet) stats.APRSDStats().tx(packet)
@ -50,6 +59,9 @@ class PacketList(MutableMapping):
def _add(self, packet): def _add(self, packet):
self[packet.key] = packet self[packet.key] = packet
def copy(self):
return self.d.copy()
@property @property
def maxlen(self): def maxlen(self):
return self._maxlen return self._maxlen

View File

@ -65,6 +65,7 @@ class PacketTrack(objectstore.ObjectStoreMixin):
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
def add(self, packet): def add(self, packet):
key = packet.msgNo key = packet.msgNo
packet._last_send_attempt = 0
self.data[key] = packet self.data[key] = packet
self.total_tracked += 1 self.total_tracked += 1
@ -83,7 +84,7 @@ class PacketTrack(objectstore.ObjectStoreMixin):
"""Walk the list of messages and restart them if any.""" """Walk the list of messages and restart them if any."""
for key in self.data.keys(): for key in self.data.keys():
pkt = self.data[key] pkt = self.data[key]
if pkt.last_send_attempt < pkt.retry_count: if pkt._last_send_attempt < pkt.retry_count:
tx.send(pkt) tx.send(pkt)
def _resend(self, packet): def _resend(self, packet):

View File

@ -1,4 +1,3 @@
import datetime
import logging import logging
import time import time
@ -128,10 +127,10 @@ class SendPacketThread(aprsd_threads.APRSDThread):
# Message is still outstanding and needs to be acked. # Message is still outstanding and needs to be acked.
if packet.last_send_time: if packet.last_send_time:
# Message has a last send time tracking # Message has a last send time tracking
now = datetime.datetime.now() now = int(round(time.time()))
sleeptime = (packet.send_count + 1) * 31 sleeptime = (packet.send_count + 1) * 31
delta = now - packet.last_send_time delta = now - packet.last_send_time
if delta > datetime.timedelta(seconds=sleeptime): if delta > sleeptime:
# It's time to try to send it again # It's time to try to send it again
send_now = True send_now = True
else: else:
@ -140,7 +139,7 @@ class SendPacketThread(aprsd_threads.APRSDThread):
if send_now: if send_now:
# no attempt time, so lets send it, and start # no attempt time, so lets send it, and start
# tracking the time. # tracking the time.
packet.last_send_time = datetime.datetime.now() packet.last_send_time = int(round(time.time()))
send(packet, direct=True) send(packet, direct=True)
packet.send_count += 1 packet.send_count += 1
@ -173,13 +172,13 @@ class SendAckThread(aprsd_threads.APRSDThread):
if self.packet.last_send_time: if self.packet.last_send_time:
# Message has a last send time tracking # Message has a last send time tracking
now = datetime.datetime.now() now = int(round(time.time()))
# aprs duplicate detection is 30 secs? # aprs duplicate detection is 30 secs?
# (21 only sends first, 28 skips middle) # (21 only sends first, 28 skips middle)
sleep_time = 31 sleep_time = 31
delta = now - self.packet.last_send_time delta = now - self.packet.last_send_time
if delta > datetime.timedelta(seconds=sleep_time): if delta > sleep_time:
# It's time to try to send it again # It's time to try to send it again
send_now = True send_now = True
elif self.loop_count % 10 == 0: elif self.loop_count % 10 == 0:
@ -190,7 +189,7 @@ class SendAckThread(aprsd_threads.APRSDThread):
if send_now: if send_now:
send(self.packet, direct=True) send(self.packet, direct=True)
self.packet.send_count += 1 self.packet.send_count += 1
self.packet.last_send_time = datetime.datetime.now() self.packet.last_send_time = int(round(time.time()))
time.sleep(1) time.sleep(1)
self.loop_count += 1 self.loop_count += 1

View File

@ -0,0 +1,403 @@
var packet_list = {};
var tx_data = [];
var rx_data = [];
var packet_types_data = {};
var mem_current = []
var mem_peak = []
function start_charts() {
console.log("start_charts() called");
// Initialize the echarts instance based on the prepared dom
create_packets_chart();
create_packets_types_chart();
create_messages_chart();
create_ack_chart();
create_memory_chart();
}
function create_packets_chart() {
// The packets totals TX/RX chart.
pkt_c_canvas = document.getElementById('packetsChart');
packets_chart = echarts.init(pkt_c_canvas);
// Specify the configuration items and data for the chart
var option = {
title: {
text: 'APRS Packet totals'
},
legend: {},
tooltip : {
trigger: 'axis'
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: true},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis: { type: 'time' },
yAxis: { },
series: [
{
name: 'tx',
type: 'line',
smooth: true,
color: 'red',
encode: {
x: 'timestamp',
y: 'tx' // refer sensor 1 value
}
},{
name: 'rx',
type: 'line',
smooth: true,
encode: {
x: 'timestamp',
y: 'rx'
}
}]
};
// Display the chart using the configuration items and data just specified.
packets_chart.setOption(option);
}
function create_packets_types_chart() {
// The packets types chart
pkt_types_canvas = document.getElementById('packetTypesChart');
packet_types_chart = echarts.init(pkt_types_canvas);
// The series and data are built and updated on the fly
// as packets come in.
var option = {
title: {
text: 'Packet Types'
},
legend: {},
tooltip : {
trigger: 'axis'
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: true},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis: { type: 'time' },
yAxis: { },
}
packet_types_chart.setOption(option);
}
function create_messages_chart() {
msg_c_canvas = document.getElementById('messagesChart');
message_chart = echarts.init(msg_c_canvas);
// Specify the configuration items and data for the chart
var option = {
title: {
text: 'Message Packets'
},
legend: {},
tooltip: {
trigger: 'axis'
},
toolbox: {
show: true,
feature: {
mark : {show: true},
dataView : {show: true, readOnly: true},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable: true,
xAxis: { type: 'time' },
yAxis: { },
series: [
{
name: 'tx',
type: 'line',
smooth: true,
color: 'red',
encode: {
x: 'timestamp',
y: 'tx' // refer sensor 1 value
}
},{
name: 'rx',
type: 'line',
smooth: true,
encode: {
x: 'timestamp',
y: 'rx'
}
}]
};
// Display the chart using the configuration items and data just specified.
message_chart.setOption(option);
}
function create_ack_chart() {
ack_canvas = document.getElementById('acksChart');
ack_chart = echarts.init(ack_canvas);
// Specify the configuration items and data for the chart
var option = {
title: {
text: 'Ack Packets'
},
legend: {},
tooltip: {
trigger: 'axis'
},
toolbox: {
show: true,
feature: {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable: true,
xAxis: { type: 'time' },
yAxis: { },
series: [
{
name: 'tx',
type: 'line',
smooth: true,
color: 'red',
encode: {
x: 'timestamp',
y: 'tx' // refer sensor 1 value
}
},{
name: 'rx',
type: 'line',
smooth: true,
encode: {
x: 'timestamp',
y: 'rx'
}
}]
};
ack_chart.setOption(option);
}
function create_memory_chart() {
ack_canvas = document.getElementById('memChart');
memory_chart = echarts.init(ack_canvas);
// Specify the configuration items and data for the chart
var option = {
title: {
text: 'Memory Usage'
},
legend: {},
tooltip: {
trigger: 'axis'
},
toolbox: {
show: true,
feature: {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable: true,
xAxis: { type: 'time' },
yAxis: { },
series: [
{
name: 'current',
type: 'line',
smooth: true,
color: 'red',
encode: {
x: 'timestamp',
y: 'current' // refer sensor 1 value
}
},{
name: 'peak',
type: 'line',
smooth: true,
encode: {
x: 'timestamp',
y: 'peak'
}
}]
};
memory_chart.setOption(option);
}
function updatePacketData(chart, time, first, second) {
tx_data.push([time, first]);
rx_data.push([time, second]);
option = {
series: [
{
name: 'tx',
data: tx_data,
},
{
name: 'rx',
data: rx_data,
}
]
}
chart.setOption(option);
}
function updatePacketTypesData(time, typesdata) {
//The options series is created on the fly each time based on
//the packet types we have in the data
var series = []
for (const k in typesdata) {
tx = [time, typesdata[k]["tx"]]
rx = [time, typesdata[k]["rx"]]
if (packet_types_data.hasOwnProperty(k)) {
packet_types_data[k]["tx"].push(tx)
packet_types_data[k]["rx"].push(rx)
} else {
packet_types_data[k] = {'tx': [tx], 'rx': [rx]}
}
}
}
function updatePacketTypesChart() {
series = []
for (const k in packet_types_data) {
entry = {
name: k+"tx",
data: packet_types_data[k]["tx"],
type: 'line',
smooth: true,
encode: {
x: 'timestamp',
y: k+'tx' // refer sensor 1 value
}
}
series.push(entry)
entry = {
name: k+"rx",
data: packet_types_data[k]["rx"],
type: 'line',
smooth: true,
encode: {
x: 'timestamp',
y: k+'rx' // refer sensor 1 value
}
}
series.push(entry)
}
option = {
series: series
}
console.log(option)
packet_types_chart.setOption(option);
}
function updateTypeChart(chart, key) {
//Generic function to update a packet type chart
if (! packet_types_data.hasOwnProperty(key)) {
return;
}
if (! packet_types_data[key].hasOwnProperty('tx')) {
return;
}
var option = {
series: [{
name: "tx",
data: packet_types_data[key]["tx"],
},
{
name: "rx",
data: packet_types_data[key]["rx"]
}]
}
chart.setOption(option);
}
function updateMemChart(time, current, peak) {
mem_current.push([time, current]);
mem_peak.push([time, peak]);
option = {
series: [
{
name: 'current',
data: mem_current,
},
{
name: 'peak',
data: mem_peak,
}
]
}
memory_chart.setOption(option);
}
function updateMessagesChart() {
updateTypeChart(message_chart, "MessagePacket")
}
function updateAcksChart() {
updateTypeChart(ack_chart, "AckPacket")
}
function update_stats( data ) {
console.log(data);
our_callsign = data["stats"]["aprsd"]["callsign"];
$("#version").text( data["stats"]["aprsd"]["version"] );
$("#aprs_connection").html( data["aprs_connection"] );
$("#uptime").text( "uptime: " + data["stats"]["aprsd"]["uptime"] );
const html_pretty = Prism.highlight(JSON.stringify(data, null, '\t'), Prism.languages.json, 'json');
$("#jsonstats").html(html_pretty);
t = Date.parse(data["time"]);
ts = new Date(t);
updatePacketData(packets_chart, ts, data["stats"]["packets"]["sent"], data["stats"]["packets"]["received"]);
updatePacketTypesData(ts, data["stats"]["packets"]["types"]);
updatePacketTypesChart();
updateMessagesChart();
updateAcksChart();
updateMemChart(ts, data["stats"]["aprsd"]["memory_current"], data["stats"]["aprsd"]["memory_peak"]);
//updateQuadData(message_chart, short_time, data["stats"]["messages"]["sent"], data["stats"]["messages"]["received"], data["stats"]["messages"]["ack_sent"], data["stats"]["messages"]["ack_recieved"]);
//updateDualData(email_chart, short_time, data["stats"]["email"]["sent"], data["stats"]["email"]["recieved"]);
//updateDualData(memory_chart, short_time, data["stats"]["aprsd"]["memory_peak"], data["stats"]["aprsd"]["memory_current"]);
}

View File

@ -6,6 +6,7 @@
<script src="https://cdn.socket.io/4.7.1/socket.io.min.js" integrity="sha512-+NaO7d6gQ1YPxvc/qHIqZEchjGm207SszoNeMgppoqD/67fEqmc1edS8zrbxPD+4RQI3gDgT/83ihpFW61TG/Q==" crossorigin="anonymous"></script> <script src="https://cdn.socket.io/4.7.1/socket.io.min.js" integrity="sha512-+NaO7d6gQ1YPxvc/qHIqZEchjGm207SszoNeMgppoqD/67fEqmc1edS8zrbxPD+4RQI3gDgT/83ihpFW61TG/Q==" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>
@ -15,7 +16,7 @@
<link rel="stylesheet" href="/static/css/prism.css"> <link rel="stylesheet" href="/static/css/prism.css">
<script src="/static/js/prism.js"></script> <script src="/static/js/prism.js"></script>
<script src="/static/js/main.js"></script> <script src="/static/js/main.js"></script>
<script src="/static/js/charts.js"></script> <script src="/static/js/echarts.js"></script>
<script src="/static/js/tabs.js"></script> <script src="/static/js/tabs.js"></script>
<script src="/static/js/send-message.js"></script> <script src="/static/js/send-message.js"></script>
<script src="/static/js/logs.js"></script> <script src="/static/js/logs.js"></script>
@ -83,6 +84,7 @@
<div class="item" data-tab="plugin-tab">Plugins</div> <div class="item" data-tab="plugin-tab">Plugins</div>
<div class="item" data-tab="config-tab">Config</div> <div class="item" data-tab="config-tab">Config</div>
<div class="item" data-tab="log-tab">LogFile</div> <div class="item" data-tab="log-tab">LogFile</div>
<!-- <div class="item" data-tab="oslo-tab">OSLO CONFIG</div> //-->
<div class="item" data-tab="raw-tab">Raw JSON</div> <div class="item" data-tab="raw-tab">Raw JSON</div>
</div> </div>
@ -92,25 +94,25 @@
<div class="ui equal width relaxed grid"> <div class="ui equal width relaxed grid">
<div class="row"> <div class="row">
<div class="column"> <div class="column">
<div class="ui segment" style="height: 300px"> <div class="ui segment" style="height: 300px" id="packetsChart"></div>
<canvas id="packetsChart"></canvas>
</div>
</div>
<div class="column">
<div class="ui segment" style="height: 300px">
<canvas id="messageChart"></canvas>
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="column"> <div class="column">
<div class="ui segment" style="height: 300px"> <div class="ui segment" style="height: 300px" id="packetTypesChart"></div>
<canvas id="emailChart"></canvas> </div>
</div> </div>
<div class="row">
<div class="column">
<div class="ui segment" style="height: 300px" id="messagesChart"></div>
</div> </div>
<div class="column"> <div class="column">
<div class="ui segment" style="height: 300px"> <div class="ui segment" style="height: 300px" id="acksChart"></div>
<canvas id="memChart"></canvas> </div>
</div>
<div class="row">
<div class="column">
<div class="ui segment" style="height: 300px" id="memChart">
</div> </div>
</div> </div>
</div> </div>
@ -118,7 +120,7 @@
<div id="stats" class="two column"> <div id="stats" class="two column">
<button class="ui button" id="toggleStats">Toggle raw json</button> <button class="ui button" id="toggleStats">Toggle raw json</button>
<pre id="jsonstats" class="language-json">{{ stats }}</pre> <pre id="jsonstats" class="language-json">{{ stats }}</pre>
</div> --!> </div> //-->
</div> </div>
</div> </div>
@ -164,9 +166,15 @@
<pre id="logContainer" style="height: 600px;overflow-y:auto;overflow-x:auto;"><code id="logtext" class="language-log" ></code></pre> <pre id="logContainer" style="height: 600px;overflow-y:auto;overflow-x:auto;"><code id="logtext" class="language-log" ></code></pre>
</div> </div>
<!--
<div class="ui bottom attached tab segment" data-tab="oslo-tab">
<h3 class="ui dividing header">OSLO</h3>
<pre id="osloContainer" style="height:600px;overflow-y:auto;" class="language-json">{{ oslo_out|safe }}</pre>
</div> //-->
<div class="ui bottom attached tab segment" data-tab="raw-tab"> <div class="ui bottom attached tab segment" data-tab="raw-tab">
<h3 class="ui dividing header">Raw JSON</h3> <h3 class="ui dividing header">Raw JSON</h3>
<pre id="jsonstats" class="language-json">{{ stats|safe }}</pre> <pre id="jsonstats" class="language-yaml" style="height:600px;overflow-y:auto;">{{ stats|safe }}</pre>
</div> </div>
<div class="ui text container"> <div class="ui text container">

View File

@ -1,4 +1,6 @@
import datetime import datetime
import importlib.metadata as imp
import io
import json import json
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
@ -8,7 +10,7 @@ import flask
from flask import Flask from flask import Flask
from flask.logging import default_handler from flask.logging import default_handler
from flask_httpauth import HTTPBasicAuth from flask_httpauth import HTTPBasicAuth
from oslo_config import cfg from oslo_config import cfg, generator
import socketio import socketio
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
@ -96,9 +98,17 @@ def _stats():
if packet_list: if packet_list:
rx = packet_list.total_rx() rx = packet_list.total_rx()
tx = packet_list.total_tx() tx = packet_list.total_tx()
types = {}
types_copy = packet_list.types.copy()
for key in types_copy:
types[str(key)] = dict(types_copy[key])
stats_dict["packets"] = { stats_dict["packets"] = {
"sent": tx, "sent": tx,
"received": rx, "received": rx,
"types": types,
} }
if track: if track:
size_tracker = len(track) size_tracker = len(track)
@ -123,7 +133,6 @@ def stats():
@app.route("/") @app.route("/")
def index(): def index():
stats = _stats() stats = _stats()
LOG.debug(stats)
wl = aprsd_rpc_client.RPCClient().get_watch_list() wl = aprsd_rpc_client.RPCClient().get_watch_list()
if wl and wl.is_enabled(): if wl and wl.is_enabled():
watch_count = len(wl) watch_count = len(wl)
@ -185,6 +194,7 @@ def index():
watch_age=watch_age, watch_age=watch_age,
seen_count=seen_count, seen_count=seen_count,
plugin_count=plugin_count, plugin_count=plugin_count,
# oslo_out=generate_oslo()
) )
@ -205,10 +215,12 @@ def get_packets():
LOG.debug("/packets called") LOG.debug("/packets called")
packet_list = aprsd_rpc_client.RPCClient().get_packet_list() packet_list = aprsd_rpc_client.RPCClient().get_packet_list()
if packet_list: if packet_list:
packets = packet_list.get()
tmp_list = [] tmp_list = []
for pkt in packets: pkts = packet_list.copy()
tmp_list.append(pkt.json) for key in pkts:
pkt = packet_list.get(key)
if pkt:
tmp_list.append(pkt.json)
return json.dumps(tmp_list) return json.dumps(tmp_list)
else: else:
@ -225,6 +237,36 @@ def plugins():
return "reloaded" return "reloaded"
def _get_namespaces():
args = []
all = imp.entry_points()
selected = []
if "oslo.config.opts" in all:
for x in all["oslo.config.opts"]:
if x.group == "oslo.config.opts":
selected.append(x)
for entry in selected:
if "aprsd" in entry.name:
args.append("--namespace")
args.append(entry.name)
return args
def generate_oslo():
CONF.namespace = _get_namespaces()
string_out = io.StringIO()
generator.generate(CONF, string_out)
return string_out.getvalue()
@auth.login_required
@app.route("/oslo")
def oslo():
return generate_oslo()
@auth.login_required @auth.login_required
@app.route("/save") @app.route("/save")
def save(): def save():
@ -348,6 +390,8 @@ if __name__ == "uwsgi_file_aprsd_wsgi":
log_level = init_app( log_level = init_app(
log_level="DEBUG", log_level="DEBUG",
config_file="/config/aprsd.conf", config_file="/config/aprsd.conf",
# Commented out for local development.
# config_file=cli_helper.DEFAULT_CONFIG_FILE
) )
setup_logging(app, log_level) setup_logging(app, log_level)
sio.register_namespace(LoggingNamespace("/logs")) sio.register_namespace(LoggingNamespace("/logs"))
@ -362,7 +406,11 @@ if __name__ == "aprsd.wsgi":
sio = socketio.Server(logger=True, async_mode=async_mode) sio = socketio.Server(logger=True, async_mode=async_mode)
app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app) app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app)
log_level = init_app(config_file="/config/aprsd.conf", log_level="DEBUG") log_level = init_app(
log_level="DEBUG",
# config_file="/config/aprsd.conf",
config_file=cli_helper.DEFAULT_CONFIG_FILE,
)
setup_logging(app, log_level) setup_logging(app, log_level)
sio.register_namespace(LoggingNamespace("/logs")) sio.register_namespace(LoggingNamespace("/logs"))
CONF.log_opt_values(LOG, logging.DEBUG) CONF.log_opt_values(LOG, logging.DEBUG)

View File

@ -1,212 +1,87 @@
# #
# This file is autogenerated by pip-compile with Python 3.11 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile --annotation-style=line dev-requirements.in # pip-compile --annotation-style=line dev-requirements.in
# #
add-trailing-comma==3.1.0 add-trailing-comma==3.1.0 # via gray
# via gray alabaster==0.7.13 # via sphinx
alabaster==0.7.13 attrs==23.1.0 # via jsonschema, referencing
# via sphinx autoflake==1.5.3 # via gray
attrs==23.1.0 babel==2.13.1 # via sphinx
# via black==23.11.0 # via gray
# jsonschema build==1.0.3 # via pip-tools
# referencing cachetools==5.3.2 # via tox
autoflake==1.5.3 certifi==2023.7.22 # via requests
# via gray cfgv==3.4.0 # via pre-commit
babel==2.12.1 chardet==5.2.0 # via tox
# via sphinx charset-normalizer==3.3.2 # via requests
black==23.7.0 click==8.1.7 # via black, pip-tools
# via gray colorama==0.4.6 # via tox
build==1.0.3 commonmark==0.9.1 # via rich
# via pip-tools configargparse==1.7 # via gray
cachetools==5.3.1 coverage[toml]==7.3.2 # via coverage, pytest-cov
# via tox distlib==0.3.7 # via virtualenv
certifi==2023.7.22 docutils==0.20.1 # via sphinx
# via requests exceptiongroup==1.1.3 # via pytest
cfgv==3.4.0 filelock==3.13.1 # via tox, virtualenv
# via pre-commit fixit==0.1.4 # via gray
chardet==5.2.0 flake8==6.1.0 # via -r dev-requirements.in, fixit, pep8-naming
# via tox gray==0.13.0 # via -r dev-requirements.in
charset-normalizer==3.2.0 identify==2.5.31 # via pre-commit
# via requests idna==3.4 # via requests
click==8.1.7 imagesize==1.4.1 # via sphinx
# via importlib-resources==6.1.1 # via fixit
# black iniconfig==2.0.0 # via pytest
# pip-tools isort==5.12.0 # via -r dev-requirements.in, gray
colorama==0.4.6 jinja2==3.1.2 # via sphinx
# via tox jsonschema==4.20.0 # via fixit
commonmark==0.9.1 jsonschema-specifications==2023.11.1 # via jsonschema
# via rich libcst==1.1.0 # via fixit
configargparse==1.7 markupsafe==2.1.3 # via jinja2
# via gray mccabe==0.7.0 # via flake8
coverage[toml]==7.3.1 mypy==1.7.0 # via -r dev-requirements.in
# via pytest-cov mypy-extensions==1.0.0 # via black, mypy, typing-inspect
distlib==0.3.7 nodeenv==1.8.0 # via pre-commit
# via virtualenv packaging==23.2 # via black, build, pyproject-api, pytest, sphinx, tox
docutils==0.20.1 pathspec==0.11.2 # via black
# via sphinx pep8-naming==0.13.3 # via -r dev-requirements.in
filelock==3.12.3 pip-tools==7.3.0 # via -r dev-requirements.in
# via platformdirs==3.11.0 # via black, tox, virtualenv
# tox pluggy==1.3.0 # via pytest, tox
# virtualenv pre-commit==3.5.0 # via -r dev-requirements.in
fixit==0.1.4 pycodestyle==2.11.1 # via flake8
# via gray pyflakes==3.1.0 # via autoflake, flake8
flake8==6.1.0 pygments==2.16.1 # via rich, sphinx
# via pyproject-api==1.6.1 # via tox
# -r dev-requirements.in pyproject-hooks==1.0.0 # via build
# fixit pytest==7.4.3 # via -r dev-requirements.in, pytest-cov
# pep8-naming pytest-cov==4.1.0 # via -r dev-requirements.in
gray==0.13.0 pyupgrade==3.15.0 # via gray
# via -r dev-requirements.in pyyaml==6.0.1 # via fixit, libcst, pre-commit
identify==2.5.27 referencing==0.31.0 # via jsonschema, jsonschema-specifications
# via pre-commit requests==2.31.0 # via sphinx
idna==3.4 rich==12.6.0 # via gray
# via requests rpds-py==0.13.0 # via jsonschema, referencing
imagesize==1.4.1 snowballstemmer==2.2.0 # via sphinx
# via sphinx sphinx==7.2.6 # via -r dev-requirements.in, sphinxcontrib-applehelp, sphinxcontrib-devhelp, sphinxcontrib-htmlhelp, sphinxcontrib-qthelp, sphinxcontrib-serializinghtml
importlib-resources==6.0.1 sphinxcontrib-applehelp==1.0.7 # via sphinx
# via fixit sphinxcontrib-devhelp==1.0.5 # via sphinx
iniconfig==2.0.0 sphinxcontrib-htmlhelp==2.0.4 # via sphinx
# via pytest sphinxcontrib-jsmath==1.0.1 # via sphinx
isort==5.12.0 sphinxcontrib-qthelp==1.0.6 # via sphinx
# via sphinxcontrib-serializinghtml==1.1.9 # via sphinx
# -r dev-requirements.in tokenize-rt==5.2.0 # via add-trailing-comma, pyupgrade
# gray toml==0.10.2 # via autoflake
jinja2==3.1.2 tomli==2.0.1 # via black, build, coverage, mypy, pip-tools, pyproject-api, pyproject-hooks, pytest, tox
# via sphinx tox==4.11.3 # via -r dev-requirements.in
jsonschema==4.19.0 typing-extensions==4.8.0 # via black, libcst, mypy, typing-inspect
# via fixit typing-inspect==0.9.0 # via libcst
jsonschema-specifications==2023.7.1 unify==0.5 # via gray
# via jsonschema untokenize==0.1.1 # via unify
libcst==1.0.1 urllib3==2.1.0 # via requests
# via fixit virtualenv==20.24.6 # via pre-commit, tox
markupsafe==2.1.3 wheel==0.41.3 # via pip-tools
# via jinja2
mccabe==0.7.0
# via flake8
mypy==1.5.1
# via -r dev-requirements.in
mypy-extensions==1.0.0
# via
# black
# mypy
# typing-inspect
nodeenv==1.8.0
# via pre-commit
packaging==23.1
# via
# black
# build
# pyproject-api
# pytest
# sphinx
# tox
pathspec==0.11.2
# via black
pep8-naming==0.13.3
# via -r dev-requirements.in
pip-tools==7.3.0
# via -r dev-requirements.in
platformdirs==3.10.0
# via
# black
# tox
# virtualenv
pluggy==1.3.0
# via
# pytest
# tox
pre-commit==3.4.0
# via -r dev-requirements.in
pycodestyle==2.11.0
# via flake8
pyflakes==3.1.0
# via
# autoflake
# flake8
pygments==2.16.1
# via
# rich
# sphinx
pyproject-api==1.6.1
# via tox
pyproject-hooks==1.0.0
# via build
pytest==7.4.2
# via
# -r dev-requirements.in
# pytest-cov
pytest-cov==4.1.0
# via -r dev-requirements.in
pyupgrade==3.10.1
# via gray
pyyaml==6.0.1
# via
# fixit
# libcst
# pre-commit
referencing==0.30.2
# via
# jsonschema
# jsonschema-specifications
requests==2.31.0
# via sphinx
rich==12.6.0
# via gray
rpds-py==0.10.2
# via
# jsonschema
# referencing
snowballstemmer==2.2.0
# via sphinx
sphinx==7.2.5
# via
# -r dev-requirements.in
# sphinxcontrib-applehelp
# sphinxcontrib-devhelp
# sphinxcontrib-htmlhelp
# sphinxcontrib-qthelp
# sphinxcontrib-serializinghtml
sphinxcontrib-applehelp==1.0.7
# via sphinx
sphinxcontrib-devhelp==1.0.5
# via sphinx
sphinxcontrib-htmlhelp==2.0.4
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.6
# via sphinx
sphinxcontrib-serializinghtml==1.1.9
# via sphinx
tokenize-rt==5.2.0
# via
# add-trailing-comma
# pyupgrade
toml==0.10.2
# via autoflake
tox==4.11.2
# via -r dev-requirements.in
typing-extensions==4.7.1
# via
# libcst
# mypy
# typing-inspect
typing-inspect==0.9.0
# via libcst
unify==0.5
# via gray
untokenize==0.1.1
# via unify
urllib3==2.0.7
# via requests
virtualenv==20.24.5
# via
# pre-commit
# tox
wheel==0.41.2
# via pip-tools
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# pip # pip

View File

@ -36,3 +36,4 @@ rpyc
shellingham shellingham
geopy geopy
rush rush
dataclasses-json

View File

@ -1,180 +1,83 @@
# #
# This file is autogenerated by pip-compile with Python 3.11 # This file is autogenerated by pip-compile with Python 3.10
# by the following command: # by the following command:
# #
# pip-compile --annotation-style=line requirements.in # pip-compile --annotation-style=line requirements.in
# #
aprslib==0.7.2 aprslib==0.7.2 # via -r requirements.in
# via -r requirements.in attrs==23.1.0 # via -r requirements.in, ax253, kiss3, rush
attrs==23.1.0 ax253==0.1.5.post1 # via kiss3
# via beautifulsoup4==4.12.2 # via -r requirements.in
# -r requirements.in bidict==0.22.1 # via python-socketio
# ax253 bitarray==2.8.3 # via ax253, kiss3
# kiss3 blinker==1.7.0 # via flask
# rush certifi==2023.7.22 # via requests
ax253==0.1.5.post1 charset-normalizer==3.3.2 # via requests
# via kiss3 click==8.1.7 # via -r requirements.in, click-completion, click-params, flask
beautifulsoup4==4.12.2 click-completion==0.5.2 # via -r requirements.in
# via -r requirements.in click-params==0.4.1 # via -r requirements.in
bidict==0.22.1 commonmark==0.9.1 # via rich
# via python-socketio dacite2==2.0.0 # via -r requirements.in
bitarray==2.8.1 dataclasses==0.6 # via -r requirements.in
# via dataclasses-json==0.6.2 # via -r requirements.in
# ax253 debtcollector==2.5.0 # via oslo-config
# kiss3 decorator==5.1.1 # via validators
blinker==1.6.2 dnspython==2.4.2 # via eventlet
# via flask eventlet==0.33.3 # via -r requirements.in
certifi==2023.7.22 flask==3.0.0 # via -r requirements.in, flask-httpauth, flask-socketio
# via requests flask-httpauth==4.8.0 # via -r requirements.in
charset-normalizer==3.2.0 flask-socketio==5.3.6 # via -r requirements.in
# via requests geographiclib==2.0 # via geopy
click==8.1.7 geopy==2.4.0 # via -r requirements.in
# via gevent==23.9.1 # via -r requirements.in
# -r requirements.in greenlet==3.0.1 # via eventlet, gevent
# click-completion h11==0.14.0 # via wsproto
# click-params idna==3.4 # via requests
# flask imapclient==3.0.0 # via -r requirements.in
click-completion==0.5.2 importlib-metadata==6.8.0 # via ax253, kiss3
# via -r requirements.in itsdangerous==2.1.2 # via flask
click-params==0.4.1 jinja2==3.1.2 # via click-completion, flask
# via -r requirements.in kiss3==8.0.0 # via -r requirements.in
commonmark==0.9.1 markupsafe==2.1.3 # via jinja2, werkzeug
# via rich marshmallow==3.20.1 # via dataclasses-json
dacite2==2.0.0 mypy-extensions==1.0.0 # via typing-inspect
# via -r requirements.in netaddr==0.9.0 # via oslo-config
dataclasses==0.6 oslo-config==9.2.0 # via -r requirements.in
# via -r requirements.in oslo-i18n==6.2.0 # via oslo-config
debtcollector==2.5.0 packaging==23.2 # via marshmallow
# via oslo-config pbr==6.0.0 # via -r requirements.in, oslo-i18n, stevedore
decorator==5.1.1 pluggy==1.3.0 # via -r requirements.in
# via validators plumbum==1.8.2 # via rpyc
dnspython==2.4.2 pygments==2.16.1 # via rich
# via eventlet pyserial==3.5 # via pyserial-asyncio
eventlet==0.33.3 pyserial-asyncio==0.6 # via kiss3
# via -r requirements.in python-engineio==4.8.0 # via python-socketio
flask==2.3.3 python-socketio==5.10.0 # via -r requirements.in, flask-socketio
# via pytz==2023.3.post1 # via -r requirements.in
# -r requirements.in pyyaml==6.0.1 # via -r requirements.in, oslo-config
# flask-httpauth requests==2.31.0 # via -r requirements.in, oslo-config, update-checker
# flask-socketio rfc3986==2.0.0 # via oslo-config
flask-httpauth==4.8.0 rich==12.6.0 # via -r requirements.in
# via -r requirements.in rpyc==5.3.1 # via -r requirements.in
flask-socketio==5.3.6 rush==2021.4.0 # via -r requirements.in
# via -r requirements.in shellingham==1.5.4 # via -r requirements.in, click-completion
geographiclib==2.0 simple-websocket==1.0.0 # via python-engineio
# via geopy six==1.16.0 # via -r requirements.in, click-completion, eventlet
geopy==2.4.0 soupsieve==2.5 # via beautifulsoup4
# via -r requirements.in stevedore==5.1.0 # via oslo-config
gevent==23.9.1 tabulate==0.9.0 # via -r requirements.in
# via -r requirements.in thesmuggler==1.0.1 # via -r requirements.in
greenlet==3.0.0rc3 typing-extensions==4.8.0 # via typing-inspect
# via typing-inspect==0.9.0 # via dataclasses-json
# eventlet update-checker==0.18.0 # via -r requirements.in
# gevent urllib3==2.1.0 # via requests
idna==3.4 validators==0.20.0 # via click-params
# via requests werkzeug==3.0.1 # via -r requirements.in, flask
imapclient==2.3.1 wrapt==1.16.0 # via -r requirements.in, debtcollector
# via -r requirements.in wsproto==1.2.0 # via simple-websocket
importlib-metadata==6.8.0 zipp==3.17.0 # via importlib-metadata
# via zope-event==5.0 # via gevent
# ax253 zope-interface==6.1 # via gevent
# kiss3
itsdangerous==2.1.2
# via flask
jinja2==3.1.2
# via
# click-completion
# flask
kiss3==8.0.0
# via -r requirements.in
markupsafe==2.1.3
# via
# jinja2
# werkzeug
netaddr==0.8.0
# via oslo-config
oslo-config==9.2.0
# via -r requirements.in
oslo-i18n==6.1.0
# via oslo-config
pbr==5.11.1
# via
# -r requirements.in
# oslo-i18n
# stevedore
pluggy==1.3.0
# via -r requirements.in
plumbum==1.8.2
# via rpyc
pygments==2.16.1
# via rich
pyserial==3.5
# via pyserial-asyncio
pyserial-asyncio==0.6
# via kiss3
python-engineio==4.7.0
# via python-socketio
python-socketio==5.9.0
# via
# -r requirements.in
# flask-socketio
pytz==2023.3.post1
# via -r requirements.in
pyyaml==6.0.1
# via
# -r requirements.in
# oslo-config
requests==2.31.0
# via
# -r requirements.in
# oslo-config
# update-checker
rfc3986==2.0.0
# via oslo-config
rich==12.6.0
# via -r requirements.in
rpyc==5.3.1
# via -r requirements.in
rush==2021.4.0
# via -r requirements.in
shellingham==1.5.3
# via
# -r requirements.in
# click-completion
six==1.16.0
# via
# -r requirements.in
# click-completion
# eventlet
# imapclient
soupsieve==2.5
# via beautifulsoup4
stevedore==5.1.0
# via oslo-config
tabulate==0.9.0
# via -r requirements.in
thesmuggler==1.0.1
# via -r requirements.in
update-checker==0.18.0
# via -r requirements.in
urllib3==2.0.7
# via requests
validators==0.20.0
# via click-params
werkzeug==3.0.1
# via
# -r requirements.in
# flask
wrapt==1.15.0
# via
# -r requirements.in
# debtcollector
zipp==3.16.2
# via importlib-metadata
zope-event==5.0
# via gevent
zope-interface==6.0
# via gevent
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
# setuptools # setuptools

View File

@ -2,7 +2,7 @@
minversion = 2.9.0 minversion = 2.9.0
skipdist = True skipdist = True
skip_missing_interpreters = true skip_missing_interpreters = true
envlist = pep8,py{39,310} envlist = pep8,py{39,310,311}
#requires = tox-pipenv #requires = tox-pipenv
# pip==22.0.4 # pip==22.0.4
# pip-tools==5.4.0 # pip-tools==5.4.0