|
| 1 | +# SPDX-FileCopyrightText: 2022 John Park for Adafruit Industries |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | +# |
| 5 | +# DTMF keypad phone Dial-a-Song |
| 6 | +import time |
| 7 | +import random |
| 8 | +import board |
| 9 | +import keypad |
| 10 | +from audiocore import WaveFile |
| 11 | +from audiopwmio import PWMAudioOut as AudioOut # for RP2040 etc |
| 12 | +import audiomixer |
| 13 | + |
| 14 | +# time.sleep(3) # let USB settle during development, remove when on battery |
| 15 | + |
| 16 | +km = keypad.KeyMatrix( |
| 17 | + # 2500 phone ignoring first column store/redial/memory. reverse mount on Feather RP2040 |
| 18 | + column_pins=( board.A3, board.A2, board.A1,), |
| 19 | + row_pins=( |
| 20 | + board.D24, |
| 21 | + board.D25, |
| 22 | + board.SCK, |
| 23 | + board.MOSI, |
| 24 | + ), |
| 25 | +) |
| 26 | + |
| 27 | +numbers = { |
| 28 | + "8675309" : "songs/beepbox.wav", |
| 29 | + "6358393" : "songs/streetchicken.wav", |
| 30 | + "5551212" : "songs/carpeter.wav", |
| 31 | + "7654321" : "songs/daisy.wav" |
| 32 | +} |
| 33 | + |
| 34 | +ringing = "songs/full_ring.wav" |
| 35 | +wrong_number = "songs/blank_number.wav" |
| 36 | +dial_tone = "songs/dial_tone_loop.wav" |
| 37 | +busy_signal = "songs/busy_loop.wav" |
| 38 | + |
| 39 | +button_tones = [ |
| 40 | + "dtmf/tt_1.wav", "dtmf/tt_2.wav", "dtmf/tt_3.wav", |
| 41 | + "dtmf/tt_4.wav", "dtmf/tt_5.wav", "dtmf/tt_6.wav", |
| 42 | + "dtmf/tt_7.wav", "dtmf/tt_8.wav", "dtmf/tt_9.wav", |
| 43 | + "dtmf/tt_star.wav", "dtmf/tt_0.wav", "dtmf/tt_pound.wav" |
| 44 | +] |
| 45 | + |
| 46 | +digits_entered = 0 # counter |
| 47 | +dialed = [] # list of digits user enters to make one 7 digit number |
| 48 | +dialed_str = "" # stores the phone number string for dictionary comparison |
| 49 | + |
| 50 | +audio = AudioOut(board.TX) # PWM out pin |
| 51 | +mixer = audiomixer.Mixer( |
| 52 | + voice_count=4, |
| 53 | + sample_rate=22050, |
| 54 | + channel_count=1, |
| 55 | + bits_per_sample=16, |
| 56 | + samples_signed=True, |
| 57 | +) |
| 58 | +audio.play(mixer) |
| 59 | +mixer.voice[0].level = 1.0 # dial tone voice |
| 60 | +mixer.voice[1].level = 1.0 # touch tone voice |
| 61 | +mixer.voice[2].level = 0.0 # song/message voice |
| 62 | +mixer.voice[3].level = 0.0 # busy signal |
| 63 | + |
| 64 | +wave_file0 = open(dial_tone, "rb") |
| 65 | +wave0 = WaveFile(wave_file0) |
| 66 | +mixer.voice[0].play(wave0, loop=True) # play dial tone |
| 67 | + |
| 68 | +wave_file2 = open(wrong_number, "rb") |
| 69 | +wave2 = WaveFile(wave_file2) |
| 70 | + |
| 71 | +wave_file3 = open(busy_signal, "rb") |
| 72 | +wave3 = WaveFile(wave_file3) |
| 73 | +mixer.voice[3].play(wave3, loop=True) # play dial tone |
| 74 | + |
| 75 | + |
| 76 | +def reset_number(): |
| 77 | + # pylint: disable=global-statement |
| 78 | + global digits_entered, dialed, dialed_str |
| 79 | + digits_entered = 0 |
| 80 | + dialed = [] |
| 81 | + dialed_str = "" |
| 82 | + km.events.clear() |
| 83 | + |
| 84 | + |
| 85 | +while True: |
| 86 | + |
| 87 | + event = km.events.get() # check for keypad presses |
| 88 | + if event: |
| 89 | + if event.pressed: |
| 90 | + mixer.voice[0].level = 0.0 # mute the dial tone |
| 91 | + wave_file1 = open(button_tones[event.key_number], "rb") # play Touch Tone |
| 92 | + wave1 = WaveFile(wave_file1) |
| 93 | + mixer.voice[1].play(wave1) |
| 94 | + if event.key_number == 9 or event.key_number == 11: # check for special keys |
| 95 | + if event.key_number == 9: # pressed the '*' key |
| 96 | + reset_number() # or make some cool new function for this key |
| 97 | + if event.key_number == 11: # pressed the '#' key |
| 98 | + reset_number() # or make some cool new function for this key |
| 99 | + |
| 100 | + else: # number keys |
| 101 | + if digits_entered < 7: # adding up to full number |
| 102 | + # convert event to number printed on the keypad button, append to string |
| 103 | + if event.key_number < 9: # 1-9 on keypad |
| 104 | + dialed.append(event.key_number+1) |
| 105 | + if event.key_number == 10: # the 0 key, ignore '*' and "#' |
| 106 | + dialed.append(0) |
| 107 | + dialed_str = "".join(str(n) for n in dialed) |
| 108 | + digits_entered = digits_entered + 1 # increment counter |
| 109 | + |
| 110 | + if digits_entered == 7: # a full number has been entered |
| 111 | + if not mixer.voice[2].playing: |
| 112 | + dialed_str = "".join(str(n) for n in dialed) |
| 113 | + if dialed_str in numbers: # check if dialed string is one in the directory |
| 114 | + value = numbers[dialed_str] |
| 115 | + time.sleep(0.6) |
| 116 | + |
| 117 | + wave_file2 = open(ringing, "rb") # ring before it answers |
| 118 | + wave2 = WaveFile(wave_file2) |
| 119 | + mixer.voice[2].level = 1.0 |
| 120 | + mixer.voice[2].play(wave2, loop=True) |
| 121 | + |
| 122 | + time.sleep(random.uniform(4.0, 9.5)) # random ring before "answer" |
| 123 | + |
| 124 | + wave_file2 = open(value, "rb") # answered |
| 125 | + wave2 = WaveFile(wave_file2) |
| 126 | + mixer.voice[2].level = 1.0 |
| 127 | + mixer.voice[2].play(wave2, loop=True) |
| 128 | + |
| 129 | + else: # number is not in directory |
| 130 | + time.sleep(0.5) |
| 131 | + weighted_coin_toss = random.randint(0, 4) |
| 132 | + if weighted_coin_toss < 3: # favor the "not in service" message |
| 133 | + mixer.voice[2].level = 1.0 |
| 134 | + mixer.voice[2].play(wave2) |
| 135 | + else: |
| 136 | + mixer.voice[3].level = 1.0 |
| 137 | + |
| 138 | + reset_number() |
| 139 | + |
| 140 | + if mixer.voice[2].playing: |
| 141 | + reset_number() # stop #s dialed during message play from doing anything |
0 commit comments