Skip to main content

Device Preferences

A Device Preference is a configuration that enables users to customize the behavior of their device. A Device Preference is typically set to provide users with a way to tweak their device, and may optionally include a default value. Users can change a Device Preference to customize their device by tapping their device in the SmartThings app, tapping the overflow (3-dot) menu icon, and tapping settings.

Device Preferences may be defined one of two ways:

  1. Explicitly, similar to Custom Capabilities, as a standalone entity
  2. Embedded directly into the Device Profile of the device

Explicit Device Preferences#

Explicit Device Preferences allow users to author a single Preference for use by many devices.

Device Preferences defined explicitly will always be namespaced under the author's personal Namespace. Device Preferences that are defined this way are not versioned, and Device Profiles that integrate these Preferences do not get updated if the Preference is updated - the Profile will always have the same version of the Device Preference it had when it was added.

In order to include Explicit Device Preferences for use with a device you will need to tag the profiles that need the Preference as using it. This is done within each profile YAML file in the driver package. Here is a simple profile example using one of these Preferences:

name: preference-profile
components:
- id: main
capabilities:
- id: temperatureMeasurement
version: 1
preferences:
- preferenceId: tempOffset
explicit: true

For common use cases, SmartThings has developed a number of standard Device Preferences that may be integrated into devices. The current set of Standard Device Preferences may be found here.

note

Standard Device Preferences are contained under the smartthings namespace. However, when accessing a standard Preference, no namespace is required to be given. Device Preferences authored by users will use the namespace owned by the user.

Embedded Device Preferences#

To simplify the process of creating unique Preferences relevant only for a specific model of a device, a Device Preference may be defined directly within a Device Profile.

Embedded Device Preferences are unique to an individual Device Profile. They are defined directly in the Device Profile of the device that will be using the Preference. These Preferences and are not versioned, shareable, or update-able; the Preference exists only within the Device Profile. The Preference also includes additional information about the type and limits of the Preference.

Below is an example profile YAML file using an Embedded Preference:

name: preference-profile
components:
- id: main
capabilities:
- id: switch
version: 1
- id: battery
version: 1
categories:
- name: RemoteController
preferences:
- title: "IP Address"
name: ipAddress
description: "IP address of the Pi-Hole"
required: true
preferenceType: string
definition:
minLength: 7
maxLength: 15
stringType: text
default: ""

The preferenceId (and thus what you will use to reference the Preference from the driver) will be the name you define in the Preference definition.

Device Preference Value Types#

tip

Visit the Device Preferences Reference for a list of Preferences and their types.

A Device Preference's value can be one of five types:

  • Boolean
  • Number
  • Integer
  • String
  • Enumeration

All Device Preferences must have at minimum three required fields:

  • title: The Device Preference Title must conform to Custom Capability naming conventions.

  • preferenceType: This field is a constant based on the type of Device Preference. Must be one of: boolean, number, integer, string, or enumeration

  • definition: This block is required, but may be empty if no further customization is required.

Optional fields that apply to all Preference types include:

  • description: Describes the Device Preference.

  • required: A Boolean value that represents whether this Preference must be set. Default: false

  • definition.default: The default value for this Preference if the user does not set the Preference.

Boolean Preference#

{
"title": "string",
"description": "string",
"required": "boolean",
"preferenceType": "boolean",
"definition": {
"default": "true" or "false"
}
}

Boolean Preferences must have a preferenceType of boolean.

Boolean Preferences must have a definition block, but may be empty if default does not need to be set.

Number Preference#

{
"title": "string",
"description": "string",
"required": "boolean",
"preferenceType": "number",
"definition": {
"minimum": "a number",
"maximum": "a number",
"default": "a number"
}
}

Number Preferences must have a preferenceType of number.

Number Preferences may optionally have a minimum and maximum number value, that define the lower and upper bound constraint this number may be set to.

Integer Preference#

{
"title": "string",
"description": "string",
"required": "boolean",
"preferenceType": "integer",
"definition": {
"minimum": "an integer",
"maximum": "an integer",
"default": "an integer"
}
}

Integer Preferences must have a preferenceType of integer.

Replace "integer" with your desired integer value. Integer Preferences may optionally define a minimum and maximum number value, that define the lower and upper bound constraint this integer may be set to.

String Preference#

{
"title": "string",
"description": "string",
"required": "boolean",
"preferenceType": "string",
"definition": {
"stringType": "string",
"minLength": "integer",
"maxLength": "integer",
"default": "string"
}
}

String Preferences must have a preferenceType of string.

stringType is a required field, and must be one of 'text', 'paragraph', or 'password'.

String Preferences may optionally define a minLength and maxLength integer constraint, defining the minimum length of this string.

Enumeration Preference#

{
"title": "string",
"description": "string",
"required": "boolean",
"preferenceType": "enumeration",
"definition": {
"options": {
"key1": "string",
"key2": "string"
},
"default": "string"
}
}

Enumeration Preferences must have a preferenceType of enumeration.

Enumeration Preferences must have an options block, that defines what enumerated values may be selected. The options block must have at least one enumeration defined. Enumerations are a simple key-value pair, where the key is a unique identifier, and the value is a displayable label describing the enumeration. Enumeration keys may be a numeric string, such as "1", but it is recommended that they be descriptive, e.g. "lightUrple".

If a default is set, it must match an option key.

Using Preferences in an Edge Driver#

Once you have defined a Device Profile to use a Preference, you will need to access that value from the Edge Driver to modify whatever behavior the Preference is intended to control. In the sections below, we look at some of the ways Preferences are commonly used.

Query Current Value#

One common use case is when you need the value at a single point in time to adjust or control a command or event related to the device. For these cases, you can check the value of the Preference as needed. The Preference values will be accessible via the device object in the driver. device.preferences.<preference_id> will give you the value of that Preference for that device.

Here is a snippet using a levelOffset Preference value to adjust a value sent to a Zigbee device for the switchLevel.setLevel command:

local function set_level(driver, device, command)
local raw_level = utils.clamp_value(command.args.level + device.preferences.levelOffset, 0, 100)
local level = math.floor(raw_level/100.0 * 254)
device:send_to_component(command.component, zcl_clusters.Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF))
End

Handling Changes to Preferences#

Another common use case is when a Preference is used to control something directly on the device, and thus a Preference value change needs to generate a message sent to the device to update its behavior. In these cases, you will need to have a handler ready for when a Preference value changes. Since the Preference value is stored on the device object in the cloud, any value changes will be communicated using the device_lifecycle event of infoChanged. If you need to send a message to the device, you can register a handler for the infoChanged event to handle changes.

Below is an example handler that checks if a Preference has changed and sends a command to the device:

local function device_info_changed(driver, device, event, args)
-- Did my preference value change
if args.old_st_store.preferences.sensitivityLevel ~= device.preferences.sensitivityLevel then
device:send(<message_to_control_device>)
end
end

Remember that infoChanged will not automatically be called when the device is created. If you need to send some of the initial commands for the Preference values on device creation, you should separate out the Preference handling into a function that you can call from both the infoChanged handler and the deviceAdded handler.

Handling Changes to Preferences For a Sleepy Z-Wave Device#

If Preferences are updated for a sleepy device and commands need to be sent to the device, those commands cannot be sent until the device is awake. Sending the commands in the device infoChanged lifecycle event handler will not work since the device is likely asleep.

The Z-Wave device object allows a developer to set an update_preferences function on the device which will automatically get called when the device wakes up. The function is provided args which are similar to the args on the infoChanged lifecycle event and contain the Preferences that were present the last time the device woke up (args.old_st_store.preferences). Note that there will still be an infoChanged event for sleepy devices, and this automatic Preference update mechanism only works for devices that support the WakeUp command class.

Below is an example of how this functionality should be handled in a driver:

local function update_preferences(device, args)
-- find preference differences between the args.old_st_store.preferences and device.st_store.preferences
device:send(Configuration:Set({parameter_number = 1, size = 2, configuration_value = 600}))
device:send(Configuration:Get({})
end
local function device_init(self, device)
device:set_update_preferences_fn(update_preferences)
end
local function info_changed(self, device, event, args)
-- only update preferences for devices we know are awake
-- if this driver only supports sleepy devices, an infoChanged handler is not needed.
if ~device:is_cc_supported(cc.WAKE_UP) then
update_preferences(device, args)
end
end
local zwave_driver = {
supported_capabilities = {
capabilities.contactSensor,
capabilities.battery
},
lifecycle_handlers = {
init = device_init,
infoChanged = info_changed,
},
}

Testing Device Preference Changes#

Verify that the driver behaves as expected when receiving a new device Preference value by creating an infoChanged test event containing the new Preference value. This event is then sent to the driver. The following snipped below demonstrates creating this test event:

test.socket.device_lifecycle():__queue_receive(mock_device:generate_info_changed({ preferences = { myPreference = 1 } }))