Diddiborg with Ultraborg over Web UI

Hi !

Need some help with my project.
I like to run the Diddiborg with Ultraborg over Web UI. I'm using the code I found on this Forum. https://www.piborg.org/node/1846

Two HC-SR04 are in the front of the Diddiborg. I get correct readings in the Web UI.
But when I try to use "Semi" or "Auto-mode" there is no reaction.

Another thing is to control LED lights over the Web UI but I'm not sure how to put inside. Right now I turn on the lights when I start the script and turn off when the script ends. >> # Settings Light

Thanks :-)

Andreas

Attachments: 
piborg's picture

The reason that semi-automatic and automatic modes are not really working is that they are currently unfinished.
In its current state the automatic mode does nothing at all.
The semi-automatic mode should:

  • Allow normal movement when both sensors are beyond 100 mm
  • Move forward slowly if either of the sensor readings are between 50 and 100 mm
  • Stop if either of the sensor readings are below 50 mm

I do not remember if they have made any progress with the functionality since that version, but I think they were intending to make the automatic mode move around in a simlar fashion to the automaic carpet cleaners.

What you would need to do to make the LEDs do something entirely depends on what you want them to do.
For example if you were to change the values inside the various movement mode sections then they would show you which mode you are in.
e.g.

            if movementMode == MANUAL_MODE:
                # User movement, wait a second before checking again
                GPIO.output(40, False)
                GPIO.output(38, False)
                GPIO.output(36, True)
                time.sleep(1.0)

Thanks for the hint to see which mode I'm in.
The semi-automatic mode doesn't work for me and I have no idea whats wrong.
Doesn't slow down and don't stop below 50 mm.

            elif movementMode == SEMI_AUTO_MODE:
                # Semi-automatic movement mode, checks twice per second
                # Ultrasonic distance readings semi auto mode
                print 'Semi auto mode'
                GPIO.output(40, True)
                GPIO.output(38, False)
                GPIO.output(36, True)

                # Get the readings from ultra sensors
                print 'Reading distance'
                distance1 = int(UB.GetDistance1())
                distance2 = int(UB.GetDistance2())

                # Set critical allowed distance to object in front
                if distance1 <= 50:
                    PBR.MotorsOff()
                elif distance1 <= 100:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

Finally I found the problem with the semi-automatic mode.
Instead <=50 I have 150 and <=100 I have 300.
Diddiborg slow down and stop directly before the obstacle.

Web UI show 50 mm to obstacle...

            elif movementMode == SEMI_AUTO_MODE:
                # Semi-automatic movement mode, checks twice per second
                # Ultrasonic distance readings semi auto mode
                print 'Semi auto mode'
                GPIO.output(40, True)
                GPIO.output(38, False)
                GPIO.output(36, True)

                # Get the readings from ultra sensors
                print 'Reading distance'
                distance1 = int(UB.GetDistance1())
                distance2 = int(UB.GetDistance2())

                # Set critical allowed distance to object in front
                if distance1 <= 150:
                    PBR.MotorsOff()
                elif distance1 <= 300:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

                # Set critical allowed distance to object in front
                if distance2 <= 150:
                    PBR.MotorsOff()
                elif distance1 <= 300:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

Now I'm testing the Auto mode but having trouble getting the code free of buggs.
Need a little help to get ahead :-)

            elif movementMode == AUTO_MODE:
                # Automatic movement mode, updates five times per second
                GPIO.output(40, False)
                GPIO.output(38, False)
                GPIO.output(36, False)

                # TODO: Fill in logic here

                # Get the readings from ultra sensors
                print 'Reading distance'
                distance1 = int(UB.GetDistance1())
                distance2 = int(UB.GetDistance2())

                if distance1 >= 150 and distance2 >= 150:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(driveLeft
                elif distance1 <= 149:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(-driveRight)
                    PBR.SetMotor2(driveLeft)
                else distance2 <= 149:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)
piborg's picture

The distances shown in the web UI in that code are divided by 10, so they display as cm.
The values in the code are in mm, so they need to be 10 times larger.
e.g.

if distance1 < 150:

will be for distances below 15 cm as shown on the web UI.

The code you have shown for the auto mode has a couple of coding mistakes that will cause it to not work:

  • One of the lines is missing the final ).
  • You need to use elif for each additional condition, else is only used to mean "anything that is not covered by any if or elif above".
  • All of the conditions set the same speed: 30%.
    I guess you were trying to make him stop when too close.

You can simply the code a bit by only doing the maxPower calculation and the setting of the motors once.
This is because each time you want to set the motors, the only difference is the speed you are setting.

I think something like this would do what you were intending:

            elif movementMode == AUTO_MODE:
                # Automatic movement mode, updates five times per second
                GPIO.output(40, False)
                GPIO.output(38, False)
                GPIO.output(36, False)

                # TODO: Fill in logic here

                # Get the readings from ultra sensors
                print 'Reading distance'
                distance1 = int(UB.GetDistance1())
                distance2 = int(UB.GetDistance2())

                if distance1 >= 150 and distance2 >= 150:
                    driveRight = 0.3
                    driveLeft = 0.3
                elif distance1 <= 149:
                    driveRight = 0.0
                    driveLeft = 0.0
                elif distance2 <= 149:
                    driveRight = 0.0
                    driveLeft = 0.0
				PBR.SetMotor1(driveRight * maxPower)
				PBR.SetMotor2(-driveLeft * maxPower)

Thanks for your help.
I will trying to make him turning left or right when too close.
When I run the script I get an error .

>> line 260
driveRight = 0.3
^
IndentationError: expected an indented block <<

Here is my slightly working update for the "Auto Mode"

In "Auto Mode" the Borg have to turn right when obstacle is detected. But it seems there is to much time between the Sono readings. The Borg turns around 45° degrees and don't see that the obstacle is still there.

Hoppe you can help me with this.

           elif movementMode == AUTO_MODE:
                # Automatic movement mode, updates five times per second
                GPIO.output(40, False)
                GPIO.output(38, False)
                GPIO.output(36, False)

                # TODO: Fill in logic here

                # Get the readings from ultra sensors
                print 'Reading distance auto mode'
                distance1 = int(UB.GetDistance1())
                distance2 = int(UB.GetDistance2())

                if distance1 >= 350 or distance2 >= 350:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

                elif distance1 <= 330 or distance2 <=330:
                    driveRight = 0.5 * maxPower
                    driveLeft = 0.5 * maxPower
                    PBR.SetMotor1(-driveRight)
                    PBR.SetMotor2(-driveLeft)
piborg's picture

I think the reason the code is slow to react to changes is due to the onboard filtering performed by the UltraBorg.

UltraBorg has two modes for reading ditances:

  1. Filtered:
    Cleans up the distance readings to give better accuracy and stability.
    The filtered readings update slowly, they may take up to 2 seconds to change to a new value.
    Used by calling the UB.GetDistanceX() functions.
  2. Unfiltered / raw:
    Provides the latest reading from the sensor without any filtering.
    These raw readings update quickly, but they suffer from 'noise' and may be less accurate for measuring a fixed distance.
    Used by calling the UB.GetRawDistanceX() functions.

I would suggest replacing UB.GetDistance1() and UB.GetDistance2() with UB.GetRawDistance1() and UB.GetRawDistance2() and see if the response is faster.

UB.GetRawDistanceX() works perfect; reading and response are much better.
Now I have to tune the setup for the moves. I will also try with one more ultrasonic module rear.

#!/usr/bin/env python
# coding: Latin-1

# Creates a web-page interface for DiddyBorg Metal Edition

# Import library functions we need
import PicoBorgRev
import time
import sys
import threading
import SocketServer
import picamera
import picamera.array
import cv2
import UltraBorg
import datetime
import RPi.GPIO as GPIO

# Settings Light
GPIO.setmode(GPIO.BOARD)
GPIO.setup(40, GPIO.OUT)
GPIO.setup(38, GPIO.OUT)
GPIO.setup(36, GPIO.OUT)
#GPIO.output(40, True)
#GPIO.output(38, True)
#GPIO.output(36, True)

# Settings for the web-page
webPort = 80                            # Port number for the web-page, 80 is what web-pages normally use
imageWidth = 480                        # Width of the captured image in pixels
imageHeight = 360                       # Height of the captured image in pixels
frameRate = 20                          # Number of images to capture per second
displayRate = 4                         # Number of images to request per second
photoDirectory = '/home/pi'             # Directory to save photos to

# Movement mode constants
MANUAL_MODE = 0                         # User controlled movement
SEMI_AUTO_MODE = 1                      # Semi-automatic movement
AUTO_MODE = 2                           # Fully automatic movement

# Global values
global PBR
global lastFrame
global lockFrame
global camera
global processor
global running
global watchdog
global movementMode
running = True
movementMode = MANUAL_MODE

# Setup the UltraBorg
global UB
UB = UltraBorg.UltraBorg()              # Create a new UltraBorg object
UB.Init()                               # Set the board up (checks the board is connected)

# Setup the PicoBorg Reverse
PBR = PicoBorgRev.PicoBorgRev()
#PBR.i2cAddress = 0x44                  # Uncomment and change the value if you have changed the board address
PBR.Init()
if not PBR.foundChip:
    boards = PicoBorgRev.ScanForPicoBorgReverse()
    if len(boards) == 0:
        print 'No PicoBorg Reverse found, check you are attached :)'
    else:
        print 'No PicoBorg Reverse at address %02X, but we did find boards:' % (PBR.i2cAddress)
        for board in boards:
            print '    %02X (%d)' % (board, board)
        print 'If you need to change the I²C address change the setup line so it is correct, e.g.'
        print 'PBR.i2cAddress = 0x%02X' % (boards[0])
    sys.exit()
#PBR.SetEpoIgnore(True)                 # Uncomment to disable EPO latch, needed if you do not have a switch / jumper
PBR.SetCommsFailsafe(False)             # Disable the communications failsafe
PBR.ResetEpo()

# Power settings
voltageIn = 1.2 * 12                    # Total battery voltage to the PicoBorg Reverse
voltageOut = 12.0                       # Maximum motor voltage

# Setup the power limits
if voltageOut > voltageIn:
    maxPower = 1.0
else:
    maxPower = voltageOut / float(voltageIn)


# Timeout thread
class Watchdog(threading.Thread):
    def __init__(self):
        super(Watchdog, self).__init__()
        self.event = threading.Event()
        self.terminated = False
        self.start()
        self.timestamp = time.time()
        
    def run(self):
        timedOut = True
        # This method runs in a separate thread
        while not self.terminated:
            # Wait for a network event to be flagged for up to one second
            if timedOut:
                if self.event.wait(1):
                    # Connection
                    print 'Reconnected...'
                    timedOut = False
                    self.event.clear()
            else:
                if self.event.wait(1):
                    self.event.clear()
                else:
                    # Timed out
                    print 'Timed out...'
                    timedOut = True
                    PBR.MotorsOff()

# Image stream processing thread
class StreamProcessor(threading.Thread):
    def __init__(self):
        super(StreamProcessor, self).__init__()
        self.stream = picamera.array.PiRGBArray(camera)
        self.event = threading.Event()
        self.terminated = False
        self.start()
        self.begin = 0

    def run(self):
        global lastFrame
        global lockFrame
        # This method runs in a separate thread
        while not self.terminated:
            # Wait for an image to be written to the stream
            if self.event.wait(1):
                try:
                    # Read the image and save globally
                    self.stream.seek(0)
                    flippedArray = cv2.flip(self.stream.array, -1) # Flips X and Y
                    retval, thisFrame = cv2.imencode('.jpg', flippedArray)
                    del flippedArray
                    lockFrame.acquire()
                    lastFrame = thisFrame
                    lockFrame.release()
                finally:
                    # Reset the stream and event
                    self.stream.seek(0)
                    self.stream.truncate()
                    self.event.clear()

# Image capture thread
class ImageCapture(threading.Thread):
    def __init__(self):
        super(ImageCapture, self).__init__()
        self.start()

    def run(self):
        global camera
        global processor
        print 'Start the stream using the video port'
        camera.capture_sequence(self.TriggerStream(), format='bgr', use_video_port=True)
        print 'Terminating camera processing...'
        processor.terminated = True
        processor.join()
        print 'Processing terminated.'

    # Stream delegation loop
    def TriggerStream(self):
        global running
        while running:
            if processor.event.is_set():
                time.sleep(0.01)
            else:
                yield processor.stream
                processor.event.set()

# Automatic movement thread
class AutoMovement(threading.Thread):
    def __init__(self):
        super(AutoMovement, self).__init__()
        self.terminated = False
        self.start()

    def run(self):
        global movementMode
        # This method runs in a separate thread
        while not self.terminated:
            # See which mode we are in
            if movementMode == MANUAL_MODE:
		# User movement, wait a second before checking again
		#print 'Manual mode'
		GPIO.output(40, True)
		GPIO.output(38, True)
		GPIO.output(36, True)
                time.sleep(1.0)
            elif movementMode == SEMI_AUTO_MODE:
                # Semi-automatic movement mode, checks twice per second
                # Ultrasonic distance readings semi auto mode
		#print 'Semi auto mode'
		GPIO.output(40, True)
		GPIO.output(38, False)
		GPIO.output(36, True)

                # Get the readings from ultra sensors
      		#print 'Reading distance'
                distance1 = int(UB.GetRawDistance1())
                distance2 = int(UB.GetRawDistance2())
                
                # Set critical allowed distance to object in front
                if distance1 <= 200:
		    PBR.MotorsOff()
                elif distance1 <= 400:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

                # Set critical allowed distance to object in front
                if distance2 <= 200:
                    PBR.MotorsOff()
                elif distance2 <= 400:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

  
                # Wait for 1/2 of a second before reading again
                time.sleep(0.1)

            elif movementMode == AUTO_MODE:
                # Automatic movement mode, updates five times per second
                GPIO.output(40, False)
                GPIO.output(38, False)
                GPIO.output(36, False)

                # Get the readings from ultra sensors
                distance1 = int(UB.GetRawDistance1())
                distance2 = int(UB.GetRawDistance2())

                #if distance1 >= 400 or distance2 >= 400:
                #    driveRight = 0.4 * maxPower
                #    driveLeft = 0.4 * maxPower
                #    PBR.SetMotor1(driveRight)
                #    PBR.SetMotor2(-driveLeft)

                if distance1 <= 350:
                    driveRight = 0.5 * maxPower
                    driveLeft = 0.5 * maxPower
                    PBR.SetMotor1(-driveRight)
                    PBR.SetMotor2(-driveLeft)

                elif distance2 <= 350:
                    driveRight = 0.5 * maxPower
                    driveLeft = 0.5 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(driveLeft)

                else:
                    driveRight = 0.4 * maxPower
                    driveLeft = 0.4 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)

  
                # Wait for 1/5 of a second before reading again
                time.sleep(0.0001)
            else: 
                # Unexpected, print an error and wait a second before trying again
                print 'Unexpected movement mode %d' % (movementMode)
                time.sleep(0.01)

# Class used to implement the web server
class WebServer(SocketServer.BaseRequestHandler):
    def handle(self):
        global PBR
        global lastFrame
        global watchdog
        global movementMode
        # Get the HTTP request data
        reqData = self.request.recv(1024).strip()
        reqData = reqData.split('\n')
        # Get the URL requested
        getPath = ''
        for line in reqData:
            if line.startswith('GET'):
                parts = line.split(' ')
                getPath = parts[1]
                break

        watchdog.event.set()

        if getPath.startswith('/distances-once'):
            # Ultrasonic distance readings
            # Get the readings
            distance1 = int(UB.GetRawDistance1())
            distance2 = int(UB.GetRawDistance2())
            distance3 = int(UB.GetRawDistance3())
            
            # Build a table for the values
            httpText = ''
            if distance1 == 0:
                httpText += 'None'
            else:
                httpText += '%04d' % (distance1/10)
	    if distance3 == 0:
                httpText += 'None'
            else:
                httpText += '%04d' % (distance3/10)
            if distance2 == 0:
                httpText += 'None'
            else:
                httpText += '%04d' % (distance2/10)
            httpText += ''
            self.send(httpText) 

        elif getPath.startswith('/semiAuto'):
              # Toggle Auto mode
              if movementMode == SEMI_AUTO_MODE:
                  # We are in semi-auto mode, turn it off
                  movementMode = MANUAL_MODE
                  httpText = ''
                  httpText += 'Speeds: 0 %, 0 %'
                  httpText += ''
                  self.send(httpText)
                  PBR.MotorsOff()
              else:
                  # We are not in semi-auto mode, turn it on
                  movementMode = SEMI_AUTO_MODE
                  httpText = ''
                  httpText += 'Semi Mode'
                  httpText += ''
                  self.send(httpText)

        elif getPath.startswith('/Auto'):
              # Toggle Auto mode
              if movementMode == AUTO_MODE:
                  # We are in auto mode, turn it off
                  movementMode = MANUAL_MODE
                  httpText = ''
                  httpText += 'Speeds: 0 %, 0 %'
                  httpText += ''
                  self.send(httpText)
                  PBR.MotorsOff()
              else:
                  # We are not in auto mode, turn it on
                  movementMode = AUTO_MODE
                  httpText = ''
                  httpText += 'Auto Mode'
                  httpText += ''
                  self.send(httpText)

        elif getPath.startswith('/cam.jpg'):
            # Camera snapshot
            lockFrame.acquire()
            sendFrame = lastFrame
            lockFrame.release()
            if sendFrame != None:
                self.send(sendFrame.tostring())

        elif getPath.startswith('/off'):
            # Turn the drives off and switch to manual mode
            movementMode = MANUAL_MODE
            httpText = ''
            httpText += 'Speeds: 0 %, 0 %'
            httpText += ''
            self.send(httpText)
            PBR.MotorsOff()

        elif getPath.startswith('/set/'):
            # Motor power setting: /set/driveLeft/driveRight
            parts = getPath.split('/')
            # Get the power levels
            if len(parts) >= 4:
                try:
                    driveLeft = float(parts[2])
                    driveRight = float(parts[3])
                except:
                    # Bad values
                    driveRight = 0.0
                    driveLeft = 0.0
            else:
                # Bad request
                driveRight = 0.0
                driveLeft = 0.0
            # Ensure settings are within limits
            if driveRight < -1:
                driveRight = -1
            elif driveRight > 1:
                driveRight = 1
            if driveLeft < -1:
                driveLeft = -1
            elif driveLeft > 1:
                driveLeft = 1
                
            # Report the current settings
            percentLeft = driveLeft * 100.0;
            percentRight = driveRight * 100.0;
            httpText = ''
            if movementMode == MANUAL_MODE:
                httpText += 'Speeds: %.0f %%, %.0f %%' % (percentLeft, percentRight)
            elif movementMode == SEMI_AUTO_MODE:
                httpText += 'Semi: %.0f %%, %.0f %%' % (percentLeft, percentRight)
            elif movementMode == AUTO_MODE:
                percentLeft = PBR.GetMotor2() * 100.0;
                percentRight = PBR.GetMotor1() * 100.0;
                httpText += 'Auto: %.0f %%, %.0f %%' % (percentLeft, percentRight)
            httpText += ''
            self.send(httpText)
            
            # Set the outputs as long as we are not in auto mode
            if movementMode != AUTO_MODE:
                driveLeft *= maxPower
                driveRight *= maxPower
                PBR.SetMotor1(driveRight)
                PBR.SetMotor2(-driveLeft)

        elif getPath.startswith('/photo'):
            # Save camera photo
            lockFrame.acquire()
            captureFrame = lastFrame
            lockFrame.release()
            httpText = ''
            if captureFrame != None:
                photoName = '%s/Photo %s.jpg' % (photoDirectory, datetime.datetime.utcnow())
                try:
                    photoFile = open(photoName, 'wb')
                    photoFile.write(captureFrame)
                    photoFile.close()
                    httpText += 'Photo saved to %s' % (photoName)
                except:
                    httpText += 'Failed to take photo!'
            else:
                httpText += 'Failed to take photo!'
            httpText += ''
            self.send(httpText)                    

        elif getPath == '/':
            # Main page, click buttons to move and to stop
            httpText = '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += 'Spin Left\n'
            httpText += 'Forward\n'
            httpText += 'Spin Right\n'
            httpText += '\n'
            httpText += 'Turn Left\n'
            httpText += 'Stop\n'
            httpText += 'Turn Right\n'
            httpText += '\n'
            httpText += 'Semi Auto\n'
            httpText += 'Reverse\n'
            httpText += 'Auto Mode\n'
            httpText += '\n'
            httpText += 'Save Photo\n'
            httpText += '\n'
            httpText += '\n'
            httpText += 'Distances (cm)\n'
            httpText += '\n'            
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            self.send(httpText)
            
        elif getPath == '/stream':
            # Streaming frame, set a delayed refresh
            displayDelay = int(1000 / displayRate)
            httpText = '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n' % (displayDelay)
            httpText += '\n'
            httpText += '\n'
            httpText += '\n'
            self.send(httpText)
            
        elif getPath == '/distances':
            # Repeated reading of the ultrasonic distances, set a delayed refresh
            # We use AJAX to avoid screen refreshes caused by refreshing a frame
            displayDelay = int(1000 / displayRate)
            httpText = '\n'
            httpText += '\n'
            httpText += 'Failed reading distances (not running?)";\n'
            httpText += '   }\n'
            httpText += '  }\n'
            httpText += ' }\n'
            httpText += ' xmlhttp.open("GET","distances-once",true);\n'
            httpText += ' xmlhttp.send();\n'
            httpText += ' setTimeout("readDistances()", %d);\n' % (displayDelay)
            httpText += '}\n'
            httpText += '//-->\n'
            httpText += '\n'
            httpText += '\n'
            httpText += '\n' % (displayDelay)
            httpText += 'Waiting for first distance reading...\n'
            httpText += '\n'
            httpText += '\n'
            self.send(httpText)

        else:
            # Unexpected page
            self.send('Path : "%s"' % (getPath))

    def send(self, content):
        self.request.sendall('HTTP/1.0 200 OK\n\n%s' % (content))


# Create the image buffer frame
lastFrame = None
lockFrame = threading.Lock()

# Startup sequence
print 'Setup camera'
camera = picamera.PiCamera()
camera.resolution = (imageWidth, imageHeight)
camera.framerate = frameRate

print 'Setup the stream processing thread'
processor = StreamProcessor()

print 'Wait ...'
time.sleep(2)
captureThread = ImageCapture()

print 'Setup the watchdog'
watchdog = Watchdog()

print 'Setup the automatic movement'
autoMovement = AutoMovement()

# Run the web server until we are told to close
httpServer = SocketServer.TCPServer(("0.0.0.0", webPort), WebServer)
try:
    print 'Press CTRL+C to terminate the web-server'
    while running:
        httpServer.handle_request()
except KeyboardInterrupt:
    # CTRL+C exit
    print '\nUser shutdown'
finally:
    # Turn the motors off under all scenarios
    PBR.MotorsOff()
    print 'Motors off'
# Tell each thread to stop, and wait for them to end
running = False
captureThread.join()
processor.terminated = True
watchdog.terminated = True
autoMovement.terminated = True
processor.join()
watchdog.join()
autoMovement.join()
del camera
PBR.SetLed(True)
GPIO.output(40, False)
GPIO.output(38, False)
GPIO.output(36, False)
print 'Web-server terminated.'

I wonder if I can read the direction?
When I'm in the "Semi" mode and the Borg stops I need to push the Stop button before I can move backwards. When "Semi" is on I can't drive backwards.

Like ...

if direction "forwards" and distanceX <= 350 
MotorsOff()

Maybe end the "Semi mode"

                if distance2 <= 200:
                    PBR.MotorsOff()
                   end == SEMI_AUTO_MODE
                elif distance2 <= 400:
                    driveRight = 0.3 * maxPower
                    driveLeft = 0.3 * maxPower
                    PBR.SetMotor1(driveRight)
                    PBR.SetMotor2(-driveLeft)
piborg's picture

What you could do is make another couple of global variables for the users set speeds.
You can then do some simple tests to see what direction the DiddyBorg is set to move in.

Here are the basic bits to get a "direction" value which you can compare to presets:

# Add some new constants to the movement mode constants
DIR_STOP = 0
DIR_FORWARD = 1
DIR_REVERSE = 2
DIR_LEFT = 3
DIR_RIGHT = 4
DIR_SPIN_LEFT = 5
DIR_SPIN_RIGHT = 6
DIR_REV_LEFT = 7
DIR_REV_RIGHT = 8

# Add these to the global section
global userLeft
global userRight
userLeft = 0.0
userRight = 0.0

# Save the user set speeds in the "Motor power setting" section, above the AUTO_MODE check
userLeft = driveLeft
userRight = driveRight

# This block can be run before your distance tests to determine the direction
if (userLeft == 0.0):
    # Left stopped
    if (userRight == 0.0):
        # Both stopped
        direction = DIR_STOP
    elif (userRight > 0.0):
        # Turning left
        direction = DIR_LEFT
    else:
        # Reversing left
        direction = DIR_REV_LEFT
elif (userLeft > 0.0):
    # Left forward
    if (userRight == 0.0):
        # Turning right
        direction = DIR_RIGHT
    elif (userRight > 0.0):
        # Both forward
        direction = DIR_FORWARD
    else:
        # Spinning right
        direction = DIR_SPIN_RIGHT
else:
    # Left reverse
    if (userRight == 0.0):
        # Reversing right
        direction = DIR_REV_RIGHT
    elif (userRight > 0.0):
        # Spinning left
        direction = DIR_SPIN_LEFT
    else:
        # Both reverse
        direction = DIR_REVERSE

I will try :-)

The Raspberry Pi doesn’t have a power switch, which means I have to remember to properly shut it down every time I want to turn it off. So the idea is to add a Shutdown Switch to the Borg. The script alone runs fine but having trouble getting the code free of buggs when I try to put into the diddy script.

#!/usr/bin/env python
# coding: Latin-1

import time
import os
import RPi.GPIO as GPIO

#pins where the switch is connected
buttonPin1 = 33
GPIO.setmode(GPIO.BCM)
GPIO.setup(buttonPin1,GPIO.IN)

while True:
  #assuming script1 
  if (GPIO.input(buttonPin1)):
    #this is the script that will be called (as root)
    os.system(”sudo shutdown-h now)
piborg's picture

That is a good idea :)

It should be fairly easy to put that code into the diddyWeb.py script.
I think the simplest way would be to add these blocks of code:

At the bottom of the import list:

import os
import RPi.GPIO as GPIO

Before the "Timeout thread" code:

#pins where the switch is connected
global buttonPin1
buttonPin1 = 33
GPIO.setmode(GPIO.BCM)
GPIO.setup(buttonPin1,GPIO.IN)

And add the highlighted lines to the section "Stream delegation loop" which is run each time an image is taken:

    # Stream delegation loop
    def TriggerStream(self):
        global running
        global buttonPin1
        while running:
            if processor.event.is_set():
                time.sleep(0.01)
            else:
                yield processor.stream
                processor.event.set()
            # Check for button press
            if (GPIO.input(buttonPin1)):
                #this is the script that will be called (as root)
                os.system("sudo shutdown-h now")

Works fine, but I have to replace "sudo shutdown-h now" with "sudo init 0"
How to put in the code into the "metalJoy.py" script ?

piborg's picture

Adding the button code to metalJoy.py is fairly simple:

  1. Add the imports to the import list at the top
  2. Add the pin setup above the "Setup pygame ..." section
  3. Add the highlighted lines to the "loop indefinitely" section:
        # Loop indefinitely
        while running:
            # Check for button press
            if (GPIO.input(buttonPin1)):
               #this is the script that will be called (as root)
               os.system("sudo shutdown-h now")
            # Get the latest events from the system
            hadEvent = False
            events = pygame.event.get()
    

unfortunately it didn't work :-(

File "metaljoy.py", line 133
os.system("sudo shutdown-h now")
^
IndentationError: expected an indented block

piborg's picture

That line needs to be further to the right than the if line above it.

Put simply Python is sensitive to the use of tabs or spaces at the start of the line.
The error you are seeing is telling you it expected a line to start to the right of a line above.

Hi PiBorg.

I am planning on having a power (battery power) switch, and also a button to switch off the pi.

The on/off switch is easy.

But the shut down switch using the code above, what sort of switch would that need to be?

push to break (momentary) or
push to make (momentary)?

Thanks in advance

Chris

piborg's picture

You could use either, but I would suggest a push to make would be best.

You should wire it with a weak pull-down resistor like this:

Use a nice large value like 10 KΩ for R1, Vcc will be the 3V3 pin.

OK, I know I am going to sound stupid here..

But here goes.

Following on from your diagram, and also considering that the 6 pins are taken by the reverse board.

so cable to pin 17 - 3.3v
other end to switch.

2 wires attached to other side of switch.
one to pin 40 (GPIO 21)
other to pin 39 (Ground) via a low value resistor.

Then code which says when GPIO goes high run sudo shutdown now

does that sound right?

Cheers

Chris

piborg's picture

That is the basic idea, yes.

The only thing is that you want a large value resistor.
A larger value means less wasted power when the switch is on.
Too low and you might exceed the 50 mA maximum for the 3.3v pin.

For the resistor I would use at least 1 KΩ, which would use 3.3 mA when turned on.
Ideally something like 10 KΩ would be better, that would use 0.33 mA when turned on.

10k resister.

I have some of those

Now I'm playing around with some ideas.
I try to turn on light over the Web UI. Unfortunately i have now luck.
To put the buttons on the Web UI was easy, but so easy to activate :-(

I was trying ...

        elif getPath.startswith(’/Lighton'):
            # Lights on
                GPIO.output(38, True)

But it won't work.
It will be very nice if you could lock at this. Thanks

# Class used to implement the web server
class WebServer(SocketServer.BaseRequestHandler):
    def handle(self):
        global PBR
        global lastFrame
        global watchdog
        global movementMode
        # Get the HTTP request data
        reqData = self.request.recv(1024).strip()
        reqData = reqData.split('\n')
        # Get the URL requested
        getPath = ''
        for line in reqData:
            if line.startswith('GET'):
                parts = line.split(' ')
                getPath = parts[1]
                break

        watchdog.event.set()

        if getPath.startswith('/distances-once'):
            # Ultrasonic distance readings
            # Get the readings
            distance1 = int(UB.GetRawDistance1())
            distance2 = int(UB.GetRawDistance2())
            distance3 = int(UB.GetRawDistance3())
            distance4 = int(UB.GetRawDistance4())

            # Read and display time
            localtime = time.asctime( time.localtime(time.time()) )
            
            # Build a table for the values
            httpText = '<html><body><center><table border="0" style="width:60%"><tr>'
            if distance1 == 0:
                httpText += '<td width="25%"><center>None</center></td>'
            else:
                httpText += '<td width="25%%"><center>Front: %04d</center></td>' % (distance4/10)
        if distance4 == 0:
                httpText += '<td width="25%"><center>None</center></td>'
            else:
                httpText += '<td width="25%%"><center>Left: %04d</center></td>' % (distance1/10)
            if distance3 == 0:
                httpText += '<td width="25%"><center>None</center></td>'
            else:
                httpText += '<td width="25%%"><center>Right: %04d</center></td>' % (distance2/10)
            if distance2 == 0:
                httpText += '<td width="25%"><center>None</center></td>'
            else:
                httpText += '<td width="25%%"><center>Rear: %04d</center></td>' % (distance3/10)
            httpText += '</tr></table>'
            httpText += 'Temperature: {0:0.1f}°C -  Humidity: {1:0.1f}% '.format(temperature, humidity)
            #httpText += 'Wifi Signal Strength %.0f %%' % (wifiPercent)
            httpText += ' - %s' % (localtime)
            httpText += '</center></body></html>'
            self.send(httpText) 

        elif getPath.startswith('/semiAuto'):
              # Toggle Auto mode
              if movementMode == SEMI_AUTO_MODE:
                  # We are in semi-auto mode, turn it off
                  movementMode = MANUAL_MODE
                  httpText = '<html><body><center>'
                  httpText += 'Speeds: 0 %, 0 %'
                  httpText += '</center></body></html>'
                  self.send(httpText)
                  PBR.MotorsOff()
              else:
                  # We are not in semi-auto mode, turn it on
                  movementMode = SEMI_AUTO_MODE
                  httpText = '<html><body><center>'
                  httpText += 'Semi Mode'
                  httpText += '</center></body></html>'
                  self.send(httpText)

        elif getPath.startswith('/Auto'):
              # Toggle Auto mode
              if movementMode == AUTO_MODE:
                  # We are in auto mode, turn it off
                  movementMode = MANUAL_MODE
                  httpText = '<html><body><center>'
                  httpText += 'Speeds: 0 %, 0 %'
                  httpText += '</center></body></html>'
                  self.send(httpText)
                  PBR.MotorsOff()
              else:
                  # We are not in auto mode, turn it on
                  movementMode = AUTO_MODE
                  httpText = '<html><body><center>'
                  httpText += 'Auto Mode'
                  httpText += '</center></body></html>'
                  self.send(httpText)

        elif getPath.startswith('/cam.jpg'):
            # Camera snapshot
            lockFrame.acquire()
            sendFrame = lastFrame
            lockFrame.release()
            if sendFrame != None:
                self.send(sendFrame.tostring())

        elif getPath.startswith(’/Lighton'):
            # Lights on
                GPIO.output(38, True)
 
        elif getPath.startswith(’/Lightoff'):
            # Lights off
                GPIO.output(38, False)
 
        elif getPath.startswith('/off'):
            # Turn the drives off and switch to manual mode
            movementMode = MANUAL_MODE
            httpText = '<html><body><center>'
            httpText += 'Speeds: 0 %, 0 %'
            httpText += '</center></body></html>'
            self.send(httpText)
            PBR.MotorsOff()

        elif getPath.startswith('/set/'):
            # Motor power setting: /set/driveLeft/driveRight
            parts = getPath.split('/')
            # Get the power levels
            if len(parts) >= 4:
                try:
                    driveLeft = float(parts[2])
                    driveRight = float(parts[3])
                except:
                    # Bad values
                    driveRight = 0.0
                    driveLeft = 0.0
            else:
                # Bad request
                driveRight = 0.0
                driveLeft = 0.0
            # Ensure settings are within limits
            if driveRight < -1:
                driveRight = -1
            elif driveRight > 1:
                driveRight = 1
            if driveLeft < -1:
                driveLeft = -1
            elif driveLeft > 1:
                driveLeft = 1
                
            # Report the current settings
            percentLeft = driveLeft * 100.0;
            percentRight = driveRight * 100.0;
            httpText = '<html><body><center>'
            if movementMode == MANUAL_MODE:
                httpText += 'Speeds: %.0f %%, %.0f %%' % (percentLeft, percentRight)
            elif movementMode == SEMI_AUTO_MODE:
                httpText += 'Semi: %.0f %%, %.0f %%' % (percentLeft, percentRight)
            elif movementMode == AUTO_MODE:
                percentLeft = PBR.GetMotor2() * 100.0;
                percentRight = PBR.GetMotor1() * 100.0;
                httpText += 'Auto: %.0f %%, %.0f %%' % (percentLeft, percentRight)
            httpText += '</center></body></html>'
            self.send(httpText)
            
            # Set the outputs as long as we are not in auto mode
            if movementMode != AUTO_MODE:
                driveLeft *= maxPower
                driveRight *= maxPower
                PBR.SetMotor1(driveRight)
                PBR.SetMotor2(-driveLeft)

        elif getPath.startswith('/photo'):
            # Save camera photo
            lockFrame.acquire()
            captureFrame = lastFrame
            lockFrame.release()
            httpText = '<html><body><center>'
            if captureFrame != None:
                photoName = '%s/Photo %s.jpg' % (photoDirectory, datetime.datetime.utcnow())
                try:
                    photoFile = open(photoName, 'wb')
                    photoFile.write(captureFrame)
                    photoFile.close()
                    httpText += 'Photo saved to %s' % (photoName)
                except:
                    httpText += 'Failed to take photo!'
            else:
                httpText += 'Failed to take photo!'
            httpText += '</center></body></html>'
            self.send(httpText)                    

        elif getPath == '/':
            # Main page, click buttons to move and to stop
            httpText = '<html>\n'
            httpText += '<head>\n'
            httpText += '<script language="JavaScript"><!--\n'
            httpText += 'function Drive(left, right) {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' var slider = document.getElementById("speed");\n'
            httpText += ' left *= speed.value / 100.0;'
            httpText += ' right *= speed.value / 100.0;'
            httpText += ' iframe.src = "/set/" + left + "/" + right;\n'
            httpText += '}\n'
            httpText += 'function Lightoff() {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' iframe.src = ”/Lightoff";\n'
            httpText += '}\n'
            httpText += 'function Lighton() {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' iframe.src = ”/Lighton;\n’
            httpText += '}\n'
            httpText += 'function Off() {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' iframe.src = "/off";\n'
            httpText += '}\n'
            httpText += 'function Photo() {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' iframe.src = "/photo";\n'
            httpText += '}\n'
            httpText += 'function semiAuto() {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' iframe.src = "/semiAuto";\n'
            httpText += '}\n'
            httpText += 'function Auto() {\n'
            httpText += ' var iframe = document.getElementById("setDrive");\n'
            httpText += ' iframe.src = "/Auto";\n'
            httpText += '}\n'
            httpText += '//--></script>\n'
            httpText += '</head>\n'
            httpText += '<body>\n'
            httpText += '<iframe src="/stream" width="100%" height="500" frameborder="0"></iframe>\n'
            httpText += '<iframe id="setDrive" src="/off" width="100%" height="50" frameborder="0"></iframe>\n'
            httpText += '<center>\n'
            httpText += '<button onclick="Drive(-1,1)" style="width:200px;height:40px;"><b>Spin Left</b></button>\n'
            httpText += '<button onclick="Drive(1,1)" style="width:200px;height:40px;"><b>Forward</b></button>\n'
            httpText += '<button onclick="Drive(1,-1)" style="width:200px;height:40px;"><b>Spin Right</b></button>\n'
            httpText += '<br /><br />\n'
            httpText += '<button onclick="Drive(0,1)" style="width:200px;height:40px;"><b>Turn Left</b></button>\n'
            httpText += '<button onclick="Off()" style="width:200px;height:40px;"><b>Stop</b></button>\n'
            httpText += '<button onclick="Drive(1,0)" style="width:200px;height:40px;"><b>Turn Right</b></button>\n'
            httpText += '<br /><br />\n'
            httpText += '<button onclick="semiAuto(1)" style="width:200px;height:40px;"><b>Semi Auto</b></button>\n'
            httpText += '<button onclick="Drive(-1,-1)" style="width:200px;height:40px;"><b>Reverse</b></button>\n'
            httpText += '<button onclick="Auto(1)" style="width:200px;height:40px;"><b>Auto Mode</b></button>\n'
            httpText += '<br /><br />\n'
            httpText += '<button onclick=”Lighton()” style="width:200px;height:40px;"><b>Light On</b></button>\n'
            httpText += '<button onclick="Photo()" style="width:200px;height:40px;"><b>Save Photo</b></button>\n'
            httpText += '<button onclick=”Lightoff()” style="width:200px;height:40px;"><b>Light Off</b></button>\n'
            httpText += '<br /><br />\n'
            httpText += '<input id="speed" type="range" min="0" max="100" value="40" style="width:600px" />\n'
            httpText += '<br /><center> - Distances - </centre><br />\n'
            httpText += '<iframe src="/distances" width="100%" height="200" frameborder="0"></iframe>\n'            
            httpText += '</center>\n'
            httpText += '</body>\n'
            httpText += '</html>\n'
            self.send(httpText)
piborg's picture

This might be a problem cause by your text editor :)

Programming languages are very picky about the character symbols used in code.
In particular the quoting marks for Python and HTML need to be of the " or ' type.

At the moment you have:
httpText += ' iframe.src = ”/Lightoff";\n'
and
httpText += ' iframe.src = ”/Lighton;\n’
for example which look like the text editor has swapped some of the quotes and double-quotes with the fancier style ones.

What you wanted to have was:
httpText += ' iframe.src = "/Lightoff";\n'
and
httpText += ' iframe.src = "/Lighton";\n'

The symbols you want to use are: " '
The ones you want to watch out for are: “ ‟ ‘ ‛
These are often used by editors such as Word to make you text look nicer.

There are a fair number of lines with these rouge symbols, and they can be quite hard to spot.
I would suggest either using the find tool on your editor if it has one, otherwise turning the font size up helps a lot.
In the section of code you posted there appear to be bad quote symbols on lines: 101, 105, 200, 204, 240, 242.

Thanks so much :-)

These “ ‟ ‘ ‛ characters was killing all, now it works perfect.

Subscribe to Comments for &quot;Diddiborg with Ultraborg over Web UI&quot;