• iOS
  • Android
  • Server-to-Server
  • Networks
  • Usage

    The call to start the Mira SDK should be done as early as possible in your application lifecycle. The most common location will be in your UIApplicationDelegate instance. By default, the SDK will log interesting beacon events to the console and post a notification named "MiraEvent" with the log string. You can turn these off by calling [MiraSDK setLoggingEnabled:NO] or MiraSDK.setLoggingEnabled(false) as shown below.

    // Objective-C
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        [MiraSDK startWithAPIKey:@"YOUR API KEY HERE"];
    
        // Turn off logging
        // [MiraSDK setLoggingEnabled:NO];
    
        ...
    
        return YES;
    }
    // Swift
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    
        MiraSDK.startWithAPIKey("YOUR API KEY HERE")
    
        // Turn off logging
        // MiraSDK.setLoggingEnabled(false)
    
        ...
    
        return true;
    }

    Info.plist

    You will need to add the following two UIBackgroundModes to your Info.plist file:

    <key>UIBackgroundModes</key>
    <array>
      <string>bluetooth-central</string>
      <string>location</string>
    </array>

    The location mode is to allow background updates when a user comes within range of a beacon. The bluetooth-central mode is to allow the SDK to write a small packet of information called a “genotype” to proprietary Mira-controlled beacons found in elevators, taxi cabs, subways, etc. where there is little to no WiFi or cellular connectivity.

    In addition, you will need to add the NSLocationAlwaysUsageDescription and NSLocationUsageDescription keys to allow your app to receive beacon events in the background. Otherwise, beacon events will only be recorded when your app is active.

    <key>NSLocationAlwaysUsageDescription</key>
    <string>We monitor your iBeacon interactions for advertising purposes.</string>
    <key>NSLocationUsageDescription</key>
    <string>We monitor your iBeacon interactions for advertising purposes.</string>

    You may change the descriptions to better suite your application’s needs.

    Installation

    The SDK targets iOS 7.0+ and is published as a CocoaPod. To use it, simply add the following line to your Podfile:

    pod "MiraSDK"

    Although not required, it is suggested to add the use_frameworks! tag to your Podfile when integrating the Mira SDK into a iOS project built with Swift.

    You can also view/copy the code in our Github repo. In that case, you will need to link to the following frameworks:

    AdSupport
    CoreBluetooth
    CoreLocation
    SystemConfiguration
    Foundation
    UIKit
  • Installation

    The SDK effectively requires API level 18 (Android 4.3/Jelly Bean), but will compile on API level 10 and later. On systems earlier than API level 18 or those without Bluetooth LE functionality, the SDK will effectively be inert. We perform runtime checks of system compatibility, and as such most developers won’t have to change their minSdkLevel

    For those using a Gradle-based build system, the SDK can be included as a dependency and is available from the jcenter repository. jcenter is the default repository in Android Studio projects, but can be manually included as well.

    repositories {
      jcenter() // Should be included by default
    }
    dependencies {
      compile 'co.mira:mira-android:1.1.+'
    }

    If you are using Android Studio with Gradle, the AndroidManifest.xml requirements from above will be automatically merged into your project manifest. For other IDEs/build systems, the Mira SDK and its dependencies can be added as JAR files, but the manifest file must be updated manually. Add the following dependencies to your project:

    Permissions

    These permissions are included in the library’s manifest:

    • INTERNET
    • RECEIVE_BOOT_COMPLETED
    • BLUETOOTH
    • BLUETOOTH_ADMIN
    • ACCESS_COARSE_LOCATION (for >23)

    Runtime (for 23 and greater):

    • ACCESS_COARSE_LOCATION

    AndroidManifest.xml

    In addition to the above permissions, the application requires that the following service and receiver be declared in the <application> element.

    <service android:name="co.mira.android.AdvertisingService"/>
    <receiver android:name="co.mira.android.MiraReceiver"
        android:enabled="false"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>

    Most IDEs will perform an automatic manifest merger (union of all AndroidManifest.xml files) between your modules while compiling your application. If this is not the case for your environment, use the Mira SDK Manifest as a guide.

    Note: Some optional functionality makes use of the ACCESS_FINE_LOCATION permission, but we don’t require it and we won’t ask for it, even if the host app asks Mira to handle runtime permissions (see app2).

    Configuration

    Most developers will simply want to start the Mira SDK in the onCreate() method of your Application subclass or main Activity subclass. However, with the addition of runtime permissions in Android Marshmallow (6.0; SDK Level 23), the function you call may depend on the permissions your app currently uses, or how you want to handle runtime permissions.

    We’ve included two application modules in our repository that illustrate two ways of dealing with permissions - both manifest/install and runtime - for the Mira SDK. Runtime permissions are only relevant to apps targeting Android Marshmallow (API Level 23) and above, which now requires applications to not only declare permissions in the manifest (presented to user at install time), but also to obtain on-the-fly user approval of certain “dangerous” permissions at runtime (see Android 6.0 changes for details). The difference between the two configurations boils down to which component is declaring manifest permissions and obtaining runtime permissions: the SDK or the host application?

    Host App Handles Permissions (app)

    This application implements the case in which the host application (you) handles obtaining runtime permissions. In this case, the host application already has some functionality that uses the ACCESS_COARSE_LOCATION permission. In the sample, Mira.start() is called in the onCreate() method of the Activity. This will start the necessary components of the Mira SDK if the permissions already exist. If the permissions do not exist, it does nothing.

    The host application must already have some logic to handle when permissions are granted if it is already handling runtime permissions. You can optionally add Mira.start() again in the permission request result handler, to guarantee that it runs.

    public class MainActivity extends Activity {
      private static final String API_KEY = "YOUR API KEY HERE";
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
    
        ...
    
        Mira.start(this, API_KEY);
      }
    
      ...
    
      @Override
      public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
          switch (requestCode) {
              case PERMISSION_REQUEST_COARSE_LOCATION: {
                  if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      Log.d(TAG, "Permissions granted!");
                      doHostAppLocationTask();
                      Mira.start(this, API_KEY);
                  } else {
                      Log.w(TAG, "Permissions rejected!");
                  }
                  return;
              }
          }
      }
    }

    This is the preferred method of integrating as it gives the app developer more control over timing and messaging (what the dialog says).

    Mira Handles Permissions (app2)

    This application demonstrates how to run Mira if the host application doesn’t currently do anything with permissions, runtime or manifest.

    This application does not do anything within its own scope that requires runtime permissions, so it lets Mira ask for them. In this case, the function that is called is Mira.startOrPermissions, which either starts the Mira SDK if the runtime permissions are already granted, or prompts the user to give the current activity runtime permissions.

    In order to work properly, Mira needs to get notification that the permission was granted, so override the onRequestPermissionResult method of your application and pass the result to Mira.handlePermissions.

    public class MainActivity extends Activity {
      private static final String API_KEY = "YOUR API KEY HERE";
      private Mira mira;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
    
          ...
    
          mira = Mira.getInstance(this);
          mira.startOrPermissions(this, API_KEY);
      }
    
      @Override
      public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
          mira.handlePermissions(requestCode, permissions, grantResults);
      }
    }
  • Usage

    If your app is already collecting beacon bumps or location information, it’s possible to forward us these events as they arrive. Data can be sent via an HTTPS POST request to https://api.mira.co/partner/LocationEvent.

    Below is an example event and an explanation of required and optional fields.

    {
      "apiVersion": 1,
      "apiKey": "5196d8569179496198529a375bd3fad5",
      "advertisingId": "0a31b055-4c18-44f9-b30a-c37dd29fe803",
      "beacon": {
        "uuid": "56de7c19-7508-4351-bd58-379eadc5aa2e",
        "major": 1,
        "minor": 2
      },
      "location": {
        "latitude": 36.090519,
        "longitude": -79.801034,
        "accuracy": 10
      },
      "timestamp": "2015-12-23T15:28:52-05:00",
      "sourceIp": "63.118.185.98",
      "os": "ios"
    }

    Required fields

    apiVersion - currently 1.

    apiKey - your API key.

    advertisingId - the advertising id is the UUID provided by the Android or iOS operating system for the purpose of mobile advertising. They are known variously as the Android ADID or IDFA.

    beacon and/or location - at least one of these must be present. If you have information on both, send both. Note: the accuracy field is in meters and is optional.

    Optional fields

    timestamp - ISO 8601 timestamp of when the event was triggered. If there is a significant lag between the event hitting your server and your notifying us, please provide this field.

    sourceIp - the IP address of the device as received by your server.

    os - either "ios" or "android".

  • Overview and Integration Guide for OOH Networks

    Updated Aug 28, 2018

    This document is meant to serve as an overview of the integration process between Mira and digital out-of-home networks. It is intended for network administrators, engineers, and other managers responsible for the technical health of their ad serving and content management systesms

    What is Mira?

    Mira is the crowd analytics platform. Crowd analytics seeks to answer the question: what types of people are in a given place at given time? Mira uses historical and real-time location and behavioral data to place a digital overlay on physical locations. This data can be used to optimize out-of-home campaigns, by informing the decision of which content to show based on the real-time audience.

    High-Level Architecture Overview

    A Mira campaign is driven by a model, which is essentially a decision tree that can pull from a variety of static or real-time sources. A source is something like the segments that are currently present at a sign, the number of impressions per creative to date, etc.

    Mira’s server maintains a real-time ledger of what content every device should be playing for a given campaign. New data from each source is fed through the subscribed models, which in turn update this ledger with the most up-to-date decision.

    There are internal relationships that affect how individual screens are matched to their respective decisions. For example, clusters of devices can be grouped together to access the same entry in the ledger, so that they are always using the same decision.

    This ledger is updated independently of what the signs are doing. It doesn’t matter if there is a schedule for the content. Our server simply answers the question “what should I play right now?”, where “I” can be an entire network or just one device.

    Most of the integration centers around asking that question, and getting the answer to translate into something on the screen. There are a few standard delivery methods (see below), but custom solutions are pretty simple to implement. Something has to hit our server, and the “slot” must be configurable to change what is shown, either directly through the CMS (think RSS feeds for weather), or through a dynamic file type that reads this data.

    Integration

    There are a few steps in integrating a network with Real-Time Optimization.

    Inventory

    Mira needs to generate identifiers for each unique screen, so the first step is typically ingesting a list of inventory. This can be done on a per-campaign basis or all at once for the entire network. The only field that is strictly necessary is a name or other unique identifer, but other fields typically included are

    • Address
    • Bearing/Facing
    • Field of View
    • Latitude/Longitude
    • Format
    • Resolution
    • Dimensions
    • Geopath ID

    Inventory is represented in our system as a display unit. However, a display unit does not have to correspond to a single screen or player. A display unit can correspond to an entire mall, or any other arbitrary entity. A display unit is simply the most granular unit for which you can obtain an ad serving decision. So it is possible to allow multiple unique devices to act as the same display unit, but it is important to note that they will be indistinguishable from each other on our server.

    Geofences

    When using real-time location data in a campaign (which is the case for most RTO campaigns), some specification of a geofence needs to be uploaded as well. This represents the visible area for a particular unit. This can be communicated to us by using a geojson format to specify the polygons for each unit, or by using some generative approach, like combining latitude, longitude, radius, bearing, and field of view to produce a geofence from a flat CSV file.

    Delivery Methods

    Delivery methods are simply how dyanmic content is pushed through your network. How this happens is usually dictated by the CMS. Note that most of these integrations require the CMS to have the ability to set injectable variables for different screens. An example of this is BroadSign’s Content Variables.

    Remote URL

    Your CMS “visits a website” that shows the appropriate content. This is the easiest way to integrate if your CMS can fetch HTML content from a remote source. The /screens/play endpoint wraps around the /screens/decide endpoint and renders the asset that is returned. The wrapper responds with an HTML5 page sized appropriately for your device, and the decided content. This also eliminates the need to call the /flights/impressions endpoint. This requires reliable Internet connectivity.

    HTML5 Bundle

    If your system does not have reliable Internet or cannot fetch remote URLs, but can render HTML5 content (i.e. has a browser/web rendering engine), then an HTML5 bundle might be the best option. In this scenario, we deliver a zip file with the following structure:

    .
    ├── bundle.js
    ├── assets
    │   ├── mira
    │   │   ├── 4e3d8c38211d45a9bebba2c2402bf4ef.png
    │   │   ├── 7ca9a504814644edb544aae0d558066b.png
    │   │   └── e7cdb5609f4e4f5c88458a4ed0501f16.png
    │   └── placeholder.png
    ├── index.html
    └── styles
        └── css
            ├── global.css
            └── normalize.css

    The index.html file is a container that executes code from bundle.js, which is configured to ping the /screens/decide endpoint with the appropriate parameters for the slot the bundle is to be schedule in. After receiving a response, the container’s DOM is manipulated to show the appropriate content.

    If you are using a CDN to deliver assets already with your CMS, the HTML5 bundle can be configured to load its assets from a local directory, such as C:\sync\mira. In this case, the bundle is just the shell that makes the API call, and depending on the CMS configurability, the same bundle file can be used for any and all Mira campaigns.

    Alternatively, assets can be included in the HTML5 bundle, in which case a new bundle is minted for each campaign.

    Flash Bundle

    Same idea as an HTML5 bundle, but the server is pinged from ActionScript. Flash can also use a CDN to read from a local directory, or can have assets directly embedded into the flash movie itself as different scenes.

    CMS-Based Triggering System

    Many networks and CMSs have their own data fetching and triggering system. One very common use of this is to fetch weather data and trigger different content based on if it’s sunny, raining, etc.

    In this method, the CMS itself performs the data fetching and is responsible for triggering the correct asset. Usually, each asset in the campaign will have a simple mapping rule configured in the CMS, each of which is ultimately mapped to a file. The CMS then extracts the decision URI from Mira’s responses, and uses its rules to determine what to play.

    Custom

    We can work with a variety of custom configurations. These revolve around some system hitting the /screens/decide endpoint for the campaign/device, and making that data available to some container that can alter the default display behavior.

    API Documentation

    The API is available at https://api.mira.co. Currently, the API is only able to fetch decisions and report ad rolls; it is not used to configure networks and inventory.

    Authentication

    A valid account is required to access the APIs, which will be configured for you. Authentication can be done in one of two ways.

    • Append a valid API key to every request, like /screens/decide?api_key=abcdef...
    • Obtain a JWT by POSTing login information to the /users/login endpoint, and using that as a bearer auth header, i.e. Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.... This JWT will expire, so has to be periodically refreshed.

    Usually the API key is the easier of the two.

    GET /screens/decide

    Decide is the interface to Mira’s ad server.

    It returns either a single decision (for one campaign/device combo), or an array of decisions (for one campaign). The latter can be used in situations where the OOH Network wants to obtain the relevant decisions for all signs in the network corresponding to a particular campaign.

    Parameters
    • campaign_id (required) - the identifier of the campaign being decided, generated by Mira (required). This will throw a 400 Bad Request if absent or invalid.
    • display_unit_id - the identifier of a particular device, generated by Mira
    Response Examples

    /screens/decide?api_key=780f2f38504c425fbc3fb0284cdb5908&campaign_id=12345&device_id=1928

    {
      "timestamp": "2018-08-28T19:31:28.671290",
      "decision": {
          "network_id": null,
          "campaign_id": 12345,
          "display_unit_id": 1928,
          "model_instance": 45093,
          "creative_id": 8123094,
          "decision_uri": "s3://mira-assets/7e/0a/7e0ac2c9c04a4c37b8253052790e072c.mp4",
      }
    }

    /screens/decide?api_key=780f2f38504c425fbc3fb0284cdb5908&campaign_id=12345

    {
      "campaign_id": 12345,
      "decisions": [
        {
            "network_id": null,
            "campaign_id": 12345,
            "display_unit_id": 1928,
            "model_instance": 45093,
            "creative_id": 8123094,
            "decision_uri": "s3://mira-assets/7e/0a/7e0ac2c9c04a4c37b8253052790e072c.mp4",
        },
        {
            "network_id": null,
            "campaign_id": 12345,
            "display_unit_id": 1929,
            "model_instance": 45094,
            "creative_id": 8123095,
            "decision_uri": "s3://mira-assets/d7/e6/d7e6805d6ebe49579335af0d6bfc4b9e.mp4",
        },
        {
            "network_id": null,
            "campaign_id": 12345,
            "display_unit_id": 1930,
            "model_instance": 45095,
            "creative_id": 8123094,
            "decision_uri": "s3://mira-assets/7e/0a/7e0ac2c9c04a4c37b8253052790e072c.mp4",
        }
      ],
      "timestamp": "2018-08-28T19:31:28.671290",
    }

    NB: The creative_id is a different identifier than the unique resource identifier corresponding to the asset.

    POST /flights/impressions

    The impressions endpoint logs out-of-home impressions. It is called after a decision is made and the asset is rendered.

    Parameters
    • display_id (required) - the identifier of the device that rendered the impression, generated by Mira.
    • creative_id (required) - the identifier of the creative that was rendered, generated by Mira.

    /flights/impressions?display_unit_id=1234&creative_id=5678

    {
      "display_unit_id": 1234,
      "campaign_id": 9,
      "creative_id": 5678,
      "created_at": "2018-08-28T19:36:06+0000"
    }

The Mira SDK needs an authorized API key to function. If you need an API key, please email support@mira.co.