1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
"""
    Filename: rolling_control.py
    Created by Judy Stephen (jls633), Cameron Boroumand (cb596)
    Created on: November 28, 2016
    Last Updated: December 10, 2016
"""

"""
Note: Comments in this code use "ball" and "object" interchangeably. 
They are equivalent in meaning 
"""
import os
import RPi.GPIO as GPIO
import time
import sys
#import pygame
#from pygame.locals import *

# import the necessary picamera and opencv packages
from picamera.array import PiRGBArray
from picamera import PiCamera
import time
import cv2
import numpy as np

#import stuff for voice recognition
import pyttsx
import subprocess                 # useful to run espeak on terminal
import datetime                   # useful to get today's date
import speech_recognition as sr   # speech recognition engine
import pyaudio                    # needed to record microphone 
import wave                       # needed to save as wav file

from os import path
AUDIO_FILE = path.join(path.dirname(path.realpath(__file__)), "user.wav")

GPIO.setmode(GPIO.BCM) #set for broadcom numbering

#initalize the camera 
camera = PiCamera()
camera.resolution = (640,480)
camera.framerate = 32

#set up state variable
listeningState = False

#def left servo pin
leftServo=6

#def right servo pin
rightServo = 5

#set up left servo pin
GPIO.setup(leftServo,GPIO.OUT) #set up left pin as output
leftChannel = leftServo
leftFrequency =50 #hz
leftP = GPIO.PWM(leftChannel,leftFrequency)

#set up right servo pin
GPIO.setup(rightServo, GPIO.OUT) #set up right pin as output
rightChannel = rightServo
rightFrequency = 50 #hz
rightP = GPIO.PWM(rightChannel,rightFrequency)

maxSpeedClockwise =6.63 
maxSpeedCounterClockwise = 7.8

maxSpeedRightForward = maxSpeedClockwise
maxSpeedRightBackward = maxSpeedCounterClockwise
maxSpeedLeftForward = maxSpeedCounterClockwise
maxSpeedLeftBackward = maxSpeedClockwise

#stop the servo
def stopServo(servo):
    dc = 0#7.18
    if servo == rightServo:
        rightP.start(dc)
        #print "right servo stopped"
    if servo == leftServo:
        leftP.start(dc)
        #print "left servo stopped"

#move servo forward at max speed
def moveForward(servo):
    if servo == rightServo:
        dc = maxSpeedRightForward
        rightP.start(dc)
        #print "right servo forward"
    if servo == leftServo:
        dc = maxSpeedLeftForward
        leftP.start(dc)

#move servo backward at max speed
def moveBackward(servo):
    if servo == rightServo:
        dc = maxSpeedRightBackward
        rightP.start(dc)
    if servo == leftServo:
        dc = maxSpeedLeftBackward
        leftP.start(dc)

def moveForwardVoice():
    startingTime = time.clock()
    while (time.clock() - startingTime < 5):
        moveRobotForward()
        if GPIO.input(longDist): #if there is object in front of you
            time.sleep(1) #move forward a bit more
            stopRobot()
            break
    stopRobot()

def moveBackwardVoice():
    startingTime = time.clock()
    while (time.clock() - startingTime < 5):
        moveRobotBackward()
        if GPIO.input(longDist): #if there is object in front of you
            time.sleep(1) #move backward a bit more
            stopRobot()
            break
    stopRobot()

def turnRightVoice():
    pivot90Right()
    moveForwardVoice()

def turnLeftVoice():
    pivot90Left()
    moveForwardVoice()

def moveRobotForward():
    moveForward(rightServo)
    moveForward(leftServo)

def moveRobotBackward():
    moveBackward(rightServo)
    moveBackward(leftServo) 

def stopRobot():
    stopServo(leftServo)
    stopServo(rightServo)

def pivot90Right():
    moveForward(leftServo)
    moveBackward(rightServo)
    time.sleep(0.6)
    stopRobot()
    
def pivot90Left():
    moveBackward(leftServo)
    moveForward(rightServo)
    time.sleep(0.6)
    stopRobot()

def pivot45Left():
    moveBackward(leftServo)
    moveForward(rightServo)
    time.sleep(1)
    stopRobot()

def pivot45Right():
    moveForward(leftServo)
    moveBackward(rightServo)
    time.sleep(1)
    stopRobot()

def pivot30Right():
    moveForward(leftServo)
   # moveBackward(rightServo)
    stopServo(rightServo)
    time.sleep(0.65) #was 0.5
    stopRobot()

def veerLeft():
    #max speed on right wheel
    #slow on left wheel
    moveForward(rightServo)
    stopServo(leftServo)
    time.sleep(0.2)
    stopRobot()

def veerRight():
    #max speed on left wheel
    #slow on right wheel
    moveForward(leftServo)
    stopServo(rightServo)
    time.sleep(0.2)

    stopRobot()
        
#set up exit button
pinNum = 17
GPIO.setup(pinNum,GPIO.IN,pull_up_down=GPIO.PUD_UP)

#set up interrupt on exit button 
def GPIO17_callback(channel):
    print "falling edge detected on port 17"
    exitProgram()
GPIO.add_event_detect(17,GPIO.FALLING,callback=GPIO17_callback,bouncetime=300)

#on exiting program (when exit button itnterrupt is called)
def exitProgram():
    print "exiting program"
    stopRobot()
    GPIO.cleanup()
    sys.exit()

#set up start button
startButton = 22
GPIO.setup(startButton, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#set up interrupt on start button 
def GPIO22_callback(channel):
    print "Listening now"
    global listeningState
    stopRobot()
    listeningState = True
GPIO.add_event_detect(22,GPIO.FALLING,callback=GPIO22_callback,bouncetime=300)


#set up long distance sensor
longDist = 19
GPIO.setup(longDist, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)


#set up LED on pin 13
ledPin = 13
GPIO.setup(ledPin, GPIO.OUT)
ledFreq = 10
ledP = GPIO.PWM(ledPin,ledFreq)
ledDC = 50.0

#define simple on and off LED functions
def turnLedOn():
    ledP.start(50)

def turnLedOff():
    ledP.start(0)

#this function is called once robot has found object/ball
def atBall(color):
    stopRobot()
    turnLedOn()
    time.sleep(5) #flash LED for 5 seconds
    turnLedOff()
    if color == "blue": 
        fetchVoice("green") #now go find green object

"""
This function is called when the robot is in motion
towards the ball
Returns x, 900, 1000
x: returns x_coordinate position of object
900: ball is extremely to robot
1000: ball is not in vision of robot
"""
def findBall(color, objectStatus):
    #look for ball
    #grab a reference to the raw camera capture
    rawCapture = PiRGBArray(camera,size=(640,480))
    #allow the camera to warmup
    time.sleep(0.1)
    #capture frames from the camera
    for frame in camera.capture_continuous(rawCapture,format="bgr",use_video_port=True):
        image=frame.array
        # Convert BGR to HSV
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

        # define range of blue color in HSV
        lower_blue = np.array([110,50,50])
        upper_blue = np.array([130,255,255])

        lower_green = np.array([29,86,6])
        upper_green = np.array([64,255,255])

        # Threshold the HSV image to get either only blue or only green colors
        if color == "blue":
            mask = cv2.inRange(hsv, lower_blue, upper_blue)
        else:
            mask = cv2.inRange(hsv, lower_green, upper_green)
        
        mask = cv2.erode(mask,None,iterations=2)
        mask=cv2.dilate(mask,None,iterations=2)

        cnts = cv2.findContours(mask.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
        center = None
        if len(cnts) > 0:
            c = max(cnts,key=cv2.contourArea)
            ((x,y),radius) = cv2.minEnclosingCircle(c)
            M = cv2.moments(c)
            center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
            if objectStatus == "notVeryClose": 
                if color == "blue":
                    threshold = 20 #larger threshold for blue b/c using larger blue object
                else:
                    threshold = 10 #smaller threshold for green b/c using smaller green object
                if radius > 175:
                    return 900 #signal that robot is very close to object
                elif radius >threshold: 
                    return x
                else:
                    return 1000 #radius too small to be valid
            else:
                if radius > 100:#10:
                    return x #ball is infront of us
                else:
                #doesn't make sense for code to fall in this
                #b/c radius should not be <100 at this point,
                #so just stop the robot since something is wrong
                    return 1000 #signal to stop robot
        else:
            print 1000 
            return 1000 #object is not in camera's view
                   
        #cv2.imshow('frame',image)

        #cv2.imshow('mask',mask)

        key = cv2.waitKey(1) & 0xFF
        rawCapture.truncate(0)

        if key == ord("q"):
            #camera.release()
            cv2.destroyAllWindows()
            exitProgram()

        
"""
Target is already found. keep going towards it
until either the distance sensor sees it
OR until camera loses it, whichever occurs first
"""
def moveUntilLost(color):
    xPos = findBall(color,"veryClose")
    if xPos == 1000: #ball lost, just stop
        atBall(color)
    elif GPIO.input(longDist):
        print "we are at object"
        atBall(color)
    elif (xPos<480 and xPos > 160): #ball is straight ahead
        print "found ball, go straight"
        moveRobotForward()
        moveUntilLost(color)
    elif (xPos < 160): #ball is to the left
        print "found ball, veer left"
        veerLeft()
        moveUntilLost(color)
    else: #ball is to the right
        print "found ball, veer right"
        veerRight()
        moveUntilLost(color)
     
"""
This function is called when robot is to be moving towards
the object and constantly checking the object's location relative
to itself so the robot can continously adjust its motion
"""
def moveToTarget(color):
    xPos = findBall(color, "notVeryClose") 
    if xPos == 1000: #ball is not in camera's sight, just stop and go back to polling
        stopRobot()
        fetchVoice(color) #look for object again
    elif xPos ==900: #ball is right infront of robot, go for a bit more until we lose the object
        print "code 900"
        moveUntilLost(color)
    elif GPIO.input(longDist): #distance sensor senses the object. go for a bit more until we lose the object
        print "at object"
        moveUntilLost(color)
    elif (xPos < 480 and xPos > 160): #ball is straight ahead
        print "go straight"
        moveRobotForward()
        moveToTarget(color)
    elif (xPos < 160): #ball is to the left
        print "veer left"
        veerLeft()
        moveToTarget(color)
    else: #ball is to the right
        print "veer right" 
        veerRight()
        moveToTarget(color)

"""
This function is called when robot is sotpped and looking for
the object
returns 0,1, or 2:
0: no ball found
1: ball found, robot is not too close to ball
2: ball found. robot is extremely close to ball
"""
def pollBall(startTime, color):
    #look for ball
    #runs until 1.3 seconds elapses or ball is found, whichever comes first
    #grab a reference to the raw camera capture
    rawCapture = PiRGBArray(camera,size=(640,480))
    #allow the camera to warmup
    time.sleep(0.1)
    global ballFound
    global ballXPos
    global ballYPos
    global pollingState
    #capture frames from the camera
    for frame in camera.capture_continuous(rawCapture,format="bgr",use_video_port=True):
        currentTime = time.clock()
        if currentTime - startTime > 1.3:
            return 0 #no ball found
        image=frame.array
        # Convert BGR to HSV
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

        # define range of blue color in HSV
        lower_blue = np.array([110,50,50])
        upper_blue = np.array([130,255,255])

        #define rnage of green color in HSV
        lower_green = np.array([29,86,6])
        upper_green = np.array([64,255,255])

        # Threshold the HSV image to get either only blue or only green 
        if color == "blue":
            mask = cv2.inRange(hsv, lower_blue, upper_blue)
        else:
            mask = cv2.inRange(hsv, lower_green, upper_green)
            
        mask = cv2.erode(mask,None,iterations=2)
        mask=cv2.dilate(mask,None,iterations=2)

        cnts = cv2.findContours(mask.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
        center = None

        if len(cnts) > 0:
            c = max(cnts,key=cv2.contourArea)
            ((x,y),radius) = cv2.minEnclosingCircle(c)
            M = cv2.moments(c)
            center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
            if color == "blue":
                threshold = 20 #larger threshold for blue b/c using larger blue object
            else:
                threshold = 10 #smaller threshold for green b/c using smaller green object
            if radius > threshold:
                if radius > 175: #object/ball is right next to us!
                    print "found it next to us"
                    return 2 #ball saturation
                else: 
                    print "found a ball - leave poll"
                    return 1 #ball is found but not right next to robot              
        key = cv2.waitKey(1) & 0xFF
        rawCapture.truncate(0)

        if key == ord("q"):
            cv2.destroyAllWindows()
            exitProgram()
        
def fetchVoice(color):
    if color == "blue":
        print "looking for some blue now"
    else:
        print "looking for some green now"
    startTime = time.clock()
    myFoundBall = pollBall(startTime, color)
    if myFoundBall == 1: #ball has been located, so move towards it
        moveToTarget(color)
    elif myFoundBall == 0: #no ball was found
        print "pivoting now"
        pivot30Right() #pivot ~30 degrees 
        fetchVoice(color) #look for object again
    else:
        print "found ball while polling" #ball/object is right next to robot
        moveUntilLost(color)
        
"""Say today's date """
def sayDate():
    myDateList = []
    today = datetime.date.today()
    myDateList.append(today)
    formatedDate =  today.strftime("It is %A, %B %d %Y") # 'It is Saturday, December 03 2016'
    print formatedDate
    # speak the date 
    speakStr(formatedDate)

""" Records user input from microphone for 3 seconds and saves in wave file user.wav"""
def record_audio():
    CHUNK = 8192
    FORMAT = pyaudio.paInt16
    CHANNELS = 1  # used to be 2 but our microphone only has 1
    RATE = 44100
    RECORD_SECONDS = 3
    WAVE_OUTPUT_FILENAME = "user.wav"

    p = pyaudio.PyAudio()

    stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)

    print "* Recoding audio..."

    frames = []
    
    for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
        data = stream.read(CHUNK)
        frames.append(data)

    print "* done\n"

    stream.stop_stream()
    stream.close()
    p.terminate()

    wf = wave.open(WAVE_OUTPUT_FILENAME, "wb")
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()

""" Recognize user speech by recognizing the speech from wav file user.wav
    Currently uses Google Speech Recognition because it is much faster than Sphinx 
    Return: recognized speech or "error_recognizing" if there was an error """
def recognize_speech():
    recognized_speech = "initialized recognized_speech"
    r = sr.Recognizer()
    with sr.AudioFile(AUDIO_FILE) as source:
        audio = r.record(source)      # read the entire audio file

    start = time.clock()
    try:
        recognized_speech = r.recognize_google(audio)
        print("Google Speech Recognition thinks you said " + recognized_speech)
    except sr.UnknownValueError:
        print("Google Speech Recognition could not understand your input")
    except sr.RequestError as e:
        print("Could not request results from Google Speech Recognition serivce; {0}".format(e))
    end = time.clock()
    timeElapsed = end - start
    print "computation time for Google Speech Recognition : " + str(timeElapsed)
    return recognized_speech

song_wf = 0
WAVE_FILE_NAME = path.join(path.dirname(path.realpath(__file__)), "discovery.wav")

def play_song_callback(in_date, frame_count, time_info, status):
    data = song_wf.readframes(frame_count)
    return (data, pyaudio.paContinue)

""" Play a wave file """
def play_song():
    # WAVE_FILE_NAME = "discovery.wav"
    song_wf = wave.open(WAVE_FILE_NAME, 'rb')

    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(song_wf.getsampwidth()),
                    channels=song_wf.getnchannels(),
                    rate=song_wf.getframerate(),
                    output=True,
                    stream_callback=play_song_callback)
    stream.start_stream()
    while stream.is_active():
        time.sleep(0.1)
    stream.stop_stream()
    stream.close()
    song_wf.close()
    print "play song stream closed"
    p.terminate()

"""Input: the recognized user command
   This function will find what the user wants to do and perform it
   For example, if the user says "move forward" then it will move the 
   robot forward. 
   Supported commands: move forward, move back, left, right, stop, play song"""
def command_robot(recognized_command):
    global pollingState
    global movingState
    global doneState
    if "forward" in recognized_command:
        # move forward
        print "move robot forward"
        moveForwardVoice()
    elif "move back" in recognized_command:
        # move backward
        print "move robot backward"
        moveBackwardVoice()
    elif "left" in recognized_command:
        # move left
        turnLeftVoice()
        print "move robot left"
    elif "right" in recognized_command:
        # move right
        print "move robot right"
        turnRightVoice()
    elif "stop" in recognized_command:
        # stop robot
        print "stop the robot"
    elif "song" in recognized_command:
        print "play a song"
        #play_song()
    elif "ball" in recognized_command:
        # fetch command 
        print "robot in fetch mode"
        fetchVoice("blue")
    else: 
        print "unsupported command"

if __name__=="__main__":
    engine = pyttsx.init()
    try:
        while (True):
            if listeningState:
                listeningState = False
                print 'running pi_voice.py ...'
                record_audio()
                recognized_word = recognize_speech()
                command_robot(recognized_word)
                
    except KeyboardInterrupt:
        print "keyboard interrupt"

    exitProgram()