Building a VHF / LoRa APRS Bridge

In this article I will expose how I built a VHF (1200bps AX25 APRS) to UHF LoRa APRS Bridge without going through APRS-IS (Internet). This is work in progress, feel free to comment and come back to make sure you are up to date with the latest version of this project.

Introduction

I always considered APRS to be a full two-way system. Lately there has been a trend to turn it into a dumb one-way internet dependent vehicle tracking system, especially in the LoRa APRS world. The poor WB4APR must be spinning in his grave at high RPM. APRS is about being aware of your “ham-radio” surrounding.

Ok, enough of ranting. Since LoRa APRS is booming in Germany just across the border, I decided to give it a try. I thought it is quite sad that 1200bps AX25APRS and LoRa APRS just coexist without any bridges in between. As a matter of fact I started thinking about how to build a bridge between the two worlds so as both sides are locally aware of each other.

My first thought was to use APRS-IS, but this turned out to be a pain in the *ss, as it is almost impossible to cherry pick only the LoRa stations. Finally I came out with an onsite solution which does not require the internet at all! Perfect for remote sites without internet!

Requirements

Here are the requirements I had in mind:

  • Only pass directly heard packets. This has the beauty to restrict the range of the bridge to the actual RF covered area of both technologies. No fancy geographical filters are needed. It also prevents looping between the two worlds.
    Digipeating any packet that has already been digipeated can cause loops between VHF and LoRA and is detrimental to both networks as it will cloak both channels: DO NOT DO IT!
    ONLY CROSSBAND DIRECTLY HEARD PACKETS !
  • Only pass positions and messages. Digipating objects,telemetry,items,weather is considered spam on VHF thus I consider them to be spam when digipeated on LoRa.
  • Rate Limit packets based on callsign. This is to mitigate people who think it is ok to beacon with a 6sec beacon rate.
  • Single responsibility principle: Be able to stop the bridge and keep the VHF and LoRa running separately

Prerequisites

From now on, I assume you already have the following things working. For the sake of conciseness, I will not describe how to set these things up, Google and the documentation of the involved software are your friends.

VHF APRS AX25 1200bps

You need to have one of the following:

  • A running AX25 setup using the Linux AX25 Stack
    – OR –
  • A KISS TNC connected through serial port
    – OR –
  • A KISS TNC exposed through TCP

I went for the latter option, as I already had it up and running. The AX25 stack setup was working but, somehow, APRX was not happy about using different callsigns than the ones specified on the underlying AX25 interfaces.

UHF LoRa

What you need:

Bridging

The bridge will be done using APRX. APRX is a digipeater software capable of handling multiple ports. It can be configured to only allow digipeating between certain ports.

I assume you have it compiled and installed and that you also know how to start it.

Principle

My setup consists of the following:

  • Direwolf handling the VHF part. It does all the digipeating on its own.
  • LoRa handling is done by a simple TTGO, it is autonomous and does all the digipeating on its own.

We have these two blocks and now we need to configure aprx to act as a bridge between the two world.

Here is a simple diagram of the whole system:

Configuring APRX

While I was never impressed by APRX’s igating capabilities I always regarded it as a very good an highly configurable digipeater software.

To meet our requirement of only digipeating directly heard poisitions and messages we need to setup APRX the proper way. We do not want APRX to digipeat anything on the same band, this is handled by Direwolf and the TTGO.

Below is a copy of my aprx.conf setup. I am using AX25 stack on both LoRa and VHF. I tried to explain each option as much as good as I can.

For some reason, APRX needs to operate under distinct callsigns on each bands. Otherwise, I observed some strange effects on its own beacons.

The VHF side will be opearting under F4FXL-4 and the LoRa side as F4FXL-11

# This is the callsign and SSID our bridge wil operate under,
# we do not need it as each band will have it's own callsign
# yet we need to specify it otherwise APRX won't TX beacons (bug ?)
mycall  F4FXL-11

# !!!!! this the location of the bridge used in beacons, adjust to your own!
myloc lat 4849.50N lon 00736.28E

# Below is some logging stuff
<logging>
	pidfile /var/run/aprx.pid
	rflog /var/log/aprx/aprx-rf.log
	aprxlog /var/log/aprx/aprx.log
	#erlangfile /var/run/aprx.state
</logging>

# here we define all the interfaces
# F4FXL-4 is the VHF interface
<interface>
	tcp-device	127.0.0.1 8001 KISS # this is the direwolf IP
	callsign	F4FXL-4
	tx-ok		true	# this interface is allowed to transmit
	telem-to-is	false	# we do not want the traffic channel telemetry
</interface>
# F4FXL-11 is the LoRa interface
<interface>
	tcp-device	192.168.123.192 8001 KISS # this is the TTGO on my LAN
	callsign 	F4FXL-11 # this interface is allowed to transmit
	tx-ok		true
	telem-to-is	false
</interface>

# Below we define the beacons which are sent over the air
# First beacon block goes out on LoRa
<beacon>
	beaconmode	radio
	cycle-size	30m
	beacon symbol B# $myloc comment "VHF <=> LoRa Bridge (LoRa Side)" srccall F4FXL-11 interface F4FXL-11 via WIDE1-1
	beacon symbol B# $myloc comment "VHF <=> LoRa bridge (LoRa Side)" srccall F4FXL-11 interface F4FXL-11
</beacon>
# Second beacon block goes out on VHF
<beacon>
	beaconmode	radio
	cycle-size	30m
        beacon symbol B# $myloc comment "VHF <=> LoRa Bridge (VHF Side)" srccall F4FXL-4 interface F4FXL-4 via WIDE1-1
        beacon symbol B# $myloc comment "VHF <=> LoRa bridge (VHF Side)" srccall F4FXL-4 interface F4FXL-4
</beacon>

#Here we define the digipeaters with their sources and transmitters
#LoRa to VHF
<digipeater>
	transmitter	F4FXL-4 # VHF is our outgoing interface
	srcratelimit    3 6	# channel flood protection allow max 3 (peak 6) packet/minutes/callsign
	<source>
		source		F4FXL-11 # LoRa is our incoming interface
		relay-type	directonly # restrict to directly heard frames
		regex-filter data ^\} # somehow the blocking of 3rd party frames does not always work, hence this
		filter		t/pmq # We only pass positions, messages and queries
		filter		-t/3cinostuw # block everything else
		<trace>
			keys	WIDE # only apply WIDEn-N paradigm
		</trace>
	</source>
</digipeater>

#VHF to LoRa
<digipeater>
	transmitter	F4FXL-11 # LoRa is our outgoing interface
	srcratelimit	3 6	# channel flood protection allow max 3 (peak 6) packets/minutes/callsign
	<source>
		source		F4FXL-4 # VHF is our incoming interface
		relay-type	directonly # restrict to directly heard frames
		regex-filter data ^\} # somehow the blocking of 3rd party frames does not always work, hence this
		filter t/pmq # only allow positions, messages and queries
		filter -t/3cinostuw # block everything else
		<trace>
			keys	WIDE #only apply WIDEn-N paradigm
		</trace>
	</source>
</digipeater>

Code language: Apache (apache)

How it works

a packet entering VHF as

F1ABC>APRS,WIDE1-1,WIDE2-1:blablaCode language: CSS (css)

will got out on LoRa as

F1ABC>APRS,F4FXL-11*,WIDE2-1:blablaCode language: CSS (css)

The other way around, a packet entering LoRa as

F1ABC>APRS,WIDE1-1,WIDE2-1:blablaCode language: CSS (css)

will go out on VHF as

F1ABC>APRS,F4FXL-4*,WIDE2-1:blablaCode language: CSS (css)

Revision History

May 30th 2024: first publication
May 31st 2024: correct typo in diagram
June 1st 2024: clarify why it is important to only cross band directly heard packets!
June 3rd 2024: Tighten 3rd Party Filter

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.