... | ... |
@@ -55,6 +55,9 @@ ptrDevice = None |
55 | 55 |
# CTCSS tone, and DCS code are translated to the repective FT991 parameter |
56 | 56 |
# value. |
57 | 57 |
|
58 |
+# Binary On / Off state |
|
59 |
+bState = { 'OFF':'0', 'ON':'1' } |
|
60 |
+ |
|
58 | 61 |
# Modulation modes |
59 | 62 |
dMode = { 'LSB':'1', 'USB':'2', 'CW':'3', 'FM':'4', 'AM':'5', |
60 | 63 |
'RTTY-LSB':'6', 'CW-R':'7', 'DATA-LSB':'8', 'RTTY-USB':'9', |
... | ... |
@@ -113,9 +116,12 @@ dDcs = { '23':'000', '25':'001', '26':'002', '31':'003', '32':'004', |
113 | 116 |
'664':'095', '703':'096', '712':'097', '723':'098', '731':'099', |
114 | 117 |
'732':'100', '734':'101', '743':'102', '754':'103' } |
115 | 118 |
|
116 |
-# Clarifier state |
|
117 |
-dRxClar = { 'OFF':'0', 'ON':'1' } |
|
118 |
-dTxClar = { 'OFF':'0', 'ON':'1' } |
|
119 |
+# Preamplifier State |
|
120 |
+dPreamp = { 'IPO':'0', 'AMP 1':'1', 'AMP 2':'2' } |
|
121 |
+ |
|
122 |
+# Narror band filter state |
|
123 |
+dNAR = { 'WIDE':'0', 'NARROW':'1' } |
|
124 |
+ |
|
119 | 125 |
|
120 | 126 |
############################################################################# |
121 | 127 |
# Define 'get' methods to encapsulate FT991 commands returning status info. # |
... | ... |
@@ -135,24 +141,24 @@ def getMemory(memloc): |
135 | 141 |
|
136 | 142 |
# Parse memory settings string returned by the FT991 |
137 | 143 |
memloc = sResult[2:5] |
138 |
- rxfreq = sResult[5:14] |
|
144 |
+ vfoa = sResult[5:14] |
|
139 | 145 |
clarfreq = sResult[14:19] |
140 | 146 |
rxclar = sResult[19] |
141 | 147 |
txclar = sResult[20] |
142 | 148 |
mode = sResult[21] |
143 | 149 |
encode = sResult[23] |
144 |
- shift = sResult[26] |
|
150 |
+ rpoffset = sResult[26] |
|
145 | 151 |
tag = sResult[28:40] |
146 | 152 |
|
147 | 153 |
# Store the memory settings in a dictionary object. |
148 | 154 |
dMem['memloc'] = str(int(memloc)) |
149 |
- dMem['rxfreq'] = str(float(rxfreq) / 10**6) |
|
155 |
+ dMem['vfoa'] = str(float(vfoa) / 10**6) |
|
150 | 156 |
dMem['clarfreq'] = str(int(clarfreq)) |
151 |
- dMem['rxclar'] = dRxClar.keys()[dRxClar.values().index(rxclar)] |
|
152 |
- dMem['txclar'] = dTxClar.keys()[dTxClar.values().index(txclar)] |
|
157 |
+ dMem['rxclar'] = bState.keys()[bState.values().index(rxclar)] |
|
158 |
+ dMem['txclar'] = bState.keys()[bState.values().index(txclar)] |
|
153 | 159 |
dMem['mode'] = dMode.keys()[dMode.values().index(mode)] |
154 | 160 |
dMem['encode'] = dEncode.keys()[dEncode.values().index(encode)] |
155 |
- dMem['shift'] = dShift.keys()[dShift.values().index(shift)] |
|
161 |
+ dMem['rpoffset'] = dShift.keys()[dShift.values().index(rpoffset)] |
|
156 | 162 |
dMem['tag'] = tag.strip() |
157 | 163 |
|
158 | 164 |
return dMem |
... | ... |
@@ -190,10 +196,9 @@ def getRxClarifier(): |
190 | 196 |
""" |
191 | 197 |
# An exception will automatically be raised if incorrect data is |
192 | 198 |
# supplied - most likely a "key not found" error. |
193 |
- sCmd = 'RT;' |
|
194 |
- sResult = sendCommand(sCmd) |
|
199 |
+ sResult = sendCommand('RT;') |
|
195 | 200 |
state = sResult[2] |
196 |
- return dRxClar.keys()[dRxClar.values().index(state)] |
|
201 |
+ return bState.keys()[bState.values().index(state)] |
|
197 | 202 |
## end def |
198 | 203 |
|
199 | 204 |
def getTxClarifier(): |
... | ... |
@@ -204,14 +209,182 @@ def getTxClarifier(): |
204 | 209 |
""" |
205 | 210 |
# An exception will automatically be raised if incorrect data is |
206 | 211 |
# supplied - most likely a "key not found" error. |
207 |
- sCmd = 'XT;' |
|
208 |
- sResult = sendCommand(sCmd) |
|
212 |
+ sResult = sendCommand('XT;') |
|
209 | 213 |
state = sResult[2] |
210 |
- return dTxClar.keys()[dTxClar.values().index(state)] |
|
214 |
+ return bState.keys()[bState.values().index(state)] |
|
215 |
+## end def |
|
216 |
+ |
|
217 |
+def getPower(): |
|
218 |
+ """ |
|
219 |
+ Description: Gets the transmit power level. |
|
220 |
+ Parameters: none |
|
221 |
+ Returns: string containing the power in Watts |
|
222 |
+ """ |
|
223 |
+ sResult = sendCommand('PC;') |
|
224 |
+ return sResult[2:5] |
|
225 |
+##end def |
|
226 |
+ |
|
227 |
+def getPreamp(): |
|
228 |
+ """ |
|
229 |
+ Description: Gets the state of the Rx preamplifier. |
|
230 |
+ Parameters: none |
|
231 |
+ Returns: string containing the state of the preamplifier. |
|
232 |
+ """ |
|
233 |
+ # Get result of PA0 command |
|
234 |
+ sResult = sendCommand('PA0;') |
|
235 |
+ ipo = sResult[3:4] |
|
236 |
+ return dPreamp.keys()[dPreamp.values().index(ipo)] |
|
237 |
+## end def |
|
238 |
+ |
|
239 |
+def getRfAttn(): |
|
240 |
+ """ |
|
241 |
+ Description: Gets the state of the Rf attenuator. |
|
242 |
+ Parameters: none |
|
243 |
+ Returns: string containing the state of the Rf attenuator. |
|
244 |
+ """ |
|
245 |
+ sResult = sendCommand('RA0;') |
|
246 |
+ if sResult == '?;': |
|
247 |
+ return 'NA' |
|
248 |
+ attn = sResult[3:4] |
|
249 |
+ return bState.keys()[bState.values().index(attn)] |
|
250 |
+## end def |
|
251 |
+ |
|
252 |
+def getNoiseBlanker(): |
|
253 |
+ """ |
|
254 |
+ Description: Gets the state of the noise blanker. |
|
255 |
+ Parameters: none |
|
256 |
+ Returns: string containing the state of the noise blanker. |
|
257 |
+ """ |
|
258 |
+ sResult = sendCommand('NB0;') |
|
259 |
+ nb = sResult[3:4] |
|
260 |
+ return bState.keys()[bState.values().index(nb)] |
|
261 |
+## end def |
|
262 |
+ |
|
263 |
+def getIFshift(): |
|
264 |
+ """ |
|
265 |
+ Description: Gets the value in Hz of IF shift. |
|
266 |
+ Parameters: none |
|
267 |
+ Returns: string containing the amount of shift. |
|
268 |
+ """ |
|
269 |
+ if getRfAttn() == 'NA': |
|
270 |
+ return 'NA' |
|
271 |
+ sResult = sendCommand('IS0;') |
|
272 |
+ if sResult == '?;': |
|
273 |
+ return 'NA' |
|
274 |
+ shift = int(sResult[3:8]) |
|
275 |
+ return shift |
|
276 |
+## end def |
|
277 |
+ |
|
278 |
+def getIFwidth(): |
|
279 |
+ """ |
|
280 |
+ Description: Gets the index of the width setting. IF width settings |
|
281 |
+ vary according to the modulation type and bandwidth. |
|
282 |
+ Therefore only the index gets saved. |
|
283 |
+ Parameters: none |
|
284 |
+ Returns: string containing the amount index number. |
|
285 |
+ """ |
|
286 |
+ if getRfAttn() == 'NA': |
|
287 |
+ return 'NA' |
|
288 |
+ sResult = sendCommand('SH0;') |
|
289 |
+ width = int(sResult[3:5]) |
|
290 |
+ return width |
|
291 |
+## end def |
|
292 |
+ |
|
293 |
+def getContour(): |
|
294 |
+ """ |
|
295 |
+ Description: Gets the four contour parameters. |
|
296 |
+ Parameters: none |
|
297 |
+ Returns: list object containing the four parameters. |
|
298 |
+ """ |
|
299 |
+ if getRfAttn() == 'NA': |
|
300 |
+ return [ 'NA', 'NA', 'NA', 'NA' ] |
|
301 |
+ lContour = [] |
|
302 |
+ |
|
303 |
+ sResult = sendCommand('CO00;') |
|
304 |
+ lContour.append(int(sResult[4:8])) |
|
305 |
+ |
|
306 |
+ sResult = sendCommand('CO01;') |
|
307 |
+ lContour.append(int(sResult[4:8])) |
|
308 |
+ |
|
309 |
+ sResult = sendCommand('CO02;') |
|
310 |
+ lContour.append(int(sResult[4:8])) |
|
311 |
+ |
|
312 |
+ sResult = sendCommand('CO03;') |
|
313 |
+ lContour.append(int(sResult[4:8])) |
|
314 |
+ return lContour |
|
315 |
+## end def |
|
316 |
+ |
|
317 |
+def getDNRstate(): |
|
318 |
+ """ |
|
319 |
+ Description: Gets digital noise reduction (DNR) state. |
|
320 |
+ Parameters: none |
|
321 |
+ Returns: 0 = OFF or 1 = ON |
|
322 |
+ """ |
|
323 |
+ sResult = sendCommand('NR0;') |
|
324 |
+ if sResult == '?;': |
|
325 |
+ return 'NA' |
|
326 |
+ state = sResult[3:4] |
|
327 |
+ return bState.keys()[bState.values().index(state)] |
|
328 |
+## end def |
|
329 |
+ |
|
330 |
+def getDNRalgorithm(): |
|
331 |
+ """ |
|
332 |
+ Description: Gets the algorithm used by the DNR processor. |
|
333 |
+ Parameters: none |
|
334 |
+ Returns: a number between 1 and 16 inclusive |
|
335 |
+ """ |
|
336 |
+ sResult = sendCommand('RL0;') |
|
337 |
+ algorithm = int(sResult[3:5]) |
|
338 |
+ return algorithm |
|
339 |
+## end def |
|
340 |
+ |
|
341 |
+def getDNFstate(): |
|
342 |
+ """ |
|
343 |
+ Description: Gets digital notch filter (DNF) state. |
|
344 |
+ Parameters: none |
|
345 |
+ Returns: 0 = OFF or 1 = ON |
|
346 |
+ """ |
|
347 |
+ sResult = sendCommand('BC0;') |
|
348 |
+ if sResult == '?;': |
|
349 |
+ return 'NA' |
|
350 |
+ state = sResult[3:4] |
|
351 |
+ return bState.keys()[bState.values().index(state)] |
|
211 | 352 |
## end def |
212 | 353 |
|
354 |
+def getNARstate(): |
|
355 |
+ """ |
|
356 |
+ Description: Gets narrow/wide filter (NAR) state. |
|
357 |
+ Parameters: none |
|
358 |
+ Returns: 0 = Wide or 1 = Narrow |
|
359 |
+ """ |
|
360 |
+ sResult = sendCommand('NA0;') |
|
361 |
+ if sResult == '?;': |
|
362 |
+ return 'NA' |
|
363 |
+ state = sResult[3:4] |
|
364 |
+ return dNAR.keys()[dNAR.values().index(state)] |
|
365 |
+## end def |
|
366 |
+ |
|
367 |
+def getNotchState(): |
|
368 |
+ """ |
|
369 |
+ Description: Gets the notch filter state and setting. |
|
370 |
+ Parameters: none |
|
371 |
+ Returns: a tuple containing state and frequency |
|
372 |
+ state = 0 (OFF) or 1 (ON) |
|
373 |
+ frequency = number between 1 and 320 (x 10 Hz) |
|
374 |
+ """ |
|
375 |
+ sResult = sendCommand('BP00;') |
|
376 |
+ if sResult == '?;': |
|
377 |
+ return ('NA', 'NA') |
|
378 |
+ state = sResult[6:7] |
|
379 |
+ state = bState.keys()[bState.values().index(state)] |
|
380 |
+ sResult = sendCommand('BP01;') |
|
381 |
+ freq = int(sResult[4:7]) |
|
382 |
+ return (state, freq) |
|
383 |
+## end def |
|
384 |
+ |
|
385 |
+ |
|
213 | 386 |
############################################################################# |
214 |
-# Define 'set' functions to encapsulate the various FT991 CAT commands. # |
|
387 |
+# Define 'set' methods to encapsulate the various FT991 CAT commands. # |
|
215 | 388 |
############################################################################# |
216 | 389 |
|
217 | 390 |
def setMemory(dMem): |
... | ... |
@@ -221,13 +394,13 @@ def setMemory(dMem): |
221 | 394 |
defined: |
222 | 395 |
|
223 | 396 |
memloc - the memory location to be written |
224 |
- rxfreq - receive frequency of VFO-A in MHz |
|
397 |
+ vfoa - receive frequency of VFO-A in MHz |
|
225 | 398 |
clarfreq - clarifier frequency and direction |
226 | 399 |
rxclar - receive clarifier state |
227 | 400 |
txclar - transmit clarifier state |
228 | 401 |
mode - the modulation mode |
229 | 402 |
encode - the tone or DCS encoding mode |
230 |
- shift - the direction of the repeater shift |
|
403 |
+ rpoffset - the direction of the repeater shift |
|
231 | 404 |
tag - a label for the memory location |
232 | 405 |
|
233 | 406 |
Returns: nothing |
... | ... |
@@ -243,7 +416,7 @@ def setMemory(dMem): |
243 | 416 |
sCmd += '%0.3d' % iLocation |
244 | 417 |
|
245 | 418 |
# Validate and append the vfo-a frequency data. |
246 |
- iRxfreq = int(float(dMem['rxfreq']) * 1E6) # vfo-a frequency in Hz |
|
419 |
+ iRxfreq = int(float(dMem['vfoa']) * 1E6) # vfo-a frequency in Hz |
|
247 | 420 |
if iRxfreq < 0.030E6 or iRxfreq > 450.0E6: |
248 | 421 |
raise Exception('VFO-A frequency must be between 30 kHz and ' \ |
249 | 422 |
'450 MHz, inclusive.') |
... | ... |
@@ -259,13 +432,13 @@ def setMemory(dMem): |
259 | 432 |
# The following commands will automatically raise an exception if |
260 | 433 |
# incorrect data is supplied. The exception will be a dictionary |
261 | 434 |
# object "key not found" error. |
262 |
- sCmd += dRxClar[dMem['rxclar']] |
|
263 |
- sCmd += dTxClar[dMem['txclar']] |
|
435 |
+ sCmd += bState[dMem['rxclar']] |
|
436 |
+ sCmd += bState[dMem['txclar']] |
|
264 | 437 |
sCmd += dMode[dMem['mode']] |
265 | 438 |
sCmd += '0' |
266 | 439 |
sCmd += dEncode[dMem['encode']] |
267 | 440 |
sCmd += '00' |
268 |
- sCmd += dShift[dMem['shift']] |
|
441 |
+ sCmd += dShift[dMem['rpoffset']] |
|
269 | 442 |
sCmd += '0' |
270 | 443 |
sTag = dMem['tag'] |
271 | 444 |
|
... | ... |
@@ -342,7 +515,7 @@ def setRxClarifier(state='OFF'): |
342 | 515 |
""" |
343 | 516 |
# An exception will automatically be raised if incorrect data is |
344 | 517 |
# supplied - most likely a "key not found" error. |
345 |
- sCmd = 'RT%s;' % dRxClar[state] |
|
518 |
+ sCmd = 'RT%s;' % bState[state] |
|
346 | 519 |
# Send the completed command. |
347 | 520 |
sResult = sendCommand(sCmd) |
348 | 521 |
if sResult == '?;': |
... | ... |
@@ -358,7 +531,7 @@ def setTxClarifier(state='OFF'): |
358 | 531 |
""" |
359 | 532 |
# An exception will automatically be raised if incorrect data is |
360 | 533 |
# supplied - most likely a "key not found" error. |
361 |
- sCmd = 'XT%s;' % dTxClar[state] |
|
534 |
+ sCmd = 'XT%s;' % bState[state] |
|
362 | 535 |
# Send the completed command. |
363 | 536 |
sResult = sendCommand(sCmd) |
364 | 537 |
if sResult == '?;': |
... | ... |
@@ -386,6 +559,182 @@ def setMemoryLocation(iLocation): |
386 | 559 |
return str(iLocation) |
387 | 560 |
## end def |
388 | 561 |
|
562 |
+def setPreamp(state='IPO'): |
|
563 |
+ """ |
|
564 |
+ Description: Sends a formatted PA command that sets the preamplifier |
|
565 |
+ state. |
|
566 |
+ Parameters: state - string 'IPO', 'AMP 1', 'AMP 2' |
|
567 |
+ Returns: nothing |
|
568 |
+ """ |
|
569 |
+ # An exception will automatically be raised if incorrect data is |
|
570 |
+ # supplied - most likely a "key not found" error. |
|
571 |
+ sCmd = 'PA0%s;' % dPreamp[state] |
|
572 |
+ # Send the completed command. |
|
573 |
+ sResult = sendCommand(sCmd) |
|
574 |
+ if sResult == '?;': |
|
575 |
+ raise Exception('setPreAmp error') |
|
576 |
+## end def |
|
577 |
+ |
|
578 |
+def setRfAttn(state='NA'): |
|
579 |
+ """ |
|
580 |
+ Description: Sends a formatted PA command that sets the RF attenuator |
|
581 |
+ state. Note that attempting to write or read the |
|
582 |
+ attenuator for a VHF or UHF band results in an error. |
|
583 |
+ Hence the addition of a state 'NA' for NOT APPLICABLE. |
|
584 |
+ Parameters: state - string 'OFF', 'ON', 'NA' |
|
585 |
+ Returns: nothing |
|
586 |
+ """ |
|
587 |
+ if state == 'NA': |
|
588 |
+ return |
|
589 |
+ sCmd = 'RA0%s;' % bState[state] |
|
590 |
+ sResult = sendCommand(sCmd) |
|
591 |
+ if sResult == '?;': |
|
592 |
+ raise Exception('setRfAttn error') |
|
593 |
+## end def |
|
594 |
+ |
|
595 |
+def setNoiseBlanker(state='OFF'): |
|
596 |
+ """ |
|
597 |
+ Description: Sends a formatted NB command that sets the noise blanker |
|
598 |
+ state. |
|
599 |
+ Parameters: state - string 'OFF', 'ON' |
|
600 |
+ Returns: nothing |
|
601 |
+ """ |
|
602 |
+ sCmd = 'NB0%s;' % bState[state] |
|
603 |
+ sResult = sendCommand(sCmd) |
|
604 |
+ if sResult == '?;': |
|
605 |
+ raise Exception('setNoiseBlanker error') |
|
606 |
+## end def |
|
607 |
+ |
|
608 |
+def setIFshift(shift='NA'): |
|
609 |
+ """ |
|
610 |
+ Description: Sends a formatted IS command that sets the amount of |
|
611 |
+ IF shift. |
|
612 |
+ Parameters: state - string 'OFF', 'ON' |
|
613 |
+ Returns: nothing |
|
614 |
+ """ |
|
615 |
+ if shift == 'NA': |
|
616 |
+ return |
|
617 |
+ shift = int(shift) |
|
618 |
+ if abs(shift) > 1200: |
|
619 |
+ raise Exception('setIFshift error: data out of bounds') |
|
620 |
+ sCmd = 'IS0%0+5d;' % shift |
|
621 |
+ sResult = sendCommand(sCmd) |
|
622 |
+ if sResult == '?;': |
|
623 |
+ raise Exception('setIFshift error') |
|
624 |
+## end def |
|
625 |
+ |
|
626 |
+def setIFwidth(index='NA'): |
|
627 |
+ """ |
|
628 |
+ Description: Sends a formatted SH command that sets the IF width |
|
629 |
+ IF shift. |
|
630 |
+ Parameters: index of shift - value between 0 and 21, inclusive |
|
631 |
+ Returns: nothing |
|
632 |
+ """ |
|
633 |
+ if index == 'NA': |
|
634 |
+ return |
|
635 |
+ index = int(index) |
|
636 |
+ if index < 0 or index > 21: |
|
637 |
+ raise Exception('setIFwidth error: data out of bounds') |
|
638 |
+ sCmd = 'SH0%02d;' % index |
|
639 |
+ sResult = sendCommand(sCmd) |
|
640 |
+ if sResult == '?;': |
|
641 |
+ raise Exception('setIFwidth error') |
|
642 |
+## end def |
|
643 |
+ |
|
644 |
+def setContour(lParams): |
|
645 |
+ """ |
|
646 |
+ Description: Sends a formatted CO command that sets contour parameters |
|
647 |
+ Parameters: lParams - list object containing the parameters |
|
648 |
+ Returns: nothing |
|
649 |
+ """ |
|
650 |
+ if lParams[0] == 'NA': |
|
651 |
+ return |
|
652 |
+ |
|
653 |
+ for inx in range(4): |
|
654 |
+ sCmd = 'CO0%d%04d' % (inx, int(lParams[inx])) |
|
655 |
+ sResult = sendCommand(sCmd) |
|
656 |
+ if sResult == '?;': |
|
657 |
+ raise Exception('setContour error') |
|
658 |
+## end def |
|
659 |
+ |
|
660 |
+def setDNRstate(state = 'NA'): |
|
661 |
+ """ |
|
662 |
+ Description: Sets the state (on or off) of the digital noise |
|
663 |
+ reduction (DNR) processor. |
|
664 |
+ Parameters: State = 0 (OFF) or 1 (ON) |
|
665 |
+ Returns: nothing |
|
666 |
+ """ |
|
667 |
+ if state == 'NA': |
|
668 |
+ return |
|
669 |
+ sCmd = 'NR0%01d' % int(bState[state]) |
|
670 |
+ sResult = sendCommand(sCmd) |
|
671 |
+ if sResult == '?;': |
|
672 |
+ raise Exception('setDNR error') |
|
673 |
+## end def |
|
674 |
+ |
|
675 |
+def setDNRalgorithm(algorithm = 1): |
|
676 |
+ """ |
|
677 |
+ Description: Sets the algorithm used by the digital noise |
|
678 |
+ reduction (DNR) processor. |
|
679 |
+ Parameters: algorithm - a number between 1 and 16 inclusive |
|
680 |
+ Returns: nothing |
|
681 |
+ """ |
|
682 |
+ sCmd = 'RL0%02d' % int(algorithm) |
|
683 |
+ sResult = sendCommand(sCmd) |
|
684 |
+ if sResult == '?;': |
|
685 |
+ raise Exception('setDNR error') |
|
686 |
+## end def |
|
687 |
+ |
|
688 |
+def setDNFstate(state = 'NA'): |
|
689 |
+ """ |
|
690 |
+ Description: Sets the state (on or off) of the digital notch |
|
691 |
+ filter (DNF) processor. |
|
692 |
+ Parameters: State = 0 (OFF) or 1 (ON) |
|
693 |
+ Returns: nothing |
|
694 |
+ """ |
|
695 |
+ if state == 'NA': |
|
696 |
+ return |
|
697 |
+ sCmd = 'BC0%01d' % int(bState[state]) |
|
698 |
+ sResult = sendCommand(sCmd) |
|
699 |
+ if sResult == '?;': |
|
700 |
+ raise Exception('setDNF error') |
|
701 |
+## end def |
|
702 |
+ |
|
703 |
+def setNARstate( state = 'NA'): |
|
704 |
+ """ |
|
705 |
+ Description: Gets narrow/wide filter (NAR) state. |
|
706 |
+ Parameters: none |
|
707 |
+ Returns: 0 = Wide or 1 = Narrow |
|
708 |
+ """ |
|
709 |
+ if state == 'NA': |
|
710 |
+ return |
|
711 |
+ sCmd = 'NA0%s' % dNAR[state] |
|
712 |
+ sResult = sendCommand(sCmd) |
|
713 |
+ if sResult == '?;': |
|
714 |
+ raise Exception('setNAR error') |
|
715 |
+## end def |
|
716 |
+ |
|
717 |
+def setNotchState( state = ('NA', 'NA') ): |
|
718 |
+ """ |
|
719 |
+ Description: Gets the notch filter state and setting. |
|
720 |
+ Parameters: none |
|
721 |
+ Returns: a tuple containing state and frequency |
|
722 |
+ state = 0 (OFF) or 1 (ON) |
|
723 |
+ frequency = number between 1 and 320 (x 10 Hz) |
|
724 |
+ """ |
|
725 |
+ if state[0] == 'NA': |
|
726 |
+ return |
|
727 |
+ sCmd = 'BP0000%s' % bState[state[0]] |
|
728 |
+ sResult = sendCommand(sCmd) |
|
729 |
+ if sResult == '?;': |
|
730 |
+ raise Exception('setNotch error') |
|
731 |
+ sCmd = 'BP01%03d' % int(state[1]) |
|
732 |
+ sResult = sendCommand(sCmd) |
|
733 |
+ if sResult == '?;': |
|
734 |
+ raise Exception('setNotch error') |
|
735 |
+## end def |
|
736 |
+ |
|
737 |
+ |
|
389 | 738 |
############################################################################# |
390 | 739 |
# Helper functions to assist in various tasks. # |
391 | 740 |
############################################################################# |
... | ... |
@@ -404,18 +753,29 @@ def parseCsvData(sline): |
404 | 753 |
return None |
405 | 754 |
# Store the parsed items with the appropriate key in the dictionary object. |
406 | 755 |
dChan['memloc'] = lchan[0] |
407 |
- dChan['rxfreq'] = lchan[1] |
|
408 |
- dChan['txfreq'] = lchan[2] |
|
409 |
- dChan['offset'] = lchan[3] |
|
410 |
- dChan['shift'] = lchan[4] |
|
411 |
- dChan['mode'] = lchan[5] |
|
412 |
- dChan['tag'] = lchan[6] |
|
413 |
- dChan['encode'] = lchan[7] |
|
414 |
- dChan['tone'] = lchan[8] |
|
415 |
- dChan['dcs'] = lchan[9] |
|
416 |
- dChan['clarfreq'] = lchan[10] |
|
417 |
- dChan['rxclar'] = lchan[11] |
|
418 |
- dChan['txclar'] = lchan[12] |
|
756 |
+ dChan['vfoa'] = lchan[1] |
|
757 |
+ dChan['vfob'] = lchan[2] |
|
758 |
+ dChan['rpoffset'] = lchan[3] |
|
759 |
+ dChan['mode'] = lchan[4] |
|
760 |
+ dChan['tag'] = lchan[5] |
|
761 |
+ dChan['encode'] = lchan[6] |
|
762 |
+ dChan['tone'] = lchan[7] |
|
763 |
+ dChan['dcs'] = lchan[8] |
|
764 |
+ dChan['clarfreq'] = lchan[9] |
|
765 |
+ dChan['rxclar'] = lchan[10] |
|
766 |
+ dChan['txclar'] = lchan[11] |
|
767 |
+ dChan['preamp'] = lchan[12] |
|
768 |
+ dChan['rfattn'] = lchan[13] |
|
769 |
+ dChan['nblkr'] = lchan[14] |
|
770 |
+ dChan['shift'] = lchan[15] |
|
771 |
+ dChan['width'] = lchan[16] |
|
772 |
+ dChan['contour'] = [ lchan[17], lchan[18], lchan[19], lchan[20] ] |
|
773 |
+ dChan['dnrstate'] = lchan[21] |
|
774 |
+ dChan['dnralgorithm'] = lchan[22] |
|
775 |
+ dChan['dnfstate'] = lchan[23] |
|
776 |
+ dChan['narstate'] = lchan[24] |
|
777 |
+ dChan['notchstate'] = lchan[25] |
|
778 |
+ dChan['notchfreq'] = lchan[26] |
|
419 | 779 |
return dChan # return the dictionary object |
420 | 780 |
## end def |
421 | 781 |
|
... | ... |
@@ -561,7 +921,7 @@ def main(): |
561 | 921 |
# Instantiate serial connection to FT991 |
562 | 922 |
begin() |
563 | 923 |
# Set and receive a memory channel |
564 |
- dMem = {'memloc': '98', 'rxfreq': '146.52', 'shift': 'OFF', \ |
|
924 |
+ dMem = {'memloc': '98', 'vfoa': '146.52', 'shift': 'OFF', \ |
|
565 | 925 |
'mode': 'FM', 'encode': 'TONE ENC', 'tag': 'KA7JLO', \ |
566 | 926 |
'clarfreq': '1234', 'rxclar': 'ON', 'txclar': 'ON' \ |
567 | 927 |
} |
... | ... |
@@ -575,7 +935,7 @@ def main(): |
575 | 935 |
getMemory(int(dMem['memloc'])) |
576 | 936 |
|
577 | 937 |
# Set and receive a memory channel |
578 |
- dMem = {'memloc': '99', 'rxfreq': '146.52', 'shift': 'OFF', \ |
|
938 |
+ dMem = {'memloc': '99', 'vfoa': '146.52', 'shift': 'OFF', \ |
|
579 | 939 |
'mode': 'FM', 'encode': 'OFF', 'tag': 'KA7JLO', \ |
580 | 940 |
'clarfreq': '0', 'rxclar': 'OFF', 'txclar': 'OFF' \ |
581 | 941 |
} |
... | ... |
@@ -132,8 +132,6 @@ def getMemory(memloc): |
132 | 132 |
# Send the get memory settings string to the FT991. |
133 | 133 |
sCmd = 'MT%0.3d;' % (memloc) |
134 | 134 |
sResult = sendCommand(sCmd) |
135 |
- if sResult == '?;': |
|
136 |
- return None |
|
137 | 135 |
|
138 | 136 |
# Parse memory settings string returned by the FT991 |
139 | 137 |
memloc = sResult[2:5] |
... | ... |
@@ -212,9 +210,9 @@ def getTxClarifier(): |
212 | 210 |
return dTxClar.keys()[dTxClar.values().index(state)] |
213 | 211 |
## end def |
214 | 212 |
|
215 |
-######################################################################### |
|
216 |
-# Define 'set' functions to encapsulate the various FT991 CAT commands. # |
|
217 |
-######################################################################### |
|
213 |
+############################################################################# |
|
214 |
+# Define 'set' functions to encapsulate the various FT991 CAT commands. # |
|
215 |
+############################################################################# |
|
218 | 216 |
|
219 | 217 |
def setMemory(dMem): |
220 | 218 |
""" |
... | ... |
@@ -372,7 +370,8 @@ def setMemoryLocation(iLocation): |
372 | 370 |
Description: Sends a formatted MC command that sets the current |
373 | 371 |
memory location. |
374 | 372 |
Parameters: location - integer specifying memory location |
375 |
- Returns: nothing |
|
373 |
+ Returns: None if the memory location is blank, otherwise |
|
374 |
+ returns a string containing the memory location. |
|
376 | 375 |
""" |
377 | 376 |
# Validate memory location data and send the command. |
378 | 377 |
if iLocation < 1 or iLocation > 118: |
... | ... |
@@ -382,12 +381,14 @@ def setMemoryLocation(iLocation): |
382 | 381 |
# Send the completed command. |
383 | 382 |
sResult = sendCommand(sCmd) |
384 | 383 |
if sResult == '?;': |
385 |
- raise Exception('setMemoryLocation error') |
|
384 |
+ return None |
|
385 |
+ else: |
|
386 |
+ return str(iLocation) |
|
386 | 387 |
## end def |
387 | 388 |
|
388 |
-############################################################################ |
|
389 |
-# Helper functions to assist in various tasks. # |
|
390 |
-############################################################################ |
|
389 |
+############################################################################# |
|
390 |
+# Helper functions to assist in various tasks. # |
|
391 |
+############################################################################# |
|
391 | 392 |
|
392 | 393 |
def parseCsvData(sline): |
393 | 394 |
""" |
... | ... |
@@ -447,7 +448,9 @@ def sendCommand(sCmd): |
447 | 448 |
return sResult |
448 | 449 |
## end def |
449 | 450 |
|
450 |
-# Low level serial communications functions. |
|
451 |
+############################################################################# |
|
452 |
+# Low level serial communications functions. # |
|
453 |
+############################################################################# |
|
451 | 454 |
|
452 | 455 |
def begin(baud=9600): |
453 | 456 |
""" |
... | ... |
@@ -562,28 +565,28 @@ def main(): |
562 | 565 |
'mode': 'FM', 'encode': 'TONE ENC', 'tag': 'KA7JLO', \ |
563 | 566 |
'clarfreq': '1234', 'rxclar': 'ON', 'txclar': 'ON' \ |
564 | 567 |
} |
565 |
- setMemory(dMem) |
|
566 | 568 |
setMemoryLocation(int(dMem['memloc'])) |
569 |
+ setMemory(dMem) |
|
567 | 570 |
setRxClarifier(dMem['rxclar']) |
568 | 571 |
setTxClarifier(dMem['txclar']) |
569 | 572 |
setCTCSS('127.3 Hz') |
570 | 573 |
setDCS('115') |
571 | 574 |
|
572 |
- getMemory(98) |
|
575 |
+ getMemory(int(dMem['memloc'])) |
|
573 | 576 |
|
574 | 577 |
# Set and receive a memory channel |
575 | 578 |
dMem = {'memloc': '99', 'rxfreq': '146.52', 'shift': 'OFF', \ |
576 | 579 |
'mode': 'FM', 'encode': 'OFF', 'tag': 'KA7JLO', \ |
577 | 580 |
'clarfreq': '0', 'rxclar': 'OFF', 'txclar': 'OFF' \ |
578 | 581 |
} |
579 |
- setMemory(dMem) |
|
580 | 582 |
setMemoryLocation(int(dMem['memloc'])) |
583 |
+ setMemory(dMem) |
|
581 | 584 |
setRxClarifier(dMem['rxclar']) |
582 | 585 |
setTxClarifier(dMem['txclar']) |
583 | 586 |
setCTCSS('141.3 Hz') |
584 | 587 |
setDCS('445') |
585 | 588 |
|
586 |
- getMemory(99) |
|
589 |
+ getMemory(int(dMem['memloc'])) |
|
587 | 590 |
|
588 | 591 |
|
589 | 592 |
# Test set commands |
... | ... |
@@ -117,7 +117,9 @@ dDcs = { '23':'000', '25':'001', '26':'002', '31':'003', '32':'004', |
117 | 117 |
dRxClar = { 'OFF':'0', 'ON':'1' } |
118 | 118 |
dTxClar = { 'OFF':'0', 'ON':'1' } |
119 | 119 |
|
120 |
-# Define 'get' methods to encapsulate FT991 commands that return status info. |
|
120 |
+############################################################################# |
|
121 |
+# Define 'get' methods to encapsulate FT991 commands returning status info. # |
|
122 |
+############################################################################# |
|
121 | 123 |
|
122 | 124 |
def getMemory(memloc): |
123 | 125 |
""" |
... | ... |
@@ -127,17 +129,11 @@ def getMemory(memloc): |
127 | 129 |
""" |
128 | 130 |
dMem = {} |
129 | 131 |
|
130 |
- # Set memory location pointer in FT991. This is done |
|
131 |
- # by sending the memory location select (MC) command. |
|
132 |
- sCmd = 'MC%0.3d;' % (memloc) |
|
133 |
- sResult = sendCommand(sCmd) |
|
134 |
- # Skip blank memory locations, which return '?;'. |
|
135 |
- if sResult == '?;': |
|
136 |
- return None |
|
137 |
- |
|
138 | 132 |
# Send the get memory settings string to the FT991. |
139 | 133 |
sCmd = 'MT%0.3d;' % (memloc) |
140 | 134 |
sResult = sendCommand(sCmd) |
135 |
+ if sResult == '?;': |
|
136 |
+ return None |
|
141 | 137 |
|
142 | 138 |
# Parse memory settings string returned by the FT991 |
143 | 139 |
memloc = sResult[2:5] |
... | ... |
@@ -188,7 +184,37 @@ def getDCS(): |
188 | 184 |
return dDcs.keys()[dDcs.values().index(dcs)] |
189 | 185 |
## end def |
190 | 186 |
|
191 |
-# Define 'set' functions to encapsulate the various FT991 CAT commands. |
|
187 |
+def getRxClarifier(): |
|
188 |
+ """ |
|
189 |
+ Description: Gets the state of the Rx clarifier. |
|
190 |
+ Parameters: none |
|
191 |
+ Returns: string containing the state of the clarifier |
|
192 |
+ """ |
|
193 |
+ # An exception will automatically be raised if incorrect data is |
|
194 |
+ # supplied - most likely a "key not found" error. |
|
195 |
+ sCmd = 'RT;' |
|
196 |
+ sResult = sendCommand(sCmd) |
|
197 |
+ state = sResult[2] |
|
198 |
+ return dRxClar.keys()[dRxClar.values().index(state)] |
|
199 |
+## end def |
|
200 |
+ |
|
201 |
+def getTxClarifier(): |
|
202 |
+ """ |
|
203 |
+ Description: Gets the state of the Tx clarifier. |
|
204 |
+ Parameters: none |
|
205 |
+ Returns: string containing the state of the clarifier |
|
206 |
+ """ |
|
207 |
+ # An exception will automatically be raised if incorrect data is |
|
208 |
+ # supplied - most likely a "key not found" error. |
|
209 |
+ sCmd = 'XT;' |
|
210 |
+ sResult = sendCommand(sCmd) |
|
211 |
+ state = sResult[2] |
|
212 |
+ return dTxClar.keys()[dTxClar.values().index(state)] |
|
213 |
+## end def |
|
214 |
+ |
|
215 |
+######################################################################### |
|
216 |
+# Define 'set' functions to encapsulate the various FT991 CAT commands. # |
|
217 |
+######################################################################### |
|
192 | 218 |
|
193 | 219 |
def setMemory(dMem): |
194 | 220 |
""" |
... | ... |
@@ -206,25 +232,17 @@ def setMemory(dMem): |
206 | 232 |
shift - the direction of the repeater shift |
207 | 233 |
tag - a label for the memory location |
208 | 234 |
|
209 |
- Returns: a string containing the formatted command |
|
235 |
+ Returns: nothing |
|
210 | 236 |
""" |
211 |
- # Format the set memory location (MC) command. |
|
212 |
- iMemloc = int(dMem['memloc']) |
|
213 |
- # Validate memory location data and send the command. |
|
214 |
- if iMemloc < 1 or iMemloc > 118: |
|
237 |
+ # Format the set memory with tag command (MT). |
|
238 |
+ sCmd = 'MT' |
|
239 |
+ |
|
240 |
+ # Validate and append memory location data. |
|
241 |
+ iLocation = int(dMem['memloc']) |
|
242 |
+ if iLocation < 1 or iLocation > 118: |
|
215 | 243 |
raise Exception('Memory location must be between 1 and ' \ |
216 | 244 |
'118, inclusive.') |
217 |
- sCmd = 'MC%0.3d;' % iMemloc |
|
218 |
- sResult = sendCommand(sCmd) |
|
219 |
- |
|
220 |
- # While the 'MW' and 'MT' commands can be used to turn the Rx |
|
221 |
- # and Tx clarifiers on, the clarifier states can only be turned |
|
222 |
- # off by sending the 'RT0' and 'XT0' commands. This situation is |
|
223 |
- # probably due to a bug in the CAT interface. |
|
224 |
- sResult = sendCommand('RC;RT0;XT0;') |
|
225 |
- |
|
226 |
- # Format the set memory with tag command (MT). |
|
227 |
- sCmd = 'MT%0.3d' % iMemloc |
|
245 |
+ sCmd += '%0.3d' % iLocation |
|
228 | 246 |
|
229 | 247 |
# Validate and append the vfo-a frequency data. |
230 | 248 |
iRxfreq = int(float(dMem['rxfreq']) * 1E6) # vfo-a frequency in Hz |
... | ... |
@@ -261,7 +279,8 @@ def setMemory(dMem): |
261 | 279 |
|
262 | 280 |
# Send the completed command. |
263 | 281 |
sResult = sendCommand(sCmd) |
264 |
- return sResult |
|
282 |
+ if sResult == '?;': |
|
283 |
+ raise Exception('setMemory error') |
|
265 | 284 |
## end def |
266 | 285 |
|
267 | 286 |
def setCTCSS(tone): |
... | ... |
@@ -270,12 +289,15 @@ def setCTCSS(tone): |
270 | 289 |
CTCSS tone. |
271 | 290 |
Parameters: tone - a string containing the CTCSS tone in Hz, e.g., |
272 | 291 |
'100 Hz' |
273 |
- Returns: a string containing the formatted command |
|
292 |
+ Returns: nothing |
|
274 | 293 |
""" |
275 | 294 |
# An exception will automatically be raised if incorrect data is |
276 | 295 |
# supplied - most likely a "key not found" error. |
277 | 296 |
sCmd = 'CN00%s;' % dTones[tone] |
278 |
- return sendCommand(sCmd) |
|
297 |
+ # Send the completed command. |
|
298 |
+ sResult = sendCommand(sCmd) |
|
299 |
+ if sResult == '?;': |
|
300 |
+ raise Exception('setCTCSS error') |
|
279 | 301 |
## end def |
280 | 302 |
|
281 | 303 |
def setDCS(code): |
... | ... |
@@ -283,12 +305,15 @@ def setDCS(code): |
283 | 305 |
Description: Sends a formatted CN command that sets the desired |
284 | 306 |
DCS code. |
285 | 307 |
Parameters: code - a string containing the DCS code, e.g., '23' |
286 |
- Returns: a string containing the formatted command |
|
308 |
+ Returns: nothing |
|
287 | 309 |
""" |
288 | 310 |
# An exception will automatically be raised if incorrect data is |
289 | 311 |
# supplied - most likely a "key not found" error. |
290 | 312 |
sCmd = 'CN01%s;' % dDcs[code] |
291 |
- return sendCommand(sCmd) |
|
313 |
+ # Send the completed command. |
|
314 |
+ sResult = sendCommand(sCmd) |
|
315 |
+ if sResult == '?;': |
|
316 |
+ raise Exception('setDCS error') |
|
292 | 317 |
## end def |
293 | 318 |
|
294 | 319 |
def setPower(power): |
... | ... |
@@ -296,17 +321,73 @@ def setPower(power): |
296 | 321 |
Description: Sends a PC command that sets the desired |
297 | 322 |
RF transmit power level. |
298 | 323 |
Parameters: power - Watts, an integer between 5 and 100 |
299 |
- Returns: a string containing the formatted command |
|
324 |
+ Returns: nothing |
|
300 | 325 |
""" |
301 | 326 |
power = int(power) |
302 | 327 |
# Validate power data and format command. |
303 | 328 |
if power < 5 or power > 100: |
304 | 329 |
raise Exception('Power must be between 0 and 100 watts, inclusive.') |
305 | 330 |
sCmd += 'PC%03.d;' % power |
306 |
- return sendCommand(sCmd) |
|
331 |
+ # Send the completed command. |
|
332 |
+ sResult = sendCommand(sCmd) |
|
333 |
+ if sResult == '?;': |
|
334 |
+ raise Exception('setPower error') |
|
335 |
+ |
|
307 | 336 |
## end def |
308 | 337 |
|
309 |
-# Helper functions to assist in various tasks. |
|
338 |
+def setRxClarifier(state='OFF'): |
|
339 |
+ """ |
|
340 |
+ Description: Sends a formatted RT command that turns the Rx clarifier |
|
341 |
+ on or off. |
|
342 |
+ Parameters: state - string 'OFF' or 'ON' |
|
343 |
+ Returns: nothing |
|
344 |
+ """ |
|
345 |
+ # An exception will automatically be raised if incorrect data is |
|
346 |
+ # supplied - most likely a "key not found" error. |
|
347 |
+ sCmd = 'RT%s;' % dRxClar[state] |
|
348 |
+ # Send the completed command. |
|
349 |
+ sResult = sendCommand(sCmd) |
|
350 |
+ if sResult == '?;': |
|
351 |
+ raise Exception('setRxClarifier error') |
|
352 |
+## end def |
|
353 |
+ |
|
354 |
+def setTxClarifier(state='OFF'): |
|
355 |
+ """ |
|
356 |
+ Description: Sends a formatted XT command that turns the Rx clarifier |
|
357 |
+ on or off. |
|
358 |
+ Parameters: state - string 'OFF' or 'ON' |
|
359 |
+ Returns: nothing |
|
360 |
+ """ |
|
361 |
+ # An exception will automatically be raised if incorrect data is |
|
362 |
+ # supplied - most likely a "key not found" error. |
|
363 |
+ sCmd = 'XT%s;' % dTxClar[state] |
|
364 |
+ # Send the completed command. |
|
365 |
+ sResult = sendCommand(sCmd) |
|
366 |
+ if sResult == '?;': |
|
367 |
+ raise Exception('setTxClarifier error') |
|
368 |
+## end def |
|
369 |
+ |
|
370 |
+def setMemoryLocation(iLocation): |
|
371 |
+ """ |
|
372 |
+ Description: Sends a formatted MC command that sets the current |
|
373 |
+ memory location. |
|
374 |
+ Parameters: location - integer specifying memory location |
|
375 |
+ Returns: nothing |
|
376 |
+ """ |
|
377 |
+ # Validate memory location data and send the command. |
|
378 |
+ if iLocation < 1 or iLocation > 118: |
|
379 |
+ raise Exception('Memory location must be an integer between 1 and ' \ |
|
380 |
+ '118, inclusive.') |
|
381 |
+ sCmd = 'MC%0.3d;' % iLocation |
|
382 |
+ # Send the completed command. |
|
383 |
+ sResult = sendCommand(sCmd) |
|
384 |
+ if sResult == '?;': |
|
385 |
+ raise Exception('setMemoryLocation error') |
|
386 |
+## end def |
|
387 |
+ |
|
388 |
+############################################################################ |
|
389 |
+# Helper functions to assist in various tasks. # |
|
390 |
+############################################################################ |
|
310 | 391 |
|
311 | 392 |
def parseCsvData(sline): |
312 | 393 |
""" |
... | ... |
@@ -476,14 +557,41 @@ def main(): |
476 | 557 |
|
477 | 558 |
# Instantiate serial connection to FT991 |
478 | 559 |
begin() |
479 |
- # Commands that send a setting |
|
480 |
- dMem = {'rxfreq': '146.52', 'shift': 'OFF', 'encode': 'OFF', \ |
|
481 |
- 'txclar': 'OFF', 'tag': 'KA7JLO', 'mode': 'FM', 'rxclar': 'OFF', \ |
|
482 |
- 'memloc': '99', 'clarfreq': '0'} |
|
560 |
+ # Set and receive a memory channel |
|
561 |
+ dMem = {'memloc': '98', 'rxfreq': '146.52', 'shift': 'OFF', \ |
|
562 |
+ 'mode': 'FM', 'encode': 'TONE ENC', 'tag': 'KA7JLO', \ |
|
563 |
+ 'clarfreq': '1234', 'rxclar': 'ON', 'txclar': 'ON' \ |
|
564 |
+ } |
|
483 | 565 |
setMemory(dMem) |
484 |
- sendCommand('MC002;') |
|
485 |
- # Commands that return data |
|
566 |
+ setMemoryLocation(int(dMem['memloc'])) |
|
567 |
+ setRxClarifier(dMem['rxclar']) |
|
568 |
+ setTxClarifier(dMem['txclar']) |
|
569 |
+ setCTCSS('127.3 Hz') |
|
570 |
+ setDCS('115') |
|
571 |
|
|
572 |
+ getMemory(98) |
|
573 |
|
|
574 |
+ # Set and receive a memory channel |
|
575 |
+ dMem = {'memloc': '99', 'rxfreq': '146.52', 'shift': 'OFF', \ |
|
576 |
+ 'mode': 'FM', 'encode': 'OFF', 'tag': 'KA7JLO', \ |
|
577 |
+ 'clarfreq': '0', 'rxclar': 'OFF', 'txclar': 'OFF' \ |
|
578 |
+ } |
|
579 |
+ setMemory(dMem) |
|
580 |
+ setMemoryLocation(int(dMem['memloc'])) |
|
581 |
+ setRxClarifier(dMem['rxclar']) |
|
582 |
+ setTxClarifier(dMem['txclar']) |
|
583 |
+ setCTCSS('141.3 Hz') |
|
584 |
+ setDCS('445') |
|
585 |
|
|
486 | 586 |
getMemory(99) |
587 |
|
|
588 |
+ |
|
589 |
+ # Test set commands |
|
590 |
+ #setMemoryLocation(2) |
|
591 |
+ # Test get commands |
|
592 |
+ # commands... |
|
593 |
+ # Test CAT commands via direct pass-through |
|
594 |
+ # Commands that return data |
|
487 | 595 |
sendCommand('IF;') |
488 | 596 |
# Invalid command handling |
489 | 597 |
sendCommand('ZZZ;') |
... | ... |
@@ -117,67 +117,26 @@ dDcs = { '23':'000', '25':'001', '26':'002', '31':'003', '32':'004', |
117 | 117 |
dRxClar = { 'OFF':'0', 'ON':'1' } |
118 | 118 |
dTxClar = { 'OFF':'0', 'ON':'1' } |
119 | 119 |
|
120 |
-# Define 'set' functions to encapsulate the various FT991 CAT commands. |
|
120 |
+# Define 'get' methods to encapsulate FT991 commands that return status info. |
|
121 | 121 |
|
122 |
-def setMemory(dMem): |
|
122 |
+def getMemory(memloc): |
|
123 | 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: |
|
124 |
+ Description: Get memory settings of a specific memory location. |
|
125 |
+ Parameters: memloc - an integer specifying memory location |
|
126 |
+ Returns: a dictionary object containing the memory ettings |
|
168 | 127 |
""" |
169 | 128 |
dMem = {} |
170 | 129 |
|
171 | 130 |
# Set memory location pointer in FT991. This is done |
172 | 131 |
# by sending the memory location select (MC) command. |
173 |
- sCmd = 'MC%0.3d;' % (memLoc) |
|
132 |
+ sCmd = 'MC%0.3d;' % (memloc) |
|
174 | 133 |
sResult = sendCommand(sCmd) |
175 | 134 |
# Skip blank memory locations, which return '?;'. |
176 | 135 |
if sResult == '?;': |
177 | 136 |
return None |
178 | 137 |
|
179 | 138 |
# Send the get memory settings string to the FT991. |
180 |
- sCmd = 'MT%0.3d;' % (memLoc) |
|
139 |
+ sCmd = 'MT%0.3d;' % (memloc) |
|
181 | 140 |
sResult = sendCommand(sCmd) |
182 | 141 |
|
183 | 142 |
# Parse memory settings string returned by the FT991 |
... | ... |
@@ -207,9 +166,9 @@ def getMemory(memLoc): |
207 | 166 |
|
208 | 167 |
def getCTCSS(): |
209 | 168 |
""" |
210 |
- Description: |
|
211 |
- Parameters: |
|
212 |
- Returns: |
|
169 |
+ Description: Get the CTCSS tone setting for the current memory location. |
|
170 |
+ Parameters: none |
|
171 |
+ Returns: string containing the CTCSS tone |
|
213 | 172 |
""" |
214 | 173 |
# Get result CTCSS tone |
215 | 174 |
sResult = sendCommand('CN00;') |
... | ... |
@@ -219,9 +178,9 @@ def getCTCSS(): |
219 | 178 |
|
220 | 179 |
def getDCS(): |
221 | 180 |
""" |
222 |
- Description: |
|
223 |
- Parameters: |
|
224 |
- Returns: |
|
181 |
+ Description: Get the DCS code setting for the current memory location. |
|
182 |
+ Parameters: none |
|
183 |
+ Returns: string containing the DCS code |
|
225 | 184 |
""" |
226 | 185 |
# Get result of CN01 command |
227 | 186 |
sResult = sendCommand('CN01;') |
... | ... |
@@ -229,41 +188,126 @@ def getDCS(): |
229 | 188 |
return dDcs.keys()[dDcs.values().index(dcs)] |
230 | 189 |
## end def |
231 | 190 |
|
191 |
+# Define 'set' functions to encapsulate the various FT991 CAT commands. |
|
192 |
+ |
|
193 |
+def setMemory(dMem): |
|
194 |
+ """ |
|
195 |
+ Description: Sends a formatted MT command. |
|
196 |
+ Parameters: dMem - a dictionary objected with the following keys |
|
197 |
+ defined: |
|
198 |
+ |
|
199 |
+ memloc - the memory location to be written |
|
200 |
+ rxfreq - receive frequency of VFO-A in MHz |
|
201 |
+ clarfreq - clarifier frequency and direction |
|
202 |
+ rxclar - receive clarifier state |
|
203 |
+ txclar - transmit clarifier state |
|
204 |
+ mode - the modulation mode |
|
205 |
+ encode - the tone or DCS encoding mode |
|
206 |
+ shift - the direction of the repeater shift |
|
207 |
+ tag - a label for the memory location |
|
208 |
+ |
|
209 |
+ Returns: a string containing the formatted command |
|
210 |
+ """ |
|
211 |
+ # Format the set memory location (MC) command. |
|
212 |
+ iMemloc = int(dMem['memloc']) |
|
213 |
+ # Validate memory location data and send the command. |
|
214 |
+ if iMemloc < 1 or iMemloc > 118: |
|
215 |
+ raise Exception('Memory location must be between 1 and ' \ |
|
216 |
+ '118, inclusive.') |
|
217 |
+ sCmd = 'MC%0.3d;' % iMemloc |
|
218 |
+ sResult = sendCommand(sCmd) |
|
219 |
+ |
|
220 |
+ # While the 'MW' and 'MT' commands can be used to turn the Rx |
|
221 |
+ # and Tx clarifiers on, the clarifier states can only be turned |
|
222 |
+ # off by sending the 'RT0' and 'XT0' commands. This situation is |
|
223 |
+ # probably due to a bug in the CAT interface. |
|
224 |
+ sResult = sendCommand('RC;RT0;XT0;') |
|
225 |
+ |
|
226 |
+ # Format the set memory with tag command (MT). |
|
227 |
+ sCmd = 'MT%0.3d' % iMemloc |
|
228 |
+ |
|
229 |
+ # Validate and append the vfo-a frequency data. |
|
230 |
+ iRxfreq = int(float(dMem['rxfreq']) * 1E6) # vfo-a frequency in Hz |
|
231 |
+ if iRxfreq < 0.030E6 or iRxfreq > 450.0E6: |
|
232 |
+ raise Exception('VFO-A frequency must be between 30 kHz and ' \ |
|
233 |
+ '450 MHz, inclusive.') |
|
234 |
+ sCmd += '%0.9d' % iRxfreq |
|
235 |
+ |
|
236 |
+ # Validate and append the clarifier data. |
|
237 |
+ iClarfreq = int(dMem['clarfreq']) |
|
238 |
+ if abs(iClarfreq) > 9999: |
|
239 |
+ raise Exception('Clarifer frequency must be between -9999 Hz ' \ |
|
240 |
+ 'and +9999 Hz, inclusive.') |
|
241 |
+ sCmd += '%+0.4d' % iClarfreq |
|
242 |
+ |
|
243 |
+ # The following commands will automatically raise an exception if |
|
244 |
+ # incorrect data is supplied. The exception will be a dictionary |
|
245 |
+ # object "key not found" error. |
|
246 |
+ sCmd += dRxClar[dMem['rxclar']] |
|
247 |
+ sCmd += dTxClar[dMem['txclar']] |
|
248 |
+ sCmd += dMode[dMem['mode']] |
|
249 |
+ sCmd += '0' |
|
250 |
+ sCmd += dEncode[dMem['encode']] |
|
251 |
+ sCmd += '00' |
|
252 |
+ sCmd += dShift[dMem['shift']] |
|
253 |
+ sCmd += '0' |
|
254 |
+ sTag = dMem['tag'] |
|
255 |
+ |
|
256 |
+ # Validate and append the memory tag data. |
|
257 |
+ if len(sTag) > 12: |
|
258 |
+ raise Exception('Memory tags must be twelve characters or less.') |
|
259 |
+ sCmd += '%-12s' % sTag |
|
260 |
+ sCmd += ';' # Terminate the completed command. |
|
261 |
+ |
|
262 |
+ # Send the completed command. |
|
263 |
+ sResult = sendCommand(sCmd) |
|
264 |
+ return sResult |
|
265 |
+## end def |
|
266 |
+ |
|
232 | 267 |
def setCTCSS(tone): |
233 | 268 |
""" |
234 |
- Description: returns a formatted CN command that sets the desired |
|
269 |
+ Description: Sends a formatted CN command that sets the desired |
|
235 | 270 |
CTCSS tone. |
236 | 271 |
Parameters: tone - a string containing the CTCSS tone in Hz, e.g., |
237 | 272 |
'100 Hz' |
238 | 273 |
Returns: a string containing the formatted command |
239 | 274 |
""" |
275 |
+ # An exception will automatically be raised if incorrect data is |
|
276 |
+ # supplied - most likely a "key not found" error. |
|
240 | 277 |
sCmd = 'CN00%s;' % dTones[tone] |
241 | 278 |
return sendCommand(sCmd) |
242 | 279 |
## end def |
243 | 280 |
|
244 | 281 |
def setDCS(code): |
245 | 282 |
""" |
246 |
- Description: returns a formatted CN command that sets the desired |
|
283 |
+ Description: Sends a formatted CN command that sets the desired |
|
247 | 284 |
DCS code. |
248 |
- Parameters: code - a string containing the DCS code, e.g., '23' |
|
285 |
+ Parameters: code - a string containing the DCS code, e.g., '23' |
|
249 | 286 |
Returns: a string containing the formatted command |
250 | 287 |
""" |
288 |
+ # An exception will automatically be raised if incorrect data is |
|
289 |
+ # supplied - most likely a "key not found" error. |
|
251 | 290 |
sCmd = 'CN01%s;' % dDcs[code] |
252 | 291 |
return sendCommand(sCmd) |
253 | 292 |
## end def |
254 | 293 |
|
255 | 294 |
def setPower(power): |
256 | 295 |
""" |
257 |
- Description: returns a formatted PC command that sets the desired |
|
296 |
+ Description: Sends a PC command that sets the desired |
|
258 | 297 |
RF transmit power level. |
259 | 298 |
Parameters: power - Watts, an integer between 5 and 100 |
260 | 299 |
Returns: a string containing the formatted command |
261 | 300 |
""" |
262 |
- sCmd = 'PC' |
|
263 |
- sCmd += '%03.d;' % power |
|
301 |
+ power = int(power) |
|
302 |
+ # Validate power data and format command. |
|
303 |
+ if power < 5 or power > 100: |
|
304 |
+ raise Exception('Power must be between 0 and 100 watts, inclusive.') |
|
305 |
+ sCmd += 'PC%03.d;' % power |
|
264 | 306 |
return sendCommand(sCmd) |
265 | 307 |
## end def |
266 | 308 |
|
309 |
+# Helper functions to assist in various tasks. |
|
310 |
+ |
|
267 | 311 |
def parseCsvData(sline): |
268 | 312 |
""" |
269 | 313 |
Description: stores each item in the comma delimited line in a single |
... | ... |
@@ -286,14 +330,43 @@ def parseCsvData(sline): |
286 | 330 |
dChan['tag'] = lchan[6] |
287 | 331 |
dChan['encode'] = lchan[7] |
288 | 332 |
dChan['tone'] = lchan[8] |
289 |
- dChan['dcs'] = str(int(lchan[9])) |
|
333 |
+ dChan['dcs'] = lchan[9] |
|
290 | 334 |
dChan['clarfreq'] = lchan[10] |
291 | 335 |
dChan['rxclar'] = lchan[11] |
292 | 336 |
dChan['txclar'] = lchan[12] |
293 | 337 |
return dChan # return the dictionary object |
294 | 338 |
## end def |
295 | 339 |
|
296 |
-# Define serial communications functions. |
|
340 |
+def sendCommand(sCmd): |
|
341 |
+ """ |
|
342 |
+ Description: Sends a formatted FT911 command to the communication |
|
343 |
+ port connected to the FT991. Prints to stdout the |
|
344 |
+ answer from the FT991 (if any). |
|
345 |
+ Parameters: device - a pointer to the FT991 comm port |
|
346 |
+ sCmd - a string containing the formatted command |
|
347 |
+ Returns: nothing |
|
348 |
+ """ |
|
349 |
+ # Debug mode in conjunction with verbose mode is for verifying |
|
350 |
+ # correct formatting of commands before they are actually sent |
|
351 |
+ # to the FT991. |
|
352 |
+ if verbose: |
|
353 |
+ print sCmd, |
|
354 |
+ # In debug mode do not actually send commands to the FT991. |
|
355 |
+ if debug: |
|
356 |
+ return '' |
|
357 |
+ |
|
358 |
+ # Send the formatted command to the FT991 and get an answer, if any. |
|
359 |
+ # If the command does not generate an answer, no characters will be |
|
360 |
+ # returned by the FT991, resulting in an empty string returned by |
|
361 |
+ # the receiveSerial function. |
|
362 |
+ sendSerial(sCmd) |
|
363 |
+ sResult = receiveSerial(); |
|
364 |
+ if verbose: |
|
365 |
+ print sResult |
|
366 |
+ return sResult |
|
367 |
+## end def |
|
368 |
+ |
|
369 |
+# Low level serial communications functions. |
|
297 | 370 |
|
298 | 371 |
def begin(baud=9600): |
299 | 372 |
""" |
... | ... |
@@ -316,6 +389,7 @@ def begin(baud=9600): |
316 | 389 |
# In debug mode do not actually send commands to the FT991. |
317 | 390 |
if debug: |
318 | 391 |
return |
392 |
+ |
|
319 | 393 |
# Create a FT991 object for serial communication |
320 | 394 |
try: |
321 | 395 |
ptrDevice = serial.Serial(port, baud, |
... | ... |
@@ -332,35 +406,6 @@ def begin(baud=9600): |
332 | 406 |
return ptrDevice |
333 | 407 |
## end def |
334 | 408 |
|
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 | 409 |
def receiveSerial(termchar=';'): |
365 | 410 |
""" |
366 | 411 |
Description: Reads output one character at a time from the device |
... | ... |
@@ -413,6 +458,10 @@ def sendSerial(command): |
413 | 458 |
ptrDevice.flushOutput() # Flush serial buffer to prevent overflows |
414 | 459 |
## end def |
415 | 460 |
|
461 |
+# Main routine only gets called when this module is run as a program rather |
|
462 |
+# than imported into another python module. Code testing the functions in |
|
463 |
+# this module should be placed here. |
|
464 |
+ |
|
416 | 465 |
def main(): |
417 | 466 |
""" |
418 | 467 |
Description: Place code for testing this module here. |
... | ... |
@@ -425,17 +474,19 @@ def main(): |
425 | 474 |
verbose = True |
426 | 475 |
debug = False |
427 | 476 |
|
477 |
+ # Instantiate serial connection to FT991 |
|
428 | 478 |
begin() |
429 |
- sendCommand('IF;') |
|
430 |
- sendCommand('MC001;') |
|
431 |
- sendCommand('ZZZ;') |
|
432 |
- |
|
479 |
+ # Commands that send a setting |
|
433 | 480 |
dMem = {'rxfreq': '146.52', 'shift': 'OFF', 'encode': 'OFF', \ |
434 | 481 |
'txclar': 'OFF', 'tag': 'KA7JLO', 'mode': 'FM', 'rxclar': 'OFF', \ |
435 | 482 |
'memloc': '99', 'clarfreq': '0'} |
436 | 483 |
setMemory(dMem) |
437 |
- print getMemory(99) |
|
438 |
- |
|
484 |
+ sendCommand('MC002;') |
|
485 |
+ # Commands that return data |
|
486 |
+ getMemory(99) |
|
487 |
+ sendCommand('IF;') |
|
488 |
+ # Invalid command handling |
|
489 |
+ sendCommand('ZZZ;') |
|
439 | 490 |
## end def |
440 | 491 |
|
441 | 492 |
if __name__ == '__main__': |
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() |