Reputation: 11
So I’m working on a project for a class where I need to take button presses, as Morse code and then translate them to English. I am using a dictionary for this. However I am a little stuck on where to go after the dictionary and variables.
My variables are:
I have already imported gpiozero
and time
. And I know that I need to make some sort of timer to differentiate between dots and dashes, and a timer to differentiate between character space, letter space and a regular space. But I’m not quite sure how to set all of it up and then translate it to English.
Edit:
def user_message():
message = ""
current_word = ""
start_time = 0
while True:
if button.is_pressed = False:
start_time = time.time()
else:
if start_time != 0:
end_time = time.time()
pressed_time = end_time - start_time
start_time = 0
if pressed_time > dot_time:
current_word += "-"
else:
current_word += "."
time.sleep(character_space)
This is the current code I have right now, sorry for my not being clear. The button is on a breadboard connected to a raspberry pi. And what I am trying to do is take inputs from the button in the form of Morse code and then have the Morse code translated to English. Thank you for being patient with me since I’m new to the platform.
I have tried to do functions and referenced multiple GitHub sources that do similar things but they haven’t worked.
Upvotes: 0
Views: 184
Reputation: 5133
Update: I should perhaps make it clear that in this setup, I assume that there are no events available through the button that we could attach to – which, I guess, is the case for a button connected via a breadboard, as described in the question. Event handling would imply a different approach.
I see as the main issues of your approach of tracking the signal length, that (a) the times of spaces (character, letter, word) are currently not recorded, and (b) the sleep times between repeatedly querying the button
are way too long:
As to (a): To distinguish where the sequence of characters (.
,-
) of your current letter ends and where the sequence of letters of your current word ends, character spaces, letter spaces, and word spaces need to be distinguished. You should therefore not only handle and distinguish the periods of the button being pressed, but also the periods of the button being released (not pressed).
As to (b): At present, after each character evaluation, the sleep time is set to character_space
. In my opinion, this is way too long: consider that the times of the button presses may not be perfect and that your own code also has its own processing time; this will soon get your processing out of sync with the actual signal sequence (if it has ever been in sync in the first place).
I think, both issues can be fixed with the following approach:
This can be achieved with the following code:
times = [] # Record time of all symbols (even idx: released, odd idx: pressed)
was_previously_pressed = False # Monitor previous button state
start_time = time.time()
while True:
is_pressed = button.is_pressed
# Button state toggled → toggle time state and record time delta
if was_previously_pressed != is_pressed:
toggle_time = time.time()
times.append(toggle_time - start_time)
was_previously_pressed = is_pressed
start_time = toggle_time
# Stop listening after long silence (i.e. button released)
if not is_pressed and time.time() - start_time > 5 * max(time_by_symbol.values()):
break
# Wait a tiny bit to spare CPU cycles
time.sleep(.1 * min(time_by_symbol.values()))
Here, time_by_symbol
is a dictionary that captures the expected intervals, following your definitions (at present we only use its values, but we will later also use its keys):
# ".": dot, "-": dash, "": char sep., " ": letter sep., " / ": space (word sep.)
time_by_symbol = {".": .15, "-": .45, "": .15, " ": .45, " / ": 1.05}
As a result, we get the list times
, which captures the time intervals for the alternating buttons states, starting with the released / not pressed state. In other words: At even indexes of times
, we now have the time intervals of spaces, at odd indexes of times
, we now have the time intervals of characters.
What is now still missing is the decoding of the message.
Decoding, in my opinion, should be broken down into the following steps: (1) decode time intervals to Morse characters and spaces, (2) decode Morse character sequences to Morse letters, (3) decode Morse letters to Latin letters (a, b, c, …).
As to (1): I would simply map each time interval to its closest match in expected length, after skipping the initial silence (and thus evaluating characters as candidates for the even indexes and spaces as candidates for the odd indexes in times[1:]
):
decoded_morse = ""
for i, t in enumerate(times[1:]):
# Skip initial silence (now even idx: pressed, odd idx: released), then
# find and append symbol that is closest in length to the measured length
candidates = ["", " ", " / "] if (i % 2) else [".", "-"]
closest_symbol = lambda cand: abs(t - time_by_symbol[cand])
decoded_morse += min(candidates, key=closest_symbol)
As a result, we should now have the string decoded_morse
, which consists of Morse characters (.
,-
), spaces as Morse letter separators, and slashes (/
) as word separators.
As to (2) and (3): I would define a list of all known Morse letters and a sequence of all corresponding Latin letters, where the index of the Morse letter matches the index of the Latin letter (alternatively, a dictionary would work). I would then try to find, for each sequence of Morse characters, the index of its corresponding Morse letter in the list of Morse letters, then use this index to get the corresponding Latin letter. Here we have to take care of some special cases:
/
in decoded_morse
, can be treated as its own Morse letter, which is simply being decoded as a space in the Latin letter sequence.Altogether, this could look as follows:
abc_latin = "abcdefghijklmnopqrstuvwxyz "
# only "ab…yz" for simplicity, plus "/" for space
abc_morse = [
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---",
"-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-",
"...-", ".--", "-..-", "-.--", "--..", "/"
]
decoded_latin = "".join(abc_latin[abc_morse.index(l)] if l in abc_morse
else "-" for l in decoded_morse.split(" "))
As a result, we should now have the actual message, decoded as a string of Latin letters, in decoded_latin
.
Putting it all together, and including a button
simulator that runs in its own thread, this could look as follows:
import threading
import time
# ".": dot, "-": dash, "": char sep., " ": letter sep., " / ": space (word sep.)
time_by_symbol = {".": .15, "-": .45, "": .15, " ": .45, " / ": 1.05}
abc_latin = "abcdefghijklmnopqrstuvwxyz "
# only "ab…yz" for simplicity, plus "/" for space
abc_morse = [
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---",
"-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-",
"...-", ".--", "-..-", "-.--", "--..", "/"
]
# TODO: With ``given_latin``, provide a sequence of letters and spaces to be
# encoded to morse and decoded back to latin letters ("abc…")
given_latin = "hello world"
# TODO: With ``verbose``, provide whether the capturing of button states should
# be printed (True) or not (False)
verbose = True
assert set(given_latin).issubset(set(abc_latin)) # Check for valid letters
given_morse = " ".join([abc_morse[abc_latin.index(l)] for l in given_latin.strip().lower()])
print(f"Given sequence (latin): '{given_latin}'")
print(f"Given sequence (Morse): '{given_morse}'")
# Provide code that simulates the button
class Button(threading.Thread):
def __init__(self, sequence):
super().__init__(daemon=True)
assert set(sequence).issubset(".- /") # Check for valid characters
# Split the sequence into lists of words of lists of letters
self._sequence = [sub.split(" ") for sub in sequence.split(" / ")]
self.is_pressed = False
self.start() # Auto-start the value generation
def run(self):
time.sleep(time_by_symbol[" / "]) # Head-start with a bit of waiting
for word in self._sequence:
for letter in word:
for char in letter:
self.is_pressed=True
time.sleep(time_by_symbol[char]) # Dot/dash length
self.is_pressed=False
time.sleep(time_by_symbol[""]) # Char sep. length
time.sleep(time_by_symbol[" "] - time_by_symbol[""]) # Letter sep.
time.sleep(time_by_symbol[" / "] - time_by_symbol[" "]) # Word sep.
while True: # End of sequence → sleep forever
time.sleep(time_by_symbol[" / "])
print("Produce and capture sequence …")
# Create the button simulator, passing it the sequence
button = Button(given_morse)
times = [] # Record time of all symbols (even idx: released, odd idx: pressed)
was_previously_pressed = False # Monitor previous button state
start_time = time.time()
while True:
is_pressed = button.is_pressed
# Button state toggled → toggle time state and record time delta
if was_previously_pressed != is_pressed:
toggle_time = time.time()
times.append(toggle_time - start_time)
if verbose:
# Show "not pressed" for ``is_pressed=True`` and vice versa,
# because the time is for the previous state and ``is_pressed``
# is the new state
print(f"({times[-1]:.3f}) {'not ' if is_pressed else ''}pressed ")
was_previously_pressed = is_pressed
start_time = toggle_time
# Stop listening after long silence (i.e. button released)
if not is_pressed and time.time() - start_time > 5 * max(time_by_symbol.values()):
break
# Wait a tiny bit to spare CPU cycles
time.sleep(.1 * min(time_by_symbol.values()))
print("Decode sequence …")
decoded_morse = ""
for i, t in enumerate(times[1:]):
# Skip initial silence (now even idx: pressed, odd idx: released), then
# find and append symbol that is closest in length to the measured length
candidates = ["", " ", " / "] if (i % 2) else [".", "-"]
closest_symbol = lambda cand: abs(t - time_by_symbol[cand])
decoded_morse += min(candidates, key=closest_symbol)
decoded_latin = "".join(abc_latin[abc_morse.index(l)] if l in abc_morse
else "-" for l in decoded_morse.split(" "))
print(f"Decoded sequence (Morse): '{decoded_morse}'")
print(f"Decoded sequence (latin): '{decoded_latin}'")
Note that I included an external link to an earlier version of this code, in a comment to the question. I have done so since the question was closed at that time, and there are some minor differences as compared to the code above.
Upvotes: 1