#!/usr/bin/python
###############################################################################################################                                                               
# This library is for using the GrovePi with Scratch
# http://www.dexterindustries.com/GrovePi/                                                                
# History
# ------------------------------------------------
# Author	Date           	Comments
# Karan		29 June 15     	Initial Authoring                                                        
# John		22 Feb 16   	Adding GrovePi Barometer
# Nicole	Nov 16			Added Folder support for take_picture 
# Nicole	Nov 16			Added eSpeak Support
# Nicole	18 Nov 16		Adding PivotPi support
# SimonW    22 Mar 18       Bug fix in error handling line 383
'''
## License

The MIT License (MIT)

GrovePi for the Raspberry Pi: an open source platform for connecting Grove Sensors to the Raspberry Pi.
Copyright (C) 2016  Dexter Industries

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''
'''       
# 
# Based on the BrickPi Scratch Library written by Jaikrishna
#
# The Python program acts as the Bridge between Scratch & GrovePi and must be running for the Scratch program to run.
##############################################################################################################
'''
import scratch,sys,threading,math
import grovepi
import time
import os # to handle folder paths

try:
    sys.path.insert(0, '/home/pi/Dexter/PivotPi/Software/Scratch/')
    import PivotPiScratch
    pivotpi_available=True
except:
    pivotpi_available=False

# Folders where pictures get saved
defaultCameraFolder="/home/pi/Desktop/"
cameraFolder = defaultCameraFolder


# Pi user ID Number - used for setting permissions
pi_user=1000
pi_group=1000

# used is GrovePi is actually connected - allows for testing without a GrovePi
en_grovepi=1
# print debugging statements
en_debug=1

try:
    s = scratch.Scratch()
    if s.connected:
        print "GrovePi Scratch: Connected to Scratch successfully"
    #else:
    #sys.exit(0)
except scratch.ScratchError:
    print "GrovePi Scratch: Scratch is either not opened or remote sensor connections aren't enabled"
    #sys.exit(0)

class myThread (threading.Thread):     
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):
        while running:
            time.sleep(.2)              # sleep for 200 ms

thread1 = myThread(1, "Thread-1", 1)        #Setup and start the thread
thread1.setDaemon(True)

analog_sensors=['analogRead','rotary','sound','light','moisture']
digitalInp=['button']
digitalOp=['led','relay']
pwm=['LEDPower','buzzer','analogWrite']

def match_sensors(msg,lst):
    for i,e in enumerate(lst):
        if msg[:len(e)].lower()==e.lower():
            return i
    return -1
    
try:
    s.broadcast('READY')
except NameError:
    print "GrovePi Scratch: Unable to Broadcast"


while True:
    try:
        m = s.receive()

        while m==None or m[0] == 'sensor-update':
            m = s.receive()

        originalmsg = m[1]

        # change input to all lowercase. 
        # Users can enter any which way they want
        msg = originalmsg.lower()
        if en_debug:
            print "Rx:",originalmsg

        if msg == 'SETUP'.lower() :
            print "Setting up sensors done"
        elif msg == 'START'.lower() :
            running = True
            if thread1.is_alive() == False:
                thread1.start()
            print "Service Started"
        
        # ANALOG SENSORS
        elif match_sensors(msg,analog_sensors) >=0:
            if en_grovepi:
                s_no=match_sensors(msg,analog_sensors)
                sens=analog_sensors[s_no]
                port=int(msg[len(sens):])
                a_read=grovepi.analogRead(port)
                s.sensorupdate({sens:a_read})
                
            if en_debug:
                print msg
                print sens +'op:'+ str(a_read)
        
        elif msg[:8].lower()=="setInput".lower():
            if en_grovepi:
                port=int(msg[8:])
                grovepi.pinMode(port,"INPUT")
            if en_debug:
                print msg   
                
        elif msg[:9].lower()=="setOutput".lower():
            if en_grovepi:
                port=int(msg[9:])
                grovepi.pinMode(port,"OUTPUT")
            if en_debug:
                print msg
                
        elif msg[:11].lower()=="digitalRead".lower():
            if en_grovepi:
                port=int(msg[11:])
                grovepi.pinMode(port,"INPUT")
                d_read=grovepi.digitalRead(port)
                s.sensorupdate({'digitalRead':d_read})
            if en_debug:
                print msg
                print "Digital Reading: " + str(d_read)
        
        elif match_sensors(msg,digitalInp) >=0:
            if en_grovepi:
                s_no=match_sensors(msg,digitalInp)
                sens=digitalInp[s_no]
                port=int(msg[len(sens):])
                sens += str(port)
                grovepi.pinMode(port,"INPUT")
                d_read=grovepi.digitalRead(port)
                s.sensorupdate({sens:d_read})
            if en_debug:
                print msg,
                print sens +' output:'+ str(d_read)
                
        elif msg[:16].lower()=="digitalWriteHigh".lower():
            if en_grovepi:
                port=int(msg[16:])
                grovepi.pinMode(port,"OUTPUT")
                grovepi.digitalWrite(port,1)
            if en_debug:
                print msg
                
        elif msg[:15].lower()=="digitalWriteLow".lower():
            if en_grovepi:
                port=int(msg[15:])
                grovepi.pinMode(port,"OUTPUT")
                grovepi.digitalWrite(port,0)
            if en_debug:
                print msg

        elif match_sensors(msg,pwm) >=0:
            if en_grovepi:
                s_no=match_sensors(msg,pwm)
                sens=pwm[s_no]
                l=len(sens)
                port=int(msg[l:l+1])
                power=int(msg[l+1:])
                grovepi.pinMode(port,"OUTPUT")
                grovepi.analogWrite(port,power)
            if en_debug:
                print msg
        
        elif match_sensors(msg,digitalOp) >=0:
            if en_grovepi:
                s_no=match_sensors(msg,digitalOp)
                sens=digitalOp[s_no]
                l=len(sens)
                port=int(msg[l:l+1])
                state=msg[l+1:]
                grovepi.pinMode(port,"OUTPUT")
                if state=='on':
                    grovepi.digitalWrite(port,1)
                else:
                    grovepi.digitalWrite(port,0)
            if en_debug:
                print msg
        
        elif msg[:4].lower()=="temp".lower():
            if en_grovepi:
                port=int(msg[4:])
                [temp,humidity] = grovepi.dht(port,0)
                s.sensorupdate({'temp':temp})
            if en_debug:
                print msg
                print "temp: ",temp
        
        elif msg[:8].lower()=="humidity".lower():
            if en_grovepi:
                port=int(msg[8:])
                [temp,humidity] = grovepi.dht(port,0)
                s.sensorupdate({'humidity':humidity})
            if en_debug:
                print msg
                print "humidity:",humidity
        
        elif msg[:8].lower()=="distance".lower():
            if en_grovepi:
                port=int(msg[8:])
                dist=grovepi.ultrasonicRead(port)
                s.sensorupdate({'distance':dist})
            if en_debug:
                print msg
                print "distance=",dist
        
        elif msg[:3].lower()=="lcd".lower():
            if en_grovepi:
                if en_debug:
                    print msg[:3], msg[3:6], originalmsg[6:]

                sys.path.insert(0, '/home/pi/Dexter/GrovePi/Software/Python/grove_rgb_lcd')
                import grove_rgb_lcd 
                if msg[3:6].lower() == "col".lower(): #lower() added just for consistency. Not really needed
                    rgb = []
                    for i in range(0,6,2): 
                        rgb.append(int(msg[6:][i:i+2],16))  # convert from one hex string to three ints
                    if en_debug:
                        print "colours are:",rgb[0],rgb[1],rgb[2]
                    grove_rgb_lcd.setRGB(rgb[0],rgb[1],rgb[2])
                elif msg[3:6].lower() == "txt".lower():
                    txt = originalmsg[6:]
                    grove_rgb_lcd.setText_norefresh(txt)
                else:
                    pass
            if en_debug:
                print msg
            
        # elif msg[:10].lower()=="setOutput".lower():
        #   if en_grovepi:
        #       port=int(msg[10:])
        #       a_read=grovepi.analogRead(port)
        #       s.sensorupdate({'analogRead':a_read})
        #   if en_debug:
        #       print msg
        #       print "Analog Reading: " + str(a_read)   
        elif msg.lower()=="READ_IR".lower() or msg.lower()=="IR".lower():
            print "READ_IR!" 
            if en_ir_sensor==0:
                import lirc
                sockid = lirc.init("keyes", blocking = False)
                en_ir_sensor=1
            try:
                read_ir= lirc.nextcode()  # press 1 
                if len(read_ir) !=0:
                    print read_ir[0]
            except:
                if en_debug:
                    e = sys.exc_info()[1]
                    print "Error reading IR sensor: " + str(read_ir)
            if en_debug:
                print "IR Recv Reading: " + str(read_ir)
            if en_gpg:
                if len(read_ir) !=0:
                    s.sensorupdate({'read_ir':read_ir[0]})
                else:
                    s.sensorupdate({'read_ir':""})
        # CREATE FOLDER TO SAVE PHOTOS IN

        elif msg[:6].lower()=="FOLDER".lower():
            print "Camera folder"
            try:
                cameraFolder=defaultCameraFolder+str(msg[6:])
                if not os.path.exists(cameraFolder):
                    os.makedirs(cameraFolder)
                    os.chown(cameraFolder,pi_user,pi_group)
                    s.sensorupdate({"folder":"created"})
                else:
                    s.sensorupdate({"folder":"set"})
            except:
                print "error with folder name"

        elif msg.lower()=="TAKE_PICTURE".lower():
            print "TAKE_PICTURE!" 
            try:
                from subprocess import call
                import datetime
                newimage = "{}/img_{}.jpg".format(cameraFolder,str(datetime.datetime.now()).replace(" ","_",10).replace(":","_",10))
                photo_cmd="raspistill -o {} -w 640 -h 480 -t 1".format(newimage)
                print photo_cmd
                call ([photo_cmd], shell=True)
                os.chown(newimage,pi_user,pi_group)
                print "Picture Taken"
            except:
                if en_debug:
                    e = sys.exc_info()[1]
                    print "Error taking picture",e
                s.sensorupdate({'camera':"Error"})
            s.sensorupdate({'camera':"Picture Taken"})

        # Barometer code, pressure
        elif msg[:9].lower()=="pressure".lower():
            if en_grovepi:
                # We import here to prevent errors thrown.  If the import fails, you just get an error message instead of the communicator crashing.
                # If user is using multiple sensors and using their own image which does not have the pythonpath set correctly then 
                #    they'll just not get the output for 1 sensor, and the others will still keep working
                from grove_i2c_barometic_sensor_BMP180 import BMP085 # Barometric pressure sensor.
                bmp = BMP085(0x77, 1)           #Initialize the pressure sensor (barometer)
                press = bmp.readPressure()/100.0
                s.sensorupdate({'pressure':press})
                if en_debug:
                    print "Pressure: " + str(press)
            if en_debug:        # If Debug is enabled, print the value of msg.
                print msg

        elif (msg[:5].lower()=="SPEAK".lower()  or msg[:3].lower()=="SAY".lower() ):
            try:
                if en_grovepi:
                    from subprocess import call
                    cmd_beg = "espeak-ng -ven+f1 "
                    in_text = msg[len("SPEAK"):]
                    cmd_end = " 2>/dev/null"

                    call([cmd_beg+"\""+in_text+"\""+cmd_end], shell=True)
                if en_debug:
                    print(msg)
            except:
                print("Issue with espeak")

        # PIVOTPI
        elif pivotpi_available==True and PivotPiScratch.isPivotPiMsg(msg):
            pivotsensors = PivotPiScratch.handlePivotPi(msg)
            # print "Back from PivotPi",pivotsensors
            s.sensorupdate(pivotsensors)

        else:
            if en_debug:
                print "Ignoring: ",msg


                    
    except KeyboardInterrupt:
        running= False
        print "GrovePi Scratch: Disconnected from Scratch"
        break
    except (scratch.ScratchConnectionError,NameError) as e: #bug fix simonw 22Mar18
        while True:
            #thread1.join(0)
            print "GrovePi Scratch: Scratch connection error, Retrying"
            # print e
            time.sleep(5)
            try:
                s = scratch.Scratch()
                s.broadcast('READY')
                print "GrovePi Scratch: Connected to Scratch successfully"
                break;
            except scratch.ScratchError:
                print "GrovePi Scratch: Scratch is either not opened or remote sensor connections aren't enabled\n..............................\n"
    except:
        e = sys.exc_info()[0]
        print "GrovePi Scratch: Error %s" % e
