Monday Jan 21, 2013

Building an SD Card Image For a Raspberry Pi Java Hands On Lab

Building an SD Card Image For a Raspberry Pi Hands On Lab Last year we ran a very successful hands on lab for developers at Devoxx in Antwerp.  The concept was to have 40 people in a room, give them all a Raspberry Pi, cables and a pre-configured SD card and get them to build cool JavaFX apps.  One of the things I had to do was organise all the equipment and make a suitable image for the SD card.  As this was before Oracle had announced the early access of JDK8 for the Raspberry Pi with hard float support we used the soft float of Java SE embedded version 7 for ARMv6 and a non-production build of JavaFX.  As we're repeating this lab at JFokus in a couple of weeks I thought it might be useful to write up how I built the SD image as there may well be people who want to run something similar.

Hardware Setup

To simplify matters from a hardware perspective (and to make the lab economically viable) we decided not to provide attendees with monitors, keyboards and mice.  All interaction with the Pi would need to be via the network connection.  To eliminate the need for two power outlets per attendee we also decided to use USB Y power cables that can draw power from two USB ports on the attendee's laptop.  Since USB ports are rated at 500mA two would give us more than the minimum 700mA required for the Pi (as a side note I've found that you can happily boot a Pi from one USB port on a MacBook Pro - although that is without any USB peripherals attached to the Pi). 

With no monitor or USB keyboard/mouse all interaction would be via the network connection.  Again, to simplify the infrastructure we provided all attendees with an ethernet cross-over cable.  One end is connected to the ethernet port on the attendee's laptop, the other to the ethernet port on the Raspberry Pi.

The hardware setup is shown in the diagram below:
Machine setup

Software Setup

For this part I'll describe the setup necessary for the Rasbian distro so we can use the new JDK8 EA build.  One issue with this is that the JavaFX libraries included no longer supprt rendering via X11.  Since the ARM port of JavaFX is aimed at embedded devices like parking meters and point-of-sale devices we don't expect these to use an X based desktop underneath.  Now that rendering is only supported directly to the framebuffer (which gives us significantly better performance) projecting the JavaFX applications back to the attendees laptop via VNC will no longer work.  Although there is a package calld fbvnc this will not work as the rendering on the Pi does not use areas of memory that are accessible this way.

Here is a step-by-step guide:
  1. Install the Rasbian distro on an SD card.  I use 4Gb SanDisk class 4 cards which provide enough space, work with the Pi and are cheap.  When you need to replicate a significant number of cards, smaller is quicker.  To install the distro either use DiskImager (on Windows) or a simple dd command on Linux or Mac (detailed instructions can be found on the Raspberry Pi web site).
  2. Put this in a Pi and boot.  I do this with a monitor and USB keyboard connected to make life simpler.  When the Pi has finished booting you will be presented with a screen as shown:
menu
  1. Move down to expand_rootfs and select this by pressing RETURN.  This will expand the filesystem to fill the available space on the SD card.
  2. Select overclock and accept the notice about potentially reducing the lifetime of your Pi.  Remember: live fast, die young.  Seriously, though, given the cost of the Pi and the fact that the manufacturers will honour the warranty for anything up to a 1GHz clockrate and I think this is pretty safe.  I go for the medium setting of 900MHz.  This has not given me any issues, although you may want to go higher or lower as preferred.
  3. Select SSH.  I think this is enabled by default, but just to make sure select it.
  4. Lastly on this screen select update.  This will update any packages necessary in the Linux distribution.  Obviously for this you will need your Pi connected to a network where it can find the internet settings via DHCP, etc.
  5. Tab to 'Finish', hit RETURN and you will be dropped into a shell.
  6. Being an old school UNIX hacker I really don't like sudo, so the first thing I do is sudo bash and then set a password for root so I can su whenever I need to.
  7. There is a user account, pi, that is created by default.  For our labs I create a separate account for attendees to login as.  Use something like useradd -u 1024 -d /home/lab -m -s /bin/bash lab.  Remember to set the user's password: passwd lab.
  8. Since we want things to be as simple as possible we setup a DHCP server on the Pi.  Before we do that we need the Pi to use a static IP address.  Edit the /etc/network/interfaces file and change

  9. iface eth0 inet dhcp

    to

    iface eth0 inet static
        address 10.0.0.8
        gateway 10.0.0.254    

        netmask 255.255.255.0


    In these settings I've used a class A private network which is the same as the one I use in my office.  This makes things easy, as I can also configure the gateway so that the Pi can access the internet which will be required for the next stages.  If you are using a class C private network (like 192.168.0.X) you will need to change this accordingly.

    At this point I reboot the machine with the monitor and keyboard disconnected and switch to doing everything over SSH.

  10. Login over the network using SSH (use either the lab account or the pre-installed pi one) and su to root. 
  11. Install the DHCP server package, apt-get install isc-dhcp-server
  12. Configure DHCP by editing the /etc/dhcp/dhcpd.conf file.  Under the comment line 
     # This is a very basic subnet declaration.
    Add

    subnet 10.0.0.0 netmask 255.255.255.0 {
        range 10.0.0.164 10.0.0.170;
    }

    This will provide an IP address in the range from 164 to 170. Having seven available addresses is a bit of overkill, but gives us some flexibility (change your IP addresses as necessary).  In addition you must comment out these two lines at the start of the file:

    option domain-name "example.org";
    option domain-name-servers ns1.example.org, ns2.example.org;

    This was one of the things that changed between the soft float Wheezy distro and the hard float Raspbian distro.  It took me ages to figure out why the DHCP server would not work properly on Raspbian.  When I used the soft float distro all I needed to do was add the subnet and range definition.  On Raspbian the DHCP server refused to serve IP addresses even though the log messages seemed to indicate that it was fine.  After I did a diff on the dhcp.conf files from both I noticed the two lines that had been uncommented.  I commented them out again and everything worked fine.
For our lab the attendees wrote the code on their laptops using the NetBeans IDE and then transferred the project across to the Pi to run.  To make life as easy as possible the Pi is configured to support multiple ways of getting files onto it: FTP, NFS and Samba.
  1. Install the FTP server package, apt-get install proftpd-basic.  Although it would seem logical to want to run this from inetd, choose the standalone option as this actually works better and gets started, quite happily, at boot time.
  2. Configure the FTP server by editing the /etc/proftpd/proftpd.conf file.  This is not strictly necessary, but if you want to be able to use anonymous ftp then uncomment the sizeable section that starts with the comment,

    # A basic anonymous configuration, no upload directories.

  3. Install the necessary packages for NFS server support, apt-get install nfs-kernel-server nfs-common
  4. Edit the /etc/exports file to add the user home directory,

    /home/lab     10.0.0.*(rw,sync,no_subtree_check)
At this point you would think, like I did, that rebooting the machine would give you a functioning NFS server.  In fact on the soft float Wheezy distro this is exactly what happened.  As with DHCP there is some weirdness in terms of changes that were made between the soft float Wheezy distro and the Raspbian one.  With Raspbian if you use the showmount -e command, either locally or remotely you get the somewhat cryptic error message, clnt_create: RPC: Port mapper failure - RPC: Unable to receive.

I'm sure with hindsight I should have been able to solve this quicker, but having had it working fine on Wheezy I just couldn't figure out why the same thing didn't work on Raspbian.  Evantually after much Googling and head scratching I determined that it was down to the RPC bind daemon not being started at boot time.  Some kind and thoughtful person decided that RPC didn't need to run at boot time.  Rather than leaving the package out so that when it's needed it gets installed and correctly configured they just moved the links from /etc/rc2.d and /etc/rc3.d from being S (for start) to K (for kill), so it doesn't start.
  1. Make the RPC bind daemon start at boot time by running update-rc.d rpcbind enable (as root)
  2. Install the Samba packagaes with apt-get install libcups2 samba samba-common
  3. Configure samba.  Edit the /etc/samba/smb.conf file and add the following at the end of the file:

    [lab]
    comment = Raspberry Pi Java Lab
    path = /home/lab
    writable = yes
    guest ok = yes
If you want the attendees to be able to project the desktop of the Pi to their laptops then you will need VNC.
  1.  Install the VNC server, apt-get install tightvncserver
  2. As the lab user, set a password for the VNC server with tightvncpasswd.  When doing this you can set different passwords for a fully interactive session and a view only one.
  3. Run tightvncserver :1 to generate all the necessary configuration files.  You will now be able to access the Raspberry Pi desktop remotely using a VNC client (I use [the bizarrely named] Chicken of the VNC on the Mac, RealVNC on Windows and xtightvncviewer on Linux).
  4. In order for the VNC server to start up whenever the system boots a script is required in the /etc/init.d directory.  I call it tightvncviewer, for which the code is:

    #!/bin/sh -e
    #
    # Start/stop VNC server

    ### BEGIN INIT INFO
    # Provides:          tightvncserver
    # Required-Start:    $network $local_fs
    # Required-Stop:     $network $local_fs
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: tightvncserver remote X session projection
    # Description:       tightvncserver allows VNC clients to connect to
    #                    this machine and project the X desktop to the
    #                    remote machine.
    ### END INIT INFO

    . /lib/lsb/init-functions

    # Carry out specific functions when asked to by the system
    case "$1" in
      start)
        echo Starting tightVNC server
        su lab -c 'tightvncserver :1 > /tmp/vnclog 2>&1'
        ;;
      stop)
        echo Stopping tightVNC server
        su lab -c 'tightvncserver -kill :1'
        ;;
      restart)
        echo Restarting vncserver
        $0 stop
        $0 start
        ;;
      *)
        echo "Usage: /etc/init.d/vncserver {start|stop|restart}"
        exit 1
        ;;
    esac

    exit 0

    Make sure that this script has execute permission.  To create the necessary links into the /etc/rc*.d directories run update-rc.d tightvncserver defaults.  Note that this provides the desktop of the 'lab' user.  If you want to support a different user change the name.  More users can be supported by creating additional servers running on screens other than :1.
To avoid having to provide printed instructions for the lab or distribute files on a CD or memory stick I also configure Apache on the Pi so that once the Pi is connected to the attendee's laptop they can simply open a web page and have whatever instructions and software available from there.
  1. Install Apache, apt-get install apache2
  2. Create your HTML content and put it in /var/www
  3. Finally install the Java runtime.  I put it in /opt and set the PATH environment variable in the user's .bashrc file.

Thursday Aug 16, 2012

JavaFX Interface For Power Control

Power Control JavaFX Interface
Having completed the construction of my power control system I've finally found time to build the software interface using the Arduino board I included in it.

First off I needed some code on the Arduino that would listen for commands comming via the USB connection and then take the appropriate action.  Since all that is required is to set one of two pins either high or low the protocol is trivial.  The C code for the Arduino is shown below:
#define SOCKET_PIN_1 3
#define SOCKET_PIN_2 2
#define SOCKET_1_ON 65
#define SOCKET_1_OFF 97
#define SOCKET_2_ON 66
#define SOCKET_2_OFF 98

void setup(){
  pinMode(SOCKET_PIN_1, OUTPUT);
  pinMode(SOCKET_PIN_2, OUTPUT);
 
  Serial.begin(9600);
  Serial.println("READY");
}

void loop(){
  int incomingByte = 0;
 
  /* Wait for control command from the PC */
  if (Serial.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial.read();
 
    switch (incomingByte) {
      case SOCKET_1_ON:
        digitalWrite(SOCKET_PIN_1, HIGH);
        Serial.println("Socket 1: ON");
        break;
      case SOCKET_1_OFF:
        digitalWrite(SOCKET_PIN_1, LOW);
        Serial.println("Socket 1: OFF");
        break;
      case SOCKET_2_ON:
        digitalWrite(SOCKET_PIN_2, HIGH);
        Serial.println("Socket 2: ON");
        break;
      case SOCKET_2_OFF:
        digitalWrite(SOCKET_PIN_2, LOW);
        Serial.println("Socket 2: OFF");
        break; 
    }
  }
}
All this does is initialise the serial port to work at 9600 baud and configure pins 2 and 3 as outputs.  Why use pins 2 and 3 and not 0 and 1 I hear you ask.  The answer is that pins 0 and 1 are also used for accessing the UART of the Arduino.  Once programmed and up and running this is no problem, but if you have an application running that is using these pins you can't then upload a program the Arduino though the USB port.  This caused me some problems to start with until I found a blog reference to this elsewhere.  To make life easier I switched to using pins 2 and 3.

The loop function looks for bytes being sent via the USB serial connection and takes the appropriate action in setting the pins high or low.  To keep things simple I used 'a' and 'A' for socket 1 and 'b' and 'B' for socket 2.  Lower case sets the pins low (turning the socket off) and upper case sets the pin high (turning the socket on).  To test this all you need to do is use the Serial Monitor in the Arduino IDE and type the appropriate character.

Next we need some way of sending the appropriate bytes from the controlloing PC.  Java has long had the JavaComm API which provides an API for all things serial and parallel.  The PC I'm using for the UI is running Ubuntu Linux, so I used the available librxtx-java package.  This has a rather frustrating limitation, that I would describe as a bug.  Plugging the Arduino USB into my machine automatically creates me a device to use to access this, which is what we need.  In this case the device is /dev/ttyACM0.  The problem is that librxtx-java will only recognise serial ports of the form /dev/ttyS{number}.  To get round this I created a symbolic link from /dev/ttyACM0 to /dev/ttyS4 (since I actually have physical serial ports on my machine using ttyS0 to ttyS3).  The big drawback to this is that when the machine is rebooted the OS very thoughtfully removes my symbolic link.  At some point I need to try and figure out if there is a way through udev to make this work properly.

The code below shows part of the class I created to handle communication with the Arduino through the serial port:
 public ArduinoComms(String portName) throws ArduinoCommsException {
   debug("AC: opening port: " + portName);
   CommPortIdentifier portIdentifier = null;
   CommPort commPort = null;

   try {
     portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
     debug("AC: Got portIdentifier");
   } catch (NoSuchPortException ex) {
     debug("AC: getPortIdentifier failed");
     throw new ArduinoCommsException(ex.getMessage());
   }

   if (portIdentifier.isCurrentlyOwned())
     throw new ArduinoCommsException("Error: Port is currently in use");
   else {
     try {
       commPort = portIdentifier.open(this.getClass().getName(), 2000);
       debug("AC: Opened port");
 
       if (commPort instanceof SerialPort) {
         SerialPort serialPort = (SerialPort) commPort;
         serialPort.setSerialPortParams(9600,
         SerialPort.DATABITS_8,
         SerialPort.STOPBITS_1,
         SerialPort.PARITY_NONE);
         debug("AC: Set parameters");

         in = serialPort.getInputStream();
         out = serialPort.getOutputStream();
         debug("AC: Got input/output streams");
       } else {
         System.out.println("ERROR: Not recognised as a serial port!" );
         throw new ArduinoCommsException(portName);
       }
     } catch (PortInUseException |
              UnsupportedCommOperationException |
              IOException ex) {
       throw new ArduinoCommsException(ex.getMessage());
     }
   }
 }
Passing /dev/ttyS4 to this constructor provides the application with an InputStream and OutputStream to communicate with the Arduino.  To simplify things further I subclassed my Arduino communications class to make it specific to my power control adding some useful methods shown below:
 /**
  * Turn socket one on
  * 
  * @throws IOException If this fails
  */
 public void socketOneOn() throws IOException {
   debug("PC: socketOneOn");
 
   if (out != null)
     out.write(SOCKET_ONE_ON);
   else
     throw new IOException("Output stream is null!");
 }

 /**
  * Turn socket one off
  * 
  * @throws IOException If this fails
  */
 public void socketOneOff() throws IOException {
   debug("PC: socketOneOff");
 
   if (out != null)
     out.write(SOCKET_ONE_OFF);
   else
     throw new IOException("Output stream is null!");
 }

 /**
  * Turn socket two on
  * 
  * @throws IOException If this fails
  */
 public void socketTwoOn() throws IOException {
   debug("PC: socketTwoOn");
 
   if (out != null)
     out.write(SOCKET_TWO_ON);
   else
     throw new IOException("Output stream is null!");
 }

 /**
  * Turn socket two off
  * 
  * @throws IOException If this fails
  */
 public void socketTwoOff() throws IOException {
   debug("PC: socketTwoOff");
 
   if (out != null)
     out.write(SOCKET_TWO_OFF);
   else
     throw new IOException("Output stream is null!");
 }

All that is the required now is a user interface to provide a way of sending the appropriate character when the user wants to change the power state.  I borrowed some button graphics from Jaspers JavaOne Kinect demo last year and a nice background I found here.  The result is shown below:
screen shot 1

screen shot 2

screen shot 3

The code for the JavaFX part is shown below:

 
    /**
     * Background
     */
    URL resourceURL = PowerUI.class.getResource("resources/background.png");
    Image backgroundImage = new Image(resourceURL.toExternalForm());
    ImageView background = new ImageView(backgroundImage);
    getChildren().add(background);
    
    /**
     * Images for switches
     */
    resourceURL = PowerUI.class.getResource("resources/power-off.png");
    Image powerOffImage = new Image(resourceURL.toExternalForm());
    resourceURL = PowerUI.class.getResource("resources/power-on.png");
    Image powerOnImage = new Image(resourceURL.toExternalForm());

    final ImageView powerOffSocketA = new ImageView(powerOffImage);
    final ImageView powerOnSocketA = new ImageView(powerOnImage);
    final ImageView powerOffSocketB = new ImageView(powerOffImage);
    final ImageView powerOnSocketB = new ImageView(powerOnImage);

    Font f = new Font(18);
    
    /**
     * Label and control for the first socket
     */
    Group labelA = GroupBuilder.
        create().
        translateX(35).
        translateY(20).
        build();
    Rectangle r = RectangleBuilder.
        create().
        width(120).
        height(30).
        fill(Color.YELLOW).
        build();
    labelA.getChildren().add(r); 
    Text socketALabel = TextBuilder.
        create().
        text("POWER 1").
        font(f).
        fill(Color.BLACK).
        translateX(18).
        translateY(21).
        build();
    labelA.getChildren().add(socketALabel);
    getChildren().add(labelA);
    
    powerOffSocketA.setTranslateX(60);
    powerOffSocketA.setTranslateY(70);
    powerOffSocketA.setOnMouseClicked(new EventHandler() {
      @Override
      public void handle(MouseEvent t) {
        powerOffSocketA.setVisible(false);
        powerOnSocketA.setVisible(true);
        
        try {
          debug("PUI: Socket 1 ON");
          comms.socketOneOn();
        } catch (IOException ex) {
          System.out.println("ERROR: " + ex.getMessage());
        }
      }
    });
    getChildren().add(powerOffSocketA);

    powerOnSocketA.setTranslateX(60);
    powerOnSocketA.setTranslateY(70);
    powerOnSocketA.setOnMouseClicked(new EventHandler() {
      @Override
      public void handle(MouseEvent t) {
        powerOffSocketA.setVisible(true);
        powerOnSocketA.setVisible(false);
        
        try {
          debug("PUI: Socket 1 OFF");
          comms.socketOneOff();
        } catch (IOException ex) {
          System.out.println("ERROR: " + ex.getMessage());
        }
      }
    });
    powerOnSocketA.setVisible(false);
    getChildren().add(powerOnSocketA);

    /**
     * Label and control for the first socket
     */
    Group labelB = GroupBuilder.
        create().
        translateX(190).
        translateY(20).
        build();
    r = RectangleBuilder.
        create().
        width(120).
        height(30).
        fill(Color.YELLOW).
        build();
    labelB.getChildren().add(r); 
    Text socketBLabel = TextBuilder.
        create().
        text("POWER 2").
        font(f).
        fill(Color.BLACK).
        translateX(18).
        translateY(21).
        build();
    labelB.getChildren().add(socketBLabel);
    getChildren().add(labelB);
    
    powerOffSocketB.setTranslateX(215);
    powerOffSocketB.setTranslateY(70);
    powerOffSocketB.setOnMouseClicked(new EventHandler() {
      @Override
      public void handle(MouseEvent t) {
        powerOffSocketB.setVisible(false);
        powerOnSocketB.setVisible(true);
        
        try {
          debug("PUI: Socket 2 ON");
          comms.socketTwoOn();
        } catch (IOException ex) {
          System.out.println("ERROR: " + ex.getMessage());
        }
      }
    });
    getChildren().add(powerOffSocketB);
    powerOnSocketB.setTranslateX(215);
    powerOnSocketB.setTranslateY(70);
    powerOnSocketB.setOnMouseClicked(new EventHandler() {
      @Override
      public void handle(MouseEvent t) {
        powerOffSocketB.setVisible(true);
        powerOnSocketB.setVisible(false);
        
        try {
          debug("PUI: Socket 2 OFF");
          comms.socketTwoOff();
        } catch (IOException ex) {
          System.out.println("ERROR: " + ex.getMessage());
        }
      }
    });
    powerOnSocketB.setVisible(false);
    getChildren().add(powerOnSocketB);
One of the things I've just started really using when developing JavaFX is the Builder classes.  These are great for making it easy to create Nodes and setting numerous attributes without having to call each method individually on the object.

I guess the next thing is to make this into a simple web service so I can control my Raspberry Pi and Beagle Board from a web browser antwhere in the world.

About

A blog covering aspects of Java SE, JavaFX and embedded Java that I find fun and interesting and want to share with other developers. As part of the Developer Outreach team at Oracle I write a lot of demo code and I use this blog to highlight useful tips and techniques I learn along the way.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
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
   
       
Today