#Software #POTA #Hobby Of Hobbies

Silly Website Widget

I had a weird thought while I was lying awake waiting to let the dog back in at about 03:00. That thought was an idea for a web widget that I couldn’t implement in Wordpress (well, I couldn’t) so I burned down my entire site for this. It’s silly.

What Is It?

It was a really simple thought: wouldn’t it be neat to put a little widget on my website to show when I’m on the air for a POTA activation? Just something that says KC8JC is on the air on this mode at this frequency. Go check him out! It didn’t seem like it would be too hard given that the POTA.app site has an API (unsupported) that has all of that data. Why not grab it and go?

My first thought was to do something in PHP but then, over a cup of coffee, I decided that I’d just do it as a javascript function and let it run when the page loads. Why get fancy or weird about it?

So What Does It Do?

It’s really simple. This thing just hits the POTA.app API to see if anything comes back. My site is pretty low traffic, so I’m not thinking that this will be any more burden than I put on it when I go to a park and sit there seeing if RBN or POTA is hearing me when I’ve gone 20 minutes without a contact. I try not to get desperate, but I imagine I’m not alone there. If there’s a spot for my call, it shows the mode and frequency. Really, really, brutally simple.

But what if I’m on the air and NOT doing POTA? What then?

Well, why not have it hit PSKReporter and see what comes back? Why not? So I did that too.

Where’s The Code?

This isn’t particularly elegant and I’m putting it here more so I remember what I did than anything else. Also, it’s important to note that I’m using this with Jekyll. If you choose to scoop this up and run with it, put it in the includes folder so that it gets packaged up correctly. I’m still REALLY new to Jekyll so it took me a bit to figure that out. I gotta say, for generating a static site, it’s really neat even if I’m not a ruby guy and I have to keep looking up how to keep stuff up to date so that things work on multiple platforms. Again, that’s a Me Problem.

This isn’t going on Codeberg, I don’t think, but if you promise not to laugh, here it is:

<div id="pota-widget" style="display:none;">
  <div class="widget widget--pota">
    <h3 class="widget-head">on the air now</h3>
    <div class="pota-spot">
      <div class="pota-spot__park">
        <span class="pota-spot__ref" id="pota-ref"></span>
        <span class="pota-spot__name" id="pota-name"></span>
      </div>
      <div class="pota-spot__freq">
        <span class="pota-spot__mhz" id="pota-freq"></span>
        <span class="pota-spot__mode" id="pota-mode"></span>
      </div>
      <a class="pota-spot__link" id="pota-link"
         href="#" target="_blank" rel="noopener"></a>
    </div>
  </div>
</div>

<script>
(function () {
  var CALLSIGN = 'KC8JC';
  var POTA_URL = 'https://api.pota.app/spot/activator';
  var PSK_URL  = 'https://retrieve.pskreporter.info/query' +
                 '?senderCallsign=' + CALLSIGN +
                 '&flowStartSeconds=-1800' +
                 '&rronly=1';

  function show(ref, name, freqMhz, mode, linkHref, linkText) {
    document.getElementById('pota-ref').textContent  = ref;
    document.getElementById('pota-name').textContent = name;
    document.getElementById('pota-freq').textContent = freqMhz + ' MHz';
    document.getElementById('pota-mode').textContent = mode;
    var link         = document.getElementById('pota-link');
    link.href        = linkHref;
    link.textContent = linkText;
    document.getElementById('pota-widget').style.display = '';
  }

  function tryPOTA() {
    return fetch(POTA_URL)
      .then(function (r) {
        if (!r.ok) throw new Error('POTA error - probably my fault...');
        return r.json();
      })
      .then(function (spots) {
        var mine = spots.filter(function (s) {
          return s.activator &&
                 s.activator.toUpperCase() === CALLSIGN &&
                 !s.invalid;
        });
        if (mine.length === 0) return false;

        mine.sort(function (a, b) {
          return new Date(b.spotTime) - new Date(a.spotTime);
        });

        var spot    = mine[0];
        var freqKhz = parseFloat(spot.frequency);
        show(
          spot.reference,
          spot.name || '',
          (freqKhz / 1000).toFixed(3),
          spot.mode || '',
          'https://pota.app/#/',
          'view on pota.app →'
        );
        return true;
      });
  }

  function tryPSKReporter() {
    return fetch(PSK_URL)
      .then(function (r) {
        if (!r.ok) throw new Error('PSKReporter error - probably my fault...');
        return r.text();
      })
      .then(function (xml) {
        var doc     = new DOMParser().parseFromString(xml, 'text/xml');
        var reports = Array.from(doc.querySelectorAll('receptionReport'));
        if (reports.length === 0) return false;

        // Sort spots with newest first
        reports.sort(function (a, b) {
          return parseInt(b.getAttribute('flowStartSeconds'), 10) -
                 parseInt(a.getAttribute('flowStartSeconds'), 10);
        });

        var newest  = reports[0];
        var freqHz  = parseInt(newest.getAttribute('frequency'), 10);
        var mode    = newest.getAttribute('mode') || '';
        var count   = reports.length;

        show(
          '',
          'heard by ' + count + ' station' + (count !== 1 ? 's' : ''),
          (freqHz / 1000000).toFixed(3),
          mode,
          'https://pskreporter.info/pskmap.html?callsign=' + CALLSIGN,
          'view on pskreporter →'
        );
        return true;
      });
  }

  // Try POTA first; Try PSKReporter if not spotted there.
  // All failures are silent and the widget stays hidden if both come up empty - NICE!.
  tryPOTA()
    .then(function (found) {
      if (!found) return tryPSKReporter();
    })
    .catch(function () {
      tryPSKReporter().catch(function () {});
    });
})();
</script>

That’s all there is to it. If it doesn’t find anything then there’s nothing on the site. If it does, then there should be. So go test it with a callsign that’s on the air when you’re messing around with it. Oh, and change the callsign in the code above or you’ll only see when I’m on the air. I’m not sure that’s what you want. Good luck with it!

Final

Hobby of hobbies indeed! I’m not sure that this was the best reason to burn down my site and start over, but I’m really starting to think critically about the tech I use. Why on earth am I using a database and a bunch of nonsense when my site is really a static reference and series of stories about a dude and his radio? I’m trying to get rid of complexity where I can and this is a pretty great place to start. Besides, it makes hacking together things like this more attractive because there isn’t the overhead of the monolith that is Wordpress.

And hey, maybe static sites are just QRP websites? I didn’t think of it that way until just now.

I hope this was at least amusing and maybe you’ll use it to catch me on an activation some day soon!

72 de KC8JC