WebThings Framework Things illustration

Build Your Own Web Things

The WebThings Framework is a collection of re-usable software components to help you build your own web things, which directly expose the Web Thing API. This means they can be discovered by a Web of Things gateway or client, which can then automatically detect the device's capabilities and monitor and control it over the web.

Installation

$ npm install webthing
$ pip install webthing

Maven:

<dependencies>
      <dependency>
          <groupId>org.mozilla.iot</groupId>
          <artifactId>webthing</artifactId>
          <version>LATEST</version>
      </dependency>
  </dependencies>

Gradle:

dependencies {
      runtime(
          [group: 'org.mozilla.iot', name: 'webthing', version: 'LATEST'],
      )
  }

Cargo:

[dependencies]
  webthing = "0.12"

Arduino IDE:
Add the webthing and ArduinoJson libraries to your project.

PlatformIO:
Add the webthing-arduino and ArduinoJson libraries to your project.

Example

const {
    Action,
    Event,
    Property,
    SingleThing,
    Thing,
    Value,
    WebThingServer,
  } = require('webthing');
  const uuidv4 = require('uuid/v4');

  class OverheatedEvent extends Event {
    constructor(thing, data) {
      super(thing, 'overheated', data);
    }
  }

  class FadeAction extends Action {
    constructor(thing, input) {
      super(uuidv4(), thing, 'fade', input);
    }

    performAction() {
      return new Promise((resolve) => {
        setTimeout(() => {
          this.thing.setProperty('brightness', this.input.brightness);
          this.thing.addEvent(new OverheatedEvent(this.thing, 102));
          resolve();
        }, this.input.duration);
      });
    }
  }

  function makeThing() {
    const thing = new Thing('urn:dev:ops:my-lamp-1234',
                            'My Lamp',
                            ['OnOffSwitch', 'Light'],
                            'A web connected lamp');

    thing.addProperty(
      new Property(thing,
                   'on',
                   new Value(true),
                   {
                     '@type': 'OnOffProperty',
                     title: 'On/Off',
                     type: 'boolean',
                     description: 'Whether the lamp is turned on',
                   }));
    thing.addProperty(
      new Property(thing,
                   'brightness',
                   new Value(50),
                   {
                     '@type': 'BrightnessProperty',
                     title: 'Brightness',
                     type: 'integer',
                     description: 'The level of light from 0-100',
                     minimum: 0,
                     maximum: 100,
                     unit: 'percent',
                   }));

    thing.addAvailableAction(
      'fade',
      {
        title: 'Fade',
        description: 'Fade the lamp to a given level',
        input: {
          type: 'object',
          required: [
            'brightness',
            'duration',
          ],
          properties: {
            brightness: {
              type: 'integer',
              minimum: 0,
              maximum: 100,
              unit: 'percent',
            },
            duration: {
              type: 'integer',
              minimum: 1,
              unit: 'milliseconds',
            },
          },
        },
      },
      FadeAction);

    thing.addAvailableEvent(
      'overheated',
      {
        description: 'The lamp has exceeded its safe operating temperature',
        type: 'number',
        unit: 'degree celsius',
      });

    return thing;
  }

  function runServer() {
    const thing = makeThing();

    // If adding more than one thing, use MultipleThings() with a name.
    // In the single thing case, the thing's name will be broadcast.
    const server = new WebThingServer(new SingleThing(thing), 8888);

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

    server.start().catch(console.error);
  }

  runServer();
from __future__ import division
  from webthing import (Action, Event, Property, SingleThing, Thing, Value,
                        WebThingServer)
  import logging
  import time
  import uuid


  class OverheatedEvent(Event):

      def __init__(self, thing, data):
          Event.__init__(self, thing, 'overheated', data=data)


  class FadeAction(Action):

      def __init__(self, thing, input_):
          Action.__init__(self, uuid.uuid4().hex, thing, 'fade', input_=input_)

      def perform_action(self):
          time.sleep(self.input['duration'] / 1000)
          self.thing.set_property('brightness', self.input['brightness'])
          self.thing.add_event(OverheatedEvent(self.thing, 102))


  def make_thing():
      thing = Thing(
          'urn:dev:ops:my-lamp-1234',
          'My Lamp',
          ['OnOffSwitch', 'Light'],
          'A web connected lamp'
      )

      thing.add_property(
          Property(thing,
                   'on',
                   Value(True),
                   metadata={
                       '@type': 'OnOffProperty',
                       'title': 'On/Off',
                       'type': 'boolean',
                       'description': 'Whether the lamp is turned on',
                   }))
      thing.add_property(
          Property(thing,
                   'brightness',
                   Value(50),
                   metadata={
                       '@type': 'BrightnessProperty',
                       'title': 'Brightness',
                       'type': 'integer',
                       'description': 'The level of light from 0-100',
                       'minimum': 0,
                       'maximum': 100,
                       'unit': 'percent',
                   }))

      thing.add_available_action(
          'fade',
          {
              'title': 'Fade',
              'description': 'Fade the lamp to a given level',
              'input': {
                  'type': 'object',
                  'required': [
                      'brightness',
                      'duration',
                  ],
                  'properties': {
                      'brightness': {
                          'type': 'integer',
                          'minimum': 0,
                          'maximum': 100,
                          'unit': 'percent',
                      },
                      'duration': {
                          'type': 'integer',
                          'minimum': 1,
                          'unit': 'milliseconds',
                      },
                  },
              },
          },
          FadeAction)

      thing.add_available_event(
          'overheated',
          {
              'description':
              'The lamp has exceeded its safe operating temperature',
              'type': 'number',
              'unit': 'degree celsius',
          })

      return thing


  def run_server():
      thing = make_thing()

      # If adding more than one thing, use MultipleThings() with a name.
      # In the single thing case, the thing's name will be broadcast.
      server = WebThingServer(SingleThing(thing), port=8888)
      try:
          logging.info('starting the server')
          server.start()
      except KeyboardInterrupt:
          logging.info('stopping the server')
          server.stop()
          logging.info('done')


  if __name__ == '__main__':
      logging.basicConfig(
          level=10,
          format="%(asctime)s %(filename)s:%(lineno)s %(levelname)s %(message)s"
      )
      run_server()
package org.mozilla.iot.webthing.example;

  import org.json.JSONArray;
  import org.json.JSONObject;
  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;
  import org.mozilla.iot.webthing.errors.PropertyError;

  import java.io.IOException;
  import java.util.Arrays;
  import java.util.UUID;

  public class SingleThing {
      public static Thing makeThing() {
          Thing thing = new Thing("urn:dev:ops:my-lamp-1234",
                                  "My Lamp",
                                  new JSONArray(Arrays.asList("OnOffSwitch",
                                                              "Light")),
                                  "A web connected lamp");

          JSONObject onDescription = new JSONObject();
          onDescription.put("@type", "OnOffProperty");
          onDescription.put("title", "On/Off");
          onDescription.put("type", "boolean");
          onDescription.put("description", "Whether the lamp is turned on");
          thing.addProperty(new Property(thing,
                                         "on",
                                         new Value(true),
                                         onDescription));

          JSONObject brightnessDescription = new JSONObject();
          brightnessDescription.put("@type", "BrightnessProperty");
          brightnessDescription.put("title", "Brightness");
          brightnessDescription.put("type", "integer");
          brightnessDescription.put("description",
                                    "The level of light from 0-100");
          brightnessDescription.put("minimum", 0);
          brightnessDescription.put("maximum", 100);
          brightnessDescription.put("unit", "percent");
          thing.addProperty(new Property(thing,
                                         "brightness",
                                         new Value(50),
                                         brightnessDescription));

          JSONObject fadeMetadata = new JSONObject();
          JSONObject fadeInput = new JSONObject();
          JSONObject fadeProperties = new JSONObject();
          JSONObject fadeBrightness = new JSONObject();
          JSONObject fadeDuration = new JSONObject();
          fadeMetadata.put("title", "Fade");
          fadeMetadata.put("description", "Fade the lamp to a given level");
          fadeInput.put("type", "object");
          fadeInput.put("required",
                        new JSONArray(Arrays.asList("brightness", "duration")));
          fadeBrightness.put("type", "integer");
          fadeBrightness.put("minimum", 0);
          fadeBrightness.put("maximum", 100);
          fadeBrightness.put("unit", "percent");
          fadeDuration.put("type", "integer");
          fadeDuration.put("minimum", 1);
          fadeDuration.put("unit", "milliseconds");
          fadeProperties.put("brightness", fadeBrightness);
          fadeProperties.put("duration", fadeDuration);
          fadeInput.put("properties", fadeProperties);
          fadeMetadata.put("input", fadeInput);
          thing.addAvailableAction("fade", fadeMetadata, FadeAction.class);

          JSONObject overheatedMetadata = new JSONObject();
          overheatedMetadata.put("description",
                                 "The lamp has exceeded its safe operating temperature");
          overheatedMetadata.put("type", "number");
          overheatedMetadata.put("unit", "degree celsius");
          thing.addAvailableEvent("overheated", overheatedMetadata);

          return thing;
      }

      public static void main(String[] args) {
          Thing thing = makeThing();
          WebThingServer server;

          try {
              // If adding more than one thing, use MultipleThings() with a name.
              // In the single thing case, the thing's name will be broadcast.
              server = new WebThingServer(new WebThingServer.SingleThing(thing),
                                          8888);

              Runtime.getRuntime().addShutdownHook(new Thread() {
                  public void run() {
                      server.stop();
                  }
              });

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

      public static class OverheatedEvent extends Event {
          public OverheatedEvent(Thing thing, int data) {
              super(thing, "overheated", data);
          }
      }

      public static class FadeAction extends Action {
          public FadeAction(Thing thing, JSONObject input) {
              super(UUID.randomUUID().toString(), thing, "fade", input);
          }

          @Override
          public void performAction() {
              Thing thing = this.getThing();
              JSONObject input = this.getInput();
              try {
                  Thread.sleep(input.getInt("duration"));
              } catch (InterruptedException e) {
              }

              try {
                  thing.setProperty("brightness", input.getInt("brightness"));
                  thing.addEvent(new OverheatedEvent(thing, 102));
              } catch (PropertyError e) {
              }
          }
      }
  }
extern crate env_logger;
  #[macro_use]
  extern crate serde_json;
  extern crate uuid;
  extern crate webthing;

  use std::sync::{Arc, RwLock, Weak};
  use std::{thread, time};
  use uuid::Uuid;
  use webthing::{
      Action, BaseAction, BaseEvent, BaseProperty, BaseThing, Event, Thing, ThingsType,
      WebThingServer,
  };

  use webthing::server::ActionGenerator;

  pub struct OverheatedEvent(BaseEvent);

  impl OverheatedEvent {
      fn new(data: Option<serde_json::Value>) -> OverheatedEvent {
          OverheatedEvent(BaseEvent::new("overheated".to_owned(), data))
      }
  }

  impl Event for OverheatedEvent {
      fn get_name(&self) -> String {
          self.0.get_name()
      }

      fn get_data(&self) -> Option<serde_json::Value> {
          self.0.get_data()
      }

      fn get_time(&self) -> String {
          self.0.get_time()
      }
  }

  pub struct FadeAction(BaseAction);

  impl FadeAction {
      fn new(
          input: Option<serde_json::Map<String, serde_json::Value>>,
          thing: Weak<RwLock<Box<Thing>>>,
      ) -> FadeAction {
          FadeAction(BaseAction::new(
              Uuid::new_v4().to_string(),
              "fade".to_owned(),
              input,
              thing,
          ))
      }
  }

  impl Action for FadeAction {
      fn set_href_prefix(&mut self, prefix: String) {
          self.0.set_href_prefix(prefix)
      }

      fn get_id(&self) -> String {
          self.0.get_id()
      }

      fn get_name(&self) -> String {
          self.0.get_name()
      }

      fn get_href(&self) -> String {
          self.0.get_href()
      }

      fn get_status(&self) -> String {
          self.0.get_status()
      }

      fn get_time_requested(&self) -> String {
          self.0.get_time_requested()
      }

      fn get_time_completed(&self) -> Option<String> {
          self.0.get_time_completed()
      }

      fn get_input(&self) -> Option<serde_json::Map<String, serde_json::Value>> {
          self.0.get_input()
      }

      fn get_thing(&self) -> Option<Arc<RwLock<Box<Thing>>>> {
          self.0.get_thing()
      }

      fn set_status(&mut self, status: String) {
          self.0.set_status(status)
      }

      fn start(&mut self) {
          self.0.start()
      }

      fn perform_action(&mut self) {
          let thing = self.get_thing();
          if thing.is_none() {
              return;
          }

          let thing = thing.unwrap();
          let input = self.get_input().unwrap().clone();
          let name = self.get_name();
          let id = self.get_id();

          thread::spawn(move || {
              thread::sleep(time::Duration::from_millis(
                  input.get("duration").unwrap().as_u64().unwrap(),
              ));

              let thing = thing.clone();
              let mut thing = thing.write().unwrap();
              let _ = thing.set_property(
                  "brightness".to_owned(),
                  input.get("brightness").unwrap().clone(),
              );
              thing.add_event(Box::new(OverheatedEvent::new(Some(json!(102)))));

              thing.finish_action(name, id);
          });
      }

      fn cancel(&mut self) {
          self.0.cancel()
      }

      fn finish(&mut self) {
          self.0.finish()
      }
  }

  struct Generator;

  impl ActionGenerator for Generator {
      fn generate(
          &self,
          thing: Weak<RwLock<Box<Thing>>>,
          name: String,
          input: Option<&serde_json::Value>,
      ) -> Option<Box<Action>> {
          let input = match input {
              Some(v) => match v.as_object() {
                  Some(o) => Some(o.clone()),
                  None => None,
              },
              None => None,
          };

          let name: &str = &name;
          match name {
              "fade" => Some(Box::new(FadeAction::new(input, thing))),
              _ => None,
          }
      }
  }

  fn make_thing() -> Arc<RwLock<Box<Thing + 'static>>> {
      let mut thing = BaseThing::new(
          "urn:dev:ops:my-lamp-1234".to_owned(),
          "My Lamp".to_owned(),
          Some(vec!["OnOffSwitch".to_owned(), "Light".to_owned()]),
          Some("A web connected lamp".to_owned()),
      );

      let on_description = json!({
          "@type": "OnOffProperty",
          "title": "On/Off",
          "type": "boolean",
          "description": "Whether the lamp is turned on"
      });
      let on_description = on_description.as_object().unwrap().clone();
      thing.add_property(Box::new(BaseProperty::new(
          "on".to_owned(),
          json!(true),
          None,
          Some(on_description),
      )));

      let brightness_description = json!({
          "@type": "BrightnessProperty",
          "title": "Brightness",
          "type": "integer",
          "description": "The level of light from 0-100",
          "minimum": 0,
          "maximum": 100,
          "unit": "percent"
      });
      let brightness_description = brightness_description.as_object().unwrap().clone();
      thing.add_property(Box::new(BaseProperty::new(
          "brightness".to_owned(),
          json!(50),
          None,
          Some(brightness_description),
      )));

      let fade_metadata = json!({
          "title": "Fade",
          "description": "Fade the lamp to a given level",
          "input": {
              "type": "object",
              "required": [
                  "brightness",
                  "duration"
              ],
              "properties": {
                  "brightness": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100,
                      "unit": "percent"
                  },
                  "duration": {
                      "type": "integer",
                      "minimum": 1,
                      "unit": "milliseconds"
                  }
              }
          }
      });
      let fade_metadata = fade_metadata.as_object().unwrap().clone();
      thing.add_available_action("fade".to_owned(), fade_metadata);

      let overheated_metadata = json!({
          "description": "The lamp has exceeded its safe operating temperature",
          "type": "number",
          "unit": "degree celsius"
      });
      let overheated_metadata = overheated_metadata.as_object().unwrap().clone();
      thing.add_available_event("overheated".to_owned(), overheated_metadata);

      Arc::new(RwLock::new(Box::new(thing)))
  }

  fn main() {
      env_logger::init();
      let thing = make_thing();

      // If adding more than one thing, use ThingsType::Multiple() with a name.
      // In the single thing case, the thing's name will be broadcast.
      let mut server = WebThingServer::new(
          ThingsType::Single(thing),
          Some(8888),
          None,
          None,
          Box::new(Generator),
          None,
          None,
      );
      server.create();
      server.start();
  }
#include <Arduino.h>
  #include <Thing.h>
  #include <WebThingAdapter.h>

  const char* ssid = "......";
  const char* password = "..........";

  #if defined(LED_BUILTIN)
  const int lampPin = LED_BUILTIN;
  #else
  const int lampPin = 13;  // manually configure LED pin
  #endif

  WebThingAdapter* adapter;

  const char* lampTypes[] = {"OnOffSwitch", "Light", nullptr};
  ThingDevice lamp("lamp", "My Lamp", lampTypes);

  ThingProperty lampOn("on", "Whether the lamp is turned on", BOOLEAN, "OnOffProperty");
  ThingProperty lampLevel("level", "The level of light from 0-100", NUMBER, "BrightnessProperty");

  void setup(void){
    pinMode(lampPin, OUTPUT);
    digitalWrite(lampPin, HIGH);
    Serial.begin(115200);
  #if defined(ESP8266) || defined(ESP32)
    WiFi.mode(WIFI_STA);
  #endif
    WiFi.begin(ssid, password);
    Serial.println("");

    // Wait for connection
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }

    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    adapter = new WebThingAdapter("led-lamp", WiFi.localIP());

    lamp.addProperty(&lampOn);
    lamp.addProperty(&lampLevel);
    adapter->addDevice(&lamp);
    adapter->begin();
    Serial.println("HTTP server started");

  #ifdef analogWriteRange
    analogWriteRange(255);
  #endif
  }

  void loop(void){
    adapter->update();
    if (lampOn.getValue().boolean) {
      int level = map(lampLevel.getValue().number, 0, 100, 255, 0);
      analogWrite(lampPin, level);
    } else {
      analogWrite(lampPin, 255);
    }
  }
Learn More

Mozilla WebThings Libraries

Third-Party Libraries