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: ft991.py
#
# Description: This module contains tables for translating common transceiver
# settings to FT991 CAT parameters. Low level serial
# communication functions are also handled by this module. In
# particular this module handles:
# 1. Instantiating a serial connection object
# 2. Sending character strings to the serial port
# 3. Reading characters from the serial port
# 4. Parsing and formatting of FT991 commands
# 5. Translating radio operating parameters to CAT
# commands, i.e., CTCSS tones.
#
# 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
# * v10 24 Nov 2019 by J L Owrey; first release
|
616f4281 |
# * v11 03 Oct 2020 by J L owrey; upgraded to Python 3
|
0ad18757 |
#
# This script has been tested with the following
#
|
616f4281 |
# Python 3.8.10 (default, Mar 15 2022, 12:22:08)
# [GCC 9.4.0] on linux
|
0ad18757 |
#2345678901234567890123456789012345678901234567890123456789012345678901234567890
import sys, serial, time
# General constant defines
_INTERFACE_TIMEOUT = 0.1 # seconds
_SERIAL_READ_TIMEOUT = 0.1 # seconds
_SERIAL_READ_BUFFER_LENGTH = 1024 # characters
# Define globals
verbose = False
debug = False
ptrDevice = None
# Define lookup tables for common transceiver settings. Common settings
# such as modulation mode, repeater offset direction, DCS/CTCSS mode,
# CTCSS tone, and DCS code are translated to the repective FT991 parameter
# value.
|
cdcdd076 |
# Binary On / Off state
bState = { 'OFF':'0', 'ON':'1' }
|
0ad18757 |
# Modulation modes
dMode = { 'LSB':'1', 'USB':'2', 'CW':'3', 'FM':'4', 'AM':'5',
'RTTY-LSB':'6', 'CW-R':'7', 'DATA-LSB':'8', 'RTTY-USB':'9',
'DATA-FM':'A', 'FM-N':'B', 'DATA-USB':'C', 'AM-N':'D',
'C4FM':'E' }
# Repeater shift direction
dShift = { 'OFF':'0', '+RPT':'1', '-RPT':'2' }
# Power settings
|
616f4281 |
dPower = { 'LOW':'5', 'MID':'020', 'HIGH':'50', 'MAX':'100' }
|
0ad18757 |
# Repeater signaling modes
dEncode = { 'OFF':'0', 'ENC/DEC':'1', 'TONE ENC':'2',
'DCS ENC/DEC':'4', 'DCS':'3' }
# CTCSS Tones
dTones = { '67.0 Hz':'000', '69.3 Hz':'001', '71.9 Hz':'002',
'74.4 Hz':'003', '77.0 Hz':'004', '79.7 Hz':'005',
'82.5 Hz':'006', '85.4 Hz':'007', '88.5 Hz':'008',
'91.5 Hz':'009', '94.8 Hz':'010', '97.4 Hz':'011',
'100.0 Hz':'012', '103.5 Hz':'013', '107.2 Hz':'014',
'110.9 Hz':'015', '114.8 Hz':'016', '118.8 Hz':'017',
'123.0 Hz':'018', '127.3 Hz':'019', '131.8 Hz':'020',
'136.5 Hz':'021', '141.3 Hz':'022', '146.2 Hz':'023',
'151.4 Hz':'024', '156.7 Hz':'025', '159.8 Hz':'026',
'162.2 Hz':'027', '165.5 Hz':'028', '167.9 Hz':'029',
'171.3 Hz':'030', '173.8 Hz':'031', '177.3 Hz':'032',
'179.9 Hz':'033', '183.5 Hz':'034', '186.2 Hz':'035',
'189.9 Hz':'036', '192.8 Hz':'037', '196.6 Hz':'038',
'199.5 Hz':'039', '203.5 Hz':'040', '206.5 Hz':'041',
'210.7 Hz':'042', '218.1 Hz':'043', '225.7 Hz':'044',
'229.1 Hz':'045', '233.6 Hz':'046', '241.8 Hz':'047',
'250.3 Hz':'048', '254.1 Hz':'049' }
# DCS Tones
dDcs = { '23':'000', '25':'001', '26':'002', '31':'003', '32':'004',
'36':'005', '43':'006', '47':'007', '51':'008', '53':'009',
'54':'010', '65':'011', '71':'012', '72':'013', '73':'014',
'74':'015', '114':'016', '115':'017', '116':'018', '122':'019',
'125':'020', '131':'021', '132':'022', '134':'023', '143':'024',
'145':'025', '152':'026', '155':'027', '156':'028', '162':'029',
'165':'030', '172':'031', '174':'032', '205':'033', '212':'034',
'223':'035', '225':'036', '226':'037', '243':'038', '244':'039',
'245':'040', '246':'041', '251':'042', '252':'043', '255':'044',
'261':'045', '263':'046', '265':'047', '266':'048', '271':'049',
'274':'050', '306':'051', '311':'052', '315':'053', '325':'054',
'331':'055', '332':'056', '343':'057', '346':'058', '351':'059',
'356':'060', '364':'061', '365':'062', '371':'063', '411':'064',
'412':'065', '413':'066', '423':'067', '431':'068', '432':'069',
'445':'070', '446':'071', '452':'072', '454':'073', '455':'074',
'462':'075', '464':'076', '465':'077', '466':'078', '503':'079',
'506':'080', '516':'081', '523':'082', '526':'083', '532':'084',
'546':'085', '565':'086', '606':'087', '612':'088', '624':'089',
'627':'090', '631':'091', '632':'092', '654':'093', '662':'094',
'664':'095', '703':'096', '712':'097', '723':'098', '731':'099',
'732':'100', '734':'101', '743':'102', '754':'103' }
|
cdcdd076 |
# Preamplifier State
dPreamp = { 'IPO':'0', 'AMP 1':'1', 'AMP 2':'2' }
# Narror band filter state
dNAR = { 'WIDE':'0', 'NARROW':'1' }
|
0ad18757 |
|
91164a30 |
#############################################################################
# Define 'get' methods to encapsulate FT991 commands returning status info. #
#############################################################################
|
0ad18757 |
|
75d24721 |
def getMemory(memloc):
|
0ad18757 |
"""
|
75d24721 |
Description: Get memory settings of a specific memory location.
Parameters: memloc - an integer specifying memory location
Returns: a dictionary object containing the memory ettings
|
0ad18757 |
"""
dMem = {}
# Send the get memory settings string to the FT991.
|
75d24721 |
sCmd = 'MT%0.3d;' % (memloc)
|
0ad18757 |
sResult = sendCommand(sCmd)
# Parse memory settings string returned by the FT991
memloc = sResult[2:5]
|
cdcdd076 |
vfoa = sResult[5:14]
|
0ad18757 |
clarfreq = sResult[14:19]
rxclar = sResult[19]
txclar = sResult[20]
mode = sResult[21]
encode = sResult[23]
|
cdcdd076 |
rpoffset = sResult[26]
|
0ad18757 |
tag = sResult[28:40]
# Store the memory settings in a dictionary object.
dMem['memloc'] = str(int(memloc))
|
cdcdd076 |
dMem['vfoa'] = str(float(vfoa) / 10**6)
|
0ad18757 |
dMem['clarfreq'] = str(int(clarfreq))
|
616f4281 |
dMem['rxclar'] = list(bState.keys())[list(bState.values()).index(rxclar)]
dMem['txclar'] = list(bState.keys())[list(bState.values()).index(txclar)]
dMem['mode'] = list(dMode.keys())[list(dMode.values()).index(mode)]
dMem['encode'] = list(dEncode.keys())[list(dEncode.values()).index(encode)]
dMem['rpoffset'] = list(dShift.keys())[list(dShift.values()).index(rpoffset)]
|
0ad18757 |
dMem['tag'] = tag.strip()
return dMem
## end def
def getCTCSS():
"""
|
75d24721 |
Description: Get the CTCSS tone setting for the current memory location.
Parameters: none
Returns: string containing the CTCSS tone
|
0ad18757 |
"""
# Get result CTCSS tone
sResult = sendCommand('CN00;')
tone = sResult[4:7]
|
616f4281 |
return list(dTones.keys())[list(dTones.values()).index(tone)]
|
0ad18757 |
## end def
def getDCS():
"""
|
75d24721 |
Description: Get the DCS code setting for the current memory location.
Parameters: none
Returns: string containing the DCS code
|
0ad18757 |
"""
# Get result of CN01 command
sResult = sendCommand('CN01;')
dcs = sResult[4:7]
|
616f4281 |
return list(dDcs.keys())[list(dDcs.values()).index(dcs)]
|
0ad18757 |
## end def
|
91164a30 |
def getRxClarifier():
"""
Description: Gets the state of the Rx clarifier.
Parameters: none
Returns: string containing the state of the clarifier
"""
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
|
cdcdd076 |
sResult = sendCommand('RT;')
|
91164a30 |
state = sResult[2]
|
616f4281 |
return list(bState.keys())[list(bState.values()).index(state)]
|
91164a30 |
## end def
def getTxClarifier():
"""
Description: Gets the state of the Tx clarifier.
Parameters: none
Returns: string containing the state of the clarifier
"""
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
|
cdcdd076 |
sResult = sendCommand('XT;')
|
91164a30 |
state = sResult[2]
|
616f4281 |
return list(bState.keys())[list(bState.values()).index(state)]
|
cdcdd076 |
## end def
def getPower():
"""
Description: Gets the transmit power level.
Parameters: none
Returns: string containing the power in Watts
"""
sResult = sendCommand('PC;')
return sResult[2:5]
##end def
def getPreamp():
"""
Description: Gets the state of the Rx preamplifier.
Parameters: none
Returns: string containing the state of the preamplifier.
"""
# Get result of PA0 command
sResult = sendCommand('PA0;')
ipo = sResult[3:4]
|
616f4281 |
return list(dPreamp.keys())[list(dPreamp.values()).index(ipo)]
|
cdcdd076 |
## end def
def getRfAttn():
"""
Description: Gets the state of the Rf attenuator.
Parameters: none
Returns: string containing the state of the Rf attenuator.
"""
sResult = sendCommand('RA0;')
if sResult == '?;':
return 'NA'
attn = sResult[3:4]
|
616f4281 |
return list(bState.keys())[list(bState.values()).index(attn)]
|
cdcdd076 |
## end def
def getNoiseBlanker():
"""
Description: Gets the state of the noise blanker.
Parameters: none
Returns: string containing the state of the noise blanker.
"""
sResult = sendCommand('NB0;')
nb = sResult[3:4]
|
616f4281 |
return list(bState.keys())[list(bState.values()).index(nb)]
|
cdcdd076 |
## end def
def getIFshift():
"""
Description: Gets the value in Hz of IF shift.
Parameters: none
Returns: string containing the amount of shift.
"""
if getRfAttn() == 'NA':
return 'NA'
sResult = sendCommand('IS0;')
if sResult == '?;':
return 'NA'
shift = int(sResult[3:8])
return shift
## end def
def getIFwidth():
"""
Description: Gets the index of the width setting. IF width settings
vary according to the modulation type and bandwidth.
Therefore only the index gets saved.
Parameters: none
Returns: string containing the amount index number.
"""
if getRfAttn() == 'NA':
return 'NA'
sResult = sendCommand('SH0;')
width = int(sResult[3:5])
return width
## end def
def getContour():
"""
Description: Gets the four contour parameters.
Parameters: none
Returns: list object containing the four parameters.
"""
if getRfAttn() == 'NA':
return [ 'NA', 'NA', 'NA', 'NA' ]
lContour = []
sResult = sendCommand('CO00;')
lContour.append(int(sResult[4:8]))
sResult = sendCommand('CO01;')
lContour.append(int(sResult[4:8]))
sResult = sendCommand('CO02;')
lContour.append(int(sResult[4:8]))
sResult = sendCommand('CO03;')
lContour.append(int(sResult[4:8]))
return lContour
## end def
def getDNRstate():
"""
Description: Gets digital noise reduction (DNR) state.
Parameters: none
Returns: 0 = OFF or 1 = ON
"""
sResult = sendCommand('NR0;')
if sResult == '?;':
return 'NA'
state = sResult[3:4]
|
616f4281 |
return list(bState.keys())[list(bState.values()).index(state)]
|
cdcdd076 |
## end def
def getDNRalgorithm():
"""
Description: Gets the algorithm used by the DNR processor.
Parameters: none
Returns: a number between 1 and 16 inclusive
"""
sResult = sendCommand('RL0;')
algorithm = int(sResult[3:5])
return algorithm
## end def
def getDNFstate():
"""
Description: Gets digital notch filter (DNF) state.
Parameters: none
Returns: 0 = OFF or 1 = ON
"""
sResult = sendCommand('BC0;')
if sResult == '?;':
return 'NA'
state = sResult[3:4]
|
616f4281 |
return list(bState.keys())[list(bState.values()).index(state)]
|
91164a30 |
## end def
|
cdcdd076 |
def getNARstate():
"""
Description: Gets narrow/wide filter (NAR) state.
Parameters: none
Returns: 0 = Wide or 1 = Narrow
"""
sResult = sendCommand('NA0;')
if sResult == '?;':
return 'NA'
state = sResult[3:4]
|
616f4281 |
return list(dNAR.keys())[list(dNAR.values()).index(state)]
|
cdcdd076 |
## end def
def getNotchState():
"""
Description: Gets the notch filter state and setting.
Parameters: none
Returns: a tuple containing state and frequency
state = 0 (OFF) or 1 (ON)
frequency = number between 1 and 320 (x 10 Hz)
"""
sResult = sendCommand('BP00;')
if sResult == '?;':
return ('NA', 'NA')
state = sResult[6:7]
|
616f4281 |
state = list(bState.keys())[list(bState.values()).index(state)]
|
cdcdd076 |
sResult = sendCommand('BP01;')
freq = int(sResult[4:7])
return (state, freq)
## end def
|
b844f45d |
#############################################################################
|
cdcdd076 |
# Define 'set' methods to encapsulate the various FT991 CAT commands. #
|
b844f45d |
#############################################################################
|
75d24721 |
def setMemory(dMem):
"""
Description: Sends a formatted MT command.
Parameters: dMem - a dictionary objected with the following keys
defined:
memloc - the memory location to be written
|
cdcdd076 |
vfoa - receive frequency of VFO-A in MHz
|
75d24721 |
clarfreq - clarifier frequency and direction
rxclar - receive clarifier state
txclar - transmit clarifier state
mode - the modulation mode
encode - the tone or DCS encoding mode
|
cdcdd076 |
rpoffset - the direction of the repeater shift
|
75d24721 |
tag - a label for the memory location
|
91164a30 |
Returns: nothing
|
75d24721 |
"""
|
91164a30 |
# Format the set memory with tag command (MT).
sCmd = 'MT'
# Validate and append memory location data.
iLocation = int(dMem['memloc'])
if iLocation < 1 or iLocation > 118:
|
75d24721 |
raise Exception('Memory location must be between 1 and ' \
'118, inclusive.')
|
91164a30 |
sCmd += '%0.3d' % iLocation
|
75d24721 |
# Validate and append the vfo-a frequency data.
|
cdcdd076 |
iRxfreq = int(float(dMem['vfoa']) * 1E6) # vfo-a frequency in Hz
|
75d24721 |
if iRxfreq < 0.030E6 or iRxfreq > 450.0E6:
raise Exception('VFO-A frequency must be between 30 kHz and ' \
'450 MHz, inclusive.')
sCmd += '%0.9d' % iRxfreq
# Validate and append the clarifier data.
iClarfreq = int(dMem['clarfreq'])
if abs(iClarfreq) > 9999:
raise Exception('Clarifer frequency must be between -9999 Hz ' \
'and +9999 Hz, inclusive.')
sCmd += '%+0.4d' % iClarfreq
# The following commands will automatically raise an exception if
# incorrect data is supplied. The exception will be a dictionary
# object "key not found" error.
|
cdcdd076 |
sCmd += bState[dMem['rxclar']]
sCmd += bState[dMem['txclar']]
|
75d24721 |
sCmd += dMode[dMem['mode']]
sCmd += '0'
sCmd += dEncode[dMem['encode']]
sCmd += '00'
|
cdcdd076 |
sCmd += dShift[dMem['rpoffset']]
|
75d24721 |
sCmd += '0'
sTag = dMem['tag']
# Validate and append the memory tag data.
if len(sTag) > 12:
raise Exception('Memory tags must be twelve characters or less.')
sCmd += '%-12s' % sTag
sCmd += ';' # Terminate the completed command.
# Send the completed command.
sResult = sendCommand(sCmd)
|
91164a30 |
if sResult == '?;':
raise Exception('setMemory error')
|
75d24721 |
## end def
|
0ad18757 |
def setCTCSS(tone):
"""
|
75d24721 |
Description: Sends a formatted CN command that sets the desired
|
0ad18757 |
CTCSS tone.
Parameters: tone - a string containing the CTCSS tone in Hz, e.g.,
'100 Hz'
|
91164a30 |
Returns: nothing
|
0ad18757 |
"""
|
75d24721 |
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
|
0ad18757 |
sCmd = 'CN00%s;' % dTones[tone]
|
91164a30 |
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setCTCSS error')
|
0ad18757 |
## end def
def setDCS(code):
"""
|
75d24721 |
Description: Sends a formatted CN command that sets the desired
|
0ad18757 |
DCS code.
|
75d24721 |
Parameters: code - a string containing the DCS code, e.g., '23'
|
91164a30 |
Returns: nothing
|
0ad18757 |
"""
|
75d24721 |
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
|
0ad18757 |
sCmd = 'CN01%s;' % dDcs[code]
|
91164a30 |
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setDCS error')
|
0ad18757 |
## end def
def setPower(power):
"""
|
75d24721 |
Description: Sends a PC command that sets the desired
|
0ad18757 |
RF transmit power level.
Parameters: power - Watts, an integer between 5 and 100
|
91164a30 |
Returns: nothing
|
0ad18757 |
"""
|
75d24721 |
power = int(power)
# Validate power data and format command.
if power < 5 or power > 100:
raise Exception('Power must be between 0 and 100 watts, inclusive.')
sCmd += 'PC%03.d;' % power
|
91164a30 |
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setPower error')
|
0ad18757 |
## end def
|
91164a30 |
def setRxClarifier(state='OFF'):
"""
Description: Sends a formatted RT command that turns the Rx clarifier
on or off.
Parameters: state - string 'OFF' or 'ON'
Returns: nothing
"""
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
|
cdcdd076 |
sCmd = 'RT%s;' % bState[state]
|
91164a30 |
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setRxClarifier error')
## end def
def setTxClarifier(state='OFF'):
"""
Description: Sends a formatted XT command that turns the Rx clarifier
on or off.
Parameters: state - string 'OFF' or 'ON'
Returns: nothing
"""
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
|
cdcdd076 |
sCmd = 'XT%s;' % bState[state]
|
91164a30 |
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setTxClarifier error')
## end def
def setMemoryLocation(iLocation):
"""
Description: Sends a formatted MC command that sets the current
memory location.
Parameters: location - integer specifying memory location
|
b844f45d |
Returns: None if the memory location is blank, otherwise
returns a string containing the memory location.
|
91164a30 |
"""
# Validate memory location data and send the command.
if iLocation < 1 or iLocation > 118:
raise Exception('Memory location must be an integer between 1 and ' \
'118, inclusive.')
sCmd = 'MC%0.3d;' % iLocation
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
|
b844f45d |
return None
else:
return str(iLocation)
|
91164a30 |
## end def
|
cdcdd076 |
def setPreamp(state='IPO'):
"""
Description: Sends a formatted PA command that sets the preamplifier
state.
Parameters: state - string 'IPO', 'AMP 1', 'AMP 2'
Returns: nothing
"""
# An exception will automatically be raised if incorrect data is
# supplied - most likely a "key not found" error.
sCmd = 'PA0%s;' % dPreamp[state]
# Send the completed command.
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setPreAmp error')
## end def
def setRfAttn(state='NA'):
"""
Description: Sends a formatted PA command that sets the RF attenuator
state. Note that attempting to write or read the
attenuator for a VHF or UHF band results in an error.
Hence the addition of a state 'NA' for NOT APPLICABLE.
Parameters: state - string 'OFF', 'ON', 'NA'
Returns: nothing
"""
if state == 'NA':
return
sCmd = 'RA0%s;' % bState[state]
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setRfAttn error')
## end def
def setNoiseBlanker(state='OFF'):
"""
Description: Sends a formatted NB command that sets the noise blanker
state.
Parameters: state - string 'OFF', 'ON'
Returns: nothing
"""
sCmd = 'NB0%s;' % bState[state]
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setNoiseBlanker error')
## end def
def setIFshift(shift='NA'):
"""
Description: Sends a formatted IS command that sets the amount of
IF shift.
Parameters: state - string 'OFF', 'ON'
Returns: nothing
"""
if shift == 'NA':
return
shift = int(shift)
if abs(shift) > 1200:
raise Exception('setIFshift error: data out of bounds')
sCmd = 'IS0%0+5d;' % shift
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setIFshift error')
## end def
def setIFwidth(index='NA'):
"""
Description: Sends a formatted SH command that sets the IF width
IF shift.
Parameters: index of shift - value between 0 and 21, inclusive
Returns: nothing
"""
if index == 'NA':
return
index = int(index)
if index < 0 or index > 21:
raise Exception('setIFwidth error: data out of bounds')
sCmd = 'SH0%02d;' % index
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setIFwidth error')
## end def
def setContour(lParams):
"""
Description: Sends a formatted CO command that sets contour parameters
Parameters: lParams - list object containing the parameters
Returns: nothing
"""
if lParams[0] == 'NA':
return
for inx in range(4):
sCmd = 'CO0%d%04d' % (inx, int(lParams[inx]))
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setContour error')
## end def
def setDNRstate(state = 'NA'):
"""
Description: Sets the state (on or off) of the digital noise
reduction (DNR) processor.
Parameters: State = 0 (OFF) or 1 (ON)
Returns: nothing
"""
if state == 'NA':
return
sCmd = 'NR0%01d' % int(bState[state])
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setDNR error')
## end def
def setDNRalgorithm(algorithm = 1):
"""
Description: Sets the algorithm used by the digital noise
reduction (DNR) processor.
Parameters: algorithm - a number between 1 and 16 inclusive
Returns: nothing
"""
sCmd = 'RL0%02d' % int(algorithm)
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setDNR error')
## end def
def setDNFstate(state = 'NA'):
"""
Description: Sets the state (on or off) of the digital notch
filter (DNF) processor.
Parameters: State = 0 (OFF) or 1 (ON)
Returns: nothing
"""
if state == 'NA':
return
sCmd = 'BC0%01d' % int(bState[state])
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setDNF error')
## end def
def setNARstate( state = 'NA'):
"""
Description: Gets narrow/wide filter (NAR) state.
Parameters: none
Returns: 0 = Wide or 1 = Narrow
"""
if state == 'NA':
return
sCmd = 'NA0%s' % dNAR[state]
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setNAR error')
## end def
def setNotchState( state = ('NA', 'NA') ):
"""
Description: Gets the notch filter state and setting.
Parameters: none
Returns: a tuple containing state and frequency
state = 0 (OFF) or 1 (ON)
frequency = number between 1 and 320 (x 10 Hz)
"""
if state[0] == 'NA':
return
sCmd = 'BP0000%s' % bState[state[0]]
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setNotch error')
sCmd = 'BP01%03d' % int(state[1])
sResult = sendCommand(sCmd)
if sResult == '?;':
raise Exception('setNotch error')
## end def
|
b844f45d |
#############################################################################
# Helper functions to assist in various tasks. #
#############################################################################
|
75d24721 |
|
0ad18757 |
def parseCsvData(sline):
"""
Description: stores each item in the comma delimited line in a single
dictionary object using a key appropriate for that item.
Parameters: a string containing the comma delimited items to be parsed.
Returns: a dictionary object containing the parsed line.
"""
dChan = {} # define an empty dictionary object
lchan = sline.split(',') # split the line at the commas
# If the first line is a header line, ignore it.
if not lchan[0].isdigit():
return None
# Store the parsed items with the appropriate key in the dictionary object.
dChan['memloc'] = lchan[0]
|
cdcdd076 |
dChan['vfoa'] = lchan[1]
dChan['vfob'] = lchan[2]
dChan['rpoffset'] = lchan[3]
dChan['mode'] = lchan[4]
dChan['tag'] = lchan[5]
dChan['encode'] = lchan[6]
dChan['tone'] = lchan[7]
dChan['dcs'] = lchan[8]
dChan['clarfreq'] = lchan[9]
dChan['rxclar'] = lchan[10]
dChan['txclar'] = lchan[11]
dChan['preamp'] = lchan[12]
dChan['rfattn'] = lchan[13]
dChan['nblkr'] = lchan[14]
dChan['shift'] = lchan[15]
dChan['width'] = lchan[16]
dChan['contour'] = [ lchan[17], lchan[18], lchan[19], lchan[20] ]
dChan['dnrstate'] = lchan[21]
dChan['dnralgorithm'] = lchan[22]
dChan['dnfstate'] = lchan[23]
dChan['narstate'] = lchan[24]
dChan['notchstate'] = lchan[25]
dChan['notchfreq'] = lchan[26]
|
0ad18757 |
return dChan # return the dictionary object
## end def
|
75d24721 |
def sendCommand(sCmd):
"""
Description: Sends a formatted FT911 command to the communication
port connected to the FT991. Prints to stdout the
answer from the FT991 (if any).
Parameters: device - a pointer to the FT991 comm port
sCmd - a string containing the formatted command
Returns: nothing
"""
# Debug mode in conjunction with verbose mode is for verifying
# correct formatting of commands before they are actually sent
# to the FT991.
if verbose:
|
616f4281 |
print(sCmd, end='')
|
75d24721 |
# In debug mode do not actually send commands to the FT991.
if debug:
return ''
# Send the formatted command to the FT991 and get an answer, if any.
# If the command does not generate an answer, no characters will be
# returned by the FT991, resulting in an empty string returned by
# the receiveSerial function.
sendSerial(sCmd)
sResult = receiveSerial();
if verbose:
|
616f4281 |
print(sResult)
|
75d24721 |
return sResult
## end def
|
b844f45d |
#############################################################################
# Low level serial communications functions. #
#############################################################################
|
0ad18757 |
|
616f4281 |
def begin(comPort, baud=9600):
|
0ad18757 |
"""
Description: Initiates a serial connection the the FT991. Should
always be called before sending commands to or
receiving data from the FT991. Only needs to be called
once.
Parameters: none
Returns: a pointer to the FT991 serial connection
"""
global ptrDevice
# In debug mode do not actually send commands to the FT991.
if debug:
return
|
75d24721 |
|
0ad18757 |
# Create a FT991 object for serial communication
try:
|
616f4281 |
ptrDevice = serial.Serial(comPort, baud,
|
0ad18757 |
timeout=_INTERFACE_TIMEOUT)
|
616f4281 |
except Exception as error:
|
0ad18757 |
if str(error).find('could not open port') > -1:
|
616f4281 |
print('Please be sure the usb cable is properly connected to\n' \
|
0ad18757 |
'your FT991 and to your computer, and that the FT991 is\n' \
|
616f4281 |
'turned ON. Then restart this program.')
|
0ad18757 |
else:
|
616f4281 |
print('Serial port error: %s\n' % error)
|
0ad18757 |
exit(1)
time.sleep(.1) # give the connection a moment to settle
return ptrDevice
## end def
def receiveSerial(termchar=';'):
"""
Description: Reads output one character at a time from the device
until a terminating character is received. Returns a
string containing the characters read from the serial
port.
Parameters: termchar - character terminating the answer string
Returns: a string containing the received data
"""
answer = '' # initialize answer string to empty string
charCount = 0 # reset read character count to zero
while True:
startTime = time.time() # Start read character timer
c =''
while True:
# Check for a character available in the serial read buffer.
if ptrDevice.in_waiting:
c = ptrDevice.read()
break
# Timeout if a character does not become available.
if time.time() - startTime > _SERIAL_READ_TIMEOUT:
break # Character waiting timer has timed out.
# Return empty string if a character has not become available.
|
616f4281 |
if c == '' or c == b'\xfe':
|
0ad18757 |
break;
|
616f4281 |
try:
# Form a string from the received characters.
answer += c.decode('utf_8')
except Exception as e:
print('serial rx error: %s' % e)
#answer += str(c) # Form a string from the received characters.
|
0ad18757 |
charCount += 1 # Increment character count.
# If a semicolon has arrived then the FT991 has completed
# sending output to the serial port so stop reading characters.
# Also stop if max characters received.
if c == termchar:
break
if charCount > _SERIAL_READ_BUFFER_LENGTH:
raise Exception('serial read buffer overflow')
ptrDevice.flushInput() # Flush serial buffer to prevent overflows.
return answer
## end def
def sendSerial(command):
"""
Description: Writes a string to the device.
Parameters: command - string containing the FT991 command
Returns: nothing
"""
# In debug we only want to see the output of the command formatter,
# not actually send commands to the FT991. Debug mode should be
# used in conjunction with verbose mode.
|
616f4281 |
ptrDevice.write(command.encode('utf_8')) # Send command string to FT991
|
0ad18757 |
ptrDevice.flushOutput() # Flush serial buffer to prevent overflows
## end def
|
75d24721 |
# Main routine only gets called when this module is run as a program rather
# than imported into another python module. Code testing the functions in
# this module should be placed here.
|
0ad18757 |
def main():
"""
Description: Place code for testing this module here.
Parameters: none
Returns: nothing
"""
# Test this module.
global verbose, debug
verbose = True
debug = False
|
616f4281 |
# Determine OS type and set device port accordingly.
OS_type = sys.platform
if 'WIN' in OS_type.upper():
port = 'COM5'
else:
port = '/dev/ttyUSB0'
|
75d24721 |
# Instantiate serial connection to FT991
|
616f4281 |
begin(port, 9600)
|
91164a30 |
# Set and receive a memory channel
|
cdcdd076 |
dMem = {'memloc': '98', 'vfoa': '146.52', 'shift': 'OFF', \
|
91164a30 |
'mode': 'FM', 'encode': 'TONE ENC', 'tag': 'KA7JLO', \
'clarfreq': '1234', 'rxclar': 'ON', 'txclar': 'ON' \
}
setMemoryLocation(int(dMem['memloc']))
|
b844f45d |
setMemory(dMem)
|
91164a30 |
setRxClarifier(dMem['rxclar'])
setTxClarifier(dMem['txclar'])
setCTCSS('127.3 Hz')
setDCS('115')
print
|
b844f45d |
getMemory(int(dMem['memloc']))
|
91164a30 |
print
# Set and receive a memory channel
|
cdcdd076 |
dMem = {'memloc': '99', 'vfoa': '146.52', 'shift': 'OFF', \
|
91164a30 |
'mode': 'FM', 'encode': 'OFF', 'tag': 'KA7JLO', \
'clarfreq': '0', 'rxclar': 'OFF', 'txclar': 'OFF' \
}
setMemoryLocation(int(dMem['memloc']))
|
b844f45d |
setMemory(dMem)
|
91164a30 |
setRxClarifier(dMem['rxclar'])
setTxClarifier(dMem['txclar'])
setCTCSS('141.3 Hz')
setDCS('445')
print
|
b844f45d |
getMemory(int(dMem['memloc']))
|
91164a30 |
print
# Test set commands
#setMemoryLocation(2)
# Test get commands
# commands...
# Test CAT commands via direct pass-through
# Commands that return data
|
75d24721 |
sendCommand('IF;')
# Invalid command handling
sendCommand('ZZZ;')
|
0ad18757 |
## end def
if __name__ == '__main__':
main()
|