On Creating the Parks on the Air Webmap

As an Amateur Radio Operator, I frequently take my gear out to state parks to operate Parks on the Air, “international portable amateur radio operations that promote emergency awareness and communications from national/federal and state/provincial level parks.”

As a Geospatial Information System professional, I thought combining the two and creating a webmap, would be pertinent.The backbone of the webmap is a Postgresql database with the PostGIS extension, so that it can house geospatial information. This database is running on my Proxmox server in an LXC container running Debian 12.

The data is served with Geoserver, which is also running on Proxmox in an LXC container. I chose an LXC container instead of a full virtual machine as they are much lighter, resource-wise.The webmap itself was written in JavaScript. I used OpenLayers to create the map layers. This was quite simple, but the tricky part was dynamically appending to the activation table. Since I sometimes show up at a specific activation location multiple times, the table needed to be resized accordingly. To do that, I wrote this:

function showActivationDetailsDialog(activationDetailsPromise) {
    showHideActivationDetailsDialog("show");

    activationDetailsPromise
        .then(activationDetails => {
            const activationInfoElement = document.getElementById('activationInfo');
            activationInfoElement.innerHTML = ""; // Clear existing content

            const tableElement = document.createElement('table');
            tableElement.classList.add('styled-table');

            const theadElement = document.createElement('thead');
            const tbodyElement = document.createElement('tbody');

            const titleRowElement = document.createElement('tr');
            const titleCellElement = document.createElement('th');
            titleCellElement.colSpan = 3; // Updated colspan to include the new column
            titleCellElement.classList.add('table-title');
            titleCellElement.textContent = 'Activations';
            titleRowElement.appendChild(titleCellElement);
            theadElement.appendChild(titleRowElement);

            const headerRowElement = document.createElement('tr');
            const dateHeaderCellElement = document.createElement('th');
            dateHeaderCellElement.textContent = 'Date';
            const validHeaderCellElement = document.createElement('th');
            validHeaderCellElement.textContent = 'Valid Activation?'; // New header for the new column
            const emptyHeaderCellElement = document.createElement('th');
            headerRowElement.appendChild(dateHeaderCellElement);
            headerRowElement.appendChild(validHeaderCellElement);
            headerRowElement.appendChild(emptyHeaderCellElement);
            theadElement.appendChild(headerRowElement);

            activationDetails.forEach(activation => {
                if (activation.hasOwnProperty('date')) {
                    const cleanedDate = activation.date.slice(0, -1);
                    const rowElement = document.createElement('tr');
                    rowElement.classList.add('active-row'); // Add active-row class to all rows
                    const dateCellElement = document.createElement('td');
                    dateCellElement.textContent = cleanedDate;
                    const validCellElement = document.createElement('td');
                    validCellElement.textContent = activation.is_valid ? 'Yes' : 'No';
                    const emptyCellElement = document.createElement('td');
                    rowElement.appendChild(dateCellElement);
                    rowElement.appendChild(validCellElement);
                    rowElement.appendChild(emptyCellElement);
                    tbodyElement.appendChild(rowElement);
                }
            });

            tableElement.appendChild(theadElement);
            tableElement.appendChild(tbodyElement);
            activationInfoElement.appendChild(tableElement);
        })
        .catch(error => {
            console.error(error);
            // Handle the error case
        });
}

Updating the data is simple! The QGIS (Quantum Geographic Information System) software easily connects to my Postgresql database and it just takes a few clicks to add a new activation location. A few more clicks and some typing, and all the required information is logged.If you want to see the full code behind the website, it is here.

Enabling KISS mode on the Kenwood TM-D710G

I recently picked up a new Kenwood D710G to be used for 2 meter packet radio. I decided to set up a BPQ node as the software is very powerful and offers a lot of features.

In order to get the D710G working with BPQ, you must first enable KISS mode in the built-in TNC. To do so, you’ll. need to first connect to it. You’ll need the Minicom software to do so. Follow these steps:

  1. Run sudo apt install minicom
  2. Run sudo minicom -s
  3. Go to Serial port setup and press enter
  4. Press A, and type your radio’s serial port location. It is most likely /dev/ttyUSB0
  5. Press enter, and press E
  6. Select your radio’s baud rate
  7. Pres enter
  8. Press escape
  9. Go to Exit from Minicom, and press enter
  10. Run sudo minicom
  11. Press enter if you don’t see cmd:
  12. run kiss on
  13. run restart
  14. Press CTRL-A and then Z and then X to exit minicom

BPQ should now be able to communicate with your radio’s TNC.

Checking the weather using packet radio

After spending some time playing around with BPQ, I have learned how powerful its applications interface is. I first followed a guide to create a system info application, allowing for checking system resources from afar. Today, I set out to create a weather application that allows the user to check their local (to the zip code) weather over packet.

Get the wx.py script at GitHub.

The script is written in Python, and is fairly simple. It only requires one module installation: requests, which is used for retrieving the JSON data from Openweathermap. This script requests the user’s zip code, and returns values such as:

  • Current temperature
  • “Feels like” temperature
  • Barometric pressure
  • Humidity
  • Wind speed, direction, gust speed
  • Textual description of the weather.
  • Rainfall amounts in the past 1, and past 3 hours
  • Snowfall amounts in the past 1 and past 3 hours

All you’ve got to do to run it is request an API key from Openweathermap, install the requests module via pip install requests, create the configuration file which simply consists of your API key, and run it. Running the program on its own for the first time instructions you how to create the configuration file, but it simply contains the following:

[DEFAULT]
api_key=YOURAPIKEY

Replace YOURAPIKEY with whichever key you’re assigned by Openweathermap.

If running it plainly, and not through BPQ, you’ll see a blank screen. This is because of a line in the code that waits for BPQ to send the user’s call, call = input(). This is done to capture the call string that is sent by BPQ by default to applications run through its application interface. Press enter at this blank screen and you’ll be prompted to enter the zip code for which you’d like to request the weather report.

In order to set the application up to run through BPQ, follow this guide. In my configuration, the entry in bpq32.cfg looks like:

APPLICATION 6,WX, C 2 HOST 2 S

When users connect to my node, they will see the “WX” option and are able to access it through that.

Please note that, after cloning the script from GitHub, you must make it executable. In Linux, this is accomplished by running the command chmod +x wx.py. If you don’t do this, it will not run when executed from BPQ.

Monitoring System Stats via BPQ & Python

While setting up my BPQ node, I came across another node that hosted an interesting application: System Info. This application displayed stats such as CPU usage, system uptime, available memory, etc. I think this would be interesting for remote management, so I decided to implement it.

I found this guide, which was very helpful, but it used the Perl language. Not a big fan of Perl (mostly because I don’t know it well), I wanted to rewrite the small script in Python. Here is the script. Before you run it, you’ll need to install the following modules:

  • py-cpuinfo
  • certifi
  • distro
  • psutil
  • uptime

I’ve included a requirements.txt file, so all you’ll have to do is run pip install -r requirements.txt. If you haven’t got pip install, run sudo apt install -y python3-pip.

Follow the guide linked to above, until you get to the scripting portion. Simply replace the line in /etc/inetd.conf with this:

bpqdemo    stream    tcp    nowait    YOURUSER  /usr/local/linbpq/sysinfo.py

make sure you change the path to wherever your linbpq installation is located. It will look like /home/USERNAME/linbpq, so the full path should look like /home/USERNAME/linbpq/sysinf.py. Also, don’t forget to replace YOURUSER with the username used to run linbpq.

The script has been tested on Ubuntu 20.04, but it should run anywhere. Hope you enjoy it! Feel free to submit pull requests if you have any ideas for improvements.

The KD5LPB-7 2m BPQ32 Node

Over the past month, I’ve been at work setting up a 2 meter BQP node here in Aurora, Colorado. I chose BPQ32 as it is full of features, is well supported, and is widely used. The nodes broadcast feature is attractive to me, as I think it would be cool to have an automatically generated list of nodes that are reachable from any one station.

I’m new to this mode and am learning something every day.

The node equipment:

  • Kenwood D710G
  • Intel NUC running linbpq on Ubuntu 20.04
  • 20m/440 mobile antenna

The D710G is a great rig for packet, as it comes with a built-in TNC. The built-in TNC has basic BBS functionality, but nothing compared to what is offered by BPQ.

While the antenna is subpar for a fixed node, it’s the best I can do while living in an apartment. It’s mounted to the balcony rail using an MFJ-1907 balcony mount. This seems to work fairly well, and achieves a 1.2:1 VSWR on the local packet frequency, 145.050.

Packet radio in Colorado is very much alive and well. I only wish more folks broadcast their nodes list so that a more comprehensive list of local nodes could be built.

My hierarchical route is KD5LPB.#NCO.CO.USA.NOAM

I can be mapped in BPQ via MAP kd5lpb-7 node.kd5lpb.com UDP 10093 B; Aurora, CO

WordPress Appliance - Powered by TurnKey Linux