Skip to content

Commit 4e03de0

Browse files
authored
Merge branch 'master' into master
2 parents a60f9fe + 38abbf2 commit 4e03de0

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed

Baudot_TTY/baudot_tty_GUI.py

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
### Baudot TTY Message Transmitter with CLUE GUI
2+
### Pick from four phrases to send from the CLUE screen with buttons
3+
4+
### The 5-bit mode is defined in ANSI TIA/EIA-825 (2000)
5+
### "A Frequency Shift Keyed Modem for use on the Public Switched Telephone Network"
6+
7+
import time
8+
import math
9+
import array
10+
import board
11+
from audiocore import RawSample
12+
import audiopwmio
13+
import displayio
14+
from adafruit_display_shapes.circle import Circle
15+
from adafruit_clue import clue
16+
from adafruit_display_text import label
17+
import terminalio
18+
19+
# Enter your messages here no more than 34 characters including spaces per line
20+
messages = [
21+
"HELLO FROM ADAFRUIT INDUSTRIES",
22+
"12345678910 -$!+='()/:;?",
23+
"WOULD YOU LIKE TO PLAY A GAME?",
24+
"WELCOME TO JOHN PARK'S WORKSHOP",
25+
]
26+
27+
28+
clue.display.brightness = 1.0
29+
screen = displayio.Group(max_size=5)
30+
31+
VFD_GREEN = 0x00FFD2
32+
VFD_BG = 0x000505
33+
34+
# setup screen
35+
# BG
36+
color_bitmap = displayio.Bitmap(240, 240, 1)
37+
color_palette = displayio.Palette(1)
38+
color_palette[0] = VFD_BG
39+
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
40+
screen.append(bg_sprite)
41+
42+
# title
43+
title_label = label.Label(
44+
terminalio.FONT, text="TTY CLUE", scale=4, color=VFD_GREEN, max_glyphs=11
45+
)
46+
title_label.x = 20
47+
title_label.y = 16
48+
screen.append(title_label)
49+
50+
# footer
51+
footer_label = label.Label(
52+
terminalio.FONT, text="<PICK SEND>", scale=2, color=VFD_GREEN, max_glyphs=40
53+
)
54+
footer_label.x = 4
55+
footer_label.y = 220
56+
screen.append(footer_label)
57+
58+
# message configs
59+
messages_config = [
60+
(0, messages[0], VFD_GREEN, 2, 60),
61+
(1, messages[1], VFD_GREEN, 2, 90),
62+
(2, messages[2], VFD_GREEN, 2, 120),
63+
(3, messages[3], VFD_GREEN, 2, 150),
64+
]
65+
66+
messages_labels = {} # dictionary of configured messages_labels
67+
68+
message_group = displayio.Group(max_size=5, scale=1)
69+
70+
for message_config in messages_config:
71+
(name, textline, color, x, y) = message_config # unpack tuple into five var names
72+
message_label = label.Label(terminalio.FONT, text=textline, color=color, max_glyphs=50)
73+
message_label.x = x
74+
message_label.y = y
75+
messages_labels[name] = message_label
76+
message_group.append(message_label)
77+
screen.append(message_group)
78+
79+
# selection dot
80+
dot_y = [52, 82, 112, 142]
81+
dot = Circle(220, 60, 8, outline=VFD_GREEN, fill=VFD_BG)
82+
screen.append(dot)
83+
84+
clue.display.show(screen)
85+
86+
# constants for sine wave generation
87+
SIN_LENGTH = 100 # more is less choppy
88+
SIN_AMPLITUDE = 2 ** 12 # 0 (min) to 32768 (max) 8192 is nice
89+
SIN_OFFSET = 32767.5 # for 16bit range, (2**16 - 1) / 2
90+
DELTA_PI = 2 * math.pi / SIN_LENGTH # happy little constant
91+
92+
sine_wave = [
93+
int(SIN_OFFSET + SIN_AMPLITUDE * math.sin(DELTA_PI * i)) for i in range(SIN_LENGTH)
94+
]
95+
tones = (
96+
RawSample(array.array("H", sine_wave), sample_rate=1800 * SIN_LENGTH), # Bit 0
97+
RawSample(array.array("H", sine_wave), sample_rate=1400 * SIN_LENGTH), # Bit 1
98+
)
99+
100+
bit_0 = tones[0]
101+
bit_1 = tones[1]
102+
carrier = tones[1]
103+
104+
105+
char_pause = 0.1 # pause time between chars, set to 0 for fastest rate possible
106+
107+
dac = audiopwmio.PWMAudioOut(
108+
board.A2
109+
) # the CLUE edge connector marked "#0" to STEMMA speaker
110+
# The CLUE's on-board speaker works OK, not great, just crank amplitude to full before trying.
111+
# dac = audiopwmio.PWMAudioOut(board.SPEAKER)
112+
113+
114+
LTRS = (
115+
"\b",
116+
"E",
117+
"\n",
118+
"A",
119+
" ",
120+
"S",
121+
"I",
122+
"U",
123+
"\r",
124+
"D",
125+
"R",
126+
"J",
127+
"N",
128+
"F",
129+
"C",
130+
"K",
131+
"T",
132+
"Z",
133+
"L",
134+
"W",
135+
"H",
136+
"Y",
137+
"P",
138+
"Q",
139+
"O",
140+
"B",
141+
"G",
142+
"FIGS",
143+
"M",
144+
"X",
145+
"V",
146+
"LTRS",
147+
)
148+
149+
FIGS = (
150+
"\b",
151+
"3",
152+
"\n",
153+
"-",
154+
" ",
155+
"-",
156+
"8",
157+
"7",
158+
"\r",
159+
"$",
160+
"4",
161+
"'",
162+
",",
163+
"!",
164+
":",
165+
"(",
166+
"5",
167+
'"',
168+
")",
169+
"2",
170+
"=",
171+
"6",
172+
"0",
173+
"1",
174+
"9",
175+
"?",
176+
"+",
177+
"FIGS",
178+
".",
179+
"/",
180+
";",
181+
"LTRS",
182+
)
183+
184+
char_count = 0
185+
current_mode = LTRS
186+
187+
# The 5-bit Baudot text telephone (TTY) mode is a Frequency Shift Keyed modem
188+
# for use on the Public Switched Telephone network.
189+
#
190+
# Definitions:
191+
# Carrier tone is a 1400Hz tone.
192+
# Binary 0 is an 1800Hz tone.
193+
# Binary 1 is a 1400Hz tone.
194+
# Bit duration is 20ms.
195+
196+
# Two modes exist: Letters, aka LTRS, for alphabet characters
197+
# and Figures aka FIGS for numbers and symbols. These modes are switched by
198+
# sending the appropriate 5-bit LTRS or FIGS character.
199+
#
200+
# Character transmission sequence:
201+
# Carrier tone transmits for 150ms before each character.
202+
# Start bit is a binary 0 (sounded for one bit duration of 20ms).
203+
# 5-bit character code can be a combination of binary 0s and binary 1s.
204+
# Stop bit is a binary 1 with a minimum duration of 1-1/2 bits (30ms)
205+
#
206+
#
207+
208+
209+
def baudot_bit(pitch=bit_1, duration=0.022): # spec says 20ms, but adjusted as needed
210+
dac.play(pitch, loop=True)
211+
time.sleep(duration)
212+
# dac.stop()
213+
214+
215+
def baudot_carrier(duration=0.15): # Carrier tone is transmitted for 150 ms before the
216+
# first character is transmitted
217+
baudot_bit(carrier, duration)
218+
dac.stop()
219+
220+
221+
def baudot_start():
222+
baudot_bit(bit_0)
223+
224+
225+
def baudot_stop():
226+
baudot_bit(bit_1, 0.04) # minimum duration is 30ms
227+
dac.stop()
228+
229+
230+
def send_character(value):
231+
baudot_carrier() # send carrier tone
232+
baudot_start() # send start bit tone
233+
for i in range(5): # send each bit of the character
234+
bit = (value >> i) & 0x01 # bit shift and bit mask to get value of each bit
235+
baudot_bit(tones[bit]) # send each bit, either 0 or 1, of a character
236+
baudot_stop() # send stop bit
237+
baudot_carrier() # not to spec, but works better to extend carrier
238+
239+
240+
def send_message(text):
241+
global char_count, current_mode # pylint: disable=global-statement
242+
for char in text:
243+
if char not in LTRS and char not in FIGS: # just skip unknown characters
244+
print("Unknown character:", char)
245+
continue
246+
247+
if char not in current_mode: # switch mode
248+
if current_mode == LTRS:
249+
print("Switching mode to FIGS")
250+
current_mode = FIGS
251+
send_character(current_mode.index("FIGS"))
252+
elif current_mode == FIGS:
253+
print("Switching mode to LTRS")
254+
current_mode = LTRS
255+
send_character(current_mode.index("LTRS"))
256+
# Send char mode at beginning of message and every 72 characters
257+
if char_count >= 72 or char_count == 0:
258+
print("Resending mode")
259+
if current_mode == LTRS:
260+
send_character(current_mode.index("LTRS"))
261+
elif current_mode == FIGS:
262+
send_character(current_mode.index("FIGS"))
263+
# reset counter
264+
char_count = 0
265+
print(char)
266+
send_character(current_mode.index(char))
267+
time.sleep(char_pause)
268+
# increment counter
269+
char_count += 1
270+
271+
272+
message_pick = 0
273+
274+
while True:
275+
if clue.button_a:
276+
message_pick = (message_pick + 1) % 4 # loop through the lines
277+
dot.y = dot_y[message_pick]
278+
time.sleep(0.4) # debounce
279+
280+
if clue.button_b:
281+
dot.fill = VFD_GREEN
282+
send_message(messages[message_pick])
283+
dot.fill = VFD_BG

0 commit comments

Comments
 (0)