Simple Thing Protocol

For those things that are third-party programmable but lack an effective interface for steward management, the Simple Thing protocol provides a mechanism for adding steward management. For things that only need to report readings or measurements to the steward you might want to look at the Thing Sensor Reporting Protocol instead as it is much simpler to implement.

Operations

Protocol Diagram

The basics:

Here are some examples. A request:

{ path              : '/api/v1/thing/hello/1'
, requestID         : '1'

  // other parameters, if any, go here...
}

An intermediate response:

{ requestID         : '1'
}

An error response:

{ requestID         : '1'
, error             :
  { permanent       : false
  , diagnostic      : '...'
  }
}

A simple result:

{ requestID         : '1'
, result            :
  { status          : 'success'

  // other parameters, if any, go here...
  }
}

A detailed result:

{ requestID         : '10'
, tasks             :
  { 'taskID1'       :
    { status        : 'success'
    }
  , 'taskID2'       :
    { error         :
      { permanent   : true
      , diagnostic  : 'invalid parameter value for task performance'
      }
    }
  }
}

Session Establishment

When an implementation detects a steward advertisement via multicast DNS, it establishes a WebSockets connection to the '/native' resources on the steward:

wss://IP:PORT/manage

After verifying the signature, the implementation sends a pair or hello message, depending on whether it has paired with the steward before.

Pair implementation to steward

The pair message is sent when the implementation has not paired with the steward before:

{ path              : '/api/v1/thing/pair/UUID'
, requestID         : '1'
, name              : '...'
, pairingCode       : 'nnnnnn'
}

If the steward is configured to require a 'pairing code', then an administrator queries the 'place' actor to ascertain the current code and enters that into the implementation, which in turn sends it as the 'pairingCode' parameter.

When the steward receives the pair message, it sends a simple result containing information for future authentication:

{ requestID         : '1'
, result            :
  { status          : 'success'
  , thingID         : '...'
  , params          : { ... }
  }
}

The thingID and params parameters must be retained by the implementation.

The implementation must now issue the hello message using those parameters.

If the steward is configured to require a 'pairing code' and it does not match the 'pairingCode' parameter (or the parameter is not present), then an error response is returned:

{ requestID         : '1'
, error             :
  { permanent       : true
  , diagnostic      : 'not authorized'
  }
}

Implementation authenticates with steward

When the implementation has a thingID/params pairing, the hello message is sent to the steward:

{ path              : '/api/v1/thing/hello/thingID'
, requestID         : '2'
, response          : 'XXXXXX'
}

where the 'thingID' suffix on the path was returned during pairing, and the 'params' parameter returned during pairing is used to generate the 'response' parameter. The 'response' parameter is a Time-Based One-Time Password (TOTP) generated according to RFC 6238*.

*The steward using the Speakeasy node.js implementation of this standard.

When the steward receives the hello message, a simple result indicates that authentication is successful:

{ requestID         : '2'
, result            :
  { status          : 'success'
  }
}

Otherwise, an error response is returned:

{ requestID         : '2'
, error             :
  { permanent       : true
  , diagnostic      : 'authentication failed'
  }
}

Session Exchanges

When the implementation is successfully authenticated, it exchanges zero or more messages with the steward. Note that requests may be originated both by the implementation and the steward.

State is maintained over the duration of a session. If the underlying connection is broken, then any mappings of thingIDs, eventIDs, and taskIDs should be deleted by both peers. (Note that in version 1 of the Simple Thing protocol, taskID values have no significance outside of a single message exchange.

Define Prototypes

In order to understand the properties which are used to describe the state of a thing, you MUST read the documentation on the Device Taxonomy.

The prototype message is sent by the implementation to the steward to define one or more thing prototypes:

{ path              : '/api/v1/thing/prototype'
, requestID         : '3'
, things            :
  { '/device/A/B/C' :
    { device        :
      { name        : '...'
      , maker       : '...'
      }
    , observe       : [ 'o1', 'o2', ..., 'oN' ]
    , perform       : [ 'p1', 'p2', ..., 'pN' ]
    , name          : true
    , status        : [ 's1', 's2', ..., 'sN' ]
    , properties    :
      { 
        // other properties go here...
      }
    , validate      :
      { observe     : true
      , perform     : true
      }
    }

    // other prototype definitions, if any, go here...
  }
}

When the steward receives the prototype message, either a detailed result or error response is returned:

{ requestID         : '3'
, things            :
  { '/device/A/B/C' :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

or

{ requestID         : '3'
, things            :
  { '/device/A/B/C' :
    { error         :
      { permanent   : true
      , diagnostic  : 'missing properties parameter'
      }
    }

    // other results, if any, go here...
  }
}

Define Instances

The register message is sent by the implementation to the steward to register one or more things corresponding to a prototype:

{ path              : '/api/v1/thing/register'
, requestID         : '4'
, things            :
  { 't1'            :
    { devicetype    : '/device/A/B/C'
    , name          : '...'
    , status        : '...'
    , device        :
      { name        : '...'
      , maker       : '...'
      , model       :
        { name      : '...'
        , descr     : '...'
        , number    : '...'
        }
      , unit        :
        { serial    : '...'
          udn       : 'UID'
        }
      }
    , updated       : timestamp
    , info          :
      {
      // per-instance properties values go here...
      }
    }

    // other thing registrations, if any, go here...
  }
}

PLEASE READ CAREFULLY: It is imperative that the choice of the udn parameter be both globally-unique and specific to the thing being registered. For example, if the thing is a PTZ mount for a mobile device, the udn parameter must uniquely identify the PTZ mount, regardless of whatever mobile device is providing the implementation. (Think of the udn parameter as the serial number of the core function, and not the IP or MAC address of the platform: the IP or MAC address of a thing may change, but its serial number never will.)

When the steward receives the register message, either a detailed result or error response is returned. If a result response is returned, it contains a thingID value for each thing, e.g.,

{ requestID         : '4'
, things            :
  { 't1'            :
    { status        : 'success'
      thingID       : 'thingID1'
    }

    // other results, if any, go here...
  }
}

or

{ requestID         : '4'
, things            :
  { 't1'            :
    { error         :
      { permanent   : false
      , diagnostic  : 'UDN is already registered'
      }
    }

    // other results, if any, go here...
  }
}

The thingID value is used by both the steward and implementation when referring to the thing for the duration of the session.

Update Properties

The update message is sent by the implementation to the steward to update the state of a thing:

{ path              : '/api/v1/thing/update'
, requestID         : '5'
, things            :
  { 'thingID2'      :
    { name          : '...'
    , status        : '...'
    , updated       : timestamp
    , info          :
      {
      // the entire list of properties goes here...
      }
    }

    // updates for other things, if any, go here...
  }
}

When the steward receives the update message, either a detailed result or error response is returned:

{ requestID         : '5'
, things            :
  { 'thingID2'      :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

or

{ requestID         : '5'
, things            :
  { 'thingID2'      :
    { error         :
      { permanent   : true
      , diagnostic  : 'no such thingID'
      }
    }

    // other results, if any, go here...
  }
}

Note that if an implementation does not send an update for a thing at least every 60 seconds, then the steward will automatically change the thing's status to 'absent'.

Observe and Report Events

The observe message is sent by the steward to the implementation to ask it to observe one or more events:

{ path              : '/api/v1/thing/observe'
, requestID         : '6'
, events            :
  { 'eventID1'      :
    { thingID       : 'thingID1'
    , observe       : '...'
    , parameter     : '...'
    , testOnly      : false
    }

    // observation requests for other events, if any, go here...
  }
}

When the implementation receives the observe message, if the testOnly parameter is true, then the implementation evaluates the observation parameters, and either a detailed result or error response is returned:

{ path              : '/api/v1/thing/report'
, requestID         : '6'
, events            :
  { 'eventID1'      :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

or

{ path              : '/api/v1/thing/report'
, requestID         : '6'
, events            :
  { 'eventID1'      :
    { error         :
      { permanent   : true
      , diagnostic  : 'invalid parameter value for event observation'
      }
    }

    // other results, if any, go here...
  }
}

If the testOnly parameter is false, then the implementation immediately returns a proceed status or an error response:

{ path              : '/api/v1/thing/report'
, requestID         : '6'
, events            :
  { 'eventID1'      :
    { status        : 'proceed'
    }

    // other results, if any, go here...
  }
}

and going forward, the eventID value is used by both the steward and implementation when referring to the thing for the duration of the session.

Whenever any of the events occur in the future, will send an report message to the steward:

{ path              : '/api/v1/thing/report'
, requestID         : '7'
, events            :
  { 'eventID1'      :
    { reason        : 'observe'
    }

    // observation reports for other events, if any, go here...
  }
}

and the steward responds with either a detailed result or error response:

{ requestID         : '7'
, events            :
  { 'eventID1'      :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

or

{ requestID         : '7'
, events            :
  { 'eventID1'      :
    { error         :
      { permanent   : true
      , diagnostic  : 'invalid eventID'
      }
    }

    // other results, if any, go here...
  }
}

Report Event Observation Failure

If the implementation is no longer able to observe an event:

{ path              : '/api/v1/thing/report'
, requestID         : '8'
, events            :
  { 'eventID1'      :
    { reason        : 'failure'
      permanent     : true
      diagnostic    : '...'
    }

    // observation reports for other events, if any, go here...
  }
}

and the steward responds with a detailed result response:

{ requestID         : '8'
, events            :
  { 'eventID1'      :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

Cancel Event Observation

If the steward no longer wishes for the implementation to observe an event:

{ path              : '/api/v1/thing/report'
, requestID         : '9'
, events            :
  { 'eventID1'      :
    { reason        : 'cancel'
    }
  }
}

and the implementation responds with either a detailed result or error response:

{ path              : '/api/v1/thing/report'
, requestID         : '9'
, events            :
  { 'eventID1'      :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

or

{ path              : '/api/v1/thing/report'
, requestID         : '9'
, events            :
  { 'eventID1'      :
    { error         :
      { permanent   : false
      , diagnostic  : 'database busy, please retry...'
      }
    }

    // other results, if any, go here...
  }
}

Perform Tasks

The perform message is sent by the steward to the implementation to ask it to observe one or more tasks:

{ path              : '/api/v1/thing/perform'
, requestID         : '10'
, tasks             :
  { 'taskID1'       :
    { thingID       : 'thingID1'
    , perform       : '...'
    , parameter     : '...'
    , testOnly      : false
    }

    // performance requests for other tasks, if any, go here...
  }
}

When the implementation receives the perform message, if the _testOnly parameter is true, then the implementation evaluates the performance parameters, and either a detailed result or error response is returned:

{ path              : '/api/v1/thing/report'
, requestID         : '10'
, tasks             :
  { 'taskID1'       :
    { status        : 'success'
    }

    // other results, if any, go here...
  }
}

or

{ path              : '/api/v1/thing/report'
, requestID         : '10'
, tasks             :
  { 'taskID1'       :
    { error         :
      { permanent   : true
      , diagnostic  : 'invalid parameter value for task performance'
      }
    }

    // other results, if any, go here...
  }
}

If the testOnly parameter is false, then the implementation immediately returns a detailed result or error response, and then begins to perform the tasks.

Security

Security is based on these assumptions: