|
| 1 | +""" |
| 2 | +Scramblepad - a random scramble keypad simulation for Adafruit MACROPAD. |
| 3 | +""" |
| 4 | +# SPDX-FileCopyrightText: Copyright (c) 2021 Anne Barela for Adafruit Industries |
| 5 | +# |
| 6 | +# SPDX-License-Identifier: MIT |
| 7 | + |
| 8 | +import time |
| 9 | +import random |
| 10 | +import board |
| 11 | +from digitalio import DigitalInOut, Direction |
| 12 | +import displayio |
| 13 | +import terminalio |
| 14 | +from adafruit_display_shapes.rect import Rect |
| 15 | +from adafruit_display_text import label |
| 16 | +from adafruit_macropad import MacroPad |
| 17 | + |
| 18 | +# CONFIGURABLES ------------------------ |
| 19 | + |
| 20 | +# Password information |
| 21 | +# For higher security, place password in a separate file like secrets.py |
| 22 | +PASSWORD = "2468" |
| 23 | +PASSWORD_LENGTH = len(PASSWORD) |
| 24 | + |
| 25 | +# States keypad may be in |
| 26 | +STATE_ENTRY = 1 |
| 27 | +STATE_CLEAR = 2 |
| 28 | +STATE_RESET = 3 |
| 29 | + |
| 30 | +# Color defines for keys |
| 31 | +WHITE = 0xFFFFFF |
| 32 | +BLACK = 0x000000 |
| 33 | +RED = 0xFF0000 |
| 34 | +ORANGE = 0xFFA500 |
| 35 | +YELLOW = 0xFFFF00 |
| 36 | +GREEN = 0x00FF00 |
| 37 | +BLUE = 0x0000FF |
| 38 | +PURPLE = 0x800080 |
| 39 | +PINK = 0xFFC0CB |
| 40 | +TEAL = 0x2266AA |
| 41 | +MAGENTA = 0xFF00FF |
| 42 | +CYAN = 0x00FFFF |
| 43 | + |
| 44 | +colors = [PINK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, TEAL, MAGENTA, CYAN] |
| 45 | +current_colors = [] |
| 46 | + |
| 47 | +# Define sounds the keypad makes |
| 48 | +tones = (440, 220, 245, 330, 440) # Initial tones while scrambling |
| 49 | +press_tone = 660 # This tone is used when each key is pressed |
| 50 | + |
| 51 | +# Initial key values - this list will be scrambled by the scramble function |
| 52 | +key_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] |
| 53 | + |
| 54 | +# Define the STEMMA QT I2C line SDA to be a digital output (nonstandard use) |
| 55 | +# SDA will be used as a digital pin to trigger a transistor to |
| 56 | +# axctivate a solenoid which will unlock, like a door |
| 57 | +solenoid = DigitalInOut(board.SDA) |
| 58 | +solenoid.direction = Direction.OUTPUT |
| 59 | + |
| 60 | +# FUNCTIONS ------------------ |
| 61 | + |
| 62 | +def keys_clear(): # Set display in the Start mode, key LEDs off |
| 63 | + for i in range(12): |
| 64 | + macropad.pixels[i] = 0x000000 |
| 65 | + group[i].text = " " |
| 66 | + macropad.pixels.show() |
| 67 | + group[9].text = "START" |
| 68 | + macropad.display.show(group) |
| 69 | + macropad.display.refresh() |
| 70 | + |
| 71 | +def scramble(): # Scramble values of the keys and display on screen |
| 72 | + for times in range(5): |
| 73 | + # The following lines implement a random.shuffle method |
| 74 | + # See https://www.rosettacode.org/wiki/Knuth_shuffle#Python |
| 75 | + # random.shuffle(key_values) # Shuffle the key array |
| 76 | + for i in range(len(key_values)-1, 0, -1): |
| 77 | + j = random.randrange(i + 1) |
| 78 | + key_values[i], key_values[j] = key_values[j], key_values[i] |
| 79 | + keys_display() |
| 80 | + macropad.play_tone(tones[times], 0.3) # Play a tone each scramble iteration |
| 81 | + time.sleep(0.01) |
| 82 | + |
| 83 | +def keys_display(): # Display the current values of the keys on screen |
| 84 | + for k in range(9): # The first 9 keys |
| 85 | + group[k].text = str(key_values[k]) |
| 86 | + macropad.pixels[k] = colors[key_values[k]] |
| 87 | + group[10].text = str(key_values[9]) # The 'Zero' position number |
| 88 | + group[9].text = " " # Start blanks |
| 89 | + group[11].text = " " # Status blanks |
| 90 | + macropad.pixels[10] = colors[key_values[9]] |
| 91 | + macropad.display.refresh() |
| 92 | + macropad.pixels.show() |
| 93 | + |
| 94 | +# INITIALIZATION ----------------------- |
| 95 | + |
| 96 | +macropad = MacroPad() # Set up MacroPad library and behavior |
| 97 | +macropad.display.auto_refresh = False |
| 98 | +macropad.pixels.auto_write = False |
| 99 | + |
| 100 | +# Set up displayio group with all the labels |
| 101 | +group = displayio.Group() |
| 102 | +for key_index in range(12): |
| 103 | + x = key_index % 3 |
| 104 | + y = key_index // 3 |
| 105 | + group.append(label.Label(terminalio.FONT, text='', color=0xFFFFFF, |
| 106 | + anchored_position=((macropad.display.width - 1) * x / 2, |
| 107 | + macropad.display.height - 1 - |
| 108 | + (3 - y) * 12), |
| 109 | + anchor_point=(x / 2, 1.0))) |
| 110 | +group.append(Rect(0, 0, macropad.display.width, 12, fill=0xFFFFFF)) |
| 111 | +group.append(label.Label(terminalio.FONT, text='ScramblePad', color=0x000000, |
| 112 | + anchored_position=(macropad.display.width//2, -2), |
| 113 | + anchor_point=(0.5, 0.0))) |
| 114 | + |
| 115 | + |
| 116 | +# Initialize in a clear state |
| 117 | +state = STATE_CLEAR |
| 118 | +macropad.keyboard.release_all() |
| 119 | +keys_clear() |
| 120 | +solenoid.value = False |
| 121 | + |
| 122 | +# MAIN LOOP ---------------------------- |
| 123 | + |
| 124 | +while True: |
| 125 | + if state == STATE_RESET: |
| 126 | + print("Reset state") |
| 127 | + macropad.keyboard.release_all() |
| 128 | + password_guess = "" # Reset password entry |
| 129 | + state = STATE_CLEAR # Reset state |
| 130 | + |
| 131 | + # Check for key presses/releases |
| 132 | + event = macropad.keys.events.get() |
| 133 | + if not event: |
| 134 | + continue |
| 135 | + key_number = event.key_number |
| 136 | + pressed = event.pressed |
| 137 | + |
| 138 | + if pressed: |
| 139 | + if state == STATE_CLEAR: |
| 140 | + if key_number != 9: # Waiting to hit START |
| 141 | + print("You must press start, lower left") |
| 142 | + macropad.keyboard.release(key_number) |
| 143 | + else: # START pressed |
| 144 | + print("START pressed, enter your password") |
| 145 | + macropad.keyboard.release(key_number) |
| 146 | + password_guess = "" |
| 147 | + scramble() |
| 148 | + state = STATE_ENTRY |
| 149 | + continue |
| 150 | + if state == STATE_ENTRY: |
| 151 | + if key_number == 9: # Start key during entry |
| 152 | + print("Restart whole key entry") |
| 153 | + macropad.keyboard.release_all() |
| 154 | + password_guess = "" # Reset password entry |
| 155 | + scramble() |
| 156 | + continue |
| 157 | + # |
| 158 | + # From here out is password entry, state is KEY_ENTRY |
| 159 | + # |
| 160 | + if key_number < 11: # Ignore encoder and lower right button |
| 161 | + old_color = macropad.pixels[key_number] # Save color of key pressed |
| 162 | + macropad.pixels[key_number] = 0xFFFFFF # Turn key white while down |
| 163 | + macropad.pixels.show() # Show key as white |
| 164 | + macropad.play_tone(press_tone, 0.6) # Play tone when key pressed |
| 165 | + # Process input - add the key pressed to the password entry |
| 166 | + if key_number == 10: # The "0" position is shifted over, take one away |
| 167 | + password_guess = password_guess + str(key_values[key_number-1]) |
| 168 | + else: # The 1-9 keys (index values 0 to 8) |
| 169 | + password_guess = password_guess + str(key_values[key_number]) |
| 170 | + print(password_guess) |
| 171 | + if len(password_guess) == PASSWORD_LENGTH: # We've entered all digits |
| 172 | + keys_clear() # Clear the keypad |
| 173 | + if password_guess == PASSWORD: # Success |
| 174 | + group[9].text = " " |
| 175 | + group[11].text = "OPEN" |
| 176 | + macropad.display.show(group) |
| 177 | + macropad.display.refresh() |
| 178 | + macropad.pixels[11] = GREEN |
| 179 | + macropad.pixels.show() |
| 180 | + |
| 181 | + # Activate solenoid |
| 182 | + solenoid.value = True |
| 183 | + time.sleep(2) # Limit time open to spare current in transistor |
| 184 | + solenoid.value = False |
| 185 | + # Reset |
| 186 | + time.sleep(5) |
| 187 | + macropad.pixels[11] = BLACK |
| 188 | + macropad.pixels.show() |
| 189 | + else: # fail! |
| 190 | + group[11].text = "FAIL" |
| 191 | + group[9].text = " " |
| 192 | + macropad.display.show(group) |
| 193 | + macropad.display.refresh() |
| 194 | + for _ in range(3): # Flash lower right 3 times red with beeps |
| 195 | + macropad.pixels[11] = RED |
| 196 | + macropad.pixels.show() |
| 197 | + macropad.play_tone(880, 1) |
| 198 | + time.sleep(0.1) |
| 199 | + macropad.pixels[11] = BLACK |
| 200 | + macropad.pixels.show() |
| 201 | + time.sleep(0.1) |
| 202 | + # Reset state after both success and failure |
| 203 | + keys_clear() |
| 204 | + state = STATE_RESET |
| 205 | + |
| 206 | + else: # Release any still-pressed keys |
| 207 | + macropad.keyboard.release(key_number) |
| 208 | + # Change key color back |
| 209 | + if state == STATE_ENTRY: |
| 210 | + if key_number in (0, 1, 2, 3, 4, 5, 6, 7, 8, 10): |
| 211 | + macropad.pixels[key_number] = old_color |
| 212 | + macropad.pixels.show() |
0 commit comments