import RPi.GPIO as GPIO
import time
import threading
 
# Define GPIO pin numbers
SER = 23
SRCLK = 25
RCLK = 24
SRCLR = 12
DIGIT_PINS = [5, 6, 13,16]
# Define bit patterns for numbers 0 to 9
PATTERNS = [0xfc, 0x60, 0xda, 0xf2, 0x66, 0xb6, 0xbe, 0xe0, 0xfe, 0xf6]
 
# Define initializing function
def initialize():
    GPIO.setmode(GPIO.BCM)
    # Deselect all the digits
    for digit in DIGIT_PINS:
        GPIO.setup(digit, GPIO.OUT, initial=GPIO.LOW)
    # LEDs are anode common
    GPIO.setup(SER, GPIO.OUT, initial=GPIO.HIGH)
    GPIO.setup(SRCLK, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(RCLK, GPIO.OUT, initial=GPIO.LOW)
    # SRCLR is negative logic
    GPIO.setup(SRCLR, GPIO.OUT, initial=GPIO.HIGH)
 
# Define ending process function
def destroy():
    for digit in DIGIT_PINS:
        GPIO.output(digit, GPIO.LOW)
    GPIO.cleanup()
 
# Define function to display single digit
# digit: suffixes of DIGITS list
# pattern_number: suffixes of PATTERNS list
# duration: Duration time to turn on the designated digit (milliseconds)
def display_a_digit(digit, pattern_number, duration):
    GPIO.output(DIGIT_PINS[digit], GPIO.HIGH)
    # Do for each number display
    for n in range(8):
        # Input bits from LSB to MSB
        # True == 1 == GPIO.HIGH and False == 0 == GPIO.LOW
        GPIO.output(SER, not((PATTERNS[pattern_number] >> n) & 1))
        # Send bit to shift register
        GPIO.output(SRCLK, GPIO.HIGH)
        GPIO.output(SRCLK, GPIO.LOW)
    # Get parallel data
    GPIO.output(RCLK, GPIO.HIGH)
    GPIO.output(RCLK, GPIO.LOW)
    # Continue to turn on during duration time
    time.sleep(duration)
    # Turn off the digit afterwards
    GPIO.output(DIGIT_PINS[digit], GPIO.LOW)
 
# Display all the digits
# value_string: 4-digit integer value in string to display
# duration: duration time to display each digit
def display_digits(value_string, duration):
    for digit, number in enumerate(list(value_string)):
        display_a_digit(digit, int(number), duration)
 
# Define function to convert integer to 2-digit string with 0 prefixed
def get_two_digits(n):
    return ('00' + str(n))[-2:]
 
# Define custom class inheriting Thread to display number
class NumberDisplay(threading.Thread):
    # Arguments are same to those of display_digits function
    def __init__(self, value_string, duration):
        threading.Thread.__init__(self)
        self.value_string = value_string
        self.duration = duration
        self.is_running = True
 
    # Override run method of Thread class
    def run(self):
        # Continue while the flag is True
        while self.is_running:
            display_digits(self.value_string, self.duration)
 
    # Quit running by setting the flag to False
    def stop(self):
        self.is_running = False
 
# Start Execution
 
initialize()
 
# time is counted in seconds
minutes = 0
seconds = 5
time_in_seconds = minutes * 60 + seconds
 
try:
    # Loop while time remains
    while time_in_seconds > 0:
        # Set value_string from minuts and seconds
        m = time_in_seconds // 60
        s = time_in_seconds - m * 60
        value_string = get_two_digits(m) + get_two_digits(s)
        # Create thread to display the number
        t = NumberDisplay(value_string, 0.001)
        # Continue to display for 1 second
        t.start()
        time.sleep(1.0)
        t.stop()
        # Count down
        time_in_seconds -= 1
 
    while True:
            display_digits('0000', 0.001)
except KeyboardInterrupt:
    destroy()