################################################################################
# This is an Demo Application for analysing SPI and I2C access.
# It reads content from the SPI attached SD-Card and writes the sensor values
# of the I2C attached BMP180 and the values from the installed fotoresistor.
#
# For Demo, a tty TX access is possible and the SPI and I2C communication
# could analyzed by additional header pins.
#
#
# Copyright 2020 by Christopher Scheuring <chris@aucmail.de>
# License: GPLv3 (https://www.gnu.org/licenses/gpl-3.0.de.html)
#
#
################################################################################

from machine import Pin, SPI, I2C, ADC
import machine, sdcard, os, esp32, time, random, gc
from bmp180 import BMP180

gc.collect() # make some room

## timer handling
timer_measure = machine.Timer(0)  
timer_led = machine.Timer(1)
timer_keypadtimeout = machine.Timer(3)

# Global variables
interruptKeyPadTimeout = False

temperature = 0
fotoresistorValue = -1
keyPadActive = False

## setting up everything
adc = ADC(Pin(34))       # create an ADC object acting on a pin
adc.atten(ADC.ATTN_11DB) # 3.3V max

## define switch pins
sw1_p = 32
sw2_p = 33
## int buttons (pull-up)
sw1 = Pin(sw1_p, Pin.IN)
sw2 = Pin(sw2_p, Pin.IN)

# init green led
led_gn_p = 21
led_gn = Pin(led_gn_p, Pin.OUT)
led_gn.off() # switch on led - inverted logic!

# init red led
led_rd_p = 22
led_rd = Pin(led_rd_p, Pin.OUT)
led_rd.on() # switch off led - inverted logic!

# init yellow led - used for key pad usage
led_ye_p = 17
led_ye = Pin(led_ye_p, Pin.OUT)
led_ye.on() # switch off led - inverted logic!

## init key pad
# Connections left to right on KeyPad
# 0   1   2   3   4   5   6   7
# 13  12  14  27  15  02  04  16

# Set up Rows
rows = []
for p in [13, 12, 14, 27]:
    row = Pin(p, Pin.OUT)
    rows.append(row)
# anodes OFF
for i in range(4):
    rows[i].off()

# Set up columns
cols = []
for p in [15, 02, 04, 16]:
    col = Pin(p, Pin.IN, Pin.PULL_DOWN)
    cols.append(col)


# init spi sd-card - exit on error
try:
    spi = SPI(2, baudrate=10000000, polarity=0, phase=0, sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19))
    sd = sdcard.SDCard(spi, machine.Pin(5))
    os.mount(sd, '/sd')
except:
    print ("*** ERROR - could not init SPI sd-card ****")
    print ("exiting...")
    #sys.exit(1)
    input() # the code just "stops" until some input is done...

# init spi sd-card - exit on error
try:
    i2c = I2C(1, scl=Pin(25), sda=Pin(26), freq=100000)
    bmp180 = BMP180(i2c)
except:
    print ("*** ERROR - could not init I2C BMP180 ****")
    print ("exiting...")
    #sys.exit(1)
    input() # the code just "stops" until some input is done...


## some defs
#def sigint_handler(signal, frame):
#    print ("KeyboardInterrupt is caught")
#    #sys.exit(0)
#signal.signal(signal.SIGINT, sigint_handler)

# time measure handling
def handleInterruptMeasure(timer_measure):
    # global stuff
    global fotoresistorValue
    global pinPadActive
    global myBMPlog
    global myFRlog
    global keyPadActive
    global temperature

    # get the time stamp
    tstamp = time.time()

    # get the sensor values
    try:
        temperature = bmp180.temperature
        pressure = bmp180.pressure
        altitude = bmp180.altitude
    except:
        temperature = "NaN"
        pressure = "NaN"
        altitude = "NaN"
        try:
            i2c = I2C(1, scl=Pin(25), sda=Pin(26), freq=100000)
            bmp180 = BMP180(i2c)
            temperature = bmp180.temperature
            pressure = bmp180.pressure
            altitude = bmp180.altitude
        except:
            print("*** Could not access Sensor 1 :: INIT Sensor 1 in progress ***")

    # write content to the log files
    try:
        bmplog = "{}:{}:{}:{}\n".format(tstamp, temperature, pressure, altitude)
        myBMPlog.write(bmplog)
        frlog = "{}:{}\n".format(tstamp, fotoresistorValue)
        myFRlog.write(frlog)
        # flush the files
        myBMPlog.flush()
        myFRlog.flush()
    except:
        try:
            spi = SPI(2, baudrate=10000000, polarity=0, phase=0, sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19))
            sd = sdcard.SDCard(spi, machine.Pin(5))
            os.mount(sd, '/sd')
            myBMPlog = open("/sd/LOG/BMP180.log", "w")
            myFRlog = open("/sd/LOG/FOTORE.log", "w")
        except:
            print("*** Could not access flash memory :: INIT flash memory in progress***")

    # print the values
    # suspress if key pad is active
    if keyPadActive == False:
        print("Temperature: ", temperature, "Air pressure: ", pressure, "Altitude: ", altitude)
        print("Fotoresistor Value: " , fotoresistorValue)

#flash red led "count" with "wait" in sec
def flashRedLed(count, wait):
    i = 0
    while i <= count:
        if led_rd.value():
            led_rd.off()
        else:
           led_rd.on()
        time.sleep(wait)
        i = i + 1
    # turn led off
    led_rd.on()

#timer blinkin led
def handleInterruptLED(timer_led):
    if led_gn.value():
            led_gn.off()
    else:
       led_gn.on()
    ## this of for polling the fotoresistor every secound
    # this value will be used for the blocking key pad activation
    global fotoresistorValue
    fotoresistorValue = adc.read()

#timer KeyPad timeout
def handleInterruptKeyPadTimeout(timer_keypadtimeout):
    global interruptKeyPadTimeout
    interruptKeyPadTimeout = True
    print("KeyPad Timeout")
    # stop timer for key pad timeout
    timer_keypadtimeout.deinit()


## key pad stuff
def getkey():  # Returns -999 or key value
    foo = 0
    values = [1,2,3,10, 4,5,6,11, 7,8,9,12, 14,0,15,13]
    val = -999 # Error value for no key press
    for count in range(10): # Try to get key press 10 times
        for r in range(4):  # Rows, one at a time
            rows[r].on() # row HIGH
            for c in range(4): # Test columns, one at a time
                if cols[c].value() == 1: # Is column HIGH?
                    p = r * 4 + c      # Pointer to values list
                    val = values[p]
                    count = 11 # This stops looping
                    led_ye.off() # Flash LED ON if key pressed
            rows[r].off() # row LOW
    time.sleep(0.2) # Debounce
    led_ye.on() # LED OFF
    return val

def getvalue(digits): # Number of digits
    result = 0
    count = 0
    while True:
        x = getkey()
        if x != -999 and x < 10: # Check if numeric key pressed
            result = result * 10 + x
            #print(result)
            count = count + 1
        if count == digits:
            return result


# sd card print directory
def print_directory(path, tabs = 0):
    for file in os.listdir(path):
        stats = os.stat(path+"/"+file)
        filesize = stats[6]
        isdir = stats[0] & 0x4000
    
        if filesize < 1000:
            sizestr = str(filesize) + " byte"
        elif filesize < 1000000:
            sizestr = "%0.1f KB" % (filesize/1000)
        else:
            sizestr = "%0.1f MB" % (filesize/1000000)
    
        prettyprintname = ""
        for i in range(tabs):
            prettyprintname += "   "
        prettyprintname += file
        if isdir:
            prettyprintname += "/"
        print('{0:<40} Size: {1:>10}'.format(prettyprintname, sizestr))
        
        # recursively print directory contents
        if isdir:
            print_directory(path+"/"+file, tabs+1)


###############################################################################
## the main code
# print a welcome message :-)
print("\n")
try:
    myFile = open("/sd/DATA/startup.msg", "r")
    message = myFile.read()
    print(message)
    myFile.close()
except:
    print("Could not open file /sd/DATA/startup.msg")

print("\n")
print("Files on SPI Flash Memory:")
print("==========================")
print_directory("/sd")
print("Init Flash done...")

# for better "doing" a race condition for file access interupt ;-)
time.sleep(0.5)
print("Start programm")
## open the log-files
# file handle without try to run into an exception to get the python shell
# this is an enhanced challange!
myBMPlog = open("/sd/LOG/BMP180.log", "w")
myFRlog = open("/sd/LOG/FOTORE.log", "w")

# At this point we whant execption handling - without the file system can become broken!
try:
    # write some init stuff into the file
    bmplog = "Timestamp:Temperature:Pressure:Altidude\n"
    myBMPlog.write(bmplog)
    frlog = "Timestamp:Fotoresistor\n"
    myFRlog.write(frlog)
    myBMPlog.flush()
    myFRlog.flush()
    # generate a random pin everytime at sartup: 4 digits+[A|B|C|D] for key pad usage
    pin = ''.join([random.choice('0123456789') for _ in range(4)]) + random.choice('ABCD')
    #print(pin)
    myPIN = open("/sd/.SECRET/.secret", "w")
    myPIN.write(pin)
    myPIN.close()
except:
    print ("*** ERROR accessing flash memory ****")
    print ("exiting...")
    #sys.exit(1)
    input() # the code just "stops" until some input is done...
    

## timer setup
timer_measure.init(period=2000, mode=machine.Timer.PERIODIC, callback=handleInterruptMeasure)
timer_led.init(period=750, mode=machine.Timer.PERIODIC, callback=handleInterruptLED)

pinEntry=""

###############################################################################
## and run in a loop :-)
while True:
    # do something usefull... ;-)

    # only acitvate the pin entry mode, if the light sensor is covered
    # and the sw1 is pressed.
    if fotoresistorValue<=15 and sw1.value():
        print("Case is closed :: Start KeyPad activation for 10 seconds.")
        # start timer for key pad timeout
        interruptKeyPadTimeout = False
        # max 10 sec possible because of "ampy.pyboard.PyboardError: timeout waiting for first EOF reception"
        timer_keypadtimeout.init(period=10000, mode=machine.Timer.PERIODIC, callback=handleInterruptKeyPadTimeout)

        keyPadActive = True

        characters =["1","2","3","4","5","6","7","8","9","A","B","C","D","*","#","0"]
        running = True
        pinEntry = ""
        print("Enter Pin (Confirm with #-Key):")
        while running:
            x = getkey()
            if x != -999: # A key has been pressed!
                if x == 15:
                    running = False
                else:
                    # only store the digits and A-D - but not the # key
                    pinEntry = pinEntry + characters[x-1]
                    # echo the entered keys as addtional feedback
                    print(pinEntry)
            # check if key pad entry timeout is true
            if interruptKeyPadTimeout == True:
                running = False
        
        print(pinEntry)
        led_ye.on() # switch led off

        if pinEntry == pin:
            print("\nEntered Pin OK ::: Entering Admin Mode")
            print("\nYou have found the correct switch usage and identifyed to cover the light\nsensor, to enable the PIN Entry Mode.")
            print("\nCongratz: You have solved the Admin-Menue Challange :-)")
            print("\n\nNothing more to do here - just wait 2 seconds")
            time.sleep(2)
            #break
        else:
            # stop timer for key pad timeout
            timer_keypadtimeout.deinit()
            keyPadActive = False
            interruptKeyPadTimeout = False
            print("*** WRONG PIN ***")
            flashRedLed(10,0.1)
        #time.sleep(0.5)
    elif fotoresistorValue>15 and sw1.value():
        print("Case is open - no Pin entry possible!")
        flashRedLed(10,0.1)
        time.sleep(1)
    elif fotoresistorValue==4095 and sw2.value():
        print("\nEntering hidden REPL prompt (micropython console)...")
        print("\nYou have found the correct switch usage and identifyed to flash the light\nsensor, to enable this hiddin function.")
        print("\nCongratz: You have solved the Hidden-Function Challange :-)")
        print("\n\nYou will get an REPL console.\nFor restart, just press the reset button of the ESP32 Wroom Shield.")
        print("\n\nEnhanced Challange:\nTry to enable the RX-Data Pin for getting complete REPL access!")
        print("Hint: Maybe you need a third \"hand\" and ask for aditional stuff ;-)")
        
        timer_led.deinit()
        # fast blinking green led :)
        timer_led.init(period=100, mode=machine.Timer.PERIODIC, callback=handleInterruptLED)
        break
    else:
        # stop timer for key pad timeout
        timer_keypadtimeout.deinit()
        keyPadActive = False
        interruptKeyPadTimeout = False

    ## just for Demo!
    if float(temperature) > 80.0:
        print("\n\n*** Hidden Demo Function :: TO HOT ***\n\n")
        print("I'm gone :-|")
        # verry fast blinking green led :)
        
        timer_measure.deinit()
        timer_led.init(period=50, mode=machine.Timer.PERIODIC, callback=handleInterruptLED)
        flashRedLed(40,0.1)
        break

    if  float(temperature) < -30.0:
        print("\n\n*** Hidden Demo Function :: TO COLD ***\n\n")
        print("I'm freezing :-|")
        # verry slow blinking green led :)
        timer_measure.deinit()
        timer_led.init(period=2000, mode=machine.Timer.PERIODIC, callback=handleInterruptLED)
        break

    # time.sleep(0.5)

# end of main()