Do 2m FM activators still call on 145.500 ?

The RSS feed should still work and if it doesn’t then I need to fix it. It’s low priority as it seems no one uses it much (it broke for several months after the initial SW3 transfer and no one complained )

1 Like

I use the api2.sota.org.uk and wrote my own code in Python so it does what I want.

You can build your own filters for spots and alerts.

Bits it doesn’t handle well are Commas and Nulls in comments fields I generally have them fixed.

As for calling I call 145.500 and QSY. I thank the many chasers who put a spot up. I let chasers know which other bands I will be on (70.45MHz-FM etc…)

Anyhow here is the Python Code I use. I don’t think it imports too well into the reflector.

#!/usr/bin/python3

import urllib.request
import time
import json
import os.path

Windows = 1

cls = lambda: print(‘\n’ *40) # clear the console by printing 25 blank lines

ActivationWindow = 90 # Window for alerts in days
SpotWindow = 4 # Window for Spots in houra
AssociationScope = str.upper(“g”) # Set Association, this can be a part match i.e. G will select G GM GD GJ GW etc.
PollTime = 300 # Number of seconds between polling the SOTA database
DisplayLines = 15 # Number of lines to be displayed in Spots and Alerts the entrys dusplayed are the closest times

ReqSpot = urllib.request.Request(‘https://api2.sota.org.uk/api/spots/100’)
ReqAlert = urllib.request.Request(‘https://api2.sota.org.uk/api/alerts’)
VersionText = “G4VFL test version 26/11/2021 Polling period set to " + str(PollTime) + " Seconds = " + “{:.0f}”.format(PollTime / 60) + " Min”

t = time.time() - (PollTime * 2) # Set the initalise time to display data on start

Load settings from the config file, if the file does not exist create it then use the default settings.

if Windows == 1 :
import os
os.system(“mode con cols=200 lines=” + str((DisplayLines * 2) + 10))
os.system(’ color e1 ') # Yellow background

def DisplayAlert():

Pull the data from the SOTA API

html = urllib.request.urlopen(ReqAlert).read()
DisplayLinesAlert = DisplayLines

# Initslise variables for global use
print("Next " + str(ActivationWindow) + " days of " + AssociationScope + " Alerts. Polled at " + time.strftime("%H:%M",time.gmtime()) + " GMT" ) 
                                   
x = 0
Startstr = 2
Endstr = 0

testtext = html.decode("utf-8-sig").replace('null}','"  "}') # Convert the JSON data to a string variable and replace null with an empty string
     
while x < len(testtext) :

print (testtext) # for testing

    if testtext[x] == '{' and testtext[x - 1] == ',' :      # find the beginning of a JSON data block, these filters are to avoid picking a { in a comment
        Startstr = x + 1
        
    if testtext[x] == '}' and testtext[x - 1] == '"' :      #  find the end of a JSON data block, these filters are to avoid picking a } in a comment
        Endstr = x
        
        # Convert JSON into Python Dict 
        SpotDict = json.loads(testtext[Startstr - 1 : Endstr + 1]  )

        # reset varables
        DisplayText = ""
        Summit = ""
        Summit = SpotDict.get("associationCode") + "/" + SpotDict.get("summitCode") # Concatanate the Summit
        # produce display text this can be altered to suit the user and the screen size
        DisplayText =   SpotDict.get("dateActivated")[0:10].ljust(11," ") \
                      + SpotDict.get("dateActivated")[11:16].ljust(8," ") \
                      + SpotDict.get("activatingCallsign").ljust(12," ") \
                      + SpotDict.get("activatorName")[0:10].ljust(12," ") \
                      + Summit.ljust(12," ") \
                      + SpotDict.get("summitDetails").ljust(50, " ") \
                      + SpotDict.get("frequency")[0:35].ljust(40," ") \
                      + SpotDict.get("comments")[0:30]                      # This is half of the field to save space

--------------------------------------------------------------------

Set the if statement to use to decide which output will be displayed

--------------------------------------------------------------------

if SpotDict.get(“frequency”).find(“145”)) > -1 : # only display a certain band

if SpotDict.get(“activatingCallsign”).find(str.upper(“ea2”)) > -1 : # Callsign full or part match

if Summit.find(str.upper(“G/Sp-005”)) > -1 : # Spot a region or specific summit

if SpotDict.get(“associationCode”)[0:len(AssociationScope)] == str.upper(AssociationScope) : # Display an Association or summit area or specific summit note the slice lenght will need altering

         # To filter out centurys that will cause errors AND Select activations in the next nn days AND Association summits in scope
        if SpotDict.get("dateActivated")[0:2] == "20" \
           and time.mktime(time.strptime(SpotDict.get("dateActivated")[0:10], "%Y-%m-%d" ))< time.time() + ActivationWindow*24*3600 \
           and SpotDict.get("associationCode")[0:len(AssociationScope)] == str.upper(AssociationScope) \
           and DisplayLinesAlert > 0 : 

             print( DisplayText )
             DisplayLinesAlert = DisplayLinesAlert - 1

    x = x + 1

def DisplaySpot():

# grab the api data
html = urllib.request.urlopen(ReqSpot).read()
DisplayLinesSpot = DisplayLines

print(AssociationScope + " Spots, polled at " + time.strftime("%H:%M",time.gmtime()) + " GMT.  Spot window set at " + str(SpotWindow) + " Hours")

x = 0                           # declare global variables
Startstr = 2
Endstr = 0
cuttext = ""

testtext = html.decode("utf-8-sig").replace('null','""')                # convert the api grab to a string , the replace is to remove null and replace it as a string.
testtext = html.decode("utf-8-sig").replace('""','')                    # convert the api grab to a string , the replace is to remove null and replace it as a string.
testtext = html.decode("utf-8-sig").replace('":,"','":"No Comment","')  # convert the api grab to a string , the replace is to remove null and replace it as a string.
     
while x < len(testtext) :
   # print (testtext[x])         # for testing

    # Find the begining and end of the JSON strings
    if testtext[x] == '{' and testtext[x - 1] == ',' :      # Filter to ensure no false { found in comments
        Startstr = x + 1
        
    if testtext[x] == '}' and testtext[x - 1] == '"'  :     # filter to ensure no false } found in comments
        Endstr = x

print(testtext[Startstr - 1 : Endstr + 1 ] ) # For debug

        # Load the JSON string into the dictionary
        SpotDict = json.loads(testtext[Startstr - 1 : Endstr + 1 ]  ) 

        # clear variables                
        DisplayText = ""
        Summit = ""

        # Produce the display strings
        Summit = SpotDict.get("associationCode") + "/" + SpotDict.get("summitCode")
        DisplayText =   SpotDict.get("timeStamp")[0:10].ljust(11," ") \
                      + SpotDict.get("timeStamp")[11:16].ljust(8," ") \
                      + SpotDict.get("activatorCallsign").ljust(12," ") \
                      + SpotDict.get("activatorName")[0:10].ljust(12," ") \
                      + Summit.ljust(12," ") \
                      + SpotDict.get("summitDetails").ljust(50," ") \
                      + SpotDict.get("frequency").ljust(10," ") \
                      + SpotDict.get("mode").ljust(6," ") 

+ SpotDict.get(“comments”)[0:30]

Select a display string

if SpotDict.get(“frequency”).find(“145”)) > -1 : # select the freq

if SpotDict.get(“activatorCallsign”).find(str.upper(“ea2”)) > -1 : # Callsign full or part match

if Summit.find(str.upper(“G/Sp-005”)) > -1 : # Spot a region or specific summit

        if SpotDict.get("associationCode")[0:len(AssociationScope)] == str.upper(AssociationScope) \
           and time.mktime(time.strptime(SpotDict.get("timeStamp")[0:16], "%Y-%m-%dT%H:%M" ))> time.time()- (SpotWindow * 3600) \
            and DisplayLinesSpot > 0 :    # Association, note they are different lengths so the slice may need altered and Only a limited number of lines are displayed to prevent over run on a busy screen

if True : # print everything

            print( DisplayText)
            DisplayLinesSpot = DisplayLinesSpot - 1

    x = x + 1

while True:

if time.time() - t > PollTime :           # once ever couple of minute run the process


    try:
        urllib.request.urlopen(ReqSpot)

    except urllib.error.URLError  :
        print("Check Internet Connetion  " + time.strftime("%H:%M",time.gmtime()))
        

    else:

        cls()
        DisplaySpot()
        print()
        DisplayAlert()
        print("\n" + VersionText)

    finally:

        t = time.time()

time.sleep(PollTime/10)                      # to reduce the load on the CPU

Almost always start with a CQ on 145.500.

The exception is if I’m working in the hills with a guided group and I’ve got limited time. I occasionally just have a few folk lined up and quickly qualify, but if others find me I do work them too. This is very rare and forced on me by time constraints.

Too many aspects of code syntax being interpreted as formatting instructions, particularly the leading space (which is syntactically important in Python). It might work if you put it inside a “</> Pre-formatted text” block…

Hi Gerald @MW0WML,

The aim of the game is obviously to qualify the summit but also work all the great supportive chasers that call into our SOTA stations (and any other stations that call in as well). Been quite active over the past few months, I find checking a frequency is available first before calling on S20 works well. For example: I tend to ask if freq 145.475 is free while I’m typing up my spot. Once I hear nothing, I QSY back to S20 and put a CQ SOTA call out explaining what summit I’m on and where to QSY. Then I again ask if the freq is free (put my spot on if it is), and call CQ SOTA, hoping for a nice pile-up :blush: that seems to work perfectly and once I’ve worked everyone, I put a couple of final calls out.

73, GW4BML. Ben

4 Likes

Many thanks for the tip on formatting,

If anyone really wants the code here is a 2nd go. Like all amateur code it is “as is”. I haven’t seen any guidence on what would be acceptable polling of the SOTA database but I think 5 min is not excessive.

I run it on a PC and a Raspberry PI with some minor differneces because of the way the consoles work.

I have a variant that works with WOTA using the RSS feeds.

Sorry nothing for HEMAs, Screen scraping is too much hassel.

73 de Andrew G4VFL

#!/usr/bin/python3


import urllib.request
import time
import json
import os.path


Windows = 1



cls = lambda: print('\n' *40)       # clear the console by printing 25 blank lines

ActivationWindow = 90               # Window for alerts in days
SpotWindow       = 4                # Window for Spots in houra
AssociationScope = str.upper("g")   # Set Association, this can be a part match i.e. G will select G GM GD GJ GW etc.
PollTime         = 300              # Number of seconds between polling the SOTA database
DisplayLines     = 15               # Number of lines to be displayed in Spots and Alerts the entrys dusplayed are the closest times

ReqSpot = urllib.request.Request('https://api2.sota.org.uk/api/spots/100')
ReqAlert = urllib.request.Request('https://api2.sota.org.uk/api/alerts')
VersionText = "G4VFL test version 26/11/2021      Polling period set to "  + str(PollTime) + " Seconds = " + "{:.0f}".format(PollTime / 60)  + " Min"

t = time.time() - (PollTime * 2)    # Set the initalise time to display data on start    


# Load settings from the config file, if the file does not exist create it then use the default settings.


if Windows == 1 :
    import os
    os.system("mode con cols=200 lines=" + str((DisplayLines * 2) + 10))
    os.system(' color e1 ')         # Yellow background 












def DisplayAlert():

# Pull the data from the SOTA API
    html = urllib.request.urlopen(ReqAlert).read()
    DisplayLinesAlert = DisplayLines

    # Initslise variables for global use
    print("Next " + str(ActivationWindow) + " days of " + AssociationScope + " Alerts. Polled at " + time.strftime("%H:%M",time.gmtime()) + " GMT" ) 
                                       
    x = 0
    Startstr = 2
    Endstr = 0

    testtext = html.decode("utf-8-sig").replace('null}','"  "}') # Convert the JSON data to a string variable and replace null with an empty string
         
    while x < len(testtext) :
#           print (testtext[x])   # for testing
        if testtext[x] == '{' and testtext[x - 1] == ',' :      # find the beginning of a JSON data block, these filters are to avoid picking a { in a comment
            Startstr = x + 1
            
        if testtext[x] == '}' and testtext[x - 1] == '"' :      #  find the end of a JSON data block, these filters are to avoid picking a } in a comment
            Endstr = x
            
            # Convert JSON into Python Dict 
            SpotDict = json.loads(testtext[Startstr - 1 : Endstr + 1]  )

            # reset varables
            DisplayText = ""
            Summit = ""
            Summit = SpotDict.get("associationCode") + "/" + SpotDict.get("summitCode") # Concatanate the Summit
            # produce display text this can be altered to suit the user and the screen size
            DisplayText =   SpotDict.get("dateActivated")[0:10].ljust(11," ") \
                          + SpotDict.get("dateActivated")[11:16].ljust(8," ") \
                          + SpotDict.get("activatingCallsign").ljust(12," ") \
                          + SpotDict.get("activatorName")[0:10].ljust(12," ") \
                          + Summit.ljust(12," ") \
                          + SpotDict.get("summitDetails").ljust(50, " ") \
                          + SpotDict.get("frequency")[0:35].ljust(40," ") \
                          + SpotDict.get("comments")[0:30]                      # This is half of the field to save space

            
# --------------------------------------------------------------------
# Set the if statement to use to decide which output will be displayed
# --------------------------------------------------------------------
#            if SpotDict.get("frequency").find("145")) > -1 : # only display a certain band
#            if SpotDict.get("activatingCallsign").find(str.upper("ea2")) > -1 :  # Callsign full or part match
#            if Summit.find(str.upper("G/Sp-005")) > -1 :  # Spot a region or specific summit 
#            if SpotDict.get("associationCode")[0:len(AssociationScope)] == str.upper(AssociationScope) : # Display an Association or summit area or specific summit note the slice lenght will need altering

             # To filter out centurys that will cause errors AND Select activations in the next nn days AND Association summits in scope
            if SpotDict.get("dateActivated")[0:2] == "20" \
               and time.mktime(time.strptime(SpotDict.get("dateActivated")[0:10], "%Y-%m-%d" ))< time.time() + ActivationWindow*24*3600 \
               and SpotDict.get("associationCode")[0:len(AssociationScope)] == str.upper(AssociationScope) \
               and DisplayLinesAlert > 0 : 

                 print( DisplayText )
                 DisplayLinesAlert = DisplayLinesAlert - 1

        x = x + 1


def DisplaySpot():
    
    # grab the api data
    html = urllib.request.urlopen(ReqSpot).read()
    DisplayLinesSpot = DisplayLines

    print(AssociationScope + " Spots, polled at " + time.strftime("%H:%M",time.gmtime()) + " GMT.  Spot window set at " + str(SpotWindow) + " Hours")

    x = 0                           # declare global variables
    Startstr = 2
    Endstr = 0
    cuttext = ""

    testtext = html.decode("utf-8-sig").replace('null','""')                # convert the api grab to a string , the replace is to remove null and replace it as a string.
    testtext = html.decode("utf-8-sig").replace('""','')                    # convert the api grab to a string , the replace is to remove null and replace it as a string.
    testtext = html.decode("utf-8-sig").replace('":,"','":"No Comment","')  # convert the api grab to a string , the replace is to remove null and replace it as a string.
         
    while x < len(testtext) :
       # print (testtext[x])         # for testing

        # Find the begining and end of the JSON strings
        if testtext[x] == '{' and testtext[x - 1] == ',' :      # Filter to ensure no false { found in comments
            Startstr = x + 1
            
        if testtext[x] == '}' and testtext[x - 1] == '"'  :     # filter to ensure no false } found in comments
            Endstr = x

   #         print(testtext[Startstr - 1 : Endstr + 1 ] )  # For debug

            # Load the JSON string into the dictionary
            SpotDict = json.loads(testtext[Startstr - 1 : Endstr + 1 ]  ) 

            # clear variables                
            DisplayText = ""
            Summit = ""

            # Produce the display strings
            Summit = SpotDict.get("associationCode") + "/" + SpotDict.get("summitCode")
            DisplayText =   SpotDict.get("timeStamp")[0:10].ljust(11," ") \
                          + SpotDict.get("timeStamp")[11:16].ljust(8," ") \
                          + SpotDict.get("activatorCallsign").ljust(12," ") \
                          + SpotDict.get("activatorName")[0:10].ljust(12," ") \
                          + Summit.ljust(12," ") \
                          + SpotDict.get("summitDetails").ljust(50," ") \
                          + SpotDict.get("frequency").ljust(10," ") \
                          + SpotDict.get("mode").ljust(6," ") 
#                          + SpotDict.get("comments")[0:30]                  
# Select a display string               
#            if SpotDict.get("frequency").find("145")) > -1 :  # select the freq
#            if SpotDict.get("activatorCallsign").find(str.upper("ea2")) > -1 :  # Callsign full or part match
#            if Summit.find(str.upper("G/Sp-005")) > -1 :  # Spot a region or specific summit 
            if SpotDict.get("associationCode")[0:len(AssociationScope)] == str.upper(AssociationScope) \
               and time.mktime(time.strptime(SpotDict.get("timeStamp")[0:16], "%Y-%m-%dT%H:%M" ))> time.time()- (SpotWindow * 3600) \
                and DisplayLinesSpot > 0 :    # Association, note they are different lengths so the slice may need altered and Only a limited number of lines are displayed to prevent over run on a busy screen
#            if True :                   # print everything
                print( DisplayText)
                DisplayLinesSpot = DisplayLinesSpot - 1

        x = x + 1


while True:

    if time.time() - t > PollTime :           # once ever couple of minute run the process


        try:
            urllib.request.urlopen(ReqSpot)

        except urllib.error.URLError  :
            print("Check Internet Connetion  " + time.strftime("%H:%M",time.gmtime()))
            

        else:

            cls()
            DisplaySpot()
            print()
            DisplayAlert()
            print("\n" + VersionText)

        finally:

            t = time.time()

    time.sleep(PollTime/10)                      # to reduce the load on the CPU


1 Like

Bearing in mind I’m new and started in winter, I like 2m for SOTA as the kit is compact, ideal in the cold & rain as it’s quickly set up & packed away. When the weather is more reliable I’ll get a dipole up and may even send some baffling, dyslexic CW before autumn.

For me, SOTA operating is 145.500MHz for CQ then move to work a clear channel. Usually get followed so calling QRZ leads to a nice clutch of contacts. If things dry up, back to CQ on 145.500MHz and then try to get back on the same working channel. Doesn’t always work. Love S2S calls. Happy to give up a channel if it gives a contact for another SOTA player. Always amazed at the range possible on a good day.

Usually a kind chaser chalks up a spot for me on SOTAwatch. Oddly I’ve never carried a mobile phone in the hills yet a radio seems ok!?!?

3 Likes

I think it depends very much where you are operating. In your region within sight so to speak of the Lake District and the South Pennines not to mention Snowdon you will be able to operate as you describe. In other places it is harder as I re-discovered on G/DC-001 last Friday. Only HF saved the day.:slight_smile:
But it’s all part of the fun!

1 Like

It was the Program I was using that stopped working for some reason - it appeared to be trying to call ‘home’ and failing to do so and then not loading the SOTA RSS Feed - I did try removing and re-installing but that did not help - I don’t recall it’s name.

If anyone is using an RSS Reader that works I wouldn’t mind trying another one, but I was not impressed with the ones I found from a search.

1 Like

Stewart, did you try the built-in Sotawatch filtering?

I often keep a tab open with a filter set for just G* and EI summits. Go into settings on sotawatch (under your account - top right). There you can turn on the audible warnings and set the filtering (alerts too, as well as spots).

The filtering may also be controlled from the little settings area that can be made visible at the top of the spots/alerts table. This doesn’t control the sounds though.

Mostly though I listen for the alerts coming through Ham Alert on my phone. My colleagues in Zoom meetings are also very familiar with the Morse for SOTA by now!

3 Likes

Ok, the first sentence still stands true. However, when activating GM/CS-008 on 2m FM last week, I couldn’t get a word in on 145.500MHz due to the level of activity, nets starting etc, so I went to 145.425, spotted this frequency and started calling immediately. The Chasers found me, as did a few others. So, all good.

From a listening (or Chasing) point of view, if I have 2m FM on at home (rare) or whilst mobile (daily), I’ll always have the radio scanning constantly through memory - simplex and local repeater channels.

Isn’t that the simplest answer to the original question?

1 Like

That really does not work here it stops scanning because of noise etc too often - I often have two bands of noise that randomly move around the 2m band.

1 Like

Yes, thats a fair point! Not an issue for me in rural Aberdeenshire.

In some regions of the country, you absolutely should call on the calling frequency to have any hope of reaching anyone, because you are simply not going to attract a chain of callers. You will need to go back to the calling frequency between every QSO and put three or four CQ calls out or more over a one hour period in order to get anyone because the amateur radio population on 2m is so thin on the ground. If you do get a pile up, then lucky you, you won’t need to go back to the calling frequency. If a spot goes out, it should spot the frequency you are on, however, alerts on the other hand should show just the calling frequency because you don’t know what frequencies will be available until you arrive at the summit.

1 Like