|
| 1 | +### Baudot TTY Message Transmitter |
| 2 | + |
| 3 | +### The 5-bit mode is defined in ANSI TIA/EIA-825 (2000) |
| 4 | +### "A Frequency Shift Keyed Modem for use on the Public Switched Telephone Network" |
| 5 | + |
| 6 | +import time |
| 7 | +import math |
| 8 | +import array |
| 9 | +import board |
| 10 | +from audiocore import RawSample |
| 11 | +import audiopwmio |
| 12 | + |
| 13 | +# constants for sine wave generation |
| 14 | +SIN_LENGTH = 100 # more is less choppy |
| 15 | +SIN_AMPLITUDE = 2 ** 12 # 0 (min) to 32768 (max) 8192 is nice |
| 16 | +SIN_OFFSET = 32767.5 # for 16bit range, (2**16 - 1) / 2 |
| 17 | +DELTA_PI = 2 * math.pi / SIN_LENGTH # happy little constant |
| 18 | + |
| 19 | +sine_wave = [ |
| 20 | + int(SIN_OFFSET + SIN_AMPLITUDE * math.sin(DELTA_PI * i)) for i in range(SIN_LENGTH) |
| 21 | +] |
| 22 | +tones = ( |
| 23 | + RawSample(array.array("H", sine_wave), sample_rate=1800 * SIN_LENGTH), # Bit 0 |
| 24 | + RawSample(array.array("H", sine_wave), sample_rate=1400 * SIN_LENGTH), # Bit 1 |
| 25 | +) |
| 26 | + |
| 27 | +bit_0 = tones[0] |
| 28 | +bit_1 = tones[1] |
| 29 | +carrier = tones[1] |
| 30 | + |
| 31 | + |
| 32 | +char_pause = 0.1 # pause time between chars, set to 0 for fastest rate possible |
| 33 | + |
| 34 | +dac = audiopwmio.PWMAudioOut( |
| 35 | + board.A2 |
| 36 | +) # the CLUE edge connector marked "#0" to STEMMA speaker |
| 37 | +# The CLUE's on-board speaker works OK, not great, just crank amplitude to full before trying. |
| 38 | +# dac = audiopwmio.PWMAudioOut(board.SPEAKER) |
| 39 | + |
| 40 | + |
| 41 | +LTRS = ( |
| 42 | + "\b", |
| 43 | + "E", |
| 44 | + "\n", |
| 45 | + "A", |
| 46 | + " ", |
| 47 | + "S", |
| 48 | + "I", |
| 49 | + "U", |
| 50 | + "\r", |
| 51 | + "D", |
| 52 | + "R", |
| 53 | + "J", |
| 54 | + "N", |
| 55 | + "F", |
| 56 | + "C", |
| 57 | + "K", |
| 58 | + "T", |
| 59 | + "Z", |
| 60 | + "L", |
| 61 | + "W", |
| 62 | + "H", |
| 63 | + "Y", |
| 64 | + "P", |
| 65 | + "Q", |
| 66 | + "O", |
| 67 | + "B", |
| 68 | + "G", |
| 69 | + "FIGS", |
| 70 | + "M", |
| 71 | + "X", |
| 72 | + "V", |
| 73 | + "LTRS", |
| 74 | +) |
| 75 | + |
| 76 | +FIGS = ( |
| 77 | + "\b", |
| 78 | + "3", |
| 79 | + "\n", |
| 80 | + "-", |
| 81 | + " ", |
| 82 | + "-", |
| 83 | + "8", |
| 84 | + "7", |
| 85 | + "\r", |
| 86 | + "$", |
| 87 | + "4", |
| 88 | + "'", |
| 89 | + ",", |
| 90 | + "!", |
| 91 | + ":", |
| 92 | + "(", |
| 93 | + "5", |
| 94 | + '"', |
| 95 | + ")", |
| 96 | + "2", |
| 97 | + "=", |
| 98 | + "6", |
| 99 | + "0", |
| 100 | + "1", |
| 101 | + "9", |
| 102 | + "?", |
| 103 | + "+", |
| 104 | + "FIGS", |
| 105 | + ".", |
| 106 | + "/", |
| 107 | + ";", |
| 108 | + "LTRS", |
| 109 | +) |
| 110 | + |
| 111 | +char_count = 0 |
| 112 | +current_mode = LTRS |
| 113 | + |
| 114 | +# The 5-bit Baudot text telephone (TTY) mode is a Frequency Shift Keyed modem |
| 115 | +# for use on the Public Switched Telephone network. |
| 116 | +# |
| 117 | +# Definitions: |
| 118 | +# Carrier tone is a 1400Hz tone. |
| 119 | +# Binary 0 is an 1800Hz tone. |
| 120 | +# Binary 1 is a 1400Hz tone. |
| 121 | +# Bit duration is 20ms. |
| 122 | + |
| 123 | +# Two modes exist: Letters, aka LTRS, for alphabet characters |
| 124 | +# and Figures aka FIGS for numbers and symbols. These modes are switched by |
| 125 | +# sending the appropriate 5-bit LTRS or FIGS character. |
| 126 | +# |
| 127 | +# Character transmission sequence: |
| 128 | +# Carrier tone transmits for 150ms before each character. |
| 129 | +# Start bit is a binary 0 (sounded for one bit duration of 20ms). |
| 130 | +# 5-bit character code can be a combination of binary 0s and binary 1s. |
| 131 | +# Stop bit is a binary 1 with a minimum duration of 1-1/2 bits (30ms) |
| 132 | +# |
| 133 | +# |
| 134 | + |
| 135 | + |
| 136 | +def baudot_bit(pitch=bit_1, duration=0.022): # spec says 20ms, but adjusted as needed |
| 137 | + dac.play(pitch, loop=True) |
| 138 | + time.sleep(duration) |
| 139 | + # dac.stop() |
| 140 | + |
| 141 | + |
| 142 | +def baudot_carrier(duration=0.15): # Carrier tone is transmitted for 150 ms before the |
| 143 | + # first character is transmitted |
| 144 | + baudot_bit(carrier, duration) |
| 145 | + dac.stop() |
| 146 | + |
| 147 | + |
| 148 | +def baudot_start(): |
| 149 | + baudot_bit(bit_0) |
| 150 | + |
| 151 | + |
| 152 | +def baudot_stop(): |
| 153 | + baudot_bit(bit_1, 0.04) # minimum duration is 30ms |
| 154 | + dac.stop() |
| 155 | + |
| 156 | + |
| 157 | +def send_character(value): |
| 158 | + baudot_carrier() # send carrier tone |
| 159 | + baudot_start() # send start bit tone |
| 160 | + for i in range(5): # send each bit of the character |
| 161 | + bit = (value >> i) & 0x01 # bit shift and bit mask to get value of each bit |
| 162 | + baudot_bit(tones[bit]) # send each bit, either 0 or 1, of a character |
| 163 | + baudot_stop() # send stop bit |
| 164 | + baudot_carrier() # not to spec, but works better to extend carrier |
| 165 | + |
| 166 | + |
| 167 | +def send_message(text): |
| 168 | + global char_count, current_mode # pylint: disable=global-statement |
| 169 | + for char in text: |
| 170 | + if char not in LTRS and char not in FIGS: # just skip unknown characters |
| 171 | + print("Unknown character:", char) |
| 172 | + continue |
| 173 | + |
| 174 | + if char not in current_mode: # switch mode |
| 175 | + if current_mode == LTRS: |
| 176 | + print("Switching mode to FIGS") |
| 177 | + current_mode = FIGS |
| 178 | + send_character(current_mode.index("FIGS")) |
| 179 | + elif current_mode == FIGS: |
| 180 | + print("Switching mode to LTRS") |
| 181 | + current_mode = LTRS |
| 182 | + send_character(current_mode.index("LTRS")) |
| 183 | + # Send char mode at beginning of message and every 72 characters |
| 184 | + if char_count >= 72 or char_count == 0: |
| 185 | + print("Resending mode") |
| 186 | + if current_mode == LTRS: |
| 187 | + send_character(current_mode.index("LTRS")) |
| 188 | + elif current_mode == FIGS: |
| 189 | + send_character(current_mode.index("FIGS")) |
| 190 | + # reset counter |
| 191 | + char_count = 0 |
| 192 | + print(char) |
| 193 | + send_character(current_mode.index(char)) |
| 194 | + time.sleep(char_pause) |
| 195 | + # increment counter |
| 196 | + char_count += 1 |
| 197 | + |
| 198 | + |
| 199 | +while True: |
| 200 | + send_message("\nADAFRUIT 1234567890 -$!+='()/:;?,. ") |
| 201 | + time.sleep(2) |
| 202 | + send_message("\nWELCOME TO JOHN PARK'S WORKSHOP!") |
| 203 | + time.sleep(3) |
| 204 | + send_message("\nWOULD YOU LIKE TO PLAY A GAME?") |
| 205 | + time.sleep(5) |
| 206 | + |
| 207 | + # here's an example of sending a character |
| 208 | + # send_character(current_mode.index("A")) |
| 209 | + # time.sleep(char_pause) |
0 commit comments