Overview This is a tutorial about location and context-aware services, using a Raspberry Pi and a GrovePi+. You will learn to: perform some basic relative location measurements using presence and proximityl consume location-based servicesl produce a context-aware systeml We will continue with pair programming in these practical sessions. You will need to be in a group of two people (with no more than one group of three people). Each person in the group will alternate between the different roles of driver and navigator. Connecting your Pi Each pair will need to sit at a workstation which is fitted with a KVM switch (keyboard, video display, and mouse) and: a green box (containing a Raspberry Pi 2 model B with a micro-SD card with Raspbianl pre-installed, a GrovePi+ board, and a set of GrovePi+ peripherals) a power supply (mains to 5V adapter with a micro-USB connector)l an HDMI cable and HDMI female-female adapterl an Ethernet cable and Ethernet female-female adapterl a USB extension leadl Make sure the green Raspberry Pi board is connected to the blue GrovePi board (carefully do so if not), and the micro-SD card is in the slot on the underside of the Pi. Connect: HDMI cable (through a HDMI female-female adapter, to the HDMI KVM cable)1. USB keyboard and mouse connection (through a USB male-A to female-A extension cable, to the2. USB KVM cable) Ethernet cable (through a Ethernet female-female adapter, to the Ethernet cable on the desk)3. Plug in the power adaptor and connect the micro-USB cable to the Pi.4. The Pi will now power up. Press the KVM switch once if you don't see anything. Setting up your Pi for the GrovePi+ DO NOT do these steps if your Pi has already been set up for the GrovePi+ (e.g. if the login credentials above worked, your Pi should already be set up). If you are asked to log-in, the default credentials are pi and password raspberry.1. In LXTerminal, run the configuration tool with sudo raspi-config.2. Choose Change User Password. Type the password ubicomp (nothing will be displayed as you3. type) then Enter, type the password again and Enter. Choose Advanced Options and Hostname. Delete raspberrypi and type the name on your label.4. Press Enter to accept. Choose Advanced Options, Device Tree, Yes.5. Choose Advanced Options, I2C, Yes, Ok, Yes, Ok.6. Choose Finish, and do not accept the prompt when asked to reboot.7. Edit the list of kernel modules that the operating system will load when it boots with sudo nano8. /etc/modules, add two lines, one with i2c-bcm2708 and another with i2c-dev Install i2c-tools: sudo apt-get install i2c-tools9. Allow the user pi to access the I^2^C bus: sudo usermod -a -G i2c pi10. Now reboot: sudo reboot11. Log in (username pi, password ubicomp).12. Get the Java library for I/O: curl -s get.pi4j.com | sudo bash13. Get and build the library to access the GrovePi+: cd ~; git clone14. https://github.com/digitalinteraction/jgrove.git; cd jgrove; sudo apt-get install ant; ant Check the communication with the GrovePi+ board is working by running the input test15. command (you should see a stream of 1): java -jar /home/pi/jgrove/dist/lib/jgrove.jar Development tools Ensure you have the latest version of the GrovePi+ Java library built: cd ~/jgrove && git pull && ant If you want to use the graphical IDE Geany: Start the graphical environment: startx1. If Geany is not already installed: sudo apt-get install geany2. Run Geany: geany &3. Open a .java file (or create a new one)4. Go to Build, Set Build Commands5. Set Java Commands/Compile to: javac -cp .:/home/pi/jgrove/dist/lib/jgrove.jar "%f"6. Set Execute to: java -cp .:/home/pi/jgrove/dist/lib/jgrove.jar "%e"7. Alternatively, if you want to run javac and java from the command line, you must first set the CLASSPATH environment variable: export CLASSPATH=.:/home/pi/jgrove/dist/lib/jgrove.jar ...and remember that you can both compile and, if successful, run with a single command line (where $CLASSNAME is the class and file name): javac $CLASSNAME.java && java $CLASSNAME If you get UnsupportedClassVersionError, either use javac -source 6 -target 6, or fix the version switching: sudo update-java-alternatives -s `update-java-alternatives -l | tail -n 1 | cut -f 1 -d " "` Presence Detector Edit a new file at /home/pi/practical3/Presence.java (if you're at the terminal, you can run: mkdir ~/practical3; cd ~/practical3; nano Presence.java). Start with a class Presence, which has a public static void main(String[] args) method. We will need to add imports for grovepi.GrovePi, grovepi.Pin, grovepi.PinMode, and all sensor classes with grovepi.sensors.*. In the main() method, we need to obtain an instance of a GrovePi class: GrovePi grovePi = new GrovePi(); ...rather than use one of the (slightly neater) wrappers in the sensors package, we'll just directly read the analogue values from A0. Let's make sure it's set up as an input: grovePi.pinMode(Pin.ANALOG_PIN_0, PinMode.INPUT); ...now write a loop to continuously poll the state of the pin using grovePi.analogRead(Pin.ANALOG_PIN_0), which returns an int, and print the result with System.out.println(). When your code successfully compiles and runs, connect a Grove light sensor to port A0 with a cable. Covering the sensor should change the output. Now modify your code to detect a significant change in the value and output "PRESENCE". One approach would be to check the difference (e.g. using Math.abs()) between the current value and the last time you signalled "PRESENCE". Once this works, modify your code to signal "ABSENCE" when the time since the last "PRESENCE" is more than some threshold (e.g. 10 seconds). You can get the current system time in milliseconds (1/1000th of a second) with long System.currentTimeMillis(). Now add an LED output that's turned on with "PRESENCE", and off with "ABSENCE". If you've completed this part quickly, you could try adding a sound sensor instead. Use a threshold value for "noise" (rather than a difference), and use this to trigger "PRESENCE". Proximity Detector If you had time for this in the last practical, some of this may already be familiar to you. Ultrasonic ranging works by transmitting a sound pulse at a frequency beyond human hearing, then timing how long it takes to appear at the receiver. Create a new source file Distance.java, obtain the GrovePi instance in the same way as last time, then get an object for range sensing: UltrasonicRangerSensor rangeSensor = grovePi.getDeviceFactory().createUltraSonicSensor(Pin.DIGITAL_PIN_4); Write a sampling loop to measure and display the distance to the nearest object (int rangeSensor.getDistance()). Connect an ultrasonic range sensor to pin D4 and run your program. Move your hand to different distances to see the range change. Add two thresholds to your code, below the lower one (e.g. 50cm) print "AT", between the two print "NEAR", above the upper one (e.g. 150) print "FAR". If you've completed this part quickly, you could try adding LEDs to indicate the state. For 'AT', light just the red LED (D3), for "NEAR", light just the green LED (D5), for "FAR", light just the blue LED (D6). Geolocation Service Edit a new file at /home/pi/practical3/Location.java. Add the imports: import java.net.*; import java.io.*; In the class Location, add a new method to fetch the contents of an HTTP page: public static String fetchUrl(String address) { try { URLConnection connection = new URL(address).openConnection(); connection.connect(); InputStream in = connection.getInputStream(); try { java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } finally { in.close(); } } catch (Exception e) { e.printStackTrace(); } return null; } Let's fetch the geolocation data for our address from http://ipinfo.io/. public static void main(String[] args) { String result = fetchUrl("http://ipinfo.io/json"); System.out.println(result); } Compiling and running your code should show a JSON object. External services Let's get some input from an external weather service. There are plenty of APIs out there, but one that doesn't require an API key is http://api.met.no/weatherapi/. First, modify your code to get the latitude and longitude rather than city name: String location = fetchUrl("http://ipinfo.io/loc"); ...you should now see an output similar to this: 54.9733,-1.6140 You can split this into the latitude and longitude elements with: String[] location = result.trim().split(","); Now, let's construct the full URL for a weather forecast: String url = "http://api.met.no/weatherapi/locationforecast/1.9/?"; url += "lat=" + location[0] + ";"; url += "lon=" + location[1] + ";"; String weather = fetchUrl(url); System.out.println(weather); Running the code now should display a weather forecast in XML. To parse the XML in Java, add the imports: import javax.xml.parsers.*; import org.w3c.dom.*; ...and the code to main() to parse the XML into a DOM object: try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(weather.getBytes())); } catch (Exception e) { e.printStackTrace(); } Within the try block, add extract all temperature nodes: NodeList nodes = doc.getElementsByTagName("temperature"); Now take the first temperature measurement: Element element = (Element)nodes.item(0); String temperatureString = element.getAttribute("value"); float temperature = Float.parseFloat(temperatureString); ...and display it: System.out.println("Temperature now: " + temperature); Running the code now should display the current outdoor temperature in degrees C. Only if you have time, extend the code to display all the temperature elements (rather than just the first one), and explore other elements of the forecast. Only if you still have time, add a function your code to map a temperature range (0-40 degrees C) to fading LEDs: At (or below) zero, begin with the blue LED (D6) on full, and the green (D5) and red LED (D3) off.l As the temperature increases to 10, fade up green to maximum (keeping blue on).l As the temperature increases to 20, fade down blue to off (leaving just green).l As the temperature increases to 30, fade up red to maximum (keeping green on).l As the temperature increases to 40, fade down green to off (leaving just red).l ...run the program through a test loop. Once you're happy, set it with the actual temperature. Turning off your Pi Once you are finished, you can safely power down your Pi, by running: sudo halt -p