UltraBorg - Precision servo control with ultrasonic module support
This is our web based interface for controlling your UltraBorg from a phone or browser.
This example provides web-based access to UltraBorg using a web browser on both phones and desktops.
This allows you to both read ultrasonic distances and move servos via a Raspberry Pi.
We would also suggest you tune the servos using the Tuning GUI, but this is entirely optional.
Make sure your Raspberry Pi is connected to your router before running the scripts.
This example should work with both WiFi and Ethernet based connections.
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 sliders for each servo, some current servo position readings, and some distance readings.
To move a servo simply tap or click on the new position you want.
You can also drag the slider up and down, the servo should move whilst you are dragging it.
The values at the bottom are the distance readings in mm. A value of None indicates either:
The last motor settings are displayed below the image.
Replace
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/ultraborg-web
This example provides web-based access to UltraBorg using a web browser on both phones and desktops.
This allows you to both read ultrasonic distances and move servos via a Raspberry Pi.
Getting ready
Before using this script you should make sure your UltraBorg is working with the standard examples.We would also suggest you tune the servos using the Tuning GUI, but this is entirely optional.
Make sure your Raspberry Pi is connected to your router before running the scripts.
This example should work with both WiFi and Ethernet based connections.
Downloading the code from GitHub
In a terminal run the following commands:cd ~ git clone https://github.com/piborg/ultraborg-web.gitAlternatively you can download the ubWeb.py script file as text here.
Running the code
This is easiest done via SSH over the network.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 ~/ultraborg-web/ubWeb.py
Wait for the script to load, when it is ready it should say:
Press CTRL+C to terminate the web-server
Controlling your servos
Load your web browser on your phone or desktop.Once loaded enter your IP address in the address bar
You should be presented with sliders for each servo, some current servo position readings, and some distance readings.
To move a servo simply tap or click on the new position you want.
You can also drag the slider up and down, the servo should move whilst you are dragging it.
The values at the bottom are the distance readings in mm. A value of None indicates either:
- There is no ultrasonic module attached to that connector
- The distance you are measuring is extremely close, within a couple of centimeters
- The distance you are measuring is too far away, more than a few meters
- If changing between None and a value the ultrasonic module is struggling to "see" the object
The last motor settings are displayed below the image.
Alternative options
There are some other URLs you can use to get different functionality.Replace
192.168.0.198
in the below addresses with your IP address:http://192.168.0.198
- Standard controls, allows servo control and reads distanceshttp://192.168.0.198/servo
- Servo controls only, no distances are displayedhttp://192.168.0.198/distances
- Gets the distance readings without the servo controls, refreshes at a regular intervalshttp://192.168.0.198/distances-once
- Single set of distance readings, you may need to force-refresh to get new values
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 trydisplayRate
- The number of times per second the web browser will refresh the distance readingssliderHeight
- The height of the sliders in pixels, adjust this if the sliders are too tall or too short
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/ultraborg-web/ubWeb.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/ultraborg-web
The source code
#!/usr/bin/env python # coding: Latin-1 # Creates a web-page interface for UltraBorg # Import library functions we need import UltraBorg import time import sys import threading import SocketServer # Settings for the web-page webPort = 80 # Port number for the web-page, 80 is what web-pages normally use displayRate = 2 # Number of times to read the ultrasonic readings per second sliderHeight = 800 # The number of pixels high to make the sliders # Setup the UltraBorg global UB UB = UltraBorg.UltraBorg() # Create a new UltraBorg object UB.Init() # Set the board up (checks the board is connected) # Class used to implement the web server class WebServer(SocketServer.BaseRequestHandler): def handle(self): global UB # 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 if getPath.startswith('/distances-once'): # Ultrasonic distance readings # Get the readings distance1 = int(UB.GetDistance1()) distance2 = int(UB.GetDistance2()) distance3 = int(UB.GetDistance3()) distance4 = int(UB.GetDistance4()) # Build a table for the values httpText = '<html><body><table border="0" style="width:100%%"><tr>' if distance1 == 0: httpText += '<td width="25%%"><center><h2>None</h2></center></td>' else: httpText += '<td width="25%%"><center><h2>%04d</h2></center></td>' % (distance1) if distance2 == 0: httpText += '<td width="25%%"><center><h2>None</h2></center></td>' else: httpText += '<td width="25%%"><center><h2>%04d</h2></center></td>' % (distance2) if distance3 == 0: httpText += '<td width="25%%"><center><h2>None</h2></center></td>' else: httpText += '<td width="25%%"><center><h2>%04d</h2></center></td>' % (distance3) if distance4 == 0: httpText += '<td width="25%%"><center><h2>None</h2></center></td>' else: httpText += '<td width="25%%"><center><h2>%04d</h2></center></td>' % (distance4) httpText += '</tr></table></body></html>' self.send(httpText) elif getPath.startswith('/set/'): # Servo position setting: /set/servo/position parts = getPath.split('/') # Get the power levels if len(parts) >= 4: try: servo = int(parts[2]) position = float(parts[3]) except: # Bad values servo = 0 position = 0.0 else: # Bad request servo = 0 position = 0.0 # Ensure settings are within limits if position < -1: position = -1 elif position > 1: position = 1 # Set the new servo position if servo == 1: UB.SetServoPosition1(position) elif servo == 2: UB.SetServoPosition2(position) elif servo == 3: UB.SetServoPosition3(position) elif servo == 4: UB.SetServoPosition4(position) # Read the current servo positions position1 = UB.GetServoPosition1() * 100.0 position2 = UB.GetServoPosition2() * 100.0 position3 = UB.GetServoPosition3() * 100.0 position4 = UB.GetServoPosition4() * 100.0 # Build a table for the values httpText = '<html><body><table border="0" style="width:100%%"><tr>' httpText += '<td width="25%%"><center><h2>%.0f %%</h2></center></td>' % (position1) httpText += '<td width="25%%"><center><h2>%.0f %%</h2></center></td>' % (position2) httpText += '<td width="25%%"><center><h2>%.0f %%</h2></center></td>' % (position3) httpText += '<td width="25%%"><center><h2>%.0f %%</h2></center></td>' % (position4) httpText += '</tr></table></body></html>' self.send(httpText) elif getPath == '/': # Main page, move sliders to change the servo positions httpText = '<html>\n' httpText += '<head>\n' httpText += '<style>\n' httpText += ' input[type=range][orient=vertical]\n' httpText += ' {\n' httpText += ' writing-mode: bt-lr; /* IE */\n' httpText += ' -webkit-appearance: slider-vertical; /* WebKit */\n' httpText += ' padding: 0 0;\n' httpText += ' }\n' httpText += '</style>\n' httpText += '<script language="JavaScript"><!--\n' httpText += 'function SetServo(servo, position) {\n' httpText += ' var iframe = document.getElementById("setPosition");\n' httpText += ' position = position / 100.0;\n' httpText += ' iframe.src = "/set/" + servo + "/" + position;\n' httpText += '}\n' httpText += '//--></script>\n' httpText += '</head>\n' httpText += '<body>\n' httpText += '<table border="0" style="width:100%%;"><tr>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(1, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(2, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(3, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(4, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += '</tr></table>' httpText += '<iframe id="setPosition" src="/set/0/0" width="100%%" height="100" frameborder="0"></iframe>\n' httpText += '<br /><center><h2>Distances (mm)</h2></centre><br />\n' httpText += '<iframe src="/distances" width="100%%" height="100" frameborder="0"></iframe>\n' httpText += '</body>\n' httpText += '</html>\n' self.send(httpText) elif getPath == '/servo': # Alternative page with only servo control, move sliders to change the servo positions httpText = '<html>\n' httpText += '<head>\n' httpText += '<style>\n' httpText += ' input[type=range][orient=vertical]\n' httpText += ' {\n' httpText += ' writing-mode: bt-lr; /* IE */\n' httpText += ' -webkit-appearance: slider-vertical; /* WebKit */\n' httpText += ' padding: 0 0;\n' httpText += ' }\n' httpText += '</style>\n' httpText += '<script language="JavaScript"><!--\n' httpText += 'function SetServo(servo, position) {\n' httpText += ' var iframe = document.getElementById("setPosition");\n' httpText += ' position = position / 100.0;\n' httpText += ' iframe.src = "/set/" + servo + "/" + position;\n' httpText += '}\n' httpText += '//--></script>\n' httpText += '</head>\n' httpText += '<body>\n' httpText += '<table border="0" style="width:100%%;"><tr>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(1, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(2, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(3, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += ' <td width="25%%"><center>' httpText += ' <input type="range" min="-100" max="100" value="0" orient="vertical" style="width:100%%; height:%dpx" onchange="SetServo(4, this.value);" />\n' % (sliderHeight) httpText += ' </center></td>' httpText += '</tr></table>' httpText += '<iframe id="setPosition" src="/set/0/0" width="100%%" height="100" frameborder="0"></iframe>\n' httpText += '</body>\n' httpText += '</html>\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 = '<html>\n' httpText += '<head>\n' httpText += '<script language="JavaScript"><!--\n' httpText += 'function readDistances() {\n' httpText += ' var xmlhttp;\n' httpText += ' if (window.XMLHttpRequest) {\n' httpText += ' // code for IE7+, Firefox, Chrome, Opera, Safari\n' httpText += ' xmlhttp = new XMLHttpRequest();\n' httpText += ' } else {\n' httpText += ' // code for IE6, IE5\n' httpText += ' xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");\n' httpText += ' }\n' httpText += ' xmlhttp.onreadystatechange = function() {\n' httpText += ' var div = document.getElementById("readDistances");\n' httpText += ' var DONE = 4;\n' httpText += ' var OK = 200;\n' httpText += ' if (xmlhttp.readyState == DONE) {\n' httpText += ' if (xmlhttp.status == OK) {\n' httpText += ' div.innerHTML = xmlhttp.responseText;\n' httpText += ' } else {\n' httpText += ' div.innerHTML = "<center><h2>Failed reading distances (not running?)</h2></center>";\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 += '//--></script>\n' httpText += '</head>\n' httpText += '<body>\n' httpText += '<body onLoad="setTimeout(\'readDistances()\', %d)">\n' % (displayDelay) httpText += '<div id="readDistances"><center><h2>Waiting for first distance reading...</h2></center></div>\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)) # 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 True: httpServer.handle_request() except KeyboardInterrupt: # CTRL+C exit print '\nUser shutdown' print 'Web-server terminated.'