Location (API)

One of the key features in Relay is location services. Here we'll describe how to programmatically fetch the device's location in a workflow.

If you need to fetch only the location information and not perform any other device actions, you don't have to use a workflow - see the Device Info (API) HTTP API.


In the case of Relay's built-in panic workflow, the location of the triggering device is scanned and sent to the Relay server to be announced to the panic responders, so they know where to go to assist the originator. The first level of location information is outdoor location. The second level of location information is the indoor location. This outdoor and indoor location can be used in your own custom workflows. Below we describe how to fetch outdoor location and indoor location.


URN Type for Location

The target URN that is needed to be passed into the location methods here is an interaction URN.

Location Enablement

By default, the devices' location services are disabled. In order to get any location information from the device, the location services must be enabled for the device. This can be done in Dash, which is the web administration console for Relay. In Dash, from the main menu select Account -> Users, select the desired device, and in the Settings menu the Location Tracking enablement can be toggled. You can also enable or disable the device's location services through the enableLocation() and disableLocation() actions via a workflow. See more on how to use these in the Device Info (API) section.

On that same Device screen in Dash, if the location services are enabled and it can read location sensor data, it should show the current location of the device. If you see the current location in Dash, that is a good verification that the device's location services are working properly.

You can also programatically verify if location services are enabled for the device using the getDeviceLocationEnabled() method, which returns a boolean:

const enabled = await workflow.getDeviceLocationEnabled(interaction_uri)
await workflow.say(interaction_uri, `location enabled is ${enabled}`)
// true
enabled = await workflow.get_device_location_enabled(interaction_uri)
await workflow.say(interaction_uri, f'location enabled is {enabled}')
# true
var enabled = await Relay.GetDeviceLocationEnabled(this, interaction_uri, false);
await Relay.Say(this, interaction_uri, $"location enabled is {enabled}");
// true
boolean enabled = relay.getDeviceLocationEnabled(interactionUri, false);
relay.say(interactionUri, "location enabled is " + enabled);
// true
enabled := api.GetDeviceLocationEnabled(interactionUri, false)
api.Say(interactionUri, "location enabled is " + strconv.FormatBool(enabled), sdk.ENGLISH)
// true

Outdoor Location

Latitude & Longitude

Using GPS and other related data from the device, the Relay server can read the device's latitude and longitude. Accuracy will be governed by what is available to the device, such as the number of GPS satellites visible. The latitude and longitude values can be read with the getDeviceCoordinates() method, which returns an array of two numbers:

const coords = await workflow.getDeviceCoordinates(interaction_uri, false)
await workflow.say(interaction_uri, `device coordinates are ${coords[0]} latitude and ${coords[1]} longitude`)
// [ 35.771934, -78.678157 ]
coords = await workflow.get_device_coordinates(interaction_uri, False)
await workflow.say(interaction_uri, f'device coordinates are {coords[0]} latitude and {coords[1]} longitude')
# [ 35.771934, -78.678157 ]
var coords = await Relay.GetDeviceCoordinates(this, interaction_uri, false);
await Relay.say(this, interaction_uri, $"device coordinates are {coords[0]} latitude and {coords[1]} longitude");
// [ 35.771934, -78.678157 ]
double[] coords = relay.getDeviceCoordinates(interactionUri, false);
relay.say(interactionUri, "device coordinates are " + coords[0] + "latitude and " + coords[1] + " longitude");
// [ 35.771934, -78.678157 ]
coordinates := api.GetDeviceCoordinates(interactionUri, false)
api.Say(interactionUri, "The device coordinates are " + fmt.Sprintf("%f", coordinates[0]) + " latitude and " + fmt.Sprintf("%f", coordinates[0]) + " longitude", sdk.ENGLISH)
// [ 35.771934, -78.678157 ]

Note that there is a boolean as the last argument to this method. If set to true, it will force a new scan of the sensor data (i.e., get a new GPS fix) before returning the coordinates. If set to false, it will return the cached last-scanned location. Doing a new scan takes several seconds, which will delay the response. Retrieving the cached last-scanned location is immediate. By default, the device will do scans automatically every 30 seconds. So any last-scanned location shouldn't be older than that, if the device can read sensor data. If you look at the device's current location in Dash, there should be a "Refresh Location" button that indicates the age of the last scan. You'll see this boolean argument on the other methods below, and it has the same effect. If you initiate a panic using the built-in capability, you may notice a pause of a few seconds before the panic is announced. This "delay" is the running of a new scan of the sensor data so that the freshest location can be included in the announcement.

Street Address

Additionally, if a street address is available for the latitude and longitude, that is available via the getDeviceLocation() method, which returns a string representation of the street address:

const outdoorLocation = await workflow.getDeviceLocation(interaction_uri, false)
await workflow.say(interaction_uri, `The device's outdoor location is ${outdoorLocation}`)
// "940 Main Campus Dr, Raleigh, NC 27606"
outdoorLocation = await workflow.get_device_location(interaction_uri, False)
await workflow.say(interaction_uri, f'The devices outdoor location is {outdoorLocation}')
# "940 Main Campus Dr, Raleigh, NC 27606"
var outdoorLocation = await Relay.GetDeviceLocation(this, interaction_uri, false);
await Relay.Say(interaction_uri, $"The devices outdoor location is {outdoorLocation}");
// "940 Main Campus Dr, Raleigh, NC 27606"
String outdoorLocation = relay.getDeviceLocation(interactionUri, false);
relay.say(interactionUri, "The devices outdoor location is " + outdoorLocation);
// "940 Main Campus Dr, Raleigh, NC 27606"
outdoorLocation := api.GetDeviceLocation(interactionUri, false)
api.Say(interactionUri, "The devices outdoor location is " + outdoorLocation, sdk.ENGLISH)
// "940 Main Campus Dr, Raleigh, NC 27606"

Indoor Location

There may be cases where latitude/longitude or street address does not provide enough resolution for your use case, especially at a large venue like a hotel , office building, or data center. Part of that resolution challenge may be that multiple floors are present, or a large quantity of small rooms (ie., hotel). So you may have the need for room-level resolution for location. Relay has a solution for that.

There are two ways that Relay can provide room-level resolution. First is through the use of inexpensive Relay-provided Bluetooth low-energy beacons. These beacons simply broadcast out their MAC address on a periodic basis. You can self-deploy the Bluetooth beacons, and manually capture which room you installed the beacon in and the unique MAC address for that beacon. When the Relay device does a Bluetooth scan and sees the MAC address of the strongest beacon signal, the device knows it is near that beacon and thus in that room.

The other way is by passively observing WiFi access points. Assuming that you have a WiFi infrastructure deployed that involves multiple access points and those access points don't move, there is a process to walk your facility and capture fingerprints of the visible access points and their relative signal strength, and map that to a room. When the Relay device does a WiFi scan and sees a fingerprint similar to one you've captured, the device knows it is in that room. The Relay device does not need to connect these WiFi access points, it just needs to be able to see their BSSID during a scan.

Once you have performed one of these two mappings, the device can do a Bluetooth scan and/or a WiFi scan to determine its room-level indoor location. You can get the indoor location via the getDeviceIndoorLocation method, which returns a string concatenation of the building + floor + room that you manually captured earlier:

const indoorLocation = await workflow.getDeviceIndoorLocation(interaction_uri, false)
await workflow.say(interaction_uri, `The device's indoor location is ${indoorLocation}`)
// main office 3rd floor conference room b
indoorLocation = await workflow.get_device_indoor_location(interaction_uri, False)
await workflow.say(interaction_uri, f'The devices indoor location is {indoorLocation}')
# main office 3rd floor conference room b
var indoorLocation = await Relay.GetDeviceIndoorLocation(interaction_uri, false);
await Relay.Say(interaction_uri, $"The devices indoor location is {indoorLocation}");
// main office 3rd floor conference room b
String indoorLocation = relay.getDeviceIndoorLocation(interactionUri, false);
relay.say(interactionUri, "The devices indoor location is " + indoorLocation);
// main office 3rd floor conference room b
indoorLocation := api.GetDeviceIndoorLocation(interactionUri, false)
api.Say(interactionUri, "The devices outdoor location is " + indoorLocation, sdk.ENGLISH)
// main office 3rd floor conference room b

This indoor location is also visible within Dash, right next to the outdoor location, you can verify it manually there.