4Borg - DiddyBorg's smaller brother
This is our web based interface for controlling your 4Borg from a phone or browser.

This example provides web-based access to a 4Borg using a web browser on both phones and desktops.
The interface streams images from the Raspberry Pi camera, movement can be controlled from the buttons.
You will need to also perform the optional camera setup so the script can stream images.
You will not need the option joystick setup for this example.
You will probably want to use a WiFi dongle for the best results.
Make sure your WiFi is working and connected to you router before running the scripts.
First find out what your IP address is using the
It should be 4 numbers separated by dots, e.g.
We will need this to access the controls, so make a note of it.
Next run the script using:
Wait for the script to load, when it is ready it should say:
Once loaded enter your IP address in the address bar
You should be presented with the camera image, some text, some buttons, and a slider.

To move click a movement button, such as Forward.
To stop moving click the Stop button.
To change the speed, drag the slider before clicking a movement button.
Left is slower, right is faster.
The last motor settings are displayed below the image.
We think sharing software is awesome, so we encourage others to extend and/or improve on this script to make it do even more :)
To assist that we have uploaded the files as a self-contained project on GitHub:
https://github.com/piborg/diddyborg-web

This example provides web-based access to a 4Borg using a web browser on both phones and desktops.
The interface streams images from the Raspberry Pi camera, movement can be controlled from the buttons.
Getting ready
Before using this script you should make sure your 4Borg is working with the standard examples.You will need to also perform the optional camera setup so the script can stream images.
You will not need the option joystick setup for this example.
You will probably want to use a WiFi dongle for the best results.
Make sure your WiFi is working and connected to you router before running the scripts.
Downloading the code from GitHub
In a terminal run the following commands:cd ~ git clone https://github.com/piborg/diddyborg-web.gitAlternatively you can download the 4BorgWeb.py script file as text here.
Running the code
This is easiest done via SSH over the WiFi.First find out what your IP address is using the
ifconfig
command.It should be 4 numbers separated by dots, e.g.
192.168.0.198
We will need this to access the controls, so make a note of it.
Next run the script using:
sudo ~/diddyborg-web/4BorgWeb.py
Wait for the script to load, when it is ready it should say:
Press CTRL+C to terminate the web-server
Controlling your 4Borg
Load your web browser on your phone or desktop.Once loaded enter your IP address in the address bar
You should be presented with the camera image, some text, some buttons, and a slider.

To move click a movement button, such as Forward.
To stop moving click the Stop button.
To change the speed, drag the slider before clicking a movement button.
Left is slower, right is faster.
The last motor settings are displayed below the image.
Additional settings
There are some settings towards the top of the script which may be changed to adjust the behaviour of the interface:webPort
- Sets the port number, 80 is the default port web browsers will tryimageWidth
- The width of the captured camera image, higher will need more network bandwidthimageHeight
- The height of the captured camera image, higher will need more network bandwidthframeRate
- The number of images taken from the camera each second by the Raspberry PidisplayRate
- The number of times per second the web browser will refresh the camera imagephotoDirectory
- The directory that photos are saved to when taken
Auto start at boot
To get the web interface to load on its own do the following:- Open the Cron table using
crontab -e
- Add a cron job to the bottom of the file using the following line:
@reboot sudo /home/pi/diddyborg-web/4BorgWeb.py
- Save the file
- Close the file
Going further
This is just a simple example of how a web interface can be made using Python on the Raspberry Pi to control a robot.We think sharing software is awesome, so we encourage others to extend and/or improve on this script to make it do even more :)
To assist that we have uploaded the files as a self-contained project on GitHub:
https://github.com/piborg/diddyborg-web
The source code
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 | #!/usr/bin/env python # coding: Latin-1 # Creates a web-page interface for 4Borg # Import library functions we need import PicoBorgRev import time import sys import threading import SocketServer import picamera import picamera.array import cv2 import datetime # Settings for the web-page webPort = 80 # Port number for the web-page, 80 is what web-pages normally use imageWidth = 240 # Width of the captured image in pixels imageHeight = 180 # Height of the captured image in pixels frameRate = 10 # Number of images to capture per second displayRate = 2 # Number of images to request per second photoDirectory = '/home/pi' # Directory to save photos to # Global values global PBR global lastFrame global lockFrame global camera global processor global running global watchdog running = True # 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 = 8.4 # Total battery voltage to the PicoBorg Reverse (change to 9V if using a non-rechargeable battery) voltageOut = 6.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 () # Class used to implement the web server class WebServer(SocketServer.BaseRequestHandler): def handle( self ): global PBR global lastFrame global watchdog # 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( '/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 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>' httpText + = 'Speeds: %.0f %%, %.0f %%' % (percentLeft, percentRight) httpText + = '</center></body></html>' self .send(httpText) # Set the outputs driveLeft * = maxPower driveRight * = maxPower PBR.SetMotor1( - driveLeft) PBR.SetMotor2(driveRight) 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 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 + = '//--></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:100px;"><b>Spin Left</b></button>\n' httpText + = '<button onclick="Drive(1,1)" style="width:200px;height:100px;"><b>Forward</b></button>\n' httpText + = '<button onclick="Drive(1,-1)" style="width:200px;height:100px;"><b>Spin Right</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<button onclick="Drive(0,1)" style="width:200px;height:100px;"><b>Turn Left</b></button>\n' httpText + = '<button onclick="Drive(-1,-1)" style="width:200px;height:100px;"><b>Reverse</b></button>\n' httpText + = '<button onclick="Drive(1,0)" style="width:200px;height:100px;"><b>Turn Right</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<button onclick="Off()" style="width:200px;height:100px;"><b>Stop</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<button onclick="Photo()" style="width:200px;height:100px;"><b>Save Photo</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<input id="speed" type="range" min="0" max="100" value="100" style="width:600px" />\n' httpText + = '</center>\n' httpText + = '</body>\n' httpText + = '</html>\n' self .send(httpText) elif getPath = = '/hold' : # Alternate page, hold buttons to move (does not work with all devices) 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 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 + = '//--></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 onmousedown="Drive(-1,1)" onmouseup="Off()" style="width:200px;height:100px;"><b>Spin Left</b></button>\n' httpText + = '<button onmousedown="Drive(1,1)" onmouseup="Off()" style="width:200px;height:100px;"><b>Forward</b></button>\n' httpText + = '<button onmousedown="Drive(1,-1)" onmouseup="Off()" style="width:200px;height:100px;"><b>Spin Right</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<button onmousedown="Drive(0,1)" onmouseup="Off()" style="width:200px;height:100px;"><b>Turn Left</b></button>\n' httpText + = '<button onmousedown="Drive(-1,-1)" onmouseup="Off()" style="width:200px;height:100px;"><b>Reverse</b></button>\n' httpText + = '<button onmousedown="Drive(1,0)" onmouseup="Off()" style="width:200px;height:100px;"><b>Turn Right</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<button onclick="Photo()" style="width:200px;height:100px;"><b>Save Photo</b></button>\n' httpText + = '<br /><br />\n' httpText + = '<input id="speed" type="range" min="0" max="100" value="100" style="width:600px" />\n' httpText + = '</center>\n' httpText + = '</body>\n' httpText + = '</html>\n' self .send(httpText) elif getPath = = '/stream' : # Streaming frame, set a delayed refresh displayDelay = int ( 1000 / displayRate) httpText = '<html>\n' httpText + = '<head>\n' httpText + = '<script language="JavaScript"><!--\n' httpText + = 'function refreshImage() {\n' httpText + = ' if (!document.images) return;\n' httpText + = ' document.images["rpicam"].src = "cam.jpg?" + Math.random();\n' httpText + = ' setTimeout("refreshImage()", %d);\n' % (displayDelay) httpText + = '}\n' httpText + = '//--></script>\n' httpText + = '</head>\n' httpText + = '<body onLoad="setTimeout(\'refreshImage()\', %d)">\n' % (displayDelay) httpText + = '<center><img src="/cam.jpg" style="width:640;height:480;" name="rpicam" /></center>\n' httpText + = '</body>\n' httpText + = '</html>\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() # 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 processor.join() watchdog.join() del camera PBR.SetLed( True ) print 'Web-server terminated.' |
