EmlaLockBox - The Code

Selfbondage software and other kinky developments

Moderators: Riddle, Shannon SteelSlave

Post Reply
User avatar
Audrey_CD
***
Posts: 224
Joined: 04 Mar 2009, 22:46
Location: United Kingdom

EmlaLockBox - The Code

Post by Audrey_CD »

In the DIY forum I posted on the EmlaLockBox I had created and offered to post the code. Sorry its taken a while - issues with having the right computer in the correct country!

Naturally you use it at your own risk and don't come crying to me if it goes horribly wrong and you can't get your keys back!

Code: Select all

#Import the modules
import requests
import json
import sys
import time
import Adafruit_CharLCD as LCD
from Adafruit_PWM_Servo_Driver import PWM


def setServoPulse(channel, pulse):
  pulseLength = 1000000                   # 1,000,000 us per second
  pulseLength /= 60                       # 60 Hz
  print "%d us per period" % pulseLength
  pulseLength /= 4096                     # 12 bits of resolution
  print "%d us per bit" % pulseLength
  pulse *= 1000
  pulse /= pulseLength
  pwm.setPWM(channel, 0, pulse)

def lock(combination):
    global servoMax
    global pwm
    pwm.setPWM(0, 0, servoMax)
    print("Click "+str(combination))
    return True;

def unlock(combination):
    global servoMin
    global pwm
    pwm.setPWM(0, 0, servoMin)
    print("Clack "+str(combination))
    return True;

def random():
    value=123456
    return value

def handleResult(t):
    global status
    global startDate
    global elError
    data = json.loads(t)
    print(data)
    try:
        status=data['ERR']
        print ('Error: ', status)
        elError=status
        return False
    except KeyError:
        status="not an error"
    try:    
        status = data['INFO']
        return True
    except KeyError:
        status="No Info"
    try:    
        status = data['sessionactive']
    except KeyError:
        status="No Info"
    elError=""
    return True
        
def currentTime():
    t = requests.get("http://www.timeapi.org/utc/now")
    return t.text

#########################################################
# Routines that handle communication with EMLALOCK.COM
#########################################################

def info():
    global userid
    global apikey
    global device
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=info")
    r.text
    if handleResult(r.text):
        return True
    else:
        return False

def checkDevice():
    global userid
    global apikey
    global device
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=checkUserDevice&deviceid="+str(device))
    r.text
    print(r.text)
    if handleResult(r.text):
        return True
    else:
        return False

def registerDevice():
    global userid
    global apikey
    global device
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=activateDeviceID&deviceid="+str(device)+"&session=1&description=Auto Lock Box&cleaning=1")
    r.text
    if handleResult(r.text):
        return True
    else:
        return False

def startSession():
    global userid
    global apikey
    global device
    combination=random()
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=activateDeviceSession&deviceid="+str(device)+"&combination="+str(combination))
    r.text
    if handleResult(r.text):
        lock(combination)
        return True
    else:
        return False
    
def endSession():
    global userid
    global apikey
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=endDeviceSession")
    r.text
    if handleResult(r.text):
        data = json.loads(r.text)
        unlock(data['CODE'])
        return True
    else:
        return False
    
def emergency(pw):
    global userid
    global apikey
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=emergencyEndDeviceSession&keyword="+str(pw)+"&failed=FAILED")
    r.text
    if handleResult(r.text):
        data = json.loads(r.text)
        unlock(data['CODE'])
        return True
    else:
        return False
    
def startHygiene():
    global userid
    global apikey
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=startDeviceHygieneopening")
    r.text
    if handleResult(r.text):
        data = json.loads(r.text)
        unlock(data['CODE'])
        return True
    else:
        return False
    
def endHygiene():
    global userid
    global apikey
    global device
    combination=random()
    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=endDeviceHygieneopening&deviceid="+str(device)+"&combination="+str(combination))
    r.text
    if handleResult(r.text):
        lock(combination)
        return True
    else:
        return False

###############################################################
# Routines that handle the menus and options
###############################################################

def waitForStart():
    global message
    global commands
    global elError
    commands=['Start','Settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Select to Start'
    lcd.set_color(0.0, 0.0, 1.0) # Blue
    option=waitForSelect()
    if option==0:
        if startSession():
            return 1  #1 = sessionRunning
    if option==1:
        return 4
    return 0
        
    
def sessionRunning():
    global message
    global commands
    global elError
    commands=['Initiate Hygiene','End Session','Emergency Quit']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Session Running'
    lcd.set_color(0.0, 1.0, 0.0) # Green
    option=waitForSelect()
    if option==0:
        if startHygiene():
            return 2 # 2 = Hygiene Session
    if option==1:
        if endSession():
            return 3 # 0 = wait for session
    if option==2:
        if emergency('HELP'):
            return 3 # 0 = wait for session
    return 1

def hygieneSession():
    global message
    global commands
    global elError
    commands=['End Cleaning','End Session','Emergency Quit']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Hygiene Running'
    lcd.set_color(1.0, 1.0, 0.0) # Yellow
    option=waitForSelect()
    if option==0:
        if endHygiene():
            return 1 # 1 = sessionRunningn
    if option==1:
        if endSession():
            return 3 # 0 = wait for session
    if option==2:
        if emergency('HELP'):
            return 3 # 0 = wait for session
    return 2

def noSession():
    global message
    global commands
    global elError
    commands=['Check for session','Settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='No session available'
    lcd.set_color(1.0, 0.0, 0.0) # Red
    option=waitForSelect()
    if option==0:
        if info():
            if status==True:
                return 1 # Active session exists: sessionRunning
            else:
                return 0 # Session exists but not active: waitForSession
    if option==1:
        return 4 # Go to the settings menu

    return 3 # Still no session - stay in this mode

def settings():
    global message
    global commands
    global elError
    commands=['Set servo lock','Set servo unlock','Quit settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Settings'
    lcd.set_color(1.0, 1.0, 1.0) # White
    option=waitForSelect()
    if option==0:
        return 5 # 5 = Set servo lock position
    if option==1:
        return 6 # 6 = Set servo unlock position
    if option==2:
        if info():
            if status==True:
                return 1 # Active session exists: sessionRunning
            else:
                return 0 # Session exists but not active: waitForSession
        return 3
    return 4


def waitForSelect():
    buttons=(LCD.SELECT,LCD.UP,LCD.DOWN)
    currentOption=0
    global message
    global commands
    message[1]=commands[currentOption]
    lcd.clear()
    lcd.message(message[0]+"\n"+commands[currentOption])
    while not lcd.is_pressed(buttons[0]):
        if lcd.is_pressed(buttons[1]):
            if currentOption==0:
                currentOption=len(commands)-1
            else:
                currentOption-=1
            print('currentOption=',currentOption)
            message[1]=commands[currentOption]
            lcd.clear()
            lcd.message(message[0]+"\n"+message[1])
        if lcd.is_pressed(buttons[2]):
            currentOption+=1
            if currentOption==len(commands):
                currentOption=0
            print('currentOption=',currentOption)
            message[1]=commands[currentOption]
            lcd.clear()
            lcd.message(message[0]+"\n"+message[1])
    return currentOption    

def setServoLock():
    print("servo lock")
    global servoMax
    buttons=(LCD.SELECT,LCD.LEFT,LCD.RIGHT)
    value=servoMax
    lcd.clear()
    lcd.message('Set Servo Lock\nServo (150:600) '+str(value))
    while not lcd.is_pressed(buttons[0]):
        if lcd.is_pressed(buttons[1]):
            if value>10:
                value-=10
            lcd.clear()
            lcd.message('Set Servo Lock\nServo (150:600) '+str(value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
        if lcd.is_pressed(buttons[2]):
            if value<590:
                value+=10
            lcd.clear()
            lcd.message('Set Servo Lock\nServo (150:600) '+str(value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
    # Store value
    servoMax=value
    print("Servo unlock value ",str(servoMax))
    return 4
  


def setServoUnlock():
    print("Servo unlock")
    global servoMin
    buttons=(LCD.SELECT,LCD.LEFT,LCD.RIGHT)
    value=servoMin
    lcd.clear()
    lcd.message('Set Servo Unlock\nServo (150:600) '+str(value))
    while not lcd.is_pressed(buttons[0]):
        if lcd.is_pressed(buttons[1]):
            if value>0:
                value-=10
            lcd.clear()
            lcd.message('Set Servo Lock\nServo (150:600) '+str(value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
        if lcd.is_pressed(buttons[2]):
            if value<590:
                value+=10
            lcd.clear()
            lcd.message('Set Servo Lock\nServo (150:600) '+str(value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
    # Store value
    servoMin=value
    print("Servo unlock value ",str(servoMin))
    return 4

# initialise global variables
userid=""
apikey=""
device=""
status=""
message=['message','option']
elError=""
servoMax=600
servoMin=150

lcd = LCD.Adafruit_CharLCDPlate()

pwm = PWM(0x40)
pwm.setPWMFreq(60)

commands=('a','b')

# get the command line args
x=1   #ignore zero entry as it will be the program file
while x<len(sys.argv):
    a=str(sys.argv[x])
    print('arg=',a)
    if a[:6]=='userid':
        userid=a[7:]
    if a[:6]=='apikey':
        apikey=a[7:]
    if a[:6]=='device':
        device=a[7:]
    if a[:8]=='servoMax':
        servoMax=int(a[9:])
    if a[:8]=='servoMin':
        servoMin=int(a[9:])
    x=x+1

#Check if the device is registered and if not register it
mustRegister=True
if checkDevice():
    for cDevice in status:
        if cDevice==device:
            mustRegister=False
if mustRegister:
    registerDevice()

# Check is a session is already running
if info():
    print(status)
    if status==True:
        command=1
    else:
        command=0
else:
    command=3
    
while True:
    if command==0:
        command=waitForStart()
    if command==1:
        command=sessionRunning()
    if command==2:
        command=hygieneSession()
    if command==3:
        command=noSession()
    if command==4:
        command=settings()
    if command==5:
        command=setServoLock()
    if command==6:
        command=setServoUnlock()
    
    
I'm not a native python developer, so you can probably suggest a hundred ways it could be improved!
User avatar
Sir Cumference
Moderator
Posts: 1606
Joined: 29 Jan 2012, 22:00
Location: Scandinavia

Re: EmlaLockBox - The Code

Post by Sir Cumference »

Thanks.

I'm quite new to python, but it is good to learn something new.

Will it be OK for you if I join the two threads?
I think it would be nice to have them together.
If not, please put a link to this tread in the box-thread too.
~ Leatherworking, blacksmithing , woodworking and programming are the most pervertable skills you can learn! ~
User avatar
Audrey_CD
***
Posts: 224
Joined: 04 Mar 2009, 22:46
Location: United Kingdom

Re: EmlaLockBox - The Code

Post by Audrey_CD »

Please do whatever you think best.
User avatar
Audrey_CD
***
Posts: 224
Joined: 04 Mar 2009, 22:46
Location: United Kingdom

Re: EmlaLockBox - The Code

Post by Audrey_CD »

I've now produced version two of the code.
The changes put in a class for the returned message from Emlalock and more importantly allow for time outs on the calls to EmlaLock, repeating the call if it hasn't responded within 10 seconds.
As before, you use at your own risk.

Code: Select all

#Import the modules
import requests
import eventlet
import json
import sys
import time
import Adafruit_CharLCD as LCD
from Adafruit_PWM_Servo_Driver import PWM

# 31-1-2017 Use a class to hold the emla results
class emlaResult:
  def __init__(self):
    self.status = ""
    self.statusText = ""
    self.deviceid = ""
    self.startdate = ""
    self.sessionactive = False
    self.duration = 0
    self.maxduration = 0
    self.minduration = 0
    self.requirements = 0
    self.displaymode = 0
    self.combination = 0

  def load(json):
    data = json.loads(t)
    try:
    	self.statusText = data['ERR']
        self.status="ERR"
        return False
    except KeyError:
        self.status="OK"
    try:
        self.combination = data['CODE']
    except KeyError:
        self.status = "No Unlock"
    try:
        self.status = data['INFO']
        return True
    except KeyError:
        self.status = "No Info"
    try:
        self.deviceid = data['deviceid']
        self.sessionactive = data['sessionactive']
        self.startdate = data['startdate']
        self.duration = data['duration']
        self.maxduration = data['maxduration']
        self.minduration = data['minduration']
        self.requirements = data['requirements']
        self.displaymode = data['displaymode']
        return True
    except KeyError:
        self.status="Whoopse"

  def gSessionActive():
    return self.sessionactive

  def gStartdate():
    return self.startdate

  def gDuration():
    return self.duration

  def gMaxduration():
    return self.maxduration

  def gMinduration():
    return self.minduration

  def gRequirements():
    return self.requirements

  def gDisplaymode():
    return self.displaymode

		
	
        



def setServoPulse(channel, pulse):
  pulseLength = 1000000                   # 1,000,000 us per second
  pulseLength /= 60                       # 60 Hz
  print "%d us per period" % pulseLength
  pulseLength /= 4096                     # 12 bits of resolution
  print "%d us per bit" % pulseLength
  pulse *= 1000
  pulse /= pulseLength
  pwm.setPWM(channel, 0, pulse)

def lock(combination):
    global servoMax
    global pwm
    pwm.setPWM(0, 0, servoMax)
    print("Click "+str(combination))
    return True;

def unlock(combination):
    global servoMin
    global pwm
    pwm.setPWM(0, 0, servoMin)
    print("Clack "+str(combination))
    return True;

def random():
    value=123456
    return value

# 31-1-2017 - Replaced with elmaResult load function
#def handleResult(t):
#    global status
#    global startDate
#    global elError
#    data = json.loads(t)
#    print(data)
#    try:
#        status=data['ERR']
#        print ('Error: ', status)
#        elError=status
#        return False
#    except KeyError:
#        status="not an error"
#    try:    
#        status = data['INFO']
#        return True
#    except KeyError:
#        status="No Info"
#    try:    
#        status = data['sessionactive']
#    except KeyError:
#        status="No Info"
#    elError=""
#    return True
        
def currentTime():
    t = requests.get("http://www.timeapi.org/utc/now")
    return t.text

#########################################################
# Routines that handle communication with EMLALOCK.COM
#########################################################

######################################################################
# 31-1-2017 generic http call routine with timeout error handling
######################################################################
def httpCall(url, source):
	retry = True
	count = 0
	while retry:
		timeout = eventlet.Timeout(10)
		try:
			r = requests.get(url, verify=False)
			retry = False
		except Timeout as t:
			count += 1
			lcd.message('%s:Timeout\nRetrying %d' % (source, count))
		finally:
			timeout.cancel()
	lcd.clear()

	return r

def info():
    global userid
    global apikey
    global emlareturn
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=info")
	# 31-1-2017 Use http routine that checks for timeout situations
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=info", "info")):
        return True
    else:
        return False

def checkDevice():
    global userid
    global apikey
    global device
    global emlareturn
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=checkUserDevice&deviceid="+str(device))
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=checkUserDevice&deviceid="+str(device), "chkDevice")):
        return True
    else:
        return False

def registerDevice():
    global userid
    global apikey
    global device
    global emlareturn
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=activateDeviceID&deviceid="+str(device)+"&session=1&description=Auto Lock Box&cleaning=1")
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=activateDeviceID&deviceid="+str(device)+"&session=1&description=Auto Lock Box&cleaning=1", "Register")):
        return True
    else:
        return False

def startSession():
    global userid
    global apikey
    global device
    global emlareturn
    combination=random()
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=activateDeviceSession&deviceid="+str(device)+"&combination="+str(combination))
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=activateDeviceSession&deviceid="+str(device)+"&combination="+str(combination), "Start")):
        lock(combination)
        return True
    else:
        return False
    
def endSession():
    global userid
    global apikey
    global emlareturn
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=endDeviceSession")
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=endDeviceSession", "End Session")):
        unlock(emlaresult.combination)
        return True
    else:
        return False
    
def emergency(pw):
    global userid
    global apikey
    global emlareturn
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=emergencyEndDeviceSession&keyword="+str(pw)+"&failed=FAILED")
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=emergencyEndDeviceSession&keyword="+str(pw)+"&failed=FAILED", "Emergency")):
        unlock(emlaresult.combination)
        return True
    else:
        return False
    
def startHygiene():
    global userid
    global apikey
    global emlareturn
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=startDeviceHygieneopening")
    if emlaresult.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=startDeviceHygieneopening", "Start Cleaning")):
        unlock(emlaresult.combination)
        return True
    else:
        return False
    
def endHygiene():
    global userid
    global apikey
    global device
    global emlareturn
    combination=random()
	# 31-1-2017 Use http routine that checks for timeout situations
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=endDeviceHygieneopening&deviceid="+str(device)+"&combination="+str(combination))
    if emlareturn.load(httpCall("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=endDeviceHygieneopening&deviceid="+str(device)+"&combination="+str(combination), "End Cleaning")):
        lock(combination)
        return True
    else:
        return False

###############################################################
# Routines that handle the menus and options
###############################################################

def waitForStart():
    global message
    global commands
    global elError
    commands=['Start','Settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Select to Start'
    lcd.set_color(0.0, 0.0, 1.0) # Blue
    option=waitForSelect()
    if option==0:
        if startSession():
            return 1  #1 = sessionRunning
    if option==1:
        return 4
    return 0
        
    
def sessionRunning():
    global message
    global commands
    global elError
    commands=['Initiate Hygiene','End Session','Emergency Quit']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Session Running'
    lcd.set_color(0.0, 1.0, 0.0) # Green
    option=waitForSelect()
    if option==0:
        if startHygiene():
            return 2 # 2 = Hygiene Session
    if option==1:
        if endSession():
            return 3 # 0 = wait for session
    if option==2:
        if emergency('HELP'):
            return 3 # 0 = wait for session
    return 1

def hygieneSession():
    global message
    global commands
    global elError
    commands=['End Cleaning','End Session','Emergency Quit']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Hygiene Running'
    lcd.set_color(1.0, 1.0, 0.0) # Yellow
    option=waitForSelect()
    if option==0:
        if endHygiene():
            return 1 # 1 = sessionRunningn
    if option==1:
        if endSession():
            return 3 # 0 = wait for session
    if option==2:
        if emergency('HELP'):
            return 3 # 0 = wait for session
    return 2

def noSession():
    global message
    global commands
    global elError
    commands=['Check for session','Settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='No session available'
    lcd.set_color(1.0, 0.0, 0.0) # Red
    option=waitForSelect()
    if option==0:
        if info():
            if status==True:
                return 1 # Active session exists: sessionRunning
            else:
                return 0 # Session exists but not active: waitForSession
    if option==1:
        return 4 # Go to the settings menu

    return 3 # Still no session - stay in this mode

def settings():
    global message
    global commands
    global elError
    commands=['Set servo lock','Set servo unlock','Quit settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Settings'
    lcd.set_color(1.0, 1.0, 1.0) # White
    option=waitForSelect()
    if option==0:
        return 5 # 5 = Set servo lock position
    if option==1:
        return 6 # 6 = Set servo unlock position
    if option==2:
        if info():
            if status==True:
                return 1 # Active session exists: sessionRunning
            else:
                return 0 # Session exists but not active: waitForSession
        return 3
    return 4


def waitForSelect():
    buttons=(LCD.SELECT,LCD.UP,LCD.DOWN)
    currentOption=0
    global message
    global commands
    message[1]=commands[currentOption]
    lcd.clear()
    lcd.message(message[0]+"\n"+commands[currentOption])
    while not lcd.is_pressed(buttons[0]):
        if lcd.is_pressed(buttons[1]):
            if currentOption==0:
                currentOption=len(commands)-1
            else:
                currentOption-=1
            print('currentOption=',currentOption)
            message[1]=commands[currentOption]
            lcd.clear()
            lcd.message(message[0]+"\n"+message[1])
        if lcd.is_pressed(buttons[2]):
            currentOption+=1
            if currentOption==len(commands):
                currentOption=0
            print('currentOption=',currentOption)
            message[1]=commands[currentOption]
            lcd.clear()
            lcd.message(message[0]+"\n"+message[1])
    return currentOption    

def setServoLock():
    print("servo lock")
    global servoMax
    buttons=(LCD.SELECT,LCD.LEFT,LCD.RIGHT)
    value=servoMax
    lcd.clear()
    lcd.message('Set Servo Lock\n(150:600) %d' % (value))
    while not lcd.is_pressed(buttons[0]):
        if lcd.is_pressed(buttons[2]):
            if value>10:
                value-=10
            lcd.clear()
            lcd.message('Set Servo Lock\n(150:600) %d' % (value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
        if lcd.is_pressed(buttons[1]):
            if value<600:
                value+=10
            lcd.clear()
            lcd.message('Set Servo Lock\n(150:600) %d' % (value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
    # Store value
    servoMax=value
    # Move lock to unlock position
    pwm.setPWM(0, 0, servoMin)
    print("Servo unlock value ",str(servoMax))
    return 4
  


def setServoUnlock():
    print("Servo unlock")
    global servoMin
    buttons=(LCD.SELECT,LCD.LEFT,LCD.RIGHT)
    value=servoMin
    lcd.clear()
    lcd.message('Set Servo Unlock\n(150:600) %d' % (value))
    while not lcd.is_pressed(buttons[0]):
        if lcd.is_pressed(buttons[2]):
            if value>0:
                value-=10
            lcd.clear()
            lcd.message('Set Servo Unlock\n(150:600) %d' % (value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
        if lcd.is_pressed(buttons[1]):
            if value<600:
                value+=10
            lcd.clear()
            lcd.message('Set Servo Unlock\n(150:600) %d' % (value))
            # Do something with the servo
            pwm.setPWM(0, 0, value)
    # Store value
    servoMin=value
    # Move lock to unlock position
    pwm.setPWM(0, 0, servoMin)
    print("Servo unlock value ",str(servoMin))
    return 4

# initialise global variables
userid=""
apikey=""
emlareturn = emlaResult()

message=['message','option']
elError=""
servoMax=600
servoMin=150

lcd = LCD.Adafruit_CharLCDPlate()
lcd.clear()
lcd.message("Emla Auto Safe\nStart up")

pwm = PWM(0x40)
pwm.setPWMFreq(60)

commands=('a','b')

# get the command line args
x=1   #ignore zero entry as it will be the program file
while x<len(sys.argv):
    a=str(sys.argv[x])
    print('arg=',a)
    if a[:6]=='userid':
        userid=a[7:]
    if a[:6]=='apikey':
        apikey=a[7:]
    if a[:6]=='device':
        device=a[7:]
    if a[:8]=='servoMax':
        servoMax=int(a[9:])
    if a[:8]=='servoMin':
        servoMin=int(a[9:])
    x=x+1

#Check if the device is registered and if not register it
mustRegister=True
if checkDevice():
    for cDevice in emlareturn.status:
        if cDevice==device:
            mustRegister=False
if mustRegister:
    registerDevice()

# Check is a session is already running
if info():
    print(status)
    if status==True:
        command=1
    else:
        command=0
else:
    command=3
    
while True:
    if command==0:
        command=waitForStart()
    if command==1:
        command=sessionRunning()
    if command==2:
        command=hygieneSession()
    if command==3:
        command=noSession()
    if command==4:
        command=settings()
    if command==5:
        command=setServoLock()
    if command==6:
        command=setServoUnlock()
    
    
Thub56
*
Posts: 10
Joined: 01 Nov 2017, 23:38

Re: EmlaLockBox - The Code

Post by Thub56 »

I love this idea. Two questions, Which LCD did you use? How difficult would it be to make a version that displays to the HDMI as an onboard UI? I've done very little python coding (or coding of any kind for that matter)
User avatar
Audrey_CD
***
Posts: 224
Joined: 04 Mar 2009, 22:46
Location: United Kingdom

Re: EmlaLockBox - The Code

Post by Audrey_CD »

Skipping over version 3.0 and onto version 4.0.

As usual, the code below is provided without any warranty, guarantee or assurance. If it all goes horribly wrong, don't come crying to me! ;)

Code: Select all

#Import the modules
import requests
import eventlet
import json
import sys
import time
import Adafruit_CharLCD as LCD
import configparser
from Adafruit_PWM_Servo_Driver import PWM
import tty, termios
import events
import evdev
from evdev import InputDevice, categorize, ecodes
import random
from PIL import Image, ImageDraw, ImageFont
import os


def version():
#   1.0  Original
#   2.0   31-1-2017
#         Use a class to hold the emla results
#         Generic http call routine with timeout error handling
#   3.0   11-5-2017
#         Add version control
#         Add PIN security/PIN change function
#         Store parameters in an INI file
#         Allow the user to be selected (when not in a session) from list in INI file.
#   4.0   Change to use without Emlalock API. Now generates a combination and saves it into an image file
#         Process is now put to keys in box, issue a lock command which will generate the combination code, convert it
#         to an image and save the image somewhere

    return "4.0"

class getkeys():

    asciiCodes =   {82: "0", 79: "1", 80: "2", 81: "3", 75: "4", 76: "5", 77: "6", 71: "7", 72: "8", 73: "9",
                    98: "/", 55: "*", 74: "-", 78: "+", 14: "D", 96: "E", 83: "."}
    cursorCodes =  {79:  "End", 80:  "Down", 81:  "PgDn", 75:  "Left", 77:  "Right", 71:  "Home", 72:  "Up", 73:  "PgUp", 96:  "Enter"}

# Note ASCII codes use "D" for del and "E" for Enter

    def __init__(self,devName):
        self.data = []
        devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
        self.device = devices[0]
        for devicest in devices:
#            if devicest.name == "HID 13ba:0001":
            if devicest.name == devName:
                 self.device=devicest
        self.device.grab()
        
    def getkey(self, keyType):
        event = None
        while True:
            event = self.device.read_one()
            if event <> None:
                if event.type == ecodes.EV_KEY and event.value == 0 and event.code<>69:
                    if keyType == "ascii":
                        return self.asciiCodes.get(event.code)
                    else:
                        return self.cursorCodes.get(event.code)
                         
    def __del__(self):                   
        device.ungrab()



# 31-1-2017 Use a class to hold the emla results
class emlaResult:
  def __init__(self):
    self.status = ""
    self.statusText = ""
    self.startdate = ""
    self.sessionactive = False
    self.duration = 0
    self.maxduration = 0
    self.minduration = 0
    self.requirements = 0
    self.displaymode_timepassed = 0
    self.displaymode_timeleft = 0
    self.displaymode_showapproximate = 0
    self.displaymode_surpriseme = 0

  def load(self, result):
    data = json.loads(result)
    try:
        self.statusText = data['error']
        self.status="error"
        return False
    except KeyError:
        self.status="OK"
    try:
        temp = data['chastitysession']['chastitysessionid']
        self.sessionactive = True
        try:
            self.startdate = data['chastitysession']['startdate']
            self.duration = data['chastitysession']['duration']
            self.maxduration = data['chastitysession']['maxduration']
            self.minduration = data['chastitysession']['minduration']
            self.requirements = data['chastitysession']['requirements']
            self.displaymode_timepassed = data['chastitysession']['displaymode']['timepassed']
            self.displaymode_timeleft = data['chastitysession']['displaymode']['timeleft']
            self.displaymode_showapproximate = data['chastitysession']['displaymode']['showapproximate']
            self.displaymode_surpriseme = data['chastitysession']['displaymode']['surpriseme']
            return True
        except KeyError:
            self.status="Whoopse"
            return False
    except KeyError:
        self.sessionactive = False
        return True
        


  def gSessionActive():
    return self.sessionactive

  def gStartdate():
    return self.startdate

  def gDuration():
    return self.duration

  def gMaxduration():
    return self.maxduration

  def gMinduration():
    return self.minduration

  def gRequirements():
    return self.requirements

  def gDisplaymode():
    return self.displaymode

		
def getPIN(prompt, hide):
    global keypad
    lcd.clear()
    lcd.message(prompt)
    # turn on numlock
    pinComplete = False
    enteredpin = ""
    while not pinComplete:
        ch=keypad.getkey("ascii")
        print "character: %d Text: %s" % (ord(ch), ch)
        if ord(ch) >= 48 and ord(ch) <= 57:
            enteredpin = enteredpin + ch
        if ch == "E":
            pinComplete = True
        if ch == "D":
            enteredpin = enteredpin[0:len(enteredpin)-1]

        lcd.clear
        if hide:
            if len(enteredpin)==0:
                displaypin = ""
            else:
                displaypin = "**********"[0:len(enteredpin)-1]+enteredpin[len(enteredpin)-1]
            lcd.message(prompt + displaypin)
        else:
            lcd.message(prompt + enteredpin)
    return enteredpin


def setServoPulse(channel, pulse):
    pulseLength = 1000000                   # 1,000,000 us per second
    pulseLength /= 60                       # 60 Hz
    print "%d us per period" % pulseLength
    pulseLength /= 4096                     # 12 bits of resolution
    print "%d us per bit" % pulseLength
    pulse *= 1000
    pulse /= pulseLength
    pwm.setPWM(channel, 0, pulse)

def lock():
    global servoLock
    global config
    global iniFile
    pwm = PWM(0x40)
    pwm.setPWMFreq(60)
    pwm.setPWM(0, 0, servoLock)
    time.sleep(1)
    pwm.setPWM(0, 0, 0)
    print("Click")
    config['session']['status']='Locked'
    with open(iniFile, 'w') as configfile:
        config.write(configfile)

    return True;

def unlock():
    global servoUnlock
    global config
    global iniFile
    pwm = PWM(0x40)
    pwm.setPWMFreq(60)
    pwm.setPWM(0, 0, servoUnlock)
    time.sleep(1)
    pwm.setPWM(0, 0, 0)
    print("Clack")
    config['session']['status']='Unlocked'
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
    return True;

def generateCombination():
    global config
    global iniFile
    # Generate a random combination with number of digits in config
    digits = config['device']['keylength']
    value = int(random.random()*(10**int(digits)))
    # write the combination to the ini file
    config['session']['combination'] = str(value)
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
    # Generate an image containing the combination
    img_width = 200
    img_height = 60
    img = Image.new('RGB', (img_width, img_height), color = (73, 109, 137))
    d = ImageDraw.Draw(img)
    
    font = ImageFont.truetype(config['device']['fontLoc'], size=36)
    text_width, text_height = d.textsize(str(value),font=font)
    start_width = (img_width - text_width) / 2
    start_height = (img_height - text_height) / 2
    d.text((start_width,start_height), str(value), fill=(255,255,0), font=font)
    img.save(config['device']['picLoc']+'/emlaCombination.png')
    return True

       
def currentTime():
    t = requests.get("http://www.timeapi.org/utc/now")
    return t.text

#########################################################
# Routines that handle communication with EMLALOCK.COM
#########################################################

######################################################################
# 31-1-2017 generic http call routine with timeout error handling
######################################################################
def httpCall(url, source):
    print url
    retry = True
    count = 0
    while retry:
        timeout = eventlet.Timeout(10)
        try:
            r = requests.get(url)
            retry = False
        except Timeout as t:
            count += 1
            lcd.message('%s:Timeout\nRetrying %d' % (source, count))
        finally:
            timeout.cancel()
            lcd.clear()
    r.text
    print(r.text)
    return r.text

def info():
    global userid
    global apikey
    global emlareturn
	#    r = requests.get("https://www.emlalock.com?client=api&userid="+str(userid)+"&apikey="+str(apikey)+"&job=info")
	# 31-1-2017 Use http routine that checks for timeout situations
    if emlareturn.load(httpCall("https://www.emlalock.com/api/info/?userid="+str(userid)+"&apikey="+str(apikey),"info")):
        return True
    else:
        return False



###############################################################
# Routines that handle the menus and options
###############################################################

def waitForStart():
    global message
    global commands
    global elError
    commands=['Lock','Settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Select to Start'
    lcd.set_color(0.0, 0.0, 1.0) # Blue
    option=waitForSelect()
    if option==0:
        if startSession():
            return waitForDelete(1)  #2 = waitForDelete
    if option==1:
        return 4      # 4 = Settings
    return 0
        
    
def startSession():
    global config
    if generateCombination():
        lock()
        return True
    else:
        return False

def waitForDelete(mode):
    global emlareturn
    global config
    print "WaitForDelete"
    if mode == 1:
        waitForDelete.cycleCount=0
    else:
        waitForDelete.cycleCount+=1
        
    # Wait thirty seconds, then check if lock session exists
    lcd.clear()
    lcd.message("Combo created\nWait to delete "+str(waitForDelete.cycleCount))
    time.sleep(30)
    if info():
        if emlareturn.sessionactive:
            # delete the image file
            os.remove(config['device']['picLoc']+'/emlaCombination.png')
            return 1 # SessionRunning
        else:
            return 2 # waitForDelete - Going around again!
    else:
        lcd.clear()
        lcd.message("Emla Error\n"+emlareturn.status)
        time.sleep(3)

       
    

    
def endSession():
    global config
    global message
    # Get combination from keypad
    combination=getPIN('Combination?\n',False)
    # Compare to stored combination
    if combination == config['session']['combination']:
        # Unlock box
        unlock()
        return True
    else:
        message[0]="Wrong code"
        return False

def sessionRunning():
    global message
    global commands
    global elError
    commands=['End Session','Cancel']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Session Running'
    lcd.set_color(0.0, 1.0, 0.0) # Green
    option=waitForSelect()
    if option==0:
        if endSession():
            return 0 # 0 = wait for start
    return 1

def settings():
    global message
    global commands
    global elError
    commands=['Set servo lock','Set servo unlock','Set new PIN','Change User','Combo length','Quit settings']
    if len(elError)>0:
        message[0]=elError
    else:
        message[0]='Settings'
#    lcd.clear()
    lcd.set_color(1.0, 1.0, 1.0) # White
    option=waitForSelect()
    if option==0:
        return 5 # 5 = Set servo lock position
    if option==1:
        return 6 # 6 = Set servo unlock position
    if option==2:
        return 7 # 7 = Set password
    if option==3:
        return 8 # 8 = Choose the emla user
    if option==4:
        return 9 # 9 = Set the combination length
    if option==5:
        return 0
    return 4


def waitForSelect():
    
    global message
    global commands
    global keypad
    currentOption=0
    message[1]=commands[currentOption]
    lcd.clear()
    lcd.message(message[0]+"\n"+commands[currentOption])
    keyPress = keypad.getkey("cursor")
    while keyPress <> "Enter":
        if keyPress == "Up":
            currentOption = (currentOption -1) % len(commands)
            print('currentOption=',currentOption)
        if keyPress == "Down":
            currentOption = (currentOption +1) % len(commands)
            print('currentOption=',currentOption)
        message[1]=commands[currentOption]
        lcd.clear()
        lcd.message(message[0]+"\n"+message[1])
        keyPress = keypad.getkey("cursor")
    return currentOption

def setServoLock():
    print("servo lock")
    global servoLock
    global config
    global iniFile
    global keypad
    pwm = PWM(0x40)
    pwm.setPWMFreq(60)

    keyPress = keypad.getkey("cursor")
    value=servoLock
    lcd.clear()
    lcd.message('Set Servo Lock\n(150:600) %d' % (value))
    while keyPress <> "Enter":
        if keyPress == "Left":
            if value>150:
                value-=10
        if keyPress == "Right":
            if value<590:
                value+=10
        lcd.clear()
        lcd.message('Set Servo Lock\n(150:600) %d' % (value))
        # Do something with the servo
        pwm.setPWM(0, 0, value)
        keyPress = keypad.getkey("cursor")

    # Store value
    servoLock=value
    config['servo']['lock']=str(servoLock)
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
    # Move lock to unlock position
    pwm.setPWM(0, 0, servoUnlock)
    print("Servo unlock value ",str(servoLock))
    return 4
  
def setServoUnlock():
    print("Servo unlock")
    global servoUnlock
    global config
    global iniFile
    global keypad
    pwm = PWM(0x40)
    pwm.setPWMFreq(60)

    keyPress = keypad.getkey("cursor")
    value=servoUnlock
    lcd.clear()
    lcd.message('Set Servo Unlock\n(150:600) %d' % (value))
    while keyPress <> "Enter":
        if keyPress == "Left":
            if value>0:
                value-=10
        if keyPress == "Right":
            if value<590:
                value+=10
        lcd.clear()
        lcd.message('Set Servo Unlock\n(150:600) %d' % (value))
        # Do something with the servo
        pwm.setPWM(0, 0, value)
        keyPress = keypad.getkey("cursor")

    # Store value
    servoUnlock=value
    config['servo']['unlock']=str(servoUnlock)
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
    # Move lock to unlock position
    pwm.setPWM(0, 0, servoUnlock)
    print("Servo unlock value ",str(servoUnlock))
    return 4

def setPassword():
    # turn off numlock
    newpin = getPIN('Enter new PIN\n',True)
    confirmpin = getPIN('Confirm\n',True)
    lcd.clear()
    if newpin == confirmpin:
        #update
        config['device']['PIN']=newpin
        with open(iniFile, 'w') as configfile:
            config.write(configfile)
        lcd.message('PIN Changed')
    else:
        lcd.message("PINs Don't match\nNo update")
    time.sleep(5)
    return 4

def chooseUser():
    global config
    global keypad
    global userName
    global userid
    global apikey
    
    userNames=[]
    userIds=[]
    userAPIs=[]
    for key in config['userList']:
        userNames.append(key)
        userInfo = config['userList'][key]
        userIds.append(userInfo.split('|')[0])
        userAPIs.append(userInfo.split('|')[1])
    print "users:%d" % len(userNames)
    value=0
    lcd.clear()
    lcd.message('Set Safe User\n'+userNames[value])
    keyPress = keypad.getkey("cursor")
    while keyPress <> "Enter":
        if keyPress == "Right":
            value=(value+1) % len(userNames)
        if keyPress == "Left":
            value=(value-1) % len(userNames)
        print "List value %d" % value
        lcd.clear()
        lcd.message('Set Safe User\n'+userNames[value])
        keyPress = keypad.getkey("cursor")
        
    # Update the global variable
    userName=userNames[value]
    userid=userIds[value]
    apikey=userAPIs[value]

    # Update the config file
    config['currentUser']['name']=userName
    config['currentUser']['userid']=userid
    config['currentUser']['apikey']=apikey
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
    # Update emlaReturn incase of different result for new user
    info()

    return 4

def setComboLength():
    global config
    global iniFile
    try:
        digits = int(config['device']['keylength'])
    except KeyError:
        print "Set combination length to default"
        digits = 4
        
    lcd.clear()
    lcd.message('Set key length\n'+str(digits))
    keyPress = keypad.getkey("cursor")
    while keyPress <> "Enter":
        if keyPress == "Up":
            digits=(digits+1) % 10
            if digits == 0:
                digits = 1
        if keyPress == "Down":
            digits=(digits-1) % 10
            if digits == 0:
                digits = 9
        print "Digit count %d" % digits
        lcd.clear()
        lcd.message('Set Safe User\n'+str(digits))
        keyPress = keypad.getkey("cursor")
    
    config['device']['keylength']=str(digits)
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
    return 4

# initialise global variables
userid=""
apikey=""
emlareturn = emlaResult()

message=['message','option']
elError=""
servoLock=600
servoUnlock=150
iniFile='emlaAutoSafe.ini'

lcd = LCD.Adafruit_CharLCDPlate()
lcd.clear()
lcd.message("Emla Auto Safe\nversion "+version())
time.sleep(3)
lcd.clear()
#lcd.message("Emla Auto Safe\nstart up")



commands=('a','b')

# get the command line args
x=1   #ignore zero entry as it will be the program file
while x<len(sys.argv):
    a=str(sys.argv[x])
    print('arg=',a)
    if a[:4]=='-ini':
        iniFile=a[5:]
    x=x+1
    
#get parameters from the config file
config = configparser.ConfigParser()
config.read(iniFile)
userName=config['currentUser']['name']
userid=config['currentUser']['userid']
apikey=config['currentUser']['apikey']
try:
    pin=config['device']['PIN']
except KeyError:
    print "Setting PIN to default"
    pin = '0000'
    config['device']['PIN']=pin
    with open(iniFile, 'w') as configfile:
        config.write(configfile)
try:
    lockStatus=config['session']['status']
except KeyError:
    print "Setting status to default."
    lockStatus='Unlocked'
    config['session']['status']=lockStatus
    with open(iniFile, 'w') as configfile:
        config.write(configfile)


servoLock=int(config['servo']['lock'])
servoUnlock=int(config['servo']['unlock'])
keypad = getkeys(config['device']['keybd'])

#    if a[:6]=='userid':
#        userid=a[7:]
#    if a[:6]=='apikey':
#        apikey=a[7:]
#    if a[:6]=='device':
#        device=a[7:]
#    if a[:8]=='servoLock':
#        servoLock=int(a[9:])
#    if a[:8]=='servoUnlock':
#        servoUnlock=int(a[9:])

#user to input pin
newpin=""
while newpin <> pin:
    newpin = getPIN('Hello '+userName+'\nEnter PIN ',True)
    
lcd.clear()
lcd.message('Checking session')

# Check is a session is already running
if config['session']['status']=='Locked':
    command=1
else:
    command=0

    
while True:
    if command==0:
        command=waitForStart()
    if command==1:
        command=sessionRunning()
    if command==2:
        command=waitForDelete(2)
    if command==4:
        command=settings()
    if command==5:
        command=setServoLock()
    if command==6:
        command=setServoUnlock()
    if command==7:
        command=setPassword()
    if command==8:
        command=chooseUser()
    if command==9:
        command=setComboLength()
It uses an .ini file to store pertinent information

Code: Select all

[device]
id = SAFE900000002
pin = 1236
keybd = USB USB Keyboard
keylength = 6
fontloc = /usr/share/fonts/truetype/freefont/FreeSans.ttf
picloc = /pathtopictures

[session]
combination = 230207
status = Unlocked

[servo]
unlock = 400
lock = 590

[currentUser]
name = user1
userid = [i]userid[/i]
apikey = [i]apikey[/i]

[userList]
user1 = [i]userid[/i]|[i]apikey[/i]
user2 = [i]userid[/i]|[i]apikey[/i]

Mostly self explanatory except:
keybd is the usb device that is read for input. The value can be found by executing the following python script

Code: Select all

>>> import evdev
>>> devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
>>> for device in devices:
>>>     print(device.fn, device.name, device.phys)
I set up the pi as a Samba server. The pathtopictures entry points to a directory that is shared via Samba, allowing you to extract the generated image.
A single call to the Emlalock API is used to find out if a session has been started, if so, the picture is deleted. The call is repeated at 30 second intervals.
Servo, keylength (how many digits in the combination number), pin can all be maintained via the application and keypad. It is recommended that the .ini file is created with all entries.
User avatar
Audrey_CD
***
Posts: 224
Joined: 04 Mar 2009, 22:46
Location: United Kingdom

Re: EmlaLockBox - The Code

Post by Audrey_CD »

Last iteration of EmlaLockBox code using and RFID/NFC connection to a mobile phone.

Code: Select all

#include <EEPROM.h>

#define DEBUG

#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <Ndef.h>


PN532_I2C pn532_i2c(Wire);
PN532 nfc = PN532(pn532_i2c);

//Define pins for lock solenoid and buzzer
#define UNLOCKPIN 6
#define WARNPIN 7

//Connections to RFID board
//  SCL   A5
//  SDA   A4
//  VCC   3.3
//  GND   GND

// Define EEPROM locations for combination and status variables
const int statusAddress = 0;
const int combinationAddress = 1;
const int combinationLength = 4;
const int AudiableWarningAddress = 5;

enum beepType {
  cardFound,
  commandAccepted,
  error
};

void setup(void) {
#ifdef DEBUG
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("NDEF Reader");
#endif
  pinMode(UNLOCKPIN, OUTPUT);
  pinMode(WARNPIN, OUTPUT);
  nfc.begin();

  // Check locked status
  String combination = readCombination();
  String status = getStatus();
#ifdef DEBUG
  Serial.println("Status:" + getStatus());
  Serial.println("Combination:" + combination);
  Serial.print("Alert type:");
  if (readAudiableWarning())
    Serial.println("buzz");
  else
    Serial.println("none");
#endif
  if (status == "unknown") {
    // no valid status - do the combo reset
    resetCombination();
  }

#ifdef DEBUG
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // halt
  }
  // Got ok data, print it out!
  Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);
#endif
  // configure board to read RFID tags
  nfc.SAMConfig();
#ifdef DEBUG
  Serial.println("Waiting for an ISO14443A Card ...");
#endif
}

void fireSolenoid() {
#ifdef DEBUG
  Serial.println("Fire solenoid");
#endif
  digitalWrite(UNLOCKPIN, HIGH);
  delay(3000);
  digitalWrite(UNLOCKPIN, LOW);
}

void warningAlert(beepType buzz) {
  // Get audio warning status

  if (!readAudiableWarning())
    return;
  switch (buzz) {
    case cardFound:
    // Beep
      digitalWrite(WARNPIN, HIGH);
      delay(50);
      digitalWrite(WARNPIN, LOW);
    break;
    case commandAccepted:
    // Beep   Beep
      digitalWrite(WARNPIN, HIGH);
      delay(50);
      digitalWrite(WARNPIN, LOW);
      delay(50);
      digitalWrite(WARNPIN, HIGH);
      delay(50);
      digitalWrite(WARNPIN, LOW);
    break;
    case error:
    // Beeeeeeeeeep
      digitalWrite(WARNPIN, HIGH);
      delay(1000);
      digitalWrite(WARNPIN, LOW);
    break;
  }
  return;
}
String readCombination() {
  char combo[combinationLength];
#ifdef DEBUG
  Serial.println("Read combination");
#endif
  EEPROM.get(combinationAddress, combo);

  return String(combo);
}
void writeCombination(String sCombo) {
  char combo[combinationLength + 1];
  sCombo.toCharArray(combo, combinationLength + 1);
#ifdef DEBUG
  Serial.println("Write combination");
#endif
  EEPROM.put(combinationAddress, combo);
  setStatus("Locked");
}

void resetCombination() {
#ifdef DEBUG
  Serial.println("Reset combination");
#endif
  // Resets the combination to all xFF chars
  // and sets the status to unlocked
  char combo[combinationLength] = "0000";
  setStatus("Unlocked");
  EEPROM.put(combinationAddress, combo);
}

void setStatus(String newStatus) {
  char status = '?';
  if (newStatus.equals("Locked"))
    status = 'L';
  if (newStatus.equals("Unlocked"))
    status = 'U';
  EEPROM.write(statusAddress, status);
}

String getStatus() {
  char status = (char) EEPROM.read(statusAddress);
  String sStatus = "";

  switch (status) {
    case 'U':
      sStatus = "Unlocked";
      break;
    case 'L':
      sStatus = "Locked";
      break;
    default:
      sStatus = "unknown";
  }
  return sStatus;
}

boolean readAudiableWarning() {
  int pinBit=1;
#ifdef DEBUG
  Serial.println("read Audiable Warning");
#endif
  boolean AudiableWarning = bitRead(EEPROM.read(AudiableWarningAddress), pinBit);
  return AudiableWarning;
}

void writeAudiableWarning(boolean AudiableWarning) {
  int pinBit=1;
#ifdef DEBUG
  Serial.println("Write Audiable Warning");
#endif
  byte EEPROMbyte = EEPROM.read(AudiableWarningAddress);
  bitWrite(EEPROMbyte, pinBit, AudiableWarning);
  EEPROM.update(AudiableWarningAddress, EEPROMbyte);
}


void printResponse (uint8_t *response, uint8_t responseLength) {
  //    Serial.print("RAW: ");
  //    for (int i = 0; i < responseLength; ) {
  //        Serial.print(response[i++]);
  //        Serial.print(" ");
  //    }
  //    Serial.println(" ");
  //
  //    for (int i = 0; i < responseLength; i++) {
  //        Serial.print((char)response[i]);
  //        Serial.print(" ");
  //    }
  nfc.PrintHexChar(response,  responseLength);

}

void loop(void) {
  bool success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;                        // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
  uint8_t responseLength = 32;

#ifdef DEBUG
  Serial.println("\nScan a NFC tag\n");
#endif

  success = nfc.inListPassiveTarget();

  if (success) {   // A card is present. Send the status of the lock
#ifdef DEBUG
    Serial.print("\nCard found.");
#endif

    warningAlert(cardFound);
    uint8_t response[32];

    if (getStatus() == "Locked") {
#ifdef DEBUG
      Serial.print("\nSend 'box Locked' ");
#endif
      uint8_t selectApdu[] = {
        0x00, /* CLA */
        0xA4, /* INS */
        0x04, /* P1  */
        0x00, /* P2  */
        0x08, /* Length of AID  */
        0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x01,
        0x00  /* Le  */
      };
      success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), response, &responseLength);
    }
    else {
#ifdef DEBUG
      Serial.print("\nSend 'box Unlocked' ");
#endif
      uint8_t selectApdu[] = {
        0x00, /* CLA */
        0xA4, /* INS */
        0x04, /* P1  */
        0x00, /* P2  */
        0x08, /* Length of AID  */
        0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x02,
        0x00  /* Le  */
      };
      success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), response, &responseLength);

    }

    if (success) {
//      printResponse (*response, responseLength);
#ifdef DEBUG
        Serial.print("responseLength: "); Serial.println(responseLength);
        nfc.PrintHexChar (response, responseLength);
        Serial.print("\nSend 'Select Ndef' ");
#endif
      uint8_t selectNdef[] = {
        0x00, /* CLA */
        0xA4, /* INS */
        0x00, /* P1  */
        0x0C, /* P2  */
        0x02, /* Length of AID  */
        0xE1, 0x04,
        0x00  /* Le  */
      };
      responseLength=32;
      uint8_t responseNDEF[32];
      bool successNdef = nfc.inDataExchange(selectNdef, sizeof(selectNdef), responseNDEF, &responseLength);
      printResponse (responseNDEF, responseLength);
#ifdef DEBUG
      Serial.println("\nSend 'Select Read' ");
#endif
      uint8_t selectRead[] = {
        0x00, 0xB0,
        0x00, 0x09, /* Offset */
        0x0D,       /* Length */
        0x00 /* LE */
      };
      uint8_t payloadLength=32;
      uint8_t payload[32];
      bool successRead = nfc.inDataExchange(selectRead, sizeof(selectRead), payload, &payloadLength);
#ifdef DEBUG
      Serial.print("Payload received ");
      printResponse (payload, payloadLength);
#endif



      // Next action is contained within the response
      // TODO Lots of stuff beyond this point to handle response
      // Response values:
      //  0 - No further action. If box status is unlocked, fire the solenoid
      //  1 - New Code. If box status unlocked accept the new code and change the status to locked If locked: Rude buzz
      //  2 - Release code. If box status is locked, and code matches stored release code, change box status to unlocked and fire the solenoid.
      //      If code is wrong, rude buzz.
      //  3 - Set the status of the audiable warning

#ifdef DEBUG
      // Print the Hex and Printable Characters
      Serial.print("  Payload (HEX): ");
      printResponse(payload, payloadLength);
#endif
      // Force the data into a String (might work depending on the content)
      // Real code should use smarter processing
      String payloadAsString = "";
      for (int c = 0; c < payloadLength; c++) {
        payloadAsString += (char)payload[c];
      }
#ifdef DEBUG
      Serial.print("  Payload (as String): ");
      Serial.println(payloadAsString);
#endif
      int separator = payloadAsString.indexOf('=', 1);
      String command = payloadAsString.substring(0, separator);
      String combination = payloadAsString.substring(separator + 1,separator+5);
 #ifdef DEBUG
      Serial.print("Command "+command);
      Serial.println(" Code "+combination);
 #endif
      boolean successful = true;
      if (command.equals("lock")) {
        // TODO check current lock status
        if (getStatus().equals("Unlocked")) {
          // Store the combination in permanant memory
#ifdef DEBUG
          Serial.println(command + ": 'lock'=" + combination);
#endif
          writeCombination(combination);
          setStatus("Locked");
          
        }
        else {
#ifdef DEBUG
          Serial.println("Can't change combination on locked box");
#endif
          warningAlert(error);
          successful = false;
        }
      }
      if (command.equals("unlock")) {
#ifdef DEBUG
        Serial.println(command + ": 'unlock'=" + combination);
#endif
        // compare combination to value in permanant memory
        // if match or permanant value is empty open the lock
        if (getStatus().equals("Locked")) {
          String storedCombination = readCombination().substring(0, 4);
#ifdef DEBUG
          Serial.print("stored combo length:");
          Serial.println(storedCombination.length());
          Serial.print("enteredcombo length:");
          Serial.println(combination.length());
#endif
          if (combination.equals(storedCombination)) {
            // Combination matches - Set the output pin for the lock to hign for 3 seconds
            fireSolenoid();
            setStatus("Unlocked");
          }
          else {
            // Combination doesn't match - Set the output pin for the warning LED to hign for 3 seconds
#ifdef DEBUG
            Serial.println("Combinations didn't match");
#endif
            warningAlert(error);
            successful = false;
          }
        }
        else {
          // Already unlocked - Set the output pin for the lock to hign for 3 seconds
#ifdef DEBUG
          Serial.println("Fire solenoid");
#endif
          digitalWrite(UNLOCKPIN, HIGH);
          delay(3000);
          digitalWrite(UNLOCKPIN, LOW);
        }
      }
      if (command == "warning") {
        boolean audiableWarning = (combination=="true");
        writeAudiableWarning(audiableWarning);
        
      }
/*    Logic to send a success of fail message back to the phone
      currently forces a success message
      if (successful) {
      }*/
      uint8_t confirmCommand[] = {
        0x90, 0x00,
        0x00 /* LE */
      };
      uint8_t confirmLength=32;
      uint8_t confirmResult[32];
      successRead = nfc.inDataExchange(confirmCommand, sizeof(confirmCommand), confirmResult, &confirmLength);
      warningAlert(commandAccepted);



    }
  }

  delay(1000);
}
User avatar
sonik
**
Posts: 91
Joined: 07 Nov 2018, 00:13

Re: EmlaLockBox - The Code

Post by sonik »

I like it a lot but I don't know much about this topic. Can you explain how I can execute that code?
User avatar
Shannon SteelSlave
Moderator
Posts: 6530
Joined: 03 Feb 2019, 19:49
Location: New England, USA

Re: EmlaLockBox - The Code

Post by Shannon SteelSlave »

sonik wrote:I like it a lot but I don't know much about this topic. Can you explain how I can execute that code?
What equipment and operating system are you using, Sonik? Can anyone help my dear friend Sonik with this?
Bondage is like a foreign film without subtitles. Only through sharing and practice can we hope to understand.
A Jedi uses bondage for knowledge and defense, never for attack.
I am so smart! I am so smart! S-M-R-T!....I, I mean S-M-A-R-T!
👠👠
User avatar
lobster
*
Posts: 34
Joined: 20 Sep 2014, 10:20

Re: EmlaLockBox - The Code

Post by lobster »

Shannon SteelSlave wrote:
sonik wrote:I like it a lot but I don't know much about this topic. Can you explain how I can execute that code?
What equipment and operating system are you using, Sonik? Can anyone help my dear friend Sonik with this?
Unfortunately there is not an easy answer here. Browsing the thread, its bits and pieces of a much larger puzzle.

The idea is actually quite clever. OP figured out a way to fill in a void in the Emla APIs since the original API for connected external locks (the Device API) is no longer available. The lack of this API makes these lock boxes much harder to connect.

The last code drop is part a setup where an app (probably Android) generates an image to uploads to Emla. Once this image is released, the App verifies this and, using NFC, communicates with the hardware inside the LockBox - essentially bridging the gap between Emla's love for images and hardware devices

But there is lots missing to complete the full setup; for instance the source code of the app is not included. And you'll have to build the hardware yourself to put inside the box. Namely, and Android controller combined with an NFC (PN532) shield and wire-up the solenoid lock driver components as to not blow out you controller.

Consider this thread more an idea and not something that is ready to compile and go.
User avatar
sonik
**
Posts: 91
Joined: 07 Nov 2018, 00:13

Re: EmlaLockBox - The Code

Post by sonik »

It's very complicated, I don't hear anything :lol: :lol: :lol: :lol:
User avatar
Audrey_CD
***
Posts: 224
Joined: 04 Mar 2009, 22:46
Location: United Kingdom

Re: EmlaLockBox - The Code

Post by Audrey_CD »

My apologies, I've not been around for a couple of weeks.

The EmlaLockBox software runs on an Arduino. It will run from the moment the Arduino is powered up.

Bringing an RFID device in range of the RFID sensor will start a conversation.
Lock box says "I am lock box and I am Locked" - 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x01, Or I am lock box and I am unlocked" - 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x02
Phone will reply with a command:
// 0 - No further action. If box status is unlocked, fire the solenoid
// 1 - New Code. If box status unlocked accept the new code and change the status to locked If locked: Rude buzz
// 2 - Release code. If box status is locked, and code matches stored release code, change box status to unlocked and fire the solenoid.
// If code is wrong, rude buzz.
// 3 - Set the status of the audiable warning
Lock Box will confirm the instruction.

I would publish the Android code, but I've no idea of the best way to share it as it isn't a simple program like the Android code, and I don't have suitable web server or service. But also it isn't pretty, based on example code I didn't thoroughly understand.

Note. Details of the box are posted in DIY viewtopic.php?f=12&t=10042#p87488, but I've just seen that Photo bucket has made the images less than useful. If I can find the images again I'll put them into Flikr.
User avatar
sweh
***
Posts: 235
Joined: 10 Aug 2017, 01:14
Contact:

Re: EmlaLockBox - The Code

Post by sweh »

Audrey_CD wrote:I would publish the Android code, but I've no idea of the best way to share it as it isn't a simple program
Stick it on github :-)
Post Reply