Browse code

reorganize repository

Gandolf authored on 11/25/2019 06:37:12
Showing 1 changed files
1 1
deleted file mode 100755
... ...
@@ -1,442 +0,0 @@
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
-# Module: ft991.py
6
-#
7
-# Description:  This module contains tables for translating common transceiver
8
-#               settings to FT991 CAT parameters.  Low level serial
9
-#               communication functions are also handled by this module.  In
10
-#               particular this module handles:
11
-#                   1. Instantiating a serial connection object
12
-#                   2. Sending character strings to the serial port
13
-#                   3. Reading characters from the serial port
14
-#                   4. Parsing and formatting of FT991 commands
15
-#                   5. Translating radio operating parameters to CAT
16
-#                      commands, i.e., CTCSS tones.  
17
-#
18
-# Copyright 2019 by Jeff Owrey, Intravisions.com
19
-#    This program is free software: you can redistribute it and/or modify
20
-#    it under the terms of the GNU General Public License as published by
21
-#    the Free Software Foundation, either version 3 of the License, or
22
-#    (at your option) any later version.
23
-#
24
-#    This program is distributed in the hope that it will be useful,
25
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
26
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
-#    GNU General Public License for more details.
28
-#
29
-#    You should have received a copy of the GNU General Public Licensef
30
-#    along with this program.  If not, see http://www.gnu.org/license.
31
-#
32
-# Revision History
33
-#   * v10 24 Nov 2019 by J L Owrey; first release
34
-#
35
-# This script has been tested with the following
36
-#
37
-#     Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
38
-#     [GCC 7.3.0] on linux2
39
-#2345678901234567890123456789012345678901234567890123456789012345678901234567890
40
-
41
-import sys, serial, time
42
-
43
-# General constant defines
44
-_INTERFACE_TIMEOUT = 0.1 # seconds
45
-_SERIAL_READ_TIMEOUT = 0.1 # seconds
46
-_SERIAL_READ_BUFFER_LENGTH = 1024 # characters
47
-
48
-# Define globals
49
-verbose = False
50
-debug = False
51
-ptrDevice = None
52
-
53
-# Define lookup tables for common transceiver settings.  Common settings
54
-# such as modulation mode, repeater offset direction, DCS/CTCSS mode,
55
-# CTCSS tone, and DCS code are translated to the repective FT991 parameter
56
-# value.
57
-
58
-# Modulation modes
59
-dMode = { 'LSB':'1', 'USB':'2', 'CW':'3', 'FM':'4', 'AM':'5',
60
-          'RTTY-LSB':'6', 'CW-R':'7', 'DATA-LSB':'8', 'RTTY-USB':'9',
61
-          'DATA-FM':'A', 'FM-N':'B', 'DATA-USB':'C', 'AM-N':'D',
62
-          'C4FM':'E' }
63
-
64
-# Repeater shift direction
65
-dShift = { 'OFF':'0', '+RPT':'1', '-RPT':'2' }
66
-
67
-# Power settings
68
-dPower = { 'LOW':5, 'MID':020, 'HIGH':50, 'MAX':100 }
69
-
70
-# Repeater signaling modes
71
-dEncode = { 'OFF':'0', 'ENC/DEC':'1', 'TONE ENC':'2',
72
-            'DCS ENC/DEC':'4', 'DCS':'3' }
73
-
74
-# CTCSS Tones
75
-dTones = { '67.0 Hz':'000', '69.3 Hz':'001', '71.9 Hz':'002',
76
-           '74.4 Hz':'003', '77.0 Hz':'004', '79.7 Hz':'005',
77
-           '82.5 Hz':'006', '85.4 Hz':'007', '88.5 Hz':'008',
78
-           '91.5 Hz':'009', '94.8 Hz':'010', '97.4 Hz':'011',
79
-           '100.0 Hz':'012', '103.5 Hz':'013', '107.2 Hz':'014',
80
-           '110.9 Hz':'015', '114.8 Hz':'016', '118.8 Hz':'017',
81
-           '123.0 Hz':'018', '127.3 Hz':'019', '131.8 Hz':'020',
82
-           '136.5 Hz':'021', '141.3 Hz':'022', '146.2 Hz':'023',
83
-           '151.4 Hz':'024', '156.7 Hz':'025', '159.8 Hz':'026',
84
-           '162.2 Hz':'027', '165.5 Hz':'028', '167.9 Hz':'029',
85
-           '171.3 Hz':'030', '173.8 Hz':'031', '177.3 Hz':'032',
86
-           '179.9 Hz':'033', '183.5 Hz':'034', '186.2 Hz':'035',
87
-           '189.9 Hz':'036', '192.8 Hz':'037', '196.6 Hz':'038',
88
-           '199.5 Hz':'039', '203.5 Hz':'040', '206.5 Hz':'041',
89
-           '210.7 Hz':'042', '218.1 Hz':'043', '225.7 Hz':'044',
90
-           '229.1 Hz':'045', '233.6 Hz':'046', '241.8 Hz':'047',
91
-           '250.3 Hz':'048', '254.1 Hz':'049' } 
92
-
93
-# DCS Tones
94
-dDcs = { '23':'000', '25':'001', '26':'002', '31':'003', '32':'004',
95
-         '36':'005', '43':'006', '47':'007', '51':'008', '53':'009',
96
-         '54':'010', '65':'011', '71':'012', '72':'013', '73':'014',
97
-         '74':'015', '114':'016', '115':'017', '116':'018', '122':'019',
98
-         '125':'020', '131':'021', '132':'022', '134':'023', '143':'024',
99
-         '145':'025', '152':'026', '155':'027', '156':'028', '162':'029',
100
-         '165':'030', '172':'031', '174':'032', '205':'033', '212':'034',
101
-         '223':'035', '225':'036', '226':'037', '243':'038', '244':'039',
102
-         '245':'040', '246':'041', '251':'042', '252':'043', '255':'044',
103
-         '261':'045', '263':'046', '265':'047', '266':'048', '271':'049',
104
-         '274':'050', '306':'051', '311':'052', '315':'053', '325':'054',
105
-         '331':'055', '332':'056', '343':'057', '346':'058', '351':'059',
106
-         '356':'060', '364':'061', '365':'062', '371':'063', '411':'064',
107
-         '412':'065', '413':'066', '423':'067', '431':'068', '432':'069',
108
-         '445':'070', '446':'071', '452':'072', '454':'073', '455':'074',
109
-         '462':'075', '464':'076', '465':'077', '466':'078', '503':'079',
110
-         '506':'080', '516':'081', '523':'082', '526':'083', '532':'084',
111
-         '546':'085', '565':'086', '606':'087', '612':'088', '624':'089',
112
-         '627':'090', '631':'091', '632':'092', '654':'093', '662':'094',
113
-         '664':'095', '703':'096', '712':'097', '723':'098', '731':'099',
114
-         '732':'100', '734':'101', '743':'102', '754':'103' }
115
-
116
-# Clarifier state
117
-dRxClar = { 'OFF':'0', 'ON':'1' }
118
-dTxClar = { 'OFF':'0', 'ON':'1' }
119
-
120
-# Define 'set' functions to encapsulate the various FT991 CAT commands.
121
-
122
-def setMemory(dMem):
123
-    """
124
-    Description: Returns a formatted MT command to the calling function.
125
-    Parameters: dMem - a dictionary objected with the following keys
126
-                       defined:
127
-
128
-                       memloc - the memory location to be written
129
-                       rxfreq - the receive frequency of VFO-A in MHz
130
-                       mode - the modulation mode
131
-                       encode - the tone or DCS encoding mode
132
-                       shift - the direction of the repeater shift
133
-                       tag - a label for the memory location
134
-
135
-    Returns: a string containing the formatted command
136
-    """
137
-    sCmd = 'MC%0.3d;' % int(dMem['memloc'])
138
-    sResult = sendCommand(sCmd)
139
-
140
-    # While the 'MW' and 'MT' commands can be used to turn the Rx
141
-    # and Tx clarifiers on, the clarifier states can only be turned
142
-    # off by sending the 'RT0' and 'XT0' commands.  This situation is
143
-    # probably due to a potential bug in the CAT interface.
144
-    sResult = sendCommand('RC;RT0;XT0;')
145
-
146
-    sCmd = 'MT%0.3d' % int(dMem['memloc'])
147
-    sCmd += '%d' % int(float(dMem['rxfreq']) * 1E6)
148
-    sCmd += '%+0.4d' % int(dMem['clarfreq'])
149
-    sCmd += dRxClar[dMem['rxclar']]
150
-    sCmd += dTxClar[dMem['txclar']]
151
-    sCmd += dMode[dMem['mode']]
152
-    sCmd += '0'
153
-    sCmd += dEncode[dMem['encode']]
154
-    sCmd += '00'
155
-    sCmd += dShift[dMem['shift']]
156
-    sCmd += '0'
157
-    sCmd += '%-12s' % dMem['tag']
158
-    sCmd += ';'
159
-    sResult = sendCommand(sCmd)
160
-    return sResult
161
-## end def
162
-
163
-def getMemory(memLoc):
164
-    """
165
-    Description: 
166
-    Parameters: 
167
-    Returns: 
168
-    """
169
-    dMem = {}
170
-
171
-    # Set memory location pointer in FT991.  This is done
172
-    # by sending the memory location select (MC) command.
173
-    sCmd = 'MC%0.3d;' % (memLoc)
174
-    sResult = sendCommand(sCmd)
175
-    # Skip blank memory locations, which return '?;'.
176
-    if sResult == '?;':
177
-        return None
178
-
179
-    # Send the get memory settings string to the FT991.
180
-    sCmd = 'MT%0.3d;' % (memLoc)
181
-    sResult = sendCommand(sCmd)
182
-
183
-    # Parse memory settings string returned by the FT991
184
-    memloc = sResult[2:5]
185
-    rxfreq = sResult[5:14]
186
-    clarfreq = sResult[14:19]
187
-    rxclar = sResult[19]
188
-    txclar = sResult[20]
189
-    mode = sResult[21]
190
-    encode = sResult[23]
191
-    shift = sResult[26]
192
-    tag = sResult[28:40]
193
-
194
-    # Store the memory settings in a dictionary object.
195
-    dMem['memloc'] = str(int(memloc))
196
-    dMem['rxfreq'] = str(float(rxfreq) / 10**6)
197
-    dMem['clarfreq'] = str(int(clarfreq))
198
-    dMem['rxclar'] = dRxClar.keys()[dRxClar.values().index(rxclar)]
199
-    dMem['txclar'] = dTxClar.keys()[dTxClar.values().index(txclar)]
200
-    dMem['mode'] = dMode.keys()[dMode.values().index(mode)]
201
-    dMem['encode'] = dEncode.keys()[dEncode.values().index(encode)]
202
-    dMem['shift'] = dShift.keys()[dShift.values().index(shift)]
203
-    dMem['tag'] = tag.strip()
204
-
205
-    return dMem
206
-## end def
207
-
208
-def getCTCSS():
209
-    """
210
-    Description: 
211
-    Parameters: 
212
-    Returns: 
213
-    """
214
-    # Get result CTCSS tone
215
-    sResult = sendCommand('CN00;')
216
-    tone = sResult[4:7]
217
-    return dTones.keys()[dTones.values().index(tone)]
218
-## end def
219
-
220
-def getDCS():
221
-    """
222
-    Description: 
223
-    Parameters: 
224
-    Returns: 
225
-    """
226
-    # Get result of CN01 command
227
-    sResult = sendCommand('CN01;')
228
-    dcs = sResult[4:7]
229
-    return dDcs.keys()[dDcs.values().index(dcs)]
230
-## end def
231
-
232
-def setCTCSS(tone):
233
-    """
234
-    Description:  returns a formatted CN command that sets the desired
235
-                  CTCSS tone.
236
-    Parameters:   tone - a string containing the CTCSS tone in Hz, e.g.,
237
-                         '100 Hz'
238
-    Returns: a string containing the formatted command
239
-    """
240
-    sCmd = 'CN00%s;' % dTones[tone]
241
-    return sendCommand(sCmd)
242
-## end def
243
-
244
-def setDCS(code):
245
-    """
246
-    Description:  returns a formatted CN command that sets the desired
247
-                  DCS code.
248
-    Parameters:   code - a string containing the DCS code, e.g., '23'
249
-    Returns: a string containing the formatted command
250
-    """
251
-    sCmd = 'CN01%s;' % dDcs[code]
252
-    return sendCommand(sCmd)
253
-## end def
254
-
255
-def setPower(power):
256
-    """
257
-    Description:  returns a formatted PC command that sets the desired
258
-                  RF transmit power level.
259
-    Parameters:   power - Watts, an integer between 5 and 100
260
-    Returns: a string containing the formatted command
261
-    """
262
-    sCmd = 'PC'
263
-    sCmd += '%03.d;' % power 
264
-    return sendCommand(sCmd)
265
-## end def
266
-
267
-def parseCsvData(sline):
268
-    """
269
-    Description:  stores each item in the comma delimited line in a single
270
-                  dictionary object using a key appropriate for that item.
271
-    Parameters: a string containing the comma delimited items to be parsed.
272
-    Returns: a dictionary object containing the parsed line.
273
-    """
274
-    dChan = {} # define an empty dictionary object
275
-    lchan = sline.split(',') # split the line at the commas
276
-    # If the first line is a header line, ignore it.
277
-    if not lchan[0].isdigit():
278
-        return None
279
-    # Store the parsed items with the appropriate key in the dictionary object.
280
-    dChan['memloc'] = lchan[0]
281
-    dChan['rxfreq'] = lchan[1]
282
-    dChan['txfreq'] = lchan[2]
283
-    dChan['offset'] = lchan[3]
284
-    dChan['shift'] = lchan[4]
285
-    dChan['mode'] = lchan[5]
286
-    dChan['tag'] = lchan[6]
287
-    dChan['encode'] = lchan[7]
288
-    dChan['tone'] = lchan[8]
289
-    dChan['dcs'] = str(int(lchan[9]))
290
-    dChan['clarfreq'] = lchan[10]
291
-    dChan['rxclar'] = lchan[11]
292
-    dChan['txclar'] = lchan[12]
293
-    return dChan # return the dictionary object
294
-## end def
295
-
296
-# Define serial communications functions.
297
-
298
-def begin(baud=9600):
299
-    """
300
-    Description: Initiates a serial connection the the FT991. Should
301
-                 always be called before sending commands to or
302
-                 receiving data from the FT991.  Only needs to be called
303
-                 once.
304
-    Parameters: none
305
-    Returns: a pointer to the FT991 serial connection
306
-    """
307
-    global ptrDevice
308
-
309
-    # Determine OS type and set device port accordingly.
310
-    OS_type = sys.platform
311
-    if 'WIN' in OS_type.upper():
312
-        port = 'COM5'
313
-    else:
314
-        port = '/dev/ttyUSB0'
315
-
316
-    # In debug mode do not actually send commands to the FT991.
317
-    if debug:
318
-        return
319
-    # Create a FT991 object for serial communication
320
-    try:
321
-        ptrDevice = serial.Serial(port, baud,      
322
-                                  timeout=_INTERFACE_TIMEOUT)
323
-    except Exception, error:
324
-        if str(error).find('could not open port') > -1:
325
-            print 'Please be sure the usb cable is properly connected to\n' \
326
-                  'your FT991 and to your computer, and that the FT991 is\n' \
327
-                  'turned ON.  Then restart this program.'
328
-        else:
329
-            print 'Serial port error: %s\n' % error
330
-        exit(1)         
331
-    time.sleep(.1) # give the connection a moment to settle
332
-    return ptrDevice
333
-## end def
334
-
335
-def sendCommand(sCmd):
336
-    """
337
-    Description: Sends a formatted FT911 command to the communication
338
-                 port connected to the FT991.  Prints to stdout the
339
-                 answer from the FT991 (if any).
340
-    Parameters: device - a pointer to the FT991 comm port
341
-                sCmd - a string containing the formatted command
342
-    Returns: nothing
343
-    """
344
-    # Debug mode in conjunction with verbose mode is for verifying
345
-    # correct formatting of commands before they are actually sent
346
-    # to the FT991.
347
-    if verbose:
348
-        print sCmd,
349
-    # In debug mode do not actually send commands to the FT991.
350
-    if debug:
351
-        return ''
352
-
353
-    # Send the formatted command to the FT991 and get an answer, if any.
354
-    # If the command does not generate an answer, no characters will be
355
-    # returned by the FT991, resulting in an empty string returned by
356
-    # the receiveSerial function.
357
-    sendSerial(sCmd)
358
-    sResult  = receiveSerial();
359
-    if verbose:
360
-        print sResult
361
-    return sResult
362
-## end def
363
-
364
-def receiveSerial(termchar=';'):
365
-    """
366
-    Description: Reads output one character at a time from the device
367
-                 until a terminating character is received.  Returns a     
368
-                 string containing the characters read from the serial
369
-                 port.
370
-    Parameters:  termchar - character terminating the answer string
371
-    Returns: a string containing the received data
372
-    """
373
-    answer = '' # initialize answer string to empty string
374
-    charCount = 0  # reset read character count to zero
375
-
376
-    while True:
377
-        startTime = time.time() # Start read character timer
378
-        c =''
379
-        while True:
380
-            # Check for a character available in the serial read buffer.
381
-            if ptrDevice.in_waiting:
382
-                c = ptrDevice.read()
383
-                break
384
-            # Timeout if a character does not become available.
385
-            if time.time() - startTime > _SERIAL_READ_TIMEOUT:
386
-                break # Character waiting timer has timed out.
387
-        # Return empty string if a character has not become available.
388
-        if c == '':
389
-            break;
390
-        answer += c # Form a string from the received characters.
391
-        charCount += 1 # Increment character count.
392
-        # If a semicolon has arrived then the FT991 has completed
393
-        # sending output to the serial port so stop reading characters.
394
-        # Also stop if max characters received. 
395
-        if c == termchar:
396
-            break
397
-        if charCount > _SERIAL_READ_BUFFER_LENGTH:
398
-            raise Exception('serial read buffer overflow')
399
-    ptrDevice.flushInput() # Flush serial buffer to prevent overflows.
400
-    return answer           
401
-## end def
402
-
403
-def sendSerial(command):
404
-    """
405
-    Description: Writes a string to the device.
406
-    Parameters: command - string containing the FT991 command
407
-    Returns: nothing
408
-    """
409
-    # In debug we only want to see the output of the command formatter,
410
-    # not actually send commands to the FT991.  Debug mode should be
411
-    # used in conjunction with verbose mode.
412
-    ptrDevice.write(command) # Send command string to FT991
413
-    ptrDevice.flushOutput() # Flush serial buffer to prevent overflows
414
-## end def
415
-
416
-def main():
417
-    """
418
-    Description: Place code for testing this module here.
419
-    Parameters: none
420
-    Returns: nothing
421
-    """
422
-    # Test this module.
423
-    global verbose, debug
424
-
425
-    verbose = True
426
-    debug = False
427
-
428
-    begin()
429
-    sendCommand('IF;')
430
-    sendCommand('MC001;')
431
-    sendCommand('ZZZ;')
432
-
433
-    dMem = {'rxfreq': '146.52', 'shift': 'OFF', 'encode': 'OFF', \
434
-        'txclar': 'OFF', 'tag': 'KA7JLO', 'mode': 'FM', 'rxclar': 'OFF', \
435
-        'memloc': '99', 'clarfreq': '0'}
436
-    setMemory(dMem)
437
-    print getMemory(99)
438
-   
439
-## end def
440
-
441
-if __name__ == '__main__':
442
-    main()
Browse code

ft991 utility replaces writeMemory, and passThrough

Gandolf authored on 11/24/2019 23:01:28
Showing 1 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,442 @@
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
+# Module: ft991.py
6
+#
7
+# Description:  This module contains tables for translating common transceiver
8
+#               settings to FT991 CAT parameters.  Low level serial
9
+#               communication functions are also handled by this module.  In
10
+#               particular this module handles:
11
+#                   1. Instantiating a serial connection object
12
+#                   2. Sending character strings to the serial port
13
+#                   3. Reading characters from the serial port
14
+#                   4. Parsing and formatting of FT991 commands
15
+#                   5. Translating radio operating parameters to CAT
16
+#                      commands, i.e., CTCSS tones.  
17
+#
18
+# Copyright 2019 by Jeff Owrey, Intravisions.com
19
+#    This program is free software: you can redistribute it and/or modify
20
+#    it under the terms of the GNU General Public License as published by
21
+#    the Free Software Foundation, either version 3 of the License, or
22
+#    (at your option) any later version.
23
+#
24
+#    This program is distributed in the hope that it will be useful,
25
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
26
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
+#    GNU General Public License for more details.
28
+#
29
+#    You should have received a copy of the GNU General Public Licensef
30
+#    along with this program.  If not, see http://www.gnu.org/license.
31
+#
32
+# Revision History
33
+#   * v10 24 Nov 2019 by J L Owrey; first release
34
+#
35
+# This script has been tested with the following
36
+#
37
+#     Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
38
+#     [GCC 7.3.0] on linux2
39
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
40
+
41
+import sys, serial, time
42
+
43
+# General constant defines
44
+_INTERFACE_TIMEOUT = 0.1 # seconds
45
+_SERIAL_READ_TIMEOUT = 0.1 # seconds
46
+_SERIAL_READ_BUFFER_LENGTH = 1024 # characters
47
+
48
+# Define globals
49
+verbose = False
50
+debug = False
51
+ptrDevice = None
52
+
53
+# Define lookup tables for common transceiver settings.  Common settings
54
+# such as modulation mode, repeater offset direction, DCS/CTCSS mode,
55
+# CTCSS tone, and DCS code are translated to the repective FT991 parameter
56
+# value.
57
+
58
+# Modulation modes
59
+dMode = { 'LSB':'1', 'USB':'2', 'CW':'3', 'FM':'4', 'AM':'5',
60
+          'RTTY-LSB':'6', 'CW-R':'7', 'DATA-LSB':'8', 'RTTY-USB':'9',
61
+          'DATA-FM':'A', 'FM-N':'B', 'DATA-USB':'C', 'AM-N':'D',
62
+          'C4FM':'E' }
63
+
64
+# Repeater shift direction
65
+dShift = { 'OFF':'0', '+RPT':'1', '-RPT':'2' }
66
+
67
+# Power settings
68
+dPower = { 'LOW':5, 'MID':020, 'HIGH':50, 'MAX':100 }
69
+
70
+# Repeater signaling modes
71
+dEncode = { 'OFF':'0', 'ENC/DEC':'1', 'TONE ENC':'2',
72
+            'DCS ENC/DEC':'4', 'DCS':'3' }
73
+
74
+# CTCSS Tones
75
+dTones = { '67.0 Hz':'000', '69.3 Hz':'001', '71.9 Hz':'002',
76
+           '74.4 Hz':'003', '77.0 Hz':'004', '79.7 Hz':'005',
77
+           '82.5 Hz':'006', '85.4 Hz':'007', '88.5 Hz':'008',
78
+           '91.5 Hz':'009', '94.8 Hz':'010', '97.4 Hz':'011',
79
+           '100.0 Hz':'012', '103.5 Hz':'013', '107.2 Hz':'014',
80
+           '110.9 Hz':'015', '114.8 Hz':'016', '118.8 Hz':'017',
81
+           '123.0 Hz':'018', '127.3 Hz':'019', '131.8 Hz':'020',
82
+           '136.5 Hz':'021', '141.3 Hz':'022', '146.2 Hz':'023',
83
+           '151.4 Hz':'024', '156.7 Hz':'025', '159.8 Hz':'026',
84
+           '162.2 Hz':'027', '165.5 Hz':'028', '167.9 Hz':'029',
85
+           '171.3 Hz':'030', '173.8 Hz':'031', '177.3 Hz':'032',
86
+           '179.9 Hz':'033', '183.5 Hz':'034', '186.2 Hz':'035',
87
+           '189.9 Hz':'036', '192.8 Hz':'037', '196.6 Hz':'038',
88
+           '199.5 Hz':'039', '203.5 Hz':'040', '206.5 Hz':'041',
89
+           '210.7 Hz':'042', '218.1 Hz':'043', '225.7 Hz':'044',
90
+           '229.1 Hz':'045', '233.6 Hz':'046', '241.8 Hz':'047',
91
+           '250.3 Hz':'048', '254.1 Hz':'049' } 
92
+
93
+# DCS Tones
94
+dDcs = { '23':'000', '25':'001', '26':'002', '31':'003', '32':'004',
95
+         '36':'005', '43':'006', '47':'007', '51':'008', '53':'009',
96
+         '54':'010', '65':'011', '71':'012', '72':'013', '73':'014',
97
+         '74':'015', '114':'016', '115':'017', '116':'018', '122':'019',
98
+         '125':'020', '131':'021', '132':'022', '134':'023', '143':'024',
99
+         '145':'025', '152':'026', '155':'027', '156':'028', '162':'029',
100
+         '165':'030', '172':'031', '174':'032', '205':'033', '212':'034',
101
+         '223':'035', '225':'036', '226':'037', '243':'038', '244':'039',
102
+         '245':'040', '246':'041', '251':'042', '252':'043', '255':'044',
103
+         '261':'045', '263':'046', '265':'047', '266':'048', '271':'049',
104
+         '274':'050', '306':'051', '311':'052', '315':'053', '325':'054',
105
+         '331':'055', '332':'056', '343':'057', '346':'058', '351':'059',
106
+         '356':'060', '364':'061', '365':'062', '371':'063', '411':'064',
107
+         '412':'065', '413':'066', '423':'067', '431':'068', '432':'069',
108
+         '445':'070', '446':'071', '452':'072', '454':'073', '455':'074',
109
+         '462':'075', '464':'076', '465':'077', '466':'078', '503':'079',
110
+         '506':'080', '516':'081', '523':'082', '526':'083', '532':'084',
111
+         '546':'085', '565':'086', '606':'087', '612':'088', '624':'089',
112
+         '627':'090', '631':'091', '632':'092', '654':'093', '662':'094',
113
+         '664':'095', '703':'096', '712':'097', '723':'098', '731':'099',
114
+         '732':'100', '734':'101', '743':'102', '754':'103' }
115
+
116
+# Clarifier state
117
+dRxClar = { 'OFF':'0', 'ON':'1' }
118
+dTxClar = { 'OFF':'0', 'ON':'1' }
119
+
120
+# Define 'set' functions to encapsulate the various FT991 CAT commands.
121
+
122
+def setMemory(dMem):
123
+    """
124
+    Description: Returns a formatted MT command to the calling function.
125
+    Parameters: dMem - a dictionary objected with the following keys
126
+                       defined:
127
+
128
+                       memloc - the memory location to be written
129
+                       rxfreq - the receive frequency of VFO-A in MHz
130
+                       mode - the modulation mode
131
+                       encode - the tone or DCS encoding mode
132
+                       shift - the direction of the repeater shift
133
+                       tag - a label for the memory location
134
+
135
+    Returns: a string containing the formatted command
136
+    """
137
+    sCmd = 'MC%0.3d;' % int(dMem['memloc'])
138
+    sResult = sendCommand(sCmd)
139
+
140
+    # While the 'MW' and 'MT' commands can be used to turn the Rx
141
+    # and Tx clarifiers on, the clarifier states can only be turned
142
+    # off by sending the 'RT0' and 'XT0' commands.  This situation is
143
+    # probably due to a potential bug in the CAT interface.
144
+    sResult = sendCommand('RC;RT0;XT0;')
145
+
146
+    sCmd = 'MT%0.3d' % int(dMem['memloc'])
147
+    sCmd += '%d' % int(float(dMem['rxfreq']) * 1E6)
148
+    sCmd += '%+0.4d' % int(dMem['clarfreq'])
149
+    sCmd += dRxClar[dMem['rxclar']]
150
+    sCmd += dTxClar[dMem['txclar']]
151
+    sCmd += dMode[dMem['mode']]
152
+    sCmd += '0'
153
+    sCmd += dEncode[dMem['encode']]
154
+    sCmd += '00'
155
+    sCmd += dShift[dMem['shift']]
156
+    sCmd += '0'
157
+    sCmd += '%-12s' % dMem['tag']
158
+    sCmd += ';'
159
+    sResult = sendCommand(sCmd)
160
+    return sResult
161
+## end def
162
+
163
+def getMemory(memLoc):
164
+    """
165
+    Description: 
166
+    Parameters: 
167
+    Returns: 
168
+    """
169
+    dMem = {}
170
+
171
+    # Set memory location pointer in FT991.  This is done
172
+    # by sending the memory location select (MC) command.
173
+    sCmd = 'MC%0.3d;' % (memLoc)
174
+    sResult = sendCommand(sCmd)
175
+    # Skip blank memory locations, which return '?;'.
176
+    if sResult == '?;':
177
+        return None
178
+
179
+    # Send the get memory settings string to the FT991.
180
+    sCmd = 'MT%0.3d;' % (memLoc)
181
+    sResult = sendCommand(sCmd)
182
+
183
+    # Parse memory settings string returned by the FT991
184
+    memloc = sResult[2:5]
185
+    rxfreq = sResult[5:14]
186
+    clarfreq = sResult[14:19]
187
+    rxclar = sResult[19]
188
+    txclar = sResult[20]
189
+    mode = sResult[21]
190
+    encode = sResult[23]
191
+    shift = sResult[26]
192
+    tag = sResult[28:40]
193
+
194
+    # Store the memory settings in a dictionary object.
195
+    dMem['memloc'] = str(int(memloc))
196
+    dMem['rxfreq'] = str(float(rxfreq) / 10**6)
197
+    dMem['clarfreq'] = str(int(clarfreq))
198
+    dMem['rxclar'] = dRxClar.keys()[dRxClar.values().index(rxclar)]
199
+    dMem['txclar'] = dTxClar.keys()[dTxClar.values().index(txclar)]
200
+    dMem['mode'] = dMode.keys()[dMode.values().index(mode)]
201
+    dMem['encode'] = dEncode.keys()[dEncode.values().index(encode)]
202
+    dMem['shift'] = dShift.keys()[dShift.values().index(shift)]
203
+    dMem['tag'] = tag.strip()
204
+
205
+    return dMem
206
+## end def
207
+
208
+def getCTCSS():
209
+    """
210
+    Description: 
211
+    Parameters: 
212
+    Returns: 
213
+    """
214
+    # Get result CTCSS tone
215
+    sResult = sendCommand('CN00;')
216
+    tone = sResult[4:7]
217
+    return dTones.keys()[dTones.values().index(tone)]
218
+## end def
219
+
220
+def getDCS():
221
+    """
222
+    Description: 
223
+    Parameters: 
224
+    Returns: 
225
+    """
226
+    # Get result of CN01 command
227
+    sResult = sendCommand('CN01;')
228
+    dcs = sResult[4:7]
229
+    return dDcs.keys()[dDcs.values().index(dcs)]
230
+## end def
231
+
232
+def setCTCSS(tone):
233
+    """
234
+    Description:  returns a formatted CN command that sets the desired
235
+                  CTCSS tone.
236
+    Parameters:   tone - a string containing the CTCSS tone in Hz, e.g.,
237
+                         '100 Hz'
238
+    Returns: a string containing the formatted command
239
+    """
240
+    sCmd = 'CN00%s;' % dTones[tone]
241
+    return sendCommand(sCmd)
242
+## end def
243
+
244
+def setDCS(code):
245
+    """
246
+    Description:  returns a formatted CN command that sets the desired
247
+                  DCS code.
248
+    Parameters:   code - a string containing the DCS code, e.g., '23'
249
+    Returns: a string containing the formatted command
250
+    """
251
+    sCmd = 'CN01%s;' % dDcs[code]
252
+    return sendCommand(sCmd)
253
+## end def
254
+
255
+def setPower(power):
256
+    """
257
+    Description:  returns a formatted PC command that sets the desired
258
+                  RF transmit power level.
259
+    Parameters:   power - Watts, an integer between 5 and 100
260
+    Returns: a string containing the formatted command
261
+    """
262
+    sCmd = 'PC'
263
+    sCmd += '%03.d;' % power 
264
+    return sendCommand(sCmd)
265
+## end def
266
+
267
+def parseCsvData(sline):
268
+    """
269
+    Description:  stores each item in the comma delimited line in a single
270
+                  dictionary object using a key appropriate for that item.
271
+    Parameters: a string containing the comma delimited items to be parsed.
272
+    Returns: a dictionary object containing the parsed line.
273
+    """
274
+    dChan = {} # define an empty dictionary object
275
+    lchan = sline.split(',') # split the line at the commas
276
+    # If the first line is a header line, ignore it.
277
+    if not lchan[0].isdigit():
278
+        return None
279
+    # Store the parsed items with the appropriate key in the dictionary object.
280
+    dChan['memloc'] = lchan[0]
281
+    dChan['rxfreq'] = lchan[1]
282
+    dChan['txfreq'] = lchan[2]
283
+    dChan['offset'] = lchan[3]
284
+    dChan['shift'] = lchan[4]
285
+    dChan['mode'] = lchan[5]
286
+    dChan['tag'] = lchan[6]
287
+    dChan['encode'] = lchan[7]
288
+    dChan['tone'] = lchan[8]
289
+    dChan['dcs'] = str(int(lchan[9]))
290
+    dChan['clarfreq'] = lchan[10]
291
+    dChan['rxclar'] = lchan[11]
292
+    dChan['txclar'] = lchan[12]
293
+    return dChan # return the dictionary object
294
+## end def
295
+
296
+# Define serial communications functions.
297
+
298
+def begin(baud=9600):
299
+    """
300
+    Description: Initiates a serial connection the the FT991. Should
301
+                 always be called before sending commands to or
302
+                 receiving data from the FT991.  Only needs to be called
303
+                 once.
304
+    Parameters: none
305
+    Returns: a pointer to the FT991 serial connection
306
+    """
307
+    global ptrDevice
308
+
309
+    # Determine OS type and set device port accordingly.
310
+    OS_type = sys.platform
311
+    if 'WIN' in OS_type.upper():
312
+        port = 'COM5'
313
+    else:
314
+        port = '/dev/ttyUSB0'
315
+
316
+    # In debug mode do not actually send commands to the FT991.
317
+    if debug:
318
+        return
319
+    # Create a FT991 object for serial communication
320
+    try:
321
+        ptrDevice = serial.Serial(port, baud,      
322
+                                  timeout=_INTERFACE_TIMEOUT)
323
+    except Exception, error:
324
+        if str(error).find('could not open port') > -1:
325
+            print 'Please be sure the usb cable is properly connected to\n' \
326
+                  'your FT991 and to your computer, and that the FT991 is\n' \
327
+                  'turned ON.  Then restart this program.'
328
+        else:
329
+            print 'Serial port error: %s\n' % error
330
+        exit(1)         
331
+    time.sleep(.1) # give the connection a moment to settle
332
+    return ptrDevice
333
+## end def
334
+
335
+def sendCommand(sCmd):
336
+    """
337
+    Description: Sends a formatted FT911 command to the communication
338
+                 port connected to the FT991.  Prints to stdout the
339
+                 answer from the FT991 (if any).
340
+    Parameters: device - a pointer to the FT991 comm port
341
+                sCmd - a string containing the formatted command
342
+    Returns: nothing
343
+    """
344
+    # Debug mode in conjunction with verbose mode is for verifying
345
+    # correct formatting of commands before they are actually sent
346
+    # to the FT991.
347
+    if verbose:
348
+        print sCmd,
349
+    # In debug mode do not actually send commands to the FT991.
350
+    if debug:
351
+        return ''
352
+
353
+    # Send the formatted command to the FT991 and get an answer, if any.
354
+    # If the command does not generate an answer, no characters will be
355
+    # returned by the FT991, resulting in an empty string returned by
356
+    # the receiveSerial function.
357
+    sendSerial(sCmd)
358
+    sResult  = receiveSerial();
359
+    if verbose:
360
+        print sResult
361
+    return sResult
362
+## end def
363
+
364
+def receiveSerial(termchar=';'):
365
+    """
366
+    Description: Reads output one character at a time from the device
367
+                 until a terminating character is received.  Returns a     
368
+                 string containing the characters read from the serial
369
+                 port.
370
+    Parameters:  termchar - character terminating the answer string
371
+    Returns: a string containing the received data
372
+    """
373
+    answer = '' # initialize answer string to empty string
374
+    charCount = 0  # reset read character count to zero
375
+
376
+    while True:
377
+        startTime = time.time() # Start read character timer
378
+        c =''
379
+        while True:
380
+            # Check for a character available in the serial read buffer.
381
+            if ptrDevice.in_waiting:
382
+                c = ptrDevice.read()
383
+                break
384
+            # Timeout if a character does not become available.
385
+            if time.time() - startTime > _SERIAL_READ_TIMEOUT:
386
+                break # Character waiting timer has timed out.
387
+        # Return empty string if a character has not become available.
388
+        if c == '':
389
+            break;
390
+        answer += c # Form a string from the received characters.
391
+        charCount += 1 # Increment character count.
392
+        # If a semicolon has arrived then the FT991 has completed
393
+        # sending output to the serial port so stop reading characters.
394
+        # Also stop if max characters received. 
395
+        if c == termchar:
396
+            break
397
+        if charCount > _SERIAL_READ_BUFFER_LENGTH:
398
+            raise Exception('serial read buffer overflow')
399
+    ptrDevice.flushInput() # Flush serial buffer to prevent overflows.
400
+    return answer           
401
+## end def
402
+
403
+def sendSerial(command):
404
+    """
405
+    Description: Writes a string to the device.
406
+    Parameters: command - string containing the FT991 command
407
+    Returns: nothing
408
+    """
409
+    # In debug we only want to see the output of the command formatter,
410
+    # not actually send commands to the FT991.  Debug mode should be
411
+    # used in conjunction with verbose mode.
412
+    ptrDevice.write(command) # Send command string to FT991
413
+    ptrDevice.flushOutput() # Flush serial buffer to prevent overflows
414
+## end def
415
+
416
+def main():
417
+    """
418
+    Description: Place code for testing this module here.
419
+    Parameters: none
420
+    Returns: nothing
421
+    """
422
+    # Test this module.
423
+    global verbose, debug
424
+
425
+    verbose = True
426
+    debug = False
427
+
428
+    begin()
429
+    sendCommand('IF;')
430
+    sendCommand('MC001;')
431
+    sendCommand('ZZZ;')
432
+
433
+    dMem = {'rxfreq': '146.52', 'shift': 'OFF', 'encode': 'OFF', \
434
+        'txclar': 'OFF', 'tag': 'KA7JLO', 'mode': 'FM', 'rxclar': 'OFF', \
435
+        'memloc': '99', 'clarfreq': '0'}
436
+    setMemory(dMem)
437
+    print getMemory(99)
438
+   
439
+## end def
440
+
441
+if __name__ == '__main__':
442
+    main()