Add Edge Support to EdgeXR ARShooter SDK Sample

Last Modified: 6/23/2021

This is a guide for the open source iOS Augmented Reality Shooter Sample Application that demonstrates a use case for integrating an ARKit application with a simple Socket.IO leaderboard server that is deployed on EdgeXR.

To download the code and read instructions on how to run it yourself, check out code on the EdgeXR Samples Github. In this tutorial, we'll show you the key bits of code necessary on how the sample integrates the EdgeXR iOS SDK.

Prerequisites

  • A EdgeXR Console Account to access our SDKs on the EdgeXR Artifactory

  • MacOS Computer and an iOS Device

  • Xcode (From the Apple store, search for Xcode)

  • An Apple ID. Create an ID from the developer site on Apple

  • Cocoapods installation

Import the iOS SDK Matching Engine Library

The EdgeXR iOS MatchingEngine SDK is stored in Artifactory and we will pull it in using Cocoapods (an iOS dependency manager).

In terminal, run the following commands:

  • Install cocoapods sudo gem install cocoapods

  • Install cocoapods-art sudo gem install cocoapods-art

  • Go to root directory cd ~

  • Create a .netrc file and put in credentials:

$ echo "machine console.cloud.edgexr.org/storage/v1/artifacts/artifactory login <ArtifactoryUsername> password <ArtifactoryPassword>" > .netrc

where <ArtifactoryUsername> and <ArtifactoryPassword> are your EdgeXR Console credentials.

$ pod repo-art add cocoapods-releases https://https://console.cloud.edgexr.org/storage/v1/artifacts/artifactory/api/pods/cocoapods-releases

These commands will pull in the Podspecs from the EdgeXR Artifactory repo into your computer. You can see them by navigating to

~/.cocoapods/repos-art/cocoapods-releases/Specs/MobiledgeXiOSLibrary/3.0.5/MobiledgeXiOSLibrary.podspec

Navigate to the folder that contains the completed ARShooterGame Example and then run pod install

This will look at the podspecs specified in the Podfile: source https://github.com/CocoaPods/Specs.git for Open Source dependencies we are using, and our local cocoapods-releases directory for the MobiledgeXiOSLibrary podspec. The podspec will inform Cocoapods where the source code is and CocoaPods will automatically integrate the dependencies into our project.

iOS MatchingEngine SDK

The iOS MatchingEngine SDK uses the Promises framework to easily work with asynchronous code. We will be using the EdgeXR Distributed Matching Engine APIs to register the user and find the optimal edge data center (cloudlet) running the app. To learn more about the workflow, you may refer to the SDK Technical Overview Documentation.

We will be showing the implementation for the following:

RegisterClient() - Identifies the user (Organization Name) and application details (appName and appVersion), and allows the usage of EdgeXR integration.

FindCloudlet() - Returns the edge application server to communicate with, in the form of an AppPort list that needs to be parsed to retrieve your particular application's server details. Either TCP or UDP transport. A cloudlet is a small-scale cloud datacenter.

Edge Events() - Listen for events and send updates about the client to the EdgeXR Distributed Matching Engine in order to determine the optimal time in your application workflow to switch to a better cloudlet for your end users.

Setup Matching Engine Parameters

In the LoginViewController, first we have to import the MobiledgeXiOSLibrary in our Client code and define variables that will be used in our APIs.

import MobiledgeXiOSLibrary

var matchingEngine: MobiledgeXiOSLibrary.MatchingEngine!

Once the matchingEngine variable has been created, we next need to define which application we want to find on the EdgeXR platform. This is defined by the appName, appVers, and orgName, which you can look up in the EdgeXR Console for your application. In this case, we have already created a sample application that you can use below and here are the configurations we recommend to use, which are defined in the setupMatchingEngineFunction :

matchingEngine = MobiledgeXiOSLibrary.MatchingEngine()
dmeHost = "wifi.dme.cloud.edgexr.org"
dmePort = 38001
appName = "ARShooter"
appVers = "1.0"
orgName = "EdgeXR-Samples"
carrierName = ""
location = MobiledgeXiOSLibrary.MatchingEngine.Loc(latitude: 37.459609, longitude: -122.149349) // Get actual location and ask user for permission

Register Client

With the parameters specified above, we can make the first API call with the Matching Engine : RegisterClient. As the name implies, this registers the client iOS device with the EdgeXR Matching Engine and confirms if that specified application, ARShooter in this case, has been deployed to the edge. The request returns a Session Cookie in the RegisterClientReply that must be used for following API calls. This is the specific example code for making a Register Client Call.

// Register user to begin using edge cloudlet

let registerClientRequest = matchingEngine.createRegisterClientRequest(
  orgName: orgName,
  appName: appName,
  appVers: appVers)
        
matchingEngine.registerClient(request: registerClientRequest).then { registerClientReply in
            SKToast.show(withMessage: "RegisterClientReply is \(registerClientReply)")
            print("RegisterClientReply is \(registerClientReply)")
}

Find Cloudlet

After Registering the Client Device, we can now call the Find Cloudlet API, which is responsible for determining the best cloudlet that your application is deployed onto for the client, based on the GPS location and operator cellular connection. This is the specific example code for making a Find Cloudlet call.

// FindCloudlet
let findCloudletRequest = try self.matchingEngine.createFindCloudletRequest(
  gpsLocation: self.location!,
  carrierName: self.carrierName!
)

self.findCloudletPromise = self.matchingEngine.findCloudlet(request: findCloudletRequest)

all(self.findCloudletPromise!).then { value in
  // Handle findCloudlet reply
  let findCloudletReply = value.0
  SKToast.show(withMessage: "FindCloudletReply is \(findCloudletReply)")
  print("FindCloudletReply is \(findCloudletReply)")
  SKToast.show(withMessage: "Need to handle verifyLocation reply")
}.catch { error in
  // Handle Errors
  SKToast.show(withMessage: "Error occured in callMatchingEngineAPIs. Error is \(error.localizedDescription")
  print("Error occured in callMatchingEngineAPIs. Error is \(error.localizedDescription)")
}

GetConnection workflow:

The GetConnection workflow is the suggested workflow to register the user using an application, find the nearest cloudlet with the application backend deployed, and get a “connection” object to send and receive data.

The full workflow is:

  1. RegisterAndFindCloudlet(): Wrapper function for registerClient() and findCloudlet(). Returns a dictionary with findCloudletReply fields.

  2. Get[Protocol]AppPorts(): Returns a dictionary (key: String, value: [String: Any]), where keys are the internal port specified on app deployment and values are the AppPort “object” returned in the ports field of findCloudletReply. (This object may contain a range of ports and an fqdn prefix that is specific to the application backend)

let replyPromise = matchingEngine.registerAndFindCloudlet(
  orgName: orgName,
  gpsLocation: location!,
  appName: appName,
  appVers: appVers,
  carrierName: carrierName).then { findCloudletReply -> Promise<SocketManager> in
    // Get Dictionary: key -> internal port, value -> AppPort Dictionary
    guard let appPortsDict = try self.matchingEngine.getTCPAppPorts(findCloudletReply: findCloudletReply) else {
        throw LoginViewControllerError.runtimeError("GetTCPPorts returned nil")
    }
  1. For your app, you should know what internal port is required and get the AppPort “object” from the dictionary that corresponds to that internal port. This returned AppPort object will then be used in the GetConnection call.

  1. Get[Protocol]Connection(): Depending on the protocol (TCP, UDP, HTTP, Websockets), this will return a different Swift object to be used to send and receive data.

  // Select AppPort Dictionary corresponding to internal port 3838
    guard let appPort = appPortsDict[self.internalPort] else {
        throw LoginViewControllerError.runtimeError("No app ports with specified internal port")
    }
    return self.matchingEngine.getWebsocketConnection(findCloudletReply: findCloudletReply, appPort: appPort, desiredPort: Int(self.internalPort), timeout: 5000)
}.then { manager in
    self.manager = manager
}.catch { error in
    print("Error is \(error)")
}

For ARShooter, the app expects a Websocket connection. The getWebSocketConnection method demonstrates how you can implement this workflow for a WebSocketConnection.

iOS GPS Location

The MobiledgeXiOSLibrary requires user location to find the nearest cloudlet.

The code above hardcoded the user location for ease of use. An actual application would request location permissions from the user and grab the gps location whenever needed. This section will show how to request location permissions and convert GPS locations to a EdgeXR.Loc object that can be used in FindCloudlet.

Request Permissions

First let us request location permissions from the user.

self.locationManager = CLLocationManager()
locationManager!.delegate = self
locationManager!.requestWhenInUseAuthorization()

Note, LoginViewController implements the CLLocationManagerDelegate interface. We will implement a delegate function in this file, so we must assign LoginViewController to be the locationManager's delegate.

Implement CLLocationManagerDelegate

Finally, the client needs to listen for location updates from the manager, which is implemented in the locationManager function.

Once the user allows location permissions, we will grab the user's gps location and then convert it to a EdgeXR.Loc object to be used for the FindCloudlet EdgeXR API calls.

var currLocation: CLLocation!
if (status == .authorizedAlways || status == .authorizedWhenInUse) {
  currLocation = manager.location
  location = MobiledgeXiOSLibrary.MatchingEngine.Loc(latitude: currLocation.coordinate.latitude, longitude: currLocation.coordinate.longitude)
}
if location != nil {
  mobiledgeXIntegration()
}

ARShooter Socket.IO Server

Originally, this app used iOS’s MultipeerConnectivity Framework, which uses peer to peer communication. This framework breaks down with more than 7 or 8 players connected to the same game. So, we have to use a server to handle communication between devices and synchronize the game state (so everyone sees the same thing, or at least close to the same thing).

To see the server code, go to edge-cloud-sampleapps/iOS/ARShooterExample/ARShooterServer/shooter-server.js. This a bare-bones Node.js server that directs communication between devices and can keep track of different games. It uses the socket.io library, which is mirrored in client side with the Socket.IO-Client-Swift library (which we imported using Cocoapods).