BUG and the Axeda Platform: Part 1 of 2 - A Connected BUG Camera App

The BUG is a combination hardware and software platform designed for rapid, modular prototype development of M2M and telematics applications.   The BUGs un-tethered and modular design make it easy to connect to the Internet wherever there's 3G wireless coverage, and to add capabilities like wifi and GPS simply by adding a snap-on module.
BUG BaseBUG Modules

 

                                               BUG Base                                                                   BUG Modules

 This two-part article will walk you through capturing and sending data from the BUG to the Axeda Platform for analysis and display.  As we go along, we’ll provide examples and code that illustrate how to leverage the BUG’s wifi capabilities to create an app that transmits images and text from the handset into an HTML page via a call to the Scripto web service.  There are three components to this app:
• The BUG code, written in Java that interfaces with the camera hardware and handles the text and file uploads via the Axeda Wireless Protocol (AWP),
• The Groovy custom object that handles retrieval of the files, and
• The Scripto call made via JavaScript in the HTML front end

This tutorial is for intermediate users who are comfortable working with Eclipse and a command line interface.  

Part 1: A Simple Camera App

If you’re unfamiliar developing with BUG, don’t panic.  For this exercise the combination of Eclipse and the Dragonfly SDK will take care of the heavy lifting for you.  The Dragonfly SDK is very robust and makes the entire suite of BUG services available to you as you go. 

 Installation

To start off, install Java, Eclipse, and Dragonfly:

 1.      Install Java 1.6, available at http://www.oracle.com/technetwork/java/javase/downloads/index.html
        Select the JDK download and follow the installation instructions in the documentation at http://www.oracle.com/technetwork/java/javase/documentation/index.html
2.      Download and set up Eclipse, available at http://www.eclipse.org/
3.      Install the Dragonfly SDK for BUG which is available as a plugin for Eclipse. 

From Eclipse:
Go to Help > Install New Software.
In the Available Software Pop-up, paste the following URL and click Add:
http://www.buglabs.net/sdk/production/current/updatesite/
Note: This link will not work as a straight download as it is an Eclipse plugin available only through the new software installer.

Click the Dragonfly SDK checkbox and click Next.  The plugin will install.
 

Create a Project

Next, create a project using Dragonfly in Eclipse.

 

1.     Open the Dragonfly perspective:  Window > Open Perspective > Other > Dragonfly.
2.      In the New BUG Project dialog, name the project BUGConnexUs, enter your name as author, and enter “A BUG Demo App” as the description.
3.      Click the icon for creating a new BUG project.

 

start new project
4.      Select the display and camera modules and click the Services checkboxes that correspond to them.
5.      Launch and select the BUG Simulator that will be used to test the project.
6.      Select all services, then click Finish.

Add in the AWP Toolkit for Java in Explorer.  By including these dependencies, the project gains access to the core classes of the Axeda Wireless Protocol.

1.      Download the AWP Toolkit for Java and extract the jars from the lib directory. 

2.      Open up the project in Windows Explorer under the Eclipse workspace directory
3.      Create a folder called “jars” and move the jars into this folder
4.      Back in Eclipse, right click the project name and go to Build Path > Configure Build Path
5.      In the Libraries tab, click Add External Jars and browse to the jars folder. Select the jars and click Open, Ok.
6.      Expand the META-INF tab in the Eclipse project, then doubleclick Manifest.MF .  This Manifest lists the dependencies that are available to the BUG hardware. 
7.      Add the following text before Bundle-Version:

Bundle-ClassPath: .,jars/awp-6.1.4.jar,jars/asmp-6.1.4.jar,jars/axeda-protocol-common-6.1.4.jar,jars/harmony-asn1-6.0.1.jar, jars/log4j-1.2.13.jar

 

Now what you’ve been waiting for, we’re ready to code. 

The Scaffold

Expand the package bugconnexus and doubleclick BUGConnexUs.java.

BUG ConnexUs

We begin with the following:

  package bugconnexus; 

  import java.util.Map;
  import com.buglabs.application.ServiceTrackerHelper.ManagedRunnable;
  import javax.swing.JFrame;
  import bugconnexus.ImageCanvas;

  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.FlowLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;

 

  import javax.swing.JButton;
  import java.awt.event.WindowEvent;
  import java.awt.event.WindowStateListener;
  import java.awt.image.BufferedImage;
  import java.awt.image.DataBufferInt;
  import java.io.IOException;
  import java.util.Date;

 

  import com.axeda.protocol.awp.DataItem;
  import com.axeda.protocol.awp.DataMessage;
  import com.axeda.protocol.awp.DeviceID;
  import com.axeda.protocol.awp.Header;
  import com.axeda.protocol.awp.Message;
  import com.axeda.protocol.awp.PackageExecuteMessage;
  import com.axeda.protocol.awp.RequestUploadMessage;
  import com.axeda.protocol.awp.StreamTransmissionTransport;
  import com.axeda.protocol.awp.Transmission;
  import com.axeda.protocol.awp.TransmissionTransport;
  import com.axeda.protocol.common.ChannelProvider;
  import com.axeda.protocol.common.SocketChannelProvider;
  import com.buglabs.bug.module.camera.pub.ICamera2Device;
  import com.buglabs.bug.module.camera.pub.ICameraModuleControl;
  import com.buglabs.bug.module.lcd.pub.IModuleDisplay;
  import com.buglabs.device.ButtonEvent;
  import com.buglabs.device.IButtonEventListener;
  import com.buglabs.device.IButtonEventProvider;

 

  public class BUGConnexUs implements ManagedRunnable  {

 

                 @Override
                 public void run(Map<Object, Object> services) {                                      
             }
                  public void shutdown() {
    }
   }
  }

 

The Services

Add in IButtonEventListener and WindowStateListener after Managed Runnable:

  public class BUGConnexUs implements ManagedRunnable, IButtonEventListener, WindowStateListener {

Eclipse will pop up a dialog asking if unimplemented methods should be added.  Click the link to add these.

Add in a way to access the services on the BUG.

Insert the services variable declarations after the opening class statement:

        /** setup the camera, display and images. **/
        @SuppressWarnings("unused")
        private IModuleDisplay display;
        private ICamera2Device camera;
        private ICameraModuleControl cameraLED;
        private IButtonEventProvider buttonEventProvider;

Insert the following in the run method declaration:

  camera  = (ICamera2Device) services.get(ICamera2Device.class.getCanonicalName());
  buttonEventProvider = (IButtonEventProvider) services.get(IButtonEventProvider.class.getCanonicalName());
  display = (IModuleDisplay) services.get(IModuleDisplay.class.getCanonicalName());
  cameraLED = (ICameraModuleControl) services.get(ICameraModuleControl.class.getCanonicalName());

Next activate the camera and button services, like so:

  // we need a way to handle events, @see buttonEvent() 
  buttonEventProvider.addListener(this);
  // decided to both open and start the camera here to speed up shutter times
  camera.cameraOpenDefault();
  // as long as cameraStart is open other applications cannot use it
  camera.cameraStart();

 The Camera

Now add in a method that takes a picture with the camera.

     private void shoot () throws IOException {
       byte [] imageData = null;
          camera.cameraOpen(ICamera2Device.DEFAULT_MEDIA_NODE, -1, 320, 240, 320, 240);
          cameraLED.setLEDFlash(true);
          camera.grabPreview(buf);
          camera.grabPreview(buf);
          cameraLED.setLEDFlash(false);
         // get the image from the camera
          imageData = camera.grabFull();
}

The grabPreview method takes parameter buf, short for DataBufferInt.  Add in the following variables below the other declarations:

    private final static int imageWidth  = 320;
    private final static int imageHeight = 280;
    private final BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
    private final int [] buf = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

 If you’re curious about the parameters of any of these methods, place the caret in the method name and the documentation will pop up.

 The shoot method should fire when the camera button is pressed.

     @Override
    public void buttonEvent(ButtonEvent event) {
       if ( event.getButton() == ButtonEvent.BUTTON_BUG20_USER &&
           event.getAction() == ButtonEvent.KEY_DOWN ) {
     try {
             shoot ();
          } catch (IOException e) {                                                           
              e.printStackTrace();
          }
      }
    }

 Let’s add code into the method to shut down the camera.

     @Override
    public void shutdown() {
       // shut down the camera gracefully
       try { cameraLED.setLEDFlash(false); } catch (IOException e) { e.printStackTrace(); }
       if (camera.isCameraStarted())         { camera.cameraStop(); }
       if (camera.isCameraOpen())                            { camera.cameraClose(); }
       
       // remove event listeners
       if (buttonEventProvider != null) {
          buttonEventProvider.removeListener(this);
       }
       
       // dispose of gui
       if (frame != null) {
          frame.dispose();
          frame = null;
       }
    }

 The Display

It’s not much fun to take a picture without seeing it afterwards, so create an ImageCanvas class which allows us to see the image on the LCD display.

 After the declaration of the ButtonEventProvider variable, insert the following:

     private JFrame frame;
    private ImageCanvas ic;

 Eclipse will pop up a dialog asking if it should create a class ImageCanvas in package bugconnexus.  Click the link in the dialog to create the class.

 Paste the following code into the class:

   package bugconnexus;

  import java.awt.Canvas;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Image;
  
  public class ImageCanvas extends Canvas {
     
      private static final long serialVersionUID = -9119167926427852857L;
      private Image img;
     
      public ImageCanvas (Image img){
         this.img = img;
      }

       public void paint(Graphics g){
         g.drawImage(img, 0, 0, this);
      }
     
      public void update (Graphics g){
         paint(g);
      }
      public Dimension getMinimumSize()
      {
         return getPreferredSize();
      }
     
      public Dimension getPreferredSize()
      {
         int w, h;
         while ((w = img.getWidth(this)) == -1);
         while ((h = img.getHeight(this)) == -1);
         return new Dimension(w, h);
      }
      public Image getImg() {
         return img;
      }
      public void setImg(Image img) {
         this.img = img;
      }
     
  }

 To add the ImageCanvas into the JFrame, paste the following into the main run method under camera start:

     // create new gui
    frame = new JFrame();
    frame.addWindowStateListener(this);                       
    frame.setVisible(true);

     // add the image canvas to hold the image
    ic = new ImageCanvas(image);
    frame.add(ic, BorderLayout.CENTER);
    frame.setSize(320, 280);

 In the shoot method, add the following line at the end to display the new image on the LCD screen:

         // display the image in the image canvas 
         ic.repaint();

 Testing

Now the project is ready to run with this basic camera function.   For instructions on how to test the project in the BUG Simulator, refer to http://wiki.buglabs.net/index.php/Tutorial:BugWeatherApp_Part_2-Running_BUG_Simulator .

At this point you’ve created a simple app that will take a picture and display it when the camera button is pressed, but what we're really looking to do is transfer data.  So let's add that in.

 The Input

First, create a button to allow the user to send text as input, which will become the data item to send to Axeda.

 Insert the following code into the main run method after frame.addWindowStateListener(this).

    // create content in the JFrame   
    Container content = frame.getContentPane();
    content.setBackground(Color.white);
    content.setLayout(new FlowLayout()); 

   // create a button
    JButton btnLike = new JButton("Excited!");
    btnLike.setPreferredSize(new Dimension(100, 50));
   
    // add the button to the content
  content.add(btnLike, BorderLayout.WEST);
     frame.pack();
  frame.setSize(200, 280); 
     frame.setVisible(true);

 Next, add an ActionListener which will link a new action to the button.

     ActionListener actionListenerLike = new ActionListener() {
     public void actionPerformed(ActionEvent actionEvent) {
    String strTEXT = "Excited!";

    sendTEXT(strTEXT);
     } 
    };
  btnLike.addActionListener(actionListenerLike);

The Transmission 

Now, paste the following code in to transmit the data to the Axeda Platform:

  public void sendTEXT(String strTEXT)  {
     // create a data item with label “Mood” and value of strTEXT
     DataItem dataitem = new DataItem("Mood",strTEXT);
     DataMessage dmsg = new DataMessage(dataitem,date, 1);
     Transmission tTEXT = new Transmission(h);
     tTEXT.addMessage(dmsg);
     
     System.out.println("Acknowledged");
     
     sendAndReceive(tTEXT);
     }
                private ChannelProvider getChannelProvider()
                  {
      return (new SocketChannelProvider());
                  }
               
  private Transmission sendAndReceive(Transmission trans) 
                 {
                                DeviceID deviceID ;
                                deviceID = new DeviceID(model, serial, owner);
StreamTransmissionTransport transport = new StreamTransmissionTransport(getChannelProvider(),this.platformAddress, deviceID, 10);
                                Transmission tmpt = null;
                                System.out.println("Now sending data item to Axeda.");
                                try 
                                {
                                  tmpt = transport.pollOnce(trans, 1000);
                                             } 
                                catch (Exception e) 
                                {
                                  e.printStackTrace();
                                }
                                return tmpt ;
                 }

 

This sendTEXT() method relies on a few others for transmission.  Create the sendUploadRequest and waitForPackageExecute methods by pasting the following code into main:

 

private void sendUploadRequest(String photoFileName)
  {
    Transmission transmission;
    Header header;
    try
    {
  
   TransmissionTransport transport = new StreamTransmissionTransport(getChannelProvider(),platformAddress);
   header = new Header(model, serial, owner, false, false);
   transmission = new Transmission(header);
   RequestUploadMessage requestUploadMessage = new RequestUploadMessage(false, "photo", "CONNEXUSCLIENT", photoFileName, false, false, 1);
   transmission.addMessage(requestUploadMessage);
  
   transport.send(transmission, 1000);
   
    }
    catch (Exception ex)
    {
   ex.printStackTrace();
    }
  }
 
 private PackageExecuteMessage waitForPackageExecute(int pollRate, int timeout)
  {
  PackageExecuteMessage pmsg = null;
      DeviceID deviceID = new DeviceID(BUGConnexUs.model,
  BUGConnexUs.serial, this.owner);
    try
    {
   TransmissionTransport transport = new StreamTransmissionTransport(
    getChannelProvider(), BUGConnexUs.platformAddress, deviceID, pollRate);
   Transmission recvTransmission = transport.receive(timeout);
   if (recvTransmission != null)
   {
     Message[] msgs = recvTransmission.getMessages();
     for (int i = 0; i < msgs.length; i++)
     {
    Message msg = msgs[i];
    if (msg instanceof PackageExecuteMessage)
    {
      pmsg = (PackageExecuteMessage) msg;
      break;
    }
     }
   }
    }
    catch (Exception e)
    {
   e.printStackTrace();
    }
    return pmsg;
  } 

The Serial Number

If the model or serial does not already exist when the data is transmitted, the Platform will automatically create them.  The serial number is generated as a string with the current time appended to the name.  Note that the time string only changes if the app is restarted.

 

Add in the following variables used in this section under the other variable declarations in the global scope:

    private static String model = "connexus_user_model";
    private static String serial; 
    private String owner = "Axeda";
    // where the data will be sent, please note that the demo app is set to m2m.axeda.com:19000
    public static String platformAddress = "dev6.axeda.com:19000";
    // header for the transmission content
    private static Header h; 
    // the size in kilobytes of the chunks of data to be uploaded at a time
    private int chunksize = 65536; 
    private Date date;

 In the main run method after camerastart(), paste in the following:

     // use time as a string to generate a unique name
    SimpleDateFormat formatter = new SimpleDateFormat("yyMMddHHmmss");
    Date time = new Date();
    strtime = formatter.format(time) 
    String serial = "User_1" + strtime;

The Image File

In the shoot method, add the following code to create a file with the image data and send it:

  try {
  
    // create an image directory called “/home/root/images”
    boolean dir = new File("/home/root/images").mkdirs();
        SimpleDateFormat formatter = new SimpleDateFormat("yyMMddHHmmss");
    Date time = new Date();
    String imgFileName = "/home/root/images/image" + strtime + ".jpg";
    // create an image file with the file name
    FileOutputStream fos = new FileOutputStream(imgFileName);
   // write the image data to the file
    fos.write(imageData);
    fos.close();
    System.out.println("Wrote " + imgFileName);
             System.out.println("Now sending to Axeda...");
    sendUploadRequest(imgFileName);
     PackageExecuteMessage packageExecuteMessage = waitForPackageExecute(1, 1000);
     if (packageExecuteMessage != null){                            
    FileTransferProcessor fileTransmissionProcessor =
     new FileTransferProcessor(model, serial, "drm-data_source", chunksize, platformAddress, getChannelProvider());
    fileTransmissionProcessor.process(packageExecuteMessage);
     }
     else {
    System.out.println("PackageExecute message not received from Enterprise");
     }
                                             
                              } catch (FileNotFoundException e) {
                                             
                                             e.printStackTrace();
                              } catch (IOException e) {
                                             
                                             e.printStackTrace();
                              }
                              
               } 

Well done, you’ve added functionality to your camera app that accepts a text input, writes a camera image to a file and uploads them both to the Axeda Platform.  See Part 2  and check out a live demo of the finished tutorial at http://m2m.axeda.com/apps/bugjsondemo/index.html .

 

Download the source code for this example from the link below: 

The Java, Groovy, HTML files, plus the tutorial as a PDF and a screenshot .................................. bugjsondemo.zip

The Groovy and HTML files as an artisan project .......................................................................... bugconnexus-project.zip