WebThingsIO / api

Web Thing API Specification
http://iot.mozilla.org/wot/
Other
164 stars 25 forks source link

Capabilities System #57

Closed benfrancis closed 6 years ago

benfrancis commented 6 years ago

In implementing this specification so far we've found that just defining top level "Web Thing Types" is quite inflexible and have discussed a "capabilities" type system for standardising types of properties, actions and events which can be mixed and matched between types.

Here is a proposed example where DimmableLight is a thing type and OnOffProperty, LevelProperty, ToggleAction, FadeAction and OverheatedEvent are capabilities defined by an external context at iot.schema.org.

You will notice that properties, actions and events also have a "schema", which is the latest W3C proposal for defining data types, and "writable" to define whether a property accepts a PUT request.

{
  "@context": "https://iot.schema.org",
  "@type": "DimmableLight",
  "name":"My Lamp",
  "description": "A web connected lamp",
  "properties": {
    "on": {
      "name": "On/Off",
      "@type": "OnOffProperty",
      "schema": {
        "type": "boolean"
      }
      "writable": true,
      "description": "Whether the lamp is turned on",
      "href": "/things/lamp/properties/on"
    },
    "level" : {
      "name": "Level",
      "@type": "LevelProperty",
      "schema": {
        "type": "number",
        "minimum": 0,
        "maximum": 100,
        "unit": "percent"
      },
      "writable": true,
      "description": "The level of light from 0-100",
      "href": "/things/lamp/properties/level",
    }
  },
  "actions": {
    "toggle": {
      "name": "Toggle",
      "@type": "ToggleAction",
      "description": "Toggle the lamp on and off",
      "href": "/things/lamp/actions/toggle"
    },
    "fade": {
      "name": "Fade",
      "@type": "FadeAction",
      "description": "Fade the lamp from one level to another",
      "schema": {
        "type": "object",
        "members": {
          "to": {
            "type": "number,
            "minimum": 0,
            "maximum": 100,
            "unit": "percent" 
          },
          "duration": {
            "type": "number",
            "unit": "milliseconds"
          }
        }
      "href": "/things/lamp/actions/fade"
    }
  },
  "events": {
    "overheated": {
      "name": "Overheated",
      "@type": "OverheatedEvent",
      "schema": {
        "type": "number",
        "unit": "celcius"
      }
      "description": "The lamp has exceeded its safe operating temperature",
      "href": "/things/lamp/events/overheated"
    }
  }
}
dhylands commented 6 years ago

There should probably be a readable equivalent for write-only properties. For example, X-10 modules are all write-only. There is no way to determine if a switch is currently set to the on or off setting.

benfrancis commented 6 years ago

Yeah, sorry we should really discuss "writable" in #38. That's just what the current W3C spec is doing. But I do find the concept of write only properties really weird and wonder whether that's something we actually want to expose. We could try caching the state in the gateway for Z-Wave.

benfrancis commented 6 years ago

From https://github.com/w3c/wot/issues/371:

If we we're not limited to JSON-LD syntax and semantic annotations are optional you could actually just rename @type to @schema.

Without semantic annotations you manually define the schema:

   "properties": {
     "on": {
       "name": "On/Off",
       "schema": {
         "type": "boolean"
       }
       "writable": true,
       "description": "Whether the lamp is turned on",
       "href": "/things/lamp/properties/on"
     }

With semantic annotations you could just refer to an external schema:

"@context": "https://iot.schema.org",
...
   "properties": {
     "on": {
       "name": "On/Off",
       "@schema": "OnOffProperty",
       "writable": true,
       "description": "Whether the lamp is turned on",
       "href": "/things/lamp/properties/on"
     }

...which kind of makes sense given the "schema" property is basically a JSON Schema!

mishoboss commented 6 years ago

IMHO, this move is highly needed indeed. The sooner the better. This spec will end up with hundreds of unique Web Thing Types, and still they may not cover all devices out there. So a "capability" kind of logic is much more suitable. Projects like https://iotdb.org already explored that topic in depth.

RoboPhred commented 6 years ago

This is definitely needed. Many of the things I am working with have common attributes with wildly different core functions. For example, smart plugs from Aeon Labs have rgb-color light rings around the plug that are configurable.

It would be beneficial to have the capabilities be abstracted and queryable. A use case of this I am looking into is having the ability of setting an entire room to a color theme. If I could query things by location and by the "colorable" capability, this would be a very simple operation.

A less esoteric example might be devices that have a battery. It would be nice to be able to pull from a capability list to find all devices and their battery status, rather than needing to know in advance what devices are battery operated and what they call their remaining battery property.

benfrancis commented 6 years ago

@dhylands found a good example of a device where this is definitely needed, the "Aeotec ZW100 multi-sensor 6". This device includes many different sensors, and we definitely don't want to have to define a MotionTemperatureHumidityLuminanceUvVibrationBatterySensor web thing type!

Here is an example of what a Thing Description could look like.

{
  "name": "Aeotec ZW100 multi-sensor 6",
  "description": "Multi-sensor measuring motion, temperature, humidity, luminance, UV, vibration and battery level",
  "@context": "https://iot.mozilla.org/schemas",
  "@type": ["MultiSensor", "MotionSensor", "TemperatureSensor", "HumiditySensor", "LuminanceSensor", "UVSensor", "VibrationSensor", "BatteryLevel"],
  "properties": {
    "motion": {
      "name": "Motion",
      "description": "Whether motion is currently being detected",
      "@type": "BinaryMotionProperty",
      "type": "boolean",
      "href": "/properties/motion"
    },
    "temperature": {
      "name": "Temperature",
      "description": "Current temperature in celcius",
      "@type": "TemperatureProperty",
      "type": "number,
      "unit": "celcius",
      "href": "/properties/temperature"
    },
    "humidity": {
      "name": "Humidity",
      "description": "Current relative humidity",
      "@type": "RelativeHumidityProperty",
      "type": "number",
      "unit": "percentage",
      "href": "/properties/humidity"
    },
    "luminance": {
      "name": "Luminance",
      "description": "Current luminous intensity",
      "@type": "LuminanceProperty",
      "type": "number",
      "unit": "candela per square metre",
      "href": "/properties/luminance"
    },
    "uv": {
      "name": "UV",
      "description": "Current ultraviolet light intensity",
      "@type": "UVLightIntensityProperty",
      "type": "number",
      "unit": "watts per square metre",
      "href": "/properties/uv"
    },
    "vibration": {
      "name": "Vibration",
      "description": "Whether vibration is currently being detected",
      "@type": "BinaryVibrationProperty",
      "type": "boolean",
      "href": "/properties/vibration"
    },
    "battery": {
      "name": "Battery",
      "description": "Current percentage battery life remaining",
      "@type": "BatteryLevelProperty",
      "type": "number",
      "unit": "percentage",
      "href": "/properties/battery"
    }
}

This example includes a top level @type as an array of capabilities the device has. One use case for this top level @type array might be to present the user with a choice of default icons representing each of these capabilities, so they can choose which icon to use to represent the device depending on what they're using it for.

There are then also @types for individual properties which provide more semantic information than the primitive type does. For example, a property with a boolean type could be a BinaryMotionProperty or BinaryVibrationProperty, which provides more information to the client to help it determine how to represent this property in the UI. (The similarity between the type and @type terms is maybe a bit clunky).

An example which includes actions and events:

{
  "name":"My Lamp",
  "description": "A web connected lamp",
  "@context": "https://iot.mozilla.org/schemas",
  "@type": ["Lamp", "OnOffSwitch", "DimmerSwitch"],
  "properties": {
    "on": {
      "name": "On/Off",
      "description": "Whether the lamp is turned on",
      "@type": "OnOffProperty",
      "type": "boolean",
      "href": "/things/lamp/properties/on"
    },
    "level" : {
      "name": "Level",
      "description": "The level of light from 0-100",
      "@type": "LevelProperty",
      "type": "number",
      "minimum" : 0,
      "maximum" : 100,
      "href": "/things/lamp/properties/level"
    }
  },
  "actions": {
    "fade": {
      "name": "Fade",
      "description": "Fade the lamp to a given level",
      "@type": "FadeAction",
      "input": {
        "type": "object",
        "properties": {
          "level": {
            "name": "Level",
            "description": "The level to fade to",
            "@type": "LevelProperty",
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "duration": {
            "name": "Duration",
            "description": "Duration of fade",
            "@type": "DurationProperty",
            "type": "number",
            "unit": "milliseconds"
          }
        }
      },
      "href": "/things/lamp/actions/fade"
    }
  },
  "events": {
    "overheated": {
      "@type": "OverHeatedEvent",
      "type": "number",
      "unit": "celsius",
      "description": "The lamp has exceeded its safe operating temperature",
      "href": "/things/lamp/events/overheated"
    }
  }
}

Actions and events can also have a @type. If an action input or event payload is of type "object" then its sub-properties can also have a @type, which may be shared with top level properties of the thing. E.g. a DimmerSwitch can have a LevelProperty as well as a FadeAction which has a LevelProperty as an input to tell it what level to fade to.

One thing I'm not sure about is to what extent @types should just provide semantic information, vs. enforce certain data structures. For example, does a LevelProperty always have a type of "number" with a minimum of 0 and a maximum of 100? Or does it just tell the client that this property is for setting a level? Does a FadeAction always have a LevelProperty and a DurationProperty and does a TemperatureProperty always have a unit of "centigrade", or could it be "fahrenheit"?

What is defined by JSON Schema, and what is defined by JSON-LD style semantic annotations?

bhagman commented 6 years ago

I like this direction for a more flexible way to define devices/things.

A thought for the @type semantic field -- would it make sense to use it more in a way like classes in CSS? This would lend to having multiple @types applied to a property/event/action (PEA), and could be consumed in several different ways.

e.g.

"properties": {
    "on": {
      "name": "On/Off",
      "description": "Whether the lamp is turned on",
      "@type": [ "OnOff", "Button" ],
      "type": "boolean",
      "href": "/things/lamp/properties/on"
    }
}

or @type could be: [ "OnOff", "Switch" ] -- depending on what the author prefers/intends.

With regard to "default" semantic information for @types -- I think that with the idea of tying it close to CSS classes, there could be "default" associations for the pre-defined types. So, for example, a property with @type Temperature would also have unit default to (e.g.) "Celcius", unless another value is provided (or "" -- blank).

IMO, this would lend to an even more flexible way to define how devices/things are presented even at the UI level too (imagine a device optionally providing a CSS file for theming/describing it's components for the UI).

type -> datatype? @type stays @type?

benfrancis commented 6 years ago

@bhagman wrote:

This would lend to having multiple @types applied to a property/event/action (PEA), and could be consumed in several different ways.

This is what the current W3C specification does, although that's partly because they haven't separated out properties, actions and events members yet so each "interaction" needs marking as a @type of Property, Action or Event in addition to a more granular type.

The downside of allowing multiple @types for properties, actions and events is the increased ambiguity for Web of Things clients which somehow need to decide how to represent each one. If user interaction is required to make that decision then the client could quickly become very hard to use. It also makes interoperability more difficult.

What is the difference between OnOff and Switch in your example? How does the client decide which to use?

type -> datatype

The term "type" is taken from JSON Schema and goes multiple levels deep. A property can be of type "object" and have multiple sub-properties which also have a type, e.g.:

{
  ...
  "properties": {
    "foo" {
      "type": "object",
      "properties": {
        "bar": {
          "type": "string"
        },
        "baz": {
          "type": "number"
        }
      }
    }
  }
}

The term @type comes from JSON-LD and is used along with @context.

So we could change one or the other, but it would make us less compatible with those specifications.

Note that the current W3C spec has a schema member for each property rather than a type, and then the schema has a type, but I think that might go away once they change over to the Simplified TD proposal.

hobinjk commented 6 years ago

I think capabilities should err on the side of rigidity when defining required sets of properties, actions, and events. I definitely don't think it would be useful to have capabilities that are solely semantic hints. For example, if something has a capability OnOffSwitch but doesn't have a property "on" then what is it trying to accomplish? It's also more useful to an interface to know that "level" must be 0-100 (or at least must have a min/max). There can certainly be weak capabilities, but it makes sense for most of them to be strong to allow the creation of fine-tuned interfaces

benfrancis commented 6 years ago

OK, I've started to document a set of capability schemas for a Mozilla schema repository. My hope is that eventually we can use an external schema repository like iot.schema.org (current staging server at iotschema.org) once that matures a bit more and the Thing Description specification is a bit more solid. In the meantime a Mozilla schema repository gives us more flexibility to experiment and define data constraints for types.

The repository includes the following types:

A thing can implement multiple capabilities. A capability schema defines a set of properties, actions and events which can be expected for a thing which implements that capability - some of which are required and some of which are optional.

Property, Action and Event types describe data constraints for property values, action inputs (and possibly outputs) and event payloads. This defines what primitive data types, units, minimum and maximum values to expect in order to enable ad-hoc interoperability.

The idea is that a client can implement UI components for each known property, action and event type such that things can use any combination of those types, without having to stick to a single rigid top level thing type. The top level thing capability types provide additional information to a client to tell it what kinds of properties, actions and events to expect from a thing (as well as semantic information about what it does), without restricting that thing to a single top level type.

Below is an example of how capabilities might be used. A lamp implements the Light, OnOffSwitch and MultiLevelSwitch capabilities. The OnOffSwitch capability and MultiLevelSwitch capability are subsets of the Light capability, but the thing implements all three. This means a client which understands the OnOffSwitch capability but not the Light capability can still turn the light on and off for example.

This approach currently differs from the latest W3C proposals in that we only implement two of the three or four levels of "types" currently being discussed. We can add additional levels in future if we find they are necessary.

{
  "@context": "https://iot.mozilla.org/schemas/",
  "@type": ["Light", "OnOffSwitch", "MultiLevelSwitch"],
  "name":"My Lamp",
  "description": "A web connected lamp",
  "properties": {
    "on": {
      "@type": "OnOffProperty",
      "type": "boolean",
      "description": "Whether the lamp is turned on",
      "href": "/things/lamp/properties/on"
    },
    "level" : {
      "@type": "LevelProperty",
      "type": "number",
      "description": "The level of light from 0-100",
      "minimum" : 0,
      "maximum" : 100,
      "href": "/things/lamp/properties/level"
    }
  },
  "actions": {
    "fade": {
      "@type": "FadeAction",
      "description": "Fade the lamp to a given level",
      "input": {
        "type": "object",
        "properties": {
          "level": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "duration": {
            "type": "number",
            "unit": "milliseconds"
          }
        }
      },
      "href": "/things/lamp/actions/fade"
    }
  },
  "events": {
    "overheated": {
      "@type": "OverheatedEvent",
      "type": "number",
      "unit": "celsius",
      "description": "The lamp has exceeded its safe operating temperature",
      "href": "/things/lamp/events/overheated"
    }
  },
  "links": [
    {
      "rel": "properties",
      "href": "/things/lamp/properties"
    },
    {
      "rel": "actions",
      "href": "/things/lamp/actions"
    },
    {
      "rel": "events",
      "href": "/things/lamp/events"
    },
    {
      "rel": "alternate",
      "href": "wss://mywebthingserver.com/things/lamp"
    },
    {
      "rel": "alternate",
      "mediaType": "text/html",
      "href": "/things/lamp"
    }
  ]
}
benfrancis commented 6 years ago

closed by https://github.com/mozilla-iot/wot/commit/7fe0f9eed4021f09043a74feb274ad2f7465dbe1