616f4281 |
#!/usr/bin/python3 -u
|
0ad18757 |
# The -u option turns off block buffering of python output. This assures
# that output streams to stdout when output happens.
#
# Module: ft991utility.py
#
# Description: A utility for backing up Yaesu FT991 memory and menu settings,
# and also for restoring memory and menu settings from
# a file. Can be used both interactively or with command line
# arguments. Before running this utility, be sure to copy
# the file 'ft911.py' to the same folder as this utility.
#
# Copyright 2019 by Jeff Owrey, Intravisions.com
# 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 Licensef
# along with this program. If not, see http://www.gnu.org/license.
#
# Revision History
|
616f4281 |
# * v10 24 Nov 2019 by J L Owrey; first release
|
e275d3bf |
# * v11 03 Oct 2020 by J L Owrey; upgraded to Python 3
# * v12 24 Aug 2022 by J L Owrey; fixed error occurring when setting
# the FT991 IF filter width for unsupported modulation modes; added
# command line parameter to set the CAT baud rate; fixed error that
# occurs when the restore menu settings tries to restore the CAT baud
# rate setting
|
0ad18757 |
#
#2345678901234567890123456789012345678901234567890123456789012345678901234567890
|
616f4281 |
# Environment Setup
_WINDOWS_COM_PORT = 'COM5'
_LINUX_COM_PORT = '/dev/ttyUSB0'
|
e275d3bf |
_DEFAULT_FT991_BAUD_RATE = 4800
|
616f4281 |
|
0ad18757 |
import os, sys, serial, time
import ft991 # module should be in same directory as this utility
# Constant definitions
_DEFAULT_MENU_SETTINGS_FILE = 'ft991menu.cfg'
_DEFAULT_MEMORY_SETTINGS_FILE = 'ft991mem.csv'
_MAX_NUMBER_OF_MENU_ITEMS = 154
_MAX_NUMBER_OF_MEMORY_ITEMS = 118
|
cdcdd076 |
_DEBUG = False
|
0ad18757 |
# Global definitions
menuBackupFile = _DEFAULT_MENU_SETTINGS_FILE
memoryBackupFile = _DEFAULT_MEMORY_SETTINGS_FILE
commandLineOption = ''
|
e275d3bf |
baudRate = _DEFAULT_FT991_BAUD_RATE
|
0ad18757 |
# Command processing functions
def doUserCommand():
"""
Description: Provides an interactive user interface where the user can
enter simple text commands at a command line prompt. The
commands that a user can enter are listed in a menu splash
that the user can display anytime with the 'm' command.
Also this function processes commands provided as command
line options. In the case of a command line option this
function operates non-interactively.
Parameters: none
Returns: nothing
"""
# When command line arguments have not been provided,
# use interactive mode and give the user a prompt.
if commandLineOption == '':
|
616f4281 |
cmd = input('>').strip()
|
0ad18757 |
# If command line arguments have been provided, then
# execute the command non-interactively.
else:
cmd = commandLineOption
# Process the user command.
if cmd == '':
return
elif cmd == 'm':
printMenuSplash()
elif cmd == 'bu':
backupMenuSettings()
elif cmd == 'ru':
restoreMenuSettings()
elif cmd == 'bm':
backupMemorySettings()
elif cmd == 'rm':
restoreMemorySettings()
elif cmd == 'p':
passThroughMode()
elif cmd == 'v':
toggleVerboseMode()
elif cmd == 'x':
exit(0)
else:
|
616f4281 |
print("invalid command")
|
0ad18757 |
## end def
def backupMemorySettings():
"""
Description: Backs up all memory settings to a file. The user has the
option of providing a file name or accepting a default
file name.
Parameters: none
Returns: nothing
"""
# Prompt the user for a file name in which to store
# backed up memory settings.
fileName = getFileName(memoryBackupFile)
# Read the memory settings from the FT991...
|
616f4281 |
print('Backing up memory settings...')
|
0ad18757 |
settings = readMemorySettings()
# and write them to the file.
writeToFile(settings, fileName)
|
616f4281 |
print('Memory settings backed up to \'%s\'' % fileName)
|
0ad18757 |
## end def
def restoreMemorySettings():
"""
Description: Restores all memory settings from a file. The user has the
option of providing a file name or accepting a default
file name.
Parameters: none
Returns: nothing
"""
# Prompt the user for a file name from which to retrieve backed up
# memory settings. Also make sure the file exists.
fileName = getFileName(memoryBackupFile)
if not os.path.isfile(fileName):
|
616f4281 |
print('File not found.\n' \
|
0ad18757 |
'Please enter a valid file name. Be sure to correctly ' \
|
616f4281 |
'enter\nthe full path name or relative path name of the file.')
|
0ad18757 |
return
# Read the memory settings from the file...
|
616f4281 |
print('Restoring memory settings...')
|
0ad18757 |
settings = readFromFile(fileName)
# and write them to the FT991.
writeMemorySettings(settings)
|
616f4281 |
print('Memory settings restored from \'%s\'' % fileName)
|
0ad18757 |
## end def
def backupMenuSettings():
"""
Description: Backs up all menu settings to a file. The user has the
option of providing a file name or accepting a default
file name.
Parameters: none
Returns: nothing
"""
# Prompt the user for a file name in which to store
# backed up menu settings.
fileName = getFileName(menuBackupFile)
# Read the menu settings from the FT991...
|
616f4281 |
print('Backing up menu settings...')
|
0ad18757 |
settings = readMenuSettings()
# and write them to the file.
writeToFile(settings, fileName)
|
616f4281 |
print('Menu settings backed up to \'%s\'' % fileName)
|
0ad18757 |
## end def
def restoreMenuSettings():
"""
Description: Restores all menu settings from a file. The user has the
option of providing a file name or accepting a default
file name.
Parameters: none
Returns: nothing
"""
# Prompt the user for a file name from which to retrieve backed up
# menu settings. Also make sure the file exists.
fileName = getFileName(menuBackupFile)
if not os.path.isfile(fileName):
|
616f4281 |
print('File not found.\n' \
|
0ad18757 |
'Please enter a valid file name. Be sure to correctly ' \
|
616f4281 |
'enter\nthe full path name or relative path name of the file.')
|
0ad18757 |
return
# Read the menu settings from the file...
|
616f4281 |
print('Restoring menu settings...')
|
0ad18757 |
settings = readFromFile(fileName)
# and write them to the FT991.
writeMenuSettings(settings)
|
616f4281 |
print('Menu settings restored from \'%s\'' % fileName)
|
0ad18757 |
## end def
def passThroughMode():
"""
Description: An interactive mode whereby the user an enter FT991 CAT
commands directly on the command line. This mode greatly
facilitates development and debugging.
Parameters: none
Returns: nothing
"""
|
616f4281 |
print('Entering passthrough mode. Type \'exit\' to exit mode.')
|
0ad18757 |
while(True):
# Prompt the user to enter an FT991 CAT command, and
# process the command string.
|
616f4281 |
sCommand = input('CAT# ').upper()
|
0ad18757 |
if sCommand == 'EXIT': # exit this utility
break
# If the user fails to end a CAT command with a semi-colon,
# then provide one.
elif sCommand[-1:] != ';':
sCommand += ';'
if sCommand == '': # no command - do nothing
continue
else: # run a user command
ft991.sendSerial(sCommand)
sResult = ft991.receiveSerial();
if sResult != '':
|
616f4281 |
print(sResult)
|
0ad18757 |
## end def
def toggleVerboseMode():
"""
Description: Toggles the verbose mode on or off, depending on the
previous state. Verbose mode causes CAT commands to be
echoed to STDOUT as they are sent to the FT991. If a
CAT command returns a string, that is also echoed.
Parameters: none
Returns: nothing
"""
if ft991.verbose:
ft991.verbose = False
|
616f4281 |
print('Verbose is OFF')
|
0ad18757 |
else:
ft991.verbose = True
|
616f4281 |
print('Verbose is ON')
|
0ad18757 |
## end def
def getFileName(defaultFile):
"""
Description: Prompts the user for a file name.
Parameters: defaultFile - file name to use if the user does not
provide a file name
Returns: the user provided file name if provided, or the default
file name, otherwise.
"""
# If a command backup or restore argument provided, then do not
# query the user for a file name. A file name may be provided
# as an option on the command line.
if commandLineOption != '':
return defaultFile
# Otherwise query the user for a file name
|
616f4281 |
fileName = input('Enter file name or <CR> for default: ')
|
0ad18757 |
if fileName == '':
return defaultFile
else:
return fileName
## end def
def readMemorySettings():
"""
Description: Reads all defined memory settings from the FT991. The
settings are reformatted into a readable, comma-delimited
(csv) file, which can be viewed and modified with a
spreadsheet application such as LibreOffice Calc.
Parameters: none
Returns: a list object containing memory location settings. Each item
in the list represents a row, in comma-delimited form, that
specifies the settings for a single memory location. The first
item in the list are the column headers for remaining rows
contained in the list.
"""
# Define the column headers as the first item in the list.
|
e275d3bf |
lSettings = [ 'Memory Ch,VFO_A,' \
|
cdcdd076 |
'RepeaterShift,Mode,Tag,Encoding,Tone,DCS,' \
'Clarifier,RxClar,TxClar,PreAmp,RfAttn,NB,IFshift,' \
'IFwidthIndex,ContourState,ContourFreq,' \
'APFstate,APFfreq,DNRstate,DNRalgorithm,DNFstate,' \
'NBFstate,NotchState,NotchFreq' ]
|
0ad18757 |
|
91164a30 |
for iLocation in range(1, _MAX_NUMBER_OF_MEMORY_ITEMS):
|
b844f45d |
# If a memory location is empty (has not been programmed or has
# been erased), skip that location and do not create a list entry
# for that location.
if ft991.setMemoryLocation(iLocation) == None:
continue
|
0ad18757 |
# For each memory location get the memory contents. Note that
# several CAT commands are required to get the entire contents
# of a memory location. Specifically, additional commands are
# required to get DCS code and CTCSS tone.
|
91164a30 |
dMem = ft991.getMemory(iLocation)
|
0ad18757 |
tone = ft991.getCTCSS()
dcs = ft991.getDCS()
|
cdcdd076 |
preamp = ft991.getPreamp()
rfattn = ft991.getRfAttn()
nblkr = ft991.getNoiseBlanker()
shift = ft991.getIFshift()
width = ft991.getIFwidth()
lContour = ft991.getContour()
|
e275d3bf |
lAFP = ft991.getAPF()
|
cdcdd076 |
dnrstate = ft991.getDNRstate()
dnralgorithm = ft991.getDNRalgorithm()
dnfstate = ft991.getDNFstate()
narstate = ft991.getNARstate()
notch = ft991.getNotchState()
|
0ad18757 |
# getMemory, above, stores data in a dictionary object. Format
# the data in this object, as well as, the DCS code and CTCSS
# tone into a comma-delimited string.
|
e275d3bf |
sCsvFormat = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,' \
|
cdcdd076 |
'%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % \
(
|
e275d3bf |
dMem['memloc'], dMem['vfoa'], \
|
cdcdd076 |
dMem['rpoffset'], dMem['mode'], dMem['tag'], dMem['encode'], \
|
0ad18757 |
tone, dcs, dMem['clarfreq'], dMem['rxclar'], \
|
cdcdd076 |
dMem['txclar'], preamp, rfattn, nblkr, shift, width, \
|
e275d3bf |
lContour[0], lContour[1], lAFP[0], lAFP[1], \
|
cdcdd076 |
dnrstate, dnralgorithm, dnfstate, narstate, notch[0],
notch[1]
)
|
0ad18757 |
# Add the comma-delimited string to the list object.
|
75d24721 |
lSettings.append(sCsvFormat)
return lSettings
|
0ad18757 |
# end def
|
75d24721 |
def writeMemorySettings(lSettings):
|
0ad18757 |
"""
Description: Writes the supplied memory settings to the FT991.
|
75d24721 |
Parameters: lSettings - a list object containing the memory
|
0ad18757 |
settings in comma delimited format
Returns: nothing
"""
|
75d24721 |
for item in lSettings:
try:
|
cdcdd076 |
# Parse the comma-delimited line and store in a dictionary object.
dItem = ft991.parseCsvData(item)
# The first item in the memory settings list are the
# column headers, so ignore this item. (parseData returns
# None for this item.)
if dItem == None:
continue
|
b844f45d |
# Set the parameters for the memory location.
ft991.setMemory(dItem)
|
796cb306 |
# Set current channel to memory location being set.
ft991.setMemoryLocation(int(dItem['memloc']))
|
75d24721 |
# Set CTCSS tone for memory channel.
|
91164a30 |
ft991.setCTCSS(dItem['tone'])
|
75d24721 |
# Set DCS code for memory channel.
|
91164a30 |
ft991.setDCS(dItem['dcs'])
# Set clarifier mode. Note that
# while the 'MW' and 'MT' commands can be used to turn the Rx
# and Tx clarifiers on, the clarifier states can only be turned
# off by sending the 'RT0' and 'XT0' commands. This situation
# is probably due to a bug in the CAT interface.
ft991.setRxClarifier(dItem['rxclar'])
|
cdcdd076 |
ft991.setTxClarifier(dItem['txclar'])
# Set Rx preamplifier state
ft991.setPreamp(dItem['preamp'])
# Set RF attenuator state
ft991.setRfAttn(dItem['rfattn'])
# Set Noise Blanker state
ft991.setNoiseBlanker(dItem['nblkr'])
|
e275d3bf |
# v12 24 Aug 2022 by J L Owrey; fixed error occurring when setting
# the FT991 IF filter width for unsupported modulation modes
if dItem['mode'] in ['LSB', 'USB', 'CW', 'RTTY-LSB',
'RTTY-USB', 'DATA-LSB', 'DATA-USB']:
# Set IF width index
ft991.setIFwidth(dItem['width'])
# Set IF shift amount
ft991.setIFshift(dItem['shift'])
# Set Contour parameters; also sets audio peak filter (APF)
|
cdcdd076 |
ft991.setContour(dItem['contour'])
|
e275d3bf |
if dItem['mode'] in ['CW']:
# Set audio peak filter (APF)
ft991.setAPF(dItem['apf'])
|
cdcdd076 |
# Set DNR state and algorithm
ft991.setDNRstate(dItem['dnrstate'])
ft991.setDNRalgorithm(dItem['dnralgorithm'])
# Set DNF state
ft991.setDNFstate(dItem['dnfstate'])
# Set NAR state
ft991.setNARstate(dItem['narstate'])
# Set Notch state
ft991.setNotchState((dItem['notchstate'], dItem['notchfreq']))
|
616f4281 |
except Exception as e:
print('Memory settings restore operation failed. Most likely\n' \
|
91164a30 |
'this is due to the backup settings file corrupted or\n' \
|
616f4281 |
'incorrectly formatted. Look for the following error: \n')
print(e)
|
cdcdd076 |
raise
#exit(1)
# end for
|
0ad18757 |
## end def
|
75d24721 |
def readMenuSettings():
"""
Description: Reads all menu settings from the FT991.
Parameters: none
Returns: a list object containing all the menu settings
"""
lSettings = []
# Iterate through all menu items, getting each setting and storing
# the setting in a file.
for inx in range(1, _MAX_NUMBER_OF_MENU_ITEMS):
# Format the read menu item CAT command.
sCommand = 'EX%0.3d;' % inx
# Send the command to the FT991.
sResult = ft991.sendCommand(sCommand)
# Add the menu setting to a list object.
lSettings.append(sResult)
return lSettings
## end def
def writeMenuSettings(lSettings):
"""
Description: Writes supplied menu settings to the FT991.
Parameters: lSettings - a list object containing menu settings
Returns: nothing
"""
for item in lSettings:
# Do not write read-only menu settings as this results
# in the FT-991 returning an error. The only read-only
# setting is the "Radio ID" setting.
if item.find('EX087') > -1:
continue;
|
e275d3bf |
# Do not change the CAT baud rate while restoring menu
# items as this could result in failure to restore menu
# items after item #31. This situation might happen if
# the FT991 CAT baud rate gets changed some time after
# the menu settings have been backed up.
if item.find('EX031') > -1:
continue;
|
75d24721 |
# Send the pre-formatted menu setting to the FT991.
sResult = ft991.sendCommand(item)
if sResult.find('?;') > -1:
|
616f4281 |
print('error restoring menu setting: %s') % item
|
75d24721 |
exit(1)
## end def
|
0ad18757 |
def writeToFile(lSettings, fileName):
"""
Description: Writes supplied settings to the specified file.
Parameters: lSettings - a list object containing the settings
fileName - the name of the output file
Returns: nothing
"""
fout = open(fileName, 'w')
for item in lSettings:
fout.write('%s\n' % item)
fout.close()
## end def
def readFromFile(fileName):
"""
Description: Reads settings from the specified file.
Parameters: fileName - the name of the input file
Returns: a list object containing the settings
"""
lSettings = []
fin = open(fileName, 'r')
for line in fin:
item = line.strip() # remove new line characters
lSettings.append(item)
fin.close()
return lSettings
## end def
def setIOFile(option, commandLineFile):
"""
Description: Provides a connector between the command line and the
interactive interface. Commands included as arguments
on the command line determine default file names, as
well as the command executed by doUserCommand above.
Parameters: option - the command supplied by the command line
argument interpreter getCLarguments.
|
75d24721 |
commandLineFile - the name of the file supplied by the -f
command line argument (if supplied).
|
0ad18757 |
Returns: nothing
"""
global menuBackupFile, memoryBackupFile, commandLineOption
commandLineOption = option
if commandLineFile == '':
return
if (option == 'bu' or option == 'ru'):
menuBackupFile = commandLineFile # set menu backup file name
elif (option == 'bm' or option == 'rm'):
memoryBackupFile = commandLineFile # set memory backup file name
## end def
def printMenuSplash():
"""
Description: Prints an menu of available commands for use in
interactive mode.
Parameters: none
Returns: nothing
"""
splash = \
"""
|
ff1ca398 |
Enter a command from the list below:
|
0ad18757 |
m - show this menu
bm - backup memory to file
rm - restore memory from file
bu - backup menu to file
ru - restore menu from file
|
f7f21820 |
p - enter pass through mode
|
0ad18757 |
v - toggle verbose mode
x - exit this program
"""
|
616f4281 |
print(splash)
|
0ad18757 |
## end def
def getCLarguments():
|
75d24721 |
""" Description: Gets command line arguments and configures this program
|
0ad18757 |
to run accordingly. See the variable 'usage', below,
for possible arguments that may be used on the command
line.
Parameters: none
Returns: nothing
"""
|
e275d3bf |
global baudRate
|
0ad18757 |
index = 1
fileName = ''
backupOption = ''
# Define a splash to help the user enter command line arguments.
|
e275d3bf |
usage = "Usage: %s [-v] [OPTION] [-f file] [-u baud rate]\n" \
|
0ad18757 |
" -b: backup memory\n" \
" -r: restore memory\n" \
" -m: backup menu\n" \
" -s: restore menu\n" \
" -f: backup/restore file name\n" \
" -v: verbose mode\n" \
|
e275d3bf |
" -u: baud rate\n" \
|
0ad18757 |
% sys.argv[0].split('/')[-1]
# Process all command line arguments until done. Note that the last
# dash b, m, r, or s argument encounterd on the command line will be
# the one actually execute; any previous instances will be ignored.
while index < len(sys.argv):
if sys.argv[index] == '-f': # Backup file provided.
# Get the backup file name.
if len(sys.argv) < index + 2:
|
616f4281 |
print("-f option requires file name")
|
0ad18757 |
exit(1);
fileName = sys.argv[index + 1]
index += 1
elif sys.argv[index] == '-b': # backup memory
backupOption = 'bm'
elif sys.argv[index] == '-r': # restore memory
backupOption = 'rm'
elif sys.argv[index] == '-m': # backup menu
backupOption = 'bu'
elif sys.argv[index] == '-s': # restore menu
backupOption = 'ru'
elif sys.argv[index] == '-v': # set verbose mode 'ON'
ft991.verbose = True
elif sys.argv[index] == '-d': # set debug mode 'ON'
ft991.debug = True
|
e275d3bf |
elif sys.argv[index] == '-u': # set the baud rate
# v12 24 Aug 2022 by J L Owrey; added command line parameter
# for setting the CAT baud rate
# Get the backup file name.
baudRate = sys.argv[index + 1]
if not baudRate in ['4800', '9600', '19200', '38400']:
print("invalid baud rate")
exit(1)
baudRate = int(baudRate)
index += 1
|
0ad18757 |
else:
|
616f4281 |
print(usage)
|
0ad18757 |
exit(-1)
index += 1
## end while
# Set backup file name and backup command to execute.
setIOFile(backupOption, fileName)
##end def
def main():
"""
Description: Opens a com port connection to the FT991. Processes any
supplied command line options. If no command line options
provides, then enters interactive mode.
Parameters: none
Returns: nothing
"""
getCLarguments() # get command line options
|
616f4281 |
# Determine OS type and set device port accordingly.
#OS_type = sys.platform
#if 'WIN' in OS_type.upper():
if 'WIN' in sys.platform.upper():
port = _WINDOWS_COM_PORT
else:
port = _LINUX_COM_PORT
|
e275d3bf |
ft991.begin(port, baudRate) # open com port session to FT991
|
0ad18757 |
# Process command line options (if any).
if commandLineOption != '':
doUserCommand()
return
# Else enter user interactive mode.
printMenuSplash()
|
b844f45d |
while(True):
|
0ad18757 |
doUserCommand()
## end def
if __name__ == '__main__':
main()
|