#!/usr/bin/env python

import time
import datetime
import RPi.GPIO as GPIO
import math
import MySQLdb
from array import *
import numpy as np
import json

class ADC:
        
    # read SPI (serial peripheral interface bus) data from MCP3008 chip
    # 8 possible adc's (analog digital converter) (0 thru 7)

    def __init__(self, clockpin, mosipin, misopin, cspin):
        self.clockpin = clockpin
        self.mosipin = mosipin
        self.misopin = misopin
        self.cspin = cspin

    def readadc(self, adcnum):
        if ((adcnum > 7) or (adcnum < 0)):
            return -1
        GPIO.output(self.cspin, True)

        GPIO.output(self.clockpin, False)  # start clock low
        GPIO.output(self.cspin, False)     # bring CS low

        commandout = adcnum
        commandout |= 0x18  # start bit + single-ended bit
        commandout <<= 3    # we only need to send 5 bits here
        for i in range(5):
            if (commandout & 0x80):
                GPIO.output(self.mosipin, True)
            else:
                GPIO.output(self.mosipin, False)
            commandout <<= 1
            GPIO.output(self.clockpin, True)
            GPIO.output(self.clockpin, False)

        adcout = 0
        # read in one empty bit, one null bit and 10 ADC bits
        for i in range(12):
            GPIO.output(self.clockpin, True)
            GPIO.output(self.clockpin, False)
            adcout <<= 1
            if (GPIO.input(self.misopin)):
                adcout |= 0x1

        GPIO.output(self.cspin, True)

        adcout >>= 1       # first bit is 'null' so drop it
        return adcout

class Probe:
#
#   Rt             Rf
#   Vc ---^^^^^^---Vo---^^^^^^---GND
#
    Vc = 3.3
    Rf = 1000
    bits = 1024

    def __init__(self, num, a, b, c):
        self.num = num
        self.a = a
        self.b = b
        self.c = c

    def num(self):
        return (self.num)
                
    def rOhms(self, s):
        Vo = float(s) / float(Probe.bits) * Probe.Vc
        Rt = (Probe.Vc - Vo) / Vo * Probe.Rf
        return (Rt)
        
    def tempF(self, r):
        # Steinhart Hart Equation
        # T = 1/(a + b[ln(ohm)] + c[ln(ohm)]^3)
        lnr = math.log(r)
        tempK =  1/(self.a + self.b * lnr + self.c * math.pow(lnr,3))
        tempC = tempK - 273.15
        tempF = tempC * 9 / 5 + 32
        return tempF

class Motor:
#
#   L293D Dual Half-H Motor Driver
#
#   Pin  Code  Description
#   ---- ----- ------------------
#     1  1,2EN Drivers 1,2 Enable
#     2  1A    Input 1
#     3  1Y    Output 1
#     4  GND
#     5  GND
#     6  2Y    Output 2
#     7  2A    Input 1
#     8  Vcc2  Motors Power
#     9  3,4EN Drivers 3,4 Enable
#    10  3A    Input 3
#    11  3Y    Output 3
#    12  GND
#    13  GND
#    14  4Y    Output 4
#    15  4A    Input 4
#    16 Vcc1   Logic Power

    def __init__(self, SPI1A, SPI2A, SPIPWM, freq):
        self.SPI1A = SPI1A
        self.SPI2A = SPI2A
        self.SPIPWM = SPIPWM
        GPIO.setup(self.SPI1A, GPIO.OUT)
        GPIO.setup(self.SPI2A, GPIO.OUT)
        GPIO.setup(self.SPIPWM, GPIO.OUT)
        self.pwm = GPIO.PWM(SPIPWM, freq)
        self.pwm.start(0)

    def clockwise(self):
        GPIO.output(self.SPI1A, True)
        GPIO.output(self.SPI2A, False)

    def counterclockwise(self):
        GPIO.output(self.SPI1A, False)
        GPIO.output(self.SPI2A, True)

    def start(self, speed):
        boundedSpeed = self.boundSpeed(speed)
        self.pwm.start(boundedSpeed)

    def stop(self):
        x = 1
        self.pwm.ChangeDutyCycle(0)

    def changeSpeed(self, speed):
        boundedSpeed = self.boundSpeed(speed)
        self.pwm.ChangeDutyCycle(boundedSpeed)

    def boundSpeed(self, speed):
        if speed < 5:
            return(0)
        if speed > 100:
            return(100)
        return (speed)
#
#..............................................................................
#
#  Tower Pro micro servo SG92
#      50 Hz pulse width modulation period
#      1 - 2 ms dutycycle
#      1 ms pulse   - full left
#      2 ms pulse   - full right
#      1.5 ms pulse - middle
#      Orange wire - PWM
#      Red wire - Vcc (4.8 V)
#      Brown wire - Ground
#
#.............................................................................
#
class Servo:
    def __init__(self, SPIPWM):
        self.pwm = GPIO.PWM(SPIPWM, 50) # set PWM = 50 Hz
        self.pwm.start(5) # set initial position to closed

    def changePosition(self, position):
#
#       ... position = 0, fully closed (5% duty cycle)
#       ... position = 100, fully opened (10% duty cycle)
#
        boundedPosition = self.boundPosition(position)
        dutyCycle = (boundedPosition / 20) + 5
        self.pwm.ChangeDutyCycle(dutyCycle)

    def open(self):
        self.changePosition(100)
#        self.pwm.ChangeDutyCycle(10) # 10% dutycycle = 2 ms

    def close(self):
        self.changePosition(0)
#       self.pwm.ChangeDutyCycle(5) # 5% dutycycle = 1 ms

    def boundPosition(self, position):
        if position < 0:
            return (0)
        if position > 100:
            return (100)
        return(position)
#
#..............................................................................
#
#    log status
#
#.............................................................................
#
def logStatus(burnId, burnState, probePosition, probeTemp, blowerSpeed, ventPosition):

    try:
        db=MySQLdb.connect(host="localhost", db="burnbot", passwd="68Chevelle!", user="burnbot")
    except:
        print ("connect exception")
        raise SystemExit(0)
    curs = db.cursor()

    probePosition += 1

#   get probe parameters from currentprobes table
    try:
        curs.execute("SELECT probeId, targetTemp, highTemp, lowTemp FROM currentprobes WHERE probePosition = %s", (probePosition))
    except:
        print("currentprobes SELECT exception on currentprobes")
        raise SystemExit(0)
    row = curs.fetchone()
    probeId = row[0]
    targetTemp = row[1]
    highTemp = row[2]
    lowTemp = row[3]
    statusDateTime = datetime.datetime.now()

#   add current probe status into statuses table    
    try:        
        curs.execute("INSERT INTO statuses (burnId, burnState, probeId, probePosition, \
        statusDateTime, probeTemp, targetTemp, highTemp, lowTemp, blowerSpeed, ventPosition) \
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", \
        (burnId, burnState, probeId, probePosition, statusDateTime, probeTemp, targetTemp, highTemp, lowTemp, blowerSpeed, ventPosition))
    except:
        print ("statuses INSERT error")
        raise SystemExit(0)

#   update blower speed, vent position in currentburn table
    if probePosition ==1:
        try:
            currentBurnId = 1
            curs.execute("UPDATE currentburn SET blowerSpeed=%s, ventPosition=%s WHERE currentBurnId=%s", (blowerSpeed, ventPosition, currentBurnId))
        except:
            print ("statuses INSERT error")
            raise SystemExit(0)

#   update probe temp in currentprobes table
    try:
        curs.execute("UPDATE currentprobes SET probeTemp=%s WHERE probePosition=%s", (probeTemp, probePosition))
    except:
        print ("currentprobes INSERT error")
        raise SystemExit(0)

    db.commit()
    db.close()
#
#..............................................................................
#
#    get burn state
#
#..............................................................................
#
def getBurnState():
    try:
        db = MySQLdb.connect(host="localhost", db="burnbot",
            passwd="68Chevelle!", user="burnbot")
    except:
        print("connect exception")
        raise SystemExit(0)
    curs = db.cursor()
    try:
        curs = db.cursor()
        currentBurnId = 1
        curs.execute("SELECT burnState FROM currentburn WHERE currentBurnId = %s", (currentBurnId))
    except:
        print("SELECT exception")
        raise SystemExit(0)
    row = curs.fetchone()
#    db.close()
    burnState = row[0]
    print ("burnState=" + burnState)
    return(burnState)

#
#..............................................................................
#
#    get vent position
#
#..............................................................................
#
def getVentPosition():
    try:
        db = MySQLdb.connect(host="localhost", db="burnbot",
            passwd="68Chevelle!", user="root")
    except:
        print("connect exception")
        raise SystemExit(0)
    curs = db.cursor()
    currentBurnId = 1
    try:
        curs.execute("SELECT ventPosition \
            FROM currentburn WHERE currentBurnId = %s", (currentBurnId))
    except:
        print("SELECT exception")
        raise SystemExit(0)
    row = curs.fetchone()
    ventPosition = int(row[0])
    db.close()
    return(ventPosition)

#
#..............................................................
#
#   main
#
#..............................................................
#
def main():

#
#   ... initialize constants
#
    SPICLK = 21
    SPIMISO = 23
    SPIMOSI = 24
    SPICS = 25
    SPI1A = 4
    SPI2A = 17
    SPIPWM = 18
    SPIVPWM = 19

#
#   ... setup GPIO
#
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    GPIO.setup(SPIMOSI, GPIO.OUT)
    GPIO.setup(SPIMISO, GPIO.IN)
    GPIO.setup(SPICLK, GPIO.OUT)
    GPIO.setup(SPICS, GPIO.OUT)
    GPIO.setup(SPIVPWM, GPIO.OUT)

#
#   ... connect to database
#
    try:
        db = MySQLdb.connect(host="localhost", db="burnbot",
            passwd="68Chevelle!", user="root")
    except:
        print("connect exception")
        raise SystemExit(0)
    curs = db.cursor()
#
#   get burn parameters from currentburn
#
    currentBurnId = 1
    try:
        curs.execute("SELECT burnId, numberProbes, \
            blowerP, blowerI, BlowerD, ventP, ventI, ventP \
            FROM currentburn WHERE currentBurnId = %s", (currentBurnId))
    except:
        print("SELECT exception")
        raise SystemExit(0)
    row = curs.fetchone()
    burnId = row[0]
    numberProbes = row[1]
    blowerP = row[2]
    blowerI = row[3]
    blowerD = row[4]
    ventP = row[5]
    ventI = row[6]
    ventD = row[7]
    print ("blowerP=" + str(blowerP))

#
#   get probe parameters from currentprobes
#
    probePosition = 1
    try:
        curs.execute("SELECT targetTemp, highTemp, lowTemp, a, b, c FROM currentprobes WHERE probePosition = %s", (probePosition))
    except:
        print("SELECT exception on currentprobes")
        raise SystemExit(0)
    row = curs.fetchone()
    targetTemp = row[0]
    highTemp = row[1]
    lowTemp = row[2]
    a = row[3]
    b = row[4]
    c = row[5]
    print ("a=" + str(a))
    print ("b=" + str(b))
    print ("c=" + str(c))
        
    adc = ADC(SPICLK, SPIMOSI, SPIMISO, SPICS)
    probes = []
    n = 0
    while n < numberProbes:
        probe = Probe(n, a, b, c)
        probes.append(probe)
        n += 1
    print ("number of probes=" + str(len(probes)))
    fan = Motor(SPI1A, SPI2A, SPIPWM, 1000)
    fan.clockwise()
    fan.start(0)
#    vent = GPIO.PWM(SPIVPWM, 50) # set vent PWM freq = 50
#    vent.start(5)
    vent = Servo(SPIVPWM)
    
#
#    ... close database connection
#
#    db.close()
#
#   ... start
#
    while True:
        try:
            burnState = getBurnState()
            if burnState != "Preheating" and burnState != "Cooking" and burnState != "Checking":
                fan.changeSpeed(0)
            else:
                for probe in probes:
                    sample = adc.readadc(probe.num)
                    if sample > 0:
                        resistance = probe.rOhms(sample)
                        tempF = int(round(probe.tempF(resistance)))
                    else:
                        tempF = 0
                    if probe.num == 0:
                        try:
                            probePosition = probe.num + 1
                            curs.execute("SELECT targetTemp FROM currentprobes WHERE probePosition = %s", (probePosition))
                        except:
                            print("currentprobes SELECT exception on currentprobes")
                        row = curs.fetchone()
                        targetTemp = row[0]
                        blowerSpeed = int((targetTemp - tempF)*blowerP)
                        blowerSpeed = fan.boundSpeed(blowerSpeed)
                        fan.changeSpeed(blowerSpeed)
                        ventPosition = getVentPosition()
#                        vp = (ventPosition / 20) + 5
#                        vent.ChangeDutyCycle(vp)
                        vent.changePosition(ventPosition)
                        print ('Probe=' + str(probePosition) \
                        + ' Temp=' + str(tempF) \
                        + ' Set Point=' + str(targetTemp) \
                        + ' Blower Speed=' + str(blowerSpeed) \
                        + ' Vent Position=' + str(ventPosition) \
                        )
                    else:
                        blowerSpeed = None
                        ventPosition = None
                        print ('Probe=' + str(probe.num + 1) + ' Temp=' + str(tempF))	
                    logStatus(burnId, burnState, probe.num, tempF, blowerSpeed, ventPosition)
            time.sleep(1.0)

        except KeyboardInterrupt:
            fan.stop()
            GPIO.cleanup()
            db.close()
            print ('')
            raise SystemExit(0)

if __name__ == "__main__":
    main()
