├── meta.yml ├── cover.png ├── images ├── p1.png ├── cover.png ├── morse.png ├── MFJ-550.png ├── decoding.png ├── pull_up.png ├── pull_down.png ├── antique_key.png ├── homebrew_key.png ├── pull_down_key.png ├── pull_up_key.png ├── qst_may_1942.png ├── morse_listening.png ├── jumper_wires_key.png └── FWW_Centenary__Led_By_IWM_Red-web.png ├── sounds └── slow_morse.mp3 ├── extras.md ├── hardware.yml ├── overview.md ├── LICENCE.md ├── hardware.md ├── learn.md ├── code ├── morse_lookup.py └── final_code.py ├── CONTRIBUTING.md ├── .gitignore ├── README.md └── worksheet.md /meta.yml: -------------------------------------------------------------------------------- 1 | title: Morse Code Virtual Radio 2 | category: make 3 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/cover.png -------------------------------------------------------------------------------- /images/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/p1.png -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/cover.png -------------------------------------------------------------------------------- /images/morse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/morse.png -------------------------------------------------------------------------------- /images/MFJ-550.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/MFJ-550.png -------------------------------------------------------------------------------- /images/decoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/decoding.png -------------------------------------------------------------------------------- /images/pull_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/pull_up.png -------------------------------------------------------------------------------- /images/pull_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/pull_down.png -------------------------------------------------------------------------------- /sounds/slow_morse.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/sounds/slow_morse.mp3 -------------------------------------------------------------------------------- /images/antique_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/antique_key.png -------------------------------------------------------------------------------- /images/homebrew_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/homebrew_key.png -------------------------------------------------------------------------------- /images/pull_down_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/pull_down_key.png -------------------------------------------------------------------------------- /images/pull_up_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/pull_up_key.png -------------------------------------------------------------------------------- /images/qst_may_1942.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/qst_may_1942.png -------------------------------------------------------------------------------- /images/morse_listening.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/morse_listening.png -------------------------------------------------------------------------------- /images/jumper_wires_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/jumper_wires_key.png -------------------------------------------------------------------------------- /extras.md: -------------------------------------------------------------------------------- 1 | You may also need 2 | 3 | - A set of headphones 4 | - A [speaker](http://thepihut.com/products/mini-portable-speaker-for-the-raspberry-pi/) 5 | -------------------------------------------------------------------------------- /images/FWW_Centenary__Led_By_IWM_Red-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raspberrypilearning/morse-code-virtual-radio/HEAD/images/FWW_Centenary__Led_By_IWM_Red-web.png -------------------------------------------------------------------------------- /hardware.yml: -------------------------------------------------------------------------------- 1 | - name: 1 x Morse Code key 2 | img: morse-key 3 | - name: 2 (or more) x Male-to-female jumper wires 4 | img: jumper-male-to-female 5 | - name: Headphones or speakers 6 | img: headphones-speaker 7 | -------------------------------------------------------------------------------- /overview.md: -------------------------------------------------------------------------------- 1 | In this resource you will connect a Morse tapper key to the Raspberry Pi GPIO pins and write code to play tones when you hold the key down. You will also decode the Morse that you're keying so that it comes up on the screen. 2 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | # Licence 2 | 3 | Unless otherwise specified, everything in this repository is covered by the following licence: 4 | 5 | [![Creative Commons License](http://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) 6 | 7 | ***Morse Code Virtual Radio*** by the [Raspberry Pi Foundation](https://www.raspberrypi.org/) is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/). 8 | 9 | Based on a work at https://github.com/raspberrypilearning/morse-code-virtual-radio 10 | -------------------------------------------------------------------------------- /hardware.md: -------------------------------------------------------------------------------- 1 | # Hardware Requirements 2 | 3 | A real Morse Code key will bring this project to life, especially if you can acquire an antique one. These can often be found in local antique shops all over the country. Alternatively you can also buy one online; see the links below. 4 | 5 | - Buy new online (try [Nevada Radio](http://www.nevadaradio.co.uk/amateur-radio/morse-keys/mfj-550)) 6 | 7 | ![](images/MFJ-550.png) 8 | 9 | - Find an antique (try [eBay](http://search.ebay.co.uk/antique+morse+code+key)) 10 | 11 | Make sure it works! 12 | 13 | ![](images/antique_key.png) 14 | 15 | - Make your own! Instructions [like these](http://www.w1tp.com/perbuild.htm) are readily available online. 16 | 17 | ![](images/homebrew_key.png) 18 | -------------------------------------------------------------------------------- /learn.md: -------------------------------------------------------------------------------- 1 | By creating a Morse code virtual radio with your Raspberry Pi you will: 2 | 3 | - Understand what Morse Code is 4 | - Understand how Morse was used for communication 5 | - Understand how to send and receive Morse 6 | - Understand a pull up circuit 7 | - Understand a pull down circuit 8 | - Understand multithreading 9 | - Gain experience in Python programming 10 | - Gain experience using the Raspberry Pi GPIO pins 11 | - Gain experience in multithreaded programming 12 | 13 | This resource covers elements from the following strands of the [Raspberry Pi Digital Making Curriculum](https://www.raspberrypi.org/curriculum/): 14 | 15 | - [Apply abstraction and decomposition to solve more complex problems](https://www.raspberrypi.org/curriculum/programming/developer) 16 | - [Combine inputs and/or outputs to create projects or solve a problem](https://www.raspberrypi.org/curriculum/physical-computing/builder) 17 | - [Use manufacturing techniques and tools to create prototype projects](https://www.raspberrypi.org/curriculum/manufacture/builder) 18 | -------------------------------------------------------------------------------- /code/morse_lookup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | 4 | morse_code_lookup = { 5 | ".-": "A", 6 | "-...": "B", 7 | "-.-.": "C", 8 | "-..": "D", 9 | ".": "E", 10 | "..-.": "F", 11 | "--.": "G", 12 | "....": "H", 13 | "..": "I", 14 | ".---": "J", 15 | "-.-": "K", 16 | ".-..": "L", 17 | "--": "M", 18 | "-.": "N", 19 | "---": "O", 20 | ".--.": "P", 21 | "--.-": "Q", 22 | ".-.": "R", 23 | "...": "S", 24 | "-": "T", 25 | "..-": "U", 26 | "...-": "V", 27 | ".--": "W", 28 | "-..-": "X", 29 | "-.--": "Y", 30 | "--..": "Z", 31 | ".----": "1", 32 | "..---": "2", 33 | "...--": "3", 34 | "....-": "4", 35 | ".....": "5", 36 | "-....": "6", 37 | "--...": "7", 38 | "---..": "8", 39 | "----.": "9", 40 | "-----": "0" 41 | } 42 | 43 | def try_decode(bit_string): 44 | if bit_string in morse_code_lookup.keys(): 45 | sys.stdout.write(morse_code_lookup[bit_string]) 46 | sys.stdout.flush() 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributions are assumed to be licensed under the same licence as the source, i.e. [CC BY-SA](http://creativecommons.org/licenses/by-sa/4.0/). This licence must remain in all derivatives of this work. 4 | 5 | ## Issues 6 | 7 | If you find a mistake, bug or other problem, please [open an issue](https://github.com/raspberrypilearning/morse-code-virtual-radio/issues) in this repository. 8 | 9 | ## Pull Requests 10 | 11 | If you fix a mistake, bug or problem or have something to contribute, please create a pull request for each modification. Please consider grouping modifications sensibly, i.e. don't bundle typo fixes in the same pull request as code changes, instead file them separately. 12 | 13 | Please note that sometimes things are done for pedagogical reasons so changes which make sense from a software engineering perspective (reducing duplication or making use of more advanced programming language features) may not be suitable to maintain the intended educational value. 14 | 15 | ## Derivatives 16 | 17 | The licence must remain in all derivatives of this work. 18 | 19 | ## Licence 20 | 21 | Unless otherwise specified, everything in this repository is covered by the following licence: 22 | 23 | [![Creative Commons License](http://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) 24 | 25 | ***Morse Code Virtual Radio*** by the [Raspberry Pi Foundation](https://www.raspberrypi.org/) is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/). 26 | 27 | Based on a work at https://github.com/raspberrypilearning/morse-code-virtual-radio 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /code/final_code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import pygame 3 | import time 4 | import gpiozero as gpio 5 | import _thread as thread 6 | from array import array 7 | from pygame.locals import * 8 | from morse_lookup import * 9 | 10 | pygame.mixer.pre_init(44100, -16, 1, 1024) 11 | pygame.init() 12 | 13 | class ToneSound(pygame.mixer.Sound): 14 | def __init__(self, frequency, volume): 15 | self.frequency = frequency 16 | pygame.mixer.Sound.__init__(self, self.build_samples()) 17 | self.set_volume(volume) 18 | 19 | def build_samples(self): 20 | period = int(round(pygame.mixer.get_init()[0] / self.frequency)) 21 | samples = array("h", [0] * period) 22 | amplitude = 2 ** (abs(pygame.mixer.get_init()[1]) - 1) - 1 23 | for time in range(period): 24 | if time < period / 2: 25 | samples[time] = amplitude 26 | else: 27 | samples[time] = -amplitude 28 | return samples 29 | 30 | def decoder_thread(): 31 | global key_up_time 32 | global buffer 33 | new_word = False 34 | while True: 35 | time.sleep(.01) 36 | key_up_length = time.time() - key_up_time 37 | if len(buffer) > 0 and key_up_length >= 1.5: 38 | new_word = True 39 | bit_string = "".join(buffer) 40 | try_decode(bit_string) 41 | del buffer[:] 42 | elif new_word and key_up_length >= 4.5: 43 | new_word = False 44 | sys.stdout.write(" ") 45 | sys.stdout.flush() 46 | 47 | tone_obj = ToneSound(frequency = 800, volume = .5) 48 | 49 | pin = 4 50 | key = gpio.Button(pin, pull_up=True) 51 | 52 | DOT = "." 53 | DASH = "-" 54 | 55 | key_down_time = 0 56 | key_down_length = 0 57 | key_up_time = 0 58 | buffer = [] 59 | 60 | thread.start_new_thread(decoder_thread, ()) 61 | 62 | print("Ready") 63 | 64 | while True: 65 | key.wait_for_press() 66 | key_down_time = time.time() #record the time when the key went down 67 | tone_obj.play(-1) #the -1 means to loop the sound 68 | key.wait_for_release() 69 | key_up_time = time.time() #record the time when the key was released 70 | key_down_length = key_up_time - key_down_time #get the length of time it was held down for 71 | tone_obj.stop() 72 | buffer.append(DASH if key_down_length > 0.15 else DOT) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This is an archived resource.** The repo will remain available but the resource will no longer be maintained or updated. Some or all parts of the resource may no longer work. To see our latest resources, please visit [raspberrypi.org](http://www.raspberrypi.org). 2 | 3 | # Morse Code Virtual Radio 4 | 5 | How to program your Raspberry Pi to send, receive and decode Morse. 6 | 7 | ![](cover.png) 8 | 9 | 2014 marks the 100th anniversary of the declaration of [World War 1](http://en.wikipedia.org/wiki/World_War_I), which began on the 28th of July 1914. Over 16 million people lost their lives and 20 million were injured, making it one of the bloodiest conflicts in human history. It became known as the Great War and, subsequently, the First World War. To commemorate this event, we've come up with a way for you to simulate and experience the main form of radio communication that was used back then. Imagine being alive one hundred years ago, and sending and receiving messages that could mean life and death using only tones! 10 | 11 | ![](images/FWW_Centenary__Led_By_IWM_Red-web.png) 12 | 13 | *Note: This resource uses International Morse.* 14 | 15 | ## Lesson objectives 16 | 17 | - Understand what Morse Code is 18 | - Understand how Morse was used for communication 19 | - Understand how to send and receive Morse 20 | - Understand a pull up circuit 21 | - Understand a pull down circuit 22 | - Understand multithreading 23 | 24 | ## Lesson outcomes 25 | 26 | - To have programmed the Raspberry Pi to create Morse Code tones 27 | - To have sent and decoded messages in Morse Code 28 | - Gained experience in Python programming 29 | - Gained experience using the Raspberry Pi GPIO pins 30 | - Gained experience in multithreaded programming 31 | 32 | ## Requirements 33 | 34 | ### Hardware 35 | 36 | - 1 x [Morse Code key](http://www.nevadaradio.co.uk/amateur-radio/morse-keys/mfj-550) 37 | - 2 (or more) x [Male-to-female jumper wires](http://shop.pimoroni.com/products/jumper-jerky) 38 | - [Headphones or speakers](http://thepihut.com/products/mini-portable-speaker-for-the-raspberry-pi) 39 | 40 | ### Morse Code key options 41 | 42 | A real Morse Code key will bring this project to life, especially if you can acquire an antique one. These can often be found in local antique shops all over the country. Alternatively you can also buy one online; see the links below. 43 | 44 | - Buy new online (try [nevada radio](http://www.nevadaradio.co.uk/amateur-radio/morse-keys/mfj-550)) 45 | 46 | ![](images/MFJ-550.png) 47 | 48 | - Find an antique (try [eBay](http://search.ebay.co.uk/antique+morse+code+key)) 49 | 50 | Make sure it works! 51 | 52 | ![](images/antique_key.png) 53 | 54 | - Make your own! 55 | 56 | ![](images/homebrew_key.png) 57 | 58 | ## Worksheet & included files 59 | 60 | - [The worksheet](worksheet.md) 61 | - (Optional) Final version of Python code [final_code.py](code/final_code.py) and [morse_lookup.py](code/morse_lookup.py) 62 | - Download to your Pi with the following commands: 63 | 64 | ```bash 65 | wget https://goo.gl/K3Qlsa -O morse_lookup.py --no-check-certificate 66 | wget https://goo.gl/ilZqWF -O final_code.py --no-check-certificate 67 | sudo python final_code.py 68 | ``` 69 | 70 | ## Licence 71 | 72 | Unless otherwise specified, everything in this repository is covered by the following licence: 73 | 74 | [![Creative Commons License](http://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) 75 | 76 | ***Morse Code Virtual Radio*** by the [Raspberry Pi Foundation](https://www.raspberrypi.org/) is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/). 77 | 78 | Based on a work at https://github.com/raspberrypilearning/morse-code-virtual-radio 79 | -------------------------------------------------------------------------------- /worksheet.md: -------------------------------------------------------------------------------- 1 | # Morse Code Virtual Radio 2 | 3 | This tutorial will show you how to connect a Morse key to the Raspberry Pi GPIO pins, and how to write code to play tones when you hold the key down. You will also decode the Morse that you're keying so that it comes up on the screen. 4 | 5 | ## What is Morse Code? 6 | 7 | Invented by Samuel Morse in 1836, Morse Code is a method for sending and receiving text messages using short and long beeps. Conventionally, a short beep is called a **dot** and a long one is a **dash** (also known, respectively, as a **dit** and a **dah**). Every letter of the alphabet has a unique sequence of dots and dashes. 8 | 9 | If you look at the chart below, the letter **A** is beep beeeeeep and the letter **B** is beeeeeep beep beep beep. 10 | 11 | ![](images/morse.png) 12 | 13 | - All timings are defined as multiples of one dot length 14 | - A dash is three times the length of a dot 15 | - Each dot or dash has a short gap of silence after it (usually 1 dot length) 16 | - Letters in a word have a slightly longer gap of silence between them (usually 3 dot lengths) 17 | - Words have an even longer gap of silence between them (usually 7 dot lengths) 18 | 19 | You don't necessarily need to use sound for this, although this is the most common way Morse Code was used. You can do it with anything that can be turned on and off; this could be a torch, raising and lowering a flag, or even just blinking your eyes quickly and slowly. This makes it one of the most versatile forms of telecommunication. There is even a formal international treaty which enshrines the Morse Code for SOS `... --- ...` (Save Our Souls) as a universal distress signal. 20 | 21 | In the 1890s Morse Code was adapted for use with early radio before it was possible to send and receive voice. This was done by simply sending pulses of a carrier wave at an agreed frequency. The recipient's radio would then just play an audible tone whenever the carrier was received. It was used extensively during both World Wars and is still used to this day by amateur radio operators. 22 | 23 | There are three essentials to using Morse: 24 | 25 | - Knowing the code 26 | - Being able to key it in 27 | - Being able to decode it when listening 28 | 29 | The choice of the dot and dash combination for each letter is not random. Samuel Morse based his decision on how often letters occurred in the English language used by his local newspaper. The more commonly-used a letter was, the fewer dots and dashes he chose, thereby making it faster to key in. 30 | 31 | The chart below is the Morse Code tree, and is really helpful when listening and decoding; you might want to print it out and keep it in front of you. You can see that **E** and **T** are the most common letters. So you start at the top, go to the left if you hear a dot and to the right if you hear a dash. You can double-check this against the first chart: try it now for the letters **A** and **B**. 32 | 33 | ![listening](images/morse_listening.png) 34 | 35 | Get a pencil and paper and see how you get on with this: [listen to slow Morse Code](sounds/slow_morse.mp3). Don't be intimidated if you find following the code hard; it's always tricky to start with. As with many things, the more you do it the easier it gets. 36 | 37 | ## Play a test beep 38 | 39 | First boot up your Raspberry Pi and log in. 40 | 41 | As this exercise involves noise, you should use headphones if you are in a classroom environment, to avoid distracting others around you. If you are using headphones or a speaker on the Raspberry Pi, you will need to run the following command to redirect sound to the headphone socket: 42 | 43 | ```bash 44 | sudo amixer cset numid=3 1 45 | ``` 46 | 47 | First, we need some code to make the tone sound. Enter the following command to start editing a blank file (please note that you should use Python 3 for this project): 48 | 49 | ```bash 50 | nano morse-code.py 51 | ``` 52 | 53 | Now either copy and paste or enter the following code: 54 | 55 | ```python 56 | #!/usr/bin/python3 57 | import pygame 58 | import time 59 | from array import array 60 | from pygame.locals import * 61 | 62 | pygame.mixer.pre_init(44100, -16, 1, 1024) 63 | pygame.init() 64 | 65 | class ToneSound(pygame.mixer.Sound): 66 | def __init__(self, frequency, volume): 67 | self.frequency = frequency 68 | pygame.mixer.Sound.__init__(self, self.build_samples()) 69 | self.set_volume(volume) 70 | 71 | def build_samples(self): 72 | period = int(round(pygame.mixer.get_init()[0] / self.frequency)) 73 | samples = array("h", [0] * period) 74 | amplitude = 2 ** (abs(pygame.mixer.get_init()[1]) - 1) - 1 75 | for time in range(period): 76 | if time < period / 2: 77 | samples[time] = amplitude 78 | else: 79 | samples[time] = -amplitude 80 | return samples 81 | ``` 82 | 83 | You don't need to worry about the inner workings of this code, but, if you are interested, the code inherits one of the `pygame` sound classes, and automatically generates the wave data for playing a tone at a specified frequency. 84 | 85 | Don't worry if you've never seen a Python [class](http://en.wikipedia.org/wiki/Class_%28computer_programming%29) before. A class is like a blueprint of code that you can re-use multiple times. An instance of a class is known as an [object](http://en.wikipedia.org/wiki/Object-oriented_programming). 86 | 87 | Typical Morse Code tones are somewhere between 400 Hz and 1000 Hz; in this project, we will use a frequency of 800 Hz. In this code, `tone_obj` is the object that has been created from the blueprint `ToneSound`. 88 | 89 | Add the following code to the very bottom of the file: 90 | 91 | ```python 92 | tone_obj = ToneSound(frequency = 800, volume = .5) 93 | 94 | tone_obj.play(-1) #the -1 means to loop the sound 95 | time.sleep(2) 96 | tone_obj.stop() 97 | ``` 98 | Press `Ctrl + O` then `Enter` to save followed by `Ctrl + X` to quit. 99 | 100 | Next, mark the file as executable with the following command: 101 | 102 | ```python 103 | chmod +x morse-code.py 104 | ``` 105 | 106 | Now we can run the code; when you do, you should hear a nice two-second-long beep: 107 | 108 | ```python 109 | ./morse-code.py 110 | ``` 111 | 112 | If you didn't hear anything then double-check everything is plugged in correctly. If you're using the headphone jack of the Pi, remember that you'll need to use the command `sudo amixer cset numid=3 1` to redirect the audio. You may notice the tone sounds a bit wobbly at the start; this is just an artefact of `pygame` starting up and using up CPU cycles. Subsequent tones will sound correct. 113 | 114 | ## Connect the Morse Code key to the GPIO pins 115 | 116 | All Morse Code keys work in a similar way to a push button or switch. They have two screw terminals, to which a positive and a negative wire are attached. When you press the key down, two bits of metal touch, causing a circuit to complete. The effect would be the same if you just touched the two wires together. 117 | 118 | To connect the Morse key to the GPIO pins, we need to do a bit of physical computing. GPIO pins can be set up as an input or an output. Output mode is used when you want to supply voltage to a device like an LED or buzzer. If we use input mode instead, a GPIO pin has a value that we can read in our code. If the pin has voltage going into it, the reading would be `1` (HIGH); if the pin was connected directly to ground (no voltage), the reading would be `0` (LOW). 119 | 120 | The goal is to use the Morse Code key to switch voltage on and off for a GPIO pin, thus making the reading of the pin change in our code when we press the key. 121 | 122 | When a GPIO pin is in input mode the pin is said to be floating, meaning that it has no fixed voltage level. That's no good for what we want, as the pin will randomly float between HIGH and LOW. For this project, we need to know categorically whether the key is up or down. So we need to fix the voltage level to HIGH or LOW, and then make it change only when the key is pressed. We can do this in two ways. 123 | 124 | ### A pull up circuit 125 | 126 | Wire the GPIO pin to 3.3 volts through a large 10kΩ resistor so that it always reads HIGH. Then we can short the pin to ground via the Morse key, so that the pin will go LOW when you press it. 127 | 128 | ![](images/pull_up.png) 129 | 130 | ### A pull down circuit 131 | 132 | Wire the GPIO pin to ground through a large 10kΩ resistor so that it always reads LOW. Then we can short the pin to 3.3 volts through the Morse key, so that it goes HIGH when you press it. When the key is pressed there is a lower resistance path to 3.3 volts, and therefore the pin will read HIGH. 133 | 134 | ![](images/pull_down.png) 135 | 136 | Note: The 1kΩ R2 resistor is there in both circuits to give the GPIO pin a fail-safe protection, in case we mistakenly set the pin to be in OUTPUT mode. 137 | 138 | Fortunately, the Raspberry Pi has all the above circuitry built in and we can select either a pull up or a pull down circuit in our code for each GPIO pin. This sets up some internal circuitry that is too small for us to see. So you can get away with just using two jumper wires here, although you're welcome to wire it up the way shown above if you wish. Let's use GPIO pin #4 as an example: 139 | 140 | ### Pull up configuration 141 | 142 | GPIO pin #4 will be wired to 3.3 volts using the internal pull up resistor, so that it always reads HIGH. Then we can short the pin to ground via the Morse key, so that the pin will go LOW when you press it. 143 | 144 | ![](images/pull_up_key.png) 145 | 146 | ### Pull down configuration 147 | 148 | GPIO pin #4 will be wired to ground using the internal pull down resistor, so that it always reads LOW. Then we can short the pin to 3.3 volts via the Morse key, so that the pin will go HIGH when you press it. 149 | 150 | ![](images/pull_down_key.png) 151 | 152 | Both methods will work equally well; which one people use is often just personal preference. Take the two jumper wires and screw the male ends into the terminal blocks on your Morse Code key. On some very old antique keys this can be a tricky operation. 153 | 154 | ![](images/jumper_wires_key.png) 155 | 156 | Choose the pull up or down configuration you want to use and connect the female ends to the appropriate GPIO pins on your Raspberry Pi; use the above diagrams as a guide. Make a note of which configuration you're using as you'll need to incorporate it into your programming later. **In this worksheet, the examples given use a pull up configuration: you may use a pull down configuration if you wish, but remember that you will have to alter your code accordingly**. 157 | 158 | ## Detect the key position through the GPIO pin value 159 | 160 | Enter the following command to edit our previous tone program: 161 | 162 | ```bash 163 | nano morse-code.py 164 | ``` 165 | 166 | To give us access to the GPIO pins in our code, we need to import the `gpiozero` library. 167 | 168 | Add `gpiozero as gpio` to the `import` line at the top so that it reads: 169 | 170 | ```python 171 | import pygame 172 | import time 173 | from RPi import GPIO 174 | ``` 175 | 176 | At the bottom remove these lines (they will be put back in again later): 177 | 178 | ```python 179 | tone_obj.play(-1) 180 | time.sleep(2) 181 | tone_obj.stop() 182 | ``` 183 | 184 | Either copy and paste or enter the code below. Pay attention `key = gpio.Button(pin, pull_up=True)` line. This assigns the variable `key` to an *instance* of the class `gpio.Button`. The `gpio.Button` class holds all the important information about how to read inputs from the button, and comes with some *methods*, which are like functions, but are attached to the instance of the class, and can access its internal variables. 185 | 186 | It also tells the class to set GPIO pin 4 as input, then set the internal pull up resistor on it. If you want to use the pull down resistor, you'll need to use `pull_up=False` instead. 187 | 188 | Then, there is then a `while` loop, which continually reads the state of GPIO pin 4 and prints `ON` or `OFF` to the screen every second. 189 | 190 | ```python 191 | pin = 4 192 | key = gpio.Button(pin, pull_up=True) 193 | 194 | while True: 195 | reading = key.is_pressed 196 | print("ON" if reading else "OFF") 197 | time.sleep(1) 198 | ``` 199 | 200 | Press `Ctrl + O` then `Enter` to save followed by `Ctrl + X` to quit. 201 | 202 | If you've set the `pull_up` parameter to the right value, the program should show OFF when the key is up. Hold it down for a few seconds and it will show ON. The output should look something like this: 203 | 204 | ```bash 205 | OFF 206 | OFF 207 | OFF 208 | ON 209 | ON 210 | ON 211 | OFF 212 | OFF 213 | OFF 214 | ``` 215 | 216 | If it is the other way round (it shows `ON` when the key is up and vice-versa), change the value of the `pull_up` parameter, then retry. 217 | 218 | Press `Ctrl + C` to quit. 219 | 220 | ## Play a tone when the key is down 221 | 222 | We've now proven that the value of the GPIO pin is changing when we press the Morse key, so the electronics is done. But our code is still very basic. All we have is a loop that keeps polling the pin; the code doesn't actually respond to the press or release of the key yet. You'll notice that you can press and release the key many times within one second. 223 | 224 | For our Morse Code virtual radio to work, we need our program to respond every time the user presses or releases the key, by starting and stopping the tone sound. The `Button` class we created earlier already has a couple of methods to hold up the execution of your code until the key has been pressed or released. The overall goal here would be the following algorithm: 225 | 226 | - Loop 227 | - Wait for key down 228 | - Start playing tone 229 | - Wait for key up 230 | - Stop playing tone 231 | 232 | To wait for the press, we use two methods built into the `Button` class called `wait_for_press` and `wait_for_release`, which will make the Pi sleep until the pin state has changed. In order to have a good response time, we only need to sleep for a very short amount of time for each iteration of the loop: 0.01 seconds is ideal. 233 | 234 | Have a look at the code below. Remember that this is for a pull up configuration; if you're using a pull down configuration, change the part that says `gpio.Button(pin, pull_up=True)` to `gpio.Button(pin, pull_up=False)`. 235 | 236 | ```python 237 | 238 | tone_obj = ToneSound(frequency = 800, volume = .5) 239 | 240 | pin = 4 241 | key = gpio.Button(pin, pull_up=True) 242 | 243 | print("Ready") 244 | 245 | while True: 246 | key.wait_for_press() 247 | tone_obj.play(-1) #the -1 means to loop the sound 248 | key.wait_for_release() 249 | tone_obj.stop() 250 | 251 | ``` 252 | 253 | Enter the following command to edit our previous program: 254 | 255 | `nano morse-code.py` 256 | 257 | Leave the `ToneSound` class at the top of your program, scroll to the bottom, delete the previous `while` loop code and then add the code above. Remember to make the necessary modifications if you are using a pull-down configuration. 258 | 259 | Press `Ctrl + O` then `Enter` to save followed by `Ctrl + X` to quit. 260 | 261 | You can now test your code. 262 | 263 | ```bash 264 | ./morse-code.py 265 | ``` 266 | 267 | After the you see the `Ready` message, you should be able to start keying in your first Morse Code messages. Test the Morse key to make sure that the tone is only ever on when the key is down, and off when the key is up. If you've got it the wrong way around, check again to see if you have the `pull_up` parameter set correctly. 268 | 269 | Now have a go at a short word. Early Nokia mobile phones used the Morse Code for SMS when a text message arrived. This is a really easy one to do; the Morse Code for SMS is `... -- ...`. Try keying in other words using the chart at the top. 270 | 271 | Press `Ctrl + C` to quit. 272 | 273 | ## Decode the Morse as you go 274 | 275 | What will really help you learn is having a way to know when you're getting the code right or wrong. We can program the Pi to decode what you're keying in and then print the letters to the screen as you go. With this you can pick a message, try to key it in and immediately see if the correct text is being displayed. If the wrong text comes up then it's likely that you didn't key in the correct Morse Code sequence. Practice makes perfect! 276 | 277 | To program this, we should remind ourselves about the rules of International Morse Code: 278 | 279 | - All timings are defined as multiples of one dot length 280 | - A dash is three times the length of a dot 281 | - Each dot or dash has a short gap of silence after it (usually 1 dot length) 282 | - Letters in a word have a slightly longer gap of silence between them (usually 3 dot lengths) 283 | - Words have an even longer gap of silence between them (usually 7 dot lengths) 284 | 285 | So to start with, we need to tell the difference between a dot and a dash. We can do that by timing how long the key is held down for to give us the length of the tone. Then we need to tell the difference between the dots and dashes making up one word and the next. To do that, we can time how long the key is up for, so we're measuring the gap of silence between the tones. The same measurement of time will also give us the difference between letters making up a word and separate words. 286 | 287 | ### Distinguish dot and dash 288 | 289 | First, we need to program the Pi to recognise the difference between a dot and a dash. 290 | 291 | The aim is to time how long the key is held down for. Generally speaking, a dot is about 0.15 seconds or less and anything longer than this is a dash. You're welcome to use a different value if you wish but 0.15 seconds is a good starting point. The way to time something in code is to record the time now, wait until something has happened, and then subtract the time you recorded from the current time. 292 | 293 | Take a look at the code below; notice the the use of the `key_down_time` and `key_down_length` variables. 294 | 295 | ```python 296 | 297 | tone_obj = ToneSound(frequency = 800, volume = .5) 298 | 299 | pin = 4 300 | key = gpio.Button(pin, pull_up=True) 301 | 302 | DOT = "." 303 | DASH = "-" 304 | 305 | key_down_time = 0 306 | key_down_length = 0 307 | 308 | print("Ready") 309 | 310 | while True: 311 | key.wait_for_press() 312 | key_down_time = time.time() #record the time when the key went down 313 | tone_obj.play(-1) #the -1 means to loop the sound 314 | key.wait_for_release() 315 | key_down_length = key_up_time - key_down_time #get the length of time it was held down for 316 | tone_obj.stop() 317 | 318 | if key_down_length > 0.15: 319 | print(DASH) 320 | else: 321 | print(DOT) 322 | ``` 323 | 324 | Enter the following command to edit our previous program: 325 | 326 | ```bash 327 | nano morse-code.py 328 | ``` 329 | 330 | Scroll to the bottom and add the lines shown above that are missing from your original code. If necessary, modify the code for a pull down configuration. When you're done press `Ctrl + O` then `Enter` to save followed by `Ctrl + X` to quit. You can now test your code. 331 | 332 | ```bash 333 | ./morse-code.py 334 | ``` 335 | 336 | Use the Morse key to make some long and short tones. You should see dots and dashes appearing at the moment when you release the key. The output will look something like this: 337 | 338 | ``` 339 | . 340 | . 341 | . 342 | - 343 | - 344 | . 345 | . 346 | . 347 | ``` 348 | 349 | Press `Ctrl + C` to quit. 350 | 351 | ### Translate Morse Code into text 352 | 353 | Next, we need a way to combine these dots and dashes to form letters and words. This is actually a little more tricky than it sounds. Consider how we're going to know when the user has finished keying in a letter and when they have finished a word. The correct behaviour will be the following: 354 | 355 | - When they finish keying in a letter, display the letter 356 | - When they finish keying in a word, display a space character 357 | 358 | The following code should make this easier, by allowing you to take a string of dots and dashes and look up the corresponding letter of the alphabet. Enter the following command to download this code: 359 | 360 | ```bash 361 | wget https://goo.gl/aRjulj -O morse_lookup.py --no-check-certificate 362 | ``` 363 | 364 | Now let's have a look at it. Enter the command below to edit the file: 365 | 366 | ```bash 367 | nano morse_lookup.py 368 | ``` 369 | 370 | The `morse_code_lookup` variable is a Python dictionary object. A dictionary works using keys and values; for every key there is a corresponding value. You could create a dictionary to translate between, say, English and French. In this case, if the key was "Hello", the value would be "Bonjour". Look at the code below as an example: 371 | 372 | ```python 373 | english_to_french = { 374 | "Hello": "Bonjour", 375 | "Yes": "Oui", 376 | "No": "Non" 377 | } 378 | 379 | print(english_to_french["Hello"]) 380 | ``` 381 | 382 | The result of the above code would be: `Bonjour`. 383 | 384 | We're going to use this technique to translate between the sequence of dots and dashes and their corresponding letter. For example, `-.-.` is the letter `C`. The `try_decode` function at the bottom can be used to check that a dot-dash sequence is valid and, if so, translate it into the corresponding letter. 385 | 386 | Press `Ctrl + X` to quit from editing without saving. 387 | 388 | ### Multithreading 389 | 390 | Here, we need to introduce a new programming concept called [multithreading](http://en.wikipedia.org/wiki/Multithreading_%28software%29#Multithreading). A thread in a program is a single sequence of instructions that are being followed by the computer at any one time. In most simple programs there is only one thread, which is the main one. But it is possible to have multiple threads going at the same time: this is like making a program pat its head and rub its stomach at the same time. 391 | 392 | Because our main thread is always held up by the `wait_for_keydown` and `wait_for_keyup` functions, we need to have another thread which can constantly do the work of decoding what the user is keying in. 393 | 394 | The overall goal here will be to modify the main thread so that it stores every dot and dash in a buffer list. The decoder thread will then be watching independently for different lengths of silence. A short gap of silence denotes a new letter, so the thread will use the `try_decode` function to see if the buffer contents matches a letter; it will also empty the buffer. If the gap of silence gets longer, this denotes a new word and a space character would be shown. 395 | 396 | ### Add the code 397 | 398 | Now let's go back to editing our main program. Enter the following command: 399 | 400 | ```bash 401 | nano morse-code.py 402 | ``` 403 | 404 | Firstly, we need to add two new variables. `key_up_time` is to record when the key was released so that the length of silent gaps can be measured in our code. The other is called `buffer`; this is a list which will temporarily hold the dots and dashes before a full word is complete. 405 | 406 | ```python 407 | key_up_time = 0 408 | buffer = [] 409 | ``` 410 | 411 | Add these variables to your code as shown below. There are also two new lines to add inside the main `while` loop: a line which sets `key_up_time`, and another that appends to the `buffer` list. Make sure you add both of them. 412 | 413 | ```python 414 | key_down_time = 0 415 | key_down_length = 0 416 | key_up_time = 0 417 | buffer = [] 418 | 419 | print("Ready") 420 | 421 | while True: 422 | key.wait_for_press() 423 | key_down_time = time.time() #record the time when the key went down 424 | tone_obj.play(-1) #the -1 means to loop the sound 425 | key.wait_for_release() 426 | key_up_time = time.time() #record the time when the key was released 427 | key_down_length = key_up_time - key_down_time #get the length of time it was held down for 428 | tone_obj.stop() 429 | buffer.append(DASH if key_down_length > 0.15 else DOT) 430 | ``` 431 | 432 | Double-check that your code is the same as the above. When you're done, press `Ctrl + O` then `Enter` to save. We're not finished editing, yet, though; do not run the code as it is. We still need to add code for the new thread. 433 | 434 | First we need to add some new imports to the top of our file. Scroll up to the top and find the `import` lines. We need to add `_thread` to do multithreading and `from morse_lookup import *` to give us access to the lookup code we downloaded earlier. The code should now look like this: 435 | 436 | ```python 437 | #!/usr/bin/python3 438 | import pygame 439 | import time 440 | import gpiozero as gpio 441 | import _thread as thread 442 | from array import array 443 | from pygame.locals import * 444 | from morse_lookup import * 445 | ``` 446 | 447 | Next, let's put in the code that will run on our separate thread. To do this, you can just define a function and this will be what is run on that thread. Add this function to your code just below the `wait_for_keyup` function: 448 | 449 | ```python 450 | def decoder_thread(): 451 | global key_up_time 452 | global buffer 453 | new_word = False 454 | while True: 455 | time.sleep(.01) 456 | key_up_length = time.time() - key_up_time 457 | if len(buffer) > 0 and key_up_length >= 1.5: 458 | new_word = True 459 | bit_string = "".join(buffer) 460 | try_decode(bit_string) 461 | del buffer[:] 462 | elif new_word and key_up_length >= 4.5: 463 | new_word = False 464 | sys.stdout.write(" ") 465 | sys.stdout.flush() 466 | ``` 467 | 468 | The first thing you'll notice is the use of the `global` keyword. This will give the thread access to the `key_up_time` and `buffer` variables that belong to the main thread, so that they can be used here. Next, we have a variable called `new_word`. Once the end of the word has been detected we set this to `False`, so that we don't keep putting more spaces down. 469 | 470 | We then have another `while True` loop; the main purpose of this is to continually monitor the gaps of silence between tones. You'll see there is a `sleep` command to avoid overloading the CPU; then we calculate the `key_up_length`, which is the `key_up_time` from the main thread subtracted from the current time. Every time around the loop, which is every 0.01 seconds, the `key_up_length` value will increase as long as the Morse key stays up. 471 | 472 | Next is an `if` statement. There are two conditions upon which we need to act here: 473 | 474 | 1. When there is something in the `buffer` and the gap of silence is big enough to mean a new letter. We're hard-coding the value of `1.5` seconds for this. If this situation happens we know we're in a new word, so we set the `new_word` variable to `True`. The line `bit_string = "".join(buffer)` is taking the dots and dashes in the `buffer` list, and turning them into a single string that might be something like `.-..`. We can then see if that matches a key in the Morse translation dictionary via the `try_decode` function. The `try_decode` function displays the result. We then empty the buffer, ready for the next word, with `del buffer[:]`. If we didn't do this, the buffer would keep getting bigger, and would never match any letters in the `morse_lookup.py` dictionary. 475 | 476 | 1. When the gap of silence has increased to `4.5` seconds. Remember that a rule of Morse is that the gap of silence for a new word has to be three times the length of the gap that denotes a new letter: `1.5 x 3 = 4.5`. So here we set `new_word` to False, so that the `else if` condition no longer succeeds, and then put down a space character. 477 | 478 | Note that the use of `sys.stdout` is so that we can print to the screen without having to always show a new line, as with the default `print` command. 479 | 480 | The choice of `1.5` and `4.5` seconds is essentially arbitrary, but these gaps are about right for someone who is new to Morse, who will be going quite slowly. As your skill improves, you may wish to reduce these numbers in your code. 481 | 482 | Press `Ctrl + O` then `Enter` to save. There is one more thing we need to do before we can run our code, which is to add a line of code that will launch the new thread. This has to be done from the main thread, so scroll down and find the `print("Ready")` line. Add the line below just before it: 483 | 484 | ```bash 485 | thread.start_new_thread(decoder_thread, ()) 486 | ``` 487 | 488 | The final code should look like the example below; remember to make the necessary changes if you're using a pull down configuration instead of pull up. 489 | 490 | ```python 491 | #!/usr/bin/python3 492 | import pygame 493 | import time 494 | import gpiozero as gpio 495 | import _thread as thread 496 | from array import array 497 | from pygame.locals import * 498 | from morse_lookup import * 499 | 500 | pygame.mixer.pre_init(44100, -16, 1, 1024) 501 | pygame.init() 502 | 503 | class ToneSound(pygame.mixer.Sound): 504 | def __init__(self, frequency, volume): 505 | self.frequency = frequency 506 | pygame.mixer.Sound.__init__(self, self.build_samples()) 507 | self.set_volume(volume) 508 | 509 | def build_samples(self): 510 | period = int(round(pygame.mixer.get_init()[0] / self.frequency)) 511 | samples = array("h", [0] * period) 512 | amplitude = 2 ** (abs(pygame.mixer.get_init()[1]) - 1) - 1 513 | for time in range(period): 514 | if time < period / 2: 515 | samples[time] = amplitude 516 | else: 517 | samples[time] = -amplitude 518 | return samples 519 | 520 | def decoder_thread(): 521 | global key_up_time 522 | global buffer 523 | new_word = False 524 | while True: 525 | time.sleep(.01) 526 | key_up_length = time.time() - key_up_time 527 | if len(buffer) > 0 and key_up_length >= 1.5: 528 | new_word = True 529 | bit_string = "".join(buffer) 530 | try_decode(bit_string) 531 | del buffer[:] 532 | elif new_word and key_up_length >= 4.5: 533 | new_word = False 534 | sys.stdout.write(" ") 535 | sys.stdout.flush() 536 | 537 | tone_obj = ToneSound(frequency = 800, volume = .5) 538 | 539 | pin = 4 540 | key = gpio.Button(pin, pull_up=True) 541 | 542 | DOT = "." 543 | DASH = "-" 544 | 545 | key_down_time = 0 546 | key_down_length = 0 547 | key_up_time = 0 548 | buffer = [] 549 | 550 | thread.start_new_thread(decoder_thread, ()) 551 | 552 | print("Ready") 553 | 554 | while True: 555 | key.wait_for_press() 556 | key_down_time = time.time() #record the time when the key went down 557 | tone_obj.play(-1) #the -1 means to loop the sound 558 | key.wait_for_release() 559 | key_up_time = time.time() #record the time when the key was released 560 | key_down_length = key_up_time - key_down_time #get the length of time it was held down for 561 | tone_obj.stop() 562 | buffer.append(DASH if key_down_length > 0.15 else DOT) 563 | ``` 564 | 565 | When you're done, press `Ctrl + O` then `Enter` to save, followed by `Ctrl + X` to quit. You can now test your code. 566 | 567 | ```bash 568 | ./morse-code.py 569 | ``` 570 | 571 | Wait for the `Ready` message to show and then begin keying Morse Code in. The trick is to watch the screen and wait for a letter to appear before you start keying in the next one. You may wish to refer to the charts at the top of this page. 572 | 573 | SOS is `...` `---` `...` 574 | 575 | Hello is `....` `.` `.-..` `.-..` `---` 576 | 577 | The output should look like this: 578 | 579 | ``` 580 | SOS HELLO 581 | ``` 582 | 583 | Press `Ctrl + C` twice to quit. 584 | 585 | You can ignore the message saying `Unhandled exception in thread`; this is just the child thread being terminated when you send the `KeyboardInterrupt` with `Ctrl + C`. 586 | 587 | ## Play a listening game with a friend 588 | 589 | Now that you have a way to verify the correctness of your keying, you can play a listening game with a friend. 590 | 591 | The other person should: 592 | 593 | - Have a printout of the Morse Code tree 594 | - Some paper, a pencil, and an eraser 595 | - Be able to hear your Morse tones 596 | - Not be able to see your screen 597 | 598 | The aim of the game is to key in a message and see if the other person can decode it using just their ears. This is how it was done during both World Wars. It's trickier than it sounds so go slowly and start off with just letters, then progress onto words and messages. The other person should write down on some paper what they think is being keyed in; when the message is finished you can compare what they wrote down to what was shown on the screen. 599 | 600 | Try not to write dots and dashes; instead try to get into the zone of listening to the tones, following the tree and arriving at a letter. Record the letters instead of the code. If the message sounds like gibberish when you're decoding just keep going; the person behind the screen could be making mistakes. The aim is for you to have exactly what is shown on their screen, even if it's wrong. 601 | 602 | ## What next? 603 | 604 | - Why not extend the decoding ability of this project to include punctuation characters like the full stop `.-.-.-`, comma `--..--` and question mark `..--..`? 605 | - You could try to decode Morse in other languages. To do this you will need to edit the file `morse_lookup.py` and add the dictionary entries as appropriate. A comprehensive reference for International Morse can be found [here](https://www.itu.int/rec/R-REC-M.1677-1-200910-I/en), covering English, French, Arabic, Chinese and Russian. 606 | - You could explore Morse Code extensions: these are special procedural characters that mean things like wait, end of message or message part separator. You can find more information on these extensions [here](http://ke1g.org/media/uploads/files/MorseExtension.pdf). 607 | - You could take your Morse Code knowledge further with the [Koch Method](http://www.qsl.net/n1irz/finley.morse.html), a tried and tested way to learn Morse by listening at 15 to 20 words per minute. There is also an existing [Python package](https://pypi.python.org/pypi/KochMorse/0.99.7) which provides a Gtk2 style interface that you could install and use. 608 | - Can you work out how to modify the timing numbers we hard coded to enable you to use your `morse-code.py` project to key in at 15 to 20 words per minute? Try changing the `key_up_length` in the `decoder_thread` function. 609 | --------------------------------------------------------------------------------