Skip to content

Commit e9cc516

Browse files
authored
Add files via upload
1 parent 24b2c91 commit e9cc516

1 file changed

Lines changed: 211 additions & 0 deletions

File tree

morsecode.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Morse binary framing (Python 2 + 3 compatible)
4+
5+
Frame:
6+
STX (0x02) ...payload... ETX (0x03)
7+
8+
Within payload:
9+
Each character is encoded as a "binary Morse" string using '1' and '0'
10+
Characters are separated by GS (0x1D)
11+
12+
Binary Morse timing (classic units):
13+
dot = "1"
14+
dash = "111"
15+
intra-element gap (between dots/dashes within a character) = "0"
16+
17+
Word separation:
18+
If you include spaces in plaintext, this code uses RS (0x1E) between words.
19+
(You can remove that if you don't want word support.)
20+
"""
21+
22+
from __future__ import print_function
23+
import io
24+
25+
STX = b"\x02"
26+
ETX = b"\x03"
27+
GS = b"\x1D" # Group Separator: between characters
28+
RS = b"\x1E" # Record Separator: between words (optional)
29+
30+
# International Morse (basic set). Add more punctuation if you want.
31+
_TEXT_TO_MORSE = {
32+
"A": ".-", "B": "-...", "C": "-.-.", "D": "-..", "E": ".",
33+
"F": "..-.", "G": "--.", "H": "....", "I": "..", "J": ".---",
34+
"K": "-.-", "L": ".-..", "M": "--", "N": "-.", "O": "---",
35+
"P": ".--.", "Q": "--.-", "R": ".-.", "S": "...", "T": "-",
36+
"U": "..-", "V": "...-", "W": ".--", "X": "-..-", "Y": "-.--",
37+
"Z": "--..",
38+
"0": "-----", "1": ".----", "2": "..---", "3": "...--", "4": "....-",
39+
"5": ".....", "6": "-....", "7": "--...", "8": "---..", "9": "----.",
40+
".": ".-.-.-", ",": "--..--", "?": "..--..", "'": ".----.",
41+
"!": "-.-.--", "/": "-..-.", "(": "-.--.", ")": "-.--.-",
42+
"&": ".-...", ":": "---...", ";": "-.-.-.", "=": "-...-",
43+
"+": ".-.-.", "-": "-....-", "_": "..--.-", '"': ".-..-.",
44+
"$": "...-..-", "@": ".--.-."
45+
}
46+
47+
_MORSE_TO_TEXT = {v: k for (k, v) in _TEXT_TO_MORSE.items()}
48+
49+
50+
def _to_bytes(s):
51+
"""Accepts str/bytes in Py2/3; returns bytes."""
52+
if isinstance(s, bytes):
53+
return s
54+
# Python 2: unicode, Python 3: str
55+
return s.encode("utf-8")
56+
57+
58+
def _to_text(s):
59+
"""Accepts str/bytes in Py2/3; returns text (unicode in Py2, str in Py3)."""
60+
if isinstance(s, bytes):
61+
return s.decode("utf-8", "strict")
62+
return s
63+
64+
65+
def _morse_to_binary(morse):
66+
"""
67+
Convert ".-" to binary using:
68+
dot="1", dash="111", intra-element gap="0"
69+
Example: ".-" -> "1" + "0" + "111" = "10111"
70+
"""
71+
parts = []
72+
for i, sym in enumerate(morse):
73+
if sym == ".":
74+
parts.append("1")
75+
elif sym == "-":
76+
parts.append("111")
77+
else:
78+
raise ValueError("Invalid Morse symbol: %r" % sym)
79+
if i != len(morse) - 1:
80+
parts.append("0")
81+
return "".join(parts)
82+
83+
84+
def _binary_to_morse(bits):
85+
"""
86+
Convert binary back to ".-" by reading runs of 1s separated by 0s:
87+
"1" => dot
88+
"111" => dash
89+
"""
90+
if not bits:
91+
raise ValueError("Empty character payload")
92+
93+
# Validate characters
94+
for ch in bits:
95+
if ch not in ("0", "1"):
96+
raise ValueError("Non-binary character in payload: %r" % ch)
97+
98+
# Split into runs of 1s (elements) by 0 gaps
99+
elems = [run for run in bits.split("0") if run != ""]
100+
morse = []
101+
for run in elems:
102+
if run == "1":
103+
morse.append(".")
104+
elif run == "111":
105+
morse.append("-")
106+
else:
107+
raise ValueError("Invalid 1-run length %d in %r (expected 1 or 3)" %
108+
(len(run), bits))
109+
return "".join(morse)
110+
111+
112+
def encode_text_to_morse_binary(text, use_word_sep=True):
113+
"""
114+
Encode plaintext to framed bytes:
115+
STX + (charbits joined by GS, words by RS) + ETX
116+
117+
Returns: bytes
118+
"""
119+
t = _to_text(text).upper()
120+
121+
words = t.split(" ") if use_word_sep else [t.replace(" ", "")]
122+
encoded_words = []
123+
124+
for w in words:
125+
char_chunks = []
126+
for ch in w:
127+
if ch not in _TEXT_TO_MORSE:
128+
raise ValueError("Unsupported character: %r" % ch)
129+
morse = _TEXT_TO_MORSE[ch]
130+
bits = _morse_to_binary(morse)
131+
char_chunks.append(bits)
132+
# Join characters with GS
133+
encoded_words.append(GS.join(_to_bytes(c) for c in char_chunks))
134+
135+
payload = (RS.join(encoded_words) if use_word_sep else encoded_words[0])
136+
return STX + payload + ETX
137+
138+
139+
def decode_morse_binary_to_text(data, use_word_sep=True):
140+
"""
141+
Decode framed bytes back to plaintext.
142+
Expects:
143+
STX ... ETX
144+
characters separated by GS
145+
words separated by RS (if use_word_sep=True)
146+
147+
Returns: text (unicode in Py2, str in Py3)
148+
"""
149+
b = _to_bytes(data)
150+
151+
# Find first STX and the next ETX after it
152+
stx_i = b.find(STX)
153+
if stx_i < 0:
154+
raise ValueError("Missing STX framing byte")
155+
etx_i = b.find(ETX, stx_i + 1)
156+
if etx_i < 0:
157+
raise ValueError("Missing ETX framing byte")
158+
159+
payload = b[stx_i + 1:etx_i]
160+
161+
if payload == b"":
162+
return _to_text(b"")
163+
164+
word_blobs = payload.split(RS) if use_word_sep else [payload]
165+
out_words = []
166+
167+
for wb in word_blobs:
168+
if wb == b"":
169+
out_words.append("")
170+
continue
171+
172+
char_blobs = [x for x in wb.split(GS) if x != b""]
173+
chars = []
174+
for cb in char_blobs:
175+
bits = _to_text(cb)
176+
morse = _binary_to_morse(bits)
177+
if morse not in _MORSE_TO_TEXT:
178+
raise ValueError("Unknown Morse sequence: %r (from bits %r)" % (morse, bits))
179+
chars.append(_MORSE_TO_TEXT[morse])
180+
out_words.append("".join(chars))
181+
182+
return " ".join(out_words) if use_word_sep else "".join(out_words)
183+
184+
def send_morse_code(url, text, use_word_sep=True):
185+
outfile = io.BytesIO(encode_text_to_morse_binary(text, use_word_sep))
186+
outfile.seek(0)
187+
return pywwwget.upload_file_to_internet_file(outfile, url)
188+
189+
def recv_morse_code(url, use_word_sep=True):
190+
infile = pywwwget.download_file_from_internet_file(url)
191+
infile.seek(0)
192+
return decode_morse_binary_to_text(infile.read(), use_word_sep)
193+
194+
195+
# --- Optional helpers for debugging/printing ---
196+
def pretty_show(encoded_bytes):
197+
"""
198+
Make control characters visible:
199+
STX -> <STX>, ETX -> <ETX>, GS -> <GS>, RS -> <RS>
200+
"""
201+
b = _to_bytes(encoded_bytes)
202+
s = b.replace(STX, b"<STX>").replace(ETX, b"<ETX>").replace(GS, b"<GS>").replace(RS, b"<RS>")
203+
return _to_text(s)
204+
205+
206+
if __name__ == "__main__":
207+
msg = "SOS HELP"
208+
enc = encode_text_to_morse_binary(msg, use_word_sep=True)
209+
print("Encoded:", pretty_show(enc))
210+
dec = decode_morse_binary_to_text(enc, use_word_sep=True)
211+
print("Decoded:", dec)

0 commit comments

Comments
 (0)