#!/usr/bin/python
#################################################
#
# PiConReg
#
# Raspberry Pi QR code reader to work with the
# ConQR system from TrustedSec.
# https://github.com/trustedsec/conqr
#
# Version
# -----------------------------------------------
# 1.0	jdg	Initial Version (2016-09)
#
# Authors
# -----------------------------------------------
# jdg	Jim Gilsinn
#
#################################################

import sys, os, time, subprocess, numbers, logging
import RPi.GPIO as GPIO
import pygame, qrtools
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
import requests
from HTMLParser import HTMLParser

#################################################

#------------------------------------------------
# Usage Message
#------------------------------------------------
usage = """
Usage:	qr-reg.py [demo_file]

Process QR codes generated by the ConQR
registration system.

demo_file    (optional) Process and display
             the desired demonstration file
"""

#------------------------------------------------
# Assign Values for LEDs
#------------------------------------------------
led_status_blue_gpio = 20
led_status_green_gpio = 21
led_code_red_gpio = 16
led_code_green_gpio = 12
led_on = GPIO.LOW
led_off = GPIO.HIGH

#------------------------------------------------
# Assign Values for QR Decoder
#------------------------------------------------
window_title = "PiConReg"
image_path = "/var/lib/motion"
window_size = (640,480)
full_window_size = (0,0,640,480)
image_size = (640,360)
image_offset = (0,120)
font_color = (255,255,0)
background_color = (0,0,0)
font_type = "monospace"
font_size = 15
qr_text_offset = (25,25)
server_text_offset = (25,75)
new_image_file = False
img_file = ""
registered_message = "Attendee Registered! Thank You!"
registered_color = (0, 128, 0)
duplicate_message = "ERROR - Code Previously Registered!"
duplicate_color = (128, 0, 0)
unknown_message = "ERROR - Code Not Recognized"
unknown_color = (128, 0, 0)

#------------------------------------------------
# Assign Values for Different Timers
#------------------------------------------------
cycle_sleep_time = 0.1
blink_time = 0.5
status_display_time = 1.0

#################################################

#------------------------------------------------
# Function - Clear Status LED
#------------------------------------------------
def LEDStatusOff():
  GPIO.output(led_status_blue_gpio, led_off)
  GPIO.output(led_status_green_gpio, led_off)

#------------------------------------------------
# Function - Clear Code LED
#------------------------------------------------
def LEDCodeOff():
  GPIO.output(led_code_red_gpio, led_off)
  GPIO.output(led_code_green_gpio, led_off)

#------------------------------------------------
# Function - Clear All LEDs
#------------------------------------------------
def LEDAllOff():
  LEDStatusOff()
  LEDCodeOff()

#------------------------------------------------
# Function - Flash Status LED Blue
#------------------------------------------------
def LEDStatusFlashBlue(flash_time):
  GPIO.output(led_status_green_gpio, led_off)
  GPIO.output(led_status_blue_gpio, led_on)
  time.sleep(flash_time)
  GPIO.output(led_status_blue_gpio, led_off)
  time.sleep(flash_time)

#------------------------------------------------
# Function - Solid Status LED Blue
#------------------------------------------------
def LEDStatusSolidBlue():
  GPIO.output(led_status_green_gpio, led_off)
  GPIO.output(led_status_blue_gpio, led_on)

#------------------------------------------------
# Function - Solid Status LED Green
#------------------------------------------------
def LEDStatusSolidGreen():
  GPIO.output(led_status_blue_gpio, led_off)
  GPIO.output(led_status_green_gpio, led_on)

#------------------------------------------------
# Function - Flash Code LED Red
#------------------------------------------------
def LEDCodeFlashRed(flash_time):
  GPIO.output(led_code_green_gpio, led_off)
  GPIO.output(led_code_red_gpio, led_on)
  time.sleep(flash_time)
  GPIO.output(led_code_red_gpio, led_off)
  time.sleep(flash_time)

#------------------------------------------------
# Function - Solid Code LED Red
#------------------------------------------------
def LEDCodeSolidRed():
  GPIO.output(led_code_green_gpio, led_off)
  GPIO.output(led_code_red_gpio, led_on)

#------------------------------------------------
# Function - Solid Code LED Yellow
#------------------------------------------------
def LEDCodeSolidYellow():
  GPIO.output(led_code_green_gpio, led_on)
  GPIO.output(led_code_red_gpio, led_on)

#------------------------------------------------
# Function - Solid Code LED Green
#------------------------------------------------
def LEDCodeSolidGreen():
  GPIO.output(led_code_red_gpio, led_off)
  GPIO.output(led_code_green_gpio, led_on)

#------------------------------------------------
# Function - Initialize GPIO
#------------------------------------------------
def InitializeGPIO():
  GPIO.setmode(GPIO.BCM)
  GPIO.setup(led_status_blue_gpio, GPIO.OUT)
  GPIO.setup(led_status_green_gpio, GPIO.OUT)
  GPIO.setup(led_code_red_gpio, GPIO.OUT)
  GPIO.setup(led_code_green_gpio, GPIO.OUT)
  LEDAllOff()

#------------------------------------------------
# Function - Check That Motion Is Started
#------------------------------------------------
def CheckMotionStarted():
  motion_started = False
  while not motion_started:
    LEDStatusFlashBlue(blink_time)
    proc = subprocess.Popen(["pgrep", "motion"], stdout=subprocess.PIPE)
    proc_out = proc.stdout.readline().strip()
    if proc_out != "":
      motion_started = True
      print "Camera Motion Detection Started."
    else:
      print "Waiting for Camera Motion Detection to Start..."

#################################################

#------------------------------------------------
# Class - New Image Event Handler
#------------------------------------------------
class NewFileEventHandler(PatternMatchingEventHandler):
  patterns = ["*.jpg", "*.jpeg"]

  def process(self, event):
    """
    event.event_type 
      'modified' | 'created' | 'moved' | 'deleted'
    event.is_directory
       True | False
    event.src_path
       path/to/observed/file
    """
    global new_image_file
    new_image_file = True
    global img_file
    img_file = event.src_path

  def on_created(self, event):
    self.process(event)

#------------------------------------------------
# Class - Parse HTML Response From ConQR Server
#------------------------------------------------
class ConQRParser(HTMLParser):
  def __init__(self):
    HTMLParser.__init__(self)
    self.data = []
    self.recording = 0
    self.meta_description = False
    self.content_attr = 'junk'
    self.meta_code = False
    self.content_code = ''
    self.meta_email = False
    self.content_email = ''
    self.meta_type = False
    self.content_type = ''
	self.meta_title = False
	self.content_title = ''
	self.meta_number = False
	self.content_number = 0
	self.meta_day = False
	self.content_day = ''
	self.meta_color = False
	self.content_color = ''	

  def handle_starttag(self, tag, attributes):
    if tag != 'meta':
      return
    if self.recording:
      self.recording += 1
      return
    for name, val in attributes:
      # Look for baseline meta tag
      if name == 'name' and val == 'description':
        self.meta_description = True
      if name == 'content' and self.meta_description is True:
        self.content_attr = val
        self.meta_description = False
      # Look for registered qr code, if it exists
      if name == 'name' and val == 'code':
        self.meta_code = True
      if name == 'content' and self.meta_code is True:
        self.content_code = val
        self.meta_code = False
      # Look for registered email, if it exists
      if name == 'name' and val == 'email':
        self.meta_email = True
      if name == 'content' and self.meta_email is True:
        self.content_email = val
        self.meta_email = False
      # Look for registered ticket type, if it exists
      if name == 'name' and val == 'type':
        self.meta_type = True
      if name == 'content' and self.meta_type is True:
        self.content_type = val
        self.meta_type = False
	  # Look for registered ticket type title, if it exists
	  if name == 'name' and val == 'title':
	    self.meta_title = True
	  if name == 'content' and self.meta_title is True:
	    self.content_title = val
		self.meta_title = False
	  # Look for registered ticket type number, if it exists
	  if name == 'name' and val == 'number':
	    self.meta_number = True
	  if name == 'content' and self.meta_number is True:
	    self.content_number = int(val)
		self.meta_number = False
	  # Look for registered ticket type day, if it exists
	  if name == 'name' and val == 'day':
	    self.meta_day = True
	  if name == 'content' and self.meta_day is True:
	    self.content_day = val
		self.meta_day = False
	  # Look for registered ticket type color, if it exists
	  if name == 'name' and val == 'color':
	    self.meta_color = True
	  if name == 'content' and self.meta_color is True:
	    self.content_color = val
		self.meta_color = False
    else:
      return
    self.recording = 1

  def handle_endtag(self, tag):
    if tag == 'meta' and self.recording:
      self.recording -= 1

  def handle_data(self, data):
    if self.recording:
      self.data.append(data)

  def getContentAttr(self):
    return self.content_attr

  def getContentCode(self):
    return self.content_code

  def getContentEmail(self):
    return self.content_email

  def getContentTicket(self):
    return self.content_type

  def getContentTitle(self):
    return self.content_title

  def getContentNumber(self):
    return self.content_number

  def getContentDay(self):
    return self.content_day

  def getContentColor(self):
    return self.content_color  

#################################################

#------------------------------------------------
# Check For a Demo Test File
#------------------------------------------------
demo_file = ""
if len(sys.argv) == 1:
  print "Live Mode"
elif len(sys.argv) == 2:
  if not os.path.exists(sys.argv[1]):
    print "ERROR: Demo file not accessible"
    exit()
  else:
    demo_file = sys.argv[1]
    print "Demo File = " + demo_file
else:
  print usage
  exit()

#------------------------------------------------
# Main Program Loop
#------------------------------------------------
try:
  InitializeGPIO()
  CheckMotionStarted()
  LEDStatusSolidBlue()

  #------------------------------------------------
  # Initialize Output Window
  #------------------------------------------------
  pygame.init()
  window = pygame.display.set_mode(window_size)
  pygame.display.set_caption(window_title)
  last_qr_code = "No Code!"

  while True:
    #------------------------------------------------
    # Clear Things
    #------------------------------------------------
    LEDCodeOff()
    pygame.draw.rect(window, background_color, full_window_size)

    #------------------------------------------------
    # Parse QR Code
    #------------------------------------------------
    qr = qrtools.QR()
    qr.decode(img_file)
    response_text = ""
    if qr.decode():
      qr_code = qr.data_to_string()
      LEDStatusSolidGreen()

      if qr_code != last_qr_code:
        last_qr_code = qr_code
        server_response = requests.get(qr_code)
        parser = ConQRParser()
        parser.feed(server_response.text)
        conqr_response = parser.getContentAttr()
        if conqr_response == 'registered':
          conrq_code = parser.getContentCode()
          conrq_email = parser.getContentEmail()
          conrq_ticket = parser.getContentTicket()
          LEDCodeSolidGreen()
          response_text = registered_message
          pygame.draw.rect(window, registered_color, full_window_size)
        elif conqr_response == 'duplicate':
          LEDCodeSolidRed()
          response_text = duplicate_message
          pygame.draw.rect(window, duplicate_color, full_window_size)
        else:
          LEDCodeSolidYellow()
          response_text = unknown_message
          pygame.draw.rect(window, unknown_color, full_window_size)

    else:
      qr_code = "No Code!"
      pygame.draw.rect(window, background_color, full_window_size)
      LEDStatusSolidBlue()

    #------------------------------------------------
    # Print QR Code and Server Response in Window
    #------------------------------------------------
    myfont = pygame.font.SysFont(font_type, font_size)
    label = myfont.render(qr_code, 1, font_color)
    window.blit(label, qr_text_offset)

    label = myfont.render(response_text, 1, font_color)
    window.blit(label, server_text_offset)

    #------------------------------------------------
    # Add Image to Window
    #------------------------------------------------
    if demo_file != "":
      img_file = demo_file
    else:
      observer = Observer()
      observer.schedule(NewFileEventHandler(), path=image_path)
      observer.start()
      while not new_image_file:
        time.sleep(cycle_sleep_time)
      observer.stop()
      observer.join()
      new_image_file = False

    img = pygame.image.load(img_file)
    window.blit(img, image_offset)

    #------------------------------------------------
    # Update Window
    #------------------------------------------------
    pygame.display.flip()

    #------------------------------------------------
    # Display Status for Some Time
    #------------------------------------------------
    time.sleep(status_display_time)


#------------------------------------------------
# Cleanup and Exit
#------------------------------------------------
finally:
  pygame.quit()
  GPIO.cleanup()
