In this tutorial, you will learn how to set up the web-interface for your WaterScope, with the features described in the development page. We will also describe in more detail the implementation, the issues involved and the trade-offs of the following four subsystems:
We will also share the findings of our attempt to implement redirection to the WaterScope interface when connecting over WiFi.
This tutorial assumes that you have - if not the entire WaterScope kit - at least a Raspberry Pi 3 with Raspbian installed. For this tutorial, your Raspberry Pi will also need an Internet connection, and we'll need you to be able to execute commands on it, for example using SSH or directly with a screen, mouse and keyboard.
We also assume that you have an external device, such as a computer or a smartphone, that you want to control the interface from.
Note that we will set up the RPi to broadcasting a WiFi Access Point, so it will no longer be able to connect to a WiFi network, and will only be able to download from a wired connection. If this is not possible for you, you might want to defer setting up the Access Point till the end.
The first step of this tutorial is to connect your device to the WaterScope over a network.
You can choose any method of your liking to connect your device to the WaterScope. Some possibilities we've thought of are:
For our project, we chose to set up our WaterScope as a WiFi access point (option 3) - and we will walk you through how to do it - because it requires zero existing infrastructure (cables/adapters/network) and minimal configuration on the part of the connecting device: we think that this is the best option for deployment in the field in third-world countries.
The downside is then that since the WaterScope Raspberry Pi is broadcasting its own WiFi network, it can't connect to another at the same time. And similarly, you can't connect your device to the WaterScope and another network simultaneously - meaning that if you rely on WiFi to get Internet access, you can't while you're controlling the WaterScope.
If this might be an issue for you (and know a bit about networking), feel free to connect your WaterScope to your device any way you like: the rest of this tutorial just requires a network connection between the WaterScope and your device, and assumes that the WaterScope is set up at IP address 172.24.1.1
. If your IP address is different, then you need to replace 172.24.1.1
with the WaterScope's IP address in the project files:
find ./ -type f -exec sed -i -e 's/172.24.1.1/<actual WaterScope IP>/g' {} \;
We followed a tutorial by Phil Martin, which explains in detail the steps involved in setting up a WiFi access point on the Raspberry Pi.
In brief, here's what you need to do:
By default, the DHCP client daemon dhcpcd
is configured to listen on the wireless interface wlan0
, to negotiate with the access point of the wireless network to which the RPi connects, in order to automatically configure an IP address for the RPi.
However, we're going to be broadcasting an access point from wlan0
, so we need to tell dhcpcd
not to interfere with it. Opening up the dhcpcd
configuration file in an editor,
sudo nano /etc/dhcpcd.conf
add the following line at the bottom:
denyinterfaces wlan0
We want the WaterScope to always be configured with the IP address 172.24.1.1
on its wireless interface. To do this, edit the wlan0
entry in the interfaces configuration file:
sudo nano /etc/network/interfaces
so that it looks like this:
allow-hotplug wlan0
iface wlan0 inet static
address 172.24.1.1
netmask 255.255.255.0
network 172.24.1.0
broadcast 172.24.1.255
hostapd
configures the WiFi chip on the RPi to broadcast a WiFi access point.
First, install hostapd:
sudo apt-get install hostapd
We then configure hostapd to broadcast a WiFi access point with name Pi3-AP, and set up WPA2 wireless encryption by creating the hostapd configuration file
sudo nano /etc/hostapd/hostapd.conf
and pasting the following contents:
interface=wlan0
driver=nl80211
ssid=Pi3-AP # Name of the network
hw_mode=g # Use the 2.4GHz band
channel=6 # Use channel 6
ieee80211n=1 # Enable 802.11n
wmm_enabled=1 # Enable WMM
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40] # Enable 40MHz channels with 20ns guard interval
macaddr_acl=0 # Accept all MAC addresses
auth_algs=1 # Use WPA authentication
ignore_broadcast_ssid=0 # Require clients to know the network name
wpa=2 # Use WPA2
wpa_key_mgmt=WPA-PSK # Use a pre-shared key
wpa_passphrase=raspberry # The network passphrase
rsn_pairwise=CCMP # Use AES encryption algorithm
Finally, register this configuration file with hostapd
by editing
sudo nano /etc/default/hostapd
and adding the line
DAEMON_CONF="/etc/hostapd/hostapd.conf"
dnsmasq
is a combined DHCP and DNS server, which we use to automatically configure an IP address for your device when it connects to the WaterScope, and resolve URLs.
Backing up the default configuration file and creating a new one,
sudo mv /etc/dnsmasq.conf /etc/dnsmasq.orig.conf
sudo nano /etc/dnsmasq.conf
enter the following configuration:
interface=wlan0 # Use interface wlan0
listen-address=172.24.1.1 # Explicitly specify the address to listen on
bind-interfaces # Bind to the interface to make sure we aren't sending things elsewhere
server=8.8.8.8 # Forward DNS requests to Google DNS
domain-needed # Don't forward short names
bogus-priv # Never forward addresses in the non-routed address spaces.
dhcp-range=172.24.1.50,172.24.1.150,12h # Assign IP addresses between 172.24.1.50 and 172.24.1.150 with a 12 hour lease time
address=/raspberrypi/pi/waterscope/scope/172.24.1.1 # Forward domains raspberrypi, pi, waterscope and scope to the RPi's IP address. This means you can type http://pi in your browser and get sent to the WaterScope interface.
In order to share the Raspberry Pi's Internet connection, we have to enable packet forwarding.
To do this, open the sysctl.conf
configuration file
sudo nano /etc/sysctl.conf
and add the line
net.ipv4.ip_forward=1
Then, configure port forwarding between the wlan0
and eth0
interfaces with the following commands:
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
We then need to save those rules:
sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
and enable loading them at start-up time by editing
sudo nano /etc/rc.local
and adding the line just above exit 0
:
iptables-restore < /etc/iptables.ipv4.nat
Reboot the Raspberry Pi:
sudo reboot
and you should be able to connect to the Pi3-AP network from your computer or smartphone.
Now that the WiFi connection is set up, we turn to installing the WaterScope interface.
(diagram by Antoine)
The WaterScope interface is a webpage written in HTML, CSS and Javascript, which sends requests to URL end-points exposing the WaterScope's functions, served by Flask, a Python HTTP server framework, and handled by our script main.py
.
After completing this section, you will have installed and be able to access the WaterScope interface from your computer or smartphone. You will understand our implementation of the client-server interface, and be aware of the design issues and trade-offs. For an in-depth explanation of the video stream and our UI design in the web-page, see Kai Song's report.
Our web-server is written in Python 3 and depends on the flask
server framework. Communication with the fergboard and the Arduino depends on the smbus
and nanpy
libraries - even if you don't use the fergboard or the Arduino, we need to install those libraries for the server to load properly.
sudo apt-get install pip3
sudo pip3 install flask smbus nanpy
The streaming is done by the mjpg-streamer
program from jacksonliam's fork, which comes with a plugin for the Pi camera. To install it, here are the steps:
sudo apt-get install cmake libjpeg8-dev
wget https://github.com/jacksonliam/mjpg-streamer/archive/master.zip
unzip master.zip
cd mjpg-streamer-master/mjpg-streamer-experimental/
make
sudo make install
If you haven't already done so, download and unzip the project files from our GitHub repo:
wget https://github.com/ard61/gm2-waterscope/archive/master.zip
unzip master.zip
Then, to launch the server, just execute the main.py script. Sudo (administrator privileges) is required in order to have the server listen on port 80:
cd gm2-waterscope-master/src/server
sudo python3 main.py
That's it! If you now connect to the Pi3-AP network, open your browser and go to http://pi/, you should see the WaterScope interface!
If you want the server to start up automatically when the Raspberry Pi starts, add the following line to the startup configuration file /etc/rc.local
:
sudo sh -c "cd /home/pi/gm2-waterscope-master/src/server && exec python3 main.py"
src/server/main.py
starts up a Flask server.
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, threaded=True)
This server then exposes the WaterScope's functions as URL endpoints, in an HTTP REST interface. When the client sends an HTTP GET request to each of these endpoints, the relevant handler function is called by Flask, with the request parameters. For example, when a request is sent to the endpoint /move
, the handler function move()
is called:
@app.route('/move', methods=['GET'])
def move():
if not motors.connected:
motors.connect()
get_args = flask.request.args
x = get_args.get('x', default=0, type=int)
y = get_args.get('y', default=0, type=int)
z = get_args.get('z', default=0, type=int)
motors.move(x, y, z)
return flask.Response(status="200 OK")
which sends a command to the Fergboard to move the motors, and sends the HTTP response 200 OK
if the command has succeeded. If the command fails, an exception is raised, which causes Flask to send the HTTP response 500 Internal Server Error
, thus signalling to the client that its request was not completed.
We chose this type of interface because:
Here is a complete list of accessible end-points:
/index
: return the WaterScope interface webpage, found in src/server/templates/index.html
./start_stream
: (re)start the mjpg-streamer instance, with the camera parameters sent in the request, and wait for the streamer to be able to handle requests before sending the responsewidth
- stream widthheight
- stream heightfps
- stream frame ratesharpness
- camera sharpnesscontrast
- camera contrastbrightness
- camera brightnesssaturation
- camera saturation/capture
: stop the mjpg-streamer instance, take a photo at maximum resolution using raspistill with the camera parameters of the request (as before, error checking and correction is done on the parameters), restart the streamer, and wait for the streamer to be available before returning./static/capture.jpg
: the URL at which the captured image is made available for download. We programmed the WaterScope webpage to download this image automatically when /capture
returns. /move
: move the fergboard by the specified x, y, z amounts.x, y, z
should be sent as HTTP GET arguments./led
: Switch the Arduino-connected LED on or off.led = on|off
should be sent as an HTTP GET argument./microswitch
: get the value (on|off
) of the Arduino-connected microswitchprev_state
is given, then the server blocks until the state differs from prev_state
before returning the response. This allows us to use the technique of HTTP Long-Polling, whereby the response is deferred until a state change happens, in order to get near-instantaneous responsiveness to change without needing to generate a large number of requests, as we would by continuously polling the server.Note that although the only client we use to send requests to the end-points is the WaterScope webpage, this is a complete HTTP API that can handle requests from any client. For example, you can easily restart the stream from your device using the command-line!
curl http://pi/start_stream
Also, note that all those functions are exposed without any encryption or access control at the moment, so should not be deployed as-is. To enhance security, a solution would be to provide two access modes: an "Expert" mode and a "Standard" mode, where the "Standard" mode only has access to the stream - see Kai Song's report.
The client webpage src/server/templates/index.html
then simply incorporates logic to send requests to the server's endpoints when the relevant user action triggers an event. This is done in JavaScript, using the library JQuery
to send the asynchronous requests (AJAX) to the server. For example, the following code sends
We chose JQuery because it is recommended industry-wide, and greatly improves readability of JavaScript code. Since we host the library on the WaterScope at /src/server/static/jquery_3.2.1.min.js
, no internet access is needed to use it.
(diagram by Antoine)
Interfacing with the fergboard is managed in the Python script src/server/fergboard.py
, which i adapted from Fergus Riche's motor control utility motor.py.
This uses the Python smbus
library to send a sequence of bytes to the fergboard via the Raspberry Pi's I2C interface, and requires this I2C interface to be enabled. You can do so using the raspi-config utility:
sudo raspi-config
We set up the Arduino with the nanpy
firmware, which transforms the Arduino into a slave directly controlled by the Raspberry Pi. Then, in arduino.py
, we send commands to the Arduino using the nanpy
library (which you should have installed when setting up the server, if you didn't, do it now).
To install the nanpy firmware on your Arduino, do the following:
Important! This will remove the current firmware from your Arduino.
Important! This requires you to have the Arduino IDE downloaded and installed on your computer.
wget https://github.com/nanpy/nanpy-firmware/archive/master.zip
unzip master.zip
cd nanpy-firmware-master
./configure.sh
Then, you need to find the "sketchbook" directory of the Arduino IDE and copy the ./Nanpy
directory to it:
cp -R Nanpy <path_to_sketchbook_directory>
Finally, connect the Arduino to your computer, start the Arduino IDE, open the menu Sketchbook/Nanpy and select "Upload".
That's it, you're done!
Ever noticed how when you connect to a WiFi network that requires some sign-in, like in a hotel, the sign-in page opens automatically? Wouldn’t it be awesome if the WaterScope could do that too?
What happens behind the scenes is that the access point, instead of sending you to the page you want (such as google.com
) right away, sends back an HTTP 302 REDIRECT
response and redirects you to their log-on site. On connecting to a network, modern devices automatically probe whether the Internet is available by querying a server: if the response that comes back is HTTP 302 REDIRECT
, they know to automatically open a browser to the log-on page.
So, to emulate this functionality, the gist of what would be needed is
iptables
, or DNS forwarding e.g. using the DNS server bind9
, in order to send all incoming requests to a server on the Raspberry PiHTTP 302 REDIRECT
response to those requestsWe attempted to implement this using the open-source "captive portal" software nodogsplash. However, the tutorial we attempted to use was outdated, and when we tested it, it didn't work.
Also, note that redirecting the client to the WaterScope interface by default will completely negate the advantage of sharing the Raspberry Pi's Internet connection, a feature we found extremely useful during development. This might not be of great importance in a field deployment where there is no internet anyway, however, it might be undesirable in a laboratory setting.
Many thanks to: