Add stats_store_interval config option to control how frequently
the statsstore.json file is written to disk. Default remains 10
seconds for backward compatibility.
This allows reducing disk I/O in production deployments and
can help avoid potential file corruption issues when external
processes read the stats file.
The singleton's max_delta was being modified by test_init_custom_stale_timeout
and not restored, causing test_is_stale_connection_false to fail because
it expected 2 minutes but got 60 seconds.
The APRSISDriver uses @singleton decorator which transforms the class
into a function. The test was incorrectly trying to use __new__ which
doesn't work with decorated singletons. Instead, re-initialize the
existing instance after changing the config.
Add a new 'stale_timeout' configuration option to the aprs_network config
group that allows users to customize how long to wait before considering
an APRS-IS connection stale.
Problem:
The stale connection threshold was hardcoded to 2 minutes. In environments
with frequent network hiccups or when using certain APRS-IS servers that
may drop connections silently, 2 minutes can be too long to wait before
reconnecting, resulting in significant data loss.
Solution:
- Add 'stale_timeout' option to aprsd/conf/client.py with default of 120s
- Update APRSISDriver.__init__ to use the config value
- Maintain backward compatibility by defaulting to 120s if not configured
- Update tests to handle the new configuration option
Usage:
[aprs_network]
stale_timeout = 60 # Reconnect after 60 seconds without data
The default remains 120 seconds (2 minutes) for backward compatibility.
- APRSDRXThread: Replace time.sleep with self.wait for interruptible waits
- APRSDRXThread.stop(): Use _shutdown_event.set() instead of thread_stop
- APRSDRXThread: Error recovery waits check for shutdown signal
- APRSDFilterThread: Use queue timeout with self.period for interruptible wait
- Remove unused time import
- Update tests to use new Event-based API
- Add daemon=True class attribute (subclasses override to False)
- Add period=1 class attribute for wait interval
- Replace thread_stop boolean with _shutdown_event (threading.Event)
- Add wait() method for interruptible sleeps
- Update tests for new Event-based API
BREAKING: thread_stop boolean replaced with _shutdown_event.
Code checking thread.thread_stop directly must use thread._shutdown_event.is_set()
SECURITY FIX: Replace pickle.load() with json.load() to eliminate
remote code execution vulnerability from malicious pickle files.
Changes:
- Update ObjectStoreMixin to use JSON instead of pickle
- Add PacketJSONDecoder to reconstruct Packet objects from JSON
- Change file extension from .p to .json
- Add warning when old pickle files detected
- Add OrderedDict restoration for PacketList
- Update all tests to work with JSON format
Users with existing pickle files must run:
aprsd dev migrate-pickle
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The client registry defined a protocol that all drivers had
to implement. This patch ensures that all methods are
consistent in the protocol definition.
This adds a new option in the aprsd.conf [DEFAULT] section
that denotes who is the callsign that officially owns this APRSD
instance. This will be used for sending the instance info to the
registry. It's useful when the callsign used by the instance is
something useful on the APRS network, which isn't necessarily the
same as the person that owns it.
It's been confusing for a while that when we configured aprsd,
we had to enter the callsign in the [DEFAULT] section and
the [aprs_network] section.
This patch removes the login from the aprs_network section. aprsd
will now use the main callsign in the [DEFAULT] section as the callsign
to login to the aprsis network.
The Main RX Thread that runs the client.consumer() call used to
parse packets as soon as it got them. This lead to an iffecient
strategy for listen and acquire packets as fast as possible.
The APRSDRXThread now gets the raw packet from the client and
shoves it on the packet_queue. The other threads that are looking
for packets on the packet_queue will parse the raw packet with
aprslib. This allows us to capture packets as quickly as we can,
and then process those packets in the secondary threads.
This prevents a bottleneck capturing packets.
The watchList was updating the last seen during RX time.
This happens before the NotifySeenPlugin even sees the packet,
so the callsign is never 'old'. this patch fixes that, so the
watch list works.
This patch includes a completely reworked client structure.
There is now only 1 client object, that loads the appropriate
drivers. The drivers are fake, aprsis and tcpkiss.
The TCPKISS client was written from scratch to avoid using asyncio.
Asyncion is nothing but a pain in the ass.
If any part of the code had a packet object and called prepare()
on it, it would create a msgNo if it wasn't set. Sometimes we
get packets on the wire/RF that don't have a msgNo, so adding one
locally is wrong. We should ONLY create a msgNo on a packet that
is destined for transmission.
This patch ensures that even if prepare() is called, that only
packets that are created locally for TX get a msgNo unless it
already exists from RX'd packets.
this patch refactors the client, drivers and client factory
to use the same Protocol mechanism used by the stats collector
to construct the proper client to be used according to
the configuration
This patch eliminates the need for a custom
static method on each Packetclass to convert an aprslib
raw decoded dictionary -> correct Packet class.
This now uses the built in dataclasses_json from_dict()
mixin with an override for both the WeatherPacket and
the ThirdPartyPacket.
This patch also adds the TelemetryPacket and adds some
missing members to a few of the classes from test runs
decoding all packets from APRS-IS -> Packet classes.
Also adds some verification for packets in test_packets