QSAT Crash Course: iOS, Swift, CoreLocation (via Arduino+Bluefruit+sensor systems)

While it’s not generally feasible to learn how to build an iOS app from scratchĀ in one three-hour session, the aim of this crash course is to help you to take theĀ existing Bluefruit LE app and modify itĀ to collect data from any Arduino-compatible sensor.

How This TutorialĀ Works

For each feature that you add to the app, the subtasksĀ are written out in English (pseudocode), but there are no code snippets in this tutorial. After you try it, let me know if code snippets would have helped you learn–I might addĀ them later. If you find that you’re stuck and need some help, I pasted all the snippets into a file, which is linked at the end of the tutorial.

Prerequisites

Before the crash course, be sure that you have

  1. Acquired an analog sensor, Arduino Uno, and Bluefruit LE breakout module. You must also have an iOS device running iOS 8 or higher, and a Mac computer running Xcode 6 or higher.
  2. Connected theĀ analog sensor to your Arduino Uno’s A5 pin and checked the data in the Arduino serial monitor.
  3. Connected the Bluefruit LE to your Uno and completedĀ the Bluefruit LE tutorial.
  4. Downloaded the Bluefruit LE app from the App Store and checked that data is being transmitted from your sensor system (sensor+Uno+Bluefruit) and displayed in the Bluefruit LE app.

Getting Started

Download theĀ source code for the Bluefruit LE app here. We will modify this code in three ways:

  1. Add location and time data to each sensor value, creating a new “SensorRecord” class.
  2. Write SensorRecords to an array.
  3. Give the user the option to email the array of SensorRecords as a list of CSVs (comma-separated values), which can then be imported into spreadsheet, GIS, and other environments for visualization and analysis.

After you complete these modifications, your app should display a succession of new elementsĀ like those in the screenshots below. When interacting with the app, you are given the option to email your sensors values when you “Disconnect” from your sensor system.

IMG_2362 IMG_2365 IMG_2366 IMG_2367 IMG_2368 IMG_2370

I found theĀ location element to beĀ the most complicated since it involves issues of user privacy and permissions, so we will save that part for last.

Step 1: Getting Acclimated in Swift

If you have never programmed for iOS in Objective-C, takeĀ this Apple tutorialĀ to get acclimated to the iOS development environment with Objective-C. Now that Apple has introduced the Swift language to be simpler and less error prone than Objective-C, we’re starting to see more codebases written in Swift, including the Bluefruit LE app.

Swift, which has been called “Objective-C without C,” is meant to employ concepts and syntax closer to more modern languages like javascript or Ruby. It took me a day or so to get used to Swift syntax, and a few more days to understand the concepts of forced unwrapping / optional chaining (the reason you see so many “!” and “?” marks in Swift code), which is why I recommend the particular links below.

Step 2: Writing Sensor Values to an Array

In the original Bluefruit code, the pin I/O valuesĀ displayed without being stored, so our first coding task is to storeĀ those values inĀ an array. TheĀ Swift Tutorial: How to use an Array in SwiftĀ may be helpful for this task. We will first focus on the existing class PinIOViewController, which manages the numeric display for each I/O pin. In the class’s fileĀ PinIOViewController.swift, seeĀ if you can complete these subtasks to write the data to an array.

  1. In the variable declaration section, declare an array of type Int called sensorDataVal
  2. In the method processInputData, locate the lines where new analog valuesĀ are processed. Hint: look for the comment “//Analog Reporting (per pin)”
  3. Write new analog values to sensorDataVal and println them to the output console.
  4. Try running the app on your device.

You will need to make sensorDataValĀ available to the main viewcontroller, BLEMainViewController, so the valuesĀ can be passed into an email.Ā In the class’s fileĀ BLEMainViewController.swift, seeĀ if you can complete these subtasks to write the data to an array.

  1. In the variable declaration section, declare a private array of type Int calledĀ previousSensorDataValĀ 
  2. In the method navigationController, locate theĀ elseĀ clause that indicates a return from the PinIOViewController. Hint: look for the nameĀ ConnectionMode.PinIO
  3. In this clause, loadĀ pinIoViewController’s data into previousSensorDataVal
  4. Try running the app on your device.

Step 3: Emailing Sensor Values

In BLEMainViewController, weĀ will set up an alert to ask the user if she wants to email the sensor data, as well as relevant email functionality. To learn about alerts in iOS, NSHipster’s explanation of UIAlertControllerĀ may be helpful. To learn about sending in-app e-mails, check outĀ Send Email In-App ā€“ Using MFMailComposeViewController with Swift. Now see if you can complete these subtasks inĀ BLEMainViewController.swift:

  1. In the sameĀ elseĀ clause that indicates a return from PinIOViewController, add a UIAlertController to ask the user whether to send an email of sensor values. Add aĀ UIAlertActionĀ for if the user answers YES, and a separateĀ UIAlertActionĀ for if the user answers NO.
  2. Now modify the YESĀ UIAlertActionĀ to send an email. You will need to add 3 additional methods, based on the Send Email In-App link above:
    • configuredMailComposeViewController (where you will write the values fromĀ previousSensorDataVal out as a String into the email)
    • showSendMailErrorAlert
    • mailComposeController
  3. MakeĀ BLEMainViewControllerĀ a subclass ofĀ MFMailComposeViewControllerDelegate.
  4. Try running the app on your device.

Step 4: Adding Location and Time Data to the Mix

If you made it this far, congratulations! You are almost finished. If not, don’t despair, all the code is written out at the end!

Our final step is to add location and timestamp data to each sensor value. This means that we will now record a group of values for each sensor reading, not just one, so it makes sense to convert our sensorDataVal array from an array ofĀ IntĀ to an array of a class we create:Ā SensorRecord.

iOS offersĀ a Core Location Framework to queryĀ location data based onĀ the device’s built-in GPS, bluetooth, and wi-fi modules. Of this framework, weĀ will includeĀ theĀ CLLocationĀ libraryĀ in our previous Swift files, BLEMainViewController.swiftĀ andĀ PinIOViewController.swift, as well as a new Swift file that we create calledĀ SensorRecord.swift.

Before you get started in this section, take a look at the Apple Sample CodeĀ Locate MeĀ to see how the CLLocation library works. A new, improved privacy featureĀ in iOS 8 is that all apps are programmatically required to request authorization from the user to employ Core Location services. You can read more about it inĀ NSHipster’s post onĀ Core Location in iOS 8. Also take a look at thisĀ StackOverflow discussion onĀ Getting a very simple Swift CoreLocation example to work;Ā it helps in cullingĀ relevantĀ CoreLocation code into a few lines. After reviewing these resources, try these subtasks:

  1. Link CoreLocation.framework to your app target (see instructions here)
  2. Add the lineĀ “import CoreLocation” to the head of yourĀ BLEMainViewController.swiftĀ andĀ PinIOViewController.swiftĀ files.
  3. Create a newĀ SensorRecord.swiftĀ file with two variables. Force unwrapping by adding a “!” at the end of each variable declaration:
    • anĀ Int called sensorMeasurement
    • a CLLocation called locationInfo
  4. Make PinIOViewController a subclass of CLLocationManagerDelegate
  5. Add two new variables to PinIOViewController:
    • A private CLLocationManagerĀ calledĀ locManager. This variable will manage GPS connectionĀ and user authorization.
    • A privateĀ CLLocation called currentLocation. Enable forced unwrapping on this variable.
  6. InĀ PinIOViewController, set up the locManager in the init method.Ā You will need to
    • set locManager’s delegate to self
    • set locManager’s desiredAccuracy toĀ kCLLocationAccuracyBest
    • request permission if device is running iOS8 or higher
    • startUpdatingLocations for locManager
  7. InĀ PinIOViewController, add a new method called locationManager to poll for new GPS data. You can optionally add an error-handling method.
  8. InĀ PinIOViewController, modify sensorDataVal to beĀ an array of SensorRecords instead of an an array of Ints.
  9. In BLEMainViewController,Ā modify previousSensorDataVal to beĀ an array of SensorRecords instead of an an array of Ints.
  10. Try running the app on your device. If it works, you can now deploy the app to email you sensor data!

Step 5: The Working Code

The working code snippets are in this file, but you will still need to figure out where in theĀ pre-existing Bluefruit LE code you should paste these snippets. We will take a look at this file together in class. IfĀ you need to use this “cheat sheet” beyond class, use the code’s comments and blocks to help!

Secret StepĀ If You Really Need It

Download the finalĀ codebaseĀ from Github!