particle-iot/particle-sdk-ios

Name: particle-sdk-ios

Owner: Particle

Description: Official Particle Cloud SDK for iOS

Created: 2017-04-04 20:36:25.0

Updated: 2018-05-14 12:17:24.0

Pushed: 2018-03-10 14:51:09.0

Homepage: null

Size: 3901

Language: Objective-C

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Particle

Particle iOS Cloud SDK

Build Status license version Carthage compatible

Introduction

Particle iOS Cloud SDK enables iOS apps to interact with Particle-powered connected products via the Particle Cloud. It?s an easy-to-use wrapper for Particle REST API. The Cloud SDK will allow you to:

All cloud operations take place asynchronously and use the well-known completion blocks (closures for swift) design pattern for reporting results allowing you to build beautiful responsive apps for your Particle products and projects. iOS Cloud SDK is implemented as an open-source CocoaPods static library and also as Carthage dynamic framework dependency. See Installation section for more details. It works well for both Objective-C and Swift projects.

Beta notice

This SDK is still under development and is currently released as Beta. Although tested, bugs and issues may be present. Some code might require cleanup. In addition, until version 1.0 is released, we cannot guarantee that API calls will not break from one Cloud SDK version to the next. Be sure to consult the Change Log for any breaking changes / additions to the SDK.

Getting Started
Usage examples

Cloud SDK usage involves two basic classes: first is ParticleCloud which is a singleton object that enables all basic cloud operations such as user authentication, device listing, claiming etc. Second class is ParticleDevice which is an instance representing a claimed device in the current user session. Each object enables device-specific operation such as: getting its info, invoking functions and reading variables from it.

Return values

Most SDK functions will return an NSURLSessionDataTask object that can be queried by the app developer for further information about the status of the network operation. This is a result of the SDK relying on AFNetworking which is a networking library for iOS and Mac OS X. It's built on top of the Foundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. The Particle Cloud SDK has been relying on this powerful library since the beginning, but when version 3.0 was released not long ago it contained some breaking changes, the main change from 2.x is that NSURLConnectionOperation was deprecated by Apple and NSURLSessionDataTask was introduced to replace it. You can ignore the return value (previously it was just void) coming out of the SDK functions, alternatively you can now make use of the NSURLSessionDataTask object as described.

Here are few examples for the most common use cases to get your started:

Logging in to Particle cloud

You don't need to worry about access tokens and session expiry, SDK takes care of that for you

Objective-C

rticleCloud sharedInstance] loginWithUser:@"username@email.com" password:@"userpass" completion:^(NSError *error) {
if (!error)
    NSLog(@"Logged in to cloud");
else
    NSLog(@"Wrong credentials or no internet connectivity, please try again");

Swift

icleCloud.sharedInstance().login(withUser: "username@email.com", password: "userpass") { (error:Error?) -> Void in
if let _ = error {
    print("Wrong credentials or no internet connectivity, please try again")
}
else {
    print("Logged in")
}

Injecting a session access token (app utilizes two legged authentication)

If you use your own backend to authenticate users in your app - you can now inject the Particle access token your back end gets from Particle cloud easily using one of the new injectSessionAccessToken functions exposed from ParticleCloud singleton class. In turn the .isLoggedIn property has been deprecated in favor of .isAuthenticated - which checks for the existence of an active access token instead of a username. Additionally the SDK will now automatically renew an expired session if a refresh token exists. As increased security measure the Cloud SDK will no longer save user's password in the Keychain.

Objective-C

[[ParticleCloud sharedInstance] injectSessionAccessToken:@"9bb9f7433940e7c808b191c28cd6738f8d12986c"])
NSLog(@"Session is active!");

NSLog(@"Bad access token provided");

Swift

articleCloud.sharedInstance().injectSessionAccessToken("9bb9f7433940e7c808b191c28cd6738f8d12986c") {
print("Session is active")
se {
print("Bad access token provided")

Get a list of all devices

List the devices that belong to currently logged in user and find a specific device by name:

Objective-C

ock ParticleDevice *myPhoton;
rticleCloud sharedInstance] getDevices:^(NSArray *particleDevices, NSError *error) {
NSLog(@"%@",particleDevices.description); // print all devices claimed to user

for (ParticleDevice *device in particleDevices)
{
    if ([device.name isEqualToString:@"myNewPhotonName"])
        myPhoton = device;
}

Swift

myPhoton : ParticleDevice?
icleCloud.sharedInstance().getDevices { (devices:[ParticleDevice]?, error:Error?) -> Void in
if let _ = error {
    print("Check your internet connectivity")
}
else {
    if let d = devices {
        for device in d {
            if device.name == "myNewPhotonName" {
                myPhoton = device
            }
        }
    }
}

Read a variable from a Particle device (Core/Photon/Electron)

Assuming here that myPhoton is an active instance of ParticleDevice class which represents a device claimed to current user:

Objective-C

hoton getVariable:@"temperature" completion:^(id result, NSError *error) {
if (!error) {
    NSNumber *temperatureReading = (NSNumber *)result;
    NSLog(@"Room temperature is %f degrees",temperatureReading.floatValue);
}
else {
    NSLog(@"Failed reading temperature from Photon device");
}

Swift

oton!.getVariable("temperature", completion: { (result:Any?, error:Error?) -> Void in
if let _ = error {
    print("Failed reading temperature from device")
}
else {
    if let temp = result as? NSNumber {
        print("Room temperature is \(temp.stringValue) degrees")
    }
}

Call a function on a Particle device (Core/Photon/Electron)

Invoke a function on the device and pass a list of parameters to it, resultCode on the completion block will represent the returned result code of the function on the device. This example also demonstrates usage of the new NSURLSessionDataTask object returned from every SDK function call.

Objective-C

LSessionDataTask *task = [myPhoton callFunction:@"digitalWrite" withArguments:@[@"D7",@1] completion:^(NSNumber *resultCode, NSError *error) {
if (!error)
{
    NSLog(@"LED on D7 successfully turned on");
}

4_t bytesToReceive  = task.countOfBytesExpectedToReceive;
.do something with bytesToReceive

Swift

funcArgs = ["D7",1]
task = myPhoton!.callFunction("digitalWrite", withArguments: funcArgs) { (resultCode : NSNumber?, error : Error?) -> Void in
if (error == nil) {
    print("LED on D7 successfully turned on")
}

bytesToReceive : Int64 = task.countOfBytesExpectedToReceive
.do something with bytesToReceive
Retrieve current data usage (Electron only)

Starting SDK version 0.5.0 Assuming here that myElectron is an active instance of ParticleDevice class which represents an Electron device:

Objective-C

lectron getCurrentDataUsage:^(float dataUsed, NSError * _Nullable error) {
if (!error) {
    NSLog(@"device has used %f MBs of data this month",dataUsed);
}

Swift

.selectedDevice!.getCurrentDataUsage { (dataUsed: Float, error :Error?) in
if (error == nil) {
    print("Device has used "+String(dataUsed)+" MBs this month")
}

List device exposed functions and variables

Functions is just a list of names, variables is a dictionary in which keys are variable names and values are variable types:

Objective-C

ctionary *myDeviceVariables = myPhoton.variables;
g(@"MyDevice first Variable is called %@ and is from type %@", myDeviceVariables.allKeys[0], myDeviceVariables.allValues[0]);

ray *myDeviceFunctions = myPhoton.functions;
g(@"MyDevice first Function is called %@", myDeviceFunctions[0]);

Swift

myDeviceVariables : Dictionary? = myPhoton.variables as? Dictionary<String,String>
t("MyDevice first Variable is called \(myDeviceVariables!.keys.first) and is from type \(myDeviceVariables?.values.first)")

myDeviceFunction = myPhoton.functions
t("MyDevice first function is called \(myDeviceFunction!.first)")
Get an instance of a device

Get a device instance by its ID:

Objective-C

ock ParticleDevice *myOtherDevice;
ring *deviceID = @"53fa73265066544b16208184";
rticleCloud sharedInstance] getDevice:deviceID completion:^(ParticleDevice *device, NSError *error) {
if (!error)
    myOtherDevice = device;

Swift

myOtherDevice : ParticleDevice? = nil
ParticleCloud.sharedInstance().getDevice("53fa73265066544b16208184", completion: { (device:ParticleDevice?, error:Error?) -> Void in
    if let d = device {
        myOtherDevice = d
    }
})
Rename a device

you can simply set the .name property or use -rename() method if you need a completion block to be called (for example updating a UI after renaming was done):

Objective-C

oton.name = @"myNewDeviceName";

or

hoton rename:@"myNewDeviecName" completion:^(NSError *error) {
if (!error)
    NSLog(@"Device renamed successfully");

Swift

oton!.name = "myNewDeviceName"

or

oton!.rename("myNewDeviceName", completion: { (error:Error?) -> Void in
if (error == nil) {
    print("Device successfully renamed")
}

Logout

Also clears user session and access token

Objective-C

rticleCloud sharedInstance] logout];

Swift

icleCloud.sharedInstance().logout()
Events sub-system

You can make an API call that will open a stream of Server-Sent Events (SSEs). You will make one API call that opens a connection to the Particle Cloud. That connection will stay open, unlike normal HTTP calls which end quickly. Very little data will come to you across the connection unless your Particle device publishes an event, at which point you will be immediately notified. In each case, the event name filter is eventNamePrefix and is optional. When specifying an event name filter, published events will be limited to those events with names that begin with the specified string. For example, specifying an event name filter of 'temp' will return events with names 'temp' and 'temperature'.

Subscribe to events

Subscribe to the firehose of public events with name that starts with “temp”, plus the private events published by devices one owns:

Objective-C

he event handler:
icleEventHandler handler = ^(ParticleEvent *event, NSError *error) {
    if (!error)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Got Event %@ with data: %@",event.event,event.data);
        });
    }
    else
    {
        NSLog(@"Error occured: %@",error.localizedDescription);
    }

};

his line actually subscribes to the event stream:
ventListenerID = [[ParticleCloud sharedInstance] subscribeToAllEventsWithPrefix:@"temp" handler:handler];

Swift

handler : Any?
ler = ParticleCloud.sharedInstance().subscribeToAllEvents(withPrefix: "temp", handler: { (event :ParticleEvent?, error : Error?) in
if let _ = error {
    print ("could not subscribe to events")
} else {
    DispatchQueue.main.async(execute: {
        print("got event with data \(event?.data)")
    })
}

Note: specifying nil or empty string in the eventNamePrefix parameter will subscribe to ALL events (lots of data!) You can have multiple handlers per event name and/or same handler per multiple events names.

Subscribe to all events, public and private, published by devices the user owns (handler is a Obj-C block or Swift closure):

Objective-C

ventListenerID = [[ParticleCloud sharedInstance] subscribeToMyDevicesEventsWithPrefix:@"temp" handler:handler];

Swift

eventListenerID : Any?
tListenerID = ParticleCloud.sharedInstance().subscribeToMyDevicesEvents(withPrefix: "temp", handler: handler)

Subscribe to events from one specific device (by deviceID, second parameter). If the API user owns the device, then he'll receive all events, public and private, published by that device. If the API user does not own the device he will only receive public events.

Objective-C

ventListenerID = [[ParticleCloud sharedInstance] subscribeToDeviceEventsWithPrefix:@"temp" deviceID:@"53ff6c065075535119511687" handler:handler];

Swift

eventListenerID : Any?
tListenerID = ParticleCloud.sharedInstance().subscribeToDeviceEvents(withPrefix: "temp", deviceID: "53ff6c065075535119511687", handler: handler)

other option is calling same method via the ParticleDevice instance:

Objective-C

ventListenerID = [device subscribeToEventsWithPrefix:@"temp" handler:handler];

Swift

eventListenerID : Any?
tListenerID = device.subscribeToEvents(withPrefix : "temp", handler : handler)

this guarantees that private events will be received since having access device instance in your app signifies that the user has this device claimed.

Unsubscribing from events

Very straightforward. Keep the id object the subscribe method returned and use it as parameter to call the unsubscribe method:

Objective-C

rticleCloud sharedInstance] unsubscribeFromEventWithID:eventListenerID];

Swift

et sid = eventListenerID {
ParticleCloud.sharedInstance().unsubscribeFromEvent(withID: sid)

or via the ParticleDevice instance (if applicable):

Objective-C

ice unsubscribeFromEventWithID:self.eventListenerID];

Swift

ce.unsubscribeFromEvent(withID : eventListenerID)
Publishing an event

You can also publish an event from your app to the Particle Cloud:

Objective-C

rticleCloud sharedInstance] publishEventWithName:@"event_from_app" data:@"event_payload" isPrivate:NO ttl:60 completion:^(NSError *error) {
if (error)
{
    NSLog(@"Error publishing event: %@",error.localizedDescription);
}

Swift

icleCloud.sharedInstance().publishEvent(withName: "event_from_app", data: "event_payload", isPrivate: false, ttl: 60, completion: { (error:Error?) -> Void in
if error != nil
{
    print("Error publishing event" + e.localizedDescription)
}

Delegate Protocol

Starting version 0.5.0 You can opt-in to conform to the ParticleDeviceDelegate protocol in your viewcontroller code if you want to register for receiving system events notifications about the specific device. You do it by setting device.delegate = self where device is an instance of ParticleDevice.

The function that will be called on the delegate is: -(void)particleDevice:(ParticleDevice *)device didReceiveSystemEvent:(ParticleDeviceSystemEvent)event;

and then you can respond to the various system events by:

 particleDevice(device: ParticleDevice, receivedSystemEvent event: ParticleDeviceSystemEvent) {
    print("Received system event "+String(event.rawValue)+" from device "+device.name!)
    // do something meaningful
}

The system events types are:

OAuth client configuration

If you're creating an app you're required to provide the ParticleCloud class with OAuth clientId and secret. Those are used to identify users coming from your specific app to the Particle Cloud. Please follow the procedure decribed in our guide to create those strings, then in your AppDelegate class you can supply those credentials by setting the following properties in ParticleCloud singleton:

perty (nonatomic, strong) NSString *OAuthClientId;
perty (nonatomic, strong) NSString *OAuthClientSecret;

Important Those credentials should be kept as secret. We recommend the use of Cocoapods-keys plugin for cocoapods (which you have to use anyways to install the SDK). It is essentially a key value store for environment and application keys. It's a good security practice to keep production keys out of developer hands. CocoaPods-keys makes it easy to have per-user config settings stored securely in the developer's keychain, and not in the application source. It is a plugin that once installed will run on every pod install or pod update.

After adding the following additional lines your project Podfile:

in 'cocoapods-keys', {
:project => "YourAppName",
:keys => [
    "OAuthClientId",
    "OAuthSecret"
]}

go to your project folder in shell and run pod install - it will now ask you for “OAuthClientId”, “OAuthSecret” - you can copy/paste the generated keys there and from that point on you can feed those keys into ParticleCloud by adding this code to your AppDelegate didFinishLaunchingWithOptions function which gets called when your app starts:

Swift example code

 application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

var keys = YourappnameKeys()
ParticleCloud.sharedInstance().OAuthClientId = keys.oAuthClientId()
ParticleCloud.sharedInstance().OAuthClientSecret = keys.oAuthSecret()

return true

Be sure to replace YourAppName with your project name.

Deploying apps with the Particle Cloud SDK

Starting iOS 10 / XCode 8, Apple requires the developer to enable Keychain sharing under the app Capabilities tab when clicking on your target in the project navigator pane. Otherwise an exception will be thrown when a user logs in, the the SDK tries to write the session token to the secure keychain and will fail without this capability enabled. Consult this screenshot for reference:

Keychain sharing screenshot

Installation
CocoaPods

Particle iOS Cloud SDK is available through CocoaPods. CocoaPods is an easy to use dependency manager for iOS. You must have CocoaPods installed, if you don't then be sure to Install CocoaPods before you start: To install the iOS Cloud SDK, simply add the following line to your Podfile on main project folder:

ce 'https://github.com/CocoaPods/Specs.git'

et 'YourAppName' do
pod 'Particle-SDK'

Replace YourAppName with your app target name - usually shown as the root item name in the XCode project. In your shell - run pod update in the project folder. A new .xcworkspace file will be created for you to open by Cocoapods, open that file workspace file in Xcode and you can start interacting with Particle cloud and devices by adding #import "Particle-SDK.h". (that is not required for swift projects)

Support for Swift projects

All SDK callbacks return real optionals (ParticleDevice?) instead of implicitly unwrapped optionals (ParticleDevice!).

To use iOS Cloud SDK from within Swift based projects read here. For a detailed step-by-step help on integrating the Cloud SDK within a Swift project check out this Particle community posting.

The Apple documentation is an important resource on mixing Objective-C and Swift code, be sure to read through that as well.

Notice that we've included the required bridging header file in the SDK, you just need to copy it to your project add it as the active bridging header file in the project settings as described in the links above. There's also an example app, this app also demonstrates the Particle DeviceSetup library usage, as well as several Cloud SDK calls.

Carthage (Recommended method)

The SDK is now also available as a Carthage dependency since version 0.4.0. This should solve many issues SDK users has been reporting with mixing Swift dependencies in their projects and having to use the use_frameworks! directive in the Podfile - that flag is required for any dynamic library, which includes anything written in Swift. You must have Carthage tool installed, if you don't then be sure to install Carthage before you start. Then to build the iOS Cloud SDK, simply create a Cartfile on your project root folder, containing the following line:

ub "particle/particle-sdk-ios" "master"

and then run the following command: carthage update --platform iOS --use-submodules --no-use-binaries. A new folder will be created in your project root folder - navigate to the ./Carthage/Build/iOS folder and drag all the created .frameworks file into your project in XCode. Go to your XCode target settings->General->Embedded binaries and make sure the ParticleSDK.framework and the AFNetworking.framework are listed there. Build your project - you now have the Particle SDK embedded in your project.

Carthage example

A new example app demonstrating the usage of Carthage installation method is available here. This app is meant to serve as basic example for using the Particle Cloud SDK and Device Setup Library in the Carthage dependencies form. To get this example app running, clone it, open the project in XCode and:

  1. Flash the firmware.c (included in the repo project) firmware to an online photon available under your account, use Build or Dev or CLI.
  2. Set Photon's name to the constant deviceName in the testCloudSDK() function
  3. Set your username/password to the appropriate constants, same place
  4. Go the project root folder in your shell, run the setup shell script (under the /bin folder) which will build the latest Particle SDK 1. Carthage dependencies
  5. Drag the 3 created .framework files under /Carthage/Build/iOS to your project
  6. Go to XCode's target general settings and also add those frameworks to “embedded binaries”
  7. Run and experiment!
Reference
ParticleCloud class

@property (nonatomic, strong, nullable, readonly) NSString* loggedInUsername

Currently logged in user name, nil if no valid session

@property (nonatomic, readonly) BOOL isAuthenticated

Currently authenticated (does a access token exist?)

@property (nonatomic, strong, nullable, readonly) NSString *accessToken

Current session access token string, nil if not logged in

@property (nonatomic, nullable, strong) NSString *oAuthClientId

oAuthClientId unique for your app, use 'particle' for development or generate your OAuth creds for production apps (https://docs.particle.io/reference/api/#create-an-oauth-client)

@property (nonatomic, nullable, strong) NSString *oAuthClientSecret

oAuthClientSecret unique for your app, use 'particle' for development or generate your OAuth creds for production apps (https://docs.particle.io/reference/api/#create-an-oauth-client)

+ (instancetype)sharedInstance

Singleton instance of ParticleCloud class

Login with existing account credentials to Particle cloud

Sign up with new account credentials to Particle cloud

Sign up with new account credentials to Particle cloud

Logout user, remove session data

-(BOOL)injectSessionAccessToken:(NSString * _Nonnull)accessToken

Inject session access token received from a custom backend service in case Two-legged auth is being used. This session expected not to expire, or at least SDK won't know about its expiration date.

Inject session access token received from a custom backend service in case Two-legged auth is being used. Session will expire at expiry date.

Inject session access token received from a custom backend service in case Two-legged auth is being used. Session will expire at expiry date, and SDK will try to renew it using supplied refreshToken.

Request password reset for customer (in product mode) command generates confirmation token and sends email to customer using org SMTP settings

Request password reset for user command generates confirmation token and sends email to customer using org SMTP settings

Get an array of instances of all user's claimed devices offline devices will contain only partial data (no info about functions/variables)

Get a specific device instance by its deviceID. If the device is offline the instance will contain only partial information the cloud has cached, notice that the the request might also take quite some time to complete for offline devices.

Claim the specified device to the currently logged in user (without claim code mechanism)

Get a short-lived claiming token for transmitting to soon-to-be-claimed device in soft AP setup process

Get a short-lived claiming token for transmitting to soon-to-be-claimed device in soft AP setup process for specific product and organization (different API endpoints)

Subscribe to the firehose of public events, plus private events published by devices one owns

Subscribe to all events, public and private, published by devices one owns

Subscribe to events from one specific device. If the API user has the device claimed, then she will receive all events, public and private, published by that device. If the API user does not own the device she will only receive public events.

Unsubscribe from event/events.

Subscribe to events from one specific device. If the API user has the device claimed, then she will receive all events, public and private, published by that device. If the API user does not own the device she will only receive public events.

ParticleDevice class

typedef void (^ParticleCompletionBlock)(NSError * _Nullable error)

Standard completion block for API calls, will be called when the task is completed with a nullable error object that will be nil if the task was successful.

@property (strong, nonatomic, readonly) NSString* id

DeviceID string

@property (strong, nullable, nonatomic) NSString* name

Device name. Device can be renamed in the cloud by setting this property. If renaming fails name will stay the same.

@property (nonatomic, readonly) BOOL connected

Is device connected to the cloud? Best effort - May not accurate reflect true state.

@property (strong, nonatomic, nonnull, readonly) NSArray<NSString *> *functions

List of function names exposed by device

@property (strong, nonatomic, nonnull, readonly) NSDictionary<NSString *, NSString *> *variables

Dictionary of exposed variables on device with their respective types.

@property (strong, nonatomic, readonly) NSString *version

Device firmware version string

-(NSURLSessionDataTask *)getVariable:(NSString *)variableName completion:(nullable void(^)(id _Nullable result, NSError* _Nullable error))completion

Retrieve a variable value from the device

Call a function on the device

Signal device Will make the onboard LED “shout rainbows” for easy physical identification of a device

Request device refresh from cloud update online status/functions/variables/device name, etc

Remove device from current logged in user account

Rename device

Retrieve current data usage report (For Electron only)

Flash files to device

Flash known firmware images to device

Subscribe to events from this specific (claimed) device - both public and private.

Unsubscribe from event/events.

Communication
Maintainers
License

Particle iOS Cloud SDK is available under the Apache License 2.0. See the LICENSE file for more info.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.