通过 Swift BLE 连接到 Metawear

Connecting to Metawear via Swift BLE

我正在使用 Metawear Motion R 板。我想读取加速度计和陀螺仪数据。虽然制造商确实提供了一个 C++ SDK,我可以用它来读取这些数据,但我发现它不稳定并且在出现问题时很难调试。结果,我只想通过 Swift.

读取加速度计和陀螺仪数据

如果我要使用他们的 SDK,下面的代码会让我调用和配置设备:

// configure accelerometer
mbl_mw_acc_set_odr(self.connectedDevice?.board, 50.0);
mbl_mw_acc_set_range(self.connectedDevice?.board, 4.0);
mbl_mw_acc_write_acceleration_config(self.connectedDevice?.board);

// configure gyroscope
mbl_mw_gyro_bmi160_set_odr(self.connectedDevice?.board, MBL_MW_GYRO_BMI160_ODR_50Hz);
mbl_mw_gyro_bmi160_set_range(self.connectedDevice?.board, MBL_MW_GYRO_BMI160_RANGE_125dps);
mbl_mw_gyro_bmi160_write_config(self.connectedDevice?.board);

// read the accelerometer
let acc_signal = mbl_mw_acc_get_acceleration_data_signal(self.connectedDevice?.board);
mbl_mw_datasignal_subscribe(acc_signal, bridge(obj: self)) { (context, data) in
    let obj: MblMwCartesianFloat = data!.pointee.valueAs();
    let _self: ExerciseViewController = bridge(ptr: context!);
    _self.add_imu_records(record: ImuRecord(x: obj.x, y: obj.y, z: obj.z, time: Date()), isAccelerometer: true);
}
// read the gyroscope
let gyro_signal = mbl_mw_gyro_bmi160_get_rotation_data_signal(self.connectedDevice?.board);
mbl_mw_datasignal_subscribe(gyro_signal, bridge(obj: self)) { (context, data) in
    let obj: MblMwCartesianFloat = data!.pointee.valueAs();
    let _self: ExerciseViewController = bridge(ptr: context!);
    _self.add_imu_records(record: ImuRecord(x: obj.x, y: obj.y, z: obj.z, time: Date()), isAccelerometer: false);
}

mbl_mw_acc_enable_acceleration_sampling(self.connectedDevice?.board);
mbl_mw_acc_start(self.connectedDevice?.board);
mbl_mw_gyro_bmi160_enable_rotation_sampling(self.connectedDevice?.board);
mbl_mw_gyro_bmi160_start(self.connectedDevice?.board);

要仅使用 Swift 开始实施配置和日志记录逻辑,我遵循两个指南:Decoding metawear , Core BLE Tutorial

我下面的代码库将连接到 MetaWear 设备并查看不同的特性。

import Foundation
import CoreBluetooth

public class BleCommunicator : NSObject, CBCentralManagerDelegate {
    
    private let metaWearService = CBUUID(string: "326A9000-85CB-9195-D9DD-464CFBBAE75A")
    private let metaWearConfigurationCharacteristic = CBUUID(string: "326A9001-85CB-9195-D9DD-464CFBBAE75A")
    private let metawearDataCharacteristic = CBUUID(string: "326A9006-85CB-9195-D9DD-464CFBBAE75A")
    
    private var centralManager : CBCentralManager!
    private var myDevice: CBPeripheral!
    
    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    public func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            centralManager.scanForPeripherals(withServices: nil)
        case .unknown:
            print("### check ble connection is unknown");
        case .resetting:
            print("### check ble connection is resetting");
        case .unsupported:
            print("### check ble connection is unsupported");
        case .unauthorized:
            print("### check ble connection is unauthorized");
        case .poweredOff:
            print("### check ble connection is poweredOff");
        @unknown default:
            print("### check ble connection");
        }
    }

    public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
                        advertisementData: [String: Any], rssi RSSI: NSNumber) {
        if (peripheral.name == "MetaWear") {
            myDevice = peripheral
            myDevice.delegate = self
            centralManager.stopScan()
            centralManager.connect(myDevice)
        }
    }
    
    public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        myDevice.discoverServices([metaWearService])
    }
}

extension BleCommunicator : CBPeripheralDelegate {

    public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }
        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
    
    public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }
        for characteristic in characteristics {
            if (characteristic.uuid == metawearDataCharacteristic) {
                peripheral.readValue(for: characteristic)
                peripheral.setNotifyValue(true, for: characteristic)
            }
        }
    }
    
    public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        switch characteristic.uuid {
        case metawearDataCharacteristic:
            sensor(from: characteristic)
        default:
            print("Unhandled Characteristic UUID: \(characteristic.uuid)")
        }
    }
    
    private func sensor(from characteristic: CBCharacteristic) {
        guard let characteristicData = characteristic.value,
        let _ = characteristicData.first else { return }
        let data = String(bytes: characteristicData, encoding: .utf8)
        print("### we have this:: \(data) ");
    }
}

一次 运行,传感器功能显示一个空的 20 字节数组。我怀疑这是因为在读取数据之前需要配置设备。 (正如我在 C++ 代码块中所做的那样)第一个教程提到通过 metaWearConfigurationCharacteristic 将预格式化的模式发送到 metawear 板,如下所示,但我找不到任何文档来复制它:

Input - 13032600, where 13 is gyro module, 03 is config opcode, 26 is output data rate (38 hz), 00 is angular data range.

问题: 如何配置和读取加速度计和陀螺仪传感器,如使用 C++ 代码所示,使用 Swift?

我对 C++ SDK 进行了逆向工程,因此,我编写了这段代码来初始化设备:

public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    guard let characteristics = service.characteristics else { return }
    for characteristic in characteristics {
        if (characteristic.uuid == metawearDataCharacteristic) {
            peripheral.readValue(for: characteristic)
            peripheral.setNotifyValue(true, for: characteristic)
        } else if (characteristic.uuid == metaWearConfigurationCharacteristic) {
            let configurationSeq : [[UInt8]] = createInitSequence()
            for seq in configurationSeq {
                peripheral.writeValue(Data(seq), for: characteristic, type: CBCharacteristicWriteType.withoutResponse)
            }
        }
    }
}

private func createInitSequence() -> [[UInt8]]  {
    let sequence : [[UInt8]] = [[3, 4, 1], [19, 5, 1],
                                [3, 2, 1, 0], [3, 1, 1],
                                [19, 2, 1,0], [19, 1, 1]]
    return sequence;
}

为了读取通知上的数据,我更新了下面的函数如下:

private let factorAcceleration: Float = 8192.0000
private let factorGyroscope: Float = 262.399994

private func sensor(from characteristic: CBCharacteristic) {
    guard let characteristicData = characteristic.value,
    let _ = characteristicData.first else { return }
    let data = characteristic.value!
    var values = [UInt8](repeating: 0, count: data.count)
    data.copyBytes(to: &values, count: data.count)

    if (values[0] == 3) {
        // this is the accelerometer
        let xAxis = Float(Int16(values[2..<4])) / factorAcceleration
        let yAxis = Float(Int16(values[4..<6])) / factorAcceleration
        let zAxis = Float(Int16(values[6..<8])) / factorAcceleration
        print("accelerometer x=\(xAxis), y=\(yAxis), z=\(zAxis)");
    }
    if (values[0] == 19) {
        // this is the gyroscope
        let xAxis = Float(Int16(values[2..<4])) / factorGyroscope
        let yAxis = Float(Int16(values[4..<6])) / factorGyroscope
        let zAxis = Float(Int16(values[6..<8])) / factorGyroscope
        print("gyroscope x=\(xAxis), y=\(yAxis), z=\(zAxis)");
    }
    
}

从一个,我把unit8字节解析成了一个float。此代码将允许此转换:

extension Numeric {
    init<D: DataProtocol>(_ data: D) {
        var value: Self = .zero
        let size = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: [=12=])} )
        assert(size == MemoryLayout.size(ofValue: value))
        self = value
    }
}