🎮 Controls

ActionInput
Dot (.)Short left click
Dash (−)Long left click (>200ms)
Complete character1.5 second pause
Next word3 second pause
Complete message & read aloudRight click
Reset sessionDouble right click
Switch Mouse / Keyboard modeTAB
ExitESC or hold both buttons 5s

🔄 Core Flow

1. Mouse Click Detected

Pygame captures MOUSEBUTTONDOWN and MOUSEBUTTONUP events. The time between press and release determines whether it's a dot (<200ms) or a dash (≥200ms).

2. Morse Buffer Updated

Each dot or dash is appended to current_morse. A beep plays immediately — 800 Hz for dots, 600 Hz for dashes.

3. Character Timeout (1.5s)

A background thread monitors the time since the last input. After 1.5 seconds of silence, it looks up the Morse buffer in the dictionary and decodes the character.

4. LED Display

The decoded letter is shown on the Sense HAT's 8×8 LED matrix using sense.show_letter(). The display is rotated 270° to match the physical orientation.

5. Word Timeout (3s)

After 3 seconds of silence, the current word is completed and a new word begins. Words are tracked in a list.

6. Message Completion

Right-click joins all words into a sentence, scrolls it on the LED, reads it aloud with text-to-speech, and plays the Mario celebration tune.

🧩 Key Code Components

Morse Code Dictionary

The heart of the decoder — a Python dictionary mapping Morse patterns to characters:

# Morse code lookup table self.morse_dict = { '.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E', '..-.': 'F', '--.': 'G', '....': 'H', '..': 'I', ... # Full A-Z, 0-9, and punctuation }

Click Timing Detection

How the system distinguishes dots from dashes:

# On mouse button release press_duration = current_time - self.press_start_time if press_duration >= self.long_press_threshold: # 0.2s self.current_morse += "-" # Dash self.play_beep(frequency=600, duration=0.3) else: self.current_morse += "." # Dot self.play_beep(frequency=800, duration=0.1)

Audio Synthesis with NumPy

Beeps are generated as sine waves, not pre-recorded files:

import numpy as np # Generate a sine wave at the given frequency frames = int(duration * sample_rate) arr = np.sin(2 * np.pi * frequency * np.linspace(0, duration, frames)) arr = (arr * 32767).astype(np.int16) # Convert to stereo and play through pygame sound = pygame.sndarray.make_sound(stereo_arr) sound.play()

Thread-Safe Timeout Monitor

A background thread watches for pauses to auto-complete characters and words:

def timeout_monitor(self): while self.running: time_since_input = current_time - self.last_input_time if time_since_input >= 1.5 and self.current_morse: with self.input_lock: # Thread safety self.process_morse_character() elif time_since_input >= 3.0 and self.current_message: self.process_word_break() time.sleep(0.1)

📁 Project Structure

Raspberry-pi-morse-code/ ├── README.md ├── morse/ │ ├── morse_code_sense_hat.py # Main application (~900 lines) │ ├── install_morse_system.sh # Installation script │ ├── run_morse_system.sh # Run script │ ├── morse_autostart.sh # Auto-start helper │ └── morse-code-sense-hat.service # Systemd service file └── demo/ ├── sensehat.py # Sense HAT hello-world demo └── test.py # Test scripts

🧠 Advanced Concepts Used

Multi-Threading

The main event loop, timeout monitor, audio playback, and LED display all run concurrently. Thread locks (input_lock, led_lock) prevent race conditions.

State Machine

The system tracks state across multiple variables — current Morse buffer, current word, word list, press timing — to correctly handle the complex input flow.

Graceful Degradation

If the Sense HAT isn't connected, the system continues without LED display. If TTS fails, it falls back to espeak. Every component has error handling.

Audio Synthesis

Instead of using audio files, sounds are generated mathematically using sine waves with attack/decay envelopes for clean, professional-sounding beeps and tunes.