#!/usr/bin/env python3
########################################################################
# Filename    : Keypad.py
# Description : The module of matrix keypad 
# Author      : freenove
# modification: 2016/07/13
########################################################################
import RPi.GPIO as GPIO 
import time
#class Key:Define some of the properties of Key
class Key(object):
  NO_KEY = '\0'
  #Defines the four states of Key
  IDLE = 0
  PRESSED = 1
  HOLD = 2
  RELEASED = 3
  #define OPEN and CLOSED
  OPEN = 0
  CLOSED =1
  #constructor
  def __init__(self):
    self.kchar = self.NO_KEY
    self.kstate = self.IDLE
    self.kcode = -1
    self.stateChanged = False

class Keypad(object):
  NULL = '\0'
  LIST_MAX = 10  #Max number of keys on the active list.
  MAPSIZE = 10  #MAPSIZE is the number of rows (times 16 columns)
  bitMap = [0]*MAPSIZE
  key = [Key()]*LIST_MAX
  holdTime = 500    #key hold time
  holdTimer = 0
  startTime = 0
  #Allows custom keymap, pin configuration, and keypad sizes.
  def __init__(self,usrKeyMap,row_Pins,col_Pins,num_Rows,num_Cols):
    GPIO.setmode(GPIO.BOARD)
    self.rowPins = row_Pins
    self.colPins = col_Pins
    self.numRows = num_Rows
    self.numCols = num_Cols
    
    self.keymap = usrKeyMap
    self.setDebounceTime(10)
  #Returns a single key only. Retained for backwards compatibility.  
  def getKey(self):
    single_key = True
    if(self.getKeys() and self.key[0].stateChanged and (self.key[0].kstate == self.key[0].PRESSED)):
      return self.key[0].kchar
    single_key = False
    return self.key[0].NO_KEY
  #Populate the key list.  
  def getKeys(self):
    keyActivity = False
    #Limit how often the keypad is scanned.
    if((time.time() - self.startTime) > self.debounceTime*0.001):
      self.scanKeys()
      keyActivity = self.updateList()
      self.startTime = time.time()
    return keyActivity
  #Hardware scan ,the result store in bitMap  
  def scanKeys(self):
    #Re-intialize the row pins. Allows sharing these pins with other hardware.
    for pin_r in self.rowPins:
      GPIO.setup(pin_r,GPIO.IN,pull_up_down = GPIO.PUD_UP)
    #bitMap stores ALL the keys that are being pressed.
    for pin_c in self.colPins:
      GPIO.setup(pin_c,GPIO.OUT)
      GPIO.output(pin_c,GPIO.LOW)
      for r in self.rowPins: #keypress is active low so invert to high.
        self.bitMap[self.rowPins.index(r)] = self.bitWrite(self.bitMap[self.rowPins.index(r)],self.colPins.index(pin_c),not GPIO.input(r))
      #Set pin to high impedance input. Effectively ends column pulse.
      GPIO.output(pin_c,GPIO.HIGH)
      GPIO.setup(pin_c,GPIO.IN)
  #Manage the list without rearranging the keys. Returns true if any keys on the list changed state.    
  def updateList(self):
    anyActivity = False
    kk = Key()
    #Delete any IDLE keys
    for i in range(self.LIST_MAX):
      if(self.key[i].kstate == kk.IDLE):
        self.key[i].kchar = kk.NO_KEY
        self.key[i].kcode = -1
        self.key[i].stateChanged = False
    # Add new keys to empty slots in the key list.
    for r in range(self.numRows):
      for c in range(self.numCols):
        button = self.bitRead(self.bitMap[r],c)
        keyChar = self.keymap[r * self.numCols +c]
        keyCode = r * self.numCols +c
        idx = self.findInList(keyCode)
        #Key is already on the list so set its next state.
        if(idx > -1):
          self.nextKeyState(idx,button)
        #Key is NOT on the list so add it.
        if((idx == -1) and button):
          for i in range(self.LIST_MAX):
            if(self.key[i].kchar == kk.NO_KEY):  #Find an empty slot or don't add key to list.
              self.key[i].kchar = keyChar
              self.key[i].kcode = keyCode
              self.key[i].kstate = kk.IDLE  #Keys NOT on the list have an initial state of IDLE.
              self.nextKeyState(i,button)
              break  #Don't fill all the empty slots with the same key.
    #Report if the user changed the state of any key.
    for i in range(self.LIST_MAX):
      if(self.key[i].stateChanged):
        anyActivity = True
    return anyActivity    
  #This function is a state machine but is also used for debouncing the keys.  
  def nextKeyState(self,idx, button):
    self.key[idx].stateChanged = False
    kk = Key()
    if(self.key[idx].kstate == kk.IDLE):
      if(button == kk.CLOSED):
        self.transitionTo(idx,kk.PRESSED)
        self.holdTimer = time.time()  #Get ready for next HOLD state.
    elif(self.key[idx].kstate == kk.PRESSED):
      if((time.time() - self.holdTimer) > self.holdTime*0.001):  #Waiting for a key HOLD...  
        self.transitionTo(idx,kk.HOLD)
      elif(button == kk.OPEN):    # or for a key to be RELEASED.
        self.transitionTo(idx,kk.RELEASED)
    elif(self.key[idx].kstate == kk.HOLD):
      if(button == kk.OPEN):
        self.transitionTo(idx,kk.RELEASED)
    elif(self.key[idx].kstate == kk.RELEASED):
      self.transitionTo(idx,kk.IDLE)
      
  def transitionTo(self,idx,nextState):
    self.key[idx].kstate = nextState
    self.key[idx].stateChanged = True
  #Search by code for a key in the list of active keys.
  #Returns -1 if not found or the index into the list of active keys.
  def findInList(self,keyCode):
    for i in range(self.LIST_MAX):
      if(self.key[i].kcode == keyCode):
        return i
    return -1
  #set Debounce Time,  The default is 50ms          
  def setDebounceTime(self,ms):
    self.debounceTime = ms
  #set HoldTime,The default is 500ms
  def setHoldTime(self,ms):
    self.holdTime = ms
  #  
  def isPressed(keyChar):
    for i in range(self.LIST_MAX):
      if(self.key[i].kchar == keyChar):
        if(self.key[i].kstate == self.self.key[i].PRESSED and self.key[i].stateChanged):
          return True
    return False
  #      
  def waitForKey():
    kk = Key()
    waitKey = kk.NO_KEY
    while(waitKey == kk.NO_KEY):
      waitKey = getKey()
    return waitKey
  
  def getState():
    return self.key[0].kstate
  #  
  def keyStateChanged():
    return self.key[0].stateChanged
  
  def bitWrite(self,x,n,b):
    if(b):
      x |= (1<<n)
    else:
      x &=(~(1<<n))
    return x
  def bitRead(self,x,n):
    if((x>>n)&1 == 1):
      return True
    else:
      return False
