This is a set of loosely-organized notes about developing a flexible machine-to-machine protocol that uses CoAP.
The hope is to refine these thoughts into a standardized M2M protocol.
The pairing subsystem implements a generic way to link two resources so that changes to one cause the other to be modified automatically.
For example, let's say that you had two smart light switches
that exposed the path /1/value
. If you observe
this path, you get asyncronous updates whenever somoene
adjusts that dimmer. Pairing allows you to have changes
to /1/value
on one of those smart light switches
automatically update /1/value
on the other.
There are three types of pairings:
- Pull-type, where the resource being observed is remote and the resource being changed is local.
- Push-type, where the resource being observed is local and the resource being changed is remote.
- Sync-Type, where the both local and remote resources are observed and kept in-sync.
Pairings are managed in-band via the /.p
path. Each pairing
has the following properties:
- Stable flag
- Enabled flag
- Content type (optional)
- Local path (Considered a Local Action when pairing is a pull-type)
- Remote URL (Despite name, may not actually be remote)
- Type indicator (push, pull, or sync)
- An optional predicate string
- An optional transform string (Only for "pull" type)
Also helpful:
- Last result code from remote URL (for debugging)
- Fire count (number of times his pairing has been triggred)
A "predicate" is a way to prevent a pairing from triggering unless the changed value meets specific criteria. For example, "only trigger on the rising edge at a threshold of 75%".
A "transform" allows you define a mathmatical relationship to be between the remote value and the local value.
Both predicates and transforms are implemented with a simple float-based RPN language, chosen because it is easy to implement and understand. The maximum stack size is currently undefined, but should be larger than 16. Currently, the following operations are defined:
v_r
orv
: Push the new value from the other device onto the stack.v_l
: Push the current value onto the stack. This allows for hysteresis and edge detection.v_t
: Push the transformed value onto the stack (only available for predicates).*
: Pop two values off of stack, multiply them, and push result onto stack./
: Pop two values off of stack, divide them, and push result onto stack.+
: Pop two values off of stack, add them, and push result onto stack.-
: Pop two values off of stack, subtract them, and push result onto stack.!
: Pop one value off of stack, if it is less than "0.5" then push "1.0", otherwise push "0.0".==
: Pop two values off of stack, compare them, and push "1.0" if they are equal, "0.0" if not equal.!=
: Pop two values off of stack, compare them, and push "1.0" if they are equal, "0.0" if not equal.>
/<
: Greater/Less than. Works like you would imagine. First pop is RHS, second pop is LHS.>=
/<=
: Greater/Less than or equal to. Works like you would imagine. First pop is RHS, second pop is LHS.&&
: Logical and. Pop two values off of stack, If both are greater than "0.5", and push "1.0". Otherwise, push "0.0"||
: Logical or. Pop two values off of stack, If either are greater than "0.5", and push "1.0". Otherwise, push "0.0"XOR
: Logical xor. Pop two values off of stack, If one is greater than "0.5" and the other less than "0.5", and push "1.0". Otherwise, push "0.0"MIN
: Pop two values off the stack, compare them, and push the smaller of the two.MAX
: Pop two values off the stack, compare them, and push the larger of the two.ROUND_N
: Round value on stack to nearest whole numberROUND_U
: Round value on stack up to next whole number if not already a whole numberROUND_D
: Round value on stack down to next whole number if not already a whole numberSIN
: Sine function. Uses turns, instead of radians or degrees. (1 turn == 2π == 360°)COS
: Cosine function. Uses turns, instead of radians or degrees. (1 turn == 2π == 360°)ASIN
: ArcSine function. Returns turns, instead of radians or degrees. (1 turn == 2π == 360°)ACOS
: ArcCosine function. Returns turns, instead of radians or degrees. (1 turn == 2π == 360°)IN_RANGE
: Pop three values off the stack, if the middle value is between the two other values then push '1.0'. Push '0.0' otherwise.
Custom constants can be defined and modified at runtime. Each object can also define additional constants which are accessable in predicates and transforms. For example:
/rtc/d
: Time of day in hours. Values are between 0.000 - 23.999 and are in local time./rtc/w
: Time scaled to the week. Values are between 0.000 - 6.999 and are in local time.0.0
is midnight on Sunday./rtc/d_sr
: Time of sunrise, in terms ofrtc.d
./rtc/d_ss
: Time of sunset, in terms ofrtc.d
.
The actual predicate/transform compiler/interpreter doesn't necessarily have to use floating point: fixed point may be appropriate under certain circumstances, or perhaps half-floats(16-bit floating point). Anything beyond 32-bit floating point representations is excessive.
Examples of predicates:
v_r 0 ==
: "v_r
== 0", or "ifv_r
is zero"v_r 0.2 > v_r 0.6 < &&
: "(v_r
> 0.2) && (v_r
< 0.6)", or "ifv_r
is between 0.2 and 0.6"v_r 0.75 > v_l 0.75 <= 0 &&
: "(v_r
> 0.75) && (v_l
<= 0.75)", or "only trigger on the rising edge with a threshold of 0.75"/rtc/d_sr /rtc/d /rtc/d_ss IN_RANGE !
: Only trigger when the sun is down.
Examples of transforms:
v 0.6 *
: "v
* 0.6"v v * 0.8 MIN
: "minimum(v
*v
, 0.6)"0 v 1 min max 0.25 * cos
: Limitv
to between 0.0 and 1.0, then multiply that by 0.25 and take that value's cosine.1.0
: Always set to 1.0 when triggered.
Predicates are applied after the transform, making v_t
available in
transform functions. Predicates and transforms
are only used for "push" and "pull" types, not for "sync" types.
There is a special top-level object called /all
. For unicast traffic,
it is designed for changing writable values on all objects on the
device. For group traffic, it is designed for changing writable values
on all objects in that group. It also provides a convenient way to add
or remove all group-aware objects on the device to or from a group.
Let's say you had a group called living-room-lights.group
, which
included the following four objects across three physical devices:
- coap://power-strip.local/2
- coap://power-strip.local/4
- coap://lights-1.local/1
- coap://lights-2.local/1
To change the output of all of these objects at the same time, you
would then send out a POST
to the following resource:
- coap://living-room-lights.group/all/output
To change a scene, you would POST
the scene name to the following
resource:
- coap://living-room-lights.group/all/scene
Note that doing this would not affect objects /1
and /3
on power-strip.local
, since those objects are not a part of the
group living-room-lights.group
.
Objects are typically named with short numeric identifiers. For
example, if you have a power strip with 2 controllable outlets, then
you would have top level objets /1
, /2
and /3
. If the device can
have more than one type of object, the naming may be more descriptive
or even hierarchical.
For example, an alarm system device for controlling a Concord 4 alarm system might have a hierarchy that looks like this:
[partition-number]/light/[light-number]/
name
desc
type
output
[partition-number]/zone/[zone-number]/
name
desc
type
input
fault
trouble
bypass
[partition-number]/arm/
level
date
user
[partition-number]/alarm/
code
output
user
is-burg
is-trouble
is-fault
is-fire
[partition-number]/ui/
A local action consists of a local URL (a path and possibly a query component). Local actions are more of a concept than a concrete resource. When a local action is triggered, a POST is generated for the given path. Local actions are associated with:
- pull-style pairings
- scenes
- Schedules
- timers
Local actions can used to perform simple tasks like changing the
state of an output, but they can also be used to reconfigure
how objects behave (such as changing the occupied
scene to be
night
)
Scenes are ways to instantly set several outputs (via local actions) to a specific state. Since each device can have more than one output object, each output object can have different scenes associated with it. If a scene is set for which there is no configured output, no change occurs.
Each scene has a human-readable identifier and a list of local actions
to take. Scenes may also be defined as alternate names for other
scenes, such as unoccupied
might be another name for off
.
Scenes are kept track of per-object. A single device can have more than one object and have different scenes set per-object. The paths are:
[obj]/scene
(for setting a scene or checking what scene was last set)[obj]/scenes
(List of scenes that this object supports)
off
: All lights slowly dim over 3 seconds to finally turn completely off.on
: Lights turn on to 100% over a period of a second.night
: Lights turn on to their lowest setting. Some lights are kept off.movie
: Lights dim slowly over 3 seconds to low levels.romantic
: Lights fade to 40%.default
: Power-up state
These scenes are typically not set directly. The occupancy-related
scenes are set by changes to all/occupied
flag. The alarm-related
flags are set by changes to all/alarm
.
prev
: Go to previous state. Kind of like a scene undo.unoccupied
: Generally used to turn off lights and appliances. Should ramp down dimmers slowly.occupied
: If we have transitioned to theunoccupied
scene within the past 10 seconds (and that scene change actually did something), then this scene is interpreted asprev
. Otherwise it is interpreted as configured. By default this is configured to do nothing.
The following scenes are special in that they are not typically set
directly, but instead set when someone sets /all/alarm
to one of the
alarm values.
alarm.alert
: Immediate lights to 100%.alarm.burg
: Immediate lights to 100%, lights then alternate between 100% and 75% every half second.alarm.fire
: Immediate lights to 100%, lights then every two seconds emit three half-second pulses of 75%.
These special alarm scenes take over the operation and configuration of the light and prevent the light from being changed remotely until the alarm is cleared (manually turning off the light still works).
The device schedule is a way to schedule local actions or otherwise change local state, much like a cron job.
For example, schedules can be used to:
- Automatically turn on or off lights at specific times
- At 11pm, change the behavior of the
occupied
scene on all lights to benight
instead ofon
. - At 8am, change the behavior of the
occupied
scene on all lights to beon
instead ofnight
. - Schedule specific zones to be bypassed on certain days.
- Change the color temperature over an evening
Schedule objects consist of:
- Stable flag
- Enabled flag
- Human readable short name
- Human readable description
- Start date
- End date
- Repeat Schedule (every week, every hour, etc. See cron specification. TODO: Figure out how to specify relative to sunset/sunrise)
- Observable trigger endpoint
- List of actions to perform when triggered
Schedules are kept track of via the top-level path smcp-schedule
.
Timers are similar to schedules but are more open-ended. Each timer has a countdown duration and and can perform local actions when the timer has expired.
Timers can be used to:
- Turn objects on or off after a certain amount of time has elapsed.
- Periodically change the state of an object (flashing lights)
Timer objects consist of:
- Stable flag
- Enabled flag
- Human readable short name
- Human readable description
- Reset duration
- Remaining duration
- running flag
- recurring flag
- reset action
- Observable trigger endpoint
- List of actions to perform when triggered
Timers are kept track of via the top-level path smcp-timer
.
The clock class is a way to get and set various aspects about the current time. It has the following properties:
/clock/local-time
Current local time. Defaults to number of seconds since the epoch in the current time zone./clock/utc-time
Universal coordinated time. Defaults to number of seconds since the epoch./clock/tz
Standard Time-zone offset, in hours./clock/dst
Is daylight saving time observed./clock/dst-start
DST start time/clock/dst-stop
Is daylight saving time observed.
It is often useful to have reference times for astrological events, like sunrise or sunset.
[obj]/lon
Longitude (W is positive, E is negative). ROUND TO TWO DECIMAL PLACES. Used for calculating sunrise/sunset times.[obj]/lat
Latitude (N is positive, S is negative). ROUND TO TWO DECIMAL PLACES. Used for calculating sunrise/sunset times.[obj]/local-sunset
Time of the next sunset in local time.[obj]/local-sunrise
Time of the next sunrise in local time.[obj]/local-tide-high
Time of the next high-tide in local time.[obj]/local-tide-low
Time of the next low-tide in local time.[obj]/local-moon-full
Time of the next full moon in local time.[obj]/moonlight-twilight
Indicator of the amount of moonlight that will be present in the hours after twilight.[obj]/moonlight-dawn
Indicator of the amount of moonlight that will be present in the hours prior to dawn.
The moonlight calculations are based on a variety of factors:
- The phase of the moon.
- The aggregate angle of the moon in the sky during the given time period.
[obj]/name
(Human-readable string) Configurable name of object[obj]/desc
(Human-readable string) Configurable description of object[obj]/type
(Human-readable string) Class/type of object.
These properties are only on the /all
object.
[obj]/alarm
[obj]/occupied
- All properties from
Base
[obj]/coap-group
(CoAP group membership)[obj]/scenes/*
(Configured Scenes)[obj]/scene
(Current scene)[obj]/state/alert
- Set to a string to communicate information. Not all alerts are supported by all devices.none
- Normal operation.select
- Provides some sort of visible or audible indication once.lselect
- Similar toselect
, but performs the action 15 times, or until alert value is changed.ok
- Indicates that a user administrative action has completed successfully.err
- Indicates that a user administrative action has not completed successfully.
[obj]/cfg/lock
(Boolean) if set disallows output from being changed
[obj]/state/d
(Unsigned integer value from 0 to 2^31, in milliseconds) This is the amount of time remaining in the current transition. To abort the current transition, set this to zero. Setting this to anything other than zero will cause an error.[obj]/state/ds
Optional. String identifying the transition space. Possible values depend on the ultimate type of device, but here are some:native
: Transition using the native primary values directly.hue
: Transition using a hue-based color space.
- All properties from
BaseOutput
[obj]/state/on
(Boolean) the value of the output[obj]/state/toggle
(Boolean) Toggles 'on' if set to true. Always reads as false. Does nothing when set to false.- If also
Transitionable
...[obj]/cfg/d_on
(Optional. Integer value from 0 to 65535, in milliseconds) This is the default transition time for whenon
transitions from off to on. Default value is device specific.[obj]/cfg/d_off
(Optional. Integer value from 0 to 65535, in milliseconds) This is the default transition time for whenon
transitions from on to off. Default value is device specific.
- All properties from
BaseOutput
[obj]/state/level
(Floating point value from 0 to 1)[obj]/state/level_inc
(Floating point value from -1 to 1) - increments or decrements the value of "level" by the given amount.- If also
OnOffOutput
...[obj]/cfg/scene_on
(Optional. String. Default is unspecified) The scene that is selected whenever we transition from off to on. If unspecified, the previous value is maintained.
The meaning of the values between 0 and 1 should be distributed such that the perceived "action" is evenly distributed from 0 to 1. This means that for quantities such as light or sound, the curve should be exponential. For things with physical actuators like window blinds, the appropriate distribution would likely be linear.
If value is queried during a transition, additional parameters may be returned if the accept type is set to either JSON or url query encoded:
So, the following POST
would (regardless of POST
content) cause all
the lights in the living room to smoothly go from off to full on over
the period of one second:
coap://living-room.group/all/state?d=1&level=1&on=1
- All properties from Base
[obj]/state/open
- All properties from Base
[obj]/state/on
- All properties from Base
[obj]/state/pressed
- All properties from Base
[obj]/state/event
- All properties from Base
[obj]/state/level
(Floating point value from 0 to 1)[obj]/state/level_inc
(Floating point value from -1 to 1) Emits changes to the current level.
- All properties from OnOffInput, LevelInput
- All properties from Base
[obj]/nrg/v
(RO, Voltage, Volts)[obj]/nrg/i
(RO, Current, Amps)[obj]/nrg/p
(RO, Real power, Watts)[obj]/nrg/va
(RO, Apparent power, volt-amps)[obj]/nrg/f
(RO, Frequency, Hertz)[obj]/nrg/pf
(RO, Power Factor, Unitless)[obj]/cfg/nrg/v_max
(RW, max voltage, volts)[obj]/cfg/nrg/v_min
(RW, min voltage, volts)[obj]/cfg/nrg/i_max
(RW, max current, amps)[obj]/cfg/nrg/i_min
(RW, min current, amps)
- All properties from Transitionable, OnOffOutput
[obj]/info/bri_max
Light output in lumens under the following circumstances:state/on
is set to true.state/ct
(if applicable) is set to the brightest color temperature for this light.state/level
is set to 100% (1.0)
[obj]/info/pow_max
Power draw in watts under the following circumstances:state/on
is set to true.state/ct
(if applicable) is set to the brightest color temperature for this light.state/level
is set to 100% (1.0)
[obj]/info/ct
Native color temperature, if applicable.[obj]/info/P0/x
and[obj]/info/P0/y
: Native color in 'xy' chromacity, if applicable
- All properties from OnOffLight, LevelOutput
[obj]/info/bri_min
Light output in lumens under the following circumstances:state/on
is set to true.state/ct
(if applicable) is set to the brightest color temperature for this light.state/level
is set to 0% (0.0)
- All properties from DimmableLight
[obj]/state/ct
- Color temperature, in mireds[obj]/info/ct_min
Minimum color temperature in mireds[obj]/info/ct_max
Minimum color temperature in mireds[obj]/info/P0/x
and[obj]/info/P0/y
: Chromacity of first primary[obj]/info/P1/x
and[obj]/info/P1/y
: Chromacity of second primary
- All properties from ColorTempLight
[obj]/state/mode
- String describing the mode:ct
- Color temperature.Lab
- Color-temperature-compensated color.xyY
- Absolute color.[obj]/state/Cx
- 'x' from CIE xyY colorspace.[obj]/state/Cy
- 'y' from CIE xyY colorspace.[obj]/state/CY
- 'Y' from CIE xyY colorspace. 1.0 = completely off, 1.0 = full brightness.[obj]/state/CL
- 'L*' from CIELAB/CIELCh colorspace. 0.0 = completely off, 100 = full brightness.[obj]/state/Ca
- 'a*' from CIELAB colorspace.[obj]/state/Cb
- 'b*' from CIELAB colorspace.[obj]/state/CC
- 'C*' from CIELCh colorspace. Analogous to saturation.[obj]/state/Ch
- 'h°' from CIELCh colorspace. Analogous to hue. Units are degrees.[obj]/state/Cab_p
- magnitude ofab
vector from CIE Lab colorspace. Analogous to saturation.[obj]/info/P[0-9]/{x,y,Y}
: Parameters of each primary element
- All properties from LightColorBase
[obj]/CIE/X
[obj]/CIE/Y
[obj]/CIE/Z
[obj]/CIE/x
[obj]/CIE/y
Note that this class is a subclass of LightColorTemp
: The exact
generated color is dependent on the color temperature that is
currently set.
- All properties from LightColorTemp
[obj]/CIE/L
[obj]/CIE/a
[obj]/CIE/b
[obj]/CIE/ab_t
[obj]/CIE/ab_p
This subclass allows sRGB color values to be set.
Note that this class is a subclass of LightColorTemp
: The exact
generated color is dependent on the color temperature that is
currently set.
- All properties from LightColorTemp
[obj]/sRGB/R
[obj]/sRGB/G
[obj]/sRGB/B
As a special feature, setting the value of sRGB
directly to a hex
triplet will set the values properly for R
, G
, and B
.
- All properties from LightColorSRGB
[obj]/YPbPr/Y
[obj]/YPbPr/Pb
[obj]/YPbPr/Pr
[obj]/YPbPr/Pt
atan2(Pb,Pr) (measured in turns, 0-1)[obj]/YPbPr/Pp
sqrt(PbPb+PrPr) (magnitude of the vector)
- All properties from LightColorSRGB
[obj]/HSL/H
Hue, Measured in turns (0-1)[obj]/HSL/S
Saturation, 0-1[obj]/HSL/L
Luminance, 0-1
- All properties from Base
[obj]/disp-text
[obj]/keypad
[obj]/chirp