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.

WordPress Appliance - Powered by TurnKey Linux