Skip to content

iOS

🚀 Getting Started

Hardware Connection Guide

Before starting your development, please check out our FAQ for details on turning the device on/off, understanding the LED indicators, and device pairing procedures across all platforms: Device Setup & FAQ.

System Requirements

  • OS: iOS 12.0 or higher.
  • Architecture: Must compile for arm64 (required for physical device debugging).
  • Compiler Settings: Because the SDK relies on certain low-level C++ libraries, you must disable BitCode in your Xcode build settings (Enable Bitcode = No).
  • IDE: Xcode 12.0 or higher.

Installation

You can integrate the SDK into your project using either CocoaPods (Recommended) or by manually embedding the Framework.

Add our private spec repository and the CocoaPods public CDN to the top of your Podfile, then declare the ZenliteSDK dependency:

ruby
source 'https://github.com/BrainCoTech/cocoapods-specs.git'
source 'https://cdn.cocoapods.org/'

use_frameworks!
inhibit_all_warnings!

platform :ios, '12.0'

target 'YourApp' do
  pod 'ZenliteSDK', '=1.5.2'
end

Run pod install to install the dependency.

Option 2: Manual Integration

  1. Download the latest ZenliteSDK.framework from the developer portal or GitHub releases.
  2. In Xcode, navigate to your project settings → GeneralFrameworks, Libraries, and Embedded Content.
  3. Click the + button, add ZenliteSDK.framework, and ensure it's set to Embed & Sign.
Required System Frameworks

If you choose manual integration, you'll also need to explicitly link the following native system frameworks:

  • Accelerate.framework (For matrix math and signal filtering)
  • CoreBluetooth.framework (For BLE communication)
  • libc++.tbd (For C++ standard library support)

Configure Info.plist (Privacy Permissions)

Apple requires apps to declare Bluetooth usage. Add the following keys to your Info.plist:

xml
<!-- Required: Explains why the app needs Bluetooth -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app requires Bluetooth to connect to BrainCo devices.</string>

<!-- Optional: Required only if you need to keep the BLE connection alive in the background -->
<key>UIBackgroundModes</key>
<array>
    <string>bluetooth-central</string>
</array>

Core API Reference

💡 SDK Architecture

The iOS SDK consists of two main parts:

  1. ZenliteSDK (Imported via dependency): The closed-source core computation engine that parses raw Bluetooth packets, runs signal filters, and calculates all algorithm indicators.
  2. BLE Communication Layer: The code managing Bluetooth scanning, connecting, and sending commands. The full source code for this layer is open-source in our example repository, allowing you to heavily customize scanning behaviors and connection lifecycles.

BLEDeviceManager (Class)

The central manager for BLE scanning operations.

swift
@objc public class BLEDeviceManager: NSObject {
    // Shared singleton instance
    @objc public static let shared = BLEDeviceManager()
    
    // Delegate to receive scanning events
    @objc public weak var scannerDelegate: ZenliteScannerDelegate?
    
    // Start or stop scanning for nearby Zenlite devices
    @objc public func startScan()
    @objc public func stopScan()
}

ZenliteScannerDelegate (Protocol)

swift
@objc public protocol ZenliteScannerDelegate: NSObjectProtocol {
    // Triggered when the iPhone's Bluetooth state changes (e.g., powered off/on)
    @objc optional func centralManagerDidUpdateState(_ state: CBManagerState)
    
    // Triggered when compatible devices are found
    @objc optional func onFoundDevices(_ devices: Array<ZenliteDevice>)
}

ZenliteDevice (Class)

Represents a single physical device. This class handles the actual connection and sensor data streams.

swift
@objc public class ZenliteDevice: NSObject {
    // Delegate to receive all data flow and state changes
    @objc public weak var delegate: ZenliteDelegate?
    
    // Establish or tear down the BLE socket
    @objc public func connect()
    @objc public func disconnect()
    
    // Complete authentication and bonding after the physical connection is established
    @objc public func pair(requireAuth: Bool, onResponse: @escaping ZenliteRespCallback)
    
    // --- Sensor Switches ---
    @objc public func startEEGStream(sampleRate: EEGDataSampleRate, onResponse: @escaping ZenliteRespCallback)
    @objc public func stopEEGStream(onResponse: @escaping ZenliteRespCallback)
    
    @objc public func startIMU(sampleRate: IMUDataSampleRate, mode: IMUDataMode, onResponse: @escaping ZenliteRespCallback)
    @objc public func stopIMU(onResponse: @escaping ZenliteRespCallback)
    
    @objc public func startPPG(reportRate: PPGDataReportRate, mode: PPGDataMode, rawSetReg: Int, rawSetVal: Int, onResponse: @escaping ZenliteRespCallback)
    @objc public func stopPPG(onResponse: @escaping ZenliteRespCallback)
    
    // --- Device Settings ---
    @objc public func setDeviceName(_ name: String, onResponse: @escaping ZenliteRespCallback)
    @objc public func setSleepIdleTime(_ timeSec: Int, onResponse: @escaping ZenliteRespCallback)
    
    // --- System Queries ---
    @objc public func getSystemInfo(onResponse: @escaping ZenliteRespCallback)
}

ZenliteDelegate (Protocol)

Your app should implement this protocol (usually in a ViewModel or ViewController) to subscribe to incoming hardware events and data.

swift
@objc public protocol ZenliteDelegate {
    // === Connection and System States ===
    @objc optional func onError(_ error: ZenliteError)
    @objc optional func onDeviceInfoReady(_ deviceInfo: DeviceInfo)
    @objc optional func onSystemInfo(_ systemInfo: SystemInfo)
    @objc optional func onBatteryLevelChange(_ batteryLevel: Int)
    @objc optional func onConnectivityChange(_ connectivity: Connectivity)
    
    // === Physical Contact & Orientation ===
    @objc optional func onContactStateChange(_ contactState: ContactState)
    @objc optional func onOrientationChange(_ orientation: Orientation)
    
    // === Raw Sensor Data ===
    @objc optional func onEEGData(_ eeg: EEG)
    @objc optional func onRawEEGData(_ eegData: EEGData)
    @objc optional func onBrainWave(_ wave: BrainWave)
    @objc optional func onIMUData(_ imu: IMU)
    @objc optional func onPPGData(_ ppgData: PPGData)
    
    // === Computed Algorithm Variables ===
    @objc optional func onAttention(_ attention: Float, weightedAttention: Float)
    @objc optional func onMeditation(_ meditation: Float, calmness: Float, awareness: Float)
    @objc optional func onSleepStage(_ sleepStage: SleepStage, confidence: Float, drowsiness: Float)
    
    // === Event Triggers ===
    @objc optional func onEvent(_ event: DeviceEvent)
    @objc optional func onBlink()
}

Data Models

EEG (Brainwave Data)

swift
@objc public class EEG: NSObject {
    @objc public let sequenceNumber: Int
    @objc public let sampleRate: Float      // e.g., 256.0 Hz
    @objc public let eegData: [Float]       // Raw microvolt values
}

@objc public class BrainWave: NSObject {
    @objc public let delta: Double      // 0.5-4 Hz
    @objc public let theta: Double      // 4-8 Hz
    @objc public let alpha: Double      // 8-13 Hz
    @objc public let lowBeta: Double    // 13-20 Hz
    @objc public let highBeta: Double   // 20-30 Hz
    @objc public let gamma: Double      // 30-50 Hz
}

IMU (Motion Data)

swift
@objc public class IMU: NSObject {
    @objc public let sampleRate: Float
    @objc public let accData: [Float]   // Accelerometer [x, y, z]
    @objc public let gyroData: [Float]  // Gyroscope [x, y, z]
    @objc public let eulerAngleData: [Float]  // Euler Angles [yaw, pitch, roll]
}

PPG (Optical Heart Rate Data)

swift
@objc public class PPGData: NSObject {
    @objc public let sequenceNumber: Int
    @objc public let sampleRate: Float
    @objc public let rawData: Array<PPGRawData>
    @objc public let algoData: Array<PPGAlgoData>
    @objc public let respiratoryRate: Float
}

@objc public class PPGAlgoData: NSObject {
    @objc public let hr: Float       // Heart Rate (bpm)
    @objc public let hrConf: Int     // Confidence (0-100%)
    @objc public let rr: Float       // Respiration rate
    @objc public let spo2: Float     // Blood Oxygen Saturation
    @objc public let spo2Conf: Int   // Confidence (0-100%)
    @objc public let hrv: Float      // Heart Rate Variability
}

Enums & Constants

Connectivity and Physical Contact

swift
@objc public enum Connectivity: Int {
    case connecting = 0
    case connected = 1
    case disconnecting = 2
    case disconnected = 3
}

@objc public enum ContactState: Int {
    case unknown = 0
    case off = 1          // Not worn correctly
    case eeg = 2          // Good EEG contact
    case all = 3          // Both EEG and PPG have good contact
}

@objc public enum Orientation: Int {
    case unknown = 0
    case upward = 1       // Worn normally
    case downward = 2     // Worn upside down
}

Sleep & Respiration

swift
@objc public enum SleepStage: Int {
    case unknown = -1
    case awake = 0
    case rem = 1          // REM sleep
    case light = 2        // Light sleep
    case deep = 3         // Deep sleep
}

@objc public enum RespiratoryState: Int {
    case rest = 0         // Resting state
    case inward = 1       // Inhaling
    case outward = 2      // Exhaling
}

Stream Configuration

swift
@objc public enum EEGDataSampleRate: Int {
    case off = 1
    case sr128 = 2
    case sr256 = 3        // 256 Hz
}

@objc public enum IMUDataMode: Int {
    case none = 0
    case acc = 1          // Accelerometer only
    case gyro = 2         // Gyroscope only
    case accGyro = 3      // Accelerometer + Gyroscope
    case euler = 4        // Derived Euler Angles
}

@objc public enum PPGDataMode: Int {
    case rawData = 1      // Raw optical counts only
    case algo = 2         // Computed algorithms (HR, HRV, SpO2)
}

Troubleshooting & Best Practices

Connection Issues

If you're having trouble discovering or connecting to the device, be sure to check the global troubleshooting section: 👉 Device Setup & FAQ

CocoaPods pod install Stalls

If pod install hangs or fails on your machine:

  1. Ensure your CocoaPods toolchain is up to date (sudo gem install cocoapods).
  2. Run pod cache clean --all and pod deintegrate, then try pod install again.

iOS Best Practices

  • Update UI on the Main Thread: All SDK data callbacks (like onEEGData) run on an asynchronous background queue supplied by CoreBluetooth. If you plan to update UI elements (like a realtime brainwave chart or a connection status label), you must dispatch those updates back to the main thread (DispatchQueue.main.async or @MainActor in Swift concurrency). Failure to do so will cause UI freezes or crashes.
  • Prevent Memory Leaks (Retain Cycles): When assigning closures or delegates that listen to Bluetooth events, always use [weak self] or weak var assignments. Bluetooth manager lifecycles often outlive a single UIViewController, which makes strong reference cycles very easy to create.
  • Battery & Background Usage: Devices transmitting at 256 Hz generate significant data throughput. When the app moves to the background, call device.stopEEGStream() to stop the data stream and conserve battery on both the iPhone and the headset. Unless you have enabled bluetooth-central background modes in your Info.plist, iOS will terminate your Bluetooth connections approximately 5–10 seconds after the app enters the background.

📖 Complete iOS Source Example

The API reference above outlines the tools available in the SDK. To help you tie it all together, we've provided a complete open-source UIKit implementation.

This example app demonstrates:

  • Scanning and pairing logic using CoreBluetooth combined with the SDK.
  • Managing iOS Bluetooth permission lifecycles correctly.
  • Rendering high-frequency physiological data (like EEG waves) smoothly on the main thread using charting libraries like DGCharts.

Check out the full example codebase here:

👉 OxyZen iOS Example Repository (Swift / ObjC)