Browse code

initial commit 20191113

Gandolf authored on 11/13/2019 22:28:42
Showing 6 changed files
1 1
similarity index 100%
2 2
rename from ft991/passthrough/passThrough.py
3 3
rename to ft991/passthrough/passthrough.py
4 4
new file mode 100644
... ...
@@ -0,0 +1,43 @@
1
+Memory Ch,Rx Frequency,Tx Frequency,Offset,Repeater Shift,Mode,Tag,Encoding,Tone,DCS
2
+1,145.19,144.59,0.6,-RPT,FM,WA7ABU,TONE ENC,100.0 Hz,23
3
+2,145.29,144.69,0.6,-RPT,FM,WA7ABU,OFF,118.8 Hz,23
4
+3,444.95,449.95,5,+RPT,FM,W7ABU,TONE ENC,100.0 Hz,23
5
+4,145.49,144.89,0.6,-RPT,FM,W7PRA Sa,TONE ENC,136.5 Hz,23
6
+5,444.825,449.825,5,+RPT,FM,W7PRA Ha,DCS,100.0 Hz,23
7
+6,146.61,146.01,0.6,-RPT,FM,KA7ENW,TONE ENC,167.9 Hz,23
8
+7,442.85,447.85,5,+RPT,FM,KA7ENW,TONE ENC,167.9 Hz,23
9
+8,147.06,147.66,0.6,+RPT,FM,W1ARK,TONE ENC,100.0 Hz,23
10
+9,145.33,144.73,0.6,-RPT,FM,W7SRA,TONE ENC,186.2 Hz,23
11
+10,146.72,146.12,0.6,-RPT,FM,W7PXL,TONE ENC,100.0 Hz,23
12
+11,146.68,146.08,0.6,-RPT,FM,W7EUG,TONE ENC,100.0 Hz,23
13
+12,146.78,146.18,0.6,-RPT,FM,K7CVO,TONE ENC,156.7 Hz,23
14
+13,146.82,146.22,0.6,-RPT,FM,WA7TUV,TONE ENC,100.0 Hz,23
15
+14,434.91,439.91,5,+RPT,FM,KF7LDG,TONE ENC,100.0 Hz,23
16
+15,440.425,445.425,5,+RPT,FM,K7LNK,DCS,100.0 Hz,125
17
+16,440.8,445.8,5,+RPT,FM,AB7BS,TONE ENC,100.0 Hz,23
18
+17,441.975,446.975,5,+RPT,FM,W7CQZ,TONE ENC,100.0 Hz,23
19
+18,442.3,447.3,5,+RPT,FM,N8GFO,TONE ENC,162.2 Hz,23
20
+20,146.43,146.43,0,OFF,FM,Prep Net,OFF,100.0 Hz,23
21
+21,146.42,146.42,0,OFF,FM,VHF Prep,OFF,100.0 Hz,23
22
+22,146.55,146.55,0,OFF,FM,VHF Surv,OFF,100.0 Hz,23
23
+23,446.03,446.03,0,OFF,FM,UHF Prep,OFF,100.0 Hz,23
24
+24,146.43,146.43,0.6,OFF,FM,V Sim 1,OFF,100.0 Hz,23
25
+25,146.475,146.475,0.6,OFF,FM,V Sim 2,OFF,100.0 Hz,23
26
+26,146.58,146.58,0.6,OFF,FM,V Sim 3,OFF,100.0 Hz,23
27
+27,147.465,147.465,0.6,OFF,FM,V Sim 4,OFF,100.0 Hz,23
28
+28,445.9625,445.9625,5,OFF,FM,U Sim1,OFF,100.0 Hz,23
29
+29,445.975,445.975,5,OFF,FM,U Sim 2,OFF,100.0 Hz,23
30
+30,446.0375,446.0375,5,OFF,FM,U Sim 3,OFF,100.0 Hz,23
31
+31,446.075,446.075,5,OFF,FM,U Sim 4,OFF,100.0 Hz,23
32
+40,162.475,162.475,0,OFF,FM,WX3PA4,OFF,100.0 Hz,23
33
+41,162.5,162.5,0,OFF,FM,WX6PA5,OFF,100.0 Hz,23
34
+42,122.725,122.725,0,OFF,AM,ALB CTAF,OFF,100.0 Hz,23
35
+43,123.075,123.075,0,OFF,AM,CVO CTAF,OFF,100.0 Hz,23
36
+44,156.08,156.08,0.6,OFF,FM,PD,OFF,100.0 Hz,23
37
+45,156.815,156.815,0,OFF,FM,ALB PD,OFF,100.0 Hz,23
38
+46,155.01,155.01,0,OFF,FM,PD Sec,OFF,100.0 Hz,23
39
+47,151.13,151.13,0,OFF,FM,LCSO Knx,OFF,100.0 Hz,23
40
+48,154.71,154.71,0,OFF,FM,LCSO Sct,OFF,100.0 Hz,23
41
+49,156.7,156.7,0,OFF,FM,LCSO ST,OFF,100.0 Hz,23
42
+50,155.805,155.805,0,OFF,FM,LCSO SAR,OFF,100.0 Hz,23
43
+51,155.34,155.34,0,OFF,FM,ALB GEN,OFF,100.0 Hz,23
0 44
new file mode 100755
... ...
@@ -0,0 +1,247 @@
1
+#!/usr/bin/python -u
2
+# The -u option turns off block buffering of python output. This assures
3
+# that output streams to stdout when output happens.
4
+#
5
+# Description:  This module contains tables for tranlating common transceiver
6
+#               settings to FT991 CAT parameters.  Low level serial
7
+#               communication functions are handled by this module.  In
8
+#               particular this module handles:
9
+#                   1. Setting a serial connection object
10
+#                   2. Sending character strings to the serial port
11
+#                   3. Reading characters from the serial port
12
+#                   4. Formatting of FT991 commands
13
+#
14
+# This script has been tested with the following
15
+#
16
+#     Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
17
+#     [GCC 7.3.0] on linux2
18
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
19
+
20
+import sys, serial, time
21
+
22
+# Determine OS type and set device port accordingly.
23
+OS_type = sys.platform
24
+if 'WIN' in OS_type.upper():
25
+    _SERIAL_PORT = 'COM5'
26
+else:
27
+    _SERIAL_PORT = '/dev/ttyUSB0'
28
+
29
+# General constant defines
30
+_BAUD_RATE = 9600
31
+_INTERFACE_TIMEOUT = 0.1 # seconds
32
+_SERIAL_READ_TIMEOUT = 0.1 # seconds
33
+_SERIAL_READ_BUFFER_LENGTH = 1024 # characters
34
+
35
+verbose = False
36
+debug = False
37
+
38
+# Define lookup tables for common transceiver settings.  Common settings
39
+# such as modulation mode, repeater offset direction, DCS/CTCSS mode,
40
+# CTCSS tone, and DCS code are translated to the repective FT991 parameter
41
+# value.
42
+
43
+dMode = {'LSB':'1', 'USB':'2', 'CW':'3', 'FM':'4', 'AM':'5', 'RTTY-LSB':'6',
44
+         'CW-R':'7', 'DATA-LSB':'8', 'RTTY-USB':'9', 'DATA-FM':'A',
45
+         'FM-N':'B', 'DATA-USB':'C', 'AM-N':'D', 'C4FM':'E'}
46
+
47
+dShift = {'OFF':'0', '+RPT':'1', '-RPT':'2'}
48
+
49
+dPower = { 'LOW':'005', 'MID':'020', 'HIGH':'050' }
50
+
51
+dEncode = {'OFF':'0', 'ENC/DEC':'1', 'TONE ENC':'2', 'DCS ENC/DEC':'4',
52
+          'DCS':'3'}
53
+
54
+dTones = {'67.0 Hz':'000', '69.3 Hz':'001', '71.9 Hz':'002', '74.4 Hz':'003', 
55
+       '77.0 Hz':'004', '79.7 Hz':'005', '82.5 Hz':'006', '85.4 Hz':'007',
56
+       '88.5 Hz':'008', '91.5 Hz':'009', '94.8 Hz':'010', '97.4 Hz':'011',
57
+       '100.0 Hz':'012', '103.5 Hz':'013', '107.2 Hz':'014', '110.9 Hz':'015',
58
+       '114.8 Hz':'016', '118.8 Hz':'017', '123.0 Hz':'018', '127.3 Hz':'019',
59
+       '131.8 Hz':'020', '136.5 Hz':'021', '141.3 Hz':'022', '146.2 Hz':'023',
60
+       '151.4 Hz':'024', '156.7 Hz':'025', '159.8 Hz':'026', '162.2 Hz':'027',
61
+       '165.5 Hz':'028', '167.9 Hz':'029', '171.3 Hz':'030', '173.8 Hz':'031',
62
+       '177.3 Hz':'032', '179.9 Hz':'033', '183.5 Hz':'034', '186.2 Hz':'035',
63
+       '189.9 Hz':'036', '192.8 Hz':'037', '196.6 Hz':'038', '199.5 Hz':'039',
64
+       '203.5 Hz':'040', '206.5 Hz':'041', '210.7 Hz':'042', '218.1 Hz':'043',
65
+       '225.7 Hz':'044', '229.1 Hz':'045', '233.6 Hz':'046', '241.8 Hz':'047',
66
+       '250.3 Hz':'048', '254.1 Hz':'049'} 
67
+
68
+dDcs = {'23':'000', '25':'001', '26':'002', '31':'003', '32':'004',
69
+        '36':'005', '43':'006', '47':'007', '51':'008', '53':'009',
70
+        '54':'010', '65':'011', '71':'012', '72':'013', '73':'014',
71
+        '74':'015', '114':'016', '115':'017', '116':'018', '122':'019',
72
+        '125':'020', '131':'021', '132':'022', '134':'023', '143':'024',
73
+        '145':'025', '152':'026', '155':'027', '156':'028', '162':'029',
74
+        '165':'030', '172':'031', '174':'032', '205':'033', '212':'034',
75
+        '223':'035', '225':'036', '226':'037', '243':'038', '244':'039',
76
+        '245':'040', '246':'041', '251':'042', '252':'043', '255':'044',
77
+        '261':'045', '263':'046', '265':'047', '266':'048', '271':'049',
78
+        '274':'050', '306':'051', '311':'052', '315':'053', '325':'054',
79
+        '331':'055', '332':'056', '343':'057', '346':'058', '351':'059',
80
+        '356':'060', '364':'061', '365':'062', '371':'063', '411':'064',
81
+        '412':'065', '413':'066', '423':'067', '431':'068', '432':'069',
82
+        '445':'070', '446':'071', '452':'072', '454':'073', '455':'074',
83
+        '462':'075', '464':'076', '465':'077', '466':'078', '503':'079',
84
+        '506':'080', '516':'081', '523':'082', '526':'083', '532':'084',
85
+        '546':'085', '565':'086', '606':'087', '612':'088', '624':'089',
86
+        '627':'090', '631':'091', '632':'092', '654':'093', '662':'094',
87
+        '664':'095', '703':'096', '712':'097', '723':'098', '731':'099',
88
+        '732':'100', '734':'101', '743':'102', '754':'103'}
89
+
90
+# Define functions for implementing the various FT991 CAT commands
91
+
92
+def getMTcommand(dChan):
93
+    """
94
+    Description: returns a formatted MT command to the calling function.
95
+    Parameters: dChan - a dictionary objected with the following keys defined
96
+                    chnum - the memory location to be written
97
+                    rxfreq - the receive frequency of VFO-A in MHz
98
+                    mode - the modulation mode
99
+                    encode - the tone or DCS encoding mode
100
+                    shift - the direction of the repeater shift
101
+                    tag - a label for the memory location
102
+    Returns: a string containing the formatted command
103
+    """
104
+    sCmd = 'MT'
105
+    sCmd += '%0.3d' % int(dChan['chnum'])
106
+    sCmd += '%d' % int(float(dChan['rxfreq']) * 1E6)
107
+    sCmd += '+000000'
108
+    sCmd += dMode[dChan['mode']]
109
+    sCmd += '0'
110
+    sCmd += dEncode[dChan['encode']]
111
+    sCmd += '00'
112
+    sCmd += dShift[dChan['shift']]
113
+    sCmd += '0'
114
+    sCmd += '%-12s' % dChan['tag']
115
+    sCmd += ';'
116
+    if verbose:
117
+        print sCmd,
118
+    return sCmd
119
+ 
120
+def getCTCSScommand(dChan):
121
+    """
122
+    Description:  returns a formatted CN command that sets the desired
123
+                  CTCSS tone.
124
+    Parameters:  dChan - a dictionary object with the following key defined
125
+                     tone - the CTCSS tone in Hz, e.g.,
126
+                            dChan = {'tone':'100 Hz'}
127
+    Returns: a string containing the formatted command
128
+    """
129
+    sCmd = 'CN0'
130
+    sCmd += '0%s;' % dTones[dChan['tone']]
131
+    if verbose:
132
+        print sCmd,
133
+    return sCmd
134
+
135
+def getDCScommand(dChan):
136
+    """
137
+    Description:  returns a formatted CN command that sets the desired
138
+                  DCS code.
139
+    Parameters:  dChan - a dictionary object with the following key defined
140
+                     dcs - the DCS code, e.g., dChan = {'dcs':'23'}
141
+    Returns: a string containing the formatted command
142
+    """
143
+    sCmd = 'CN0'
144
+    sCmd += '1%s;' % dDcs[dChan['dcs']]
145
+    if verbose:
146
+        print sCmd,
147
+    return sCmd
148
+
149
+def getPCcommand(dChan):
150
+    """
151
+    Description:  returns a formatted PC command that sets the desired
152
+                  RF transmit power level.
153
+    Parameters:  dChan - a dictionary object with the following key defined
154
+                     power - the power level in Watts
155
+                            e.g., dChan = {'power':'020'}
156
+    Returns: a string containing the formatted command
157
+    """
158
+    sCmd = 'PC'
159
+    sCmd += '%s;' % dPower[dChan['power']] 
160
+    if verbose:
161
+        print sCmd,
162
+    return sCmd
163
+
164
+def receiveSerial(device, termchar=';'):
165
+    """
166
+    Description: reads output one character at a time from the device
167
+                 until a terminating character is received.  Returns a     
168
+                 string containing the characters read from the serial
169
+                 port.
170
+    Parameters:  device - a serial object connected to the device port
171
+                 termchar - character terminating the answer string
172
+    Returns:     string
173
+    """
174
+    answer = '' # initialize answer string to empty string
175
+    charCount = 0  # reset read character count to zero
176
+
177
+    while True:
178
+        startTime = time.time() # Start read character timer
179
+        c =''
180
+        while True:
181
+            # Check for a character available in the serial read buffer.
182
+            if device.in_waiting:
183
+                c = device.read()
184
+                break
185
+            # Timeout if a character does not become available.
186
+            if time.time() - startTime > _SERIAL_READ_TIMEOUT:
187
+                break # Character waiting timer has timed out.
188
+        # Return empty string if a character has not become available.
189
+        if c == '':
190
+            break;
191
+        answer += c # Form a string from the received characters.
192
+        charCount += 1 # Increment character count.
193
+        # If a semicolon has arrived then the FT991 has completed
194
+        # sending output to the serial port so stop reading characters.
195
+        # Also stop if max characters received. 
196
+        if c == termchar:
197
+            break
198
+        if charCount > _SERIAL_READ_BUFFER_LENGTH:
199
+            raise Exception('serial read buffer overflow')
200
+    device.flushInput() # Flush serial buffer to prevent overflows.
201
+    return answer           
202
+## end def
203
+
204
+def sendSerial(device, command):
205
+    """
206
+    Description: writes a string to the device.
207
+    Parameters:  device - a serial object connected to the device port
208
+                 command - string containing the FT991 command
209
+    Returns:     nothing
210
+    """
211
+    # In debug we only want to see the output of the command formatter,
212
+    # not actually send commands to the FT991.  Debug mode should be
213
+    # used in conjunction with verbose mode.
214
+    if not debug: 
215
+        device.write(command) # Send command string to FT991
216
+        device.flushOutput() # Flush serial buffer to prevent overflows
217
+
218
+def begin():
219
+    """
220
+    Description: initiates a serial connection the the FT991. Should always
221
+                 be called before sending commands to or receiving data
222
+                 from the FT991.  Only needs to be called once.
223
+    Parameters: none
224
+    Returns: a pointer to the FT991 serial connection
225
+    """    # Create a FT991 object for serial communication
226
+    pDevice = serial.Serial(_SERIAL_PORT, _BAUD_RATE,      
227
+                            timeout=_INTERFACE_TIMEOUT)
228
+    time.sleep(.1) # give the connection a moment to settle
229
+    return pDevice
230
+
231
+
232
+def main():
233
+    """
234
+    Description: functions and function calls that test the functions
235
+                 in this module should be placed here.
236
+    Parameters: none
237
+    Returns: nothing
238
+    """    # Test this module.
239
+    verbose = True
240
+    ft991 = begin()
241
+    sendSerial(ft991,'if;')
242
+    answer= receiveSerial(ft991)
243
+    print answer
244
+## end def
245
+
246
+if __name__ == '__main__':
247
+    main()
0 248
new file mode 100644
1 249
Binary files /dev/null and b/ft991/writememory/ft991_memory_settings.ots differ
2 250
new file mode 100644
... ...
@@ -0,0 +1,19 @@
1
+1.  Create a folder for the writemmory files.
2
+2.  Copy the files writememory.py and ft991.py to the folder.
3
+3.  Use the following commands to make the file executable
4
+        chmod +x writememory.py
5
+4.  An example of a comma delimited spreadsheet has been provided.
6
+    To use the example without actually programming your FT991 run
7
+    the following command
8
+        ./writememory.py -d -v -f example.csv
9
+5.  Using the -d debug option prevents commands from actually being
10
+    written to the FT991.  Using the -v verbose option allows you to
11
+    see the commands that are sent to the FT991.  The -f file option
12
+    allows you to use a custom file name for the settings file.
13
+6.  A LibreOffice spreadsheet template "ft991_memory_settings.ots" has
14
+    been provided to help you create comma delimited settings files.
15
+    Various cells have dropdown list boxes for modes, tones, dcs codes, etc.
16
+    You should use these list boxes for setting those items.  Save the
17
+    spreadsheet in comma delimited format (.csv).
18
+7.  Incorrectly entered modes, codes, tones, etc, will cause to program
19
+    to crash, most likely with a dictionary object, key error.
0 20
new file mode 100755
... ...
@@ -0,0 +1,159 @@
1
+#!/usr/bin/python -u
2
+# The -u option turns off block buffering of python output. This assures
3
+# that output streams to stdout when output happens.
4
+#
5
+# Description:  This program reads a comma delimited spreadsheet file
6
+#               containing common transceiver settings.  The settings
7
+#               are stored in the transceivers memory.
8
+#
9
+# This script has been tested with the following
10
+#
11
+#     Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
12
+#     [GCC 7.3.0] on linux2
13
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
14
+
15
+# Import python library modules. The serial module should be included in
16
+# recent linux distributions.  If not, it may need to be installed.
17
+import sys, serial, time
18
+# Import FT991 serial communication functions and command library.  This
19
+# is a custom module that should be in the same folder as this script.
20
+import ft991
21
+
22
+# Define default memory settings file
23
+_SETTINGS_FILE = './ft991mem.csv'
24
+
25
+# Define global variables
26
+settingsFile = _SETTINGS_FILE
27
+
28
+def readSettingsFile(fileName):
29
+    """
30
+    Description: reads the comma delimited memory settings file and parses
31
+                 each line of that file into a dictionary object which stores
32
+                 the settings for that memory location.  Each dictionary
33
+                 object in turn gets stored in a single list object.
34
+    Parameters: name of memory settings file
35
+    Returns: a list object
36
+    """
37
+    lChan = []
38
+    fchs = open(fileName, 'r') # open the file for reading
39
+    # read a line from the file
40
+    for line in fchs:
41
+        # remove non-printable characters
42
+        rline = line.strip()
43
+        # parse the comma delimited line and store in a dictionary object
44
+        dChanData = parseData(rline)
45
+        # store the parsed line in a list object
46
+        if dChanData != None:
47
+            lChan.append(dChanData)
48
+    fchs.close()
49
+    return lChan
50
+
51
+def parseData(sline):
52
+    """
53
+    Description:  stores each item in the comma delimited line in a single
54
+                  dictionary object using a key appropriate for that item.
55
+    Parameters: a string containing the comma delimited items to be parsed.
56
+    Returns: a dictionary object containing the parsed line.
57
+    """
58
+    dChan = {} # define an empty dictionary object
59
+    lchan = sline.split(',') # split the line at the commas
60
+    # If the first line is a header line, ignore it.
61
+    if not lchan[0].isdigit():
62
+        return None
63
+    # Store the parsed items with the appropriate key in the dictionary object.
64
+    dChan['chnum'] = lchan[0]
65
+    dChan['rxfreq'] = lchan[1]
66
+    dChan['txfreq'] = lchan[2]
67
+    dChan['offset'] = lchan[3]
68
+    dChan['shift'] = lchan[4]
69
+    dChan['mode'] = lchan[5]
70
+    dChan['tag'] = lchan[6]
71
+    dChan['encode'] = lchan[7]
72
+    dChan['tone'] = lchan[8]
73
+    dChan['dcs'] = str(int(lchan[9]))
74
+    return dChan # return the dictionary object
75
+
76
+
77
+def sendCommand(device, sCmd):
78
+    """
79
+    Description:  sends a formatted FT911 command to the communication port
80
+                  connected to the FT991.  Prints to stdout the answer from
81
+                  the FT991 (if any).
82
+    Parameters: device - a pointer to the FT991 comm port
83
+                sCmd - a string containing the formatted command
84
+    Returns: nothing
85
+    """
86
+    ft991.sendSerial(device, sCmd)
87
+    sAnswer = ft991.receiveSerial(device);
88
+    if sAnswer != '':
89
+        print sAnswer
90
+
91
+def getCLarguments():
92
+    """Get command line arguments.  There are four possible arguments
93
+          -v turns on verbose mode
94
+          -d turns on debug mode
95
+          -f name of comman delimited memory settings file
96
+       Returns: nothing
97
+    """
98
+    #global ft991.verbose, ft991.debug, settingsFile
99
+    global settingsFile
100
+
101
+    index = 1
102
+    while index < len(sys.argv):
103
+        if sys.argv[index] == '-f':
104
+            if len(sys.argv) < index + 2:
105
+                print "-f option requires file name"
106
+                exit(1);
107
+            settingsFile = sys.argv[index + 1]
108
+            index += 1
109
+        elif sys.argv[index] == '-v':
110
+            ft991.verbose = True
111
+        elif sys.argv[index] == '-d':
112
+            ft991.debug = True
113
+        else:
114
+            cmd_name = sys.argv[0].split('/')
115
+            print "Usage: %s [-v] [-f file]\n" \
116
+                  "  -f: settings file\n" \
117
+                  "  -v: verbose mode\n" \
118
+                  "  -d: debug mode" % cmd_name[-1]
119
+            exit(-1)
120
+        index += 1
121
+##end def
122
+
123
+def main():
124
+    """
125
+    Description:  Reads the settings file, creating a list object which
126
+                  contains dictionary objects as its elements.  The dictionary
127
+                  objects contain the settings for each memory location to be
128
+                  programmed.
129
+    Parameters: none
130
+    Returns: nothing
131
+    """
132
+
133
+    # Get command line arguments, if any.  Otherwise use defaults.
134
+    getCLarguments()
135
+
136
+    # Create a FT991 object for serial communication.
137
+    pft991 = ft991.begin()
138
+    time.sleep(.1) # give the connection a moment to settle
139
+
140
+    # Read and parse the settings file.
141
+    dChan = readSettingsFile(settingsFile)
142
+    # Get and send the commands to the FT991 for writing the memory locations.
143
+    for item in dChan:
144
+        if ft991.verbose:
145
+            print '%s: ' % item['chnum'],
146
+
147
+        # Format and send memory channel vfo and mode data.
148
+        sendCommand(pft991, ft991.getMTcommand(item))
149
+        # Format and send CTCSS tone for memory channel.
150
+        sendCommand(pft991, ft991.getCTCSScommand(item))
151
+        # Format and send DCS code for memory channel. 
152
+        sendCommand(pft991, ft991.getDCScommand(item))
153
+
154
+        if ft991.verbose:
155
+            print
156
+## end def
157
+
158
+if __name__ == '__main__':
159
+    main()