exposedthing

package
v0.0.0-...-2920ad6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 1, 2023 License: MIT Imports: 11 Imported by: 0

README

MQTT protocol binding

This document specifies the MQTT protocol binding for the HiveOT Hub.

Bindings are based on the W3C WoT bindings specification.

Documentation of a binding should contain:

  • URI schema
  • Mapping to WoT operations, e.g. "readproperty", "writeproperty", "invokeaction", ...
  • Document that specifies the protocol.

URI Schema

The MQTT protocol is identified with the 'mqtt://' URI schema.

WoT Operations Mapping

Based on the draft from https://w3c.github.io/wot-binding-templates/bindings/protocols/mqtt/index.html.

property operations binding topic
readproperty mqv:controlPacketValue": "PUBLISH" things/{thingID}/read/properties
readmultipleproperties mqv:controlPacketValue": "PUBLISH" things/{thingID}/read/properties
readallproperties mqv:controlPacketValue": "PUBLISH" things/{thingID}/read/properties
writeproperty "mqv:controlPacketValue": "PUBLISH" things/{thingID}/write/properties
writemultipleproperties "mqv:controlPacketValue": "PUBLISH" things/{thingID}/write/properties
writeallproperties not supported things/{thingID}/write/properties
observeproperty "mqv:controlPacketValue": "SUBSCRIBE" things/{thingID}/event/properties
observeallproperties "mqv:controlPacketValue": "SUBSCRIBE" things/{thingID}/event/properties
unobserveproperty "mqv:controlPacketValue": "UNSUBSCRIBE" things/{thingID}/event/properties
unobserveallproperties "mqv:controlPacketValue": "UNSUBSCRIBE" things/{thingID}/event/properties

Property Operations

Below the HiveOT MQTT binding for standard operations.

readproperty (tentative)

The operation readproperty is used by a consumer to request to read a property of a Thing.

This section is for consideration and not currently implemented

Form:

{
 "op": "readproperty",
 "href": "mqtts://{broker}/things/{thingID}/read/properties",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

Payload: JSON encoded array with 1 property name { ["propertyName"] }

The Exposed Thing responds with a 'properties change event' as defined in the TD properties event:

Topic: things/{thingID}/event/properties

Payload: map with name-value pair of the requested property.

{
 "propertyName": "value"
}
readmultipleproperties (tentative)

The operation readmultipleproperties is used by a consumer to request to read select properties of a Thing.

This section is for consideration and not currently implemented

Form:

{
 "op": "readmultipleproperties",
 "href": "mqtts://{broker}/things/{thingID}/read/properties",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

Payload:

  • JSON encoded array of property names

The Exposed Thing responds with a 'properties change event' as defined in the TD properties event:

Topic: things/{thingID}/event/properties

Payload: map with name-value pairs of the given properties.

{
 "property1Name": "value",
 "property2Name": "value"
}
readallproperties (tentative)

The operation readallproperties is used by a consumer to request to read all properties of a Thing.

This section is for consideration and not currently implemented

Form:

{
 "op": "readallproperties",
 "href": "mqtts://{broker}/things/{thingID}/read/properties",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

Payload: none. The lack of payload instructs to respond with all properties.

The Exposed Thing responds with a 'properties change event' as defined in the TD properties event, containing the value of all properties:

Topic: things/{thingID}/event/properties

Payload: map with name-value pairs of all properties of a thing.

{
 "property1Name": "value",
 "property2Name": "value"
}
writeproperty, writemultipleproperties

This section is for consideration and not currently implemented

The operations writeproperty(...ies) are used by a consumer to publish a request to modify one or more more properties on an ExposedThing.

Form:

{
 "op": ["writeproperty","writemultipleproperties"],
 "href": "mqtts://{broker}/things/{thingID}/write/properties",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

Payload: JSON encoded map with property name-value pairs, where value must match the description in the property affordance schema.

{
 "property1Name": "value",
 "property2Name": "value"
}

Response: When the property change is accepted, a property value change event will be sent.

observeproperty, observeallproperties

To be notified of a property value changes, subscribe to property events.

Note: This protocol binding makes no distinction between subscribing to a single or all properties. A ConsumedThing implementation can map a specific property to a corresponding callback handler.

Form:

{
 "op": ["observeproperty","observeallproperties"],
 "href": "mqtts://{broker}/things/{thingID}/event/properties",
 "mqv:controlPacketValue": "SUBSCRIBE",
 "contentType": "application/json"
}
  • Where {thingID} is the thing ID or '+' to subscribe to properties changes from all Things.

Response: Observers will receive property change events from the thing with the given ID.

Emit Property Change Form:

{
 "op": "emitpropertychange",
 "href": "mqtts://{broker}/things/{thingID}/event/properties",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

Payload: Property change events contain the JSON encoded map of property name-value pairs for each of the properties that have changed. The values are of type described by the property affordance in the TD and can be a string, number, integer, boolean, or an object with an additional map of name-value pairs. For example

{
 "property1Name" : "value1",
 "property2Name" : "value2"
}
unobserveproperty, unobserveallproperties

To end observing property changes, unsubscribe from the properties event. The protocol binding makes no distinction between subscribing to a single or all properties. A ConsumedThing implementation should only unsubscribe when there are no other property subscriptions in effect on the ConsumedThing.

{
 "op": ["unobserveproperty","unobserveallproperties"],
 "href": "mqtts://{broker}/things/{thingID}/event/properties",
 "mqv:controlPacketValue": "UNSUBSCRIBE",
 "contentType": "application/json"
}
  • Where {thingID} is the thing ID used when subscribing (observing).

Event Operations

event operations binding
subscribeevent "mqv:controlPacketValue": "SUBSCRIBE"
subscribeallevents "mqv:controlPacketValue": "SUBSCRIBE"
unsubscribeevent "mqv:controlPacketValue": "UNSUBSCRIBE"
unsubscribeallevents "mqv:controlPacketValue": "UNSUBSCRIBE"
subscribeevent

Subscribe to be notified of an event from an exposed thing.

{
 "op": "subscribeevent",
 "href": "mqtts://{broker}/things/{thingID}/event/{eventName}",
 "mqv:controlPacketValue": "SUBSCRIBE",
 "contentType": "application/json"
}
  • Where {thingID} is the thing ID or '+' to subscribe to events from all Things.
  • Where {eventName} is the event to subscribe to or '+' to subscribe to all events.

Response: Events of the Thing with the given ID and matching event name:

Form used by Exposed Thing to send the event:

{
 "op": "emitevent",
 "href": "mqtts://{broker}/things/{thingID}/event/{eventName}",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

Payload: Events contain the JSON encoded value described by the EventAffordance in the TD. value can be a string, number, integer, boolean, or an object with a map of name-value pairs. For example a map with values:

{
 "eventProperty1" : "value1",
 "eventProperty2" : "value2"
}
unsubscribeevent

Form:

{
 "op": "unsubscribeevent",
 "href": "mqtts://{broker}/things/{thingID}/event/{eventName}",
 "mqv:controlPacketValue": "UNSUBSCRIBE",
 "contentType": "application/json"
}
  • Where {thingID} is the thing ID used when subscribing
  • Where {eventName} is the event used when subscribing.

Payload: no payload

Action Operations

Operation Binding
invokeaction "mqv:controlPacketValue": "PUBLISH"
cancelaction not supported
queryaction not supported
queryallactions not supported
invokeaction

Request an action to take place on a Thing.

Form:

{
 "op": "invokeaction",
 "href": "mqtts://{broker}/things/{thingID}/action/{actionName}",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}
  • Where {thingID} is the thing ID of the Exposed Thing that handles the action.
  • Where {actionName} is the action to invoke.

Payload: JSON encoded action input data as described by the ActionAffordances in the TD. In HiveOT this typically includes a 'id' field.

Response: ExposedThings can respond to an action by emitting an action status event with the output described by the ActionAffordance in the TD. If used then the action status event must be defined with an EventAffordance using the actionName as the event name. Form:

{
 "op": "emitevent",
 "href": "mqtts://{broker}/things/{thingID}/event/{actionName}",
 "mqv:controlPacketValue": "PUBLISH",
 "contentType": "application/json"
}

The 'output' data schema of the action in the ActionAffordance describes the content of the action status event. If an 'id' field is included in the input then this will also be included in the output.

  • Event for action status: started, completed, cancelled, failed
  • Topic: things/{thingID}/event/{actionName}
  • Payload: JSON encoded action status object with the following properties:
    • id: action id provided when emitting the action
    • name: the name of the action
    • status: status of the action: started, completed, cancelled, failed
    • description: additional human description of the action status, such as error messages or other.

For example:

{
  "id": "{actionID}",
  "name": "{actionName}",
  "status": "started",
  "description": "optional details of the status"
}

Options

MQTT supports the following options. Note that these might not be supported in the current implementation.

retain - To Retain or Not To Retain

Many MQTT implementations support 'retained' messages. The last received message on a topic is stored and after subscribing to a topic, this last message for this topic is immediately received. It acts as a cache.

'retain' MUST never be used on actions as this can lead to repeated actions.

The plus side of enabling retain for TDs, properties and possibly events, is that the most recent TD's and events will be received immediately on connecting to the message bus.

The downside is that this can lead to a lot of messages when consumers use a wildcard subscription. Not all clients handle the avalanche of messages gracefully. It can also cause significant and costly bandwidth consumption.

In HiveOT the recommendation for consumers is NOT to use retain unless there is a specific use-case to do so.

qos

MQTT supports QOS of 0, 1 or 2. In HiveOT a default QOS of 1 is assumed (guaranteed delivery at least once).

A Qos of 0 can be used in case of high frequency updates of the same event, where intermittently dropped messages have little impact on the application.

For actions that are not idempotent a QOS of 2 (exactly once) should be used.

Example of a form schema in a TD:

{
  "form": {
    "op": "writemultipleproperties",
    "contentType": "application/json",
    "topic": "things/{thingID}/properties",
    "mqv:controlPacketValue": "PUBLISH",
    "options": {
      "qos" : "1"
    }
  }
}
dup

This flag is set by the protocol binding if a received message is marked as a duplicate by the MQTT broker.

It is currently ignored.

Documentation

Overview

Package exposedthing that implements the ExposedThing API Exposed Things are used by IoT device implementers to provide access to the device.

Package exposedthing that implements the ExposedThing MQTT protocol binding

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ExposedThing

type ExposedThing struct {

	// deviceID for reverse looking of device by their internal ID
	DeviceID string

	// Protocol binding hook to emit an event
	EmitEventHook func(name string, data interface{}) error

	// Protocol binding hook to emit a single property change notification
	EmitPropertyChangeHook func(name string, data interface{}) error

	// Internal slot with Thing Description document this exposed thing exposes
	TD *thing.ThingDescription
	// contains filtered or unexported fields
}

func CreateExposedThing

func CreateExposedThing(deviceID string, td *thing.ThingDescription) *ExposedThing

CreateExposedThing constructs an exposed thing from a TD.

An exposed Thing is a local instance of a thing for the purpose of interaction with remote consumers. This is intended for use by the ExposedThingFactory only. Call 'Expose' to publish the TD of the thing and to start listening for actions and property write requests.

* td is a Thing Description document of the Thing to expose. * mqttClient client for binding to the MQTT protocol

func (*ExposedThing) Destroy

func (eThing *ExposedThing) Destroy()

Destroy stops serving external requests this is an internal method for use by the factory

func (*ExposedThing) EmitEvent

func (eThing *ExposedThing) EmitEvent(name string, data interface{}) error

EmitEvent publishes a single event to subscribers. This invokes the EmitEventHook from the protocol binding

name is the name of the event as described in the TD, or one of the general purpose events. data is the event value as defined in the TD events schema and used as the payload Returns an error if the event is not found or cannot be published

func (*ExposedThing) EmitPropertyChange

func (eThing *ExposedThing) EmitPropertyChange(
	propName string, newRawValue interface{}, changesOnly bool) error

EmitPropertyChange emits a property value change event

This in turn will notify all observers (subscribers) of the change. The new value will be updated in the value store. TODO: validate property exists

propName is the name of the property in the TD
newRawValue is the new raw value of the property which will also be stored in the valueStore.
changesOnly emit the property change only if the property value has changed.

Returns an error if the property value cannot be published

func (*ExposedThing) GetThingDescription

func (eThing *ExposedThing) GetThingDescription() *thing.ThingDescription

GetThingDescription returns the TD document of this exposed Thing This returns the cached version of the TD

func (*ExposedThing) GetValue

func (eThing *ExposedThing) GetValue(key string) (value *thing.InteractionOutput, found bool)

GetValue reads the latest cached property value from the value store This is concurrent safe and should be the only way to access the values. Returns the InteractionOutput or nill if not found

func (*ExposedThing) HandleActionRequest

func (eThing *ExposedThing) HandleActionRequest(actionName string, message []byte)

HandleActionRequest for this Thing to be invoked by the protocol binding. This passes the request to the registered action handler. If no specific handler is set then the default handler with name "" is invoked.

func (*ExposedThing) SetActionHandler

func (eThing *ExposedThing) SetActionHandler(actionName string,
	actionHandler func(eThing *ExposedThing, actionName string, value *thing.InteractionOutput) error)

SetActionHandler sets the handler for handling an action for the IoT device.

Only a single handler is active. If a handler is set when a previous handler was already set then the
latest handler will be used.

The device code should implement this handler to updated configuration of the device.

actionName is the action name this handler is for. If a single handler can take care of most actions

then use "" as the name to indicate it is the default handler.

The handler should return nil if the write is accepted or an error if not accepted. The property value in the TD will be updated after the property has changed through the change notification handler.

func (*ExposedThing) SetPropertyWriteHandler

func (eThing *ExposedThing) SetPropertyWriteHandler(propName string,
	writeHandler func(eThing *ExposedThing, propName string, value *thing.InteractionOutput) error)

SetPropertyWriteHandler sets the handler for writing a property of the IoT device. This is intended to update device configuration. If the property is read-only the handler must return an error. Only a single handler is active. If a handler is set when a previous handler was already

set then the latest handler will be used.

The device code should implement this handler to updated configuration of the device.

propName is the property name this handler is for. Use "" for a default handler

The handler should return nil if the request is accepted or an error if not accepted. The property value in the TD will be updated after the property has changed through the change notification handler.

type ExposedThingFactory

type ExposedThingFactory struct {
	// contains filtered or unexported fields
}

ExposedThingFactory for managing connected instances of exposed things. Exposed Things are created using the 'expose' method. (the spec also mentions 'produce' and 'exposeInit'. Not clear how that is supposed to work so lets keep it simple.

This factory is intended for IoT devices to produce and expose 'Thing' instances to consumers. It will bind the instance to protocol bindings for publishing TDs, properties and events, and receive action and property change requests as sent by consumed things.

func CreateExposedThingFactory

func CreateExposedThingFactory(
	appID string, clientCert *tls.Certificate, caCert *x509.Certificate) *ExposedThingFactory

CreateExposedThingFactory creates a factory instance for exposed things.

Intended for use by IoT devices and Hub services. IoT devices authenticate themselves with a client certificate obtained during the provisioning process using the idprov client. Hub services have access to the hub service TLS certificate configured in the Hub configuration file.

appID unique ID of the application instance
clientCert with the certificate for authentication
caCert previously obtained CA certificate used to validate the server

func (*ExposedThingFactory) Connect

func (etFactory *ExposedThingFactory) Connect(address string, mqttPort int) error

Connect the factory to message bus. This uses the client certificate for authentication.

address of the hub server that runs the mqtt broker
mqttPort with port of the mqtt broker for certificate auth

func (*ExposedThingFactory) Destroy

func (etFactory *ExposedThingFactory) Destroy(eThing *ExposedThing)

Destroy stops and removes the exposed thing. This stops listening to external requests.

func (*ExposedThingFactory) Disconnect

func (etFactory *ExposedThingFactory) Disconnect()

Disconnect the factory from the message bus

func (*ExposedThingFactory) Expose

func (etFactory *ExposedThingFactory) Expose(deviceID string, td *thing.ThingDescription) (eThing *ExposedThing, found bool)

Expose creates an exposed thing instance and starts serving external requests for the Thing so that WoT Interactions using Properties and Actions will be possible. This also publishes the TD document of this Thing. Returns the exposed thing with a flag whether an existing thing was returned

type ExposedThingMqttBinding

type ExposedThingMqttBinding struct {
	// contains filtered or unexported fields
}

ExposedThingMqttBinding that connects the exposed thing to the message bus

func CreateExposedThingMqttBinding

func CreateExposedThingMqttBinding(eThing *ExposedThing, mqttClient *mqttclient.MqttClient) *ExposedThingMqttBinding

CreateExposedThingMqttBinding constructs a mqtt protocol binding for exposed things.

eThing is the Exposed Thing to bind to
mqttClient MQTT client for binding to the MQTT protocol

func (*ExposedThingMqttBinding) EmitEvent

func (binding *ExposedThingMqttBinding) EmitEvent(name string, data interface{}) error

EmitEvent publishes a single event to subscribers. The topic will be things/{thingID}/event/{name} and payload will be the event data. If the event cannot be published, for example it is not defined, an error is returned.

name is the name of the event as described in the TD, or one of the general purpose events. data is the event value as defined in the TD events schema and used as the payload Returns an error if the event is not found or cannot be published

func (*ExposedThingMqttBinding) EmitPropertyChange

func (binding *ExposedThingMqttBinding) EmitPropertyChange(name string, data interface{}) error

EmitPropertyChange sends a proerty change event to subscribers The topic will be things/{thingID}/event/{name} and payload will be the new property value. If the property cannot be published, for example it is not defined, an error is returned.

name is the name of the property as described in the property affordances section of the TD
data is the property value as defined in the TD events schema and serialized to json

Returns an error if the event is not found or cannot be published

func (*ExposedThingMqttBinding) Start

func (binding *ExposedThingMqttBinding) Start()

Start subscribes to Thing action requests Publish the Thing's own TD

func (*ExposedThingMqttBinding) Stop

func (binding *ExposedThingMqttBinding) Stop()

Stop unsubscribes from all messages

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL