Saturday, May 27, 2023

HamAlert.Org Integration with Pimoroni Galactic Unicorn

 

-----

The Pimoroni Galactic Unicorn is a cool piece of kit that we have had our eye on for a while.  The item is popular and sells out quickly, but we finally got our hands on one.  Summary:  It's awesome!  After the initial "WOW!" factor diminished we went in search of a project for it.

-----

The answer was obvious.  How many times are you sitting comfortably in your home theater room with the family and you miss a Morse Code CQ from one of your ham radio friends?  That's what we thought, so we set out to solve that inconvenient and irritating problem!

-----

The project needs a WiFi connection for the Pimoroni Galactic Unicorn.  No ham radio or RF access to the ham bands are needed.  

Steps:

    - Buy a Pimoroni Galactic Unicorn and set it up to run MicroPython.

    - Create a HamAlert account and set up some triggers for the ham operators of interest.  You will need to select the "telnet" reporting option in the trigger menu.

    - Copy/Paste our MicroPython source code below into the Pimoroni Galactic Unicorn. We used Thonny as our development environment.  Name the program "main.py" so it will autostart at bootup.

    - Place the rig under that big screen TV in the home theater room.  Simply wait for your movie or favorite show to be interrupted letting you know it's time to drop everything and fire up your ham radio for a QSO!

-----

Here is a live demo of the setup.  What happens is:

    - K5JM, who is in my HamAlert triggers is spotted calling CQ.

    - The Pimoroni Galactic Unicorn is logged into and monitoring the HamAlert telenet server.

    - We parse the response of the HamAlert telnet server and display the information we want on the LED matrix.

    - Of course, we answer K5JM.  Success!!!  Next we return to the home theater room and await the next interruption.


 -----

Source code below:

 '''
Morse Alert
MAY2023
WhiskeyTangoHotel.Com

This program displays your individual hamalert.org telnet CW triggers
onto the display of the Raspberry PI PicoW based Pimoroni Galactic Unicorn.
Thanks HamAlert.Org by Manuel Kasper (HB9DQM) for their telnet service!

CW CQ info shown is "Callsign", "Frequency", and "WPM", but other options are available.
The text is color coded by band.
Scroll speed and total display variables are adjustable.
Alert 'chirp' is adjustable.

A CW beacon, such as WR5U which transmits about every 30 minutes,
may be a suggested hamalert telnet trigger to avoid timeouts from the
hamalert.org server.

NOTE: Does not work with hamalert.org simulated triggers

'''


#Variable set up:
wifi_ssid = "wifi_ssid"
wifi_password =  "wifi_password"

# hamalert.org login info
username = "telnet_username"
password = "telnet_passord"
telnetaddr = "hamalert.org"  # "telnetaddress.com"
port = 7300    # port as a number, not a string

from galactic import GalacticUnicorn
gu = GalacticUnicorn()

# Set up speaker for a sweeping 'chirp' alert
volume = .5 # range is 0 to 1.  0 = For no sound
start_tone = 500 #500 is a good start  Adjust to suit.
end_tone = 1000  #1000 is a good start.  Adjust to suit.
channels = [gu.synth_channel(i) for i in range(1)]

waitmsg = "HamAlert..."
howbright = 0.1 # value range 0.0 to 1.0
dwelltime = 5 # how many seconds to display Callsign, Freq, WPM
tot_time = 60 # how long (seconds) to cycle this info

utc_offset = -5 # we print to screen (not to LEDs the zulu and local time)

import network
import usocket as socket
import time
import utime

from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN
graphics = PicoGraphics(display=DISPLAY_GALACTIC_UNICORN)

#Define some colours
BLACK = graphics.create_pen(0, 0, 0)
RED =  graphics.create_pen(255, 0, 0)
YELLOW = graphics.create_pen(255, 255, 0)
GREEN = graphics.create_pen(0, 255, 0)
CYAN =  graphics.create_pen(0, 255, 255)
BLUE =  graphics.create_pen(0, 0, 255)
MAGENTA =  graphics.create_pen(255, 0, 255)
WHITE =  graphics.create_pen(200, 200, 200)
GREY =  graphics.create_pen(100, 100, 100)
DRKGRY =  graphics.create_pen(50, 50, 50)
FREQCOLOR = WHITE  # this is the text color that will change per band
waitmsgcolor =  GREY  # number, not a string

# create a PicoGraphics framebuffer to draw into
graphics = PicoGraphics(display=DISPLAY_GALACTIC_UNICORN)
gu.set_brightness(howbright)

#Create a single wlan object and use as a global for all net calls
wlan = network.WLAN(network.STA_IF)

wlan.active(True)
wlan.connect(wifi_ssid, wifi_password)

# Wait for connect success or failure
max_wait = 100
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    wifistat = 'WiFi...' + str(100-max_wait)
    if max_wait == 0:
        wifistat = "WiFi fail"
    print(wifistat)
    width = graphics.measure_text(wifistat, 1)
    startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
    # clear the graphics object
    graphics.set_pen(BLACK)
    graphics.clear()
    # draw the text
    graphics.set_pen(waitmsgcolor)
    graphics.text(wifistat, round(startplace), 2, -1, 0.55);    
    # update the display
    gu.update(graphics)
    time.sleep(.5)
    
if max_wait > 0:
    print("WIFI OK!")
    width = graphics.measure_text('WIFI OK!', 1)
    startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
    # clear the graphics object
    graphics.set_pen(BLACK)
    graphics.clear()
    # draw the text
    graphics.set_pen(waitmsgcolor)
    graphics.text('WIFI OK!', round(startplace), 2, -1, 0.55);    
    # update the display
    gu.update(graphics)    
    time.sleep(5)

# Connect to the telnet server
tn = socket.socket()
addr = socket.getaddrinfo(telnetaddr, port)[0][-1]
tn.connect(addr)

# Log in with the username and password to telnet server
tn.send(username + "\r\n")
tn.send(password + "\r\n")

last_spot = utime.time()  # we track/print time between spots

# Read and process the telnet server response
while True:
    data = tn.recv(1024)
    data = data.decode("utf-8")
    print(data)
    
    # Center "waitmsg"
    width = graphics.measure_text(waitmsg, 1)
    startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
    # clear the graphics object
    graphics.set_pen(BLACK)
    graphics.clear()
    # draw the text
    graphics.set_pen(waitmsgcolor)
    graphics.text(waitmsg, round(startplace), 2, -1, 0.55);    
    # update the display
    gu.update(graphics)

    # Play 'connected' Chirp alert
    for tone in range(start_tone, end_tone):
        channels[0].play_tone(tone, volume)
        gu.play_synth()
        time.sleep(.0009)
    gu.stop_playing()
    
    if "DX de" in data:
        #print(data)
                
        # Split the string into a list of values
        data_list = data.split()
        
        # Assign each value to separate variables
        dx = data_list[0]
        de = data_list[1]
        spotter = data_list[2]
        freq = data_list[3]
        spotted = data_list[4]
        db = data_list[5]
        wpm = data_list[6]
        zulu = data_list[7]

        # Print the values of the variables
        print("HamAlert returns:")
        #print('dx:', dx )
        #print('de:', de)
        #print('spotter:', spotter)
        print('Freq:', freq)
        print('Spotted:', spotted)
        #print('db:', db)
        print('WPM:', wpm)
        #print('zulu:', zulu)
        
        #Convert zulu to local
        hours = int(zulu[:2])
        minutes = int(zulu[2:4])     
        local_hours = hours + utc_offset
        local_minutes = minutes
        # If negative hour fix wraparound
        if local_hours < 0:
            local_hours += 24
        # Format to local timeg
        local_time = "{:02d}:{:02d}".format(local_hours, local_minutes)
        print('Local time:', local_time)
        #print('Minutes last spot:', int( (utime.time() - last_spot)/ 60)  )
        last_spot = utime.time()  # we track/print time between spots
        print('-------------------')
        print
        
        FREQCOLOR = WHITE
        band = float(freq)
        if 24890 <= band <= 24990:
            FREQCOLOR = CYAN # text color for 12 meter spots
        if 18068 <= band <= 18168:
            FREQCOLOR = BLUE # text color for 17 meter spots
        if 14000 <= band <= 14350:
            FREQCOLOR = MAGENTA  # text color for 20 meter spots          
        if 10100 <= band <= 10150:
            FREQCOLOR = RED # text color for 30 meter spots
        if 7000 <= band <= 7300:
            FREQCOLOR = YELLOW  # text color for 40 meter spots                  
        if 3500 <= band <= 4000:
            FREQCOLOR = GREEN # text color for 80 meter spots
        
        for x in range(0, int((tot_time/(dwelltime*3)))):
            # Center position of the text
            width = graphics.measure_text(spotted, 1)
            startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
            # clear the graphics object
            graphics.set_pen(BLACK)
            graphics.clear()
            # draw the text
            graphics.set_pen(FREQCOLOR)
            graphics.text(spotted, round(startplace), 2, -1, 0.55);
            # update the display
            gu.update(graphics)
            time.sleep(dwelltime)
            
            # Center position of the text
            width = graphics.measure_text(freq, 1)
            startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
            # clear the graphics object
            graphics.set_pen(BLACK)
            graphics.clear()
            # draw the text
            graphics.set_pen(FREQCOLOR)
            graphics.text(freq, round(startplace), 2, -1, 0.55);
            # update the display
            gu.update(graphics)            
            time.sleep(dwelltime)
            
            # Center position of the text
            width = graphics.measure_text(wpm, 1)
            startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
            # clear the graphics object
            graphics.set_pen(BLACK)
            graphics.clear()
            # draw the text
            graphics.set_pen(FREQCOLOR)
            graphics.text(wpm, round(startplace), 2, -1, 0.55);
            # update the display
            gu.update(graphics)
            time.sleep(dwelltime)
                
        #print(waitmsg)
       
        # Below if you want time of last spot shown.  This can help ID a telnet timeout.
        # See our reason for having a CW beacon comment at the very top.
        waitmsg = 'Last ' + local_time
        
        # This if you want the your pre-defined waitmsg after a spot
        #width = graphics.measure_text(waitmsg, 1)
        
        width = graphics.measure_text(waitmsg, 1)
        startplace = int(float(GalacticUnicorn.WIDTH) - width) / 2
        # clear the graphics object
        graphics.set_pen(BLACK)
        graphics.clear()
        # draw the text
        graphics.set_pen(waitmsgcolor)
        graphics.text(waitmsg, round(startplace), 2, -1, 0.55);
        # update the display
        gu.update(graphics)
-----


Monday, May 15, 2023

Low Cost Temperature Data Logger

 -----

We saw this Elitech RC-5+ Temperature Data Logger at Amazon for $19.95 USD.  The specs looked great and for the price we just had to give a try.  We were not disappointed.  

The ElitechLog software is a free download and provides easy to view graphs and stats on the gathered data.   Just to do 'something' with the unit we placed it in our refrigerator overnight in various locations.  Below are the overnight averages:

 ----- 

Here is a temperature graph taken over a 20 hour time period and a data point every 60 seconds.

 -----

More equally useless projects come to mind and we will post the them below as they happen.  In the end this is a great value and a useful tool.

-----

After getting a new LG refrigerator we reran the experiment (NOV2023):

 -----