Obix Specification

______

Obix Specification

Working Draft

______

Version 0.2

22 Jan 04

1Introduction

This mission of the group is to define standard XML data representations and web services for data acquisition and control systems. The core of the standard will be a foundation of common types and services applicable to horizontal issues including discovery, points, historical trends, and alarming. Additional libraries may be defined upon this foundation for vertical domains (HVAC, lighting, security, etc) and heterogeneous protocols (BACnet, Lonworks, Mobus, etc).

1.1Architecture

The Obix architecture is built as a stack of data and service abstractions.

Note this was my original illustration, but is a bit obsolete.

The working group is currently focused on the following layers of the stack:

  • Type Library: The foundation of the stack is an XML data model which defines a core type system including points, historical trends, alarms, units, and status. These types allow normalized control system information to be expressed in XML documents.
  • Horizontal Services: A set of web services which are common to all application domains including discovery, points, historian, and alarm.

Future work entails:

  • Generic Object Model Language: A set of rules for building domain and vendor specific type systems using an extensible model.
  • Vertical Models: A library of data type models built using the generic object modeling language to standardize specific domain models including BACnet, Lonworks, and security systems.
  • Vertical Services: A set of web services for interacting with vertical domain applications.

1.2Horizontal Services

The first phase of Obix is focused on defining a horizontal set of services to enable basic interaction with data acquisition and control systems. These services are:

  • DiscoveryService: This service provides the fundamental bootstrap service used to discover other services which may be available. The DiscoveryService provides the ability to navigate the system’s logical model using a hierarchical structure and defines how this logical model maps to other plugin services.
  • PointService: The PointService is used to read and write data which fits the traditional definition of a “control point” – usually a sensor or actuator. A point provides a normalized representation of an atomic data value with its associated metadata.
  • HistoryService: The HistoryService is used to query a time sampled history of point data. It uses the same point data type models, but adds a temporal dimension.
  • AlarmService: This service provides a simple normalized representation for alarm queries and acknowledgement.

2Core Type Library

Before defining any services it is helpful to define a core set of types which provide common abstractions used by all services.

Note for now I am just using a extremely simple C/Java syntax for types – I expect that we formally define these things in XML Schema? The ^ means href or inlined and [] means list of.

2.1Primitive Types

The following built-in primitive types are used to define all other types. These types correspond exactly to XML Schema. Obix only uses a subset of the XML Schema predefined simple types.

  • boolean
  • int
  • double
  • dateTime
  • duration
  • anyURI
  • string
  • language

By convention primitive types will start with a lower case letter and Obix types with a capital letter.

2.2Text

The basic philosophy of Obix localization is that requests supply a desired localization language, and the service automatically localizes all strings which are human consumable. To make a clear distinction between machine consumable strings which will not be localized and human consumable strings which should be, we define a special string type called Text. Whenever a type declares a Text element, the implied semantics are that the service will attempt to localize when possible.

Text extends string

2.3Request / Response

The following abstract types are used for all service requests and responses.

Request

{

lang: language // used for all Text elements

}

Response

{

// standard exception reporting? do that at SOAP level?

}

2.4Indirect References

For types which map to data reused across many service operations, it is preferable to return a URI to the data, rather than having to inline it into every XML response. In these cases an href attribute may be used.

For my pseudo code I am using the ^ to indicate where an href may be used as an alternative to in-lining all the data.

3Discovery Service

The DiscoveryService is the bootstrap service of the Obix architecture. It allows clients to interrogate the logical structure of the system and discover other services available.

3.1DiscoveryNode

The basic type used in discovery is an hierarchical tree of DiscoveryNodes.

DiscoveryNode

{

id: string // identifies node in DiscoveryService

displayName: Text // readable name for the node

documentationUri: uri // uri to additional documentation

icon: anyURI // href to 16x16 GIF or PNG file

hasChildren: boolean // true if children are available

children: DiscoveryNode[] // only when read with proper depth

pointId: string // if usable by PointService

historyId: string // if usable by HistoryService

alarmId: string // if usable by AlarmService

}

The DiscoveryNode provides a basic set of information to display information to the user including a name, description, and icon. Every node may contain zero or more children nodes which allows the Obix implementation the ability to expose any logical structure desirable. The id field is used to identify the node by the DiscoveryService itself.

Each DiscoveryNode also provides the ability to act as a naming service and jump point into other pluggable services. By convention each plugin service FooService declares a field called fooId in the DiscoveryNode which identifies the node to the service. If a DiscoveryNode is not designed to work with a specific service then the id field is omitted.

For example consider a node with a pointId of “pt20”. That would mean that the node can be accessed as a Point using the PointService. The id “pt20” would be used to read and write the node as a Point. If a historyId were provided, that would indicate that clients could use the HistoryService to access historical information about the node.

The format of ids is always a implementation matter. Clients should consider ids opaque strings.

How service bindings and ids are declared within their Nodes?

How the URI of the service itself is discovered?

3.2DiscoveryService

DiscoveryService

{

read(DiscoveryReadReq) -> DiscoveryReadRes

}

DiscoveryReadReq extends Request

{

ids: string[] // list of ids to read

depth: int // number of children levels to include

}

DiscoveryReadRes extends Response

{

nodes: DiscoveryNode[] // each node matches request id

}

The DiscoveryService supports one operation called read. The read request takes one or more ids and returns their corresponding nodes. For bootstrap the root node is always identified with the id of “root”.

The depth argument specifies how many children levels to read. A value of zero indicates to just return the node itself. A value of one means return the node and its direct children, two means children and grandchildren. The children field of the DiscoveryNode is only applicable when children are read at the specified depth. The hasChildren field should always be supported so that clients performing a lazy discovery know whether to dive down into the node.

4PointService

The PointService provides a normalized represented of information using a traditional control point paradigm. The PointService supports six types of points:

  • BooleanPoint: models digital boolean true/false values
  • DoublePoint: models numeric/analog values
  • MultistatePoint: models values with a discrete range using string keys
  • StringPoint: models data as open ended character strings
  • DateTimePoint: models an absolute point in time
  • DurationPoint: models a relative duration of time

4.1Point

All Points extend from a common abstract definition:

Point

{

id: string // id used by PointService operations

value: PointValue // current value of point

valueText: Text // formatted representation of value

status: Status // current status of point

facets^: PointFacets // additional metadata (may be href)

}

PointValue

{

choiceId: string // must be id of Choice OR scalar

value: * // value, but never both

}

PointFacets

{

displayName: Text // human readable name

documentationUri: uri // uri to additional documentation

writable: boolean // can this point be written

obselete: boolean // if point is deprecated

restrictToChoices: boolean // value must be in choice list

choices^: Choice[] // list of choices for value (or href)

}

Choice

{

id: string // the string key for the choice

value: * // the value of this choice

displayName: Text // the human text for this choice

}

I took the liberty of trying to nail down a precise vocabulary. I used the term Facets for metadata to be consistent with XML Schema – do we like that? I used the term Choice for value labels, range, etc.

4.2BooleanPoint

BooleanPoint extends Point

{

value: BooleanValue

facets^: BooleanFacets

}

BooleanValue extends PointValue

{

value: boolean

}

BooleanFacets extends PointFacets

{

}

4.3DoublePoint

DoublePoint extends Point

{

value: DoubleValue

facets^: DoubleFacets

}

DoubleValue extends PointValue

{

value: double

}

DoubleFacets extends PointFacets

{

min: double // min value inclusive

max: double // max value inclusive

unit^: Unit // units of measurement (may be href)

resolution: double // resolution of value...

precision: int // num of digits to display after decimal

}

4.4MultistatePoint

MultistatePoint extends Point

{

value: MultiStateValue

facets^: MultistateFacets

}

MultistateValue extends PointValue

{

value: // Can never be used - must use choice id

}

MulistateFacets extends PointFacets

{

}

Is the choices model sufficient for describing the range of discrete values? I feel so.

4.5StringPoint

StringPoint extends Point

{

value: StringValue

facets^: StringFacets

}

StringValue extends PointValue

{

value: string

}

StringFacets extends PointFacets

{

min: int // min number of characters inclusive

max: int // max number of characters inclusive

}

4.6DateTimePoint

DateTimePoint extends Point

{

value: DateTimeValue

facets^: DateTimeFacets

}

DateTimeValue extends PointValue

{

value: dateTime

}

DateTimeFacets extends PointFacets

{

min: dateTime // min value inclusive

max: dateTime // max value inclusive

}

Do we need some sort of resolution/precision?

4.7DurationPoint

DurationPoint extends Point

{

value: DurationValue

facets^: DurationFacets

}

DurationValue extends PointValue

{

value: duration

}

DurationFacets extends PointFacets

{

min: duration // min value inclusive

max: duration // max value inclusive

}

Do we need some sort of resolution/precision?

4.8Status

The Status type is used by all points to provide normalized status information. It is not intended to provide comprehensive status details, only a normalized summary.

Status

{

down: boolean // network/communication problems

fault: boolean // software/hardware/config problems

inAlarm: boolean // if in the alarm condition

unackAlarm: boolean // if last alarm was never acknowledged

outOfService: boolean // if manually disabled

overridden: boolean // if manually overridden

null: boolean // used to indicate null/auto/void

}

4.9Units

Units of measurement is a thorny issue that has always plagued software that processes analog information from the real world. Obix provides a unit framework for mathematically defining units. An extensive database of unit objects is also predefined.

All units measure a specific quantity or dimension in the physical world. Every known dimension can be expressed as a ratio of the seven fundamental dimensions: length, mass, time, temperature, electrical current, amount of substance, and luminous intensity. These seven dimensions are represented in SI respectively as kilogram (kg), meter (m), second (sec), Kelvin (K), ampere (A), mole (mol), and candela (cd).

Obix defines the Dimension type as a ratio of the seven SI units using a positive or negative exponent. A value of zero indicates an absence of the base unit in the ratio.

Dimension

{

kg: int // exponent for kilograms

m: int // exponent for meters

sec: int // exponent for seconds

K: int // exponent for Kelvin

A: int // exponent for amperes

mol: int // exponent for moles

cd: int // exponent for candela

}

Units with equal dimensions are considered to measure the same physical quantity. This is not always precisely true, but is good enough for practice. This means that units with the same dimension are convertible. Conversion can be expressed by specifying the formula required to convert the unit to the dimension’s normalized unit. The normalized unit for every dimension is the ratio of SI units itself. For example the normalized unit of energy is the joule m2kgs-2. The kilojoule is 1000 joules and the watt-hour is 3600 joules. Most units can be mathematically converted to their normalized unit and to other units using the linear equations:

unit = dimension  scale + offset

toNormal = scalar  scale + offset

fromNormal = (scalar - offset) / scale

toUnit = fromUnit.fromNormal( toUnit.toNormal(scalar) )

There are some units which don’t fit this model including logarithm units and units dealing with angles. But this model is good enough for practice. Units which don’t fit this model should use a dimension where every exponent is set to zero. Applications should not attempt conversions on these types of units.

The Unit type extends from Ref allowing a library of predefined Units with global URIs.

Unit extends Ref

{

name: Text // human name for unit

symbol: Text // human symbol for unit

dimension: Dimension // ratio of SI base units

scale: double // toNormal/fromNormal

offset: double // toNormal/fromNormal}

4.10PointService

PointService

{

read(PointReadReq) -> PointReadRes

write(PointWriteReq) -> PointWriteRes

}

PointReadReq extends Request

{

ids: string[] // list of ids to read

facets: boolean // include the facets for each point

valueText: boolean // include valueText for each point

unit^: Unit // convert all DoublePoints to given unit

}

PointReadRes extends Response

{

points: Point[] // maps to ids in request

}

PointWriteReq extends Request

{

ids: string[] // ids to write

values: *[] // values to write

unit^: Unit // units when writing DoublePoints ???

}

PointWriteRes extends Response

{

// error conditions per write?

}

Subscription for change of value is definitely something we need eventually. Should we tackle that now or just take poll approach for Obix 1.0? Part of the problem is that async event web services standards are just emerging.

5HistoryService

The HistoryService allows querying of historical time sampled data. Since historical data is typically collected on Points, there is a great deal of overlap in the types used.

5.1PointHistory

All point historical data uses the following abstract type:

PointHistory

{

id: string // id used by HistoryService

name: Text // human readable name

description: Text // human readable description

facets^: PointFacets // metadata for samples

samples: HistorySample[] // list of samples for query

}

HistorySample

{

timestamp: dateTime // time sample was collected

value: * // value at sample time

valueText: Text // formatted text of value

status: Status // status at sample time

}

5.2BooleanHistory

BooleanHistory extends PointHistory { facets^: BooleanFacets }

BooleanSample extends HistorySample { value: BooleanPointValue }

5.3DoubleHistory

DoubleHistory extends PointHistory { facets^: DoubleFacets }

DoubleSample extends HistorySample { value: DoublePointValue }

5.4MultistateHistory

MultistateHistory extends PointHistory

{ facets^: MulitstateFacets }

MultistateSample extends HistorySample

{ value: MultiStatePointValue }

5.5StringHistory

StringHistory extends PointHistory { facets^: StringFacets }

StringSample extends HistorySample { value: StringPointValue }

Do we need DurationHistory and DateTimeHistory???

5.6HistoryService

HistoryService

{

read(HistoryReadReq) -> HistoryReadRes

}

HistoryReadReq extends Request

{

ids: string[] // list of ids to query

facets: boolean // include the facets for each history

valueText: boolean // include valueText for each sample

unit^: Unit // convert DoubleHistories to given unit

minTime: dateTime // min inclusive range of query

maxTime: dateTime // max inclusive range of query

}

HistoryReadRes extends Response

{

histories: PointHistory[] // maps to ids in request

}

6AlarmService

TODO - this will probably the hardest task to get normalization consensus

7Change Log

7.1Version 0.1 (14 Jan 03)

  • Original draft

7.2Version 0.2 (22 Jan 03)

  • Change to use “displayName” and “documentationUri” for DiscoveryNode, PointFacets, and Choice
  • Make point value’s a union of scalar or choice id by creating the following new types: PointValue, BooleanValue, DoubleValue, MultiStateValue, StringValue, DurationValue, and DateTimeValue

1