Skip to content

Instantly share code, notes, and snippets.

@darconeous
Last active June 15, 2018 17:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darconeous/fee998ee26260caad1443546e81fe06c to your computer and use it in GitHub Desktop.
Save darconeous/fee998ee26260caad1443546e81fe06c to your computer and use it in GitHub Desktop.
CoAP M2M Thoughts

CoAP M2M Thoughts

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.

Pairing

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)

Predicates and Transforms

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 or v: 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 number
  • ROUND_U : Round value on stack up to next whole number if not already a whole number
  • ROUND_D : Round value on stack down to next whole number if not already a whole number
  • SIN : 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 of rtc.d.
  • /rtc/d_ss : Time of sunset, in terms of rtc.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 "if v_r is zero"
  • v_r 0.2 > v_r 0.6 < &&: "(v_r > 0.2) && (v_r < 0.6)", or "if v_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: Limit v 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.

Special "all" object

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.

Resource object naming

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/

Local Actions

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

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)

Common Scenes (Macros?)

  • 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

Special Scenes

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 the unoccupied scene within the past 10 seconds (and that scene change actually did something), then this scene is interpreted as prev. 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).

Schedules

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 be night instead of on.
  • At 8am, change the behavior of the occupied scene on all lights to be on instead of night.
  • 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

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.

Clock Class

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.

Astro-clock extras

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.

Classes of Objects

Base

  • [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.

BaseGroup

These properties are only on the /all object.

  • [obj]/alarm
  • [obj]/occupied

BaseOutput

  • 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 to select, 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.

Lockable

  • [obj]/cfg/lock (Boolean) if set disallows output from being changed

Transitionable

  • [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.

OnOffOutput

  • 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 when on 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 when on transitions from on to off. Default value is device specific.

LevelOutput

  • 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

OpenCloseInput

  • All properties from Base
  • [obj]/state/open

OnOffInput

  • All properties from Base
  • [obj]/state/on

ButtonInput

  • All properties from Base
  • [obj]/state/pressed

GenericEventInput

  • All properties from Base
  • [obj]/state/event

LevelInput

  • 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.

DimmerControl

  • All properties from OnOffInput, LevelInput

Energy

  • 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)

OnOffLight

  • 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

DimmableLight

  • 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)

ColorTempLight

  • 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

ColorLight

  • 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 of ab vector from CIE Lab colorspace. Analogous to saturation.
  • [obj]/info/P[0-9]/{x,y,Y}: Parameters of each primary element

LightColorCIE

  • All properties from LightColorBase
  • [obj]/CIE/X
  • [obj]/CIE/Y
  • [obj]/CIE/Z
  • [obj]/CIE/x
  • [obj]/CIE/y

LightColorCIELab

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

LightColorSRGB

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.

LightColorYPbPr

  • 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)

LightColorHSL

  • 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

InfoPanel

  • All properties from Base
  • [obj]/disp-text
  • [obj]/keypad
  • [obj]/chirp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment