To use this Swift library, you start by initializing an instance of the Disruptive struct with the credentials for a Service Account (see the Authentication section below). This will be your entry-point for all the requests to the Disruptive Technologies service. This Disruptive instance will automatically handle things such as authentication, pagination, re-sending of events after rate-limiting, and other recoverable errors.
The endpoints implemented on the Disruptive struct are asynchronous, and will return its results in a closure you provide with an argument of type Result (read more about the Result type on Apple’s developer site). This Result will contain the value you requested on .success (Void if no values makes sense), or a DisruptiveError on .failure.
Note: The callback with the Result will always be called on the main queue, even if networking/processing is done in a background queue.
The following sections will provide a brief guide to the most common use-cases of the API. Check out the full Swift API documentation for more.
Authentication
Authentication is done by initializing the Disruptive instance with a type that conforms to the Authenticator protocol. The recommended type for this is OAuth2Authenticator which will authenticate a Service Account using the OAuth2 flow. Once authenticated, the Disruptive instance will make sure it always has a non-expired access token, and will add that to the Authorization header of the request before sending it. If the access token is expired when a request is made, a new access token will be fetched automatically before sending the request.
A service account can be created in DT Studio by clicking the Service Account tab under API Integrations in the side menu. Create a new key for the Service Account and make sure to note down the key id, secret, and email for the Service Account. Note that by default, the Service Account will not have access to any resources. See the section below about Permissions to learn how to grant the Service Account access to your resources.
Here’s an example of how to authenticate a service account with the OAuth2 flow:
import Disruptive
let credentials = ServiceAccountCredentials(email: "<EMAIL>", keyID: "<KEY_ID>", secret: "<SECRET>")
let authenticator = OAuth2Authenticator(credentials: credentials)
let disruptive = Disruptive(authenticator: authenticator)
// All methods called on the disruptive instance will be authenticated
Access levels for the Disruptive API can be described in terms of members, roles, and permissions. For an account (Service Account or user) to have access to a resource, it has to be a member in the project or organization that is a parent of that resource. A member will always have a role for the project/organization it’s a member of (such as project user, project admin, etc). Each of those roles as a list of permissions that describes which CRUD (create, read, update, delete) operations it can perform on various resources. Examples of permissions would be "project.read", "membership.create", "serviceaccount.key.delete", etc. To list the available roles and permissions, use the getRoles and getPermissions functions.
In order for a Service Account to be able to access a given resource, it must have sufficient permissions for that resource. By default, a Service Account does not have access to any resources. The easiest way to get started with a Service Account is by granting access for the relevant projects/organizations in DT Studio. You can give it a role in the current project by selecting Role in current Project when viewing the Service Account under API Integrations -> Service Accounts. You can also give it access to other projects/organizations by going to the list of members (in Project Settings for project members), and then adding the Service Account as a member using the Service Account’s email address, and selecting an appropriate role.
Once you have the credentials for a Service Account and have created a Disruptive instance, you can use the API to create new Service Accounts and add them as a members to your projects. See the createServiceAccount and completion:)">inviteMember functions for more details.
See the Service Accounts article on the developer website for more details about Service Accounts in general.
Making Requests
Once an instance of the Disruptive struct has been created, it will be the main entry point to make requests against the Disruptive API. See the API reference for an overview of all the functionality available on the Disruptive struct.
Lists & Pagination
There are two main approaches to fetching a list of resources (such as a list of Devices or Projects): You can either fetch them all at once, or one page at a time. Fetching all the items of a resource at once is more convenient, but if there are a lot of items this can take a long time as multiple network requests might be made in the background to get all the pages automatically.
Fetching one page of items at a time is slightly more cumbersome to implement, but provides full control of how many items are fetched at a time and when to fetch the next page of items. Fetching one page at a time is available for Organization, Project, Device, DataConnector, Member, ServiceAccount, and ServiceAccount.Key. It is not available for Role and Permission as those have a well-known, small number of items to list. It is also not available for fetching events as events can be paged by specifying start and end timestamps to fetch events between.
Here are examples of both of these approaches for fetching a list of Devices.
Fetching all Devices in a project at once:
disruptive.getAllDevices(projectID: "<PROJECT_ID>") { result in
switch result {
case .success(let devices):
print(devices)
case .failure(let error):
print("Failed to get devices: \(error)")
}
}
var fetchedDevices = [Device]()
var nextPageToken: String?
func fetchNextPage(pageToken: String?) {
disruptive.getDevicesPage(projectID: "<PROJECT_ID>", pageSize: 25, pageToken: pageToken) { result in
switch result {
case .success(let page):
// Keep track of the page token to use for the next page.
// Note that this will be `nil` when the last page is received.
nextPageToken = page.nextPageToken
// Update the list of all the devices we have found so far
fetchedDevices.append(contentsOf: page.devices)
print("Fetched \(page.devices.count) more devices. \(fetchedDevices.count) devices fetched in total")
case .failure(let error):
print("Failed to get devices: \(error)")
}
}
}
// Fetch the first page
fetchNextPage(pageToken: nil)
// Fetch subsequent pages when it makes sense (for example when pre-fetching data
// for a UITableView). Note that `nextPageToken` will be set to `nil` when the last
// page is received.
if let pageToken = nextPageToken {
fetchNextPage(pageToken: pageToken)
}
Fetching historical events for a device is similar to fetching other lists of data (like getOrganizations or getProjects). You need to specify the identifier of the project and the device, and optionally the start/end time and which events to fetch (certain event types are only available for certain device types, eg. temperature events are only available for temperature sensors). If the Result returned in the callback was .success, you will receive a value of type Events that contains an optional array of events for each event type. Only the event types that were actually returned will be non-nil, not necessarily the one specified in the eventTypes parameter.
Example of fetching just temperature events for a temperature sensor (defaults to last 24 hours):
disruptive.getEvents(projectID: "<PROJECT_ID>", deviceID: "<DEVICE_ID>", eventTypes: [.temperature]) { result in
switch result {
case .success(let events):
if let temperatureEvents = events.temperature {
print(temperatureEvents)
}
case .failure(let error):
print("Failed to get temperature events: \(error)")
}
}
When subscribing to device events you have two options: Either subscribe to a single device, or to a list of devices. If you want to subscribe to a list of devices, you can filter on which devices to subscribe to based on both device types and labels. Either way, you will get a value of type DeviceEventStream back that will let you set up a callbacks for each the various event types. Only the event types specified in the eventTypes parameter will actually receive callbacks.
Example of subscribing to temperature events for a single temperature sensor:
let stream = disruptive.subscribeToDevice(
projectID : "<PROJECT_ID>",
deviceID : "<DEVICE_ID>",
eventTypes : [.temperature] // optional
)
stream?.onTemperature = { deviceID, temperatureEvent in
print("Got temperature \(temperatureEvent) for device with id \(deviceID)")
}
stream?.onError = { error in
print("Got stream error: \(error)")
}
The requests to fetch devices has various parameters to search and/or filter devices. All of these parameters are optional (except for projectID), and can be mixed and matched as desired.
When specifying the order to retrieve the devices in, a field as well as an ascending/descending flag is included in a tuple. The value of this field is based on the JSON structure of the devices. Examples of fields to use include id (identifier), type (device type), labels.name (displayName). All events will have the format reported.<event_type>.<field>, eg. reported.networkStatus.signalStrength. See the REST API documentation for the GET Devices endpoint to get hints for which fields are available.
Here is an example of how to use all the parameters:
A single device can be looked up just by the identifier of the device. This is useful if you got a device identifier by scanning a QR code for example. Here’s an example:
disruptive.getDevice(deviceID: "<DEVICE_ID>") { result in
...
}
Emulated devices can be created and used to publish events using the API. This enables testing out other parts of the API (such as listing devices) and developing a solution around the API without having access to physical devices.
Here’s an example for how to create a new emulated temperature sensor:
disruptive.createEmulatedDevice(
projectID : "<PROJECT_ID>",
deviceType : .temperature,
displayName : "Emulated Temperature Sensor")
{ result in
...
}
Fetching projects lets you optionally filter on both the organization (by identifier) as well as a keyword based query. You can also leave both of those parameters out to fetch all projects available to the authenticated account. The following example will search for projects with a specified organization id (fetched from the getOrganizations endpoint for example) that has Building 1 in its name:
disruptive.getAllProjects(organizationID: "<ORG_ID>", query: "Building 1") { result in
...
}
Disruptive - Swift API
Swift library for accessing data from Disruptive Technologies.
API Documentation
Installation
This library is currently only available through the Swift Package Manager (SPM).
To add this Swift Package as a dependency in Xcode:
File -> Swift Packages -> Add Package Dependency...
https://github.com/vegather/Disruptive
import Disruptive
If you want to add it manually to your Swift project, you can add the following dependency to your
Package.swift
:Guides
Overview
To use this Swift library, you start by initializing an instance of the
Disruptive
struct with the credentials for a Service Account (see the Authentication section below). This will be your entry-point for all the requests to the Disruptive Technologies service. ThisDisruptive
instance will automatically handle things such as authentication, pagination, re-sending of events after rate-limiting, and other recoverable errors.The endpoints implemented on the
Disruptive
struct are asynchronous, and will return its results in a closure you provide with an argument of typeResult
(read more about theResult
type on Apple’s developer site). ThisResult
will contain the value you requested on.success
(Void
if no values makes sense), or aDisruptiveError
on.failure
.Note: The callback with the
Result
will always be called on themain
queue, even if networking/processing is done in a background queue.The following sections will provide a brief guide to the most common use-cases of the API. Check out the full Swift API documentation for more.
Authentication
Authentication is done by initializing the
Disruptive
instance with a type that conforms to theAuthenticator
protocol. The recommended type for this isOAuth2Authenticator
which will authenticate a Service Account using the OAuth2 flow. Once authenticated, theDisruptive
instance will make sure it always has a non-expired access token, and will add that to theAuthorization
header of the request before sending it. If the access token is expired when a request is made, a new access token will be fetched automatically before sending the request.A service account can be created in DT Studio by clicking the
Service Account
tab underAPI Integrations
in the side menu. Create a new key for the Service Account and make sure to note down the key id, secret, and email for the Service Account. Note that by default, the Service Account will not have access to any resources. See the section below about Permissions to learn how to grant the Service Account access to your resources.Here’s an example of how to authenticate a service account with the OAuth2 flow:
OAuth2Authenticator
documentationPermissions
Access levels for the Disruptive API can be described in terms of members, roles, and permissions. For an account (Service Account or user) to have access to a resource, it has to be a member in the project or organization that is a parent of that resource. A member will always have a role for the project/organization it’s a member of (such as project user, project admin, etc). Each of those roles as a list of permissions that describes which CRUD (create, read, update, delete) operations it can perform on various resources. Examples of permissions would be
"project.read"
,"membership.create"
,"serviceaccount.key.delete"
, etc. To list the available roles and permissions, use thegetRoles
andgetPermissions
functions.In order for a Service Account to be able to access a given resource, it must have sufficient permissions for that resource. By default, a Service Account does not have access to any resources. The easiest way to get started with a Service Account is by granting access for the relevant projects/organizations in DT Studio. You can give it a role in the current project by selecting
Role in current Project
when viewing the Service Account underAPI Integrations -> Service Accounts
. You can also give it access to other projects/organizations by going to the list of members (inProject Settings
for project members), and then adding the Service Account as a member using the Service Account’s email address, and selecting an appropriate role.Once you have the credentials for a Service Account and have created a
Disruptive
instance, you can use the API to create new Service Accounts and add them as a members to your projects. See thecreateServiceAccount
and completion:)">inviteMember
functions for more details.See the Service Accounts article on the developer website for more details about Service Accounts in general.
Making Requests
Once an instance of the
Disruptive
struct has been created, it will be the main entry point to make requests against the Disruptive API. See the API reference for an overview of all the functionality available on theDisruptive
struct.Lists & Pagination
There are two main approaches to fetching a list of resources (such as a list of
Device
s orProject
s): You can either fetch them all at once, or one page at a time. Fetching all the items of a resource at once is more convenient, but if there are a lot of items this can take a long time as multiple network requests might be made in the background to get all the pages automatically.Fetching one page of items at a time is slightly more cumbersome to implement, but provides full control of how many items are fetched at a time and when to fetch the next page of items. Fetching one page at a time is available for
Organization
,Project
,Device
,DataConnector
,Member
,ServiceAccount
, andServiceAccount.Key
. It is not available forRole
andPermission
as those have a well-known, small number of items to list. It is also not available for fetching events as events can be paged by specifying start and end timestamps to fetch events between.Here are examples of both of these approaches for fetching a list of
Device
s.Fetching all
Device
s in a project at once:getAllDevices
documentationFetching
Device
s one page at a time:getDevicesPage
documentationFetching Historical Events
Fetching historical events for a device is similar to fetching other lists of data (like
getOrganizations
orgetProjects
). You need to specify the identifier of the project and the device, and optionally the start/end time and which events to fetch (certain event types are only available for certain device types, eg.temperature
events are only available fortemperature
sensors). If theResult
returned in the callback was.success
, you will receive a value of typeEvents
that contains an optional array of events for each event type. Only the event types that were actually returned will be non-nil, not necessarily the one specified in theeventTypes
parameter.Example of fetching just temperature events for a temperature sensor (defaults to last 24 hours):
getEvents
documentationSubscribing to Device Events
When subscribing to device events you have two options: Either subscribe to a single device, or to a list of devices. If you want to subscribe to a list of devices, you can filter on which devices to subscribe to based on both device types and labels. Either way, you will get a value of type
DeviceEventStream
back that will let you set up a callbacks for each the various event types. Only the event types specified in theeventTypes
parameter will actually receive callbacks.Example of subscribing to temperature events for a single temperature sensor:
subscribeToDevice
documentationOther Common Requests
Search / Filter Devices
The requests to fetch devices has various parameters to search and/or filter devices. All of these parameters are optional (except for
projectID
), and can be mixed and matched as desired.When specifying the order to retrieve the devices in, a field as well as an ascending/descending flag is included in a tuple. The value of this field is based on the JSON structure of the devices. Examples of
field
s to use includeid
(identifier),type
(device type),labels.name
(displayName). All events will have the formatreported.<event_type>.<field>
, eg.reported.networkStatus.signalStrength
. See the REST API documentation for theGET Devices
endpoint to get hints for which fields are available.Here is an example of how to use all the parameters:
Single Device Lookup
A single device can be looked up just by the identifier of the device. This is useful if you got a device identifier by scanning a QR code for example. Here’s an example:
getDevice
documentationEmulated Devices
Emulated devices can be created and used to publish events using the API. This enables testing out other parts of the API (such as listing devices) and developing a solution around the API without having access to physical devices.
Here’s an example for how to create a new emulated temperature sensor:
createEmulatedDevice
documentationHere’s an example for how to publish a
TemperatureEvent
for an emulated sensor:publishEmulatedEvent
documentationFetch Projects & Organizations
Fetching projects lets you optionally filter on both the organization (by identifier) as well as a keyword based query. You can also leave both of those parameters out to fetch all projects available to the authenticated account. The following example will search for projects with a specified organization id (fetched from the
getOrganizations
endpoint for example) that hasBuilding 1
in its name:getAllProjects
documentationHere’s an example of fetching all the organizations available to the authenticated account:
getAllOrganizations
documentationMisc Tips
Disruptive.loggingEnabled = true
License
The Disruptive Swift library is released under the MIT license. See LICENSE for details.