#!/usr/bin/python2
# SA818/HotSpotRadio programmer by W0JRL 2021/02/05.
#Version 1.03
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
#Created by modifying
# A Simple SerialProgrammer for the 818 VHF/UHF Modules
# by w0anm which was created from examples on the web
# This code was originally,
# $Id: 818-prog 12 2014-12-27 18:27:47Z w0anm $
# Script Start
import time
import serial
import sys
#constants
flag = '0'
if len(sys.argv) > 1:
flag = str(sys.argv[1])
tx_ctcss = '';
rx_ctcss = '';
tx_dcs = '';
rx_dcs = '';
CTCSS_Reverse_Burst = '';
prog_ver = '1.03'
#CTCSS tone to code dictionary
codelookup = {
"0": "0000",
"67.0": "0001",
"71.0": "0002",
"74.4": "0003",
"77.0": "0004",
"79.7": "0005",
"82.5": "0006",
"85.4": "0007",
"88.5": "0008",
"91.5": "0009",
"94.8": "0010",
"97.4": "0011",
"100.0": "0012",
"103.5": "0013",
"107.2": "0014",
"110.9": "0015",
"114.8": "0016",
"118.8": "0017",
"123.0": "0018",
"127.3": "0019",
"131.8": "0020",
"136.5": "0021",
"141.3": "0022",
"146.2": "0023",
"151.4": "0024",
"156.7": "0025",
"162.2": "0026",
"167.9": "0027",
"173.8": "0028",
"179.9": "0029",
"186.2": "0030",
"192.8": "0031",
"203.5": "0032",
"210.7": "0033",
"218.1": "0034",
"225.7": "0035",
"233.6": "0036",
"241.8": "0037",
"250.3": "0038"
}
# DCS codes.
dcs_codes = ["023", "025", "026", "031", "032", "036", "043", "047", "051", "053",
"054", "065", "071", "072", "073", "074", "114", "115", "116", "122",
"125", "131", "132", "134", "143", "145", "152", "155", "156", "162",
"165", "172", "174", "205", "212", "223", "225", "226", "243", "244",
"245", "246", "251", "252", "255", "261", "263", "265", "266", "271",
"274", "306", "311", "315", "325", "331", "332", "343", "346", "351",
"356", "364", "365", "371", "411", "412", "413", "423", "431", "432",
"445", "446", "452", "454", "455", "462", "464", "465", "466", "503",
"506", "516", "523", "526", "532", "546", "565", "606", "612", "624",
"627", "631", "632", "654", "662", "664", "703", "712", "723", "731",
"732", "734", "743", "754"]
# configure the serial connections (the parameters differs on the device
# you are connecting to). ttyUSB0 is used for HotSpotRadio PiHat and SA818
try:
ser = serial.Serial(
port='/dev/ttyUSB0',
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS
)
except:
ser = serial.Serial(
port='/dev/ttyAMA0',
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS
)
# Splash screen
print ('------------------------------------------------------')
print ('\r\n')
print ('SA818-prog, Version ' + prog_ver + '\r\n')
print ('Programming HotSpotRadio PiHat / SA818(U/V) Module \r\n')
print ('Programming Device name:')
print (' ' + ser.portstr) # show which port was really used
print ('')
print ('------------------------------------------------------')
print ('')
# Ask what we are programming (1-HotSpotRadio PiHat 2-SA818 Module)
# If HotSpotRadio PiHat set the channel spacing to wide
while True:
try:
print('What are you programming?')
Device=raw_input("Enter 1 or 2 where HotSpotRadio PiHat=1, SA818 Module=2: ")
if int(Device) not in [1,2]:
raise ValueError()
break;
except ValueError:
print(" Sorry, you must enter either 1 or 2\r\n")
continue;
finally:
if Device == '1':
print('\r\n Programming a HotSpotRadio PiHat')
print(' Wide channel spacing')
print('')
Spacing = "1"
break
# Programming a generic SA818 module so we have to enter a channel spacing
while True:
if Device == '2':
try:
print(' \r\nProgramming a SA818 module')
Spacing=raw_input('Enter Channel Spacing (Narrow=0 or Wide=1): ')
if int(Spacing) not in [0,1]:
raise ValueError()
break;
except ValueError:
print(" Sorry, you must enter either 0 or 1")
continue
finally:
if Spacing == "0":
print('\r\n Programming an SA818 module')
print(' Narrow channel spacing')
print('')
elif Spacing == "1":
print('\r\n Programming an SA818 module')
print(' Wide channel spacing')
print('')
break
break
# Ask which band, UHF or VHF
while True:
try:
Band=raw_input('Enter band (VHF=1, UHF=2): ')
if int(Band) not in [1,2]:
raise ValueError()
break;
except ValueError:
print(" Sorry, you must enter either 1 or 2\r\n")
continue
finally:
if str(Band) == '1':
print(" You chose VHF")
print('')
if str(Band) == '2':
print(" You chose UHF")
print('')
break
# Ask for transmit frequency. Make sure it is in 2m or 70 cm ham band. Correct input format errors
while True:
try:
FreqTx=str(raw_input('Enter transmit frequency in MHz (xxx.xxxx): '))
if Band == '1':
freq=FreqTx.split('.');
if int(freq[0]) < 144 or int(freq[0]) > 148:
raise ValueError();
break;
elif Band == '2':
freq=FreqTx.split(".");
if int(freq[0]) < 420 or int(freq[0]) > 470:
raise ValueError();
break;
except ValueError as err:
if Band == '1':
print("The Tx frequency must be entered as (xxx.xxxx) and must be between 144.0000 and 148.0000.\r\n")
elif Band == '2':
print("The Tx frequency must be entered as (xxx.xxxx) and must be between 420.0000 and 470.0000.\r\n")
continue
finally:
if len(freq) < 2:
freq.append('0000');
elif len(freq[1]) == 1:
freq[1] = freq[1] + '000';
elif len(freq[1]) == 2:
freq[1] = freq[1] + '00';
elif len(freq[1]) == 3:
freq[1] = freq[1] + '0';
FreqTx = freq[0] + "." + freq[1];
break
print(' The transmit frequency is ' + FreqTx + ' MHz\r\n');
# Ask for receive frequency. Make sure it is in 2m or 70 cm ham band. Correct input format errors
while True:
try:
FreqRx=str(raw_input('Enter receive frequency in MHz(xxx.xxxx): '))
if Band == '1':
freq=FreqRx.split(".");
if int(freq[0]) < 144 or int(freq[0]) > 148:
raise ValueError();
break;
elif Band == '2':
freq=FreqRx.split(".");
if int(freq[0]) < 420 or int(freq[0]) > 470:
raise ValueError();
break;
except ValueError as err:
if Band == '1':
print("The Rx frequency must be entered as (xxx.xxxx) and must be between 144.0000 and 148.0000.\r\n")
elif Band == '2':
print("The Rx frequency must be entered as (xxx.xxxx) and must be between 420.0000 and 470.0000.\r\n")
continue
finally:
if len(freq) < 2:
#print(len(freq));
freq.append('0000');
elif len(freq[1]) == 1:
freq[1] = freq[1] + "000";
elif len(freq[1]) == 2:
freq[1] = freq[1] + "00";
elif len(freq[1]) == 3:
freq[1] = freq[1] + "0";
FreqRx = freq[0] + "." + freq[1];
break
print(' The receive frequency is ' + FreqRx + ' MHz\r\n');
#Ask if using subaudible tone
while True:
try:
audtone=str(raw_input('Do you want to use a sub audible tone? (0 = No, 1 = CTCSS, 2= DCS): '))
if int(audtone) not in [0,1,2]:
raise ValueError()
break;
except ValueError:
print(" Sorry, you must enter either 0, 1 or 2\r\n")
continue
finally:
if audtone == '0':
print(" You chose No SubAudible Tone")
print('')
tx_ctcss = "0000" #The string '0000' means no sub-audible tone
rx_ctcss = "0000" #for transmit and/or receive
if audtone == '1':
print(" You chose CTCSS")
print('')
if audtone == '2':
print(" You chose DCS")
print('')
break
while True:
try:
if not audtone == '1':
break
txctcss=str(raw_input('Enter Tx CTCSS Frequency in Hz(xxx.x): '))
if txctcss in codelookup:
print(' You entered ' + txctcss + ' Hz')
else:
raise ValueError()
except ValueError:
print(" Tx CTCSS frequency is incorrect, please re-enter\r\n")
continue
else:
if not audtone == '1':
break
tx_ctcss = str(codelookup.get(txctcss))
print(' The Tx CTCSS code is ' + tx_ctcss)
print('')
break
while True:
try:
if not audtone == '1':
break
rxctcss=str(raw_input('Enter Rx CTCSS Frequency in Hz(xxx.x): '))
if rxctcss in codelookup:
print(' You entered ' + rxctcss + ' Hz')
else:
raise ValueError()
except ValueError:
print(" Rx CTCSS frequency is incorrect, please re-enter\r\n")
continue
else:
if not audtone == '1':
break
rx_ctcss = str(codelookup.get(rxctcss))
print(' The Rx CTCSS code is ' + rx_ctcss)
print('')
break
#A CTCSS frequency has been entered. Ask about reverse burst
while True:
try:
if not audtone == '1':
break;
CTCSS_RB=str(raw_input('Enable Reverse Burst (y/[n]): ')).lower()
if CTCSS_RB == "":
CTCSS_RB="n"
if (CTCSS_RB != 'y') and (CTCSS_RB != 'n'):
raise ValueError();
except ValueError as err:
print(" You must enter y or n\r\n")
continue
else:
if audtone == '1':
if CTCSS_RB == 'y':
print(' Reverse burst is enabled\r\n')
CTCSS_Reverse_Burst = '1'
else:
print(' Reverse burst is not enabled\r\n')
print
TCSS_Reverse_Burst = '0'
break
while True:
try:
if not audtone == '2':
break
ans=str(raw_input('Would you like a list of valid DCS codes? (y/[n]): ')).lower()
if ans == "y":
print('')
print('A valid DCS code is three digits as shown in the following table')
print('followed by an N (normal) or I (inverted)')
print('')
i = 0;
for a in dcs_codes:
i=i+1;
if i == 8:
sys.stdout.write(a + ' ');
print("");
i = 0;
else:
sys.stdout.write(a + ' ');
txdcs=str(raw_input('\r\nEnter Tx DCS Code (xxxx): ')).upper()
if len(txdcs) < 4:
raise ValueError()
if txdcs[0:3] not in dcs_codes:
raise ValueError()
if txdcs[3] not in ["I", "N"]:
raise ValueError()
except ValueError:
print(' Code is incorrect')
print(' Please re-enter as three digits followed by an N or I')
continue
finally:
if not audtone == '2':
break
print(' You entered ' + txdcs + '\r\n')
tx_dcs = txdcs;
break
while True:
try:
if not audtone == '2':
break
rxdcs=str(raw_input('Enter Rx DCS Code (xxxx): ')).upper()
if len(rxdcs) < 4:
raise ValueError()
if rxdcs[0:3] not in dcs_codes:
raise ValueError()
if rxdcs[3] not in ["I", "N"]:
raise ValueError()
except ValueError:
print(' Code is incorrect')
print(' Please re-enter as three digits followed by an N or I')
continue
finally:
if not audtone == '2':
break
print(' You entered ' + rxdcs + '\r\n')
rx_dcs = rxdcs;
break
# Enter squelch value (1-9)
while True:
try:
sq=raw_input('Enter Squelch Value (1-9): ')
if int(sq) < 1 or int(sq) > 9:
raise ValueError();
except ValueError:
print(" Squelch must be an integer between 1 and 9\r\n")
continue
finally:
squelch = str(sq)
break
print(" Squelch is set to " + squelch)
print('')
# Enter volume value (1-8)
while True:
try:
vol=raw_input('Enter Volume (0-8): ')
if int(vol) < 1 or int(vol) > 8:
raise ValueError();
except ValueError:
print(" Volume must be an integer between 1 and 8\r\n")
continue
finally:
Volume = str(vol)
break
print(" Volume is set to " + Volume)
print('')
# Ask about pre-emphasis
while True:
try:
PreEmphasis=str(raw_input('Enable Pre/De-Emphasis (y/[n]): ')).lower()
if PreEmphasis == "":
PreEmphasis="n"
if (PreEmphasis != 'y') and (PreEmphasis != 'n'):
raise ValueError();
except ValueError as err:
print("Must enter Y or N")
continue
finally:
if PreEmphasis == 'y':
print(' Pre/De-emphasis is enabled')
print
else:
print(' Pre/De-emphasis is not enabled')
print
break
# Ask about high pass filter
while True:
try:
HighPass=str(raw_input('Enable High Pass Filter (y/[n]): ')).lower()
if HighPass == "":
HighPass="n"
if HighPass != "y" and HighPass != "n":
raise ValueError();
except ValueError as err:
print("Must enter Y or N")
continue
finally:
if HighPass == 'y':
print(' High pass filter is enabled')
print
else:
print(' High pass filter is not enabled')
print
break
# Ask about low pass filter
while True:
try:
LowPass=str(raw_input('Enable Low Pass Filter (y/[n]): ')).lower()
if LowPass == "":
LowPass="n"
if LowPass != "y" and LowPass != "n":
raise ValueError();
except ValueError as err:
print("Must enter Y or N")
continue
finally:
if LowPass == 'y':
print(' Low pass filter is enabled')
print
else:
print(' Low pass filter is not enabled')
print
break
#
#
#
print ('')
print ('Verify:')
print ('------------------------------------------------------')
print (' Channel Spacing: ' + Spacing + ' ')
print (' Tx Frequency: ' + FreqTx + ' ')
print (' Rx Frequency: ' + FreqRx + ' ')
if audtone == '0':
print (' Tx CTCSS code: 0000')
print (' Rx CTCSS code: 0000')
if audtone == '1':
print (' Tx CTCSS code: ' + tx_ctcss + ' ')
print (' Rx CTCSS code: ' + rx_ctcss + ' ')
print (' CTCSS Reverse Burst: ' + CTCSS_RB + ' ')
if audtone == '2':
print (' Tx DCS code: ' + tx_dcs + ' ')
print (' Rx DCS code: ' + rx_dcs + ' ')
print (' Squelch Value: ' + squelch + ' ')
print (' Volume Value: ' + Volume + ' ')
print (' PreEmphasis Enabled: ' + PreEmphasis + ' ')
print (' High Pass Enabled: ' + HighPass + ' ')
print (' Low Pass Enabled: ' + LowPass + ' ')
print ('------------------------------------------------------')
# Ask if the values are correct and whether to program the unit
Answer=str(raw_input('Is this correct ([y]/n, or a to abort) ?')).lower().strip()
if Answer == "":
Answer = "y";
if Answer == "y":
print('')
if Answer == "a" or Answer == "n":
print('')
print('Press the and on your keyboard to re-run the program')
print('')
exit();
exit
# Read and print firmware version. Invoke this by using SA818-prog -v to start program
if flag == '-v':
print ('Reading firmware version')
ser.write("AT+VERSION" + "\r\n")
time.sleep(1.00)
raw_serial = ser.readline()
response = raw_serial[:-2]
print (" Firmware Version " + response + "\r\n")
# Write the data to the SA818 module
# Set Freq/Group
# Example of the command:
# ser.write("AT+DMOSETGROUP=1,446.0500,446.0500,0020,4,0020\r\n")
try:
print ('Sending Freq Information...')
if audtone == '0' or audtone == '1':
ser.write("AT+DMOSETGROUP=" + Spacing + "," + FreqTx + "," + FreqRx + "," + tx_ctcss + "," + squelch + "," + rx_ctcss + "\r\n")
if audtone == '2':
ser.write("AT+DMOSETGROUP=" + Spacing + "," + FreqTx + "," + FreqRx + "," + tx_dcs + "," + squelch + "," + rx_dcs + "\r\n")
except Exception as e :
Print("Set frequency failed")
if flag == '-d':
print(e)
#evaluate response
try:
raw_serial = ser.readline()
response = raw_serial[:-2]
except Exception as e :
Print("failed")
if flag == '-d':
print(e)
# Bad response --> +DMOSETGROUP:1
if response == '+DMOSETGROUP:1':
print (" Error, invalid information (" + response + "). Check input format..")
print (" Command Sent:")
print (" AT+DMOSETGROUP=1," + FreqTx + "," + FreqRx + "," + tx_ctcss + "," + squelch + "," + rx_ctcss + "\r\n")
exit()
# Set Volume
try:
print ("Setting Volume - " + Volume + " ")
ser.write("AT+DMOSETVOLUME=" + Volume + "\r\n")
time.sleep(1.00)
except Exception as e :
Print("Set volume failed")
if flag == '-d':
print(e)
#evaluate response
try:
raw_serial = ser.readline()
response = raw_serial[:-2]
except Exception as e :
Print("failed")
if flag == '-d':
print(e)
# Bad response --> +DMOSETVOLUME:1
if response == '+DMOSETVOLUME:1':
print (" Error, invalid information (" + response + ")...")
print (" Command Sent:")
print (" AT+DMOSETVOLUME=" + Volume + "\r\n")
exit()
# Set CTCSS Reverse Burst
try:
print ("Setting Reverse Burst")
ser.write("AT+SETTAIL=" + CTCSS_Reverse_Burst + "\r\n")
except Exception as e :
Print("Set CTCSS Reverse Burst failed")
if flag == '-d':
print(e)
#evaluate response
# Can not evaluate because an error in NiceRF code always returns a '0'
try:
raw_serial = ser.readline()
response = raw_serial[:-2]
except Exception as e :
Print("failed")
if flag == '-d':
print(e)
# Set Filters:
# convert filters values, 0 is enable, and 1 is disable
try:
if PreEmphasis == "n":
PreEmpFilter='1'
else:
PreEmpFilter='0'
if HighPass == "n":
HPass='1'
else:
HPass='0'
if LowPass == "n":
LPass='1'
else:
LPass='0'
print ('Setting Filters\r\n')
ser.write("AT+SETFILTER=" + PreEmpFilter + "," + HPass + "," + LPass + "\r\n")
print('Programming Successful\r\n')
time.sleep(1.00)
except Exception as e :
Print("Set Filters failed")
if flag == '-d':
print(e)
#evaluate response
raw_serial = ser.readline()
response = raw_serial[:-2]
# Bad response --> +DMOSETFILTER:1
if response == '+DMOSETFILTER:1':
print (" Error, invalid information (" + response + ")...")
print (" Command Sent:")
print (" AT+SETFILTER=" + PreEmpFilter + "," + HPass + "," + LPass + "\r\n")
exit()