Chapter 6. An NFC Application in Practice

So far you’ve gotten an understanding of the differences between NFC and NDEF and an introduction to NDEF messages and records. You’ve also seen how Android’s tag dispatch system can work in either the foreground or the background to filter tags by type and take action where appropriate. In this chapter, you’ll use what you’ve learned to make a full application that can control the lights and music in your home from your mobile device. The application you’re going to build is a classic. It’s been done in so many forms and shown at so many tech conferences that it’s a cliché by now. We’ll call it the “Mood Setter” application.

The premise of the application is simple: you might want a different ambiance in your home for the various activities you do there. When you’re cleaning the house, you probably want the lights nice and bright, and some cheery music on to keep you moving. When you’re working at your desk concentrating, you might like quieter music, maybe something without words to distract you, and you don’t need the whole place lit, just where you’re working. At dinnertime, perhaps you want want a nice warm glow around the table and some soothing music in the background. Or perhaps you’re just having a relaxing evening and want the lights down low and a little Barry White on the hi-fi. Who can resist that?

To make all that happen, you’ll write the settings to NFC tags that are scattered around the house, and then use your device to read the tags and control the lights and the music. The lights in this application are the Philips Hue system. Hue is a lighting system that combines LED lamps with embedded ZigBee radios and a special Ethernet-to-ZigBee hub that controls the lights. The hub is running its own HTTP server, and the commands to set the lights are all JSON objects sent using a RESTful protocol. That means you can control the Hue lights using any device that can make an HTTP call. You’ll play the music right on your device and stream it to your stereo using a Bluetooth speaker or receiver for the stereo. Figure 6-1 shows the hardware setup.

System diagram for the Mood Setter application
Figure 6-1. System diagram for the Mood Setter application

The Bluetooth receiver can be any audio device that receives Bluetooth audio. There are dozens on the market, some of which are combination amplifier-speaker devices with Bluetooth built-in, and others are simply receivers that convert Bluetooth audio to analog audio, so you can plug them into an existing receiver. You could even use a Bluetooth headset if you wanted to. We chose a Belkin Bluetooth Music Receiver because it was inexpensive and could plug right into our existing stereo. You might also check out HomeSpot’s NFC-enabled Bluetooth Audio Receiver, which allows you to pair your device with the receiver via NFC. We found both the Belkin and the HomeSpot worked adequately for this application. Once your device is paired with the audio receiver, go to the Android system settings, choose “Bluetooth,” connect to the receiver, and you’re ready to go.

Note

We’re pretty sure that every reader of this book isn’t going to go out and buy a Philips Hue system, but we like the way Philips has implemented lighting control using web standards (HTTP, REST, and JSON) and an open radio protocol (the ZigBee LightLink lighting control protocol). The Hue system is a good example of how to handle messaging between applications using HTTP and JSON, so the concepts shown here can be generalized to many other web-based applications.

The User Interaction

There are a few actions you want the user to do with this app: set the brightness, hue, and saturation of your Hue lights, or turn them on or off; choose a song to play via Bluetooth, and play, stop, or pause it; and read or write NFC tags containing music and lighting data.

There are two main modes of operation for the app: read mode (in which it can read tags and change the lights and music automatically) and write mode (in which it can save its current settings to a tag). Regardless of which mode you’re in, you want to be able to control the songs and lights.

When you tap your device to a tag in your house, if the app is in read mode, it will automatically change the lights and play the appropriate music, depending on what settings are embedded in the tag. If it’s in write mode, it will automatically write the current settings to the tag.

The app interface has a mode button to switch from read to write mode and back; a pull-down menu to pick which light to control; controls for setting the brightness, saturation, and hue of the lights, and for turning them on or off; another pull-down menu for picking the song you want to play; and play/pause and stop controls.

The app’s user interface is shown in Figure 6-2. It’s divided into three sections: the tag mode section at the top, the lighting controls in the middle, and the music controls at the bottom. In the HTML, each of these has its own div. The message div you used in other apps is still present, but it could be removed once you’re finished coding and testing the app.

When you change the lights, the app will keep a local copy of the Hue settings you’ve changed, so that it can write them to a tag when you bring a tag in proximity.

User interface for the Mood Setter application
Figure 6-2. User interface for the Mood Setter application

Each tag you’ll use to trigger lights and music will have an NDEF message with two records: the lighting data and the music data. The lighting data will be a custom MIME-type you’ll make up, text/hue. Since it’s a custom MIME-type, you’ll be able to filter for it easily. The music tag will be a Well-Known type with a URI Record Type Definition. When your device reads the tag, it will check for a text/hue record, and if there’s one there, it will make an HTTP call to the Hue hub and pass it along. If it discovers a URI on the tag, it will attempt to play the file as an audio file.

In order to write this app, you need to know a little about the Hue platform.

Getting to Know Hue, Getting to Know All About Hue

The Philips Hue lighting system is a well-implemented example of a web-enabled home appliance. It uses web standards for end-to-end communication so that app developers can extend its functionality without having to know about the hardware. Even if you’re not interested in the Hue system itself, this section will help you to see how Internet-enabled appliances can be implemented in a way that makes them interoperable with the rest of the Web. We’ll point out where the Hue’s design choices are worth noting for other applications as we go.

The Hue system comes with three bulbs and an Ethernet-to-ZigBee hub, called the Bridge. Your device speaks to the Bridge using HTTP requests in a RESTful format. You can download the default app from Meet Hue. You’ll find instructions there for configuring your system as well (for further instructions, see Meet Hue’s developer section).

The first problem for any Internet-connected device that doesn’t have its own screen is how to let the user know whether or not it’s successfully connected to the network, and to any client devices used to control it. The Hue’s Bridge connects to your home router using a wired Ethernet connection and uses DHCP to obtain an address, so there’s no need to configure it for your particular WiFi network. Discovery is handled through a two-step interaction: first you open the app while it’s on the same network as the Bridge, then you press the discovery button on the Bridge. For a few seconds after pressing the button, the Bridge will respond to discovery requests, which the app is sending. Assuming both actions happen within a few seconds of each other, the app discovers the Bridge and identifies itself. The Bridge then registers the app as one that has the right to control the lights.

It’s worthwhile to compare the user experience of this discovery process with that of the HomeSpot NFC-enabled Bluetooth receiver. The HomeSpot receiver has an NFC tag inside containing the pairing details for its Bluetooth radio. The user pairs her NFC-enabled phone with it by tapping the phone to the receiver. Both this and the Hue discovery process rely on the fact that the user has to have physical control over both devices before they pair wirelessly. This is a common approach in networked appliances that have no screen for user interaction. You can find the IP address of your Hue hub by using the developers’ broker page. This page will look for any hub on your local area network that responds, then gives you the IP address. Note that the device you’re browsing from must be on the same LAN as your hub when you do this. Once you’ve got the IP address, you can connect to it directly by browsing to http://your.hub.ip.address/debug/clip.html. You’ll get a page that looks like Figure 6-3.

The Hue debug tool (this page is available from every Hue hub, as long as you know the hub’s IP address)
Figure 6-3. The Hue debug tool (this page is available from every Hue hub, as long as you know the hub’s IP address)

Once you’re connected to the Hue, you need to establish credentials for the app that you’ll write. This is what the Hue app normally does, but since you’re writing your own app, you’ll need to do it yourself. There are a few steps to this process. First, enter the following into the URL field in the debug tool:

/api/

Then enter this into the body field, replacing yourAppName with your actual app name, press the link button, and click “POST:”

{"devicetype":"device type","username":"yourAppName"}

Your device type can be anything you want—for example, “Nexus 4” or “Galaxy SIII” or “My Magical Portal Window.” Your username should be at least 10 characters long or you’ll get an error. In the following application, you’ll use the username “MoodSetterApp.”

You should get a response like this:

[
   {
      "success": {
         "username": "MoodSetterApp"
      }
   }
]

Now that you’ve got a username registered with your Hue hub, you’re ready to control it. To see it in action, enter the following into the debug tool:

/api/MoodSetterApp/

You’ll get a response that describes all the lights on your hub in a JSON format.

The Hue Data Format

In order to know what you’re going to write to the tag, you need to know the Hue’s data format. The Hue data format is nicely designed to be both clear to read in JSON format, yet easy to parse in code. If you’re developing your own Internet-enabled appliances, it’s a worthwhile one to study. In Chapter 7, you’ll develop an NFC-enabled door lock that uses a JSON data format for the same reason: readability from both the human and machine perspectives.

Philips has an excellent developer reference that explains the whole API. For this app, what you need to know is that each light is described in a JSON object like so:

"1":
   {"state":
      {"on":true,"bri":65,"hue":44591,"sat":254,"xy":[0.1953,0.0959],
      "ct":500,"alert":"none","effect":"none","colormode":"hs","reachable":true},
   "type":"Extended color light",
   "name":"Living room",
   "modelid":"LCT001",
   "swversion":"65003148",
   "pointsymbol":
      {"1":"none","2":"none","3":"none","4":"none",
      "5":"none","6":"none","7":"none","8":"none"}
}

There’s a lot there, but you’re only going to modify a few of the properties. This is another aspect of the Hue data protocol worth copying: the device receiving the data (the Bridge) should accept any valid subset of the whole message, and if it can, take action.

For each light, you’ll change four subproperties of the state property: the on property, the brightness, the hue, and the saturation. Here’s a summary of the JSON you might write to change a light (without the comments, of course):

"1":                    // The light number
   {"state":            // the light's state, including:
      {"on":true,       // whether it's on or off
      "bri":65,         // the brightness (0 - 254)
      "hue":44591,      // the hue (0 - 65280 in the CIE colorspace)
      "sat":254},       // the saturation of the color (0 - 254)
   }
}

You might want to change the names of your lights to reflect where they’re located in your house as well. To try this, enter the following in the debug tool:

URL:

/api/MoodSetterApp/lights/1/

Body:

{"name": "First Light"}

The response should be:

[
   {
      "success": {
         "/lights/1/name": "First Light"
      }
   }
]

And to change the state, try:

URL:

/api/MoodSetterApp/lights/1/state

Body:

{"on": true, "bri": 65, "hue": 44591, "sat": 254}

You should get a success message for every property you changed (another good general principle to note), and the light should now be a pleasant blue-lavender color.

You can’t actually send the whole JSON object using the REST API at once. You have to set the state of a given light, then the name of a given light separately.

If you’re not familiar with lighting color theory, the hue and saturation properties may be new to you. Philips uses a colorspace called the CIE, a map devised in 1931 by the International Commission on Illumination (CIE) to describe the color of light in a uniform way. Briefly, the CIE scheme maps color into two parts: the luminance and chromaticity. The luminance refers to the relative value of a color on a black-to-white scale, while the chromaticity refers to the relative value of the color. You can think of it this way: black, white, and gray all have the same chromaticity, but vary in their luminance. Figure 6-4 shows the CIE color space chromaticity diagram.

The CIE color diagram (image courtesy of Wikipedia)
Figure 6-4. The CIE color diagram (image courtesy of Wikipedia)

As you change the hue of a lamp, you’ll see it change around the CIE color space, moving from red at the low end, through all the other colors in the middle, and back to red. As you change the saturation, you’ll see the color fade from a vibrant color to a paler tint.

For a Hue hub with three lights, the default package, you might send a JSON object like this:

{"1":
   {"state":
      {"on":true,"bri":65,"hue":44591,"sat":254}
   },
"2":
   {"state":
      {"on":true,"bri":254,"hue":13122,"sat":211}
   },
"3":
   {"state":
      {"on":true,"bri":255,"hue":14922,"sat":144}
   }
}

This JSON object will be the payload of the first of two NDEF records you’ll write to the NFC tags used in this app. As mentioned before, you’ll send it as a MIME type, text/hue.

The Hue’s REST API

As you’ve probably noticed from the debug tool, the Hue’s request API is RESTful. To get data, you use the format /api/MoodSetterApp/ then add on the specifics you want and make an HTTP GET request. For example, to get the state of light 1, it’s /api/MoodSetterApp/lights/1/state. To change data, you use the same basic format with a PUT request. In the app you’re building, you’ll assemble a URL to make HTTP requests to the hub, one to PUT new data like so:

url: 'http://' + hub.ipaddress + '/api/' + hub.username
            + '/lights/' + lightId + '/state',

The other will GET data from the hub:

url: 'http://' + hub.ipaddress + '/api/' + hub.username,

Since the API is just REST URLs and JSON, you can simplify the app by using zepto.js, a slimmed-down version of the popular JQuery library. You’ll see that in the HTML head in The User Interface.

The Android Shell

The other NDEF record will be a URI record with the full file path of the song you want to play. The file will then be played using PhoneGap’s Media API. In order to simplify things, you should put all of the songs you want to use in one directory, or use the default directory for whatever music app you use on your device already.

The Android Debug Bridge (ADB), which you’ve been using already, can do more than just show you log messages while you’re debugging. You can also use it to access your Android device from the command line of your computer. To get to the shell, open a Terminal window on your computer while you’ve got the device attached via USB and type the following:

$ adb shell

You’re now accessing the device directly, and you can do most of the things you expect from a shell: list files using ls, change directories using cd, make new directories using mkdir, and so forth. If you list your root directory, you should see a directory called sdcard. This is the default storage directory for user files on Android devices, so it’s a good place to put your stuff.

You’ll need some songs for this app, so create a new directory on your device for the songs you’ll use. From Terminal, type:

$ adb shell
shell@android:/ $ mkdir sdcard/myMusic
shell@android:/ $ exit

Now that you’ve dropped out of the adb shell and you’re back in your computer’s shell, find some music on your computer that you want to transfer to the device, change to the directory it’s in, and type:

$ adb push songName.mp3 /sdcard/myMusic/.

This will move the file from your machine to the device. You can log back into the device with adb shell to check that it’s where you thought it was.

The PhoneGap Media API

To play music in this app, you’re going to use PhoneGap’s Media API, which allows you to both record and play back audio. For this app, you’ll just use it for playback, but you can find the full documentation in the PhoneGap Guides, under “Media.” The Media API is initialized by creating a new media playback object, like so:

var playBack = new Media(source, success, error, status);

The Media object’s parameters allow you to control its behavior. The source URL can be a local file using the format file:///path/to/filename, or a remote URL using http://example.com/path/to/filename. The success, error, and status handlers are all optional. Success gets called after a Media object has completed the current play, record, or stop action. Error is called when there’s a playback error, and status is called whenever you want to update the status of the playback. The playback status is useful for changing the behavior of control buttons, and for stopping and starting audio when your app is paused and resumed.

The Media object has the functions you’d expect from a Media object: you can start, stop, or pause playback; get the current position or the duration of the song; skip to a particular position; release the OS’s audio resources; and start and stop recording. You’ll see some of these functions used in the following code.

In order to use the Media API, you’ll need to install two plug-ins: the Media plug-in itself and the Filesystem plug-in that you’ll use to find files in a directory. Install them like so:

$ cordova plugin add 
  https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git
$ cordova plugin add 
  https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git

You’ll also need to modify your app’s AndroidManifest.xml to include the following permissions:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

In addition, your app’s config.xml (found in platforms/android/res/xml/config.xml) must contain the following plug-in:

<plugin name="Media" value="org.apache.cordova.AudioHandler" />

All of these XML settings will be added by default when you use the cordova create command, as you’ve been doing all along.

The User Interface

Now that you understand the new tools being used in this project, it’s time to create the project and start writing some code.

To do this, you’ll need the tools you saw in earlier chapters, and some new hardware:

  • An NFC-enabled Android device
  • The Android Software Developers Kit
  • A text editor
  • Cordova CLI installed on your machine
  • Node.js and npm installed
  • The minimized version of the Zepto jQuery library, available from Zepto JS
  • An assortment of NFC Tags (for best results across multiple devices, stick with the NFC Forum types; see Device-to-Tag Type Matching for more on which tags work with which devices)
  • Philips Hue Lighting system
  • A Bluetooth audio receiver, such as the Belkin Bluetooth Music Receiver or the HomeSpot NFC-enabled Bluetooth Audio Receiver

To get started, create a new project:

$ cordova create ~/MoodSetter com.example.moodsetter MoodSetter 1
$ cd ~/MoodSetter 2
$ cordova platform add android
$ cordova plugin add https://github.com/don/phonegap-nfc
$ cordova plugin add 
  https://git-wip-us.apache.org/repos/asf/cordova-plugin-media.git
$ cordova plugin add 
  https://git-wip-us.apache.org/repos/asf/cordova-plugin-file.git
1

Windows users should type %userprofile%MoodSetter instead of ~/Mood Setter.

2

Windows users should type /d %userprofile%MoodSetter instead of ~/MoodSetter.

Download the minimized version of the Zepto JS library and copy the zepto.min.js file to your www/js directory.

When the project’s ready to go, open the index.html and the index.js files from the www directory.

Here’s the HTML for the user interface, as shown in Figure 6-2. You’re adding some CSS styling at the top this time, to make the buttons and sliders a bit easier to touch if you have big fingers:

<!DOCTYPE html>

<html>
   <head>
      <title>Mood Setter</title>
      <style>
      input {
         margin-top: 10px;
         margin-bottom: 10px;
      }
      input[type='range'] { /* finger sized sliders */
         display:block;
         width: 100%;
      }
      .button {
         display: inline-block;
         padding: 10px;
         min-width: 80px;
         margin-top: 10px;
         margin-bottom: 5px;
         text-align: center;
         background-color: #177bbd;
         color: #ffffff;
      }
      select {
         font-size: 1.25em;
      }
      </style>
   </head>
   <body>
      <div class="app">
         <div id="tagMode">
            <h3 id="modeButton" class="button">Change Mode</h3>
            <span id="modeValue"></span><br />
            <span id="tagModeMessage"></span>
         </div>

         <div id="lightControl">
            Light Location:
            <select id="lightNumber">
               <option value="1" >No Lights Found</option>
               <option value="2" >No Lights Found</option>
               <option value="3" >No Lights Found</option>
            </select><br />
            <label for="lightOn">On:</label>
               <input type="checkbox" id="lightOn"><br />
            Brightness: <input type="range" id="bri" min="0" max="254">
            Hue: <input type="range" id="hue"  min="0" max="65535">
            Saturation: <input type="range" id="sat" min="0" max="254">
         </div>

         <div id="songPicker">
            Song:
               <div>
                  <select id="songs">
                     <option>Loading Songs</option>
                  </select>
               </div>
               <div>
                  <h2 id="playButton" class="button">Play</h2>
                  <h2 id="stopButton" class="button">Stop</h2>
               </div>
         </div>

         <div id="messageDiv">No message</div>
      </div>
      <script type="text/javascript" src="js/zepto.min.js"></script>
      <script type="text/javascript" src="cordova.js"></script>
      <script type="text/javascript" src="js/index.js"></script>
      <script>
         app.initialize();
      </script>
   </body>
</html>

There are four main sections to the app div:

  • The mode div, which contains the mode button and reports the mode you’re in
  • The lightControl div, which contains all the controls for the lights
  • The songPicker div, which lets you pick and control songs
  • The message div, for general messages

All of the user controls have listeners that you’ll see in the index.js file.

The names for the lighting controls in the HTML were chosen to match the names of the properties in the Hue’s JSON format, as you’ve seen previously. The ranges for each of the objects are set using the ranges for those properties as specified in Philips’ Hue specification. The initial values are set by querying the hub. The names of the lights in the lightNumber drop-down menu are changed dynamically when the hub is queried as well.

The Application Code

The main logic of the application will be in index.js as usual. The full source listing for this application can be found on GitHub. The JavaScript’s going to start a bit differently than usual, however. Instead of starting with the app variable, start by making a variable to keep track of all the hub’s properties as follows:

var hub = {                       // a copy of the hue settings
   lights: {},                    // states & names for the individual lights
   ipaddress: null,               // IP address of the hue
   appTitle: "NFC Mood Setter",   // The App name
   username: "MoodSetterApp",     // the App's username
   currentLight: 1                // the light you're currently setting
 };

You’ll use this object to store all the settings you need from the hub locally, as well as the app’s title, your username, and the light that you’re currently manipulating with the lighting controls in the user interface. There’s more data in the full Hue JSON reply than you need, or that you can fit on a typical NFC tag, so you’ll just take out the ones you need when you query the hub.

Next, start the app variable, beginning with a few parameters that will be used throughout:

var app = {
   mode: 'write',                 // the tag read/write mode
   mimeType: 'text/hue',          // the NFC record MIME Type
   musicPath: 'file:///sdcard/myMusic/',   // path to your music
   songPlaying: null,             // media handle for the current song playing
   songTitle: null,               // title of the song
   musicState: 0,                 // state of the song: playing stopped, etc.
   songUri: null,

Housekeeping Functions

After that, it’s time for the usual housekeeping functions, initialize() and bindEvents(). As you can see here bindEvents() initializes listeners for all your user controls, as well as for pausing and resuming the app:

   /*
      Application constructor
   */
   initialize: function() {
      this.bindEvents();
      console.log("Starting Mood Setter app");
   },

   /*
      binds events that are required on startup to listeners.
   */
   bindEvents: function() {
      document.addEventListener('deviceready', this.onDeviceReady, false);

      // hue faders from the UI: brightness, hue, saturation:
      bri.addEventListener('touchend', app.setBrightness, false);
      hue.addEventListener('touchend', app.setHue, false);
      sat.addEventListener('touchend', app.setSaturation, false);
      lightOn.addEventListener('change', app.setLightOn, false);
      lightNumber.addEventListener('change', app.getHueSettings, false);

      // buttons from the UI:
      modeButton.addEventListener('click', app.setMode, false);
      songs.addEventListener('change', app.onSongChange, false);
      playButton.addEventListener('touchstart', app.toggleAudio, false);
      stopButton.addEventListener('touchstart', app.stopAudio, false);

      // pause and resume functionality for the whole app:
      document.addEventListener('pause', this.onPause, false);
      document.addEventListener('resume', this.onResume, false);
   },

In the onDeviceReady() handler, you’re going to make the initial query to the Hue hub for settings using a function called findControllerAddress() that you’ll write shortly. You’ll also set the read/write mode of the app. You’re also going to set up three NFC listeners. The first two will listen for any NFC-formatable or NDEF tag, so you can write to it in write mode. The third will listen for MIME-types, specifically for the text/hue type. Any tag with a record containing that MIME-type should contain data for you to set the lights, and ideally, a second record for playing music as well. In the MIME media handler, you’ll need to differentiate between read and write mode. You’ll see that later on.

The handler ends with a call to a new function, getSongs(), which you’ll see shortly. It looks up the directory you assigned for music and populates the selector in the HTML with the names and URIs of the songs:

   /*
       runs when the device is ready for user interaction.
   */
   onDeviceReady: function() {
      app.clear();                   // clear any messages onscreen
      app.findControllerAddress();   // find address and get settings
      app.setMode();                 // set the read/write mode for tags

      // listen for NDEF Formatable tags (for write mode):
      nfc.addNdefFormatableListener(
         app.onWritableNfc,          // tag successfully scanned
         function (status) {         // listener successfully initialized
            console.log("Listening for NDEF-formatable tags.");
         },
         function (error) {          // listener fails to initialize
            app.display("NFC reader failed to initialize " +
               JSON.stringify(error));
         }
      );

      // listen for NDEF tags so you can overwrite MIME message onto them
      nfc.addNdefListener(
         app.onWritableNfc,          // NDEF type successfully found
         function() {                // listener successfully initialized
            console.log("listening for Ndef tags");
         },
         function(error) {           // listener fails to initialize
            console.log("ERROR: " + JSON.stringify(error)); }
      );

      // listen for MIME media types of type 'text/hue' (for read or write)
      // Android calls the most specific listener, so text/hue tags end up here
      nfc.addMimeTypeListener(
         app.mimeType,               // what type you're listening for
         app.onMimeMediaNfc,         // MIME type successfully found
         function() {                // listener successfully initialized
            console.log("listening for mime media tags");
         },
         function(error) {           // listener fails to initialize
            console.log("ERROR: " + JSON.stringify(error)); }
      );

      app.getSongs();                // load the drop-down menu with songs
   },

After onDeviceReady(), add your old workhorse handlers, display() and clear(), for showing messages in the message div. They’re the same as usual here:

   /*
      appends @message to the message div:
   */
   display: function(message) {
      var textNode = document.createTextNode(message),
         lineBreak = document.createElement("br");      // a line break

      messageDiv.appendChild(lineBreak);                // add a line break
      messageDiv.appendChild(textNode);                 // add the message node
   },

   /*
      clears the message div:
   */
   clear: function() {
      messageDiv.innerHTML = "";
   },

Global Event Handlers

Next come your first event handlers, onPause(), onResume(), and the NFC event handlers. These are the only events your app will listen for that aren’t generated directly by the user controls. They’re pretty basic. The first two check the state of media playback, and either pause or resume it as appropriate:

   /*
      This is called when the app is resumed
   */
   onResume: function() {
      if (app.musicState === Media.MEDIA_PAUSED) {
         app.startAudio();
      }
   },

   /*
      runs when an NdefListener or NdefFormatableListener event occurs.
   */
   onWritableNfc: function(nfcEvent) {
      if (app.mode === "write") {
         app.makeMessage();  // in write mode, write to the tag
      }
   },

The NFC event handlers will do just that: handle NFC events. The onWritableNfc handler is the handler for both the NdefListener and the NdefFormatableListener. The formatable listener lets you write to any blank tag that can be formatted, and the more generic NdefListener lets you overwrite existing NFC tags with MIME media-types when you’re in write mode. For example, if you had a tag that you’d used for a previous application, and that application used a MIME type that’s not the one you’re listening for in the MimeTypeListener, the NdefListener would catch it.

The onMimeMediaNfc() handler gets called only when one of your tags with the MIME-type text/hue shows up. If you’re in read mode, it reads the tag and attempts to change the music and the lights. If you’re in write mode, it overwrites the tag:

   /*
      runs when an NdefListener or NdefFormatableListener event occurs.
   */
   onWritableNfc: function(nfcEvent) {
      if (app.mode === "write") {
         app.makeMessage();  // in write mode, write to the tag
      }
   },

   /*
      runs when a MimeMedia event occurs.
   */
   onMimeMediaNfc: function(nfcEvent) {
      var tag = nfcEvent.tag;

      if (app.mode === "read") {   // in read mode, read the tag
         // when app is launched by scanning text/hue tag
         // you need to add a delay so the call to get the
         // hub address can finish before you call the api.
         // if you have the address, delay 0, otherwise, delay 50:
         var timeout = hub.ipaddress ? 0 : 500;

         setTimeout(function() {
            app.readTag(tag);
         }, timeout);

      } else {               // if you're in write mode
         app.makeMessage();  // in write mode, write to the tag
      }
   },

Since tag actions depend on the mode you’re in, you need a mode handler. This is the event handler for the mode button. You can see where it’s initialized in bindEvents(), previously. It simply changes the app.mode variable, and the text of the tag mode div so the user knows what mode she’s in:

   /*
      Sets the tag read/write mode for the app.
   */
   setMode: function() {
      if (app.mode === "write") {    // change to read mode
         app.mode = "read";
         tagModeMessage.innerHTML = "Tap a tag to read its settings.";
      } else {                       // change to write mode
         app.mode = "write";
         tagModeMessage.innerHTML = "Tap a tag to write current settings.";
      }
      modeValue.innerHTML = app.mode; // set text in the UI
   },

Hub Communication Functions

Once the basic initialization housekeeping is done, you need to query the hub, get the settings you need, and copy them into the hub object. The querying is all done using the jQuery ajax function, from the Zepto JS library that you’re using. The findControllerAddress() function shown here makes an Ajax query to Meet Hue’s IP locator page to see if it returns any local hub IP addresses. If if finds one, it sets hub.ipaddress. If not, it prompts the user that no hub was found:

   /*
      Find the Hue controller address and get its settings
   */
   findControllerAddress: function() {
      $.ajax({
         url: 'http://www.meethue.com/api/nupnp',
         dataType: 'json',
         success: function(data) {
            // expecting a list with a property called internalipaddress:
            if (data[0]) {
               hub.ipaddress = data[0].internalipaddress;
               app.getHueSettings();    // copy the Hue settings locally
            } else {
               navigator.notification.alert(
                  "Couldn't find a Hue on your network");
            }
         },
         error: function(xhr, type){    // alert box with the error
            navigator.notification.alert(xhr.responseText +
               " (" + xhr.status + ")", null, "Error");
         }
      });
   },

Note

If you have a network that’s not running DHCP, or your Hue and your Android device are running on different networks, you’ll have to modify this program. Your Hue will need a public IP address, or you’ll need to enable port forwarding on the router to which the Hue’s connected. When you’ve got that done, enter the Hue’s IP address for hub.ipaddress at the beginning of your program, and comment out the line in findControllerAddress() that reads

hub.ipaddress = data[0].internalipaddress;

Once you’ve made these changes, the app will always look for the hub at the fixed address you’ve given it.

After you’ve found the hub, you need to register your username and app name with it. The following functions, ensureAuthorized() and authorize(), take care of that. The first, ensureAuthorized(), just checks with the hub to see if your username and app name are registered with it. If it fails, it calls authorize() and attempts to get authorization. Both of these prompt the user when they fail, as you need to press the hub’s link button to authorize a new user and app:

   /*
      Checks that the username is authorized for this hub.
   */
   ensureAuthorized: function() {
      var message;      // response from the hub

      // query the hub:
      $.ajax({
         type: 'GET',
         url: 'http://' + hub.ipaddress + '/api/' + hub.username,
         success: function(data){      // successful reply from the hub
            if (data[0].error) {
               // if not authorized, users gets an alert box
               if (data[0].error.type === 1) {
                  message = "Press link button on the hub.";
               } else {
                  message = data[0].error.description;
               }
               navigator.notification.alert(message,
                  app.authorize, "Not Authorized");
            }
         },
         error: function(xhr, type){    // error message from the hub
            navigator.notification.alert(xhr.responseText +
               " (" + xhr.status + ")", null, "Error");
         }
      });
   },

   /*
      Authorizes the username for this hub.
   */
   authorize: function() {
      var data = {                      // what you'll send to the hub:
         "devicetype": hub.appTitle,    // device type
         "username": hub.username       // username
      },
      message;                          // reply from the hub

      $.ajax({
         type: 'POST',
         url: 'http://' + hub.ipaddress + '/api',
         data: JSON.stringify(data),
         success: function(data){       // successful reply from the hub
            if (data[0].error) {
               // if not authorized, users gets an alert box
               if (data[0].error.type === 101) {
                  message = "Press link button on the hub, then tap OK.";
               } else {
                  message = data[0].error.description;
               }
               navigator.notification.alert(message,
                  app.authorize, "Not Authorized");
            } else {                   // if authorized, give an alert box
               navigator.notification.alert("Authorized user " +
                  hub.username);
               app.getHueSettings();   // if authorized, get the settings
            }
         },
         error: function(xhr, type){   // error reply from the hub
            navigator.notification.alert(xhr.responseText +
               " (" + xhr.status + ")", null, "Error");
         }
      });
   },

Once you’re registered with the hub, you need to get the settings from it and copy them to your local hub object. Both findHueSettings() and authorize(), shown previously, call the function that follows. It doesn’t copy all of the hub’s settings, just the ones you need for setting on/off, brightness, hue, and saturation for the lights that the hub is controlling:

   /*
      Get the settings from the Hue and store a subset of them locally
      in hub.lights.  This is for both setting the controls, and for
      writing to tags:
   */
   getHueSettings: function() {
      // query the hub and get its current settings:

      $.ajax({
         type: 'GET',
         url: 'http://' + hub.ipaddress + '/api/' + hub.username,
         success: function(data) {
            if (!data.lights) {
               // assume they need to authorize
               app.ensureAuthorized();
            } else {
               // the full settings take more than you want to
               // fit on a tag, so just get the settings you want:
               for (var thisLight in data.lights) {
                  hub.lights[thisLight] = {};
                  hub.lights[thisLight].name = data.lights[thisLight].name;
                  hub.lights[thisLight].state = {};
                  hub.lights[thisLight].state.on =
                     data.lights[thisLight].state.on;
                  hub.lights[thisLight].state.bri =
                     data.lights[thisLight].state.bri;
                  hub.lights[thisLight].state.hue =
                     data.lights[thisLight].state.hue;
                  hub.lights[thisLight].state.sat =
                     data.lights[thisLight].state.sat;
               }
               app.setControls();
            }
         }
      });
   },

The function you just wrote, which gets the settings, implies a complementary function to put new settings back to the Hue hub. You’ll need such a function for whenever the user changes the lighting controls. So here’s putHueSettings(), which takes a JSON object, converts it to a string, and sends it to the hub:

   /*
      sends settings to the Hue hub.
   */
   putHueSettings: function(settings, lightId) {
      // if no lightId is sent, assume the current light:
      if (!lightId) {
         lightId = hub.currentLight;
      }

      // if the light's not on, you can't set the other properties.
      // so delete the other properties before sending them.
      // if "on" is a property and it's false:
      if (settings.hasOwnProperty("on") && !settings.on) {
         // go through all the other properties:
         for (var prop in settings) {
            // if this property is not inherited:
            if (settings.hasOwnProperty(prop)
               && prop != "on") {      // and it's not the "on" property
               delete(settings[prop]); // delete it
            }
         }
      }

      // set the property for the light:
      $.ajax({
         type: 'PUT',
         url: 'http://' + hub.ipaddress + '/api/' + hub.username +
            '/lights/' + lightId + '/state',
         data: JSON.stringify(settings),
         success: function(data){
            if (data[0].error) {
               navigator.notification.alert(JSON.stringify(data),
                  null, "API Error");
            }
         },
         error: function(xhr, type){
            navigator.notification.alert(xhr.responseText + " (" +
               xhr.status + ")", null, "Error");
         }
      });
   },

This function doesn’t care what the settings are, nor does it bother to update the local hub object with the settings. It leaves all of that to the functions that call it. However, it handles one important error: the Hue won’t let you set a given light’s hue, brightness, and saturation if the light is off. So this function checks to see if the settings being sent contain an “on” property and if it’s false. If so, then the function eliminates the other properties and just sends the “on” property (which is set to false).

Lighting User Interface Event Handlers

The next set of functions are the user interface control event handlers.

With the hub settings obtained by getHueSettings(), you can set the user interface controls to match the current settings of the lights they’re controlling. At the end of a successful request, getHueSettings() calls the following function, setControls(), to update the value of the UI controls:

   /*
      Set the value of the UI controls using the values from the Hue:
   */
   setControls: function() {
      hub.currentLight = lightNumber.value;

      // set the names of the lights in the dropdown menu:
      // (in a more fully developed app, you might generalize this)
      lightNumber.options[0].innerHTML = hub.lights["1"].name;
      lightNumber.options[1].innerHTML = hub.lights["2"].name;
      lightNumber.options[2].innerHTML = hub.lights["3"].name;

      // set the state of the controls with the current choice:
      var thisLight = hub.lights[hub.currentLight];
      hue.value = thisLight.state.hue;
      bri.value = thisLight.state.bri;
      sat.value = thisLight.state.sat;
      lightOn.checked = thisLight.state.on;
   },

The user interface controls for the lighting are handled by a series of event handlers, each of which sets a specific property of the light. Figure 6-5 shows a detail of the lighting controls. The drop-down menu’s event handler is getHueSettings(), which gets the current settings and updates the other controls.

The lighting controls for the app
Figure 6-5. The lighting controls for the app

The on, brightness, hue, and saturation controls apply to whatever light is chosen in the Light Location drop-down menu. Each of the individual event handlers gets the value from the control, then sets that value in the local hue object. Finally, each handler uses the new setting to update the Hue hub:

   /*
      These functions set the properties for a Hue light:
      Brightness, Hue, Saturation, and On State
   */
   setBrightness: function() {
      // get the value from the UI control:
      var thisBrightness = parseInt(bri.value, 10);
      // get the property from hub object:
      var thisLight = hub.lights[hub.currentLight];
      // change the property in hub object:
      thisLight.state.bri = thisBrightness;
      // update Hue hub with the new value:
      app.putHueSettings({ "bri": thisBrightness });
   },

   setHue: function() {
      // get the value from the UI control:
      var thisHue = parseInt(hue.value, 10);
      // get the property from hub object:
      var thisLight = hub.lights[hub.currentLight];
      // change the property in hub object:
      thisLight.state.hue = thisHue;
      // update Hue hub with the new value:
      app.putHueSettings( { "hue": thisHue } );
   },

   setSaturation: function() {
      // get the value from the UI control:
      var thisSaturation = parseInt(bri.value, 10);
      // get the property from hub object:
      var thisLight = hub.lights[hub.currentLight];
      // change the property in hub object:
      thisLight.state.sat = thisSaturation;
      // update Hue hub with the new value:
      app.putHueSettings({ "sat": thisSaturation });
   },

   setLightOn: function() {
      // get the value from the UI control:
      var thisOn = lightOn.checked;
      // get the property from hub object:
      var thisLight = hub.lights[hub.currentLight];
      // change the property in hub object:
      thisLight.state.on = thisOn;
      // update Hue hub with the new value:
      app.putHueSettings( { "on": thisOn } );
   },

These event handlers are the functions that ensure that the UI controls, the local hue object, and the remote Hue hub are in sync.

When you get to the tag-reading functionality, you’ll need a function that takes all the lighting properties from the tag and writes them to the Hue hub. You can assume that this function is getting the states for all the lights passed to it as a parameter. Group that function with the previous functions:

   /*
      Sets the state for all the lights. Assumes it's getting
      a JSON object like this:
      {
       "1": {"state": {"on":true,"bri":65,"hue":44591,"sat":254}},
       "2": {"state": {"on":true,"bri":254,"hue":13122,"sat":211}},
       "3": {"state": {"on":true,"bri":255,"hue":14922,"sat":144}}
      }
   */
   setAllLights: function(settings) {
      for (var thisLight in settings) {
          app.putHueSettings(settings[thisLight].state, thisLight);
      }
   },

Music User Interface Event Handlers

Now that the lighting user interface elements are taken care of, it’s time to write the event handlers for the music playback. Figure 6-6 shows a detail of those controls.

The music controls for the app
Figure 6-6. The music controls for the app

The first music event handler to write is the getSongs() handler. This handler is called in the onDeviceReady() handler. It uses PhoneGap’s File API to read the directory listing of the music directory and get a list of all the song names and URIs to populate the music selector in the UI.

This function is heavily dependent on callbacks, so it helps to start reading it from the bottom. It starts by looking for the directory using resolveLocalFileSystemURI(), and when that succeeds, it calls foundDirectory(). That looks for the directory entries, and when it succeeds, it calls foundFiles() to fill the selector in the HTML. There’s a common failure handler for all of these in case they fail: an alert box pops up with the cause of the failure.

   /*
      gets a list of the songs in your music directory and
      populates an options list in the UI with them
   */
    getSongs: function() {
      // failure handler for directoryReader.readEntries():
      var failure = function(error) {
         alert("Error: " + JSON.stringify(error));
      };

      // success handler for directoryReader.readEntries():
      var foundFiles = function(files) {
         if (files.length > 0) {
            // clear existing songs
            songs.innerHTML = "";
         } else {
            navigator.notification.alert(
               "Use `adb` to add songs to " + app.musicPath, {}, "No Music");
         }

         // once you have the list of files, put the valid ones in the selector:
         for (var i = 0; i < files.length; i++) {
            // if the filename is a valid file:
            if (files[i].isFile) {
               // create an option element:
               option = document.createElement("option");
               // value = song's filepath:
               option.value = files[i].fullPath;
               // label = song name:
               option.innerHTML = files[i].name;
               // select the first one and add it to the selector:
               if (i === 0) { option.selected = true; }
               songs.appendChild(option);
            }
         }
         app.onSongChange();        // update the current song
      };

      // success handler for window.resolveLocalFileSystemURI():
      var foundDirectory = function(directoryEntry) {
         var directoryReader = directoryEntry.createReader();
         directoryReader.readEntries(foundFiles, failure);
      };

      // failure handler for window.resolveLocalFileSystemURI():
      var missingDirectory = function(error) {
         navigator.notification.alert("Music directory " + app.musicPath +
            " does not exist", {}, "Music Directory");
      };

      // look for the music directory:
      window.resolveLocalFileSystemURI(app.musicPath,
        foundDirectory, missingDirectory);
   },

The function just shown and the event listener for the song picker call a function called onSongChange(), which changes the URI of the song. This, in turn, calls setSong(), which pulls the song name out of the URI it’s handed, and sets up the conditions for playing a new song:

   /*
      changes the song URI and sets the new song:
   */
   onSongChange: function(event) {
      var uri = songs[songs.selectedIndex].value;
      app.setSong(uri);
   },

   /*
      sets the song URI
   */
   setSong: function(uri) {
      if (uri !== app.songUri) {
         app.stopAudio();            // stop whatever is playing
         app.songPlaying = null;     // clear the media object
         app.musicState = 0;         // clear the music state
         app.songUri = uri;          // saves the URI of the song
         // uses the filename for a title
         app.songTitle = uri.substring(uri.lastIndexOf('/')+1);
         $(songs).val(uri);          // ensure the UI matches selection
      }
   },

You’ll see setSong() called directly without onSongChange() later, when you read a new song from an NFC tag.

The play/pause control is handled by a function called toggleAudio(), which checks the state of the media controller, and either starts playback, pauses it, or resumes it, as appropriate:

  /*
      toggles audio playback depending on current state of playback.
   */
   toggleAudio: function(event) {
      switch(app.musicState) {
         case undefined:             // if playback is undefined
         case Media.MEDIA_NONE:      // or if no media is playing
            app.startAudio();        // start playback
            break;
         case Media.MEDIA_STARTING:  // if media is starting
            state = "music starting";// no real change
            break;
         case Media.MEDIA_RUNNING:   // if playback is running
            app.pauseAudio();        // pause it
            break;
         case Media.MEDIA_PAUSED:    // if playback is paused
         case Media.MEDIA_STOPPED:   // or stopped
            app.playAudio();         // resume playback
            break;
      }
   },

The startAudio() function is called by the play button, and you’ll need it for when you read tags with valid song URIs on them as well. If there’s no playback currently, this function will check to see if it’s got a song name, and if so, start playback with it.

As you saw earlier, the Media API uses three callback functions: success, error, and status. All three of those are shown here as well. They don’t do much, except for the status handler, which updates the playback status:

   /*
      Start playing audio from your device
   */
   startAudio: function() {
      var success = false;

      // attempt to instantiate a song:
      if (app.songPlaying === null) {
         // Create Media object from songUri
         if (app.songUri) {

            app.songPlaying = new Media(
               app.songUri,      // filepath of song to play
               app.audioSuccess, // success callback
               app.audioError,   // error callback
               app.audioStatus   // update the status callback
            );
         } else {
            navigator.notification.alert("Pick a song!");
         }
      }

      // play the song:
      app.playAudio();
   },

   /*
      called when playback successfully initiated.
   */
   audioSuccess: function() {
      console.log("Audio success");
   },

   /*
      displays an error if there's a problem with playback.
   */
   audioError: function(error) {

      // Without timeout message is overwritten by "Currently Playing: ..."
      setTimeout(function() {
         app.display("Unable to play song.");
      }, 300);

   },

   /*
      updates the running audio status.
   */
   audioStatus: function(status) {
      app.musicState = status;
   },

The play, pause, and stop handlers are all very similar. They check to see that there’s current media playback, take their action, update the button labels, and print the status and song name to the message div. Here they are:

   /*
      resumes audio playback and changes state of the play button.
   */
   playAudio: function() {
      if (app.songPlaying) {             // if there's current playback
         app.songPlaying.play();         // play
         playButton.innerHTML = "Pause"; // update the play/pause button

         // clear the message div and display song name and status:
         app.clear();
         app.display("Currently playing: " + app.songTitle);
      }
   },

   /*
      pauses audio playback and changes state of the play button.
   */
   pauseAudio: function() {
      if (app.songPlaying) {             // if there's current playback
         app.songPlaying.pause();        // pause
         playButton.innerHTML = "Play";  // update the play/pause button

         // clear the message div and display song name and status:
         app.clear();
         app.display("Paused playing: " + app.songTitle);
      }
   },

   /*
      stops audio playback and changes state of the play button.
   */
   stopAudio: function() {
       if (app.songPlaying) {            // if there's current playback
         app.songPlaying.stop();         // stop
         playButton.innerHTML = "Play";  // update the play/pause button

         // clear the message div and display song name and status:
         app.clear();
         app.display("Stopped playing: " + app.songTitle);
      }
   },

NFC Event Handlers

With hub communication, music, and user interface out of the way, you can write the tag-reading and -writing functions. The first of these, readTag(), has the same structure as similar functions for reading that you’ve written already. It will read the tag, extract all the NDEF records in the NDEF message, and attempt to parse them. If it’s got a URI record, it will verify that the URI is a song’s address, and attempt to play it. If it’s got a MIME record of type text/hue, it will assume the record contains the JSON-encoded state for the lights, and attempt to send those settings to the hub:

   /*
      reads an NDEF-formatted tag.
   */
   readTag: function(thisTag) {
      var message = thisTag.ndefMessage,
         record,
         recordType,
         content;

      for (var thisRecord in message) {
         // get the next record in the message array:
         record = message[thisRecord];
         // parse the record:
         recordType = nfc.bytesToString(record.type);
         // if you've got a URI, use it to start a song:
         if (recordType === nfc.bytesToString(ndef.RTD_URI)) {

            content = ndef.uriHelper.decodePayload(record.payload);

            // make sure the song exists
            window.resolveLocalFileSystemURI(
               content,
               function() {
                  app.setSong(content);
                  app.startAudio();
               },
               function() {
                  navigator.notification.alert("Can't find " + content,
                     {}, "Missing Song");
               }
            );
         }

         // if you've got a hue JSON object, set the lights:
         if (recordType === 'text/hue') {
            // tag should be TNF_MIME_MEDIA with a type 'text/hue'
            // assume you get a JSON object as the payload
            // JSON object should have valid settings info for the hue
            // http://developers.meethue.com/1_lightsapi.html

            content = nfc.bytesToString(record.payload);
            content = JSON.parse(content);    // convert to JSON object

            app.setAllLights(content.lights); // use it to set lights
         }
      }
   },

For write mode, you’ll need to make an NDEF message with two NDEF records, one for the song and one for the lights. The lighting record should come first, so that it can trigger the MIME-type listener when you’re in read mode. Since the hub object contains some properties that are not part of the Hue protocol, you’ll need to extract the lights property and make a new object with just it. That’ll be your first NDEF record. The second will be the song name.

Once you’ve got your NDEF record assembled, you can use the same writeTag() function you’ve used before to write the message to the tag. And once you’re done with that, you’ve reached the end of the app!

   /*
      makes an NDEF message and calls writeTag() to write it to a tag:
   */
   makeMessage: function() {
      var message = [];

      // put the record in the message array:
      if (hub.lights !== {}) {
         var huePayload = JSON.stringify({"lights": hub.lights});
         var lightRecord = ndef.mimeMediaRecord(app.mimeType, huePayload);
         message.push(lightRecord);
      }
      if (app.songUri !== null) {
         var songRecord = ndef.uriRecord(app.songUri);
         message.push(songRecord);
      }

      //write the message:
      app.writeTag(message);
   },

   /*
      writes NDEF message @message to a tag:
   */
   writeTag: function(message) {
      // write the record to the tag:
      nfc.write(
         message,                  // write the record itself to the tag
         function () {             // when complete, run this callback function:
            app.clear();           // clear the message div
            app.display("Wrote data to tag.");     // notify user in message div
            navigator.notification.vibrate(100);   // vibrate the device as well
         },
         function (reason) {       // runs if the write command fails
            navigator.notification.alert(reason,
               function() {}, "There was a problem");
         }
      );
   }
};        // end of app

Again, the full source listing for this app can be found on GitHub.

When you run the app, it will check whether it’s registered with the Hue hub. If not, it will prompt you to press the link button in order to do so. Once it’s contacted the hub, it will populate the Light Location drop-down. From there, you’ll be able to change the lights and music independently of each other, and of the mode. If you’re in write mode, bringing an NDEF formatable tag in range will automatically write your current settings to that tag. In read mode, the app will automatically read the tag for a song and a set of lighting settings.

You may find that you need to hold the tag close to the phone without moving for a second or so in order to get all the settings to work. Pulling away before the device has read the tag and acted on it will cause read errors. This is the most you’ve probably written to a tag before, and it shows you that the interaction between reading and writing and the actions they are meant to trigger can have effects on the physical interaction between user, device, and tag. The more you can write your program to keep them independent of each other, the better the interaction will be.

Caution

If you decide to get fancy with the app and add your own styling through too much CSS, you may notice some performance effects on the interaction. We noticed that the Media API in particular was adversely affected by CSS stylings of the user interface. Solving those problems is beyond the scope of this book, but for more on this, look into hardware acceleration for CSS and PhoneGap.

Enabling Background Dispatch

Although your app will read tags just fine when it’s in the foreground, you really want it to read text/hue MIME types even when it’s in the background. You learned how to make this happen in Chapter 5. Open your AndroidManifest.xml file and add a new intent filter as follows:

 <intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/hue" />
 </intent-filter>

Once you’ve updated the app, quit it, and try tapping a tag that’s got a text/hue record on it. The app should launch automatically, change the lights, and play the music.

Conclusion

As you can see, the majority of making an NFC-enabled app isn’t really about the NFC. The real NFC-related work is in thinking through how to format the data for the tag in a way that’s most compatible with the rest of the functions of the app. In this case, you chose a Well-Known type record with a URI type definition because it made things easy for the Media API, and a MIME type record with a text/hue type because it made it easy to work with the Hue API. Once the user interface work was done, integrating the tag reading and writing was no different than in any of the sample apps you’ve built already. Keeping the NFC work encapsulated like this, and thinking through the protocols needed and the data structures to be used in advance will make writing NFC-enabled apps much easier for you as you move forward.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset