Create iOS app thats 'pre'-connected to my photon

I want to develop an app for controlling my different core/photons… Seeing as this is just for me I want it to already be connected to the devices (not an app to set up the core/photon). How can I accomplish this? All I can find is how to make an app where you have to set up your device first.

Example of what I want the app to do:

Say I have multiple photons/cores with functions for controlling different components in my home. Now I want a simple app with for example two buttons, if i press one button it triggers a function on one of my photons, and if I press the other it triggers a function on the other photon.

Does it have to be a native app, or would a (mobile compatible) webpage be okay too? You can save those to you homescreen as well, and it’d be functionally the same as an app, without the hassle of having to go through the whole Apple barrier. It’d also be platform independent and should be able to run on anything with a web browser.
If that’s okay, you can have a look at a page I made over here, the more mobile friendly version of that over here (WIP!), another version by @suda over here, or even a commercial variant over here.

@Moors7 Yes it has to be native. I’ve already accomplished what I want thru my website, but I want to develop an native app using xcode. If by Apple barrier you mean all the rules to publishing it to the AppStore that wont be a problem as Im the only one going to use it

Considering you’re the only one using it, might I ask what the benefits of a native app would be in this case? If buttons to quickly control your devices are all that you’re interested in, I can’t seem to see the benefits of a native app.
You could also embed an HTML view into a native app, effectively using your website. Alternatively, there are solutions like Phonegap.
If you really only want the control part, why not take the Tinker example, and take out the setup part? Have if simply authenticate with your credentials, and then go straight to the devices overview?

@Moors7 Theres a couple of reasons I want it to be native, one of wich is that Im just getting into swift and xcode, another is that I prefer interacting with an app rather than a website.

Also, the buttons was just an example, my project contains alot more features than that.

I was hoping there was some way to just add the Device IDs (and accesstoken?) programmatically.

Fair enough, though I don’t really see the difference in interacting with a (well-made) webapp as opposed to a native one, depending on the requirements.

The buttons were also an example from my side. If you don’t really need the hardware capabilities, it’s often easier to not dabble with native apps. That said, there are valid enough reasons to do so, even if it’s just for learning :wink:

As far as adding things programmatically, I wouldn’t know why that wouldn’t be possible. The way I think it currently works is that you log in using your credentials, which will in turn retrieve an accesstoken. That accesstoken is then used for further communications.
I suppose you could rip out the logging in part, and just hand it the accesstoken to begin with. The same goes for the setup, you should be able to take that out just fine.

Thanks. The fact that you say it should be possible gives me hope. Havent tried anything yet, but will as soon as I get home from work

I have done what you’re trying to do. I’ve written my own iOS code (in Swift) that interacts with my Photons, but does not do any of the setup chores that the Particle iOS app does. It allows me to call functions and read variables. I have two files, User.swift and Device.swift, that I drag into any project that I want to make for my Particle devices.

@Ric Do you have any sourcecode for this to show, or an example?

Here are the files I wrote in Swift, and some examples of how to use the methods.

The User.swift file,

import Foundation


protocol ParticleUserDelegate {
    func tokenDeletedResult(result: String)
}


class User {
    
    static var userName: String!
    static var password: String!
    static var accessToken: String!
    static var session: NSURLSession!
    static var delegate: ParticleUserDelegate!
    
    
    static func login(userEmail: String, password: String) {
        self.userName = userEmail
        self.password = password
    }
    
    
    static func listAccessTokens() {
        let url = NSURL(string: "https://api.particle.io/v1/access_tokens")
        let request = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 10)
        request.HTTPMethod = "GET"
        let userAndPassword = userName + ":" + password
        let plainData = userAndPassword.dataUsingEncoding(NSUTF8StringEncoding)
        let base64String = plainData!.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
        request.setValue("Basic " + base64String, forHTTPHeaderField: "Authorization")
        
        session = NSURLSession.sharedSession()
        
        let dataTask = session.dataTaskWithRequest(request) { (data, response, taskError) -> Void in
            if (taskError == nil) {
                do {
                    if let tokenArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSArray {
                        print(tokenArray)
                    }
                } catch let jsonError as NSError {
                    print(jsonError.localizedDescription)
                }
            }else{
                print("taskError from executeFunction func is: \(taskError!.localizedDescription)")
            }
        }
        dataTask.resume()
    }
    
    
    
    
    
    static func deleteAccessToken(token: String) {
        let url = NSURL(string: "https://api.particle.io/v1/access_tokens/\(token)")
        let request = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 10)
        request.HTTPMethod = "DELETE"
        let userAndPassword = userName + ":" + password
        let plainData = userAndPassword.dataUsingEncoding(NSUTF8StringEncoding)
        let base64String = plainData!.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
        request.setValue("Basic " + base64String, forHTTPHeaderField: "Authorization")
        
        session = NSURLSession.sharedSession()
        let dataTask = session.dataTaskWithRequest(request) { (data, response, taskError) -> Void in
            if (taskError != nil) {
                print("taskError from deleteAccessToken func is: \(taskError!.localizedDescription)")
            }
        }
        dataTask.resume()
    }

    
    
}

Here’s the Device.swift file,

import Foundation

protocol ParticleDeviceDelegate {
    func deviceIDWasSet()
}


class Device {
    var deviceID: String!
    var accessToken: String!
    var newAccessToken: String!
    var session: NSURLSession!
    var delegate: ParticleDeviceDelegate!
    
    
    
    init(device_ID deviceID: String, accessToken: String) {
        self.deviceID = deviceID
        self.accessToken = accessToken
    }
    
    
    
    init(newTokenForDeviceNamed deviceName: String) {
        let url = NSURL(string: "https://api.particle.io/oauth/token")
        let postRequest = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 10)
        postRequest.HTTPMethod = "POST"
        let userAndPassword = "particle:particle"
        let plainData = userAndPassword.dataUsingEncoding(NSUTF8StringEncoding)
        let base64String = plainData!.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
        postRequest.setValue("Basic " + base64String, forHTTPHeaderField: "Authorization")
        let bodyData = ("grant_type=password&username=\(User.userName)&password=\(User.password)")
        postRequest.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding)
        
        session = NSURLSession.sharedSession()
        
        let dataTask = session.dataTaskWithRequest(postRequest) { (data, response, taskError) -> Void in
            if (taskError == nil) {
                do {
                    if let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary {
                        self.newAccessToken = dict["access_token"] as? String
                        let defaults = NSUserDefaults.standardUserDefaults()
                        if let prevToken = defaults.objectForKey("newKey") {
                            User.deleteAccessToken(prevToken as! String)
                        }
                        defaults.setObject(self.newAccessToken, forKey: "newKey")
                        defaults.synchronize()
                    }
                } catch let jsonError as NSError {
                    print(jsonError.localizedDescription)
                }
            }else{
                print("taskError from deviceNamedWithNewToken:userName:password: is: \(taskError!.localizedDescription)")
            }
            self.downloadDeviceIDForDevice(deviceName, newToken: true)
        }
        dataTask.resume()
    }
    





    init(deviceNamed deviceName: String) {
        let url = NSURL(string: "https://api.particle.io/v1/access_tokens")
        let request = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 10)
        request.HTTPMethod = "GET"
        let userAndPassword = User.userName + ":" + User.password
        let plainData = userAndPassword.dataUsingEncoding(NSUTF8StringEncoding)
        let base64String = plainData!.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
        request.setValue("Basic " + base64String, forHTTPHeaderField: "Authorization")
        
        session = NSURLSession.sharedSession()
        
        let dataTask = session.dataTaskWithRequest(request) { (data, response, taskError) -> Void in
            if (taskError == nil) {
                do {
                    if let tokenArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSArray {
                        if tokenArray.count > 1 {
                            for aToken in tokenArray {
                                if aToken["client"] as? String == "user" {
                                    self.accessToken = aToken["token"] as! String
                                    print("token is: \(self.accessToken)")
                                    break
                                }
                            }
                        }else{
                            self.accessToken = (tokenArray.firstObject as! NSDictionary)["token"] as! String
                        }
                        print("access token from init method is: \(self.accessToken)")
                    }
                } catch let jsonError as NSError {
                    print(jsonError.localizedDescription)
                }
            }
            self.downloadDeviceIDForDevice(deviceName, newToken: false)
        }
        dataTask.resume()
    }
    
    
    
    private func downloadDeviceIDForDevice(deviceName: String, newToken: Bool) {
        let token = self.newAccessToken != nil ? self.newAccessToken : self.accessToken
        let urlString = "https://api.spark.io/v1/devices?access_token=\(token)"
        let url = NSURL(string: urlString)
        let getRequest = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 10)
        let dataTask = session.dataTaskWithRequest(getRequest) { (data, response, taskError) -> Void in
            if taskError == nil {
                do {
                    if let array = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSArray {
                        if deviceName.characters.count == 0 {
                            self.deviceID = (array[0] as! NSDictionary)["id"] as! String
                                self.delegate.deviceIDWasSet()
                        }else{
                            let indx = array.indexOfObjectPassingTest({ (obj, idx, stop) -> Bool in
                                return obj["name"] as! String == deviceName
                            })
                            
                            if indx != NSIntegerMax {
                                self.deviceID = (array[indx] as! NSDictionary)["id"] as! String
                                self.delegate.deviceIDWasSet()
                            }else{
                                print("ERROR: that device name was not found")
                            }
                        }
                    }
                } catch let jsonError as NSError {
                    print(jsonError.localizedDescription)
                }
            }
        }
        dataTask.resume()
    }

    
    
    
    
    
    func executeFunctionNamed(functionName: String, argument: String, completionHandler: (AnyObject) -> Void) {
        self.executeFunctionNamed(functionName, argument: argument, nameLengthPairs: [], completionHandler: completionHandler)
    }
    
    
    
    
    
    
    func executeFunctionNamed(functionName: String, argument: String, nameLengthPairs: [(String, Int)], completionHandler: (AnyObject) -> Void) {
        let token = self.newAccessToken != nil ? self.newAccessToken : self.accessToken
        let url = NSURL(string: "https://api.particle.io/v1/devices/\(self.deviceID)/\(functionName)")
        let postRequest = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 10.0)
        postRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        postRequest.HTTPMethod = "Post"
        let bodyData = "access_token=\(token)&params=\(argument)"
        postRequest.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding)
        let dataTask = session.dataTaskWithRequest(postRequest) { (data, response, taskError) -> Void in
            if (taskError == nil) {
                do {
                    if let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary {
                        if let returnedInt = dict["return_value"]?.integerValue {
                            if nameLengthPairs.count == 0 {
                                completionHandler(returnedInt)
                            }else{
                                let resultDict = self.parseInt(returnedInt, fromArray: nameLengthPairs)
                                completionHandler(resultDict)
                            }
                        }else{
                           let dictError = self.dictionaryErrorParser(dict)
                            completionHandler(dictError)
                        }
                    }
                } catch let jsonError as NSError {
                    print(jsonError.localizedDescription)
                }
            }else{
                print("taskError from executeFunction func is: \(taskError!.localizedDescription)")
            }
        }
        dataTask.resume()
    }
    
    
    private func parseInt(returnedInt: Int, fromArray array: [(String, Int)]) -> [String:Int] {
        var outputs = [String:Int]()
        var input = returnedInt
        let reversedArray = array.reverse()
        for (name, num) in reversedArray {
            let mask = Int(pow(Double(2), Double(num))) - 1
            let andedNum = input & mask
            outputs["\(name)"] = andedNum
            input = input >> num
        }
        return outputs
    }
    
    
    
    
    
    func readVariable(varName: String, completionHandler: (AnyObject) -> Void) {
        let token = self.newAccessToken != nil ? self.newAccessToken : self.accessToken
        let url = NSURL(string: "https://api.spark.io/v1/devices/\(self.deviceID)/\(varName)?access_token=\(token)")
        let request = NSMutableURLRequest(URL: url!, cachePolicy: .ReloadIgnoringCacheData, timeoutInterval: 20)
        let dataTask = session.dataTaskWithRequest(request) { (data, response, taskError) -> Void in
            if (taskError == nil) {
                do {
                    if let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as? NSDictionary {
                        if let readResult = dict["result"]{
                            if readResult is String{
                                print("got a string")
                            }else if readResult is Int {
                                print("got an Int whose value is: \(readResult)")
                            }else if readResult is Double{
                                print("got a Double")
                            }else{
                                print("got something we didn't expect")
                            }
                            
                            completionHandler(readResult)
                        }else{
                            let dictError = self.dictionaryErrorParser(dict) // this is a String
                            completionHandler(dictError)
                        }
                    }
                } catch let jsonError as NSError {
                    print("jsonError from readVariable is: \(jsonError.localizedDescription)")
                }

            }else{
                print("taskError from readVariable func is: \(taskError!.localizedDescription)")
            }
        }
        dataTask.resume()
    }
    
    

    private func dictionaryErrorParser(dict: NSDictionary) -> String {
        
        guard  let errorString = dict["error"] as! String! else {
            return "Error string not returned from Particle"
        }
        guard let infoString = dict["info"] as! String! else {
            return errorString
        }
        return errorString + "  " + infoString
    }
    
}

To control your Particle device with the iOS app, you first need to put in your username and password, and then instantiate an instance of your device,

var catDoorPhoton: Device!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        functionReturnLabel.text = "Connecting"
        User.login("<YOUR USERNAME HERE>", password: "<YOUR PASSWORD HERE>") // user name and password for your Particle account
        catDoorPhoton = Device(deviceNamed: "CatDoor")
        catDoorPhoton.delegate = self
    }

User.login doesn’t actually log in to Particle, it just gets your name and password into the app so it can be used when you instantiate your device.

You call a function like so,

@IBAction func overrideTimes(sender: UIButton) {
    
        catDoorPhoton.executeFunctionNamed("manual", argument: "override") {result in
            if result as! Int == 3 { // 3 is what I'm returning from the "manual" method when "override" is the command
                self.functionReturnLabel.display("Times have been Overridden")
            }else{
                self.functionReturnLabel.display("\(result)")
            }
        }
    }

Here are examples of how to read an int and a String variable,

@IBAction func startTime(sender: UIButton) {
        catDoor.readVariable("startTime") { (result) in
            let returnedInt = (result as? Int)!
            self.formatAndSetTitle(returnedInt, button: sender)
        }
    }


@IBAction func sentTimes(sender: UIButton) {
        catDoor.readVariable("sentTimes") { (result) in
            let returnedString = (result as? String)!
            sender.displayTitle(returnedString)
        }
    }
1 Like

I don’t think you’re getting great advice here. Here’s a snippet of a viewController that “pre-connects” to a photon:

- (void)loginAndConnect {
[[SparkCloud sharedInstance] logout];
myPhoton = nil;

// logging in
[[SparkCloud sharedInstance] loginWithUser:@"user@email.com" password:@"shhhItsSecret" completion:^(NSError *error) {
    if (!error) {
        
        // Find the particular device
        NSLog(@"Logged in to Cloud");
        [[SparkCloud sharedInstance] getDevices:^(NSArray *sparkDevices, NSError *error) {
            if (!error) {
                NSLog(@"Particle Devices: %@",sparkDevices.description); // print all devices claimed to user
            }
            else {
                UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Connection error"
                                                                               message:@"Failed to get device list."
                                                                        preferredStyle:UIAlertControllerStyleAlert];
                
                UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                                      handler:^(UIAlertAction * action) {}];
                
                [alert addAction:defaultAction];
                [self presentViewController:alert animated:YES completion:nil];
                NSLog(@"Error: %@", error);
                [connectedLabel setText:@"Not connected"];
            }
            
            // search for a specific device by name
            for (SparkDevice *device in sparkDevices)
            {
                if ([device.name isEqualToString:@"name_of_photon"])
                    myPhoton = device;
            }
            if (myPhoton == nil) {
                NSLog(@"Couldn't find requested device");
                [connectedLabel setText:@"Cloud Connected (missing device)"];
                
                 UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Connection error"
                                                                               message:@"Failed to find device."
                                                                        preferredStyle:UIAlertControllerStyleAlert];
                
                UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                                      handler:^(UIAlertAction * action) {}];
                
                [alert addAction:defaultAction];
                 
                [self presentViewController:alert animated:YES completion:nil];
                
                
                
            } else {
                
                if ([myPhoton connected]) {
                 //go nuts, you're online!
                } else {
                    [connectedLabel setText:@"Cloud Connected (but device offline)"];
                }
            }
        }];
    }
    else {
        
        UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Connection error"
                                                                       message:@"Failed to log in to cloud."
                                                                preferredStyle:UIAlertControllerStyleAlert];
        
        UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                              handler:^(UIAlertAction * action) {}];
        
        [alert addAction:defaultAction];
        [self presentViewController:alert animated:YES completion:nil];
        NSLog(@"Wrong credentials or no internet connectivity, please try again.  Error: %@",error.localizedDescription);
    }
}];

}