Module morseToneGeneration
[hide private]
[frames] | no frames]

Source Code for Module morseToneGeneration

  1  #!/usr/bin/env python 
  2   
  3  import subprocess; 
  4  import time; 
  5  import os; 
  6  import signal; 
  7  import threading; 
  8  from datetime import datetime; 
  9  from watchdogTimer import WatchdogTimer; 
 10  from morseCodeTranslationKey import codeKey; 
 11   
12 -class Morse:
13 DOT = 0; 14 DASH = 1;
15
16 -class TimeoutReason:
17 END_OF_LETTER = 0 18 END_OF_WORD = 1 19 BAD_MORSE_INPUT = 2
20
21 -class MorseGenerator(object):
22 ''' 23 Manages non-UI issues for Morse code generation: Interacts with the 24 tone generator, regulates auto dot/dash generation speed. 25 ''' 26
27 - def __init__(self, callback=None):
28 super(MorseGenerator, self).__init__(); 29 30 # ------ Instance Vars Available Through Getters/Setters ------------ 31 32 # Frequency of the tone: 33 self.frequency = 300; # Hz 34 35 self.callback = callback; 36 37 self.interLetterDelayExplicitlySet = False; 38 self.interWordDelayExplicitlySet = False; 39 # setSpeed() must be called before setting up 40 # watchdog timer below: 41 self.setSpeed(3.3); 42 self.automaticMorse = True; 43 44 self.recentDots = 0; 45 self.recentDashes = 0; 46 # morseResult will accumulate the dots and dashes: 47 self.morseResult = ''; 48 self.alphaStr = ''; 49 self.watchdog = WatchdogTimer(timeout=self.interLetterTime, callback=self.watchdogExpired); 50 51 # ------ Private Instance Vars ------------ 52 53 self.morseDashEvent = threading.Event(); 54 self.morseDotEvent = threading.Event(); 55 self.morseStopEvent = threading.Event(); 56 57 # Lock for regulating write-access to alpha string: 58 self.alphaStrLock = threading.Lock(); 59 # Lock for regulating write-access to mores elements 60 # delivered from the dot and dash threads: 61 self.morseResultLock = threading.Lock(); 62 63 # Set to False to stop thread: 64 self.keepRunning = True; 65 66 self.dotGenerator = MorseGenerator.DotGenerator(self).start(); 67 self.DashGenerator = MorseGenerator.DashGenerator(self).start();
68 69 # ------------------------------ Public Methods --------------------- 70
71 - def startMorseSeq(self, morseElement):
72 ''' 73 Start a sequence of dots or dashes. If auto-morse is False, 74 only one dot or dash is produced per call. Else a dashes or 75 dots are produced at an adjustable speed until stopMorseSeq() 76 is called. Use setAutoMorse() to control whether single-shot 77 or automatic sequences are produced. Use setSpeed() to set 78 the speed at which the signals are generated. 79 80 After stopMorseSeq() is called, use getRecentDots() or 81 getRecentDashes() return the number of dots or dashes that 82 were generated. 83 84 @param morseElement: whether to produce dots or dashes 85 @type morseElement: Morse enum 86 ''' 87 88 if not self.keepRunning: 89 raise RuntimeError("Called Morse generator method after stopMorseGenerator() was called."); 90 # We are starting a sequence of dots and dashes. 91 # The Morse element can't possibly be ended until 92 # stopMorseSeq() is called by mouse cursor leaving 93 # a gesture button: 94 self.watchdog.stop(); 95 # Set a thread event for which dot or dash generator are waiting: 96 if morseElement == Morse.DASH: 97 self.morseDashEvent.set(); 98 elif morseElement == Morse.DOT: 99 self.morseDotEvent.set();
100
101 - def stopMorseSeq(self):
102 ''' 103 Stop a running sequence of dots or dashes. After this call, 104 use getRecentDots() or getRecentDashes() to get a count of 105 Morse elements (dots or dashes) that were emitted. If automatic 106 Morse generation is disabled, it is not necessary to call this 107 method. See setAutoMorse(). Called when mouse cursor leaves 108 109 ''' 110 if not self.keepRunning: 111 raise RuntimeError("Called Morse generator method after stopMorseGenerator() was called."); 112 # Clear dot and dash generation events so that the 113 # generator threads will go into a wait state. 114 self.morseDotEvent.clear(); 115 self.morseDashEvent.clear(); 116 # If there will now be a pause long enough 117 # to indicate the end of a letter, this 118 # watchdog will go off: 119 self.watchdog.kick(_timeout=self.interLetterTime, callbackArg=TimeoutReason.END_OF_LETTER);
120
121 - def abortCurrentMorseElement(self):
122 self.watchdog.stop(); 123 self.setMorseResult('');
124
125 - def setAutoMorse(self, yesNo):
126 ''' 127 Determine whether dots and dashes are generated one at 128 a time each time startMorseSeq() is called, or whether 129 each call generates sequences of signals until stopMorseGenerator() 130 is called. 131 @param yesNo: 132 @type yesNo: 133 ''' 134 self.automaticMorse = yesNo;
135
136 - def numRecentDots(self):
137 return self.recentDots;
138
139 - def numRecentDashes(self):
140 return self.recentDashes;
141
142 - def setSpeed(self, dotsPlusPausePerSec):
143 ''' 144 Sets speed at which automatic dots and dashes are 145 generated. Units are Herz. One cycle is a single 146 dot, followed by the pause that separates two dots. 147 Since that pause is standardized to be equal to 148 a dot length, one can think of the speed as the 149 number times the letter "i" is generated per second. 150 151 The higher the number, the faster the dots/dashes are 152 generated. Default is 3.3. 153 154 @param dotsPlusPausePerSec: rate at which dots or dashes are produced in automatic mode. 155 @type dotsPlusPausePerSec: float 156 ''' 157 self.dotDuration = 1.0/(2*dotsPlusPausePerSec); 158 #self.dashDuration = 3*self.dotDuration; 159 self.dashDuration = 2*self.dotDuration; 160 # Times between the automatically generated 161 # dots and dashes: 162 self.interSigPauseDots = self.dotDuration; 163 self.interSigPauseDashes = self.dotDuration; 164 165 # Unless client explicitly set dwell time between 166 # letters and words, compute a default now: 167 if not self.interLetterDelayExplicitlySet: 168 self.interLetterTime = 7.0*self.dotDuration; 169 if not self.interWordDelayExplicitlySet: 170 # Turn automatic word segmentation off by default: 171 self.interWordTime = -1; 172 #self.interWordTime = 9.0*self.dotDuration; 173 self.waitDashDotThreadsIdleTime = 0.5 * self.interLetterTime;
174
175 - def setInterLetterDelay(self, secs):
176 ''' 177 Sets the time that must elapse between two letters. 178 @param secs: fractional seconds 179 @type secs: float 180 ''' 181 self.interLetterTime = secs; 182 self.watchdog.changeTimeout(self.interLetterTime); 183 self.waitDashDotThreadsIdleTime = 0.5 * self.interLetterTime; 184 self.interLetterDelayExplicitlySet = True;
185
186 - def setInterWordDelay(self, secs):
187 ''' 188 Sets the time that must elapse between two words. 189 If negative, no word segmentation is performed. 190 @param secs: fractional seconds 191 @type secs: float 192 ''' 193 self.interWordTime = secs; 194 # Indicate that client explicitly set the 195 # inter-word dwell time. This note will prevent 196 # setSpeed() from defining its default dwell: 197 self.interWordDelayExplicitlySet = True;
198
199 - def stopMorseGenerator(self):
200 ''' 201 Stop the entire generator. All threads are killed. 202 Subsequent calls to startMorseSeq() or stopMorseSeq() 203 generate exceptions. 204 ''' 205 self.keepRunning = False; 206 # Ensure that the dot and dash generators 207 # get out of the wait state to notice that 208 # they are supposed to terminate: 209 self.morseDashEvent.set(); 210 self.morseDotEvent.set();
211
212 - def getAndRemoveAlphaStr(self):
213 res = self.alphaStr; 214 self.setAlphaStr(''); 215 return res;
216
217 - def setAlphaStr(self, newAlphaStr):
218 with self.alphaStrLock: 219 self.alphaStr = newAlphaStr
220
221 - def setMorseResult(self, newMorseResult):
222 with self.morseResultLock: 223 self.morseResult = newMorseResult;
224
225 - def getInterLetterTime(self):
226 ''' 227 Return the minimum amount of silence time required 228 for the system to conclude that the Morse code equivalent 229 of a letter has been generated. I.e.: end-of-letter pause. 230 ''' 231 return self.interLetterTime;
232
233 - def getInterWordTime(self):
234 ''' 235 Return the minimum amount of silence time required 236 for the system to conclude that a word has ended. 237 I.e.: end-of-word pause. 238 ''' 239 return self.interWordTime;
240
241 - def reallySleep(self, secs):
242 ''' 243 Truly return only after specified time. Just using 244 time.sleep() sleeps shorter if any interrupts occur during 245 the sleep period. 246 @param secs: fractional seconds to sleep 247 @type secs: float 248 ''' 249 sleepStop = time.time() + secs; 250 while True: 251 timeLeft = sleepStop - time.time(); 252 if timeLeft <= 0: 253 return 254 time.sleep(timeLeft)
255 256 257 # ------------------------------ Private --------------------- 258
259 - def addMorseElements(self, dotsOrDashes, numElements):
260 if dotsOrDashes == Morse.DASH: 261 self.setMorseResult(self.morseResult + '-'*numElements); 262 else: # dots: 263 # Catch abort-letter: 264 if numElements > 7: 265 self.abortCurrentMorseElement(); 266 return; 267 self.setMorseResult(self.morseResult + '.'*numElements);
268
269 - def watchdogExpired(self, reason):
270 271 detail = ''; 272 if reason == TimeoutReason.END_OF_LETTER: 273 # If no Morse elements are in self.morseResult, 274 # it could be because the dash or dot thread are 275 # not done delivering their result. In that case, 276 # set the timer again to get them idle: 277 if len(self.morseResult) == 0: 278 self.watchdog.kick(self.waitDashDotThreadsIdleTime); 279 return; 280 newLetter = self.decodeMorseLetter(); 281 # If Morse sequence was legal and recognized, 282 # append it: 283 if newLetter is not None: 284 self.setAlphaStr(self.alphaStr + newLetter); 285 else: 286 reason = TimeoutReason.BAD_MORSE_INPUT; 287 detail = self.morseResult; 288 # One way or other, a letter has ended. Start the 289 # timeout for a word-separation sized pause: 290 if self.interWordTime > 0: 291 self.watchdog.kick(_timeout=self.interWordTime, callbackArg=TimeoutReason.END_OF_WORD); 292 elif reason == TimeoutReason.END_OF_WORD: 293 # Decided to have the client decide what to do when a word ends. 294 # The commented code would add a space: 295 #self.setAlphaStr(self.alphaStr + ' '); 296 pass; 297 self.setMorseResult(''); 298 if self.callback is not None: 299 self.callback(reason, detail);
300
301 - def decodeMorseLetter(self):
302 try: 303 letter = codeKey[self.morseResult]; 304 if letter == 'BS': 305 letter = '\b'; 306 elif letter == 'NL': 307 letter = '\r'; 308 elif letter == 'HS': 309 letter = ' '; 310 except KeyError: 311 #print("Bad morse seq: '%s'" % self.morseResult); 312 return None 313 return letter;
314 315 #----------------------------- 316 # DotGenerator Class 317 #------------------- 318
319 - class DotGenerator(threading.Thread):
320
321 - def __init__(self, parent):
322 super(MorseGenerator.DotGenerator,self).__init__(); 323 self.parent = parent; 324 self.idle = True;
325
326 - def isIdle(self):
327 return self.idle;
328
329 - def run(self):
330 self.genDots();
331
332 - def genDots(self):
333 334 # Initially: wait for first dot request. 335 self.parent.morseDotEvent.wait(); 336 # No dots generated yet: 337 numDots = 0; 338 339 while self.parent.keepRunning: 340 341 while self.parent.morseDotEvent.is_set(): 342 343 self.idle = False; 344 345 # Use Alsa utils speaker tester to produce sound: 346 proc = subprocess.Popen(['speaker-test', 347 "--test", "sine", 348 "--frequency", str(self.parent.frequency)], 349 stdout=subprocess.PIPE); # suppress speaker-test display output 350 time.sleep(self.parent.dotDuration); 351 os.kill(proc.pid, signal.SIGUSR1) 352 353 numDots += 1; 354 if not self.parent.automaticMorse: 355 # Not doing automatic dot generation. 356 # So clear the dot event that controls out 357 # loop. That would otherwise happen 358 # in self.parent.stopMorseSequence(), which 359 # clients call: 360 self.parent.morseDotEvent.clear(); 361 else: 362 # Auto dot generation: pause for 363 # the inter-dot period: 364 self.parent.reallySleep(self.parent.interSigPauseDots); 365 366 self.parent.addMorseElements(Morse.DOT, numDots); 367 # Get ready for the next request for dot sequences: 368 numDots = 0; 369 self.idle = True; 370 self.parent.morseDotEvent.wait();
371 372 #----------------------------- 373 # DashGenerator Class 374 #------------------- 375
376 - class DashGenerator(threading.Thread):
377
378 - def __init__(self, parent):
379 super(MorseGenerator.DashGenerator,self).__init__(); 380 self.parent = parent; 381 self.idle = True;
382
383 - def isIdle(self):
384 return self.idle;
385
386 - def run(self):
387 self.genDashes();
388
389 - def genDashes(self):
390 391 # Initially: wait for first dash request. 392 self.parent.morseDashEvent.wait(); 393 # No dashes generated yet: 394 numDashes = 0; 395 396 while self.parent.keepRunning: 397 while self.parent.morseDashEvent.is_set(): 398 399 self.idle = False; 400 401 # Speaker-test subprocess will run until we kill it: 402 proc = subprocess.Popen(['speaker-test', 403 "--test", "sine", 404 "--frequency", str(self.parent.frequency)], 405 stdout=subprocess.PIPE); # Suppress print output 406 time.sleep(self.parent.dashDuration); 407 os.kill(proc.pid, signal.SIGUSR1) 408 409 numDashes += 1; 410 if not self.parent.automaticMorse: 411 # Not doing automatic dash generation. 412 # So clear the dash event that controls out 413 # loop. That would otherwise happen 414 # in self.parent.stopMorseSequence(), which 415 # clients call: 416 self.parent.morseDashEvent.clear(); 417 else: 418 # Auto dot generation: pause for 419 # the inter-dash period: 420 self.parent.reallySleep(self.parent.interSigPauseDashes); 421 422 self.parent.addMorseElements(Morse.DASH, numDashes); 423 # Get ready for the next request for dashes sequences: 424 numDashes = 0; 425 self.idle = True; 426 self.parent.morseDashEvent.wait();
427 428 if __name__ == "__main__": 429 430 generator = MorseGenerator(); 431 432 # Initially: not automatic: 433 generator.setAutoMorse(False); 434 435 # generator.startMorseSeq(Morse.DOT); 436 # time.sleep(1); 437 # generator.startMorseSeq(Morse.DASH); 438 # time.sleep(1); 439 # print("Dots: %d. Dashes: %d" % (generator.numRecentDots(), generator.numRecentDashes())); 440 # 441 # generator.setAutoMorse(True); 442 # 443 # generator.startMorseSeq(Morse.DOT); 444 # time.sleep(1); 445 # generator.stopMorseSeq(); 446 # time.sleep(0.5); 447 # 448 # generator.startMorseSeq(Morse.DASH); 449 # time.sleep(1); 450 # generator.stopMorseSeq(); 451 # time.sleep(1.5); 452 # print("Dots: %d. Dashes: %d" % (generator.numRecentDots(), generator.numRecentDashes())); 453 # 454 # generator.setSpeed(6.0); #Hz 455 # generator.startMorseSeq(Morse.DOT); 456 # time.sleep(1); 457 # generator.stopMorseSeq(); 458 # time.sleep(0.5); 459 # 460 # generator.startMorseSeq(Morse.DASH); 461 # time.sleep(1); 462 # generator.stopMorseSeq(); 463 # time.sleep(0.5); 464 # print("Dots: %d. Dashes: %d" % (generator.numRecentDots(), generator.numRecentDashes())); 465 # 466 # generator.setSpeed(12.0); #Hz 467 # generator.startMorseSeq(Morse.DOT); 468 # time.sleep(1); 469 # generator.stopMorseSeq(); 470 # time.sleep(0.5); 471 # 472 # generator.startMorseSeq(Morse.DASH); 473 # time.sleep(1); 474 # generator.stopMorseSeq(); 475 # time.sleep(0.5); 476 # print("Dots: %d. Dashes: %d" % (generator.numRecentDots(), generator.numRecentDashes())); 477 478 # generator.addMorseElements(Morse.DASH, 1) 479 # time.sleep(generator.interLetterTime + 0.1); 480 # letterLookup = generator.decodeMorseLetter(); 481 # #for key in sorted(codeKey.keys()): 482 # # print(str(key) + ':' + codeKey[key]) 483 # if letterLookup != 't': 484 # raise ValueError("Morse 't' not recognized") 485