DRAFT: Guidelines for Capabilities (under construction...)

Continuing the discussion from About the New Capability Types category:


Disclaimer: This should probably be written by an official SmartThings Architect (or authorized technical writer), but I’m outlining my understanding of what makes for “good” Capability Definitions.

This is based on my analysis of the existing Capabilities Taxonomy, and the SmartApp Developer’s Guide (particularly the section on device selection via preferences { input() }, and, most importantly, the Anatomy of a Device Type (Device Type Developer’s Guide), partially extracted here:

Capabilities are the interactions that a device allows. We have a reference document that lists all available capabilities. When you define a capability, you are stating that your device supports the given capability.

Commands are the actions to be taken on your device. When you define a command, you are stating that your capabilities capability supports a given command.

Attributes are the possible values for a particular command. When you define an attribute, you are stating that your commands capability supports a given attribute.

When you define capabilities, they have defined commands and attributes that come along with them, so you don’t need to include those commands and attributes explicitly.

NB: Emphasis mine. Strike-outs, however, are my recommended corrections (@Jim) – The current documentation inconsistencies makes it difficult to write this topic with a high degree of confidence. Sorry.


Work In Progress: To be continued via edit in place…

Formatting Conventions:

I have various inconsistent ways of formatting terminology, so perhaps I’ll set some formatting guidelines first. Formatting is a bother, though, so everyone and myself is forgiven for slacking off.

  1. When referring to a SmartThings platform element term it should have a leading capital-letter: e.g., Capability, Attribute, Command, Event
  2. When referring to an Attribute, Command, Section, or Method name, they should be in pre-formatted form (back quotes in markdown, no extra double-quotes required): e.g., capability.switch, Attribute: switch -- ["off", "on"], Commands: on(), off()
  3. When referring to a custom data types, angle brackets follow the base data type. Markdown only supports this inside back-quotes: List<Capability>
  4. When referring to custom methods, angle brackets and italics surround the variable substitution part of the method name: e.g., <attribute name>State, current<Uppercase attribute name> (and may gawd have mercy on your soul for having to type any of those).
  5. Argument placeholders, may also be in italics: e.g., events([max: N]), but the ST documentation is inconsistent about this and often doesn’t bother with italics, so ??

Overview: Why Capabilities?

  1. Capabilities exist to allow a SmartApp (or external application) to select one or more Devices that it desires and is prepared to work with. It filters and groups all the Devices available in the user’s Account, via the capability filter parameter of the input preference method:

  2. Capabilities exist to inform a SmartApp (or external web connected service – this goes without saying from now on) of the public characteristics of the Devices it interacts with. Capabilities are defined in instances of SmartDevice Type, but they are inherited by the instances of Device that are created when Things are added to the Account. For simplicity, we say a Device has Capabilities.

  3. The public characteristics of a Capability are its Name, Attributes, and Commands. Attributes are implemented as Groovy variables in the device type, and Commands are implemented with Groovy local public methods. To SmartApps, however, we will call them Attributes and Commands always.

  4. The Capability definition is a way of packaging the Name, Attributes and Commands so these become the public characteristics of the each Device generated from the applied SmartDevice Type when the Device is installed (or when the Device Type is changed via the graph.api). The List of Capabilities that a Device has (via it’s attached Device Type) is provided by the Device Class as the capabilities method:

  5. A SmartDevice Type (and, thus, it’s Device Instances) can be declared with multiple Capabilities. This can cause complications: Capabilities should exist in a strict hierarchy with inheritance, but – [insert reference here for further discussion tangent].

  • If the Capabilities assigned have distinct characteristics, then Devices will have the superset of all the sets of characteristics (e.g., capability.switch + capability.switchLevel gives you all the Attributes of switch + all the Attributes of Switch Level): No overlap, no problem.
  • If the Capabilities assigned have any overlapping characteristics, then Devices will technically have a Union of the sets of characteristics (i.e., duplicates cannot exist). This creates a namespace requirement: In order to ensure consistent behavior and interpretation all Attributes across all Capabilities that share the same Attribute Name must have the same “meaning”. Similarly for all Commands. Technically this rule only needs to be enforced only for Capabilities that are ever combined in any SmartDevice Type (but we can’t control that in advance at this time). It is wise to use unique Attribute and Command names across Capabilities unless you are sure the meanings are identical or will never be combined in a Device Type.
  • I repeat: We must ensure that if a two Capabilities applied to a SmartDevice Type both require Commands on()/off(), the SmartDevice Type can safely implement only the Commands once and that will fulfill the requirements of both Capabilities meaningfully.
  • Cross-Reference: The section “Granularity”, below, probably references the immediate preceding points for context.
  1. Per the Documentation, ALL characteristics of the Capability(ies) (i.e., ALL Attributes and ALL Commands) must be implemented by any and every SmartDevice Type that claims to have these Capability(ies).

  2. Not currently enforced, but recommended: SmartDevice Types should NOT provide any public Attributes or Commands that are not in their selected Capabilities. Instead, a new Capability should be added to the SmartThings Platform (the Platform) for these additional characteristics.

  3. Not currently enforced, but recommended: Devices must not interface with SmartApps via anything other than their Attributes and Commands. The use of “event-data” unattached to a relevant attribute is risky because “event-data” is not defined in the Capabilities, and thus different SmartApps have no way of knowing how to use this data consistently, if at all.

  4. Not currently a feature, but worth considering: Capabilities could list optional characteristics (i.e., optional but pre-defined Attributes and Commands) that can be implemented in associated SmartDevice Types at the discretion of the type creator. However, since SmartApps are written to use Devices of various Types that share the same Capability(ies), any SmartApp that wishes to use an optional characteristic must first check that it exists. Since there currently is no method for this, the simplest implementation of this feature is for all Device Handlers to provide a “safe-default-null” Attribute value and and “safe-default-null” Command action for all optional Attributes and all optional Commands, if they don’t provide actual meaningful ones. A more complex implementation would have the Platform automatically supply these default-nulls, with a meaningful exception flag set for the SmartApps attempting to access the optional characteristics.

Capabilities are a Contract

Design by contract (DbC), also known as contract programming, programming by contract and design-by-contract programming, is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. These specifications are referred to as “contracts”, in accordance with a conceptual metaphor with the conditions and obligations of business contracts.

In other words, by including a Capability in a SmartDevice Type, you are signing a contract between yourself and all SmartApp writers that may use any Devices of that Type. You are agreeing that they can use those Devices as specified in the Capability – i.e., that the Device populates all the supplied Attributes with defined meaningful values, and that the Device reacts to all the Commands with defined meaningful actions.

As such, the Name and overall concept of each Capability is less important than its exact Attributes and Commands and the meaning of those Attributes and Commands. And I’ll redundantly mention that there is an implied sharing of namespace across Capabilities; so effort should be taken to ensure the meaning of Attributes and Commands in different Capabilities (but especially in similar Capabilities and any sets of Capabilities likely to be implemented at the same time in one or more Device Types) is equivalent.

Meaning of a Capability

It is difficult for me to define abstractly what or how big or how tiny the “overall meaning” of a Capability should be. As the list of Capabilities grows, some sort of naming convention and overall description phrasing conventions may arise. The current Capability Taxonomy does not even have a “Description” column, and that may be a good thing.

Instead, a Capability is sufficiently defined by its Attributes and Commands (its characteristics, for short). These are a good way to clearly define the Capability, because the Attributes will have datatypes, valid values, and meaningful descriptions of the values; and the Commands will have optional or required parameters (all typed and described), and a description of the actions performed. Combine the two, and it should be clear what the Capability is for, how represents the physical real-world devices it can be applied to, how to use it, and why to use it. The how and why are equally important to SmartDevice Type authors as well as SmartApp authors.

Scope (Granularity)

– ed: which term is best … or both?
Each Capability should contain an “appropriate” set of Attributes and/or Commands.

At this time, it is difficult to know the optimal number of these characteristics; so let’s just consider the factors involved in making the decision, and looking at examples with pros and cons.

A. The Importance of Capability for Filtering Device Lists:

Background: The preferences{ input() } section of a SmartApp, as currently implemented, can only accept a single capability.<name> argument. This severely limits the ability to filter the selectable Devices and build each Device List. The implications of this will become clearer only with examples later in these Guidelines. The Device Class, however, has the List attribute “capabilities” which can be used for sub-filtering of the initial list later in the SmartApp logic or for providing optionally executing code-blocks which are dependent on additional Capabilities not in the initial input filter.

Sample Use Case: A SmartApp that wishes to provide a high-level of functionality for a smart Lock has two choices in it’s design. It could build the list of supported Devices using:
(a) input( ..., capability.lock ) or
(b) input( ..., capability.lockCodes, ...).

With the first choice (a), the SmartApp cannot assume it has access to Attributes codeReport, codeChanged, and Methods setCode(), etc., since capability.lock only includes Attribute lock and Commands lock(), unlock(). Therefore, any code in the SmartApp that is meant for locks with combination codes, must be in a block with an if/then that checks for <device>.capability.lockCodes.

With the second choice (b), the SmartApp will not give the user the ability to select any “basic connected locks” in their Account (since they are filtered out … basic locks do not have capability.lockCodes). Thus, the SmartApp is safe running all the special combination lock logic; but, unfortunately, cannot even lock or unlock the user’s basic locks.

Option (a) is more powerful, but should only be used when necessary (i.e., when one SmartApp’s lock logic is mostly or entirely intended for any connected lock(s); basic and/or fancy).

Option (b) should be used by SmartApps that are specialized, and are intended solely for fancy locks with combination keypads. Such SmartApps exclude basic connected locks on purpose, since they are irrelevant to the function of the SmartApp.

Additional Use Case: What if we want to check the battery on the lock(s)?
If the sole function of the SmartApp is to check lock batteries, then, really, we want to restrict the selectable list of devices to locks WITH battery Capability; we desire a boolean “AND” condition in the preferences{ input()}, such as preferences{ input( ..., capability.lock && capability.battery)} … Unfortunately, this syntax is not available. So we must either use input( "locks", capability.lock ), and then an if/then block in the logic of the SmartApp that filters using the list result of locks.capabilities ~= capability.battery (ed: syntax check). NB: Or the other way around, input( “batteryDevices”, …) – but that makes less sense, since the user is much more likely to have lots of non-lock battery devices then locks without batteries.

Observation A.: Granular Capabilities with minimal characteristics representing the simplest form of a category of Device Types are necessary even if more complex Capabilities exist with overlapping characteristics. NB: It would be beneficial for the Capability Taxonomy to group these “categories of similar capabilities” so that the permissible overlap in characteristics is very obvious. A future feature could embed this concept further into the Platform, but sufficient for now to just document.

B. Further Filtering Difficulties with low Granularity Capabilities

Background: SmartThings does not have Capability inheritance, but it is not necessary since a SmartDevice Type can include as many individual Capabilities as desired. So… why not make every Attribute and every Command it’s own Capability? This would have the benefit of ensuring consistent definitions and meaning for the underlying Attributes and Commands because there would be zero duplication of characteristics among Capabilities. In the case of locks, from above, Attribute lock and Commands lock()/unlock() would be removed from capability.lockCodes because these are available by including capability.lock in all the lock Device Types, both basic and complex.

This pattern would make accurate (“fine screening”) preference input device selection list filtering impossible since there is no “AND” syntax available on preferences{ input() }.

Example: I want to support a clothes dryer, so I consider including capability.switch, capability.duration, capability.temperatureSet and a few others in my SmartDevice Type Clothes Dryer. For better or worse, every SmartApp that provides the user with a list of “switches” will include their Clothes Dryers. This may be perfect for SmartApps designed to turn off as many things as desired when there is an electricity price spike, but it is confusing present a Clothes Dryer to the user of a SmartApp which turns on lights when a presence sensor indicates a family member has arrived back home.

Observation B.: Due to the limitations of input filtering, overly granular Capabilities (i.e., Capabilities that are expected to be seldom or never used alone in SmartDevice Types) are mostly undesirable because they limit the ability to filter the preferences input list for user-friendliness (and safety!) in anything but the most generic SmartApps. NB: This concern can be eliminated with full platform implementation of Capability Categories or Compound Capabilities (ed: explore detailed concepts of these possible features elsewhere), and/or the addition of “AND” syntax to the device input capability list filter.

A + B = Conclusion Regarding Granularity:
Damned either way, is the short answer. :imp:Primarily due to the filtering limitation!!! Can ST fix that?

The long answer is that by carefully understanding everything discussed in this “Scope” section above, we will understand what is an appropriately balanced Capability Scope (Granularity).

To review, in steps, I think we should consider: – ed: This requires a bunch of thought to write clearly.

  1. If the Capability is part of a distinctly meaningful category or grouping of Capabilities then the overlap in those Capabilities doesn’t matter, but the granularity might. For example, Capability “Clothes Washer” should include all the basic and very common functions of a basic connectable Clothes Washer, including on/off (or start/stop), so that it is never necessary to confuse the Device input filter with the need to include capability.switch for a Clothes Washer Device Type. If optional Commands are supported then the additional command steam(warm|hot) could be included in this Capability – but without optional Commands, it is necessary and reasonable to instead create Capability Steam, put this in the same Capability Category (solely in the Capability Taxonomy documentation), and then include capability.steam only in Device Types that meaningfully implement the steam() command.

  2. If the Capability “safely” crosses multiple categories of Capabilities, then it should likely be highly granular. Capability Switch is used for lights, appliances, etc., etc., and that’s why capability.switch does not and should not include Attribute and Command switchLevel. If Capability Switch included Command switchLevel, it would be dangerous to assign this capability to electronic appliance Device Types which can be damaged if the power level is wonky, or then some Device Types would have to nullify that specific command (which again, suggests that switchLevel could be an optional command of capability.switch, and then also it’s own capability.switchLevel – but pretty easy to see that optional commands and attributes really just confuses things without too much, especially since the Platform does not assist in handling them.

    (ed: I’m obviously waffling on the concept of optional Attributes and Commands. I have a gut feeling they could solve some real-world problems, but I want to see those situations first. switchLevel() is a poor example of an optional command add-on to capability.switch; but toggle() is a good example of a workable extension to capability.switch – except; ahem, why make it optional if it is so easy to implement? It could be implemented in an extension of capability.switch, and inherits the abstracted code from existing Device Types … blah blah blah.)

  3. In progress: What doesn’t fit into #1 and #2?

To be continued…?


Naming (very rough…)

  1. Capabilities
  • Perhaps of the form: categoryValueType … e.g., hvacTemperatureMeasurement
  1. Attributes
  • Perhaps a “primary” attribute must match the name of the Capability (this will help avoid namespace issues): Attribute Numeric hvacTemperatureMeasurement; non-primary Attributes Numeric: hvacTemperatureMax
  1. Commands
  • If they affect a single particular attribute, name it the same as the attribute OR the attribute values:
    i.e., Attribute" switch - "on","off"… so (a) Commands: switch("on" | "off") vs (b) on(),off().
    I think prefer (a) and that format is used for many existing Capabilities, but so is (b).
    To bad this inconsistency exists. :crying_cat_face:
    IE: Every public Attribute should have a single setter command… But does OO typically follow this guideline? Probably not.
  1. Optionals
  • IF permitted, then optional Attributes and Commands should have a naming convention: optional_Toggle()?
  1. Ad Hocs in SmartDevice Types
  • IF permitted (please don’t), then ad hoc Attributes and Commands (in SmartDevice Types) should have a naming convention: dt_IndicatorColor?

Are other guideline sections needed here?
… to be continued.

3 Likes

My ignorange forces a degree of simplification so take this FWIW.

I support the idea that capabilities should be granular and that a device that supports a capability must support all attributes and commands.

To make this possible, I also agree that there should be an ‘AND’ capability in the preferences functionality. You should be able to have a SmartApp present a list of devices that support THIS capability AND THAT capability.

As has been pointed out, however, we already have this to a degree. You can create a DeviceType that supports a combination of capabilities and then have a SmartApp present a list of devices using a that DeviceType. However, what this does not support, is the use of some subset of capabilities of a device. For example, if a DeviceType is written for a particular device that supports temperature sensing, humidity sensing, heating setpoint, cooling setpoint, and mode selection; there is no mechanism available to a SmartApp to declare it needs temperature and heating setpoint capabilities and discover devices that may do this or can do this as a subset of all their capabilities.

So, it seems like to me that we should be adding AND functionality to the preferences functionality and establishing rules and guidelines along the lines of what @tgauchat has proposed above.

Thank-you, Aaron.

I hope consensus builds around the need for “AND” in the Preferences input() capability filter or an equally powerful yet simple solution.

1 Like

Maybe it makes sense to go after just the AND for now. It seems foundational to (a lot of) the rest. It can add value without anything else being done. It’s easily digestible. It would seem - and I’m going to be careful here because I have no way of knowing - to be a relatively easy change to the platform.

Has this been proposed outside of the broader context of what’s in this particular thread?

1 Like

I’ve mentioned it in a few places as well as directly emailing @Tyler and @Ben, but perhaps a new Topic might gain visibility and/or a Developer Call Agenda Item (@April).

OK. when I get a chance (tied up at the moment), I can start a new thread suggesting this with some further explanation/justification.

1 Like

Let me know if I can help. It will be good to get this written from a fresh perspective, but there’s some key elements I have in mind.

Thanks!