RepeaterBook CSV to CPS CSV converter

OpenGD77CPS
Post Reply
KJ5DFL
Posts: 5
Joined: Mon May 06, 2024 3:46 am

RepeaterBook CSV to CPS CSV converter

Post by KJ5DFL » Tue May 07, 2024 3:57 am

Hey all! I was getting tired of manually typing in all my channels, so I put together a little script to automatically take a Repeaterbook CSV export and convert it into Zones and Channels for the CPS to import. Zones are based on the county that's associated with each channel, so it's mostly location based. This does DMR and Analog repeaters, and copies locations over. It also filters out any repeaters with the status listed as unknown or offline, or any repeaters that aren't analog or DMR.

Go to repeaterbook.com, open the repeater listing for whatever category you want (I usually go by bands, as the script automatically sorts out all the modes) and then Export > CSV. If you want to put together multiple listings, just combine the files, but remember to only have one header. Also check that the downloaded CSV file doesn't have any PHP errors (stuff like <br>) at the top. If it does, just delete those lines.
Usage is pretty simple, albeit only tested on Linux. Just mark it as executable (chmod +x convert.py) and then run it and give the path of your Repeaterbook CSV. My command would be: ./convert.py repeaterbookfull.csv while Windows would probably look like python convert.py repeaterbook.csv
Take the two output files, Channels.csv and Zones.csv, don't rename them, and put them in a directory. In the CPS, press Ctrl-I and it'll ask you to point it at the folder you put the files into. Hit OK and it'll pull them in and give you a summary of what it did to your codeplug. From here, make whatever modifications you like, and upload it normally.
See you on the air :). 73 KJ5DFL

Script (just copy-paste this into a file. i saved mine as convert.py) (please don't bully me lol, i know this is awful):

Code: Select all

#!/usr/bin/python
import sys 

def shorten(string):
    return string.replace("a", "").replace("e", "").replace("i", "").replace("o", "").replace("u", "").replace(" ", "").replace("-", " ")

#print(sys.argv)
chanfile = open(sys.argv[1])
chanfiledata = chanfile.read()
chanfile.close()
chans = chanfiledata.split("\n")
splitchans = []
for i in chans:
    if ((("DMR" not in i) and ("Analog" not in i) and ("analog" not in i)) or ("On-Air" not in i) or ("OPEN" not in i)):
        continue
    toapp = i.split(",")
    toapp[5] = toapp[5].replace('"', "")
    splitchans.append(toapp)
#print(chans[0])
#print(splitchans)
#print(splitchans[1])

outchan = open("Channels.csv", "w")
outzones = open("Zones.csv", "w")
counties = {}
channum = 1
outchan.write("Channel Number,Channel Name,Channel Type,Rx Frequency,Tx Frequency,Bandwidth (kHz),Colour Code,Timeslot,Contact,TG List,DMR ID,TS1_TA_Tx,TS2_TA_Tx ID,RX Tone,TX Tone,Squelch,Power,Rx Only,Zone Skip,All Skip,TOT,VOX,No Beep,No Eco,APRS,Latitude,Longitude\n")
for i in splitchans:
    if "analog" in i[12]:
        # Channel number
        outchan.write(str(channum) + ",")
        channum += 1
        # Channel name.
        channame = str(i[9] + "2-" + shorten(i[5]))
        channame = channame[:14]
        channame = channame.strip()
        outchan.write(channame + ",")
        # Assign the channel to a zone, and check if there's a zone for its county.
        countyname = i[6].replace(" ","")
        countyname = countyname[:14]
        #print(countyname,channame)
        try:
            counties[countyname].append(channame)
        except KeyError:
            counties[countyname] = []

        # Channel type. We've already checked the type if we're running this codeblock.
        outchan.write("Analogue,")
        # Channel frequencies, RX then TX
        outchan.write(str(i[0]) + "," + str(i[1]) + ",")
        # Channel bandwidth. Should always be 25khz.
        outchan.write("25,")
        # Color code, Timeslot, Contact, Talkgroup, DMR ID, and some other nonsense that doesn't matter here.
        outchan.write(",,,,,,,")
        # Tones. We have to make sure an uplink tone is set, and I just don't use downlink tones. 
        if i[3] == "" or i[3] == "CSQ":
            uptone = "None"
        else:
            uptone = i[3]
        outchan.write(f"None,{uptone},")
        # Squelch. "Disabled" just means to use the default setting.
        outchan.write("Disabled,")
        # Power. Master just means to use default.
        outchan.write("Master,")
        # Some other stuff like RX only, Scan skips, TOT, VOX. I turn it all off, but set TOT to 180s.
        outchan.write("No,No,No,180,Off,No,No,None,")
        # Coordinates and end.
        outchan.write(f"{i[7]},{i[8]}\n")
    if "DMR" in i[12]:
        # Channel number
        outchan.write(str(channum) + ",")
        channum += 1
        # Channel name.
        channame = str(i[9] + "2-" + shorten(i[5]))
        channame = channame[:14]
        channame = channame.strip()
        outchan.write(channame + ",")
        # Assign the channel to a zone, and check if there's a zone for its county.
        countyname = i[6].replace(" ","")
        countyname = countyname[:14]
        #print(countyname,channame)
        try:
            counties[countyname].append(channame)
        except KeyError:
            counties[countyname] = []
        # Channel type. We've already checked the type if we're running this codeblock.
        outchan.write("Digital,")
        # Channel frequencies, RX then TX
        outchan.write(str(i[0]) + "," + str(i[1]) + ",")
        # Channel bandwidth. Not set on digital stations.
        outchan.write(",")
        # All the DMR stuff. Color code to TS2_TA_Tx ID. Don't ask me.
        outchan.write(f"{i[13]},1,None,None,None,Off,Off,")
        # Tones, Squelch.
        outchan.write(",,,")
        # Power. Master just means to use default.
        outchan.write("Master,")
        # Some other stuff like RX only, Scan skips, TOT, VOX. I turn it all off, but set TOT to 180s.
        outchan.write("No,No,No,180,Off,No,No,None,")
        # Coordinates and end.
        outchan.write(f"{i[7]},{i[8]}\n")

# Let's set up zones. Unlike the last part that was awful because I'm awful at programming, this
# is awful because I'm bad at programming _and_ the format is awful. Yay.

outzones.write("Zone Name,Channel1,Channel2,Channel3,Channel4,Channel5,Channel6,Channel7,Channel8,Channel9,Channel10,Channel11,Channel12,Channel13,Channel14,Channel15,Channel16,Channel17,Channel18,Channel19,Channel20,Channel21,Channel22,Channel23,Channel24,Channel25,Channel26,Channel27,Channel28,Channel29,Channel30,Channel31,Channel32,Channel33,Channel34,Channel35,Channel36,Channel37,Channel38,Channel39,Channel40,Channel41,Channel42,Channel43,Channel44,Channel45,Channel46,Channel47,Channel48,Channel49,Channel50,Channel51,Channel52,Channel53,Channel54,Channel55,Channel56,Channel57,Channel58,Channel59,Channel60,Channel61,Channel62,Channel63,Channel64,Channel65,Channel66,Channel67,Channel68,Channel69,Channel70,Channel71,Channel72,Channel73,Channel74,Channel75,Channel76,Channel77,Channel78,Channel79,Channel80\n")
for i in counties:
    #print(f"{i} {len(counties[i])}")
    outzones.write(f"{i},")
    #print(i)
    counter = 0
    #print(counties[i])
    for b in counties[i]:
        #print(b)
        outzones.write(f"{b},")
        counter += 1
    for b in range(79 - counter):
        outzones.write(",")
    outzones.write("\n")
Last edited by KJ5DFL on Thu May 16, 2024 1:09 am, edited 1 time in total.

PU2URT
Posts: 7
Joined: Mon Feb 20, 2023 3:09 pm

Re: RepeaterBook CSV to CPS CSV converter

Post by PU2URT » Thu May 09, 2024 5:47 pm

Thank you for that!

User avatar
W0AMT
Posts: 3
Joined: Tue May 14, 2024 2:48 am

Re: RepeaterBook CSV to CPS CSV converter

Post by W0AMT » Tue May 14, 2024 5:16 pm

This looks great. I will try it out today.

KJ5DFL
Posts: 5
Joined: Mon May 06, 2024 3:46 am

Re: RepeaterBook CSV to CPS CSV converter

Post by KJ5DFL » Thu May 16, 2024 1:10 am

Update time! There were some weird issues with zone stuff but I changed some of the code so it should be fine now? If you ever see any empty zones or channels that aren't in any zones please tell me, there's probably something wrong.

Post Reply