Opening the safe

The safe is opened by turning an encoder up to the first value, then back to the second, and so on, until the 4 values have been reached. Once the final position has been reached, the rotating lever allows to open the safe. Pressing on the encoder button allows to verify the sequence currently stored by the safe's controller.

This can be tested with the sequence [12, 4, 15, 8] which doesn't open the safe but displays a smiley.

Verifying the codes

The numbers of the test sequence can be verified by their Hamming code. Calculating the Hamming code is similar to a vector by matrix multiplication, where the product is implemented by an AND function and the sum by a XOR.

$\begin{bmatrix} h_3 & h_2 & h_1 & h_0 \end{bmatrix} = \begin{bmatrix} d_7 & d_6 & d_5 & d_4 & d_3 & d_2 & d_1 & d_0 \end{bmatrix} \cdot \begin{bmatrix} 0 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 \\ 0 & 1 & 1 & 0 \\ 0 & 1 & 1 & 1 \\ 1 & 0 & 0 & 1 \\ 1 & 0 & 1 & 0 \\ 1 & 0 & 1 & 1 \\ 1 & 1 & 0 & 0 \end{bmatrix}$

As an example, data value 3 has Hamming code 7:

$\begin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \end{bmatrix} \cdot \begin{bmatrix} 0 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 \\ 0 & 1 & 1 & 0 \\ 0 & 1 & 1 & 1 \\ 1 & 0 & 0 & 1 \\ 1 & 0 & 1 & 0 \\ 1 & 0 & 1 & 1 \\ 1 & 1 & 0 & 0 \end{bmatrix} = \begin{bmatrix} 0 & 1 & 1 & 1 \end{bmatrix}$

The tasks have the follwing Hamming codes:

Testing the opening of the safe

The following code is programmed in the safe's microcontroller, except for the opening_sequence. It allows to find out how the unlocking system works.

# http://microbit-micropython.readthedocs.io/
from microbit import *

# -----------------------------------------------------------------------------
# Constants
#
opening_sequence = [0, 0, 0, 0]
test_sequence = [12, 4, 15, 8]
positon_min = 0
positon_max = 99

safe_open_button = button_a
encoder_button = button_b
encoder_pin_high = pin8
encoder_pin_low = pin12
open_safe_pin = pin16

rotating_right = True
rotating_left = not rotating_right
count_to_display_units = 200
sequence_item_display_interval = 1000
test_sequence_found_image = Image.HAPPY
safe_open_sequence_found_image = Image.FABULOUS
open_safe_duration = 2000

# -----------------------------------------------------------------------------
# Functions
#
def get_encoder_value():
encoder_value = \
return(encoder_value)

def update_position(position, encoder_previous_value, encoder_value):
encoder_up = [1, 3, 0, 2]
if encoder_value == encoder_up[encoder_previous_value]:
position = position + 1
else:
position = position - 1
if position < positon_min:
position = positon_max
if position > positon_max:
position = positon_min
return(position)

def display_binary(position):
no_intensity = '0'
max_intensity = '9'
bits_per_line = 4
#                                                          prepare bits 3:0
hex_line_0 = ""
for index in range(bits_per_line):
if position % 2 > 0:
hex_line_0 = max_intensity + hex_line_0
else:
hex_line_0 = no_intensity + hex_line_0
position = int(position/2)
hex_line_0 = '0' + hex_line_0
#                                                          prepare bits 7:4
hex_line_1 = ""
for index in range(bits_per_line):
if position % 2 > 0:
hex_line_1 = max_intensity + hex_line_1
else:
hex_line_1 = no_intensity + hex_line_1
position = int(position/2)
hex_line_1 = '0' + hex_line_1
#                                                             display image
display.show(Image(
"00000:" + hex_line_1 + ":00000:" + hex_line_0 + ":00000"
))

def display_tens(position):
if position > 9:
display.show(str(int(position/10)), wait=False)
else:
display.show(str(position % 10), wait=False)

def display_units(position):
display.show(str(position % 10), wait=False)

def display_sequence(sequence):
for index in range(len(sequence)):
if sequence[index] > 9:
display.scroll(str(int(sequence[index]/10)))
display_units(sequence[index])
sleep(sequence_item_display_interval)

new_history = history[1:] + [value]
return(new_history)

def open_safe():
open_safe_pin.write_digital(1)
sleep(open_safe_duration)
open_safe_pin.write_digital(0)

# -----------------------------------------------------------------------------
# Main loop
#
encoder_value = get_encoder_value()
previous_encoder_value = encoder_value
encoder_position = 0
previous_encoder_position = encoder_position
display_units(encoder_position)
activity_counter = 0
history = [0, 0, 0, 0]
rotating_direction = rotating_right
previous_rotating_direction = rotating_direction
while True:
encoder_value = get_encoder_value()
# update position
if previous_encoder_value != encoder_value:
encoder_position = update_position(
encoder_position, previous_encoder_value, encoder_value
)
if encoder_position > previous_encoder_position:
rotating_direction = rotating_right
else:
rotating_direction = rotating_left
if rotating_direction != previous_rotating_direction:
display_binary(encoder_position)
previous_encoder_value = encoder_value
previous_encoder_position = encoder_position
previous_rotating_direction = rotating_direction
activity_counter = 0
#                                                           display history
if encoder_button.is_pressed():
display_sequence(history)
#                                               check history and open safe
if safe_open_button.is_pressed():
display.show(safe_open_sequence_found_image)
open_safe()
display_sequence(test_sequence)