Targeting Devices

Universal Resource Names (URNs)

URNs are used in the Relay system to refer to targets of workflow actions. You can send actions to any device on your account, not just the device that may have triggered the workflow. And you can send actions to any number of devices from a single workflow, you are not limited to just one device.

The general structure is as follows (with some examples) and is used throughout a workflow to identify devices, groups, and interactions:

format => urn:relay-resource:<id_type>:<resource_type>:<id>
id_type => 'id' | 'name'
resource_type => 'device' | 'group' | 'interaction'

// device named `Jim`
"urn:relay-resource:name:device:jim"

// device referenced by its ID instead of name
"urn:relay-resource:id:device:990007560012345"

// group named `Security One`
"urn:relay-resource:name:group:security%20one"

// An interaction named `hello world` belonging to device named `Cam`.
// Remember that multiple interaction/channel instances can belong to the same
// workflow instance. As a result, this includes both a device identifier
// and an interaction name.
"urn:relay-resource:name:interaction:hello%20world?device=urn%3Arelay-resource%3Aname%3Adevice%3ACam"

The "jim" example URN is the source URN for a single device, referenced in the code samples as source_uri in the trigger object in the START event. In that case, this kind of URN represents the device that triggered the workflow.

The "990007560012345" example URN is another way to refer to a single device, but to do so by its id instead of its name. It is equivalent to refer to a device by its id or name in a URN, your choice. Devices can be renamed, but the ID is immutable.

The "security%20one" example URN is for when you would like to refer to a group of devices using an already-defined group. For example, you may want a broadcast to be sent out to an entire group instead of just one device. Because of this, you could use the group URN when using Relay's notification functions, such as broadcast, alert, and notify (you can read more about how to use these functions in notifications). This would be easier than having to deal with a list of individual devices in your workflow. Groups are defined using Dash , the web console.

The last example URN above is what gets returned when you start an interaction. You would use an interaction URN when you would like to interact with a device and send actions to it. You can tell that an action requires an interaction, because it will be one of the required parameters in the method to send the action. Some Relay functions that take an interaction URN parameter are LEDs and say and listen functions. An interaction URN should be given to you with the INTERACTION_STARTED event as a result of you calling startInteraction, and you'll then pass it into actions like say() that require an interaction URN.

In advanced cases when you have a single-named interaction with multiple devices (this is not common, but is mentioned here for completeness), you can address all the devices in one invocation by omitting the device specification (not commonly used). For example:

"urn:relay-resource:name:interaction:hello%20world"

If you want to target all devices on your account, use this value: urn:relay-resource:all:device.

Using URNs with events and actions

Most workflow events provide a source_uri parameter if the event is triggered (i.e., sourced) from a device. Some workflow events are not sourced from a device (for instance, a Timer trigger or an HTTP trigger) and, as a result will not have a source_uri parameter.

Most workflow action methods have a required target URN parameter and fall into one of these categories:

  • Actions that can operate on a device or group. This would take a device URN.
  • Actions that operate on a single target interaction. This would take a single interaction URN.
  • Actions that can operate on one or more target interactions. This would take an array of interaction URNs.

The first one can be run against a device that does not have an interaction started (such as a broadcast or alert). The last two are actions that can only be delivered with a started interaction.

Interaction Lifecycle

When a workflow starts, there is no new dynamic channel created on the triggering device. This channel creation doesn't happen until your workflow requests an interaction to be started, then a series of interaction lifecycle events are emitted under the following conditions:

  • INTERACTION_STARTED is sent when the interaction is started and the device's dynamic channel is created. After this event is received, it is safe to start sending actions that require an interaction.
  • INTERACTION_RESUMED is sent when the interaction becomes the active channel on the device, including when the interaction is first started. Further, if configured, interaction channels can be navigated away from. Thus, when navigating back to this dynamic channel, this will also cause an INTERACTION_RESUMED event to be sent. Note that as the user navigates back to this dynamic channel, the channel name will be reported as the interactionName parameter used in the startInteraction invocation.
  • INTERACTION_SUSPENDED is sent when the user navigates away from the interaction channel, if that navigation is allowed per the configuration.
  • INTERACTION_ENDED is sent when the interaction is ended, usually upon a stopInteraction method invocation.

When starting an interaction, the client will always get both an INTERACTION_STARTED and INTERACTION_RESUMED lifecycle event. Note that an INTERACTION_ENDED lifecycle event is not guaranteed to arrive if the workflow is being ended at the same time as a result of a terminate action. Note that the source_uri included in these events will be an interaction URN. Interaction URNs can be used for any action requests that require an interaction URN or a device URN (when the interaction URN addresses a single interaction). This means that, in most cases, the source_uri from this interaction event can be used as an URN in almost any action request that targets a device.

...
const sampleWorkflow = createWorkflow(workflow => {
  workflow.on(Event.START, async () => {
    const { trigger: { args: { source_uri: originator } } } = event
    await workflow.startInteraction(originator, `test`)
  }
                    
  // started event is always sent first
  workflow.on(Event.INTERACTION_STARTED, async ({ source_uri: interaction_uri }) => { })

  // resumed is sent whenever
  workflow.on(Event.INTERACTION_RESUMED, async ({ source_uri: interaction_uri }) => { })

    // the user navigated away from the interaction channel
  workflow.on(Event.INTERACTION_SUSPENDED, async ({ source_uri: interaction_uri }) => { })

    // the interaction has ended and the workflow can be terminated
  workflow.on(Event.INTERACTION_ENDED, async ({ source_uri: interaction_uri }) => { })
})
...
...
@my_workflow.on_interaction_lifecycle
async def lifecycle_handler(workflow, itype, interaction_uri, reason):
    # started event is always sent first
    if itype == relay.workflow.TYPE_STARTED:
    # resumed is sent whenever
    if itype == 'resumed':
    # the user navigated away from the interaction channel
    if itype == 'suspended':
    # the interaction has ended and the workflow can be terminated
    if itype == relay.workflow.TYPE_ENDED:
...
...
public override async void OnInteractionLifecycle(IDictionary<string, object> dictionary)
        {
            var type = (string) dictionary["type"];
            // started event is always sent first
            if (type == InteractionLifecycleType.Started) {}
            // resumed is sent whenever
                    if (type == InteractionLifecycleType.Resumed){}
                    // the user navigated away from the interaction channel
                    if (type == InteractionLifecycleType.Suspended){}
                    // the interaction has ended and the workflow can be terminated
                    if (type == InteractionLifecycleType.Ended){}
...
...
@Override
public void onInteractionLifecycle(Relay relay, InteractionLifecycleEvent lifecycleEvent) {
  super.onInteractionLifecycle(relay, lifecycleEvent);

  logger.debug("User workflow got interaction lifecycle: " + lifecycleEvent);
  String interactionUri = (String)lifecycleEvent.sourceUri;
  // started event is always sent first
  if (lifecycleEvent.isTypeStarted()) {}
  // resumed is sent whenever
  if (lifecycleEvent.isTypeResumed()) {}
  // the user navigated away from the interaction channel
  if (lifecycleEvent.isTypeSuspended()) {}
  // the interaction has ended and the workflow can be terminated
  if (lifecycleEvent.isTypeEnded()) {}
}
...