Browse code

all memory

gandolf authored on 02/01/2020 05:02:27
Showing 1 changed files
... ...
@@ -42,6 +42,7 @@ _DEFAULT_MENU_SETTINGS_FILE = 'ft991menu.cfg'
42 42
 _DEFAULT_MEMORY_SETTINGS_FILE = 'ft991mem.csv'
43 43
 _MAX_NUMBER_OF_MENU_ITEMS = 154
44 44
 _MAX_NUMBER_OF_MEMORY_ITEMS = 118
45
+_DEBUG = False
45 46
 
46 47
 # Global definitions
47 48
 
... ...
@@ -262,9 +263,12 @@ def readMemorySettings():
262 263
              contained in the list.
263 264
     """
264 265
     # Define the column headers as the first item in the list.
265
-    lSettings = [ 'Memory Ch,Rx Frequency,Tx Frequency,Offset,' \
266
-                        'Repeater Shift,Mode,Tag,Encoding,Tone,DCS,' \
267
-                        'Clarifier,RxClar,TxClar' ]
266
+    lSettings = [ 'Memory Ch,VFO_A,VFO_B,' \
267
+                  'RepeaterShift,Mode,Tag,Encoding,Tone,DCS,' \
268
+                  'Clarifier,RxClar,TxClar,PreAmp,RfAttn,NB,IFshift,' \
269
+                  'IFwidthIndex,ContourState,ContourFreq,' \
270
+                  'APFstate,APFfreq,DNRstate,DNRalgorithm,DNFstate,' \
271
+                  'NBFstate,NotchState,NotchFreq' ]
268 272
 
269 273
     for iLocation in range(1, _MAX_NUMBER_OF_MEMORY_ITEMS):
270 274
 
... ...
@@ -280,14 +284,31 @@ def readMemorySettings():
280 284
         dMem = ft991.getMemory(iLocation)
281 285
         tone = ft991.getCTCSS()
282 286
         dcs = ft991.getDCS()
287
+        preamp = ft991.getPreamp()
288
+        rfattn = ft991.getRfAttn()
289
+        nblkr = ft991.getNoiseBlanker()
290
+        shift = ft991.getIFshift()
291
+        width = ft991.getIFwidth()
292
+        lContour = ft991.getContour()
293
+        dnrstate = ft991.getDNRstate()
294
+        dnralgorithm = ft991.getDNRalgorithm()
295
+        dnfstate = ft991.getDNFstate()
296
+        narstate = ft991.getNARstate()
297
+        notch = ft991.getNotchState()
283 298
         # getMemory, above, stores data in a dictionary object.  Format
284 299
         # the data in this object, as well as, the DCS code and CTCSS
285 300
         # tone into a comma-delimited string.
286
-        sCsvFormat = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % \
287
-               ( dMem['memloc'], dMem['rxfreq'], '', '', \
288
-                 dMem['shift'], dMem['mode'], dMem['tag'], dMem['encode'], \
301
+        sCsvFormat = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,' \
302
+                     '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % \
303
+               (
304
+                 dMem['memloc'], dMem['vfoa'], '', \
305
+                 dMem['rpoffset'], dMem['mode'], dMem['tag'], dMem['encode'], \
289 306
                  tone, dcs, dMem['clarfreq'], dMem['rxclar'], \
290
-                 dMem['txclar'] )
307
+                 dMem['txclar'], preamp, rfattn, nblkr, shift, width, \
308
+                 lContour[0], lContour[1], lContour[2], lContour[3], \
309
+                 dnrstate, dnralgorithm, dnfstate, narstate, notch[0],
310
+                 notch[1]
311
+               )
291 312
         # Add the comma-delimited string to the list object.
292 313
         lSettings.append(sCsvFormat)
293 314
     return lSettings
... ...
@@ -301,13 +322,14 @@ def writeMemorySettings(lSettings):
301 322
     Returns: nothing
302 323
     """
303 324
     for item in lSettings:
304
-        # Parse the comma-delimited line and store in a dictionary object.
305
-        dItem = ft991.parseCsvData(item)
306
-        # The first item in the memory settings list are the column headers;
307
-        # so ignore this item.  (parseData returns None for this item.)
308
-        if dItem == None:
309
-            continue
310 325
         try:
326
+            # Parse the comma-delimited line and store in a dictionary object.
327
+            dItem = ft991.parseCsvData(item)
328
+            # The first item in the memory settings list are the
329
+            # column headers, so ignore this item.  (parseData returns
330
+            # None for this item.)
331
+            if dItem == None:
332
+                continue
311 333
             # Set the parameters for the memory location.
312 334
             ft991.setMemory(dItem)
313 335
             # Set current channel to memory location being set.
... ...
@@ -322,13 +344,36 @@ def writeMemorySettings(lSettings):
322 344
             # off by sending the 'RT0' and 'XT0' commands.  This situation
323 345
             # is probably due to a bug in the CAT interface.
324 346
             ft991.setRxClarifier(dItem['rxclar'])
325
-            ft991.setTxClarifier(dItem['txclar'])            
347
+            ft991.setTxClarifier(dItem['txclar'])
348
+            # Set Rx preamplifier state
349
+            ft991.setPreamp(dItem['preamp'])
350
+            # Set RF attenuator state
351
+            ft991.setRfAttn(dItem['rfattn'])
352
+            # Set Noise Blanker state
353
+            ft991.setNoiseBlanker(dItem['nblkr'])
354
+            # Set IF shift amount
355
+            ft991.setIFshift(dItem['shift'])
356
+            # Set IF width index
357
+            ft991.setIFwidth(dItem['width'])
358
+            # Set Contour parameters
359
+            ft991.setContour(dItem['contour'])
360
+            # Set DNR state and algorithm
361
+            ft991.setDNRstate(dItem['dnrstate'])
362
+            ft991.setDNRalgorithm(dItem['dnralgorithm'])
363
+            # Set DNF state
364
+            ft991.setDNFstate(dItem['dnfstate'])
365
+            # Set NAR state
366
+            ft991.setNARstate(dItem['narstate'])
367
+            # Set Notch state
368
+            ft991.setNotchState((dItem['notchstate'], dItem['notchfreq']))
326 369
         except Exception, e:
327 370
             print 'Memory settings restore operation failed. Most likely\n' \
328 371
                   'this is due to the backup settings file corrupted or\n' \
329 372
                   'incorrectly formatted. Look for the following error: \n'
330 373
             print e
331
-            exit(1)
374
+            raise
375
+            #exit(1)
376
+    # end for
332 377
 ## end def
333 378
 
334 379
 def readMenuSettings():
Browse code

fix writeMemorySettings bug

Gandolf authored on 11/30/2019 05:18:16
Showing 1 changed files
... ...
@@ -308,10 +308,10 @@ def writeMemorySettings(lSettings):
308 308
         if dItem == None:
309 309
             continue
310 310
         try:
311
-            # Set current channel to memory location being set.
312
-            ft991.setMemoryLocation(int(dItem['memloc']))
313 311
             # Set the parameters for the memory location.
314 312
             ft991.setMemory(dItem)
313
+            # Set current channel to memory location being set.
314
+            ft991.setMemoryLocation(int(dItem['memloc']))
315 315
             # Set CTCSS tone for memory channel.
316 316
             ft991.setCTCSS(dItem['tone'])
317 317
             # Set DCS code for memory channel. 
Browse code

fixed but in read memory settings

Gandolf authored on 11/30/2019 04:34:27
Showing 1 changed files
... ...
@@ -264,28 +264,26 @@ def readMemorySettings():
264 264
     # Define the column headers as the first item in the list.
265 265
     lSettings = [ 'Memory Ch,Rx Frequency,Tx Frequency,Offset,' \
266 266
                         'Repeater Shift,Mode,Tag,Encoding,Tone,DCS,' \
267
-                        'Clarifier, RxClar, TxClar' ]
267
+                        'Clarifier,RxClar,TxClar' ]
268 268
 
269 269
     for iLocation in range(1, _MAX_NUMBER_OF_MEMORY_ITEMS):
270 270
 
271
+        # If a memory location is empty (has not been programmed or has
272
+        # been erased), skip that location and do not create a list entry
273
+        # for that location.
274
+        if ft991.setMemoryLocation(iLocation) == None:
275
+            continue
271 276
         # For each memory location get the memory contents.  Note that
272 277
         # several CAT commands are required to get the entire contents
273 278
         # of a memory location.  Specifically, additional commands are
274 279
         # required to get DCS code and CTCSS tone.
275 280
         dMem = ft991.getMemory(iLocation)
276
-        # If a memory location is empty (has not been programmed or has
277
-        # been erased), do not created a list entry for that location.
278
-        if dMem == None:
279
-            continue
280
-        # Set current memory location to the channel being set.
281
-        sResult = ft991.setMemoryLocation(iLocation)
282
-        # Get DCS and CTCSS.
283 281
         tone = ft991.getCTCSS()
284 282
         dcs = ft991.getDCS()
285 283
         # getMemory, above, stores data in a dictionary object.  Format
286 284
         # the data in this object, as well as, the DCS code and CTCSS
287 285
         # tone into a comma-delimited string.
288
-        sCsvFormat = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,' % \
286
+        sCsvFormat = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % \
289 287
                ( dMem['memloc'], dMem['rxfreq'], '', '', \
290 288
                  dMem['shift'], dMem['mode'], dMem['tag'], dMem['encode'], \
291 289
                  tone, dcs, dMem['clarfreq'], dMem['rxclar'], \
... ...
@@ -310,10 +308,10 @@ def writeMemorySettings(lSettings):
310 308
         if dItem == None:
311 309
             continue
312 310
         try:
313
-            # Set the parameters for the memory location.
314
-            ft991.setMemory(dItem)
315 311
             # Set current channel to memory location being set.
316 312
             ft991.setMemoryLocation(int(dItem['memloc']))
313
+            # Set the parameters for the memory location.
314
+            ft991.setMemory(dItem)
317 315
             # Set CTCSS tone for memory channel.
318 316
             ft991.setCTCSS(dItem['tone'])
319 317
             # Set DCS code for memory channel. 
... ...
@@ -520,7 +518,7 @@ def main():
520 518
 
521 519
     # Else enter user interactive mode.
522 520
     printMenuSplash()
523
-    while(1):
521
+    while(True):
524 522
         doUserCommand()
525 523
 ## end def
526 524
 
Browse code

set methods now upon errors raise exceptions

Gandolf authored on 11/28/2019 21:39:18
Showing 1 changed files
... ...
@@ -266,16 +266,19 @@ def readMemorySettings():
266 266
                         'Repeater Shift,Mode,Tag,Encoding,Tone,DCS,' \
267 267
                         'Clarifier, RxClar, TxClar' ]
268 268
 
269
-    for memoryLocation in range(1, _MAX_NUMBER_OF_MEMORY_ITEMS):
269
+    for iLocation in range(1, _MAX_NUMBER_OF_MEMORY_ITEMS):
270
+
270 271
         # For each memory location get the memory contents.  Note that
271 272
         # several CAT commands are required to get the entire contents
272 273
         # of a memory location.  Specifically, additional commands are
273 274
         # required to get DCS code and CTCSS tone.
274
-        dMem = ft991.getMemory(memoryLocation)
275
+        dMem = ft991.getMemory(iLocation)
275 276
         # If a memory location is empty (has not been programmed or has
276 277
         # been erased), do not created a list entry for that location.
277 278
         if dMem == None:
278 279
             continue
280
+        # Set current memory location to the channel being set.
281
+        sResult = ft991.setMemoryLocation(iLocation)
279 282
         # Get DCS and CTCSS.
280 283
         tone = ft991.getCTCSS()
281 284
         dcs = ft991.getDCS()
... ...
@@ -289,8 +292,6 @@ def readMemorySettings():
289 292
                  dMem['txclar'] )
290 293
         # Add the comma-delimited string to the list object.
291 294
         lSettings.append(sCsvFormat)
292
-        if ft991.verbose:
293
-            print
294 295
     return lSettings
295 296
 # end def
296 297
 
... ...
@@ -308,25 +309,28 @@ def writeMemorySettings(lSettings):
308 309
         # so ignore this item.  (parseData returns None for this item.)
309 310
         if dItem == None:
310 311
             continue
311
-        sResult = ''
312 312
         try:
313
-            # Set memory channel vfo, mode, and other data.
314
-            sResult += ft991.setMemory(dItem)
313
+            # Set the parameters for the memory location.
314
+            ft991.setMemory(dItem)
315
+            # Set current channel to memory location being set.
316
+            ft991.setMemoryLocation(int(dItem['memloc']))
315 317
             # Set CTCSS tone for memory channel.
316
-            sResult += ft991.setCTCSS(dItem['tone'])
318
+            ft991.setCTCSS(dItem['tone'])
317 319
             # Set DCS code for memory channel. 
318
-            sResult += ft991.setDCS(dItem['dcs'])
320
+            ft991.setDCS(dItem['dcs'])
321
+            # Set clarifier mode.  Note that
322
+            # while the 'MW' and 'MT' commands can be used to turn the Rx
323
+            # and Tx clarifiers on, the clarifier states can only be turned
324
+            # off by sending the 'RT0' and 'XT0' commands.  This situation
325
+            # is probably due to a bug in the CAT interface.
326
+            ft991.setRxClarifier(dItem['rxclar'])
327
+            ft991.setTxClarifier(dItem['txclar'])            
319 328
         except Exception, e:
320
-            print 'Backup settings file corrupted or incorrectly formatted.\n' \
321
-                  'Please make sure all values are correctly entered.'
329
+            print 'Memory settings restore operation failed. Most likely\n' \
330
+                  'this is due to the backup settings file corrupted or\n' \
331
+                  'incorrectly formatted. Look for the following error: \n'
322 332
             print e
323 333
             exit(1)
324
-
325
-        # Process any errors returned by the CAT interface.
326
-        if sResult.find('?;') > -1:
327
-            print 'Error restoring memory setting: %s' % sResult
328
-        if ft991.verbose:
329
-            print
330 334
 ## end def
331 335
 
332 336
 def readMenuSettings():
Browse code

minor documentation errors corrected and data validation added

Gandolf authored on 11/25/2019 20:49:16
Showing 1 changed files
... ...
@@ -241,52 +241,13 @@ def getFileName(defaultFile):
241 241
     if commandLineOption != '':
242 242
         return defaultFile
243 243
     # Otherwise query the user for a file name
244
-    fileName = raw_input("Enter file name or <CR> for default: ")
244
+    fileName = raw_input('Enter file name or <CR> for default: ')
245 245
     if fileName == '':
246 246
         return defaultFile
247 247
     else:
248 248
         return fileName
249 249
 ## end def
250 250
 
251
-def readMenuSettings():
252
-    """
253
-    Description: Reads all menu settings from the FT991.
254
-    Parameters: none
255
-    Returns: a list object containing all the menu settings
256
-    """
257
-    lMenuSettings = []
258
-    # Iterate through all menu items, getting each setting and storing
259
-    # the setting in a file.
260
-    for inx in range(1, _MAX_NUMBER_OF_MENU_ITEMS):
261
-        # Format the read menu item CAT command.
262
-        sCommand = 'EX%0.3d;' % inx
263
-        # Send the command to the FT991.
264
-        sResult = ft991.sendCommand(sCommand)
265
-        # Add the menu setting to a list object.
266
-        lMenuSettings.append(sResult)
267
-    return lMenuSettings
268
-## end def
269
-
270
-def writeMenuSettings(lMenuSettings):
271
-    """
272
-    Description: Writes supplied menu settings to the FT991.
273
-    Parameters: lMenuSettings - a list object containing menu settings
274
-    Returns: nothing
275
-    """
276
-    for item in lMenuSettings:
277
-
278
-        # Do not write read-only menu settings as this results
279
-        # in the FT-991 returning an error.  The only read-only
280
-        # setting is the "Radio ID" setting.
281
-        if item.find('EX087') > -1:
282
-            continue;
283
-        # Send the pre-formatted menu setting to the FT991.
284
-        sResult = ft991.sendCommand(item)
285
-        if sResult.find('?;') > -1:
286
-            print 'error restoring menu setting: %s' % item
287
-            exit(1)
288
-## end def
289
-
290 251
 def readMemorySettings():
291 252
     """
292 253
     Description: Reads all defined memory settings from the FT991.  The
... ...
@@ -301,7 +262,7 @@ def readMemorySettings():
301 262
              contained in the list.
302 263
     """
303 264
     # Define the column headers as the first item in the list.
304
-    lMemorySettings = [ 'Memory Ch,Rx Frequency,Tx Frequency,Offset,' \
265
+    lSettings = [ 'Memory Ch,Rx Frequency,Tx Frequency,Offset,' \
305 266
                         'Repeater Shift,Mode,Tag,Encoding,Tone,DCS,' \
306 267
                         'Clarifier, RxClar, TxClar' ]
307 268
 
... ...
@@ -327,40 +288,86 @@ def readMemorySettings():
327 288
                  tone, dcs, dMem['clarfreq'], dMem['rxclar'], \
328 289
                  dMem['txclar'] )
329 290
         # Add the comma-delimited string to the list object.
330
-        lMemorySettings.append(sCsvFormat)
291
+        lSettings.append(sCsvFormat)
331 292
         if ft991.verbose:
332 293
             print
333
-    return lMemorySettings
294
+    return lSettings
334 295
 # end def
335 296
 
336
-def writeMemorySettings(lMemorySettings):
297
+def writeMemorySettings(lSettings):
337 298
     """
338 299
     Description: Writes the supplied memory settings to the FT991.
339
-    Parameters: lMemorySettings - a list object containing the memory
300
+    Parameters: lSettings - a list object containing the memory
340 301
                                   settings in comma delimited format
341 302
     Returns: nothing
342 303
     """
343
-    for item in lMemorySettings:
304
+    for item in lSettings:
344 305
         # Parse the comma-delimited line and store in a dictionary object.
345 306
         dItem = ft991.parseCsvData(item)
346 307
         # The first item in the memory settings list are the column headers;
347 308
         # so ignore this item.  (parseData returns None for this item.)
348 309
         if dItem == None:
349 310
             continue
350
-        # Set memory channel vfo, mode, and other data.
351 311
         sResult = ''
352
-        sResult += ft991.setMemory(dItem)
353
-        # Set CTCSS tone for memory channel.
354
-        sResult += ft991.setCTCSS(dItem['tone'])
355
-        # Set DCS code for memory channel. 
356
-        sResult += ft991.setDCS(dItem['dcs'])
312
+        try:
313
+            # Set memory channel vfo, mode, and other data.
314
+            sResult += ft991.setMemory(dItem)
315
+            # Set CTCSS tone for memory channel.
316
+            sResult += ft991.setCTCSS(dItem['tone'])
317
+            # Set DCS code for memory channel. 
318
+            sResult += ft991.setDCS(dItem['dcs'])
319
+        except Exception, e:
320
+            print 'Backup settings file corrupted or incorrectly formatted.\n' \
321
+                  'Please make sure all values are correctly entered.'
322
+            print e
323
+            exit(1)
324
+
357 325
         # Process any errors returned by the CAT interface.
358 326
         if sResult.find('?;') > -1:
359
-            print 'error restoring memory setting: %s' % sResult
327
+            print 'Error restoring memory setting: %s' % sResult
360 328
         if ft991.verbose:
361 329
             print
362 330
 ## end def
363 331
 
332
+def readMenuSettings():
333
+    """
334
+    Description: Reads all menu settings from the FT991.
335
+    Parameters: none
336
+    Returns: a list object containing all the menu settings
337
+    """
338
+    lSettings = []
339
+    # Iterate through all menu items, getting each setting and storing
340
+    # the setting in a file.
341
+    for inx in range(1, _MAX_NUMBER_OF_MENU_ITEMS):
342
+        # Format the read menu item CAT command.
343
+        sCommand = 'EX%0.3d;' % inx
344
+        # Send the command to the FT991.
345
+        sResult = ft991.sendCommand(sCommand)
346
+        # Add the menu setting to a list object.
347
+        lSettings.append(sResult)
348
+    return lSettings
349
+## end def
350
+
351
+def writeMenuSettings(lSettings):
352
+    """
353
+    Description: Writes supplied menu settings to the FT991.
354
+    Parameters: lSettings - a list object containing menu settings
355
+    Returns: nothing
356
+    """
357
+    for item in lSettings:
358
+
359
+        # Do not write read-only menu settings as this results
360
+        # in the FT-991 returning an error.  The only read-only
361
+        # setting is the "Radio ID" setting.
362
+        if item.find('EX087') > -1:
363
+            continue;
364
+        # Send the pre-formatted menu setting to the FT991.
365
+        sResult = ft991.sendCommand(item)
366
+        if sResult.find('?;') > -1:
367
+            print 'error restoring menu setting: %s' % item
368
+            exit(1)
369
+## end def
370
+
364 371
 def writeToFile(lSettings, fileName):
365 372
     """
366 373
     Description: Writes supplied settings to the specified file.
... ...
@@ -398,8 +405,8 @@ def setIOFile(option, commandLineFile):
398 405
                  well as the command executed by doUserCommand above. 
399 406
     Parameters: option - the command supplied by the command line
400 407
                          argument interpreter getCLarguments.
401
-                commandLineFile - the name of the file supplied by the
402
-                          -f command line argument (if supplied). 
408
+                commandLineFile - the name of the file supplied by the -f
409
+                         command line argument (if supplied). 
403 410
     Returns: nothing
404 411
     """
405 412
     global menuBackupFile, memoryBackupFile, commandLineOption
... ...
@@ -436,7 +443,7 @@ x - exit this program
436 443
 ## end def
437 444
 
438 445
 def getCLarguments():
439
-    """ Description: gets command line arguments and configures this program
446
+    """ Description: Gets command line arguments and configures this program
440 447
                      to run accordingly.  See the variable 'usage', below,
441 448
                      for possible arguments that may be used on the command
442 449
                      line.
Browse code

add Readme file

Gandolf authored on 11/25/2019 07:15:18
Showing 1 changed files
... ...
@@ -422,7 +422,7 @@ def printMenuSplash():
422 422
     """
423 423
     splash = \
424 424
 """
425
-Enter one of the following commands:
425
+Enter a command from the list below:
426 426
 m - show this menu
427 427
 bm - backup memory to file
428 428
 rm - restore memory from file
Browse code

reorganize repository

Gandolf authored on 11/25/2019 06:37:12
Showing 1 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,517 @@
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: ft991utility.py
6
+#
7
+# Description:  A utility for backing up Yaesu FT991 memory and menu settings,
8
+#               and also for restoring memory and menu settings from
9
+#               a file.  Can be used both interactively or with command line
10
+#               arguments. Before running this utility, be sure to copy
11
+#               the file 'ft911.py' to the same folder as this utility.
12
+#
13
+# Copyright 2019 by Jeff Owrey, Intravisions.com
14
+#    This program is free software: you can redistribute it and/or modify
15
+#    it under the terms of the GNU General Public License as published by
16
+#    the Free Software Foundation, either version 3 of the License, or
17
+#    (at your option) any later version.
18
+#
19
+#    This program is distributed in the hope that it will be useful,
20
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
+#    GNU General Public License for more details.
23
+#
24
+#    You should have received a copy of the GNU General Public Licensef
25
+#    along with this program.  If not, see http://www.gnu.org/license.
26
+#
27
+# Revision History
28
+#   * v10 23 Nov 2019 by J L Owrey; first release
29
+#
30
+# This script has been tested with the following
31
+#
32
+#     Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
33
+#     [GCC 7.3.0] on linux2
34
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
35
+
36
+import os, sys, serial, time
37
+import ft991 # module should be in same directory as this utility
38
+
39
+# Constant definitions
40
+
41
+_DEFAULT_MENU_SETTINGS_FILE = 'ft991menu.cfg'
42
+_DEFAULT_MEMORY_SETTINGS_FILE = 'ft991mem.csv'
43
+_MAX_NUMBER_OF_MENU_ITEMS = 154
44
+_MAX_NUMBER_OF_MEMORY_ITEMS = 118
45
+
46
+# Global definitions
47
+
48
+menuBackupFile = _DEFAULT_MENU_SETTINGS_FILE
49
+memoryBackupFile = _DEFAULT_MEMORY_SETTINGS_FILE
50
+commandLineOption = ''
51
+
52
+# Command processing functions
53
+
54
+def doUserCommand():
55
+    """
56
+    Description: Provides an interactive user interface where the user can
57
+                 enter simple text commands at a command line prompt.  The
58
+                 commands that a user can enter are listed in a menu splash
59
+                 that the user can display anytime with the 'm' command.
60
+                 Also this function processes commands provided as command
61
+                 line options.  In the case of a command line option this
62
+                 function operates non-interactively.
63
+    Parameters: none
64
+    Returns: nothing
65
+    """
66
+    # When command line arguments have not been provided,
67
+    # use interactive mode and give the user a prompt.
68
+    if commandLineOption == '':
69
+        cmd = raw_input('>').strip()
70
+    # If command line arguments have been provided, then
71
+    # execute the command non-interactively.
72
+    else:
73
+        cmd = commandLineOption
74
+
75
+    # Process the user command.   
76
+    if cmd == '':
77
+        return
78
+    elif cmd == 'm':
79
+        printMenuSplash()
80
+    elif cmd == 'bu':
81
+        backupMenuSettings()
82
+    elif cmd == 'ru':
83
+        restoreMenuSettings()
84
+    elif cmd == 'bm':
85
+        backupMemorySettings()
86
+    elif cmd == 'rm':
87
+        restoreMemorySettings()
88
+    elif cmd == 'p':
89
+        passThroughMode()
90
+    elif cmd == 'v':
91
+        toggleVerboseMode()
92
+    elif cmd == 'x':
93
+        exit(0)
94
+    else:
95
+        print "invalid command"
96
+## end def
97
+
98
+def backupMemorySettings():
99
+    """
100
+    Description: Backs up all memory settings to a file.  The user has the
101
+                 option of providing a file name or accepting a default
102
+                 file name.
103
+    Parameters: none
104
+    Returns: nothing
105
+    """
106
+    # Prompt the user for a file name in which to store
107
+    # backed up memory settings.
108
+    fileName = getFileName(memoryBackupFile)
109
+    # Read the memory settings from the FT991...
110
+    print 'Backing up memory settings...'
111
+    settings = readMemorySettings()
112
+    # and write them to the file.
113
+    writeToFile(settings, fileName)
114
+    print 'Memory settings backed up to \'%s\'' % fileName
115
+## end def
116
+
117
+def restoreMemorySettings():
118
+    """
119
+    Description: Restores all memory settings from a file.  The user has the
120
+                 option of providing a file name or accepting a default
121
+                 file name.
122
+    Parameters: none
123
+    Returns: nothing
124
+    """
125
+    # Prompt the user for a file name from which to retrieve backed up
126
+    # memory settings.  Also make sure the file exists.
127
+    fileName = getFileName(memoryBackupFile)
128
+    if not os.path.isfile(fileName):
129
+        print 'File not found.\n' \
130
+              'Please enter a valid file name.  Be sure to correctly ' \
131
+              'enter\nthe full path name or relative path name of the file.'
132
+        return
133
+    # Read the memory settings from the file...
134
+    print 'Restoring memory settings...'
135
+    settings = readFromFile(fileName)
136
+    # and write them to the FT991.
137
+    writeMemorySettings(settings)
138
+    print 'Memory settings restored from \'%s\'' % fileName
139
+## end def
140
+
141
+def backupMenuSettings():
142
+    """
143
+    Description: Backs up all menu settings to a file.  The user has the
144
+                 option of providing a file name or accepting a default
145
+                 file name.
146
+    Parameters: none
147
+    Returns: nothing
148
+    """
149
+    # Prompt the user for a file name in which to store
150
+    # backed up menu settings.
151
+    fileName = getFileName(menuBackupFile)
152
+    # Read the menu settings from the FT991...
153
+    print 'Backing up menu settings...'
154
+    settings = readMenuSettings()
155
+    # and write them to the file.
156
+    writeToFile(settings, fileName)
157
+    print 'Menu settings backed up to \'%s\'' % fileName
158
+## end def
159
+
160
+def restoreMenuSettings():
161
+    """
162
+    Description: Restores all menu settings from a file.  The user has the
163
+                 option of providing a file name or accepting a default
164
+                 file name.
165
+    Parameters: none
166
+    Returns: nothing
167
+    """
168
+    # Prompt the user for a file name from which to retrieve backed up
169
+    # menu settings.  Also make sure the file exists.
170
+    fileName = getFileName(menuBackupFile)
171
+    if not os.path.isfile(fileName):
172
+        print 'File not found.\n' \
173
+              'Please enter a valid file name.  Be sure to correctly ' \
174
+              'enter\nthe full path name or relative path name of the file.'
175
+        return
176
+    # Read the menu settings from the file...
177
+    print 'Restoring menu settings...'
178
+    settings = readFromFile(fileName)
179
+    # and write them to the FT991.
180
+    writeMenuSettings(settings)
181
+    print 'Menu settings restored from \'%s\'' % fileName
182
+## end def
183
+
184
+def passThroughMode():
185
+    """
186
+    Description: An interactive mode whereby the user an enter FT991 CAT
187
+                 commands directly on the command line.  This mode greatly
188
+                 facilitates development and debugging.
189
+    Parameters: none
190
+    Returns: nothing
191
+    """
192
+    print 'Entering passthrough mode. Type \'exit\' to exit mode.'
193
+    while(True):
194
+        # Prompt the user to enter an FT991 CAT command, and
195
+        # process the  command string.
196
+        sCommand = raw_input('CAT# ').upper()
197
+        if sCommand == 'EXIT': # exit this utility
198
+            break
199
+        # If the user fails to end a CAT command with a semi-colon,
200
+        # then provide one.
201
+        elif sCommand[-1:] != ';':
202
+            sCommand += ';'
203
+
204
+        if sCommand == '': # no command - do nothing
205
+            continue
206
+        else: # run a user command
207
+            ft991.sendSerial(sCommand)
208
+            sResult = ft991.receiveSerial();
209
+            if sResult != '':
210
+                print sResult
211
+## end def
212
+
213
+def toggleVerboseMode():
214
+    """
215
+    Description: Toggles the verbose mode on or off, depending on the
216
+                 previous state.  Verbose mode causes CAT commands to be
217
+                 echoed to STDOUT as they are sent to the FT991.  If a
218
+                 CAT command returns a string, that is also echoed. 
219
+    Parameters: none
220
+    Returns: nothing
221
+    """
222
+    if ft991.verbose:
223
+        ft991.verbose = False
224
+        print 'Verbose is OFF'
225
+    else:
226
+        ft991.verbose = True
227
+        print 'Verbose is ON'  
228
+## end def
229
+
230
+def getFileName(defaultFile):
231
+    """
232
+    Description: Prompts the user for a file name.
233
+    Parameters: defaultFile - file name to use if the user does not
234
+                              provide a file name
235
+    Returns: the user provided file name if provided, or the default
236
+             file name, otherwise.
237
+    """
238
+    # If a command backup or restore argument provided, then do not
239
+    # query the user for a file name.  A file name may be provided
240
+    # as an option on the command line.
241
+    if commandLineOption != '':
242
+        return defaultFile
243
+    # Otherwise query the user for a file name
244
+    fileName = raw_input("Enter file name or <CR> for default: ")
245
+    if fileName == '':
246
+        return defaultFile
247
+    else:
248
+        return fileName
249
+## end def
250
+
251
+def readMenuSettings():
252
+    """
253
+    Description: Reads all menu settings from the FT991.
254
+    Parameters: none
255
+    Returns: a list object containing all the menu settings
256
+    """
257
+    lMenuSettings = []
258
+    # Iterate through all menu items, getting each setting and storing
259
+    # the setting in a file.
260
+    for inx in range(1, _MAX_NUMBER_OF_MENU_ITEMS):
261
+        # Format the read menu item CAT command.
262
+        sCommand = 'EX%0.3d;' % inx
263
+        # Send the command to the FT991.
264
+        sResult = ft991.sendCommand(sCommand)
265
+        # Add the menu setting to a list object.
266
+        lMenuSettings.append(sResult)
267
+    return lMenuSettings
268
+## end def
269
+
270
+def writeMenuSettings(lMenuSettings):
271
+    """
272
+    Description: Writes supplied menu settings to the FT991.
273
+    Parameters: lMenuSettings - a list object containing menu settings
274
+    Returns: nothing
275
+    """
276
+    for item in lMenuSettings:
277
+
278
+        # Do not write read-only menu settings as this results
279
+        # in the FT-991 returning an error.  The only read-only
280
+        # setting is the "Radio ID" setting.
281
+        if item.find('EX087') > -1:
282
+            continue;
283
+        # Send the pre-formatted menu setting to the FT991.
284
+        sResult = ft991.sendCommand(item)
285
+        if sResult.find('?;') > -1:
286
+            print 'error restoring menu setting: %s' % item
287
+            exit(1)
288
+## end def
289
+
290
+def readMemorySettings():
291
+    """
292
+    Description: Reads all defined memory settings from the FT991.  The
293
+                 settings are reformatted into a readable, comma-delimited
294
+                 (csv) file, which can be viewed and modified with a
295
+                 spreadsheet application such as LibreOffice Calc.
296
+    Parameters: none
297
+    Returns: a list object containing memory location settings. Each item
298
+             in the list represents a row, in comma-delimited form, that
299
+             specifies the settings for a single memory location. The first
300
+             item in the list are the column headers for remaining rows
301
+             contained in the list.
302
+    """
303
+    # Define the column headers as the first item in the list.
304
+    lMemorySettings = [ 'Memory Ch,Rx Frequency,Tx Frequency,Offset,' \
305
+                        'Repeater Shift,Mode,Tag,Encoding,Tone,DCS,' \
306
+                        'Clarifier, RxClar, TxClar' ]
307
+
308
+    for memoryLocation in range(1, _MAX_NUMBER_OF_MEMORY_ITEMS):
309
+        # For each memory location get the memory contents.  Note that
310
+        # several CAT commands are required to get the entire contents
311
+        # of a memory location.  Specifically, additional commands are
312
+        # required to get DCS code and CTCSS tone.
313
+        dMem = ft991.getMemory(memoryLocation)
314
+        # If a memory location is empty (has not been programmed or has
315
+        # been erased), do not created a list entry for that location.
316
+        if dMem == None:
317
+            continue
318
+        # Get DCS and CTCSS.
319
+        tone = ft991.getCTCSS()
320
+        dcs = ft991.getDCS()
321
+        # getMemory, above, stores data in a dictionary object.  Format
322
+        # the data in this object, as well as, the DCS code and CTCSS
323
+        # tone into a comma-delimited string.
324
+        sCsvFormat = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,' % \
325
+               ( dMem['memloc'], dMem['rxfreq'], '', '', \
326
+                 dMem['shift'], dMem['mode'], dMem['tag'], dMem['encode'], \
327
+                 tone, dcs, dMem['clarfreq'], dMem['rxclar'], \
328
+                 dMem['txclar'] )
329
+        # Add the comma-delimited string to the list object.
330
+        lMemorySettings.append(sCsvFormat)
331
+        if ft991.verbose:
332
+            print
333
+    return lMemorySettings
334
+# end def
335
+
336
+def writeMemorySettings(lMemorySettings):
337
+    """
338
+    Description: Writes the supplied memory settings to the FT991.
339
+    Parameters: lMemorySettings - a list object containing the memory
340
+                                  settings in comma delimited format
341
+    Returns: nothing
342
+    """
343
+    for item in lMemorySettings:
344
+        # Parse the comma-delimited line and store in a dictionary object.
345
+        dItem = ft991.parseCsvData(item)
346
+        # The first item in the memory settings list are the column headers;
347
+        # so ignore this item.  (parseData returns None for this item.)
348
+        if dItem == None:
349
+            continue
350
+        # Set memory channel vfo, mode, and other data.
351
+        sResult = ''
352
+        sResult += ft991.setMemory(dItem)
353
+        # Set CTCSS tone for memory channel.
354
+        sResult += ft991.setCTCSS(dItem['tone'])
355
+        # Set DCS code for memory channel. 
356
+        sResult += ft991.setDCS(dItem['dcs'])
357
+        # Process any errors returned by the CAT interface.
358
+        if sResult.find('?;') > -1:
359
+            print 'error restoring memory setting: %s' % sResult
360
+        if ft991.verbose:
361
+            print
362
+## end def
363
+
364
+def writeToFile(lSettings, fileName):
365
+    """
366
+    Description: Writes supplied settings to the specified file.
367
+    Parameters: lSettings - a list object containing the settings
368
+                 fileName - the name of the output file
369
+    Returns: nothing
370
+    """
371
+    fout = open(fileName, 'w')
372
+    for item in lSettings:
373
+        fout.write('%s\n' % item)
374
+    fout.close()
375
+## end def
376
+
377
+def readFromFile(fileName):
378
+    """
379
+    Description: Reads settings from the specified file.
380
+    Parameters:  fileName - the name of the input file
381
+    Returns: a list object containing the settings
382
+    """
383
+    lSettings = []
384
+
385
+    fin = open(fileName, 'r')
386
+    for line in fin:
387
+        item = line.strip() # remove new line characters
388
+        lSettings.append(item)
389
+    fin.close()
390
+    return lSettings
391
+## end def
392
+
393
+def setIOFile(option, commandLineFile):
394
+    """
395
+    Description: Provides a connector between the command line and the
396
+                 interactive interface.  Commands included as arguments
397
+                 on the command line determine default file names, as
398
+                 well as the command executed by doUserCommand above. 
399
+    Parameters: option - the command supplied by the command line
400
+                         argument interpreter getCLarguments.
401
+                commandLineFile - the name of the file supplied by the
402
+                          -f command line argument (if supplied). 
403
+    Returns: nothing
404
+    """
405
+    global menuBackupFile, memoryBackupFile, commandLineOption
406
+
407
+    commandLineOption = option
408
+    if commandLineFile == '':
409
+        return
410
+    if (option == 'bu' or option == 'ru'):
411
+        menuBackupFile = commandLineFile # set menu backup file name
412
+    elif (option == 'bm' or option == 'rm'):
413
+        memoryBackupFile = commandLineFile # set memory backup file name
414
+## end def
415
+
416
+def printMenuSplash():
417
+    """
418
+    Description: Prints an menu of available commands for use in
419
+                 interactive mode.
420
+    Parameters:  none
421
+    Returns: nothing
422
+    """
423
+    splash = \
424
+"""
425
+Enter one of the following commands:
426
+m - show this menu
427
+bm - backup memory to file
428
+rm - restore memory from file
429
+bu - backup menu to file
430
+ru - restore menu from file
431
+p - enter pass through mode
432
+v - toggle verbose mode
433
+x - exit this program
434
+"""
435
+    print splash
436
+## end def
437
+
438
+def getCLarguments():
439
+    """ Description: gets command line arguments and configures this program
440
+                     to run accordingly.  See the variable 'usage', below,
441
+                     for possible arguments that may be used on the command
442
+                     line.
443
+        Parameters: none
444
+        Returns: nothing
445
+    """
446
+    index = 1
447
+    fileName = ''
448
+    backupOption = ''
449
+
450
+    # Define a splash to help the user enter command line arguments.
451
+    usage =  "Usage: %s [-v] [OPTION] [-f file]\n"  \
452
+             "  -b: backup memory\n"                \
453
+             "  -r: restore memory\n"               \
454
+             "  -m: backup menu\n"                  \
455
+             "  -s: restore menu\n"                 \
456
+             "  -f: backup/restore file name\n"     \
457
+             "  -v: verbose mode\n"                 \
458
+             % sys.argv[0].split('/')[-1]
459
+
460
+    # Process all command line arguments until done.  Note that the last
461
+    # dash b, m, r, or s argument encounterd on the command line will be
462
+    # the one actually execute; any previous instances will be ignored.
463
+    while index < len(sys.argv):
464
+        if sys.argv[index] == '-f': # Backup file provided.
465
+            # Get the backup file name.
466
+            if len(sys.argv) < index + 2:
467
+                print "-f option requires file name"
468
+                exit(1);
469
+            fileName = sys.argv[index + 1]
470
+            index += 1
471
+        elif sys.argv[index] == '-b': # backup memory
472
+            backupOption = 'bm' 
473
+        elif sys.argv[index] == '-r': # restore memory
474
+            backupOption = 'rm'
475
+        elif sys.argv[index] == '-m': # backup menu
476
+            backupOption = 'bu'
477
+        elif sys.argv[index] == '-s': # restore menu
478
+            backupOption = 'ru'
479
+        elif sys.argv[index] == '-v': # set verbose mode 'ON'
480
+            ft991.verbose = True
481
+        elif sys.argv[index] == '-d': # set debug mode 'ON'
482
+            ft991.debug = True
483
+        else:
484
+            print usage
485
+            exit(-1)
486
+        index += 1
487
+    ## end while
488
+
489
+    # Set backup file name and backup command to execute.
490
+    setIOFile(backupOption, fileName)
491
+##end def
492
+
493
+def main():
494
+    """
495
+    Description: Opens a com port connection to the FT991. Processes any
496
+                 supplied command line options.  If no command line options
497
+                 provides, then enters interactive mode.
498
+    Parameters: none
499
+    Returns: nothing
500
+    """
501
+    getCLarguments() # get command line options
502
+    
503
+    ft991.begin() # open com port session to FT991
504
+
505
+    # Process command line options (if any).
506
+    if commandLineOption != '':
507
+       doUserCommand()
508
+       return
509
+
510
+    # Else enter user interactive mode.
511
+    printMenuSplash()
512
+    while(1):
513
+        doUserCommand()
514
+## end def
515
+
516
+if __name__ == '__main__':
517
+    main()