WebThings
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>io.webthings</groupId>
          <artifactId>webthing</artifactId>
          <version>0.13.0</version>
      </dependency>
    </dependencies>

Gradle:

dependencies {
      runtime(
          [group: 'io.webthings', name: 'webthing', version: '0.13.0'],
      )
    }

Cargo:

[dependencies]
    webthing = "0.13"

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 io.webthings.webthing.example;

import org.json.JSONArray;
import org.json.JSONObject;
import io.webthings.webthing.Action;
import io.webthings.webthing.Event;
import io.webthings.webthing.Property;
import io.webthings.webthing.Thing;
import io.webthings.webthing.Value;
import io.webthings.webthing.WebThingServer;
import io.webthings.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) {
            }
        }
    }
}
use actix_rt;
    use serde_json::json;
    use std::sync::{Arc, RwLock, Weak};
    use std::{thread, time};
    use uuid::Uuid;
    use webthing::{
    Action, BaseAction, BaseEvent, BaseProperty, BaseThing, Thing, ThingsType, WebThingServer,
    };

    use webthing::server::ActionGenerator;

    pub struct FadeAction(BaseAction);

    impl FadeAction {
    fn new(
        input: Option<serde_json::Map<String, serde_json::Value>>,
        thing: Weak<RwLock<Box<dyn 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<dyn 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(BaseEvent::new(
                "overheated".to_owned(),
                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<dyn Thing>>>,
        name: String,
        input: Option<&serde_json::Value>,
    ) -> Option<Box<dyn 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<dyn 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)))
    }

    #[actix_rt::main]
    async fn main() -> std::io::Result<()> {
    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,
    );
    server.start(None).await
    }
#define LARGE_JSON_BUFFERS 1

    #include <Arduino.h>
    #include <Thing.h>
    #include <WebThingAdapter.h>

    #ifdef ESP32
    #include <analogWrite.h>
    #endif

    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

    ThingActionObject *action_generator(DynamicJsonDocument *);

    WebThingAdapter *adapter;

    const char *lampTypes[] = {"OnOffSwitch", "Light", nullptr};
    ThingDevice lamp("urn:dev:ops:my-lamp-1234", "My Lamp", lampTypes);

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

    StaticJsonDocument<256> fadeInput;
    JsonObject fadeInputObj = fadeInput.to<JsonObject>();
    ThingAction fade("fade", "Fade", "Fade the lamp to a given level",
                 "FadeAction", &fadeInputObj, action_generator);
    ThingEvent overheated("overheated",
                      "The lamp has exceeded its safe operating temperature",
                      NUMBER, "OverheatedEvent");

    bool lastOn = true;

    void setup(void) {
    pinMode(lampPin, OUTPUT);
    digitalWrite(lampPin, HIGH);
    Serial.begin(115200);
    Serial.println("");
    Serial.print("Connecting to \"");
    Serial.print(ssid);
    Serial.println("\"");
    #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.description = "A web connected lamp";

    lampOn.title = "On/Off";
    lamp.addProperty(&lampOn);

    lampLevel.title = "Brightness";
    lampLevel.minimum = 0;
    lampLevel.maximum = 100;
    lampLevel.unit = "percent";
    lamp.addProperty(&lampLevel);

    fadeInputObj["type"] = "object";
    JsonObject fadeInputProperties =
      fadeInputObj.createNestedObject("properties");
    JsonObject brightnessInput =
      fadeInputProperties.createNestedObject("brightness");
    brightnessInput["type"] = "integer";
    brightnessInput["minimum"] = 0;
    brightnessInput["maximum"] = 100;
    brightnessInput["unit"] = "percent";
    JsonObject durationInput =
      fadeInputProperties.createNestedObject("duration");
    durationInput["type"] = "integer";
    durationInput["minimum"] = 1;
    durationInput["unit"] = "milliseconds";
    lamp.addAction(&fade);

    overheated.unit = "degree celsius";
    lamp.addEvent(&overheated);

    adapter->addDevice(&lamp);
    adapter->begin();

    Serial.println("HTTP server started");
    Serial.print("http://");
    Serial.print(WiFi.localIP());
    Serial.print("/things/");
    Serial.println(lamp.id);

    #ifdef analogWriteRange
    analogWriteRange(255);
    #endif

    // set initial values
    ThingPropertyValue initialOn = {.boolean = true};
    lampOn.setValue(initialOn);
    (void)lampOn.changedValueOrNull();

    ThingPropertyValue initialLevel = {.integer = 50};
    lampLevel.setValue(initialLevel);
    (void)lampLevel.changedValueOrNull();

    analogWrite(lampPin, 128);

    randomSeed(analogRead(0));
    }

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

    if (lastOn != on) {
    lastOn = on;
    }
    }

    void do_fade(const JsonVariant &input) {
    JsonObject inputObj = input.as<JsonObject>();
    long long int duration = inputObj["duration"];
    long long int brightness = inputObj["brightness"];

    delay(duration);

    ThingDataValue value = {.integer = brightness};
    lampLevel.setValue(value);
    int level = map(brightness, 0, 100, 255, 0);
    analogWrite(lampPin, level);

    ThingDataValue val;
    val.number = 102;
    ThingEventObject *ev = new ThingEventObject("overheated", NUMBER, val);
    lamp.queueEventObject(ev);
    }

    ThingActionObject *action_generator(DynamicJsonDocument *input) {
    return new ThingActionObject("fade", input, do_fade, nullptr);
    }
Learn More

WebThings Libraries

Third Party Libraries