iOS SmartDrive framework - scopetech/Mobile-Public GitHub Wiki

SmartDrive SDK Documentation - v1.1.11

This document describes how ScopeTechnology framework "SmartDrive" works and how to integrate into your application. While the target audience is coders/developers, it brings better understanding for those who are not too tech-savvy as well.

SmartDrive framework main functionality is to record trips. It supports trip recording:

  • with Beacons (BLE device from ScopeTechnology).
  • with MHub837s (BLE device from ScopeTechnology).
  • without any BLE device (called "Smart" version).

Part of the Consolidated SPM package. Related: App configuration & permissions ยท Integration guides ยท Release notes ยท Demo app

Table of contents

1. Installation Guide

Following instructions will allow you to integrate our SDK into your application. Additionally, here is the demo sample app that you can follow while going through this document.

1.1. Overview

In introduction we mentioned that SmartDrive has two configuration types:

  • for BLE devices (including ScopeTechnology - beacons and MHubs837s)
  • for no BLE devices (smart solution)

It is important to apply correct configuration and permissions for your specific business needs.

1.2. Requirements

  • Minimum Deployments: iOS 15.0

Permissions. SmartDrive needs location and motion access to record trips:

  • Location โ€” used to capture the trip route and analyze the trip in detail, and for BLE device scanning in beacon mode.
  • Motion & Fitness โ€” lets the algorithm tell whether the user is driving or walking (so trips are detected in time and ghost trips are avoided), and drives exception detection (driving behaviour during the trip).

Important

The full permission and background setup โ€” Info.plist keys, the required Always + Precise location level, Background Modes, Background App Refresh, and notifications โ€” is documented once for the whole package in Consolidated SPM โ€” Section 4: Required App Configuration.

Two that commonly stop background trips:

1.3. Integration

The library (SDK) is shared within SmartDriveDemo app as .xcframework

Step 1. Request the latest version of SmartDriveDemo app as .zip

Step 2. Copy SCPSmartDrive.xcframework from root directory to your project as regular framework

Important

Always confirm that you are using latest version.

2. SmartDrive Initialization

2.1 Configuration

2.1.1. Initial Configuration

All core configuration happens using SCPSmartDriveManager helper class.

import SCPSmartDrive

2.1.2 Record Only - Beacon Trips / Smart Trips

We need to tell SmartDrive framework weather we want to use BLE devices for trip recording or use smart solution instead.

Important

If SmartDrive is intended to work with BLE devices

UserDefaults.standard.set(true, forKey: "recordTripsWithBeaconOnly")
SCPSmartDriveManager.sharedInstance().setMotionActivityEnabled(false)
SCPSmartDriveManager.sharedInstance().setSBeaconManagerEnabled(true)
SCPSmartDriveManager.sharedInstance().startSignificantLocationUpdates(true)
SCPSmartDriveManager.sharedInstance().setTripMonitoringDisabled(false)

Important

If SmartDrive is intended to work with no BLE devices using smart solution

UserDefaults.standard.set(false, forKey: "recordTripsWithBeaconOnly")
SCPSmartDriveManager.sharedInstance().setMotionActivityEnabled(true)
SCPSmartDriveManager.sharedInstance().setSBeaconManagerEnabled(false)
SCPSmartDriveManager.sharedInstance().setTripAutostartEnabled(true)
SCPSmartDriveManager.sharedInstance().setTripMonitoringDisabled(false)

2.1.3. Auto Sending

Trip auto sending happens automatically, no additional code needed.

2.1.4. End Trip Timer

Our solution uses trip end timer to validate if trip has truly finished (compared to heavy traffic with no movement for longer period of time). Trip end timer does not change recorded trip actual time range.

Warning

While you can change trip end timer based on your needs, it is strongly suggested to configure it between 3 - 5 minutes range to avoid ghost trips. Default value is 300 seconds.

UserDefaults.standard.set(300, forKey: "kSettingsTripEndThreshold") //Example for 5 minutes

2.1.5. Mode of Transport (MOT)

Mode of Transport is clever solution to avoid trip recording on pre-defined transport types. For instance, if you want to avoid public transport buses or trains, you can configure so and framework will discard them. By default - disabled.

let motSkipTrip = [
    "Air", // Airplane
    "Water", // Boats, jet skis
    "Public", // Bus, trams etc.
    "Railway", // Train
    "OffRoad" // OffRoad. Available from v1.1.9
]

SCPSmartDriveManager.sharedInstance().configureMOT(withIsMOTEnabled: true,
                                                            motSkipTrip: motSkipTrip,
                                                            elevationKey: "",
                                                            staticMapsKey: "<YOUR_KEY_HERE>")

2.1.6. Distracted Driving

When enabled will monitor driver actions during the trip. Following distracting driving types are available:

  • PHONE USE - phone is being used while driving. Browsing different apps, tilting it, shaking it and other similar actions will be caught.
  • PHONE CALLS - phone call is while driving WITH OUT headset (not available in current version).
SCPSmartDriveManager.sharedInstance().setDistractedDriverDetectionEnabled(true)

// set distraction types which should be tracked
SCPSmartDriveManager.sharedInstance().getDistractedDriverManager()?.setPhoneUseDetectionEnabled(true)
SCPSmartDriveManager.sharedInstance().getDistractedDriverManager()?.setPhoneCallDetectionEnabled(true)

Tip

If you want to detect events based on minimum speed, make sure you are using library version v1.0.21+

SCPSmartDriveManager.sharedInstance().getDistractedDriverManager()?.setMinimumSpeedToDetectDistraction(5) // If user speed is below 5km/h -> ignore events

3. Trip Recording

3.1 Start SmartDrive

SmartDrive is started as user logs in and above mentioned configuration methods are called.

3.2. Stop SmartDrive

Stop SmartDrive manually on logout to prevent trip recording for unauthorised user:

SCPSmartDriveManager.sharedInstance().setTripDataMonitoringOn(false)
SCPSmartDriveManager.sharedInstance().setTripMonitoringDisabled(true)
SCPSmartDriveManager.sharedInstance().setMotionActivityEnabled(false)
SCPSmartDriveManager.sharedInstance().setDistractedDriverDetectionEnabled(false)

// If SmartDrive is intended to work **with BLE devices**
SCPSmartDriveManager.sharedInstance().startSignificantLocationUpdates(false)

// If SmartDrive is intended to work **with no BLE devices** using smart solution
SCPSmartDriveManager.sharedInstance().setTripAutostartEnabled(false)

3.3. Realtime trip events sending

When configured lets send events during the trip. Trip start, end and periodic location updates trigger all the unsent events sending. Configuration:

// Set sending enabled first:
AppDelegate.shared.isRealtimeTripSendingEnabled = true

// Provide MProfiler credentials: service url, service provider, api key:
let mProfilerServiceUrl = "<YOUR_VALUE>"
let mProfilerServiceProvider = "<YOUR_VALUE>"
let mProfilerApiKey = "<YOUR_VALUE>"
SCPSmartDriveManager.sharedInstance()?.configureMProfilerEnvironment(mProfilerServiceUrl, provider: mProfilerServiceProvider, key: mProfilerApiKey) { error in
    // Check an error here
}

// If needed it's possible to check if MProfiler is configured correctly:
SCPSmartDriveManager.sharedInstance()?.checkMProfilerConfiguration { isValid, message in
    // Check isValid  
}

// Observe for a new event and send events when start, end or periodic location updates event has been received:
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveNewEvent(_:)), name: NSNotification.Name.newEventRegistration, object: nil)
...
@objc private func didReceiveNewEvent(_ notification: NSNotification) {
    guard let event = notification.userInfo?["event"] as? SCPEvent else {
        return
    }
    if event.eventType == NSNumber(integerLiteral: Int(SCPEventType.tripStart.rawValue)) || event.eventType == NSNumber(integerLiteral: Int(SCPEventType.tripEnd.rawValue)) || event.eventType == NSNumber(integerLiteral: Int(SCPEventType.periodicLocationUpdate.rawValue)) {
        sendEvents()
    }
}
...
private func sendEvents() {
    // See implementation in SmartDriveDemo app
}

3.4. Reading the current trip (status, distance, duration, speed)

While a trip is being recorded you can read its live status and components directly from SCPSmartDriveManager. There are two kinds of information: the recorder status, which changes at trip boundaries, and the live components (distance, duration, speed), which you poll while a trip is active.

Status โ€” react to changes. SCPSmartDriveManager posts smartDriveStatusChanged whenever the recorder state changes. Observe it and read getRecorderState():

NotificationCenter.default.addObserver(self,
    selector: #selector(statusDidChange),
    name: NSNotification.Name.smartDriveStatusChanged,
    object: nil)

@objc func statusDidChange() {
    switch SCPSmartDriveManager.sharedInstance().getRecorderState() {
    case .recorderStateIdle:                            break // no trip
    case .recorderStateWaitingForLocation:              break // waiting for GPS
    case .recorderStateWaitingForTripStartConfirmation: break // possible trip starting
    case .recorderStateActiveTrip:                      break // recording
    case .recorderStatePassiveTrip:                     break // recording (passive)
    case .recorderStateWaitingForTripStopConfirmation:  break // possible trip ending
    case .recorderStatePending:                         break // finalising
    @unknown default:                                   break
    }
}

For a simple "is a trip being recorded right now" boolean, use isRecordingTrip().

Components โ€” poll while active. Read speed, distance, and duration from the manager. Gate distance and duration on there being an active trip:

let sd = SCPSmartDriveManager.sharedInstance()

// Speed is metres per second โ€” multiply by 3.6 for km/h
let speedKmh = sd.getCurrentSpeed() * 3.6

guard TripWrapper.shared.getCurrentTrip() != nil else {
    // No active trip โ€” only speed is meaningful
    return
}

let distanceKm = sd.getCurrentTripDistance()       // kilometres
let durationSeconds = sd.getCurrentTripDuration()  // seconds

Units and behaviour:

  • getCurrentSpeed() returns m/s โ€” convert to km/h with * 3.6.
  • getCurrentTripDistance() returns kilometres.
  • getCurrentTripDuration() returns seconds (format as HH:MM:SS if you display it).
  • When not recording, getCurrentSpeed() can report residual GPS drift; the demo zeroes speed below 5 km/h when !isRecordingTrip().
  • TripWrapper.shared.getCurrentTrip() returns the active trip (or nil); use it to gate distance/duration and to read identifiers such as tripId and startDate.

Refresh cadence. There is no push for live components โ€” poll them on a timer. The demo uses a 1-second repeating Timer (with 0.5 s tolerance) and pauses it in the background (invalidate on willResignActive, recreate on didBecomeActive) to save battery.

Tip

Reference implementation: TripStatusViewModel.swift (status observation, timer, formatting) and the status screen in TripsViewController.swift in the demo app.

Related documentation

โš ๏ธ **GitHub.com Fallback** โš ๏ธ