Unlocking the Power of AWS IoT SDK: use MQTT with GPS on iOS


The Internet of Things (IoT) is transforming the way we interact with the world around us. AWS offers a robust suite of services for developing and managing IoT solutions, including the AWS IoT SDK for iOS and Android. In this detailed article, we will explore how to effectively utilize the AWS IoT SDK to create a sample iOS application that leverages MQTT protocol for sending GPS locations.

Prerequisites:

To get started, you will need the following:

  • An AWS account with access to AWS IoT resources.
  • Basic knowledge of iOS development using the Swift programming language.
  • Familiarity with Android development using either Java or Kotlin.

Step 1: AWS IoT Configuration:


Learn how to configure AWS IoT to establish a seamless connection between your devices and the cloud. Follow these steps:

  1. Access the AWS Management Console and open the AWS IoT service( https://eu-west-1.console.aws.amazon.com/iot/home) .
  2. Create a descriptive “Thing” (device) name.
  3. Download the essential certificate and private key for your device.
  4. Define a comprehensive “Policy” that encompasses the necessary permissions for MQTT publish and subscribe operations.
  5. Attach the downloaded certificate and private key to your device.
  6. Assign the created policy to your device.

Step 2: Installing the AWS IoT SDK on iOS and Android:

We will guide you through the installation process for integrating the AWS IoT SDK into your iOS and Android projects.

  1. For iOS, use the AWS repo: https://github.com/aws-amplify/aws-sdk-ios. Can import using Cocoapod or SPM or other import modes.
  2. Same for Android, here the repo: https://github.com/aws-amplify/aws-sdk-android.

Running the code from AWS:

This technology, as fast as it is, will give you a lot of satisfaction. 😎


Step 3: Initializing and Configuring the AWS IoT SDK:

Effectively initialize and configure the AWS IoT SDK for seamless integration within your projects.

  1. In iOS, import the AWSIoT module into your Swift source file to harness the extensive capabilities of the AWS IoT SDK.
  2. Configure the AWSIoTDataManager with the appropriate device information and certificates obtained from AWS.
  3. For Android, import the necessary classes and create an instance of the AWSIotClient class using the device credentials.

Step 4: Connecting and Subscribing to MQTT Topics:

Learn how to establish a connection with AWS IoT and effectively subscribe to MQTT topics.

  1. In iOS, employ the connect method of AWSIoTDataManager to initiate a connection to the AWS IoT service.
  2. Utilize the subscribe(toTopic:qos:messageCallback:) method to subscribe your device to one or multiple MQTT topics.
  3. In Android, leverage the connect method of the AWSIotClient class to establish a seamless connection with AWS IoT.
  4. Use the subscribe method to subscribe your Android device to MQTT topics of interest.

Step 5: Sending GPS Locations via MQTT:

Effectively utilize the AWS IoT SDK to publish GPS locations via MQTT.

  1. In iOS, leverage the publishString method of AWSIoTDataManager to publish GPS locations to specified MQTT topics.
  2. For Android, effectively use the publish method of the AWSIotMqttManager class to publish GPS locations to specific MQTT topics.

Connection example code using Swift:

import AWSIoT

// AWS IoT Configuration
let iotDataManager = AWSIoTDataManager(forKey: "default")
let certificateId = "your-certificate-id"

// Connecting and Subscribing to MQTT Topics
iotDataManager.connect(withClientId: certificateId, cleanSession: true, certificateId:

certificateId, statusCallback: nil)
iotDataManager.subscribe(toTopic: "your-topic", qos: .messageDeliveryAttemptedAtMostOnce, messageCallback: { (payload) in
// Handle the received message payload
})

// Sending GPS Locations via MQTT
let gpsData = "{\"latitude\": 37.7749, \"longitude\": -122.4194}"
iotDataManager.publishString(gpsData, onTopic: "gps-topic", qoS: .messageDeliveryAttemptedAtLeastOnce)


Swift MapKit with live locations:

As example we can make a simple app that share, in near real time, your friends GPS position on Map, like “FindMy“.

Can add pins, route, colors and fantasy…

Data Model

An example of data model used to pass data in Json through MQTT:

// Adapt this example model as you need
struct StreamingData: Codable {
    var lastUpdate: Date() = Date()
    var name: String
    var latitude: Double
    var longitude: Double
    var type: String = "user"
}

AWSManager Initialization

import Foundation
import AWSCore
import AWSIoT

class AWSManager {
    
    private var iotDataManager: AWSIoTDataManager!
    private var iotManager: AWSIoTManager!
    private var iot: AWSIoT!
    
    private let credentialProvider = AWSCognitoCredentialsProvider(
        regionType: <#YOUR-REGION#>,
        identityPoolId: <#YOUR-ID-POOL#>
    )
    
    var delegate: AWSManagerDelegate?
    var connected: Bool = false
    
    private func initializeControlPlane(credentialsProvider: AWSCredentialsProvider) {
        let controlPlaneServiceConfiguration = AWSServiceConfiguration(region: <#YOUR-REGION#>, credentialsProvider: self.credentialProvider)
        AWSServiceManager.default().defaultServiceConfiguration = controlPlaneServiceConfiguration
        self.iotManager = AWSIoTManager.default()
        self.iot = AWSIoT.default()
    }
    
    private func initializeDataPlane(credentialsProvider: AWSCredentialsProvider) {
        let iotEndPoint = AWSEndpoint(urlString: <#YOUR-ENDPOINT#>)
        
        let iotDataConfiguration = AWSServiceConfiguration(
            region: <#YOUR-REGION#>,
            endpoint: iotEndPoint,
            credentialsProvider: credentialsProvider
        )

        AWSIoTDataManager.register(with: iotDataConfiguration!, forKey: "myKey")
        iotDataManager = AWSIoTDataManager(forKey: "myKey")
    }
    
    func initPlane() {
        self.initializeControlPlane(credentialsProvider: self.credentialProvider)
        self.initializeDataPlane(credentialsProvider: self.credentialProvider)
    }
    
    // handle here all the statuses you need. In this example only .connected and .disconnected are implemented.
    func mqttEventCallback(_ status: AWSIoTMQTTStatus) {
        DispatchQueue.main.async {
            switch status {
            case .connected:
                // here your logic
                self.connected = true
                self.delegate?.completionConnected()
            case .disconnected:
                // here your logic
                self.connected = false
                self.delegate?.completionDisconnected()
            default:
                print("Something else")
            }
        }
    }
    
    // MARK: subscribe
    
    func subscribe(topicName: String, messageCallback: @escaping AWSIoTMQTTNewMessageBlock) {
        // the "subscribe" function create a "listener" for every updates you receive (that someone published)
        self.iotDataManager.subscribe(toTopic: topicName, qoS: .messageDeliveryAttemptedAtMostOnce, messageCallback: messageCallback)
    }
    
    // MARK: publish user location
    func publish(data: String, topicName: String) {
        // publish function allows you to "send" data to all the user that are subscribed to your topic.
        iotDataManager.publishString(data, onTopic: topicName, qoS: .messageDeliveryAttemptedAtMostOnce)
    }
    
    // MARK: connect & disconnect
    func connectUsingWebSocket(uuid: String? = UUID().uuidString) {
        if let uuid = uuid, !connected {
            iotDataManager.connectUsingWebSocket(withClientId: uuid, cleanSession: true, statusCallback: self.mqttEventCallback(_:))
        }
    }
    
    func handleDisconnect() {
        if connected {
            DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
                self.iotDataManager.disconnect()
            }
        }
    }
}

Example code, with a real Map

import MapKit

class MapViewController: UIViewController {
    // 1. Your UI here
    [...]
}

extension MapViewController: MKMapViewDelegate {
    // 2. Add your favorite MapView here
    [...]
}

extension MapViewController: CLLocationManagerDelegate {
    // 3. Add your favorite location manager here
    // [...]

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let lastLocation = locations.last else { return }
        
        latestCoordinate = lastLocation.coordinate

        currentUserPinAnnotation.coordinate = CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)

        lobbyMapViewModel?.addAnnotation(annotation: self.currentUserPinAnnotation)
        lobbyMapViewModel?.centerMap(with: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude))

        if let profile = profileModel, let uid = profile.id {
            let userData = StreamingData(
                name: uid,
                latitude: lastLocation.coordinate.latitude,
                longitude: lastLocation.coordinate.longitude,
                type: "user"
            )
            
            // publish to other connected users your current location
            // using "publish" command
            if let dataDictionary = try? userData.toJSON() {
                awsManager.publish(data: "\(dataDictionary)", topicName: <#YOUR-TOPIC#>)
            }
        }
    }

}

extension MapViewController: AWSManagerDelegate {
    // 4. AWS delegate implementation

    func completionConnected() {
        // start GPS update, when connected
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()

        // subscribe to updates
        awsManager.subscribe(topicName: <#YOUR-TOPIC#>) { [self] payload in
            let stringValue = String(data: payload, encoding: .utf8)!
            if let jsonData = stringValue.data(using: .utf8) {
                do {
                    let userData = try JSONDecoder().decode(StreamingData.self, from: jsonData)

                    // skip if data become from myself
                    if userData.name != self.myUserName ?? "" {
                        DispatchQueue.main.async {
                            if let annotationToRemove = self.usersInStream?[userData.name]?.annotationType {                                self.lobbyMapViewModel?.removeAnnotation(annotation: annotationToRemove)
                            }

                            let annotation: MKPointAnnotation?
                            if userData.type == "super" {
                                annotation = SuperUsersAnnotation()
                            } else {
                                annotation = OtherUsersAnnotation()
                            }

                            annotation!.coordinate = CLLocationCoordinate2D(latitude: userData.latitude, longitude: userData.longitude)
                            annotation!.title = userData.name

                            let userMapModel = UserMapModel(userData: userData, annotationType: annotation!)
                            self.usersInStream?.updateValue(userMapModel, forKey: userData.username)
                            self.lobbyMapViewModel?.addAnnotation(annotation: annotation!)
                        }
                    }
                } catch {
                    print(error)
                }
            }
        }
    }
    
    func completionDisconnected() {
        // do something 
    }
}

Testing

You can test the app launching your favorite GPX on Xcode, you can add your custom GPX as explained in this old article: https://www.albertopasca.it/whiletrue/pokemongo-catch-world-wide-pokemon-from-your-desk/.

You can also add a security check to avoid Fake-GPS usage (read more here: https://www.albertopasca.it/whiletrue/ios-fake-gps-position-how-to-use-and-prevent/).

And finally, you can track GPS position with your app completely killed, that is more useful in this kind of application. Explanation here: https://www.albertopasca.it/whiletrue/track-gps-position-with-ios-app-killed/

Using this example you are now able to have a “common topic“, that you can share with your friend (with socials, or deep-link, or whatever you prefer), a map that update your friends location, instantly when the move on map.

You can create your favorite sharing “friends” app!


Conclusion:

With the AWS IoT SDK for iOS and Android, you can create robust IoT applications that seamlessly connect to the AWS IoT service, utilize MQTT for message exchange, and send GPS locations. This comprehensive guide has provided step-by-step instructions for configuring the AWS IoT SDK, including AWS setup, SDK installation, initialization, and MQTT integration. By following these guidelines, you can confidently develop your own IoT applications using the AWS IoT SDK, taking advantage of its powerful capabilities.

Have fun!

 

Alberto Pasca

Software engineer @ Pirelli & C. S.p.A. with a strong passion for mobile  development, security, and connected things.