SmartDriveDemo — App Overview - scopetech/Mobile-Public GitHub Wiki
The SmartDriveDemo iOS app is the reference integration for the Scope mobile SDKs
(SmartDrive / TripWrapper / SDApiManager / BLEFramework / Wellness). It demonstrates a
universal integration — the same host works for smart-mode clients and for
beacon / mHub clients — using the ScopeSDK façade. Use it as the worked
example when integrating the SDKs into a new app.
Companion wiki pages (read alongside this one):
-
iOS SDK Integration (recommended — the
ScopeSDK/AppScopeHostfaçade this app is built on. - iOS Session & App Lifecycle Management — when to arm, authenticate, and stop the SDK.
- iOS Diagnostic Logs & Export — health snapshots, log capture and export.
- iOS Consolidated SPM documentation — Swift-package installation, the product list, and Required App Configuration.
- iOS SmartDrive framework — the base trip-recording framework (configuration, thresholds, live trip reading) the app sits on.
- iOS TripWrapper SDK — the trip-lifecycle layer (detection, recording, upload, retry) the app delegates to.
- Architecture at a glance
- Frameworks, packages & required configuration
- Requirements & running the demo
- SDK integration layer
- App lifecycle —
AppDelegate.swift - Trip data & UI
- Beacon / mHub
- Diagnostics, logs & export
- Authentication —
LoginViewModel.swift - Wellness (optional module)
- Configuration & helpers
- Key flows
- Adopting this app as your integration template
- Where to start
The app is layered so that exactly one file is app-specific per concern, and the SDK integration is a thin, portable seam.
flowchart TD
UI["UI — View Controllers + XIBs"] --> VM["View Models<br/>(Login, Trips, TripStatus, Beacon, Diagnostics, Wellness)"]
VM --> READ["TripManager (facade) · FormattingHelper · TripDetailsManager"]
READ --> SCOPE["ScopeSDK (façade)"]
SCOPE --> TW["TripWrapper_iOS<br/>(owns delegate, send, retry)"]
TW --> SD["SCPSmartDrive<br/>(location, motion, trip detection)"]
SCOPE --> HOST["AppScopeHost (ScopeHost)"]
SCOPE --> BLE["BLEFrameworkProxy (beacon)"]
HOST --> API["SDApiManager (backend / auth)"]
BLE --> BLEF["BLEFramework<br/>(SBeacon / SCPBeaconManager)"]
-
App logic never talks to
SCPSmartDrivefor trip lifecycle —TripWrapperowns that. The app only reads SmartDrive for live display values (speed/distance) and supplies configuration values. -
ScopeSDK.swiftis copied verbatim from the SDK;AppScopeHost.swiftis the only integration file you write. See the SDK Integration (recommended) page.
All Scope SDKs this app uses ship as Swift packages, pinned together by the Consolidated SPM. This section states only what the Demo does and links out — the authoritative installation steps, product list, and Required App Configuration live in the iOS Consolidated SPM documentation and are not repeated here.
- Xcode → File → Add Package Dependencies…
- Paste the Consolidated SPM repository URL and choose
ConsolidatedSPM-iOS— the meta-package that pins the whole matched set. - Add the products the Demo uses to the app target:
SCPSmartDrive,TripWrapper_iOS,SDApiManager,BLEFramework,Wellness_iOS.
A single repository provides all of them; see the Consolidated SPM documentation for the exact URL and version-rule guidance.
The Demo's Info.plist and target capabilities already include everything the SDKs need
(location, motion & fitness, background modes, notifications, and camera for Wellness).
Don't re-derive these — the authoritative list is the Required App Configuration
section of the Consolidated SPM documentation. Runtime permission timing is
covered by Session & App Lifecycle Management, and the Diagnostics screen
(§8) plus the health snapshot flag any missing entry at runtime.
-
Pin via
ConsolidatedSPM-iOS. Bump it to the target release; it updates the whole matched set at once. Then commitPackage.resolved— it records the exact versions the build was tested with. -
SCPSmartDriveandTripWrapperare a matched pair — never update one without the other (ConsolidatedSPM does this for you). - SPM package version ≠ framework version. The package numbers and the framework versions shown in the health snapshot are different schemes; confirm both against the release's migration guide.
- For a full list of releases and changelogs, see the ConsolidatedSPM-iOS Releases page.
Audience: external developers integrating the Scope SDKs. This app is a working reference — the parts integrations most often get wrong are shown running end to end with inline comments.
- Xcode with CocoaPods installed. CocoaPods supplies ZIPFoundation (used only for log export); the SDKs come via SPM (§2).
- A real device — automotive trip detection does not work in the Simulator.
- Location = Always + Precise and Motion & Fitness granted, or trips won't record in the background (§2 and the Required App Configuration).
- Access to the SSO-protected
scopetechGitHub org to resolve the SPM dependencies (PAT + SSO — see Consolidated SPM §2).
-
pod install(pulls ZIPFoundation for log export). - Open
SmartDriveDemo.xcworkspace— not the.xcodeproj. - Build & run on a real device.
- Log in, then choose on the login screen: SMART (no BLE) or BEACON (BLE devices), and Real-time or Post-trip event sending. Both are stored in
UserDefaultsand restored next launch.
No backend URL to hardcode — the base URL is returned with the account after login and cached. The credentials shown on the login screen are placeholders; use your own test account.
| File | Role |
|---|---|
ScopeSDK.swift |
The façade — start / authenticated / stop, trip callbacks (onTripStarted/Ended/Updated), performBackgroundRefresh, isHealthy. Owns TripWrapper.initialize(...) and the internal HostAdapter that bridges the four TripWrapper protocols. Copied verbatim — do not edit.
|
AppScopeHost.swift |
The app's integration surface: ScopeHost (backend answers + onBeaconTripStart), ScopeConfiguration.app (mode / realtime / autoSend + configureSmartDrive/stopSmartDrive closures), and the LoggingProtocol adapter mapping TripLogGroup → LogGroup. The only file you write.
|
BLEFrameworkProxy.swift |
Bridges BLEFramework (BLEManager, SCPBeaconManager, SBeacon) to SDApiManager. Also exposes onBeaconTripStart() → SCPBeaconManager.shared.onTripStart() — the beacon snapshot for beacon/mHub trips. Living here keeps the BLEFramework import out of AppScopeHost (which would clash with TripWrapper_iOS.APIResponse). |
Beacon / mHub note. In beacon mode, ScopeSDK invokes ScopeHost.onBeaconTripStart()
at trip start; AppScopeHost forwards it to BLEFrameworkProxy.shared.onBeaconTripStart(),
which snapshots the in-range beacon so the trip resolves a device ID and can be sent.
Smart-mode apps use the protocol's default no-op. See the SDK Integration (recommended) page (§2.4 and the pitfalls) for detail.
AppDelegate owns launch/auth/background wiring and the SmartDrive values (not the
mechanics). Key responsibilities:
-
appType(smart vs beacon) and flagsisMOTEnabled,isRealtimeTripSendingEnabled— the single source thatScopeConfiguration.appreads. -
initializeFeatures()— configures the API, then armsScopeSDK.start(configuration:host:)-
authenticated();wireScopeSDKCallbacks()connectsonTripStarted/Ended/Updated.
-
-
configureSmartDrive()/stopSmartDrive()— values only: permission asks, autostart, thresholds, plusconfigureMProfiler(),configureBLEFramework(),configureMOT(). These are the closuresScopeConfigurationinvokes. Keep theminternal(they're called fromAppScopeHost.swift). -
Background tasks —
registerBackgroundFetchHandler(),scheduleBackgroundFetch(),handleAppRefreshTask(), andSCPSmartDriveBackgroundFetchDelegate; forwards toScopeSDK.performBackgroundRefresh(). -
TripWrapperDelegate—tripStarted/tripEnded/tripUpdatedfor UI refresh/analytics (never sends trips from here). -
UserDefaultshelpersgetTripEndThreshold()/setTripEndThreshold(value:).
Lifecycle rules (arm in
didFinishLaunchingguarded byisAuthorized(), log out in exactly one place, permission timing) follow the Session & App Lifecycle Management page.
| File | Role |
|---|---|
TripManager.swift |
Thin facade over TripWrapper — getLocalTrips(), getTargetTrips(), getCurrentTrip(), retryFailedTrips(), sendCompletedTrips(), and convertToTrip(from:) to bridge TripRecord → Trip. Does not implement SCPSmartDriveManagerDelegate and does not send trips. Calls retryFailedTrips() on didBecomeActive. |
TripNotificationManager.swift |
Implements TripNotificationDelegate only — showTripStartNotification(), scheduleTripEndNotification(), cancelTripEndNotification(), showPendingTripNotification(). Background-only: skips notifications when the app is active; de-dupes per tripId. Schedules the trip-end local push getTripEndThreshold() − 2 seconds out. |
TripsViewModel.swift |
Backs the Trips screen with three modes (logs, serverTrips, localTrips). Local trips come through TripManager (→ TripWrapper); server trips via SDApiManager.getTripsFor(...) + an assembled unfinished trip from getMZoneEvents(...), parsed by TripDetailsManager. |
TripStatusViewModel.swift |
Live dashboard values. A 1 s timer reads SCPSmartDriveManager for current speed/distance/duration (display only), gated by TripWrapper.shared.getCurrentTrip(); pauses in background. Publishes via TripStatusDelegate. |
TripDetailsManager.swift |
Parses backend trip JSON and MZone events into the app's models. createTrip(data:isLightMode:), createTrips(...) (assembles unfinished trips from events), filterEventsByThreshold(...), validateTrip(trip:). Defines the app data models below. |
-
Trip— app display model:tripId,distance,duration,start/endLocation,start/endDate,numberOfExceptions,exceptionType,maxSpeed,events,finishedTrip,isBusiness. -
TripEvent/TripEventType(braking, acceleration, cornering, speeding, distraction start/end) — each with atitleandimage;fromMZoneEventType(...)maps server strings. -
TripStats,TripExceptionType— scoring/summary types. -
TripRecord(fromTripWrapper_iOS) is the SDK's value type;TripManager.convertToTripbridges it toTrip.
| File | Role |
|---|---|
BeaconViewModel.swift |
Scans (SBeaconManager.startScan), lists paired (SCPBeacon) and unpaired (UnpairedSBeacon) beacons via SCPBeaconManager.getMixedBeacons(), refreshing every 1 s. Pairing installs the beacon to the selected vehicle (SDApiManager.installBeacon) and creates the "My Car" activity/beacon in SCPBeaconManager. |
BeaconViewController.swift |
Two-section table (paired / unpaired); pull-to-refresh; tap an unpaired beacon to pair. Shows in-range state from beacon.isInRange. |
Beacon trip recording is driven by the SDK, not these screens — pairing here is what
makes onBeaconTripStart (§4) resolve a device ID at trip start.
An in-app health dashboard that surfaces the same signals as the SDK's health snapshot.
It builds DiagnosticItems grouped into DiagnosticCategory (Permissions, SDK,
Configuration, Network, Storage) with per-item DiagnosticStatus (ok / warning /
critical) and a recommendation. Checks include: authorization, location & motion &
notification permissions, TripWrapper init / delegate / protocol wiring, network
(via NWPathMonitor), MProfiler config, MOT state, realtime sending, beacon config,
storage space, and SDK versions. getOverallHealth() rolls them up.
For capturing and exporting the underlying logs (and reading multi-line health snapshots), see iOS Diagnostic Logs & Export.
Beyond the in-app health dashboard, the demo captures and exports diagnostic logs:
| File | Role |
|---|---|
SmartDriveHelper.swift |
logHealthCheck — authorization, permissions, recorder state, trip counts (the same signals as the SDK health snapshot). |
LogEventManager.swift |
Core Data log store with retention; captures crashes and server errors. |
ReportManager.swift |
Builds the per-day ZIP bundle with an identifying header. |
ReportComposer.swift |
Shares the ZIP via email / the share sheet. |
TripsViewController.swift |
Hosts the Export logs button (exportLogs). |
ZIP bundling uses ZIPFoundation (via CocoaPods). Full capture / format / export guidance: iOS Diagnostic Logs & Export.
Wraps SDApiManager auth: isAuthenticated(), login(username:password:),
fetchAccountDetails(), and validateCredentials(...) (→ ValidationResult). On success
the app calls ScopeSDK.shared.authenticated() so recording begins post-auth (see the
lifecycle page).
| File | Role |
|---|---|
WellnessScanViewController.swift |
Camera-based vitals scan via Wellness_iOS (WellnessHealth): create/start/stop session, render camera + face ROI, show heart rate / breathing rate / oxygen saturation, progress, and validity guidance. Independent of trip recording. |
CircularProgressBar.swift |
Reusable CAShapeLayer ring used by the wellness progress display. |
| File | Role |
|---|---|
MOTSettings.swift |
Mode-of-transport config: MOTSettings (enabled, skip list ["Air","Water","Rail","Public"], map keys) and MOTConfig (airplane / railway / water / offroad thresholds) with createDefault() factories. Consumed by AppDelegate.configureMOT(). |
FormattingHelper.swift |
App-wide formatting: distance, duration (h m s and HH:MM:SS), speed (m/s → km/h), dates, and the server date parsers (toDateFromServerStringWithTimezone). Adds Float.format(f:), Double.formatted(decimals:), and Date.add/subtract. |
Launch → record → send.
didFinishLaunching → initializeFeatures() → configureAPI() → ScopeSDK.start(.app, AppScopeHost()) → after login ScopeSDK.authenticated() → configureSmartDrive() runs, TripWrapper.initialize(...) runs once → SmartDrive detects a drive → TripWrapper records and (auto-send / realtime per config) uploads via AppScopeHost.postTripEventBatch → TripWrapperDelegate refreshes the UI; TripNotificationManager posts background notifications.
Beacon / mHub trip.
Pair a beacon (§7) → on the next drive ScopeSDK calls onBeaconTripStart → BLEFrameworkProxy snapshots the in-range beacon → the trip is tagged with the beacon's device ID and uploaded.
Diagnostics.
DiagnosticsViewModel.refresh() recomputes all checks; the same health signals appear in exported logs (see the Diagnostic Logs page).
The order the demo follows — and the order your app should follow:
-
Configure the API base URL —
AppDelegate.configureAPI, once per launch. -
Log in —
SDApiManager.login. No SDK feature works without an authenticated session. -
Fetch the account —
SDApiManager.getAccountWithComplete; selects the vehicle and sets the user ID, unit serial number, and base URL. - Request & verify permissions — Always + Precise location, Motion, notifications.
-
Arm recording —
ScopeSDK.start(configuration:host:)+ScopeSDK.authenticated(), which runsconfigureSmartDrive()and initializes TripWrapper once.
Arm only after the account is fetched (step 3). Trips are tagged with the selected vehicle's unit serial number; starting to record before it's set means trips can't be matched to a vehicle and log headers come out empty. See Session & App Lifecycle Management.
- Copy
ScopeSDK.swiftverbatim. - Write
AppScopeHost.swift— backend answers via your API layer, config values from your app, the logging adapter for yourLogGroup. Beacon/mHub apps addonBeaconTripStart()(route through your BLE proxy). - Keep
TripManageras a thin read facade andTripNotificationManagerasTripNotificationDelegate-only. - Wire
AppDelegateper the lifecycle page; put SmartDrive values inconfigureSmartDrive(). - Use
DiagnosticsViewModel/ exported logs (Diagnostic Logs & Export) to verify the integration.
Full step-by-step: iOS SDK Integration (recommended.
- Follow §3 — Requirements & running the demo and record a trip on a real device.
- Read Session & App Lifecycle Management — the most common source of integration bugs.
- Trace
AppDelegatearming:configureAPI→ login → account fetch →ScopeSDK.start/authenticated. - Open the diagnostics files (§8) and export a log bundle to see exactly what support receives.
- Support: [email protected]
- The SDKs are proprietary and require licensing; this app is provided for integration reference only.
Questions? Contact the Scope Mobile SDK team.