Adafruit_Learning_System_Gu.../MacroPad_Scramble_Lock/code.py
2023-11-06 11:55:54 -06:00

216 lines
7.8 KiB
Python

# SPDX-FileCopyrightText: 2021 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
Scramblepad - a random scramble keypad simulation for Adafruit MACROPAD.
"""
# SPDX-FileCopyrightText: Copyright (c) 2021 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import random
import board
from digitalio import DigitalInOut, Direction
import displayio
import terminalio
from adafruit_display_shapes.rect import Rect
from adafruit_display_text import label
from adafruit_macropad import MacroPad
# CONFIGURABLES ------------------------
# Password information
# For higher security, place password in a separate file like secrets.py
PASSWORD = "2468"
PASSWORD_LENGTH = len(PASSWORD)
# States keypad may be in
STATE_ENTRY = 1
STATE_CLEAR = 2
STATE_RESET = 3
# Color defines for keys
WHITE = 0xFFFFFF
BLACK = 0x000000
RED = 0xFF0000
ORANGE = 0xFFA500
YELLOW = 0xFFFF00
GREEN = 0x00FF00
BLUE = 0x0000FF
PURPLE = 0x800080
PINK = 0xFFC0CB
TEAL = 0x2266AA
MAGENTA = 0xFF00FF
CYAN = 0x00FFFF
colors = [PINK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, TEAL, MAGENTA, CYAN]
current_colors = []
# Define sounds the keypad makes
tones = (440, 220, 245, 330, 440) # Initial tones while scrambling
press_tone = 660 # This tone is used when each key is pressed
# Initial key values - this list will be scrambled by the scramble function
key_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Define the STEMMA QT I2C line SDA to be a digital output (nonstandard use)
# SDA will be used as a digital pin to trigger a transistor to
# axctivate a solenoid which will unlock, like a door
solenoid = DigitalInOut(board.SDA)
solenoid.direction = Direction.OUTPUT
# FUNCTIONS ------------------
def keys_clear(): # Set display in the Start mode, key LEDs off
for i in range(12):
macropad.pixels[i] = 0x000000
group[i].text = " "
macropad.pixels.show()
group[9].text = "START"
macropad.display.root_group = group
macropad.display.refresh()
def scramble(): # Scramble values of the keys and display on screen
for times in range(5):
# The following lines implement a random.shuffle method
# See https://www.rosettacode.org/wiki/Knuth_shuffle#Python
# random.shuffle(key_values) # Shuffle the key array
for i in range(len(key_values)-1, 0, -1):
j = random.randrange(i + 1)
key_values[i], key_values[j] = key_values[j], key_values[i]
keys_display()
macropad.play_tone(tones[times], 0.3) # Play a tone each scramble iteration
time.sleep(0.01)
def keys_display(): # Display the current values of the keys on screen
for k in range(9): # The first 9 keys
group[k].text = str(key_values[k])
macropad.pixels[k] = colors[key_values[k]]
group[10].text = str(key_values[9]) # The 'Zero' position number
group[9].text = " " # Start blanks
group[11].text = " " # Status blanks
macropad.pixels[10] = colors[key_values[9]]
macropad.display.refresh()
macropad.pixels.show()
# INITIALIZATION -----------------------
macropad = MacroPad() # Set up MacroPad library and behavior
macropad.display.auto_refresh = False
macropad.pixels.auto_write = False
# Set up displayio group with all the labels
group = displayio.Group()
for key_index in range(12):
x = key_index % 3
y = key_index // 3
group.append(label.Label(terminalio.FONT, text='', color=0xFFFFFF,
anchored_position=((macropad.display.width - 1) * x / 2,
macropad.display.height - 1 -
(3 - y) * 12),
anchor_point=(x / 2, 1.0)))
group.append(Rect(0, 0, macropad.display.width, 12, fill=0xFFFFFF))
group.append(label.Label(terminalio.FONT, text='ScramblePad', color=0x000000,
anchored_position=(macropad.display.width//2, -2),
anchor_point=(0.5, 0.0)))
# Initialize in a clear state
state = STATE_CLEAR
macropad.keyboard.release_all()
keys_clear()
solenoid.value = False
# MAIN LOOP ----------------------------
while True:
if state == STATE_RESET:
print("Reset state")
macropad.keyboard.release_all()
password_guess = "" # Reset password entry
state = STATE_CLEAR # Reset state
# Check for key presses/releases
event = macropad.keys.events.get()
if not event:
continue
key_number = event.key_number
pressed = event.pressed
if pressed:
if state == STATE_CLEAR:
if key_number != 9: # Waiting to hit START
print("You must press start, lower left")
macropad.keyboard.release(key_number)
else: # START pressed
print("START pressed, enter your password")
macropad.keyboard.release(key_number)
password_guess = ""
scramble()
state = STATE_ENTRY
continue
if state == STATE_ENTRY:
if key_number == 9: # Start key during entry
print("Restart whole key entry")
macropad.keyboard.release_all()
password_guess = "" # Reset password entry
scramble()
continue
#
# From here out is password entry, state is KEY_ENTRY
#
if key_number < 11: # Ignore encoder and lower right button
old_color = macropad.pixels[key_number] # Save color of key pressed
macropad.pixels[key_number] = 0xFFFFFF # Turn key white while down
macropad.pixels.show() # Show key as white
macropad.play_tone(press_tone, 0.6) # Play tone when key pressed
# Process input - add the key pressed to the password entry
if key_number == 10: # The "0" position is shifted over, take one away
password_guess = password_guess + str(key_values[key_number-1])
else: # The 1-9 keys (index values 0 to 8)
password_guess = password_guess + str(key_values[key_number])
print(password_guess)
if len(password_guess) == PASSWORD_LENGTH: # We've entered all digits
keys_clear() # Clear the keypad
if password_guess == PASSWORD: # Success
group[9].text = " "
group[11].text = "OPEN"
macropad.display.root_group = group
macropad.display.refresh()
macropad.pixels[11] = GREEN
macropad.pixels.show()
# Activate solenoid
solenoid.value = True
time.sleep(2) # Limit time open to spare current in transistor
solenoid.value = False
# Reset
time.sleep(5)
macropad.pixels[11] = BLACK
macropad.pixels.show()
else: # fail!
group[11].text = "FAIL"
group[9].text = " "
macropad.display.root_group = group
macropad.display.refresh()
for _ in range(3): # Flash lower right 3 times red with beeps
macropad.pixels[11] = RED
macropad.pixels.show()
macropad.play_tone(880, 1)
time.sleep(0.1)
macropad.pixels[11] = BLACK
macropad.pixels.show()
time.sleep(0.1)
# Reset state after both success and failure
keys_clear()
state = STATE_RESET
else: # Release any still-pressed keys
macropad.keyboard.release(key_number)
# Change key color back
if state == STATE_ENTRY:
if key_number in (0, 1, 2, 3, 4, 5, 6, 7, 8, 10):
macropad.pixels[key_number] = old_color
macropad.pixels.show()