Creating Web Things with Python, Node.js, and Java

Tips

Hacking / Tips 28 Views

The Mozilla IoT team recently released the Things Framework, which allows you to build IoT devices that speak the Web Thing API. Last week, James built an ESP8266 web thing. This time, I’m going to show you how to build web things with Python, Node.js, or Java. These languages are definitely not optimal for small embedded devices; this tutorial is intended for higher-end devices that can run these languages with ease, or even your own desktop computer.

To demonstrate, we’ll be turning the Music Player Daemon (MPD) into a web thing. The libraries we’ll be using here are webthing-python, webthing-node, and webthing-java.

Intro to the Music Player Daemon (MPD)

The Music Player Daemon is an open-source music player that has been around since 2003. MPD operates in a client/server model, and is controllable over TCP with a simple text-based protocol. I won’t cover installation here, but MPD can be installed via your package manager on Linux, Homebrew on Mac OS X, or with binaries on Windows.

Some Quick Notes

Although this example is written for MPD, it would be easily portable to other music players with an API, or you could build your own player using this example as a base. More importantly, this example is primarily meant to demonstrate the Things Framework, which can be used to create an endless variety of web things.

The Python, Node.js, and Java web thing libraries all have essentially the same API. While they may not be entirely idiomatic for their respective languages, doing this allows us to maintain all the libraries more easily, which is incredibly valuable while the Web Thing API is still a living draft.

Getting Started

To start, initialize an empty project for your language of choice. For example, you might create a new project in IntelliJ IDEA for Java, or run npm init to start a new Node.js project. You’ll then want to add the webthing library as a dependency. Instructions for doing so can be found on the respective Github project page, or you can look here.

Now we’re ready to write some code. Essentially we’ll do the following:

  1. Create a Thing.
    1. Define its properties.
    2. Define its actions.
    3. Define its events.
  2. Attach the Thing to a WebThingServer.

Create a Thing

First off, let’s import our library of choice.

Python
from webthing import Action, Event, Property, Thing, Value, WebThingServer
JavaScript
const {
  Action,
  Event,
  Property,
  Thing,
  Value,
  WebThingServer,
} = require('webthing');
Java
import org.mozilla.iot.webthing.Action;
import org.mozilla.iot.webthing.Event;
import org.mozilla.iot.webthing.Property;
import org.mozilla.iot.webthing.Thing;
import org.mozilla.iot.webthing.Value;
import org.mozilla.iot.webthing.WebThingServer;

Now, we’ll create a basic subclass of Thing.

Python
class MPDThing(Thing):
    def __init__(self):
        Thing.__init__(self, 'MPD', 'musicPlayer', 'Music Player Daemon')
JavaScript
class MPDThing extends Thing {
  constructor() {
    super('MPD', 'musicPlayer', 'Music Player Daemon');
  }
}
Java
public class MPDThing extends Thing {
    public MPDThing() {
        super("MPD", "musicPlayer", "Music Player Daemon");
    }
}

Add Properties

Now that we have our MPDThing, we’ll need to add some properties to it. Obviously, a music player will have quite a few properties. To demonstrate, I will show you how to add one, and I’ll link to the rest of the code at the end of this post.

To add a property, you can do the following inside the MPDThing constructor.

Python
status = self.get_status()
self.add_property(
    Property(self,
             'volume',
             Value(self.get_volume(status), self.set_volume),
             metadata={
                 'type': 'number',
                 'description': 'Playback volume',
                 'minimum': 0,
                 'maximum': 100,
             }))
JavaScript
this.getStatus().then((status) => {
  this.getVolume(status).then((v) => {
    this.addProperty(
      new Property(this,
                   'volume',
                   new Value(v, this.setVolume.bind(this)),
                   {
                     type: 'number',
                     description: 'Playback volume',
                     minimum: 0,
                     maximum: 100,
                   }));
  });
});
Java
ServerStatus status = this.client.getServerStatus();
Map<String, Object> volumeMetadata = new HashMap<>();
volumeMetadata.put("type", "number");
volumeMetadata.put("description", "Playback volume");
volumeMetadata.put("minimum", 0);
volumeMetadata.put("maximum", 100);
this.volumeValue =
        new Value(status.getVolume(), v -> this.setVolume((int)v));
this.addProperty(new Property(this,
                              "volume",
                              this.volumeValue,
                              volumeMetadata));

We’ve now created a Property that allows us to GET and PUT the playback volume. The Value piece is an object that essentially stores a cached value and a “value forwarder” callback. When the volume is set via a PUT request, the value forwarder is used to forward the new value to the actual device, which in this case is the MPD server. We’ve also set up some metadata for the property, including a description, value type, and a minimum and maximum value.

Add Actions

A music player can also have a lot of actions. While the MPD web thing has several basic actions like play, pause, and skip, I’ve added one that takes some additional input, which will queue a series of random songs. Action inputs are verified with a JSON Schema validator.

First, let’s create an Action subclass.

Python
class QueueRandomAction(Action):
    def __init__(self, thing, input_):
        Action.__init__(
            self, uuid.uuid4().hex, thing, 'queueRandom', input_=input_)

    def perform_action(self):
        songs = self.thing.list()
        if songs:
            for _ in range(0, int(self.input['count'])):
                self.thing.add(random.choice(songs))

            playlist = self.thing.get_playlist()
            if playlist is not None:
                self.thing.add_event(
                    PlaylistUpdatedEvent(self.thing, playlist))
JavaScript
class QueueRandomAction extends Action {
  constructor(thing, input) {
    super(uuidv4(), thing, 'queueRandom', input);
  }

  performAction() {
    return this.thing.list().then((songs) => {
      const promises = [];

      if (songs) {
        for (let i = 0; i < this.input.count; ++i) {
          const uri = songs[Math.floor(Math.random() * songs.length)].file;
          promises.push(this.thing.add(uri));
        }

        promises.push(this.thing.getPlaylist().then((playlist) => {
          if (playlist) {
            this.thing.addEvent(new PlaylistUpdatedEvent(this.thing, playlist));
          }
        }));
      }

      return Promise.all(promises);
    });
  }
}
Java
public static class QueueRandomAction extends Action {
    public QueueRandomAction(Thing thing, JSONObject input) {
        super(UUID.randomUUID().toString(), thing, "queueRandom", input);
    }

    @Override
    public void performAction() {
        MPDThing thing = (MPDThing)this.getThing();
        Random random = new Random();
        List<MPDFile> songs = thing.list();

        for (int i = 0; i < this.getInput().getInt("count"); ++i) {
            MPDFile file = songs.get(random.nextInt(songs.size()));
            thing.add(file);
        }

        String playlist = thing.getPlaylist();
        thing.addEvent(new PlaylistUpdatedEvent(thing, playlist));
    }
}

QueueRandomAction takes an input, count, queues that number of random songs to the current playlist, and then emits a PlaylistUpdatedEvent (to be defined shortly). To add this new action to our MPDThing, do the following inside the MPDThing constructor:

Python
self.add_available_action(
    'queueRandom',
    {'description': 'Queue a series of random songs',
     'input': {
         'type': 'object',
         'required': [
             'count',
         ],
         'properties': {
             'count': {
                 'type': 'number',
                 'minimum': 1,
             },
         },
     }},
    QueueRandomAction)
JavaScript
this.addAvailableAction(
  'queueRandom',
  {
    description: 'Queue a series of random songs',
    input: {
      type: 'object',
      required: [
        'count',
      ],
      properties: {
        count: {
          type: 'number',
          minimum: 1,
        },
      },
    },
  },
  QueueRandomAction);
Java
Map<String, Object> queueRandomMetadata = new HashMap<>();
queueRandomMetadata.put("description",
                        "Queue a series of random songs");
Map<String, Object> queueRandomInputMetadata = new HashMap<<>();
queueRandomInputMetadata.put("type", "object");
queueRandomInputMetadata.put("required", new String[]{"count"});
Map<String, Object> queueRandomInputPropertiesMetadata =
        new HashMap<>();
Map<String, Object> queueRandomInputPropertiesCountMetadata =
        new HashedMap();
queueRandomInputPropertiesCountMetadata.put("type", "number");
queueRandomInputPropertiesCountMetadata.put("minimum", 1);
queueRandomInputPropertiesMetadata.put("count",
                                       queueRandomInputPropertiesCountMetadata);
queueRandomInputMetadata.put("properties",
                             queueRandomInputPropertiesMetadata);
queueRandomMetadata.put("input", queueRandomInputMetadata);
this.addAvailableAction("queueRandom",
                        queueRandomMetadata,
QueueRandomAction.class);

Add Events

The final piece of our Thing is its events. Since MPD is a client/server model, it can be updated externally by any number of other clients. As such, I created an event that will fire when the current playlist is updated.

As with Thing and Action, we’ll create an Event subclass.

Python
class PlaylistUpdatedEvent(Event):
    def __init__(self, thing, data):
        Event.__init__(self, thing, 'playlistUpdated', data=data)
JavaScript
class PlaylistUpdatedEvent extends Event {
  constructor(thing, data) {
    super(thing, 'playlistUpdated', data);
  }
}
Java
public static class PlaylistUpdatedEvent extends Event {
    public PlaylistUpdatedEvent(Thing thing, String data) {
        super(thing, "playlistUpdated", data);
    }
}

This is a basic Event. The data member will be filled in with a string representation of the current playlist.

To add this Event to our thing, we’ll do the following in the MPDThing constructor:

Python
self.add_available_event(
    'playlistUpdated',
    {'description': 'The current playlist has been updated',
     'type': 'string'})
JavaScript
this.addAvailableEvent(
  'playlistUpdated',
  {
    description: 'The current playlist has been updated',
    type: 'string',
  });
Java
Map<String, Object> playlistUpdatedMetadata = new HashMap<>();
playlistUpdatedMetadata.put("description",
                            "The current playlist has been updated");
playlistUpdatedMetadata.put("type", "string");
this.addAvailableEvent("playlistUpdated", playlistUpdatedMetadata);

Create a WebThingServer

Now that we have a thing with properties, actions, and events, we’ll create a WebThingServer and attach the MPDThing to it.

Python
thing = MPDThing()

server = WebThingServer([thing], port=8888)

try:
    server.start()
except KeyboardInterrupt:
    server.stop()
JavaScript
const thing = new MPDThing();

const server = new WebThingServer([thing], null, 8888);

process.on('SIGINT', () => {
  server.stop();
  process.exit();
});

server.start();
Java
MPDThing thing = new MPDThing();

List<Thing> things = new ArrayList<>();
things.add(thing);

try {
    WebThingServer server = new WebThingServer(things, null, 8888);

    Runtime.getRuntime()
           .addShutdownHook(new Thread(() -> server.stop()));

    server.start(false);
} catch (IOException e) {
    System.out.println(e);
    System.exit(1);
}

Controlling the Web Thing

Our web thing is complete and it’s now controllable via the Web Thing API. Here’s how to add it to the Things Gateway:

Empty Things Screen Add New Thing New Thing Displayed Expanded Thing UI

The Things Gateway doesn’t currently provide a way to use actions or display events, but those are in the works.

Alternatively, you can control the web thing via cURL or any other HTTP library you choose:

Shell
$ curl \
    -X POST http://127.0.0.1:8888/actions \
    -H 'Content-Type: application/json' \
    -d '{"play":{}}'

Wrapping Up

Your imagination is really your only limit as to what you can turn into a web thing. If you’d like to see the rest of this example and how the actual MPD controls are implemented, all of the code is available on Github.

If you have any questions or suggestions, you can head over to Discourse or find us in #iot on irc.mozilla.org. Additionally, feel free to create issues, or even better, submit pull requests, to the webthing library repositories!

Comments