Dynamic APRS-IS Friend Filters for Recently Heard LoRa Stations Summary #292

Open
opened 2026-10-05 05:57:52 +02:00 by adam · 0 comments
Owner

Feature Request: Dynamic APRS-IS Friend Filters for Recently Heard LoRa Stations

Summary

I would like to request an optional dynamic APRS-IS filtering feature for LoRa_APRS_iGate.

The goal is to better support high-altitude LoRa APRS stations, especially balloons, without requiring operators to use very large static APRS-IS radius filters such as m/300 or m/500.

  • Proposed behavior

Keep the operator's normal static APRS-IS filter, for example:

m/50

When the iGate hears a selected LoRa station over RF, dynamically add an APRS-IS friend-range filter around that station, for example:

m/50 f/SP0LND-6/20

Keep that dynamic filter active while the station remains in Last Heard
Automatically remove it when the station ages out

This would allow a LoRa iGate to follow APRS-IS traffic near a distant station that is actually within its RF coverage, without subscribing to all APRS traffic within a huge fixed radius around the iGate itself.


Use Case

This came up while tracking the Polish high-altitude balloon SP0LND-6 from my LoRa APRS iGate in Ridgecrest, California.

My iGate successfully received and gated SP0LND-6 from approximately 182 miles / 292 km away, while the balloon was at approximately 46,900 feet / 14,300 meters.

My station had LoRa TX enabled and APRS-IS-to-RF message/object support enabled.

However, my APRS-IS filter was:

m/50

or approximately 31 miles / 50 km.

That configuration made perfect sense from a traditional terrestrial APRS perspective, but the RF side of the LoRa iGate was hearing the balloon almost six times farther away than the APRS-IS radius I had configured.

The SP0LND-6 mission is specifically intended to support two-way communication with the balloon through third-party TX-capable LoRa APRS iGates.

That means a TX-capable iGate may be able to hear and transmit to a high-altitude balloon hundreds of kilometers away, while its APRS-IS filter is still configured only for normal local terrestrial traffic.

A large static radius such as:

m/300

would solve that problem in one sense, but it is not ideal. It causes the iGate to receive APRS traffic for a very large geographic area even when most of that traffic has no RF relevance.

A dynamic friend filter seems like a much cleaner solution.


Proposed Behavior

The operator would retain a normal base filter such as:

m/50

If the iGate hears:

SP0LND-6

over LoRa RF, the effective APRS-IS filter could become:

m/50 f/SP0LND-6/20

If it then also hears:

SP0LND-9

the effective filter could become:

m/50 f/SP0LND-6/20 f/SP0LND-9/20

When SP0LND-6 is no longer present in Last Heard, its friend filter would be removed:

m/50 f/SP0LND-9/20

When no dynamic stations remain, the filter returns to:

m/50

This preserves normal local APRS operation while temporarily following the actual RF neighborhood of distant LoRa stations.


Why Friend Filters Instead of a Large Static Radius?

A high-altitude LoRa station can have an enormous RF footprint.

In the SP0LND-6 mission documentation, LoRa links of approximately:

  • 286 miles / 461 km
  • 277 miles / 445 km

were demonstrated.

That means a terrestrial LoRa iGate can legitimately hear a balloon hundreds of kilometers away.

Using:

m/300

or:

m/500

for every iGate would pull in all APRS traffic within a huge area around the iGate, including many ground stations that are not realistically reachable over LoRa RF.

A dynamic friend filter follows only the station that the iGate has actually demonstrated it can hear.

That seems much more consistent with the normal amateur-radio goal of avoiding unnecessary traffic.


Suggested Configuration Options

It would be helpful to add a new configuration section in the Web UI, perhaps under APRS-IS:

Dynamic APRS-IS Filtering
[ ] Enable dynamic filters for heard LoRa stations

Match Mode

( ) All recently heard LoRa stations
( ) Callsign prefix
( ) APRS symbol
( ) Prefix OR symbol

Callsign Prefixes

Example:

SP0LND

Potentially allow multiple prefixes, comma-separated or space-separated:

SP0LND, W5KUB, HAB

APRS Symbols

Allow operators to select one or more APRS symbols, especially:

  • Balloon

This could be useful for operators who want to automatically support any high-altitude balloon rather than only a particular project.

Friend Radius

Example default:

20 km

This would generate:

f/CALLSIGN/20

Timeout

Example:

15 minutes

A dynamic filter would remain active while the station is being heard and expire after the configured Last Heard timeout.

Maximum Dynamic Stations

A configurable or hard maximum would be useful to prevent the filter string from growing without bounds.

For example:

Maximum dynamic stations: 9

Integration with Existing Last Heard Logic

The firmware already maintains a Last Heard list for LoRa stations.

It also already uses that information to determine whether an APRS-IS message should be transmitted back to RF.

That existing behavior is important and should remain in place.

For example:

  1. APRS-IS sends a message addressed to SP0LND-6
  2. The iGate receives that message because SP0LND-6 is included in the effective APRS-IS filter
  3. Before transmitting it over LoRa RF, the firmware still checks whether SP0LND-6 was recently heard locally
  4. If it was not recently heard, the packet is not transmitted

In other words:

APRS-IS filtering determines what traffic the iGate receives from the Internet.

Last Heard still determines what the iGate is willing to transmit over RF.

That separation seems both efficient and safe.


Dynamic Filter Updates Without Reconnecting

If possible, it would be preferable to update the APRS-IS filter dynamically rather than disconnecting and reconnecting every time the Last Heard list changes.

The server-side filter could be updated with something like:

#filter m/50 f/SP0LND-6/20

This would allow dynamic stations to be added and removed without disrupting the APRS-IS session.

A possible internal structure might be:

String buildEffectiveAprsIsFilter();
void updateAprsIsDynamicFilter();

Where:

effective filter =
base configured filter
+
active dynamic friend filters

The configured base filter should remain unchanged in persistent settings.


Suggested Matching Logic

Pseudo-logic:

onLoRaPacketReceived(packet) {

    updateLastHeard(packet.sender);

    if (!dynamicFilterEnabled)
        return;

    if (!packetHasValidPosition(packet))
        return;

    if (matchesConfiguredPrefix(packet.sender) ||
        matchesConfiguredSymbol(packet.symbol) ||
        dynamicMode == ALL_HEARD) {

        addOrRefreshDynamicStation(packet.sender);
        updateAprsIsDynamicFilter();
    }
}

Periodic cleanup:

for each dynamic station {

    if (stationIsExpiredFromLastHeard(station)) {
        removeDynamicStation(station);
        filterChanged = true;
    }
}

if (filterChanged) {
    updateAprsIsDynamicFilter();
}

Example Scenario

Base filter:

m/50

Local terrestrial stations continue to work normally within approximately 31 miles / 50 km.

A high-altitude balloon appears:

SP0LND-6

at 182 miles / 292 km.

The iGate hears it directly over LoRa RF.

Dynamic filter becomes:

m/50 f/SP0LND-6/20

The iGate can now receive APRS-IS traffic relevant to the balloon's current local area without subscribing to all APRS traffic within 186 miles / 300 km of the iGate.

When the balloon moves out of RF range and expires from Last Heard, the filter automatically returns to:

m/50

Why This Would Be Useful Beyond SP0LND

Although SP0LND-6 is what exposed this issue for me, the feature would be useful for:

  • High-altitude balloons
  • Pico balloons
  • Airborne LoRa APRS digipeaters
  • Aircraft trackers
  • Mountain-top portable stations
  • Long-distance tropospheric or line-of-sight LoRa experiments
  • Mobile stations temporarily heard far outside a normal local radius

The broader issue is that LoRa RF coverage is not always well represented by a fixed geographic radius around the iGate.

A station may be hundreds of kilometers away and still be a legitimate RF neighbor.


Optional Simpler Version

If a full dynamic friend-filter implementation is too large a change, a simpler intermediate feature could be:

Automatic Callsign-Prefix Filters

For example:

m/50 p/SP0LND

This would allow the operator to keep normal local traffic while always following all SP0LND stations.

That would already be useful for balloon support.

However, the dynamic friend-filter approach would be much more flexible and efficient because it would follow only stations the iGate is actually hearing.


Closing

I think this feature would make the iGate behave more intelligently for the kinds of long-distance RF paths that LoRa makes possible.

The issue I encountered was essentially:

RF coverage:       ~182 miles / 292 km
APRS-IS filter:     ~31 miles / 50 km

The radio could communicate with a station that the Internet-side configuration still considered far outside the local area.

A dynamic APRS-IS friend filter based on recently heard LoRa stations would bridge that gap without requiring every operator to configure an excessively large static radius.

Thanks for considering it.

73,
Edward / KI6PSP

# Feature Request: Dynamic APRS-IS Friend Filters for Recently Heard LoRa Stations ## Summary I would like to request an optional **dynamic APRS-IS filtering feature** for `LoRa_APRS_iGate`. The goal is to better support **high-altitude LoRa APRS stations, especially balloons**, without requiring operators to use very large static APRS-IS radius filters such as `m/300` or `m/500`. - ### Proposed behavior Keep the operator's normal static APRS-IS filter, for example: ```text m/50 ``` When the iGate hears a selected LoRa station over RF, dynamically add an APRS-IS **friend-range filter** around that station, for example: ```text m/50 f/SP0LND-6/20 ``` Keep that dynamic filter active while the station remains in **Last Heard** Automatically remove it when the station ages out This would allow a LoRa iGate to follow APRS-IS traffic near a distant station that is actually within its RF coverage, without subscribing to all APRS traffic within a huge fixed radius around the iGate itself. --- ## Use Case This came up while tracking the Polish high-altitude balloon **SP0LND-6** from my LoRa APRS iGate in Ridgecrest, California. My iGate successfully received and gated SP0LND-6 from approximately **182 miles / 292 km away**, while the balloon was at approximately **46,900 feet / 14,300 meters**. My station had LoRa TX enabled and APRS-IS-to-RF message/object support enabled. However, my APRS-IS filter was: ```text m/50 ``` or approximately **31 miles / 50 km**. That configuration made perfect sense from a traditional terrestrial APRS perspective, but the RF side of the LoRa iGate was hearing the balloon almost six times farther away than the APRS-IS radius I had configured. The SP0LND-6 mission is specifically intended to support **two-way communication with the balloon through third-party TX-capable LoRa APRS iGates**. That means a TX-capable iGate may be able to hear and transmit to a high-altitude balloon hundreds of kilometers away, while its APRS-IS filter is still configured only for normal local terrestrial traffic. A large static radius such as: ```text m/300 ``` would solve that problem in one sense, but it is not ideal. It causes the iGate to receive APRS traffic for a very large geographic area even when most of that traffic has no RF relevance. A dynamic friend filter seems like a much cleaner solution. --- ## Proposed Behavior The operator would retain a normal base filter such as: ```text m/50 ``` If the iGate hears: ```text SP0LND-6 ``` over LoRa RF, the effective APRS-IS filter could become: ```text m/50 f/SP0LND-6/20 ``` If it then also hears: ```text SP0LND-9 ``` the effective filter could become: ```text m/50 f/SP0LND-6/20 f/SP0LND-9/20 ``` When `SP0LND-6` is no longer present in Last Heard, its friend filter would be removed: ```text m/50 f/SP0LND-9/20 ``` When no dynamic stations remain, the filter returns to: ```text m/50 ``` This preserves normal local APRS operation while temporarily following the actual RF neighborhood of distant LoRa stations. --- ## Why Friend Filters Instead of a Large Static Radius? A high-altitude LoRa station can have an enormous RF footprint. In the SP0LND-6 mission documentation, LoRa links of approximately: - **286 miles / 461 km** - **277 miles / 445 km** were demonstrated. That means a terrestrial LoRa iGate can legitimately hear a balloon hundreds of kilometers away. Using: ```text m/300 ``` or: ```text m/500 ``` for every iGate would pull in all APRS traffic within a huge area around the iGate, including many ground stations that are not realistically reachable over LoRa RF. A dynamic friend filter follows only the station that the iGate has actually demonstrated it can hear. That seems much more consistent with the normal amateur-radio goal of avoiding unnecessary traffic. --- ## Suggested Configuration Options It would be helpful to add a new configuration section in the Web UI, perhaps under APRS-IS: ```text Dynamic APRS-IS Filtering [ ] Enable dynamic filters for heard LoRa stations ``` ### Match Mode ```text ( ) All recently heard LoRa stations ( ) Callsign prefix ( ) APRS symbol ( ) Prefix OR symbol ``` ### Callsign Prefixes Example: ```text SP0LND ``` Potentially allow multiple prefixes, comma-separated or space-separated: ```text SP0LND, W5KUB, HAB ``` ### APRS Symbols Allow operators to select one or more APRS symbols, especially: - Balloon This could be useful for operators who want to automatically support any high-altitude balloon rather than only a particular project. ### Friend Radius Example default: ```text 20 km ``` This would generate: ```text f/CALLSIGN/20 ``` ### Timeout Example: ```text 15 minutes ``` A dynamic filter would remain active while the station is being heard and expire after the configured Last Heard timeout. ### Maximum Dynamic Stations A configurable or hard maximum would be useful to prevent the filter string from growing without bounds. For example: ```text Maximum dynamic stations: 9 ``` --- ## Integration with Existing Last Heard Logic The firmware already maintains a **Last Heard** list for LoRa stations. It also already uses that information to determine whether an APRS-IS message should be transmitted back to RF. That existing behavior is important and should remain in place. For example: 1. APRS-IS sends a message addressed to `SP0LND-6` 2. The iGate receives that message because `SP0LND-6` is included in the effective APRS-IS filter 3. Before transmitting it over LoRa RF, the firmware still checks whether `SP0LND-6` was recently heard locally 4. If it was not recently heard, the packet is not transmitted In other words: > **APRS-IS filtering determines what traffic the iGate receives from the Internet.** > > **Last Heard still determines what the iGate is willing to transmit over RF.** That separation seems both efficient and safe. --- ## Dynamic Filter Updates Without Reconnecting If possible, it would be preferable to update the APRS-IS filter dynamically rather than disconnecting and reconnecting every time the Last Heard list changes. The server-side filter could be updated with something like: ```text #filter m/50 f/SP0LND-6/20 ``` This would allow dynamic stations to be added and removed without disrupting the APRS-IS session. A possible internal structure might be: ```cpp String buildEffectiveAprsIsFilter(); void updateAprsIsDynamicFilter(); ``` Where: ```text effective filter = base configured filter + active dynamic friend filters ``` The configured base filter should remain unchanged in persistent settings. --- ## Suggested Matching Logic Pseudo-logic: ```cpp onLoRaPacketReceived(packet) { updateLastHeard(packet.sender); if (!dynamicFilterEnabled) return; if (!packetHasValidPosition(packet)) return; if (matchesConfiguredPrefix(packet.sender) || matchesConfiguredSymbol(packet.symbol) || dynamicMode == ALL_HEARD) { addOrRefreshDynamicStation(packet.sender); updateAprsIsDynamicFilter(); } } ``` Periodic cleanup: ```cpp for each dynamic station { if (stationIsExpiredFromLastHeard(station)) { removeDynamicStation(station); filterChanged = true; } } if (filterChanged) { updateAprsIsDynamicFilter(); } ``` --- ## Example Scenario Base filter: ```text m/50 ``` Local terrestrial stations continue to work normally within approximately **31 miles / 50 km**. A high-altitude balloon appears: ```text SP0LND-6 ``` at **182 miles / 292 km**. The iGate hears it directly over LoRa RF. Dynamic filter becomes: ```text m/50 f/SP0LND-6/20 ``` The iGate can now receive APRS-IS traffic relevant to the balloon's current local area without subscribing to all APRS traffic within **186 miles / 300 km** of the iGate. When the balloon moves out of RF range and expires from Last Heard, the filter automatically returns to: ```text m/50 ``` --- ## Why This Would Be Useful Beyond SP0LND Although SP0LND-6 is what exposed this issue for me, the feature would be useful for: - High-altitude balloons - Pico balloons - Airborne LoRa APRS digipeaters - Aircraft trackers - Mountain-top portable stations - Long-distance tropospheric or line-of-sight LoRa experiments - Mobile stations temporarily heard far outside a normal local radius The broader issue is that **LoRa RF coverage is not always well represented by a fixed geographic radius around the iGate**. A station may be hundreds of kilometers away and still be a legitimate RF neighbor. --- ## Optional Simpler Version If a full dynamic friend-filter implementation is too large a change, a simpler intermediate feature could be: ### Automatic Callsign-Prefix Filters For example: ```text m/50 p/SP0LND ``` This would allow the operator to keep normal local traffic while always following all SP0LND stations. That would already be useful for balloon support. However, the dynamic friend-filter approach would be much more flexible and efficient because it would follow only stations the iGate is actually hearing. --- ## Closing I think this feature would make the iGate behave more intelligently for the kinds of long-distance RF paths that LoRa makes possible. The issue I encountered was essentially: ```text RF coverage: ~182 miles / 292 km APRS-IS filter: ~31 miles / 50 km ``` The radio could communicate with a station that the Internet-side configuration still considered far outside the local area. A dynamic APRS-IS friend filter based on recently heard LoRa stations would bridge that gap without requiring every operator to configure an excessively large static radius. Thanks for considering it. 73, **Edward / KI6PSP** <!-- github-sync-id:5376993677 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/LoRa_APRS_iGate#292