Discover & Connect

Adding MetaWear to a Project

This tutorial examines the dependencies and permissions needed for the SDK.

Section 1

What's Ahead

In this tutorial series, we will tour the SDK by writing small logic layer use case components for an app that collects sensor data.

Next, we’ll train and implement a CoreML activity classifier for writing letters in the air. (Yes, at some point we will YMCA.)

Bird art

Step 1

If you wish, you can clone a companion demo app, Streamy, that implements the components we build in this series.

Screenshot of Streamy on macOS and iOS

Step 2

This tutorial will not cover UI or write every line of an app. Our focus is simply the ins and outs of this SDK.

Streamy happens to use SwiftUI, but this tutorial series is UI framework agnostic.

StreamyApp.swift

@main
struct MacApp: App {


    @NSApplicationDelegateAdaptor private var app: AppDelegate


    var body: some Scene {
        MainWindowScene(factory: .init(root: app.root))
    }
}


@main
struct iOSApp: App {


    @UIApplicationDelegateAdaptor private var app: AppDelegate


    var body: some Scene {
        MainScene(factory: .init(root: app.root))
    }
}

Step 3

You can also reference the source code of Metabase 5, our easy-mode app for collecting MetaWear sensor data.

Screenshot of MetaBase on macOS and iOS

Step 4

A final resource for tinkering is using the SDK’s integration test host to write one-off tests.

ExampleTests.swift

import XCTest
import Combine
import CoreBluetooth
@testable import MetaWear
@testable import MetaWearCpp
@testable import SwiftCombineSDKTestHost


class ExampleTests: XCTestCase {


    func test_DiscoversDeviceInfo() {
        TestDevices.useOnly(.metamotionS)


        connectNearbyMetaWear(timeout: .read) { metawear, exp, subs in
            let sut = metawear.info
            XCTAssertEqual(sut.model, .motionS)
            ...
            exp.fulfill()
        }
    }


    func test_Read_LogLength_WhenPopulated() {
        connectNearbyMetaWear(timeout: .download, useLogger: false) { metawear, exp, subs in
            // Prepare
            let log: some MWLoggable = .accelerometer(rate: .hz50, gravity: .g2)
            metawear.publish()
                .deleteLoggedEntries()
                .delay(for: 2, tolerance: 0, scheduler: metawear.bleQueue)
                .log(log)
                ._assertLoggers([log.signalName], metawear: metawear)
                .delay(for: 2, tolerance: 0, scheduler: metawear.bleQueue)


            // Act
                .read(.logLength)


            // Assert
                .handleEvents(receiveOutput: { output in
                    XCTAssertGreaterThan(output.value, 1)
                })


            // Cleanup
                .map { _ in metawear }
                .command(.resetActivities)
                .deleteLoggedEntries()
                ._sinkNoFailure(&subs, receiveValue: { _ in  exp.fulfill() })
        }
    }
}
No preview
Screenshot of Streamy on macOS and iOS
Section 2

Swift Package Manager & Permissions

You can add the Swift Combine SDK to your project using Xcode’s native package dependency manager.

Bird art

Step 1

In the File menu, choose Add Package… https://github.com/mbientlab/MetaWear-Swift-Combine-SDK

By default, Xcode will update the MetaWear package when minor versions are released, but not for a major release. You can also depend on a branch head or a commit.

Xcode screenshot while adding a package

Step 2

In this tutorial, we’ll use all frameworks except the one to update device Firmware.

Xcode screenshot while selecting frameworks

Step 3

Xcode will likely only add these frameworks to one of your targets. Remedy this by tapping the + icon inside the Frameworks, Libraries and Embedded Content section to add the same frameworks as above.

.

Step 4

Don’t forget to set the two Info.plist Bluetooth privacy usage descriptions. Your app will not function otherwise.

Xcode screenshot of an Info.plist with two Privacy - Bluetooth keys

Step 5

For macOS, match permissions to this screenshot. Ensure App Sandbox -> Bluetooth and iCloud -> CloudKit permissions are enabled.

Xcode screenshot while adding Capabilities selections

Step 6

For iOS, we need to check different boxes. Add Background Modes and check Uses Bluetooth LE accessories. Add iCloud, checking Key value storage.

Xcode screenshot while adding Capabilities selections
Xcode screenshot while adding a package
Section 3

The Core SDK Classes

We’ll use three core classes: MetaWear, MetaWearScanner, and MetaWearSyncStore.

When debugging, the MWConsoleLogger can visualize Bluetooth communications.

Bird art

Step 1

Let’s look at how we could wire up the SDK inside an app’s root object.

Root.swift

import Foundation


class Root {






    init() {


    }


    func start() {


    }
}
No preview

Step 2

The MetaWearScanner abstracts the CoreBluetooth framework for you, finding nearby MetaWear devices. The sharedRestore singleton enables CoreBluetooth to recognize this app and vend it previously used peripherals.

Root.swift

import Foundation
import MetaWear


class Root {
No preview

Step 3

To persist names, capabilities, and other data about previously connected MetaWears, the scanner and other SDK components use UserDefaults storage.

FYI - You can inspect the keys used and specify the container at UserDefaults.MetaWear.suite. By default, the SDK uses .standard.

Root.swift

    let scanner: MetaWearScanner


    private let localDefaults: UserDefaults


    init() {
        self.scanner = .sharedRestore
No preview

Step 4

Apple randomizes Bluetooth device identifiers between devices. It also blocks inspecting advertised MAC addresses.

The MetaWearSyncStore allows you to stably identify MetaWears across devices by saving de-identifying metadata to iCloud key value storage, including a device’s name, serial number, and sensor capabilities. If you choose to use this feature, obtain and manage MetaWears exclusively through the sync store, rather than the Scanner.

Root.swift

import Foundation
import MetaWear
import MetaWearSync


class Root {
No preview

Step 5

An iCloud key value store must be synchronized at launch to obtain the latest values.

Root.swift

    func start() {
        do {
            _ = cloudDefaults.synchronize()
            try syncedDevices.load()

No preview

Step 6

When debugging, you might find the MWConsoleLogger helpful. If you set activateConsoleLoggingOnAllMetaWears to true at startup, all MetaWears will report activities (e.g., Bluetooth packets) to the console in Debug mode.

Tip If you wish to log just one MetaWear, assign a delegate conforming to MWConsoleLoggerDelegate to its logDelegate property.

Root.swift

        self.localDefaults = .standard
        self.cloudDefaults = .default
        MWConsoleLogger.activateConsoleLoggingOnAllMetaWears = true
        self.deviceLoader = MetaWeariCloudSyncLoader(localDefaults, cloudDefaults)
        self.syncedDevices = .init(scanner: scanner, loader: deviceLoader)
No preview
Root.swift

import Foundation


class Root {






    init() {


    }


    func start() {


    }
}
Next

Connecting to MetaWears

Learn how Streamy finds and manages both nearby and cloud-synced MetaWears.

Get started