Compare commits
1 commit
master
...
puppetModu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6d10619ff |
BIN
.DS_Store
vendored
Normal file
26
.github/workflows/build.yml
vendored
|
|
@ -1,26 +0,0 @@
|
||||||
name: Build App
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-stable:
|
|
||||||
name: Build Bluefruit in stable Xcode
|
|
||||||
runs-on: macos-13
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
xcode: ['15.0.1']
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Select ${{ matrix.xcode }}
|
|
||||||
run: |
|
|
||||||
sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app
|
|
||||||
xcode-select -p
|
|
||||||
env:
|
|
||||||
XCODE_VERSION: ${{ matrix.xcode }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: xcodebuild -scheme BluefruitPlayground -workspace BluefruitPlayground.xcworkspace -destination "generic/platform=iOS" -configuration Release build CODE_SIGNING_ALLOWED=NO
|
|
||||||
|
|
@ -28,8 +28,6 @@
|
||||||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
||||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>Puppet module requires access to the camera</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1130"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1120"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1500"
|
LastUpgradeVersion = "1130"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
||||||
BIN
BluefruitPlayground/.DS_Store
vendored
Normal file
|
|
@ -1,97 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitAccelerometer.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 25/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitAccelerometerServiceUUID = CBUUID(string: "ADAF0200-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitAccelerometerCharacteristicUUID = CBUUID(string: "ADAF0201-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitAccelerometerVersion = 1
|
|
||||||
|
|
||||||
// Structs
|
|
||||||
/// Acceleration in m/s²
|
|
||||||
struct AccelerometerValue {
|
|
||||||
var x: Float
|
|
||||||
var y: Float
|
|
||||||
var z: Float
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitAccelerometerCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitAccelerometerCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitAccelerometerCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitAccelerometerCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitAccelerometerEnable(responseHandler: @escaping(Result<(AccelerometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitAccelerometerVersion, serviceUuid: BlePeripheral.kAdafruitAccelerometerServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitAccelerometerCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
if let value = self.adafruitAccelerometerDataToAcceleromterValue(data) {
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
} else {
|
|
||||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
}
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitAccelerometerCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitAccelerometerCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitAccelerometerIsEnabled() -> Bool {
|
|
||||||
return adafruitAccelerometerCharacteristic != nil && adafruitAccelerometerCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitAccelerometerDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitAccelerometerCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitAccelerometerCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitAccelerometerLastValue() -> AccelerometerValue? {
|
|
||||||
guard let data = adafruitAccelerometerCharacteristic?.value else { return nil }
|
|
||||||
return adafruitAccelerometerDataToAcceleromterValue(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitAccelerometerDataToAcceleromterValue(_ data: Data) -> AccelerometerValue? {
|
|
||||||
|
|
||||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
|
||||||
guard bytes.count >= 3 else { return nil }
|
|
||||||
return AccelerometerValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitBarometricPressure.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitBarometricPressureServiceUUID = CBUUID(string: "ADAF0800-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitBarometricPressureCharacteristicUUID = CBUUID(string: "ADAF0801-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitBarometricPressureVersion = 1
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitBarometricPressureCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitBarometricPressureCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitBarometricPressureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitBarometricPressureVersion, serviceUuid: BlePeripheral.kAdafruitBarometricPressureServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitBarometricPressureCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
let value = self.adafruitBarometricPressureDataToFloat(data)
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitBarometricPressureCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitBarometricPressureCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitBarometricPressureIsEnabled() -> Bool {
|
|
||||||
return adafruitBarometricPressureCharacteristic != nil && adafruitBarometricPressureCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitBarometricPressureDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitBarometricPressureCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitBarometricPressureCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitBarometricPressureLastValue() -> Float? {
|
|
||||||
guard let data = adafruitBarometricPressureCharacteristic?.value else { return nil }
|
|
||||||
return adafruitBarometricPressureDataToFloat(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitBarometricPressureDataToFloat(_ data: Data) -> Float {
|
|
||||||
return data.toFloatFrom32Bits()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitButtons.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 15/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitButtonsServiceUUID = CBUUID(string: "ADAF0600-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitButtonsCharacteristicUUID = CBUUID(string: "ADAF0601-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitButtonsVersion = 1
|
|
||||||
|
|
||||||
enum SlideSwitchState: Int32 {
|
|
||||||
case right = 0
|
|
||||||
case left = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ButtonState: Int32 {
|
|
||||||
case released = 0
|
|
||||||
case pressed = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ButtonsState {
|
|
||||||
var slideSwitch: SlideSwitchState
|
|
||||||
var buttonA: ButtonState
|
|
||||||
var buttonB: ButtonState
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitButtonsCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitButtonsCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitButtonsCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitButtonsCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
let timePeriod: TimeInterval = 0 // 0 means that the responseHandler will be called only when there is a change
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitButtonsVersion, serviceUuid: BlePeripheral.kAdafruitButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitButtonsCharacteristicUUID, timePeriod: timePeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
let state = self.adafruitButtonsDataToStateMask(data)
|
|
||||||
responseHandler(.success((state, uuid)))
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitButtonsCharacteristic = characteristic
|
|
||||||
|
|
||||||
if timePeriod == 0 { // Read initial state if the timePeriod is 0 (update only when changed)
|
|
||||||
self.adafruitButtonsReadState { response in
|
|
||||||
switch response {
|
|
||||||
case .success:
|
|
||||||
completion?(.success(()))
|
|
||||||
case .failure(let error):
|
|
||||||
DLog("Error receiving initial button state data: \(error)")
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitButtonsCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitButtonsIsEnabled() -> Bool {
|
|
||||||
return adafruitButtonsCharacteristic != nil && adafruitButtonsCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitButtonsDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitButtonsCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitButtonsCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
|
||||||
guard let adafruitButtonsCharacteristic = adafruitButtonsCharacteristic else {
|
|
||||||
completion(.failure(PeripheralAdafruitError.invalidCharacteristic))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readCharacteristic(adafruitButtonsCharacteristic) { [weak self] (data, error) in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
guard error == nil, let data = data as? Data else {
|
|
||||||
completion(.failure(error ?? PeripheralAdafruitError.invalidResponseData))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = self.adafruitButtonsDataToStateMask(data)
|
|
||||||
completion(.success((state, self.identifier)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitButtonsLastValue() -> ButtonsState? {
|
|
||||||
guard let data = adafruitButtonsCharacteristic?.value else { return nil }
|
|
||||||
return adafruitButtonsDataToStateMask(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitButtonsDataToStateMask(_ data: Data) -> ButtonsState {
|
|
||||||
let stateMask = data.toInt32From32Bits()
|
|
||||||
|
|
||||||
let slideSwitchBit = stateMask & 0b1
|
|
||||||
let slideSwitchState = SlideSwitchState(rawValue: slideSwitchBit)!
|
|
||||||
|
|
||||||
let buttonABit = ( stateMask >> 1 ) & 0b1
|
|
||||||
let buttonAState = ButtonState(rawValue: buttonABit)!
|
|
||||||
|
|
||||||
let buttonBBit = ( stateMask >> 2 ) & 0b1
|
|
||||||
let buttonBState = ButtonState(rawValue: buttonBBit)!
|
|
||||||
|
|
||||||
return ButtonsState(slideSwitch: slideSwitchState, buttonA: buttonAState, buttonB: buttonBState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitColorSensor.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitColorSensorServiceUUID = CBUUID(string: "ADAF0A00-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitColorSensorCharacteristicUUID = CBUUID(string: "ADAF0A01-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitColorSensorVersion = 1
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitColorSensorCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitColorSensorCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitColorSensorCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitColorSensorCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitColorSensorEnable(responseHandler: @escaping(Result<(UIColor, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitColorSensorVersion, serviceUuid: BlePeripheral.kAdafruitColorSensorServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitColorSensorCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
if let value = self.adafruitColorSensorDataToColor(data) {
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
}
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitColorSensorCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitColorSensorCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitColorSensorIsEnabled() -> Bool {
|
|
||||||
return adafruitColorSensorCharacteristic != nil && adafruitColorSensorCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitColorSensorDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitColorSensorCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitColorSensorCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitColorSensorLastValue() -> UIColor? {
|
|
||||||
guard let data = adafruitColorSensorCharacteristic?.value else { return nil }
|
|
||||||
return adafruitColorSensorDataToColor(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitColorSensorDataToColor(_ data: Data) -> UIColor? {
|
|
||||||
guard let components = adafruitDataToUInt16Array(data) else { return nil }
|
|
||||||
guard components.count >= 3 else { return nil }
|
|
||||||
return UIColor(red: CGFloat(components[0])/CGFloat(UInt16.max), green: CGFloat(components[1])/CGFloat(UInt16.max), blue: CGFloat(components[2])/CGFloat(UInt16.max), alpha: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripehral+AdafruitCommon.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 13/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Costants
|
|
||||||
private static let kAdafruitMeasurementPeriodCharacteristicUUID = CBUUID(string: "ADAF0001-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitServiceVersionCharacteristicUUID = CBUUID(string: "ADAF0002-C332-42A8-93BD-25E905756CB8")
|
|
||||||
|
|
||||||
private static let kAdafruitDefaultVersionValue = 1 // Used as default version value if version characteristic cannot be read
|
|
||||||
|
|
||||||
static let kAdafruitSensorDefaultPeriod: TimeInterval = 0.2
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Errors
|
|
||||||
enum PeripheralAdafruitError: Error {
|
|
||||||
case invalidCharacteristic
|
|
||||||
case enableNotifyFailed
|
|
||||||
case disableNotifyFailed
|
|
||||||
case unknownVersion
|
|
||||||
case invalidResponseData
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Service Actions
|
|
||||||
func adafruitServiceEnable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<(Int, CBCharacteristic), Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [unowned self] (characteristic, error) in
|
|
||||||
guard let characteristic = characteristic, error == nil else {
|
|
||||||
completion?(.failure(error ?? PeripheralAdafruitError.invalidCharacteristic))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check version
|
|
||||||
self.adafruitVersion(serviceUuid: serviceUuid) { version in
|
|
||||||
completion?(.success((version, characteristic)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitServiceEnableIfVersion(version expectedVersion: Int, serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<CBCharacteristic, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnable(serviceUuid: serviceUuid, mainCharacteristicUuid: mainCharacteristicUuid) { [weak self] result in
|
|
||||||
self?.checkVersionResult(expectedVersion: expectedVersion, result: result, completion: completion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
- parameters:
|
|
||||||
- timePeriod: seconds between measurements. -1 to disable measurements
|
|
||||||
|
|
||||||
*/
|
|
||||||
func adafruitServiceEnableIfVersion(version expectedVersion: Int, serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, timePeriod: TimeInterval?, responseHandler: @escaping(Result<(Data, UUID), Error>) -> Void, completion: ((Result<CBCharacteristic, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
adafruitServiceEnableIfVersion(version: expectedVersion, serviceUuid: serviceUuid, mainCharacteristicUuid: mainCharacteristicUuid) { [weak self] result in
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic): // Version supported
|
|
||||||
self?.adafruitServiceSetRepeatingResponse(characteristic: characteristic, timePeriod: timePeriod, responseHandler: responseHandler, completion: { result in
|
|
||||||
|
|
||||||
completion?(.success(characteristic))
|
|
||||||
})
|
|
||||||
|
|
||||||
case let .failure(error): // Unsupported version (or error)
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func adafruitServiceSetRepeatingResponse(characteristic: CBCharacteristic, timePeriod: TimeInterval?, responseHandler: @escaping(Result<(Data, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
// Prepare notification handler
|
|
||||||
let notifyHandler: ((Error?) -> Void)? = { [unowned self] error in
|
|
||||||
guard error == nil else {
|
|
||||||
responseHandler(.failure(error!))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let data = characteristic.value {
|
|
||||||
responseHandler(.success((data, self.identifier)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh period handler
|
|
||||||
let enableNotificationsHandler = {
|
|
||||||
// Enable notifications
|
|
||||||
if !characteristic.isNotifying {
|
|
||||||
self.enableNotify(for: characteristic, handler: notifyHandler, completion: { error in
|
|
||||||
guard error == nil else {
|
|
||||||
completion?(.failure(error!))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard characteristic.isNotifying else {
|
|
||||||
completion?(.failure(PeripheralAdafruitError.enableNotifyFailed))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.updateNotifyHandler(for: characteristic, handler: notifyHandler)
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time period
|
|
||||||
if let timePeriod = timePeriod, let serviceUuid = characteristic.service?.uuid { // Set timePeriod if not nil
|
|
||||||
|
|
||||||
self.adafruitSetPeriod(timePeriod, serviceUuid: serviceUuid) { _ in
|
|
||||||
|
|
||||||
if Config.isDebugEnabled {
|
|
||||||
// Check period
|
|
||||||
self.adafruitPeriod(serviceUuid: serviceUuid) { period in
|
|
||||||
guard period != nil else { DLog("Error setting service period"); return }
|
|
||||||
//DLog("service period: \(period!)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enableNotificationsHandler()
|
|
||||||
}
|
|
||||||
} else { // Use default timePeriod
|
|
||||||
enableNotificationsHandler()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func checkVersionResult(expectedVersion: Int, result: Result<(Int, CBCharacteristic), Error>, completion: ((Result<CBCharacteristic, Error>) -> Void)?) {
|
|
||||||
switch result {
|
|
||||||
case let .success((version, characteristic)):
|
|
||||||
guard version == expectedVersion else {
|
|
||||||
DLog("Warning: adafruitServiceEnableIfVersion unknown version: \(version). Expected: \(expectedVersion)")
|
|
||||||
completion?(.failure(PeripheralAdafruitError.unknownVersion))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(characteristic))
|
|
||||||
case let .failure(error):
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitServiceDisable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [weak self] (characteristic, error) in
|
|
||||||
guard let characteristic = characteristic, error == nil else {
|
|
||||||
completion?(.failure(error ?? PeripheralAdafruitError.invalidCharacteristic))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let kDisablePeriod: TimeInterval = -1 // -1 means taht the updates will be disabled
|
|
||||||
self?.adafruitSetPeriod(kDisablePeriod, serviceUuid: serviceUuid) { [weak self] result in
|
|
||||||
// Disable notifications
|
|
||||||
if characteristic.isNotifying {
|
|
||||||
self?.disableNotify(for: characteristic) { error in
|
|
||||||
guard error == nil else {
|
|
||||||
completion?(.failure(error!))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard !characteristic.isNotifying else {
|
|
||||||
completion?(.failure(PeripheralAdafruitError.disableNotifyFailed))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
completion?(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitVersion(serviceUuid: CBUUID, completion: @escaping(Int) -> Void) {
|
|
||||||
self.characteristic(uuid: BlePeripheral.kAdafruitServiceVersionCharacteristicUUID, serviceUuid: serviceUuid) { [weak self] (characteristic, error) in
|
|
||||||
|
|
||||||
// Check if version characteristic exists or return default value
|
|
||||||
guard error == nil, let characteristic = characteristic else {
|
|
||||||
completion(BlePeripheral.kAdafruitDefaultVersionValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the version
|
|
||||||
self?.readCharacteristic(characteristic) { (result, error) in
|
|
||||||
guard error == nil, let data = result as? Data, data.count >= 4 else {
|
|
||||||
completion(BlePeripheral.kAdafruitDefaultVersionValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let version = data.toIntFrom32Bits()
|
|
||||||
completion(version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitPeriod(serviceUuid: CBUUID, completion: @escaping(TimeInterval?) -> Void) {
|
|
||||||
self.characteristic(uuid: BlePeripheral.kAdafruitMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
|
|
||||||
|
|
||||||
guard error == nil, let characteristic = characteristic else {
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.readCharacteristic(characteristic) { (data, error) in
|
|
||||||
guard error == nil, let data = data as? Data else {
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let period = TimeInterval(data.toIntFrom32Bits()) / 1000.0
|
|
||||||
completion(period)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Set measurement period
|
|
||||||
|
|
||||||
- parameters:
|
|
||||||
- period: seconds between measurements. -1 to disable measurements
|
|
||||||
|
|
||||||
*/
|
|
||||||
func adafruitSetPeriod(_ period: TimeInterval, serviceUuid: CBUUID, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.characteristic(uuid: BlePeripheral.kAdafruitMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
|
|
||||||
|
|
||||||
guard error == nil, let characteristic = characteristic else {
|
|
||||||
DLog("Error: adafruitSetPeriod: \(String(describing: error))")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let periodMillis = period == -1 ? -1 : Int32(period * 1000) // -1 means disable measurements. It is a special value
|
|
||||||
let data = periodMillis.littleEndian.data
|
|
||||||
self.write(data: data, for: characteristic, type: .withResponse) { error in
|
|
||||||
guard error == nil else {
|
|
||||||
DLog("Error: adafruitSetPeriod \(error!)")
|
|
||||||
completion?(.failure(error!))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
func adafruitDataToFloatArray(_ data: Data) -> [Float]? {
|
|
||||||
let unitSize = MemoryLayout<Float32>.stride
|
|
||||||
var bytes = [Float32](repeating: 0, count: data.count / unitSize)
|
|
||||||
(data as NSData).getBytes(&bytes, length: data.count * unitSize)
|
|
||||||
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitDataToUInt16Array(_ data: Data) -> [UInt16]? {
|
|
||||||
let unitSize = MemoryLayout<UInt16>.stride
|
|
||||||
var words = [UInt16](repeating: 0, count: data.count / unitSize)
|
|
||||||
(data as NSData).getBytes(&words, length: data.count * unitSize)
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitDataToInt16Array(_ data: Data) -> [Int16]? {
|
|
||||||
let unitSize = MemoryLayout<Int16>.stride
|
|
||||||
var words = [Int16](repeating: 0, count: data.count / unitSize)
|
|
||||||
(data as NSData).getBytes(&words, length: data.count * unitSize)
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitGyroscope.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitGyroscopeServiceUUID = CBUUID(string: "ADAF0400-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitGyroscopeCharacteristicUUID = CBUUID(string: "ADAF0401-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitGyroscopeVersion = 1
|
|
||||||
|
|
||||||
// Structs
|
|
||||||
/// Values in rad/s
|
|
||||||
struct GyroscopeValue {
|
|
||||||
var x: Float
|
|
||||||
var y: Float
|
|
||||||
var z: Float
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitGyroscopeCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitGyroscopeCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitGyroscopeCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitGyroscopeCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitGyroscopeEnable(responseHandler: @escaping(Result<(GyroscopeValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitGyroscopeVersion, serviceUuid: BlePeripheral.kAdafruitGyroscopeServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitGyroscopeCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
if let value = self.adafruitGyroscopeDataToGyroscopeValue(data) {
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
} else {
|
|
||||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
}
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitGyroscopeCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitGyroscopeCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitGyroscopeIsEnabled() -> Bool {
|
|
||||||
return adafruitGyroscopeCharacteristic != nil && adafruitGyroscopeCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitGyroscopeDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitGyroscopeCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitGyroscopeCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitGyroscopeLastValue() -> GyroscopeValue? {
|
|
||||||
guard let data = adafruitGyroscopeCharacteristic?.value else { return nil }
|
|
||||||
return adafruitGyroscopeDataToGyroscopeValue(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitGyroscopeDataToGyroscopeValue(_ data: Data) -> GyroscopeValue? {
|
|
||||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
|
||||||
guard bytes.count >= 3 else { return nil }
|
|
||||||
return GyroscopeValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitHumidity.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitHumidityServiceUUID = CBUUID(string: "ADAF0700-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitHumidityCharacteristicUUID = CBUUID(string: "ADAF0701-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitHumidityVersion = 1
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitHumidityCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitHumidityCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitHumidityEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitHumidityVersion, serviceUuid: BlePeripheral.kAdafruitHumidityServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitHumidityCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
let value = self.adafruitHumidityDataToFloat(data)
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitHumidityCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitHumidityCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitHumidityIsEnabled() -> Bool {
|
|
||||||
return adafruitHumidityCharacteristic != nil && adafruitHumidityCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitHumidityDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitHumidityCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitHumidityCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitHumidityLastValue() -> Float? {
|
|
||||||
guard let data = adafruitHumidityCharacteristic?.value else { return nil }
|
|
||||||
return adafruitHumidityDataToFloat(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitHumidityDataToFloat(_ data: Data) -> Float {
|
|
||||||
return data.toFloatFrom32Bits()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitLight.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 13/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitLightCharacteristicUUID = CBUUID(string: "ADAF0301-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitLightVersion = 1
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitLightCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitLightCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitLightCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitLightCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitLightVersion, serviceUuid: BlePeripheral.kAdafruitLightServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitLightCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
let value = self.adafruitLightDataToFloat(data)
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitLightCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitLightCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitLightIsEnabled() -> Bool {
|
|
||||||
return adafruitLightCharacteristic != nil && adafruitLightCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitLightDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitLightCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitLightCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitLightLastValue() -> Float? {
|
|
||||||
guard let data = adafruitLightCharacteristic?.value else { return nil }
|
|
||||||
return adafruitLightDataToFloat(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitLightDataToFloat(_ data: Data) -> Float {
|
|
||||||
return data.toFloatFrom32Bits()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitMagnetometer.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitMagnetometerServiceUUID = CBUUID(string: "ADAF0500-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitMagnetometerCharacteristicUUID = CBUUID(string: "ADAF0501-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitMagnetometerVersion = 1
|
|
||||||
|
|
||||||
// Structs
|
|
||||||
/// Values in microTesla (μT)
|
|
||||||
struct MagnetometerValue {
|
|
||||||
var x: Float
|
|
||||||
var y: Float
|
|
||||||
var z: Float
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitMagnetometerCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitMagnetometerCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitMagnetometerCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitMagnetometerCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitMagnetometerEnable(responseHandler: @escaping(Result<(MagnetometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitMagnetometerVersion, serviceUuid: BlePeripheral.kAdafruitMagnetometerServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitMagnetometerCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
if let value = self.adafruitMagnetometerDataToMagnetometerValue(data) {
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
} else {
|
|
||||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
}
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitMagnetometerCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitMagnetometerCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitMagnetometerIsEnabled() -> Bool {
|
|
||||||
return adafruitMagnetometerCharacteristic != nil && adafruitMagnetometerCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitMagnetometerDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitMagnetometerCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitMagnetometerCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitMagnetometerLastValue() -> MagnetometerValue? {
|
|
||||||
guard let data = adafruitMagnetometerCharacteristic?.value else { return nil }
|
|
||||||
return adafruitMagnetometerDataToMagnetometerValue(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitMagnetometerDataToMagnetometerValue(_ data: Data) -> MagnetometerValue? {
|
|
||||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
|
||||||
guard bytes.count >= 3 else { return nil }
|
|
||||||
return MagnetometerValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitNeoPixels.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 25/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Config
|
|
||||||
private static let kAdafruitNeoPixelsServiceNumberOfBitsPerPixel = 3
|
|
||||||
private static let kAdafruitNeoPixelsVersion = 1
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitNeoPixelsServiceUUID = CBUUID(string: "ADAF0900-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitNeoPixelsDataCharacteristicUUID = CBUUID(string: "ADAF0903-C332-42A8-93BD-25E905756CB8")
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitNeoPixelsDataCharacteristic: CBCharacteristic?
|
|
||||||
static var adafruitNeoPixelsDataValue: Data?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitNeoPixelsDataCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitNeoPixelsDataValue: Data {
|
|
||||||
get {
|
|
||||||
if let data = objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataValue) as? Data {
|
|
||||||
return data
|
|
||||||
} else { // Initial value
|
|
||||||
return Data(repeating: 0, count: adafruitNeoPixelsCount * BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitNeoPixelsDataValue, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitNeoPixelsEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitNeoPixelsVersion, serviceUuid: BlePeripheral.kAdafruitNeoPixelsServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitNeoPixelsDataCharacteristicUUID) { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitNeoPixelsDataCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitNeoPixelsDataCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var adafruitNeoPixelsCount: Int {
|
|
||||||
return self.adafruitManufacturerData()?.boardModel?.neoPixelsCount ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelsIsEnabled() -> Bool {
|
|
||||||
return adafruitNeoPixelsDataCharacteristic != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelsDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
adafruitNeoPixelsDataCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelSetAllPixelsColor(_ color: UIColor) {
|
|
||||||
let colors = [UIColor](repeating: color, count: adafruitNeoPixelsCount)
|
|
||||||
adafruitNeoPixelsWriteData(offset: 0, colors: colors)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelSetPixelColor(index: Int, color: UIColor) {
|
|
||||||
let offset = UInt16(index * BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
|
||||||
adafruitNeoPixelsWriteData(offset: offset, colors: [color])
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelSetColor(index: UInt, color: UIColor, pixelMask: [Bool]) {
|
|
||||||
guard let pixelData = pixelDataFromColorMask(color: color, pixelMask: pixelMask) else {
|
|
||||||
DLog("Error neopixelSetColor invalid color data")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let offset = UInt16(index * UInt(BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel))
|
|
||||||
adafruitNeoPixelsWriteData(offset: offset, pixelData: pixelData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Low level actions
|
|
||||||
func adafruitNeoPixelsWriteData(offset: UInt16, colors: [UIColor]) {
|
|
||||||
let pixelData = BlePeripheral.pixelDataFromColors(colors)
|
|
||||||
adafruitNeoPixelsWriteData(offset: offset, pixelData: pixelData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelsWriteData(offset: UInt16, pixelData: Data) {
|
|
||||||
guard let adafruitNeoPixelsDataCharacteristic = adafruitNeoPixelsDataCharacteristic else { return }
|
|
||||||
|
|
||||||
enum Flags: UInt8 {
|
|
||||||
case save = 0
|
|
||||||
case flush = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
let flags = Flags.flush
|
|
||||||
|
|
||||||
let data = offset.littleEndian.data + flags.rawValue.littleEndian.data + pixelData
|
|
||||||
// self.write(data: data, for: cpbPixelsDataCharacteristic, type: .withResponse)
|
|
||||||
self.write(data: data, for: adafruitNeoPixelsDataCharacteristic, type: .withResponse) { [unowned self] error in
|
|
||||||
guard error == nil else { DLog("Error adafruitNeoPixelsWriteData: \(error!)"); return }
|
|
||||||
|
|
||||||
self.adafruitNeoPixelsDataValue = pixelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func pixelDataFromColorMask(color: UIColor, pixelMask: [Bool]) -> Data? {
|
|
||||||
let colorData = BlePeripheral.pixelDataFromColor(color)
|
|
||||||
|
|
||||||
var pixelData = Data()
|
|
||||||
for (i, mask) in pixelMask.enumerated() {
|
|
||||||
if mask { // overwrite color
|
|
||||||
pixelData += colorData
|
|
||||||
} else { // use current color
|
|
||||||
let existingColorData: Data
|
|
||||||
let byteOffset = i * BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel
|
|
||||||
DLog("adafruitNeoPixelsDataValue.count: \(adafruitNeoPixelsDataValue.count) ")
|
|
||||||
if byteOffset < adafruitNeoPixelsDataValue.count {
|
|
||||||
existingColorData = Data(adafruitNeoPixelsDataValue[byteOffset..<(byteOffset + BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)])
|
|
||||||
} else {
|
|
||||||
existingColorData = Data(repeating: 0, count: BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
|
||||||
}
|
|
||||||
pixelData += existingColorData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pixelData
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func pixelDataFromColors(_ colors: [UIColor]) -> Data {
|
|
||||||
var pixelData = Data()
|
|
||||||
|
|
||||||
for color in colors {
|
|
||||||
pixelData += pixelDataFromColor(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pixelData
|
|
||||||
}
|
|
||||||
|
|
||||||
static func pixelDataFromColor(_ color: UIColor) -> Data {
|
|
||||||
let bytes = pixelUInt8FromColor(color)
|
|
||||||
return bytes.data
|
|
||||||
}
|
|
||||||
|
|
||||||
static func pixelUInt8FromColor(_ color: UIColor) -> [UInt8] {
|
|
||||||
var pixelBytes: [UInt8]?
|
|
||||||
|
|
||||||
let cgColor = color.cgColor
|
|
||||||
let numComponents = cgColor.numberOfComponents
|
|
||||||
if let components = cgColor.components {
|
|
||||||
if numComponents == 2 {
|
|
||||||
let white = UInt8(components[0] * 255)
|
|
||||||
//let alpha = UInt8(components[1] * 255)
|
|
||||||
|
|
||||||
pixelBytes = [white, white, white]
|
|
||||||
} else if numComponents == 4 {
|
|
||||||
|
|
||||||
let r = UInt8(components[0] * 255)
|
|
||||||
let g = UInt8(components[1] * 255)
|
|
||||||
let b = UInt8(components[2] * 255)
|
|
||||||
//let alpha = UInt8(components[3] * 255)
|
|
||||||
|
|
||||||
pixelBytes = [g, r, b]
|
|
||||||
} else {
|
|
||||||
DLog("Error converting color (number of components is: \(numComponents))")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pixelBytes ?? [UInt8](repeating: 0, count: BlePeripheral.kAdafruitNeoPixelsServiceNumberOfBitsPerPixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitQuaternion.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 25/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitQuaternionServiceUUID = CBUUID(string: "ADAF0D00-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitQuaternionCharacteristicUUID = CBUUID(string: "ADAF0D01-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitQuaternionCalibrationInCharacteristicUUID = CBUUID(string: "ADAFD002-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitQuaternionCalibrationOutCharacteristicUUID = CBUUID(string: "ADAFD003-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitQuaternionVersion = 1
|
|
||||||
|
|
||||||
// Structs
|
|
||||||
struct QuaternionValue {
|
|
||||||
var x: Float
|
|
||||||
var y: Float
|
|
||||||
var z: Float
|
|
||||||
var w: Float
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitQuaternionCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitQuaternionCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitQuaternionCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitQuaternionCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitQuaternionEnable(responseHandler: @escaping(Result<(QuaternionValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitQuaternionVersion, serviceUuid: BlePeripheral.kAdafruitQuaternionServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitQuaternionCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
if let value = self.adafruitQuaternionDataToQuaternionValue(data) {
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
} else {
|
|
||||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
}
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitQuaternionCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitQuaternionCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitQuaternionIsEnabled() -> Bool {
|
|
||||||
return adafruitQuaternionCharacteristic != nil && adafruitQuaternionCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitQuaternionDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitQuaternionCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitQuaternionCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitQuaternionLastValue() -> QuaternionValue? {
|
|
||||||
guard let data = adafruitQuaternionCharacteristic?.value else { return nil }
|
|
||||||
return adafruitQuaternionDataToQuaternionValue(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitQuaternionDataToQuaternionValue(_ data: Data) -> QuaternionValue? {
|
|
||||||
|
|
||||||
guard let bytes = adafruitDataToFloatArray(data) else { return nil }
|
|
||||||
guard bytes.count >= 4 else { return nil }
|
|
||||||
//return QuaternionValue(x: bytes[0], y: bytes[1], z: bytes[2], w: bytes[3])
|
|
||||||
return QuaternionValue(x: bytes[1], y: bytes[2], z: bytes[3], w: bytes[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitSoundSensor.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 09/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitSoundSensorServiceUUID = CBUUID(string: "ADAF0B00-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitSoundSamplesCharacteristicUUID = CBUUID(string: "ADAF0B01-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitSoundNumberOfChannelsCharacteristicUUID = CBUUID(string: "ADAF0B02-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitSoundSensorVersion = 1
|
|
||||||
|
|
||||||
static let kAdafruitSoundSensorMaxAmplitude = 32768 // Int16 range is from -32768 to 32767
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitSoundCharacteristic: CBCharacteristic?
|
|
||||||
static var adafruitSoundNumChannels: Int = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitSoundCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitSoundNumChannels: Int {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundNumChannels) as? Int ?? 0
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitSoundNumChannels, newValue, .OBJC_ASSOCIATION_ASSIGN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitSoundEnable(responseHandler: @escaping(Result<([Double], UUID), Error>) -> Void, completion: ((Result<Int, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitSoundSensorVersion, serviceUuid: BlePeripheral.kAdafruitSoundSensorServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitSoundSamplesCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { [weak self] response in
|
|
||||||
|
|
||||||
guard self?.adafruitSoundNumChannels ?? 0 > 0 else { return } // Ignore received data until sound channels are defined
|
|
||||||
// TODO: read sound channels BEFORE enabling notify
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
if let value = self?.adafruitSoundDataToAmplitudePerChannel(data) {
|
|
||||||
responseHandler(.success((value, uuid)))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
responseHandler(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
}
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitSoundCharacteristic = characteristic
|
|
||||||
|
|
||||||
// Read number of channels
|
|
||||||
self.characteristic(uuid: BlePeripheral.kAdafruitSoundNumberOfChannelsCharacteristicUUID, serviceUuid: BlePeripheral.kAdafruitSoundSensorServiceUUID) { [weak self] (characteristic, error) in
|
|
||||||
|
|
||||||
guard error == nil, let characteristic = characteristic else {
|
|
||||||
self?.adafruitSoundDisable() // Error, disable sound // TODO: dont enable until checks have been performed
|
|
||||||
completion?(.failure(PeripheralAdafruitError.invalidCharacteristic))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self?.readCharacteristic(characteristic) { (result, error) in
|
|
||||||
guard error == nil, let data = result as? Data, data.count >= 1 else {
|
|
||||||
DLog("Error reading numChannels: \(error?.localizedDescription ?? "")")
|
|
||||||
completion?(.failure(PeripheralAdafruitError.invalidResponseData))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let numChannels = Int(data[0]) // from 0 to 100
|
|
||||||
self?.adafruitSoundNumChannels = numChannels
|
|
||||||
completion?(.success(numChannels))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitSoundCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitSoundIsEnabled() -> Bool {
|
|
||||||
return adafruitSoundCharacteristic != nil && adafruitSoundCharacteristic!.isNotifying && adafruitSoundNumChannels > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitSoundDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitSoundCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitSoundCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitSoundLastAmplitudePerChannel() -> [Double]? { // Samples fo reach channel
|
|
||||||
guard let data = adafruitSoundCharacteristic?.value else { return nil }
|
|
||||||
return adafruitSoundDataToAmplitudePerChannel(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
/**
|
|
||||||
Convert raw data into the amplitude for each channel
|
|
||||||
- returns: array with amplitude for each channel measured in decibel relative to full scale (dBFS)
|
|
||||||
*/
|
|
||||||
private func adafruitSoundDataToAmplitudePerChannel(_ data: Data) -> [Double]? {
|
|
||||||
guard let samples = adafruitDataToInt16Array(data) else { return nil }
|
|
||||||
let numChannels = adafruitSoundNumChannels
|
|
||||||
guard numChannels > 0, samples.count >= numChannels else { return nil }
|
|
||||||
|
|
||||||
var samplesSumPerChannel = [Double](repeating: 0, count: numChannels)
|
|
||||||
for (index, sample) in samples.enumerated() {
|
|
||||||
let channelIndex = index % numChannels
|
|
||||||
samplesSumPerChannel[channelIndex] += abs(Double(sample))
|
|
||||||
}
|
|
||||||
|
|
||||||
let samplesPerChannel = samples.count / numChannels
|
|
||||||
var amplitudePerChannel = [Double](repeating: 0, count: numChannels)
|
|
||||||
for (index, samplesSum) in samplesSumPerChannel.enumerated() {
|
|
||||||
let samplesAvg = samplesSum / Double(samplesPerChannel)
|
|
||||||
|
|
||||||
// Calculate amplitude
|
|
||||||
// based on: https://devzone.nordicsemi.com/f/nordic-q-a/28248/get-amplitude-db-from-pdm/111560#111560
|
|
||||||
let amplitude = 20 * log10(abs(samplesAvg) / Double(BlePeripheral.kAdafruitSoundSensorMaxAmplitude))
|
|
||||||
|
|
||||||
// Note:
|
|
||||||
// The base 10 log of -1 is NaN.
|
|
||||||
// The base 10 log of 0 is -Infinity.
|
|
||||||
|
|
||||||
amplitudePerChannel[index] = amplitude
|
|
||||||
}
|
|
||||||
|
|
||||||
return amplitudePerChannel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
//
|
|
||||||
// AdafruitTemperature.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 13/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitTemperatureVersion = 1
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitTemperatureCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitTemperatureCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitTemperatureVersion, serviceUuid: BlePeripheral.kAdafruitTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitTemperatureCharacteristicUUID, timePeriod: BlePeripheral.kAdafruitSensorDefaultPeriod, responseHandler: { response in
|
|
||||||
|
|
||||||
switch response {
|
|
||||||
case let .success((data, uuid)):
|
|
||||||
let temperature = self.adafruitTemperatureDataToFloat(data)
|
|
||||||
responseHandler(.success((temperature, uuid)))
|
|
||||||
case let .failure(error):
|
|
||||||
responseHandler(.failure(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
}, completion: { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitTemperatureCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitTemperatureCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitTemperatureIsEnabled() -> Bool {
|
|
||||||
return adafruitTemperatureCharacteristic != nil && adafruitTemperatureCharacteristic!.isNotifying
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitTemperatureDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
defer {
|
|
||||||
adafruitTemperatureCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable notify
|
|
||||||
guard let characteristic = adafruitTemperatureCharacteristic, characteristic.isNotifying else { return }
|
|
||||||
disableNotify(for: characteristic)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitTemperatureLastValue() -> Float? {
|
|
||||||
guard let data = adafruitTemperatureCharacteristic?.value else { return nil }
|
|
||||||
return adafruitTemperatureDataToFloat(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utils
|
|
||||||
private func adafruitTemperatureDataToFloat(_ data: Data) -> Float {
|
|
||||||
return data.toFloatFrom32Bits()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitToneGenerator.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 18/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
static let kAdafruitToneGeneratorServiceUUID = CBUUID(string: "ADAF0C00-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitToneGeneratorCharacteristicUUID = CBUUID(string: "ADAF0C01-C332-42A8-93BD-25E905756CB8")
|
|
||||||
private static let kAdafruitToneGeneratorVersion = 1
|
|
||||||
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitToneGeneratorCharacteristic: CBCharacteristic?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitToneGeneratorCharacteristic: CBCharacteristic? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitToneGeneratorCharacteristic) as? CBCharacteristic
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitToneGeneratorCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitToneGeneratorEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
self.adafruitServiceEnableIfVersion(version: BlePeripheral.kAdafruitToneGeneratorVersion, serviceUuid: BlePeripheral.kAdafruitToneGeneratorServiceUUID, mainCharacteristicUuid: BlePeripheral.kAdafruitToneGeneratorCharacteristicUUID) { result in
|
|
||||||
switch result {
|
|
||||||
case let .success(characteristic):
|
|
||||||
self.adafruitToneGeneratorCharacteristic = characteristic
|
|
||||||
completion?(.success(()))
|
|
||||||
|
|
||||||
case let .failure(error):
|
|
||||||
self.adafruitToneGeneratorCharacteristic = nil
|
|
||||||
completion?(.failure(error))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitToneGeneratorIsEnabled() -> Bool {
|
|
||||||
return adafruitToneGeneratorCharacteristic != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitToneGeneratorDisable() {
|
|
||||||
// Clear all specific data
|
|
||||||
adafruitToneGeneratorCharacteristic = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitToneGeneratorStartPlaying(frequency: UInt16, duration: UInt32 = 0) { // Duration 0 means non-stop
|
|
||||||
guard let adafruitToneGeneratorCharacteristic = adafruitToneGeneratorCharacteristic else { return }
|
|
||||||
|
|
||||||
let data = frequency.littleEndian.data + duration.littleEndian.data
|
|
||||||
self.write(data: data, for: adafruitToneGeneratorCharacteristic, type: .withResponse)
|
|
||||||
//DLog("tone: \(frequency)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+ManufacturerAdafruit.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 10/12/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Constants
|
|
||||||
internal static let kManufacturerAdafruitIdentifier: [UInt8] = [0x22, 0x08]
|
|
||||||
|
|
||||||
// MARK: - Check Manufacturer
|
|
||||||
func isManufacturerAdafruit() -> Bool {
|
|
||||||
guard let manufacturerIdentifier = advertisement.manufacturerIdentifier else { return false }
|
|
||||||
|
|
||||||
let manufacturerIdentifierBytes = [UInt8](manufacturerIdentifier)
|
|
||||||
//DLog("\(name) manufacturer: \(advertisement.manufacturerString)")
|
|
||||||
return manufacturerIdentifierBytes == BlePeripheral.kManufacturerAdafruitIdentifier
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Adafruit Specific Data
|
|
||||||
struct AdafruitManufacturerData {
|
|
||||||
// Types
|
|
||||||
enum BoardModel: CaseIterable {
|
|
||||||
case circuitPlaygroundBluefruit
|
|
||||||
case clue_nRF52840
|
|
||||||
case feather_nRF52840_express
|
|
||||||
case feather_nRF52832
|
|
||||||
|
|
||||||
var identifier: [[UInt8]] { // Board identifiers used on the advertisement packet (USB PID)
|
|
||||||
switch self {
|
|
||||||
case .circuitPlaygroundBluefruit: return [[0x45, 0x80], [0x46, 0x80]]
|
|
||||||
case .clue_nRF52840: return [[0x71, 0x80], [0x72, 0x80]]
|
|
||||||
case .feather_nRF52840_express: return [[0x29, 0x80], [0x2A, 0x80]]
|
|
||||||
case .feather_nRF52832: return [[0x60, 0xEA]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var neoPixelsCount: Int {
|
|
||||||
switch self {
|
|
||||||
case .circuitPlaygroundBluefruit: return 10
|
|
||||||
case .clue_nRF52840: return 1
|
|
||||||
case .feather_nRF52840_express: return 0
|
|
||||||
case .feather_nRF52832: return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data
|
|
||||||
var color: UIColor?
|
|
||||||
var boardModel: BoardModel?
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
static func board(withBoardTypeData data: Data) -> BoardModel? {
|
|
||||||
let bytes = [UInt8](data)
|
|
||||||
|
|
||||||
let board = BoardModel.allCases.first(where: {
|
|
||||||
$0.identifier.contains(bytes)
|
|
||||||
})
|
|
||||||
|
|
||||||
return board
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitManufacturerData() -> AdafruitManufacturerData? {
|
|
||||||
guard let manufacturerData = advertisement.manufacturerData else { return nil }
|
|
||||||
guard manufacturerData.count > 2 else { return nil } // It should have fields beyond the manufacturer identifier
|
|
||||||
|
|
||||||
var manufacturerFieldsData = Data(manufacturerData.dropFirst(2)) // Remove manufacturer identifier
|
|
||||||
|
|
||||||
var adafruitManufacturerData = AdafruitManufacturerData()
|
|
||||||
|
|
||||||
// Parse fields
|
|
||||||
let kHeaderLength = 1 + 2 // 1 byte len + 2 bytes key
|
|
||||||
while manufacturerFieldsData.count >= kHeaderLength {
|
|
||||||
// Parse current field
|
|
||||||
guard let fieldKey = Int16(data: manufacturerFieldsData[1...2]) else { return nil }
|
|
||||||
let fieldDataLenght = Int(manufacturerFieldsData[0]) - kHeaderLength // don't count header
|
|
||||||
let fieldData: Data
|
|
||||||
if manufacturerFieldsData.count >= kHeaderLength + fieldDataLenght {
|
|
||||||
fieldData = Data(manufacturerFieldsData[kHeaderLength...])
|
|
||||||
} else {
|
|
||||||
fieldData = Data()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode field
|
|
||||||
switch fieldKey {
|
|
||||||
case 0 where fieldData.count >= 3: // Color
|
|
||||||
let r = fieldData[0]
|
|
||||||
let g = fieldData[1]
|
|
||||||
let b = fieldData[2]
|
|
||||||
adafruitManufacturerData.color = UIColor(red: CGFloat(r)/255, green: CGFloat(g)/255, blue: CGFloat(b)/255, alpha: 1)
|
|
||||||
|
|
||||||
case 1 where fieldData.count >= 2: // Board type
|
|
||||||
let boardTypeData = fieldData[0..<2]
|
|
||||||
|
|
||||||
if let board = AdafruitManufacturerData.board(withBoardTypeData: boardTypeData) {
|
|
||||||
adafruitManufacturerData.boardModel = board
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
DLog("Warning: unknown board type found: \([UInt8](boardTypeData))")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
DLog("Error processing manufacturer data with key: \(fieldKey) len: \(fieldData.count) expectedLen: \(fieldDataLenght)")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove processed field
|
|
||||||
manufacturerFieldsData = Data(manufacturerFieldsData.dropFirst(3 + fieldDataLenght))
|
|
||||||
}
|
|
||||||
|
|
||||||
return adafruitManufacturerData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,47 +8,43 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreBluetooth
|
import CoreBluetooth
|
||||||
import QuartzCore
|
|
||||||
|
|
||||||
public class BleManager: NSObject {
|
#if COMMANDLINE
|
||||||
|
#else
|
||||||
|
import MSWeakTimer
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class BleManager: NSObject {
|
||||||
// Configuration
|
// Configuration
|
||||||
private static let kStopScanningWhenConnectingToPeripheral = false
|
private static let kStopScanningWhenConnectingToPeripheral = false
|
||||||
private static let kAlwaysAllowDuplicateKeys = true
|
private static let kAlwaysAllowDuplicateKeys = true
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
public static let shared = BleManager()
|
static let shared = BleManager()
|
||||||
|
|
||||||
// Ble
|
// Ble
|
||||||
var centralManager: CBCentralManager?
|
var centralManager: CBCentralManager?
|
||||||
|
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
|
||||||
|
|
||||||
// Scanning
|
// Scanning
|
||||||
public var isScanning: Bool {
|
var isScanning = false
|
||||||
return scanningStartTime != nil
|
|
||||||
}
|
|
||||||
public var scanningElapsedTime: TimeInterval? {
|
|
||||||
guard let scanningStartTime = scanningStartTime else { return nil }
|
|
||||||
return CACurrentMediaTime() - scanningStartTime
|
|
||||||
}
|
|
||||||
private var isScanningWaitingToStart = false
|
private var isScanningWaitingToStart = false
|
||||||
internal var scanningStartTime: TimeInterval? // Time when the scanning started. nil if stopped
|
|
||||||
private var scanningServicesFilter: [CBUUID]?
|
private var scanningServicesFilter: [CBUUID]?
|
||||||
internal var peripheralsFound = [UUID: BlePeripheral]()
|
internal var peripheralsFound = [UUID: BlePeripheral]()
|
||||||
private var peripheralsFoundFirstTime = [UUID: Date]() // Date that the perihperal was discovered for the first time. Useful for sorting
|
private var peripheralsFoundLock = NSLock()
|
||||||
internal var peripheralsFoundLock = NSLock()
|
|
||||||
|
|
||||||
// Connecting
|
// Connecting
|
||||||
private var connectionTimeoutTimers = [UUID: Foundation.Timer]()
|
private var connectionTimeoutTimers = [UUID: MSWeakTimer]()
|
||||||
private var autoreconnectOnDisconnection = Set<UUID>() // List of peripheral IDs to automatically reconnect if disconnected
|
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
public enum NotificationUserInfoKey: String {
|
enum NotificationUserInfoKey: String {
|
||||||
case uuid = "uuid"
|
case uuid = "uuid"
|
||||||
case error = "error"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
centralManagerPoweredOnSemaphore.wait()
|
||||||
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background), options: [:])
|
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background), options: [:])
|
||||||
// centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [:])
|
// centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [:])
|
||||||
}
|
}
|
||||||
|
|
@ -56,16 +52,20 @@ public class BleManager: NSObject {
|
||||||
deinit {
|
deinit {
|
||||||
scanningServicesFilter?.removeAll()
|
scanningServicesFilter?.removeAll()
|
||||||
peripheralsFound.removeAll()
|
peripheralsFound.removeAll()
|
||||||
peripheralsFoundFirstTime.removeAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var state: CBManagerState {
|
public var state: CBManagerState {
|
||||||
return centralManager?.state ?? .unknown
|
return centralManager?.state ?? .unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreCentralManager() {
|
func restoreCentralManager() {
|
||||||
DLog("Restoring central manager")
|
DLog("Restoring central manager")
|
||||||
|
/*
|
||||||
|
guard centralManager?.delegate !== self else {
|
||||||
|
DLog("No need to restore it. It it still ours")
|
||||||
|
return
|
||||||
|
}*/
|
||||||
|
|
||||||
// Restore peripherals status
|
// Restore peripherals status
|
||||||
peripheralsFoundLock.lock()
|
peripheralsFoundLock.lock()
|
||||||
|
|
||||||
|
|
@ -94,9 +94,12 @@ public class BleManager: NSObject {
|
||||||
startScan()
|
startScan()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Scan
|
// MARK: - Scan
|
||||||
public func startScan(withServices services: [CBUUID]? = nil) {
|
func startScan(withServices services: [CBUUID]? = nil) {
|
||||||
|
centralManagerPoweredOnSemaphore.wait()
|
||||||
|
centralManagerPoweredOnSemaphore.signal()
|
||||||
|
|
||||||
isScanningWaitingToStart = true
|
isScanningWaitingToStart = true
|
||||||
guard let centralManager = centralManager, centralManager.state != .poweredOff && centralManager.state != .unauthorized && centralManager.state != .unsupported else {
|
guard let centralManager = centralManager, centralManager.state != .poweredOff && centralManager.state != .unauthorized && centralManager.state != .unsupported else {
|
||||||
DLog("startScan failed because central manager is not ready")
|
DLog("startScan failed because central manager is not ready")
|
||||||
|
|
@ -111,7 +114,7 @@ public class BleManager: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLog("start scan")
|
// DLog("start scan")
|
||||||
scanningStartTime = CACurrentMediaTime()
|
isScanning = true
|
||||||
NotificationCenter.default.post(name: .didStartScanning, object: nil)
|
NotificationCenter.default.post(name: .didStartScanning, object: nil)
|
||||||
|
|
||||||
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
|
let options = BleManager.kAlwaysAllowDuplicateKeys ? [CBCentralManagerScanOptionAllowDuplicatesKey: true] : nil
|
||||||
|
|
@ -119,55 +122,32 @@ public class BleManager: NSObject {
|
||||||
isScanningWaitingToStart = false
|
isScanningWaitingToStart = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stopScan() {
|
func stopScan() {
|
||||||
// DLog("stop scan")
|
// DLog("stop scan")
|
||||||
centralManager?.stopScan()
|
centralManager?.stopScan()
|
||||||
scanningStartTime = nil
|
isScanning = false
|
||||||
isScanningWaitingToStart = false
|
isScanningWaitingToStart = false
|
||||||
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func numPeripherals() -> Int {
|
func peripherals() -> [BlePeripheral] {
|
||||||
return peripheralsFound.count
|
|
||||||
}
|
|
||||||
|
|
||||||
public func peripherals() -> [BlePeripheral] {
|
|
||||||
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
||||||
return Array(peripheralsFound.values)
|
return Array(peripheralsFound.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheralsSortedByFirstDiscovery() -> [BlePeripheral] {
|
func connectedPeripherals() -> [BlePeripheral] {
|
||||||
let now = Date()
|
|
||||||
var peripheralsList = peripherals()
|
|
||||||
peripheralsList.sort { (p0, p1) -> Bool in
|
|
||||||
peripheralsFoundFirstTime[p0.identifier] ?? now < peripheralsFoundFirstTime[p1.identifier] ?? now
|
|
||||||
}
|
|
||||||
|
|
||||||
return peripheralsList
|
|
||||||
}
|
|
||||||
|
|
||||||
public func peripheralsSortedByRSSI() -> [BlePeripheral] {
|
|
||||||
var peripheralsList = peripherals()
|
|
||||||
peripheralsList.sort { (p0, p1) -> Bool in
|
|
||||||
return (p0.rssi ?? -127) > (p1.rssi ?? -127)
|
|
||||||
}
|
|
||||||
|
|
||||||
return peripheralsList
|
|
||||||
}
|
|
||||||
|
|
||||||
public func connectedPeripherals() -> [BlePeripheral] {
|
|
||||||
return peripherals().filter {$0.state == .connected}
|
return peripherals().filter {$0.state == .connected}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func connectingPeripherals() -> [BlePeripheral] {
|
func connectingPeripherals() -> [BlePeripheral] {
|
||||||
return peripherals().filter {$0.state == .connecting}
|
return peripherals().filter {$0.state == .connecting}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func connectedOrConnectingPeripherals() -> [BlePeripheral] {
|
func connectedOrConnectingPeripherals() -> [BlePeripheral] {
|
||||||
return peripherals().filter {$0.state == .connected || $0.state == .connecting}
|
return peripherals().filter {$0.state == .connected || $0.state == .connecting}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func refreshPeripherals() {
|
func refreshPeripherals() {
|
||||||
stopScan()
|
stopScan()
|
||||||
|
|
||||||
peripheralsFoundLock.lock()
|
peripheralsFoundLock.lock()
|
||||||
|
|
@ -175,7 +155,6 @@ public class BleManager: NSObject {
|
||||||
for (identifier, peripheral) in peripheralsFound {
|
for (identifier, peripheral) in peripheralsFound {
|
||||||
if peripheral.state != .connected && peripheral.state != .connecting {
|
if peripheral.state != .connected && peripheral.state != .connecting {
|
||||||
peripheralsFound.removeValue(forKey: identifier)
|
peripheralsFound.removeValue(forKey: identifier)
|
||||||
peripheralsFoundFirstTime.removeValue(forKey: identifier)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
peripheralsFoundLock.unlock()
|
peripheralsFoundLock.unlock()
|
||||||
|
|
@ -186,14 +165,12 @@ public class BleManager: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Connection Management
|
// MARK: - Connection Management
|
||||||
public func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
||||||
|
|
||||||
guard centralManager?.state == .poweredOn else {
|
centralManagerPoweredOnSemaphore.wait()
|
||||||
DLog("connect failed because central manager is not ready")
|
centralManagerPoweredOnSemaphore.signal()
|
||||||
return
|
|
||||||
}
|
// Stop scanning when connecting to a peripheral
|
||||||
|
|
||||||
// Stop scanning when connecting to a peripheral
|
|
||||||
if BleManager.kStopScanningWhenConnectingToPeripheral {
|
if BleManager.kStopScanningWhenConnectingToPeripheral {
|
||||||
stopScan()
|
stopScan()
|
||||||
}
|
}
|
||||||
|
|
@ -212,14 +189,14 @@ public class BleManager: NSObject {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if let timeout = timeout {
|
if let timeout = timeout {
|
||||||
self.connectionTimeoutTimers[peripheral.identifier] = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false)
|
connectionTimeoutTimers[peripheral.identifier] = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false, dispatchQueue: .global(qos: .background))
|
||||||
}
|
}
|
||||||
centralManager?.connect(peripheral.peripheral, options: options)
|
centralManager?.connect(peripheral.peripheral, options: options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func connectionTimeoutFired(timer: Foundation.Timer) {
|
@objc private func connectionTimeoutFired(timer: MSWeakTimer) {
|
||||||
let peripheralIdentifier = timer.userInfo as! UUID
|
let peripheralIdentifier = timer.userInfo() as! UUID
|
||||||
DLog("connection timeout for: \(peripheralIdentifier)")
|
DLog("connection timeout fired: \(peripheralIdentifier)")
|
||||||
connectionTimeoutTimers[peripheralIdentifier] = nil
|
connectionTimeoutTimers[peripheralIdentifier] = nil
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
||||||
|
|
@ -227,57 +204,37 @@ public class BleManager: NSObject {
|
||||||
if let blePeripheral = peripheralsFound[peripheralIdentifier] {
|
if let blePeripheral = peripheralsFound[peripheralIdentifier] {
|
||||||
centralManager?.cancelPeripheralConnection(blePeripheral.peripheral)
|
centralManager?.cancelPeripheralConnection(blePeripheral.peripheral)
|
||||||
} else {
|
} else {
|
||||||
//DLog("simulate disconnection")
|
DLog("simulate disconnection")
|
||||||
// The blePeripheral is available on peripheralsFound, so simulate the disconnection
|
// The blePeripheral is available on peripheralsFound, so simulate the disconnection
|
||||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
func disconnect(from peripheral: BlePeripheral) {
|
||||||
guard let centralManager = centralManager else { return}
|
|
||||||
|
|
||||||
DLog("disconnect: \(peripheral.identifier)")
|
DLog("disconnect")
|
||||||
autoreconnectOnDisconnection.remove(peripheral.identifier) // Disable autoreconnection because user initiated the disconection
|
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
|
centralManager?.cancelPeripheralConnection(peripheral.peripheral)
|
||||||
if waitForQueuedCommands {
|
|
||||||
// Send the disconnection to the command queue, so all the previous command are executed before disconnecting
|
|
||||||
peripheral.disconnect(centralManager: centralManager)
|
|
||||||
} else {
|
|
||||||
centralManager.cancelPeripheralConnection(peripheral.peripheral)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func discoverConnectedPeripherals(services: [CBUUID]) {
|
|
||||||
guard let centralManager = centralManager else { return}
|
|
||||||
|
|
||||||
let peripheralsWithServices = centralManager.retrieveConnectedPeripherals(withServices: services)
|
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||||
if !peripheralsWithServices.isEmpty {
|
var reconnecting = false
|
||||||
let alreadyConnectingOrConnectedPeripheralsIds = BleManager.shared.connectedOrConnectingPeripherals().map{$0.identifier}
|
|
||||||
for peripheral in peripheralsWithServices {
|
let knownPeripherals = centralManager?.retrievePeripherals(withIdentifiers: identifiers)
|
||||||
if !alreadyConnectingOrConnectedPeripheralsIds.contains(peripheral.identifier) {
|
if let peripherals = knownPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
|
||||||
DLog("Discovered peripheral with known service: \(peripheral.identifier)")
|
for peripheral in peripherals {
|
||||||
let advertisementData = [CBAdvertisementDataServiceUUIDsKey: services]
|
discovered(peripheral: peripheral)
|
||||||
discovered(peripheral: peripheral, advertisementData: advertisementData )
|
if let blePeripheral = peripheralsFound[peripheral.identifier] {
|
||||||
|
connect(to: blePeripheral, timeout: timeout)
|
||||||
|
reconnecting = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
let connectedPeripherals = centralManager?.retrieveConnectedPeripherals(withServices: services)
|
||||||
func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
if let peripherals = connectedPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
|
||||||
|
for peripheral in peripherals {
|
||||||
guard let centralManager = centralManager else { return false }
|
discovered(peripheral: peripheral)
|
||||||
var reconnecting = false
|
|
||||||
|
|
||||||
// Reconnect to a known identifier
|
|
||||||
let identifiers = peripheralsData.map({$0.identifier})
|
|
||||||
|
|
||||||
if !identifiers.isEmpty {
|
|
||||||
let peripheralsWithIdentifiers = centralManager.retrievePeripherals(withIdentifiers: identifiers)
|
|
||||||
for peripheral in peripheralsWithIdentifiers {
|
|
||||||
if let peripheralData = peripheralsData.first(where: {$0.identifier == peripheral.identifier}) {
|
|
||||||
DLog("Try to connect to known peripheral: \(peripheral.identifier)")
|
|
||||||
discovered(peripheral: peripheral, advertisementData: peripheralData.advertisementData)
|
|
||||||
if let blePeripheral = peripheralsFound[peripheral.identifier] {
|
if let blePeripheral = peripheralsFound[peripheral.identifier] {
|
||||||
connect(to: blePeripheral, timeout: timeout)
|
connect(to: blePeripheral, timeout: timeout)
|
||||||
reconnecting = true
|
reconnecting = true
|
||||||
|
|
@ -285,35 +242,17 @@ public class BleManager: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconnect even if no identifier was saved if we are already connected to a device with the expected services
|
|
||||||
let peripheralsWithServices = centralManager.retrieveConnectedPeripherals(withServices: services)
|
|
||||||
if !peripheralsWithServices.isEmpty {
|
|
||||||
let alreadyConnectingOrConnectedPeripheralsIds = BleManager.shared.connectedOrConnectingPeripherals().map{$0.identifier}
|
|
||||||
for peripheral in peripheralsWithServices {
|
|
||||||
if !alreadyConnectingOrConnectedPeripheralsIds.contains(peripheral.identifier) {
|
|
||||||
if let peripheralData = peripheralsData.first(where: {$0.identifier == peripheral.identifier}) {
|
|
||||||
DLog("Connect to peripheral with known service: \(peripheral.identifier)")
|
|
||||||
discovered(peripheral: peripheral, advertisementData: peripheralData.advertisementData )
|
|
||||||
if let blePeripheral = peripheralsFound[peripheral.identifier] {
|
|
||||||
connect(to: blePeripheral, timeout: timeout)
|
|
||||||
reconnecting = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reconnecting
|
return reconnecting
|
||||||
}
|
}
|
||||||
|
|
||||||
private func discovered(peripheral: CBPeripheral, advertisementData: [String: Any]? = nil, rssi: Int? = nil) {
|
private func discovered(peripheral: CBPeripheral, advertisementData: [String: Any]? = nil, rssi: Int? = nil) {
|
||||||
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
||||||
|
|
||||||
if let existingPeripheral = peripheralsFound[peripheral.identifier] {
|
if let existingPeripheral = peripheralsFound[peripheral.identifier] {
|
||||||
existingPeripheral.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
existingPeripheral.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
if let rssi = rssi, rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
|
if let rssi = rssi, rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
|
||||||
existingPeripheral.rssi = rssi
|
existingPeripheral.rssi = rssi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -326,35 +265,30 @@ public class BleManager: NSObject {
|
||||||
} else { // New peripheral found
|
} else { // New peripheral found
|
||||||
let blePeripheral = BlePeripheral(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
let blePeripheral = BlePeripheral(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
||||||
peripheralsFound[peripheral.identifier] = blePeripheral
|
peripheralsFound[peripheral.identifier] = blePeripheral
|
||||||
peripheralsFoundFirstTime[peripheral.identifier] = Date()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
public func peripheralUUID(from notification: Notification) -> UUID? {
|
func peripheral(from notification: Notification) -> BlePeripheral? {
|
||||||
return notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID
|
guard let uuid = notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID else { return nil }
|
||||||
}
|
|
||||||
|
|
||||||
public func peripheral(from notification: Notification) -> BlePeripheral? {
|
|
||||||
guard let uuid = peripheralUUID(from: notification) else { return nil }
|
|
||||||
|
|
||||||
return peripheral(with: uuid)
|
return peripheral(with: uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func error(from notification: Notification) -> Error? {
|
func peripheral(with uuid: UUID) -> BlePeripheral? {
|
||||||
return notification.userInfo?[NotificationUserInfoKey.error.rawValue] as? Error
|
|
||||||
}
|
|
||||||
|
|
||||||
public func peripheral(with uuid: UUID) -> BlePeripheral? {
|
|
||||||
return peripheralsFound[uuid]
|
return peripheralsFound[uuid]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CBCentralManagerDelegate
|
// MARK: - CBCentralManagerDelegate
|
||||||
extension BleManager: CBCentralManagerDelegate {
|
extension BleManager: CBCentralManagerDelegate {
|
||||||
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||||
|
|
||||||
DLog("centralManagerDidUpdateState: \(central.state.rawValue)")
|
DLog("centralManagerDidUpdateState: \(central.state.rawValue)")
|
||||||
|
// Unlock state lock if we have a known state
|
||||||
|
if central.state == .poweredOn || central.state == .poweredOff || central.state == .unsupported || central.state == .unauthorized {
|
||||||
|
centralManagerPoweredOnSemaphore.signal()
|
||||||
|
}
|
||||||
|
|
||||||
// Scanning
|
// Scanning
|
||||||
if central.state == .poweredOn {
|
if central.state == .poweredOn {
|
||||||
|
|
@ -365,107 +299,71 @@ extension BleManager: CBCentralManagerDelegate {
|
||||||
if isScanning {
|
if isScanning {
|
||||||
isScanningWaitingToStart = true
|
isScanningWaitingToStart = true
|
||||||
}
|
}
|
||||||
scanningStartTime = nil
|
isScanning = false
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all peripherals found (Important because the BlePeripheral queues could contain old commands that were processing when the bluetooth state changed)
|
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
||||||
peripheralsFound.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
|
||||||
|
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||||
// DLog("didDiscover: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
// DLog("didDiscover: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||||
let rssi = RSSI.intValue
|
let rssi = RSSI.intValue
|
||||||
DispatchQueue.main.async { // This Fixes iOS12 race condition on cached filtered peripherals. TODO: investigate
|
DispatchQueue.main.async { // This Fixes iOS12 race condition on cached filtered peripherals. TODO: investigate
|
||||||
self.discovered(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
self.discovered(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
||||||
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||||
DLog("didConnect: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
DLog("didConnect: \(peripheral.identifier)")
|
||||||
|
|
||||||
// Remove connection timeout if exists
|
// Remove connection timeout if exists
|
||||||
if let timer = connectionTimeoutTimers[peripheral.identifier] {
|
if let timer = connectionTimeoutTimers[peripheral.identifier] {
|
||||||
timer.invalidate()
|
timer.invalidate()
|
||||||
connectionTimeoutTimers[peripheral.identifier] = nil
|
connectionTimeoutTimers[peripheral.identifier] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set reconnection flag
|
|
||||||
autoreconnectOnDisconnection.insert(peripheral.identifier)
|
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
DispatchQueue.main.async {
|
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||||
DLog("didFailToConnect: \(peripheral.name ?? peripheral.identifier.uuidString). \(String(describing: error))")
|
DLog("didFailToConnect")
|
||||||
|
|
||||||
// Clean
|
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
peripheralsFound[peripheral.identifier]?.reset()
|
|
||||||
|
|
||||||
// Notify
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [
|
|
||||||
NotificationUserInfoKey.uuid.rawValue: peripheral.identifier,
|
|
||||||
NotificationUserInfoKey.error.rawValue: error as Any
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||||
|
DLog("didDisconnectPeripheral")
|
||||||
let peripheralIdentifier = peripheral.identifier
|
|
||||||
DLog("didDisconnectPeripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
|
|
||||||
|
|
||||||
// Clean
|
// Clean
|
||||||
peripheralsFound[peripheralIdentifier]?.reset()
|
peripheralsFound[peripheral.identifier]?.reset()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
// Notify
|
||||||
// Try to reconnect automatically
|
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
if self.autoreconnectOnDisconnection.contains(peripheralIdentifier),
|
|
||||||
let blePeripheral = self.peripheralsFound[peripheralIdentifier] {
|
// Remove from peripheral list (after sending notification so the receiving objects can query about the peripheral before being removed)
|
||||||
self.autoreconnectOnDisconnection.remove(peripheral.identifier)
|
peripheralsFoundLock.lock()
|
||||||
|
peripheralsFound.removeValue(forKey: peripheral.identifier)
|
||||||
DLog("Trying to reconnect to peripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
|
peripheralsFoundLock.unlock()
|
||||||
NotificationCenter.default.post(name: .willReconnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
|
||||||
|
|
||||||
self.connect(to: blePeripheral)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Notify
|
|
||||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
|
||||||
|
|
||||||
// Remove from peripheral list (after sending notification so the receiving objects can query about the peripheral before being removed)
|
|
||||||
self.peripheralsFoundLock.lock()
|
|
||||||
self.peripheralsFound.removeValue(forKey: peripheral.identifier)
|
|
||||||
self.peripheralsFoundLock.unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Custom Notifications
|
// MARK: - Custom Notifications
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
private static let kPrefix = Bundle.main.bundleIdentifier!
|
private static let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
public static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
|
static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
|
||||||
public static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
|
static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
|
||||||
public static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
|
static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
|
||||||
public static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
|
static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
|
||||||
public static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
|
static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
|
||||||
public static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
|
static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
|
||||||
public static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
|
static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
|
||||||
public static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
|
static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
|
||||||
public static let didDisconnectFromPeripheral = Notification.Name(kPrefix+".didDisconnectFromPeripheral")
|
static let didDisconnectFromPeripheral = Notification.Name(kPrefix+".didDisconnectFromPeripheral")
|
||||||
public static let willReconnectToPeripheral = Notification.Name(kPrefix+".willReconnectToPeripheral")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,38 +19,38 @@ extension BlePeripheral {
|
||||||
func readBatteryLevel(handler: @escaping ((Int, Error?) -> Void)) {
|
func readBatteryLevel(handler: @escaping ((Int, Error?) -> Void)) {
|
||||||
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
|
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
|
||||||
guard error == nil, let characteristic = characteristic else { DLog("Error reading battery characteristic: \(error?.localizedDescription ?? "")"); return }
|
guard error == nil, let characteristic = characteristic else { DLog("Error reading battery characteristic: \(error?.localizedDescription ?? "")"); return }
|
||||||
|
|
||||||
self.readCharacteristic(characteristic) { (result, error) in
|
self.readCharacteristic(characteristic) { (result, error) in
|
||||||
guard error == nil, let data = result as? Data, data.count >= 1 else {
|
guard error == nil, let data = result as? Data, data.count >= 1 else {
|
||||||
DLog("Error reading battery level: \(error?.localizedDescription ?? "")")
|
DLog("Error reading battery level: \(error?.localizedDescription ?? "")")
|
||||||
handler(-1, error)
|
handler(-1, error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let level = Int(data[0]) // from 0 to 100
|
let level = Int(data[0]) // from 0 to 100
|
||||||
handler(level, nil)
|
handler(level, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startReadingBatteryLevel(handler: @escaping ((Int) -> Void)) {
|
func startReadingBatteryLevel(handler: @escaping ((Int) -> Void)) {
|
||||||
|
|
||||||
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
|
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
|
||||||
guard error == nil, let characteristic = characteristic else { DLog("Error starting read for battery characteristic: \(error?.localizedDescription ?? "")"); return }
|
guard error == nil, let characteristic = characteristic else { DLog("Error starting read for battery characteristic: \(error?.localizedDescription ?? "")"); return }
|
||||||
|
|
||||||
// Read current value
|
// Read current value
|
||||||
self.readCharacteristic(characteristic) { (result, error) in
|
self.readCharacteristic(characteristic) { (result, error) in
|
||||||
guard error == nil, let data = result as? Data, data.count >= 1 else { DLog("Error reading battery level: \(error?.localizedDescription ?? "")"); return }
|
guard error == nil, let data = result as? Data, data.count >= 1 else { DLog("Error reading battery level: \(error?.localizedDescription ?? "")"); return }
|
||||||
|
|
||||||
let level = Int(data[0]) // from 0 to 100
|
let level = Int(data[0]) // from 0 to 100
|
||||||
handler(level)
|
handler(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable notifications to receive value changes
|
// Enable notifications to receive value changes
|
||||||
self.enableNotify(for: characteristic, handler: { error in
|
self.enableNotify(for: characteristic, handler: { error in
|
||||||
guard error == nil else { DLog("Error receiving notify for battery level"); return }
|
guard error == nil else { DLog("Error receiving notify for battery level"); return }
|
||||||
guard let data = characteristic.value, data.count >= 1 else { DLog("Invalid data receiving notify for battery level"); return }
|
guard let data = characteristic.value, data.count >= 1 else { DLog("Invalid data receiving notify for battery level"); return }
|
||||||
|
|
||||||
let level = Int(data[0]) // from 0 to 100
|
let level = Int(data[0]) // from 0 to 100
|
||||||
handler(level)
|
handler(level)
|
||||||
})
|
})
|
||||||
|
|
@ -60,16 +60,16 @@ extension BlePeripheral {
|
||||||
func stopReadingBatteryLevel() {
|
func stopReadingBatteryLevel() {
|
||||||
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
|
self.characteristic(uuid: BlePeripheral.kBatteryCharacteristicUUID, serviceUuid: BlePeripheral.kBatteryServiceUUID) { (characteristic, error) in
|
||||||
guard error == nil, let characteristic = characteristic else { DLog("Error stopping read for battery characteristic: \(error?.localizedDescription ?? "")"); return }
|
guard error == nil, let characteristic = characteristic else { DLog("Error stopping read for battery characteristic: \(error?.localizedDescription ?? "")"); return }
|
||||||
|
|
||||||
self.disableNotify(for: characteristic)
|
self.disableNotify(for: characteristic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Utils
|
// MARK: - Utils
|
||||||
func isBatteryAdvertised() -> Bool {
|
func isBatteryAdvertised() -> Bool {
|
||||||
return advertisement.services?.contains(BlePeripheral.kBatteryServiceUUID) ?? false
|
return advertisement.services?.contains(BlePeripheral.kBatteryServiceUUID) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasBattery() -> Bool {
|
func hasBattery() -> Bool {
|
||||||
return peripheral.services?.first(where: {$0.uuid == BlePeripheral.kBatteryServiceUUID}) != nil
|
return peripheral.services?.first(where: {$0.uuid == BlePeripheral.kBatteryServiceUUID}) != nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,10 @@ extension BlePeripheral {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
func uartEnable(uartServiceUuid: CBUUID = BlePeripheral.kUartServiceUUID,
|
func uartEnable(uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
|
||||||
txCharacteristicUuid: CBUUID = BlePeripheral.kUartTxCharacteristicUUID,
|
|
||||||
rxCharacteristicUuid: CBUUID = BlePeripheral.kUartRxCharacteristicUUID,
|
|
||||||
uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
|
|
||||||
|
|
||||||
// Get uart communications characteristic
|
// Get uart communications characteristic
|
||||||
characteristic(uuid: txCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
|
characteristic(uuid: BlePeripheral.kUartTxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
|
||||||
guard let characteristic = characteristic, error == nil else {
|
guard let characteristic = characteristic, error == nil else {
|
||||||
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
||||||
return
|
return
|
||||||
|
|
@ -88,7 +85,7 @@ extension BlePeripheral {
|
||||||
self.uartTxCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse:.withResponse
|
self.uartTxCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse:.withResponse
|
||||||
//self.uartTxCharacteristicWriteType = .withResponse // Debug: force withResponse
|
//self.uartTxCharacteristicWriteType = .withResponse // Debug: force withResponse
|
||||||
|
|
||||||
self.characteristic(uuid: rxCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
|
self.characteristic(uuid: BlePeripheral.kUartRxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
|
||||||
guard let characteristic = characteristic, error == nil else {
|
guard let characteristic = characteristic, error == nil else {
|
||||||
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
||||||
return
|
return
|
||||||
|
|
@ -125,17 +122,17 @@ extension BlePeripheral {
|
||||||
}
|
}
|
||||||
|
|
||||||
func uartDisable() {
|
func uartDisable() {
|
||||||
|
// Clear all Uart specific data
|
||||||
|
defer {
|
||||||
|
uartRxCharacteristic = nil
|
||||||
|
uartTxCharacteristic = nil
|
||||||
|
uartTxCharacteristicWriteType = nil
|
||||||
|
}
|
||||||
|
|
||||||
// Disable notify
|
// Disable notify
|
||||||
guard let characteristic = uartRxCharacteristic, characteristic.isNotifying else { return }
|
guard let characteristic = uartRxCharacteristic, characteristic.isNotifying else { return }
|
||||||
|
|
||||||
disableNotify(for: characteristic) { [weak self] error in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
// Clear all Uart specific data
|
disableNotify(for: characteristic)
|
||||||
self.uartRxCharacteristic = nil
|
|
||||||
self.uartTxCharacteristic = nil
|
|
||||||
self.uartTxCharacteristicWriteType = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Send
|
// MARK: - Send
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,19 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreBluetooth
|
import CoreBluetooth
|
||||||
|
#if COMMANDLINE
|
||||||
|
#else
|
||||||
|
import MSWeakTimer
|
||||||
|
#endif
|
||||||
|
|
||||||
// TODO: Modernize completion blocks to use Swift.Result
|
// TODO: Modernize completion blocks to use Swift.Result
|
||||||
|
|
||||||
open class BlePeripheral: NSObject {
|
class BlePeripheral: NSObject {
|
||||||
// Config
|
// Config
|
||||||
private static var kProfileCharacteristicUpdates = true
|
private static var kProfileCharacteristicUpdates = true
|
||||||
|
|
||||||
// Constants
|
|
||||||
static var kUndefinedRssiValue = 127
|
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
public enum NotificationUserInfoKey: String {
|
enum NotificationUserInfoKey: String {
|
||||||
case uuid = "uuid"
|
case uuid = "uuid"
|
||||||
case name = "name"
|
case name = "name"
|
||||||
case invalidatedServices = "invalidatedServices"
|
case invalidatedServices = "invalidatedServices"
|
||||||
|
|
@ -31,48 +32,22 @@ open class BlePeripheral: NSObject {
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
var peripheral: CBPeripheral
|
var peripheral: CBPeripheral
|
||||||
|
var rssi: Int? // rssi only is updated when a non undefined value is received from CoreBluetooth. Note: this is slighty different to the CoreBluetooth implementation, because it will not be updated with undefined values
|
||||||
|
var lastSeenTime: CFAbsoluteTime
|
||||||
|
|
||||||
public static var rssiRunningAverageFactor: Double = 1 /// Global Parameter that affects all rssi measurements. 1 means don't use a running average. The closer to 0 the more resistant the value it is to change
|
var identifier: UUID {
|
||||||
private var runningRssi: Int?
|
|
||||||
public var rssi: Int? {
|
|
||||||
/// rssi only is updated when a non undefined value is received from CoreBluetooth. Note: this is slighty different to the CoreBluetooth implementation, because it will not be updated with undefined values. If runningRssiFactorFactor == 1, the newer value replaces the old value and not average is calculated
|
|
||||||
get {
|
|
||||||
return runningRssi
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
guard newValue != BlePeripheral.kUndefinedRssiValue else { return } // Don't accept undefined values
|
|
||||||
|
|
||||||
// based on https://en.wikipedia.org/wiki/Exponential_smoothing
|
|
||||||
if newValue == nil || runningRssi == nil || runningRssi == BlePeripheral.kUndefinedRssiValue {
|
|
||||||
runningRssi = newValue
|
|
||||||
} else {
|
|
||||||
runningRssi = Int(BlePeripheral.rssiRunningAverageFactor * Double(newValue!) + (1-BlePeripheral.rssiRunningAverageFactor) * Double(runningRssi!))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public var lastSeenTime: CFAbsoluteTime
|
|
||||||
|
|
||||||
open var identifier: UUID {
|
|
||||||
return peripheral.identifier
|
return peripheral.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
open var name: String? {
|
var name: String? {
|
||||||
return peripheral.name
|
return peripheral.name
|
||||||
}
|
}
|
||||||
|
|
||||||
public var debugName: String {
|
|
||||||
return peripheral.name ?? peripheral.identifier.uuidString
|
|
||||||
}
|
|
||||||
|
|
||||||
open var state: CBPeripheralState {
|
var state: CBPeripheralState {
|
||||||
return peripheral.state
|
return peripheral.state
|
||||||
}
|
}
|
||||||
|
|
||||||
func maximumWriteValueLength(for: CBCharacteristicWriteType) -> Int {
|
|
||||||
return peripheral.maximumWriteValueLength(for: .withoutResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Advertisement {
|
struct Advertisement {
|
||||||
var advertisementData: [String: Any]
|
var advertisementData: [String: Any]
|
||||||
|
|
||||||
init(advertisementData: [String: Any]?) {
|
init(advertisementData: [String: Any]?) {
|
||||||
|
|
@ -80,73 +55,71 @@ open class BlePeripheral: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advertisement data formatted
|
// Advertisement data formatted
|
||||||
public var localName: String? {
|
var localName: String? {
|
||||||
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
public var manufacturerData: Data? {
|
var manufacturerData: Data? {
|
||||||
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||||
}
|
}
|
||||||
|
|
||||||
public var manufacturerHexDescription: String? {
|
var manufacturerHexDescription: String? {
|
||||||
guard let manufacturerData = manufacturerData else { return nil }
|
guard let manufacturerData = manufacturerData else { return nil }
|
||||||
return HexUtils.hexDescription(data: manufacturerData)
|
return HexUtils.hexDescription(data: manufacturerData)
|
||||||
// return String(data: manufacturerData, encoding: .utf8)
|
// return String(data: manufacturerData, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var manufacturerIdentifier: Data? {
|
var manufacturerIdentifier: Data? {
|
||||||
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
|
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
|
||||||
let manufacturerIdentifierData = manufacturerData[0..<2]
|
let manufacturerIdentifierData = manufacturerData[0..<2]
|
||||||
return manufacturerIdentifierData
|
return manufacturerIdentifierData
|
||||||
}
|
}
|
||||||
|
|
||||||
public var services: [CBUUID]? {
|
var services: [CBUUID]? {
|
||||||
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var servicesOverflow: [CBUUID]? {
|
var servicesOverflow: [CBUUID]? {
|
||||||
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
|
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var servicesSolicited: [CBUUID]? {
|
var servicesSolicited: [CBUUID]? {
|
||||||
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
|
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var serviceData: [CBUUID: Data]? {
|
var serviceData: [CBUUID: Data]? {
|
||||||
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
|
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var txPower: Int? {
|
var txPower: Int? {
|
||||||
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
|
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
|
||||||
return number?.intValue
|
return number?.intValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isConnectable: Bool? {
|
var isConnectable: Bool? {
|
||||||
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
|
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
|
||||||
return connectableNumber?.boolValue
|
return connectableNumber?.boolValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public var advertisement: Advertisement
|
var advertisement: Advertisement
|
||||||
|
|
||||||
typealias CapturedReadCompletionHandler = ((_ value: Any?, _ error: Error?) -> Void)
|
typealias CapturedReadCompletionHandler = ((_ value: Any?, _ error: Error?) -> Void)
|
||||||
private class CaptureReadHandler {
|
private class CaptureReadHandler {
|
||||||
|
|
||||||
var identifier: String
|
var identifier: String
|
||||||
var result: CapturedReadCompletionHandler
|
var result: CapturedReadCompletionHandler
|
||||||
var timeoutTimer: Foundation.Timer?
|
var timeoutTimer: MSWeakTimer?
|
||||||
var timeoutAction: ((String) -> Void)?
|
var timeoutAction: ((String)->())?
|
||||||
var isNotifyOmitted: Bool
|
var isNotifyOmitted: Bool
|
||||||
|
|
||||||
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction: ((String) -> Void)?, isNotifyOmitted: Bool = false) {
|
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction:((String)->())?, isNotifyOmitted: Bool = false) {
|
||||||
self.identifier = identifier
|
self.identifier = identifier
|
||||||
self.result = result
|
self.result = result
|
||||||
self.isNotifyOmitted = isNotifyOmitted
|
self.isNotifyOmitted = isNotifyOmitted
|
||||||
|
|
||||||
if let timeout = timeout {
|
if let timeout = timeout {
|
||||||
self.timeoutAction = timeoutAction
|
self.timeoutAction = timeoutAction
|
||||||
DispatchQueue.global(qos: .background).async {
|
timeoutTimer = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(timerFired), userInfo: nil, repeats: false, dispatchQueue: .global(qos: .background))
|
||||||
self.timeoutTimer = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,12 +134,12 @@ open class BlePeripheral: NSObject {
|
||||||
private func timeOutRemoveCaptureHandler(identifier: String) { // Default behaviour for a capture handler timeout
|
private func timeOutRemoveCaptureHandler(identifier: String) { // Default behaviour for a capture handler timeout
|
||||||
guard captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) else { return }
|
guard captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) else { return }
|
||||||
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
|
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
|
||||||
|
|
||||||
// Remove capture handler
|
// Remove capture handler
|
||||||
captureReadHandlers.remove(at: index)
|
captureReadHandlers.remove(at: index)
|
||||||
finishedExecutingCommand(error: PeripheralError.timeout)
|
finishedExecutingCommand(error: PeripheralError.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal data
|
// Internal data
|
||||||
private var notifyHandlers = [String: ((Error?) -> Void)]() // Nofify handlers for each service-characteristic
|
private var notifyHandlers = [String: ((Error?) -> Void)]() // Nofify handlers for each service-characteristic
|
||||||
private var captureReadHandlers = [CaptureReadHandler]()
|
private var captureReadHandlers = [CaptureReadHandler]()
|
||||||
|
|
@ -176,13 +149,13 @@ open class BlePeripheral: NSObject {
|
||||||
//private var profileStartTime: CFTimeInterval = 0
|
//private var profileStartTime: CFTimeInterval = 0
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
public init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
|
init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
|
||||||
self.peripheral = peripheral
|
self.peripheral = peripheral
|
||||||
self.advertisement = Advertisement(advertisementData: advertisementData)
|
self.advertisement = Advertisement(advertisementData: advertisementData)
|
||||||
|
self.rssi = rssi
|
||||||
self.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
self.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
self.rssi = rssi
|
|
||||||
self.peripheral.delegate = self
|
self.peripheral.delegate = self
|
||||||
// DLog("create peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
// DLog("create peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||||
commandQueue.executeHandler = executeCommand
|
commandQueue.executeHandler = executeCommand
|
||||||
|
|
@ -234,12 +207,6 @@ open class BlePeripheral: NSObject {
|
||||||
commandQueue.append(command)
|
commandQueue.append(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Connection
|
|
||||||
func disconnect(centralManager: CBCentralManager) {
|
|
||||||
let command = BleCommand(type: .disconnect, parameters: [centralManager], completion: nil)
|
|
||||||
commandQueue.append(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Service
|
// MARK: - Service
|
||||||
func discoveredService(uuid: CBUUID) -> CBService? {
|
func discoveredService(uuid: CBUUID) -> CBService? {
|
||||||
let service = peripheral.services?.first(where: {$0.uuid == uuid})
|
let service = peripheral.services?.first(where: {$0.uuid == uuid})
|
||||||
|
|
@ -281,7 +248,7 @@ open class BlePeripheral: NSObject {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func characteristic(uuid: CBUUID, serviceUuid: CBUUID, completion: ((CBCharacteristic?, Error?) -> Void)?) {
|
func characteristic(uuid: CBUUID, serviceUuid: CBUUID, completion: ((CBCharacteristic?, Error?) -> Void)?) {
|
||||||
if let discoveredService = discoveredService(uuid: serviceUuid) { // Service was already discovered
|
if let discoveredService = discoveredService(uuid: serviceUuid) { // Service was already discovered
|
||||||
characteristic(uuid: uuid, service: discoveredService, completion: completion)
|
characteristic(uuid: uuid, service: discoveredService, completion: completion)
|
||||||
|
|
@ -300,7 +267,7 @@ open class BlePeripheral: NSObject {
|
||||||
let command = BleCommand(type: .setNotify, parameters: [characteristic, true, handler as Any], completion: completion)
|
let command = BleCommand(type: .setNotify, parameters: [characteristic, true, handler as Any], completion: completion)
|
||||||
commandQueue.append(command)
|
commandQueue.append(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
func disableNotify(for characteristic: CBCharacteristic, completion: ((Error?) -> Void)? = nil) {
|
func disableNotify(for characteristic: CBCharacteristic, completion: ((Error?) -> Void)? = nil) {
|
||||||
let command = BleCommand(type: .setNotify, parameters: [characteristic, false], completion: completion)
|
let command = BleCommand(type: .setNotify, parameters: [characteristic, false], completion: completion)
|
||||||
commandQueue.append(command)
|
commandQueue.append(command)
|
||||||
|
|
@ -335,14 +302,14 @@ open class BlePeripheral: NSObject {
|
||||||
let command = BleCommand(type: .readDescriptor, parameters: [descriptor, readCompletion as Any], completion: nil)
|
let command = BleCommand(type: .readDescriptor, parameters: [descriptor, readCompletion as Any], completion: nil)
|
||||||
commandQueue.append(command)
|
commandQueue.append(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Rssi
|
// MARK: - Rssi
|
||||||
func readRssi() {
|
func readRssi() {
|
||||||
peripheral.readRSSI()
|
peripheral.readRSSI()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Command Queue
|
// MARK: - Command Queue
|
||||||
internal class BleCommand: Equatable {
|
private class BleCommand: Equatable {
|
||||||
enum CommandType {
|
enum CommandType {
|
||||||
case discoverService
|
case discoverService
|
||||||
case discoverCharacteristic
|
case discoverCharacteristic
|
||||||
|
|
@ -352,7 +319,6 @@ open class BlePeripheral: NSObject {
|
||||||
case writeCharacteristic
|
case writeCharacteristic
|
||||||
case writeCharacteristicAndWaitNofity
|
case writeCharacteristicAndWaitNofity
|
||||||
case readDescriptor
|
case readDescriptor
|
||||||
case disconnect
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CommandError: Error {
|
enum CommandError: Error {
|
||||||
|
|
@ -396,22 +362,18 @@ open class BlePeripheral: NSObject {
|
||||||
write(with: command)
|
write(with: command)
|
||||||
case .readDescriptor:
|
case .readDescriptor:
|
||||||
readDescriptor(with: command)
|
readDescriptor(with: command)
|
||||||
case .disconnect:
|
|
||||||
disconnect(with: command)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
|
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
|
||||||
guard let service = characteristic.service else { DLog("Error: handleIdentifier with nil characteritic service"); return "" }
|
return "\(characteristic.service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
|
||||||
return "\(service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handlerIdentifier(from descriptor: CBDescriptor) -> String {
|
private func handlerIdentifier(from descriptor: CBDescriptor) -> String {
|
||||||
guard let characteristic = descriptor.characteristic, let service = characteristic.service else { DLog("Error: handleIdentifier with nil descriptor service"); return "" }
|
return "\(descriptor.characteristic.service.uuid.uuidString)-\(descriptor.characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
|
||||||
return "\(service.uuid.uuidString)-\(characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func finishedExecutingCommand(error: Error?) {
|
private func finishedExecutingCommand(error: Error?) {
|
||||||
//DLog("finishedExecutingCommand")
|
//DLog("finishedExecutingCommand")
|
||||||
|
|
||||||
// Result Callback
|
// Result Callback
|
||||||
|
|
@ -439,7 +401,7 @@ open class BlePeripheral: NSObject {
|
||||||
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
|
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
|
||||||
peripheral.discoverServices(serviceUuids)
|
peripheral.discoverServices(serviceUuids)
|
||||||
} else {
|
} else {
|
||||||
// Everything was already discovered
|
// Everthing was already discovered
|
||||||
finishedExecutingCommand(error: nil)
|
finishedExecutingCommand(error: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -502,32 +464,21 @@ open class BlePeripheral: NSObject {
|
||||||
let writeType = command.parameters![1] as! CBCharacteristicWriteType
|
let writeType = command.parameters![1] as! CBCharacteristicWriteType
|
||||||
let data = command.parameters![2] as! Data
|
let data = command.parameters![2] as! Data
|
||||||
|
|
||||||
|
peripheral.writeValue(data, for: characteristic, type: writeType)
|
||||||
|
|
||||||
if writeType == .withoutResponse {
|
if writeType == .withoutResponse {
|
||||||
let mtu = maximumWriteValueLength(for: .withoutResponse)
|
|
||||||
var offset = 0
|
|
||||||
while offset < data.count {
|
|
||||||
let chunkData = data.subdata(in: offset ..< min(offset + mtu, data.count))
|
|
||||||
//DLog("blewrite offset: \(offset) / \(data.count), size: \(chunkData.count)")
|
|
||||||
peripheral.writeValue(chunkData, for: characteristic, type: .withoutResponse)
|
|
||||||
offset += chunkData.count
|
|
||||||
}
|
|
||||||
|
|
||||||
if !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
if !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
||||||
let readCharacteristic = command.parameters![3] as! CBCharacteristic
|
let readCharacteristic = command.parameters![3] as! CBCharacteristic
|
||||||
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
||||||
let timeout = command.parameters![5] as? Double
|
let timeout = command.parameters![5] as? Double
|
||||||
|
|
||||||
let identifier = handlerIdentifier(from: readCharacteristic)
|
let identifier = handlerIdentifier(from: readCharacteristic)
|
||||||
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
|
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
|
||||||
captureReadHandlers.append(captureReadHandler)
|
captureReadHandlers.append(captureReadHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedExecutingCommand(error: nil)
|
finishedExecutingCommand(error: nil)
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
peripheral.writeValue(data, for: characteristic, type: writeType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func readDescriptor(with command: BleCommand) {
|
private func readDescriptor(with command: BleCommand) {
|
||||||
|
|
@ -540,41 +491,35 @@ open class BlePeripheral: NSObject {
|
||||||
|
|
||||||
peripheral.readValue(for: descriptor)
|
peripheral.readValue(for: descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func disconnect(with command: BleCommand) {
|
|
||||||
let centralManager = command.parameters!.first as! CBCentralManager
|
|
||||||
centralManager.cancelPeripheralConnection(self.peripheral)
|
|
||||||
finishedExecutingCommand(error: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - CBPeripheralDelegate
|
// MARK: - CBPeripheralDelegate
|
||||||
extension BlePeripheral: CBPeripheralDelegate {
|
extension BlePeripheral: CBPeripheralDelegate {
|
||||||
public func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
|
func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
|
||||||
DLog("peripheralDidUpdateName: \(name ?? "{ No Name }")")
|
DLog("peripheralDidUpdateName: \(name ?? "{ No Name }")")
|
||||||
NotificationCenter.default.post(name: .peripheralDidUpdateName, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.name.rawValue: name as Any])
|
NotificationCenter.default.post(name: .peripheralDidUpdateName, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.name.rawValue: name as Any])
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
|
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
|
||||||
DLog("didModifyServices")
|
DLog("didModifyServices")
|
||||||
NotificationCenter.default.post(name: .peripheralDidModifyServices, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.invalidatedServices.rawValue: invalidatedServices])
|
NotificationCenter.default.post(name: .peripheralDidModifyServices, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.invalidatedServices.rawValue: invalidatedServices])
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||||
//DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||||
finishedExecutingCommand(error: error)
|
finishedExecutingCommand(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||||
///DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
|
DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
|
||||||
finishedExecutingCommand(error: error)
|
finishedExecutingCommand(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
finishedExecutingCommand(error: error)
|
finishedExecutingCommand(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
|
|
||||||
let identifier = handlerIdentifier(from: characteristic)
|
let identifier = handlerIdentifier(from: characteristic)
|
||||||
|
|
||||||
|
|
@ -626,8 +571,8 @@ extension BlePeripheral: CBPeripheralDelegate {
|
||||||
finishedExecutingCommand(error: error)
|
finishedExecutingCommand(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
if let command = commandQueue.first(), !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
if let command = commandQueue.first(), !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
||||||
let characteristic = command.parameters![3] as! CBCharacteristic
|
let characteristic = command.parameters![3] as! CBCharacteristic
|
||||||
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
||||||
|
|
@ -642,11 +587,11 @@ extension BlePeripheral: CBPeripheralDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
finishedExecutingCommand(error: error)
|
finishedExecutingCommand(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
|
||||||
let identifier = handlerIdentifier(from: descriptor)
|
let identifier = handlerIdentifier(from: descriptor)
|
||||||
|
|
||||||
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
|
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
|
||||||
|
|
@ -660,15 +605,15 @@ extension BlePeripheral: CBPeripheralDelegate {
|
||||||
finishedExecutingCommand(error: error)
|
finishedExecutingCommand(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
||||||
guard error == nil else { DLog("didReadRSSI error: \(error!.localizedDescription)"); return }
|
guard error == nil else { DLog("didReadRSSI error: \(error!.localizedDescription)"); return }
|
||||||
|
|
||||||
let rssi = RSSI.intValue
|
let rssi = RSSI.intValue
|
||||||
if rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
|
if rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
|
||||||
self.rssi = rssi
|
self.rssi = rssi
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .peripheralDidUpdateRssi, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
NotificationCenter.default.post(name: .peripheralDidUpdateRssi, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -676,7 +621,7 @@ extension BlePeripheral: CBPeripheralDelegate {
|
||||||
// MARK: - Custom Notifications
|
// MARK: - Custom Notifications
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
private static let kPrefix = Bundle.main.bundleIdentifier!
|
private static let kPrefix = Bundle.main.bundleIdentifier!
|
||||||
public static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
|
static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
|
||||||
public static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
|
static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
|
||||||
public static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
|
static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol UartDataManagerDelegate: AnyObject {
|
protocol UartDataManagerDelegate: class {
|
||||||
func onUartRx(data: Data, peripheralIdentifier: UUID) // data contents depends on the isRxCacheEnabled flag
|
func onUartRx(data: Data, peripheralIdentifier: UUID) // data contents depends on the isRxCacheEnabled flag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ import Foundation
|
||||||
|
|
||||||
class UartPacketManager: UartPacketManagerBase {
|
class UartPacketManager: UartPacketManagerBase {
|
||||||
|
|
||||||
// Params
|
|
||||||
var isResetPacketsOnReconnectionEnabled = true
|
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
|
||||||
override init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
override init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
||||||
super.init(delegate: delegate, isPacketCacheEnabled: isPacketCacheEnabled, isMqttEnabled: isMqttEnabled)
|
super.init(delegate: delegate, isPacketCacheEnabled: isPacketCacheEnabled, isMqttEnabled: isMqttEnabled)
|
||||||
|
|
||||||
|
|
@ -29,20 +25,14 @@ class UartPacketManager: UartPacketManagerBase {
|
||||||
private func registerNotifications(enabled: Bool) {
|
private func registerNotifications(enabled: Bool) {
|
||||||
let notificationCenter = NotificationCenter.default
|
let notificationCenter = NotificationCenter.default
|
||||||
if enabled {
|
if enabled {
|
||||||
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {
|
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {[weak self] _ in self?.clearPacketsCache()}
|
||||||
[weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
if self.isResetPacketsOnReconnectionEnabled {
|
|
||||||
self.clearPacketsCache()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
|
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Send data
|
// MARK: - Send data
|
||||||
private func sendUart(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
|
func send(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
|
||||||
sentBytes += Int64(data?.count ?? 0)
|
sentBytes += Int64(data?.count ?? 0)
|
||||||
blePeripheral.uartSend(data: data, progress: progress, completion: completion)
|
blePeripheral.uartSend(data: data, progress: progress, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
@ -61,16 +51,13 @@ class UartPacketManager: UartPacketManagerBase {
|
||||||
blePeripheral.uartSendAndWaitReply(data: data, writeProgress: writeProgress, writeCompletion: writeCompletion, readTimeout: readTimeout, readCompletion: readCompletion)
|
blePeripheral.uartSendAndWaitReply(data: data, writeProgress: writeProgress, writeCompletion: writeCompletion, readTimeout: readTimeout, readCompletion: readCompletion)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func send(blePeripheral: BlePeripheral, text: String, wasReceivedFromMqtt: Bool = false) {
|
||||||
Send data to MQTT (if enabled) and also to UART (if MQTT configuration allows it)
|
|
||||||
*/
|
|
||||||
func send(blePeripheral: BlePeripheral, data: Data, wasReceivedFromMqtt: Bool = false) {
|
|
||||||
|
|
||||||
#if MQTT_ENABLED
|
#if MQTT_ENABLED
|
||||||
if isMqttEnabled {
|
if isMqttEnabled {
|
||||||
// Mqtt publish to TX
|
// Mqtt publish to TX
|
||||||
let mqttSettings = MqttSettings.shared
|
let mqttSettings = MqttSettings.shared
|
||||||
if mqttSettings.isPublishEnabled, let text = String(data: data, encoding: .utf8) {
|
if mqttSettings.isPublishEnabled {
|
||||||
if let topic = mqttSettings.getPublishTopic(index: MqttSettings.PublishFeed.tx.rawValue) {
|
if let topic = mqttSettings.getPublishTopic(index: MqttSettings.PublishFeed.tx.rawValue) {
|
||||||
let qos = mqttSettings.getPublishQos(index: MqttSettings.PublishFeed.tx.rawValue)
|
let qos = mqttSettings.getPublishQos(index: MqttSettings.PublishFeed.tx.rawValue)
|
||||||
MqttManager.shared.publish(message: text, topic: topic, qos: qos)
|
MqttManager.shared.publish(message: text, topic: topic, qos: qos)
|
||||||
|
|
@ -80,25 +67,27 @@ class UartPacketManager: UartPacketManagerBase {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Create data and send to Uart
|
// Create data and send to Uart
|
||||||
let uartPacket = UartPacket(peripheralId: blePeripheral.identifier, mode: .tx, data: data)
|
if let data = text.data(using: .utf8) {
|
||||||
|
let uartPacket = UartPacket(peripheralId: blePeripheral.identifier, mode: .tx, data: data)
|
||||||
// Add Packet
|
|
||||||
packetsSemaphore.wait()
|
// Add Packet
|
||||||
packets.append(uartPacket)
|
packetsSemaphore.wait()
|
||||||
packetsSemaphore.signal()
|
packets.append(uartPacket)
|
||||||
|
packetsSemaphore.signal()
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.delegate?.onUartPacket(uartPacket)
|
DispatchQueue.main.async {
|
||||||
}
|
self.delegate?.onUartPacket(uartPacket)
|
||||||
|
}
|
||||||
#if MQTT_ENABLED
|
|
||||||
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
|
#if MQTT_ENABLED
|
||||||
#else
|
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
|
||||||
let shouldBeSent = true
|
#else
|
||||||
#endif
|
let shouldBeSent = true
|
||||||
|
#endif
|
||||||
if shouldBeSent {
|
|
||||||
sendUart(blePeripheral: blePeripheral, data: data)
|
if shouldBeSent {
|
||||||
|
send(blePeripheral: blePeripheral, data: data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBAccelerometer.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 25/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
static let kCPBAccelerometerServiceUUID = CBUUID(string: "ADAF0200-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBAccelerometerCharacteristicUUID = CBUUID(string: "ADAF0201-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
static let kCPBAcceleromterDefaultPeriod: TimeInterval = 0.1
|
||||||
|
|
||||||
|
struct AccelerometerValue {
|
||||||
|
var x: Float
|
||||||
|
var y: Float
|
||||||
|
var z: Float
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbAccelerometerCharacteristic: CBCharacteristic?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbAccelerometerCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbAccelerometerCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbAccelerometerCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbAccelerometerEnable(responseHandler: @escaping(Result<(AccelerometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBAccelerometerServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBAccelerometerCharacteristicUUID, timePeriod: BlePeripheral.kCPBAcceleromterDefaultPeriod, responseHandler: { response in
|
||||||
|
|
||||||
|
switch response {
|
||||||
|
case let .success((data, uuid)):
|
||||||
|
if let acceleration = self.cpbAccelerometerDataToFloatVector(data) {
|
||||||
|
responseHandler(.success((acceleration, uuid)))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
responseHandler(.failure(PeripheralCPBError.invalidResponseData))
|
||||||
|
}
|
||||||
|
case let .failure(error):
|
||||||
|
responseHandler(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}) { result in
|
||||||
|
switch result {
|
||||||
|
case let .success((version, characteristic)):
|
||||||
|
guard version == 1 else {
|
||||||
|
DLog("Warning: cpbAccelerometerEnable unknown version: \(version)")
|
||||||
|
completion?(.failure(PeripheralCPBError.unknownVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cpbAccelerometerCharacteristic = characteristic
|
||||||
|
completion?(.success(()))
|
||||||
|
|
||||||
|
case let .failure(error):
|
||||||
|
self.cpbAccelerometerCharacteristic = nil
|
||||||
|
completion?(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbAccelerometerEnabled() -> Bool {
|
||||||
|
return cpbAccelerometerCharacteristic != nil && cpbAccelerometerCharacteristic!.isNotifying
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbAccelerometerDisable() {
|
||||||
|
// Clear all specific data
|
||||||
|
defer {
|
||||||
|
cpbAccelerometerCharacteristic = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable notify
|
||||||
|
guard let characteristic = cpbAccelerometerCharacteristic, characteristic.isNotifying else { return }
|
||||||
|
disableNotify(for: characteristic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbAccelerometerLastValue() -> AccelerometerValue? {
|
||||||
|
guard let data = cpbAccelerometerCharacteristic?.value else { return nil }
|
||||||
|
return cpbAccelerometerDataToFloatVector(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Utils
|
||||||
|
private func cpbAccelerometerDataToFloatVector(_ data: Data) -> AccelerometerValue? {
|
||||||
|
|
||||||
|
let unitSize = MemoryLayout<Float32>.stride
|
||||||
|
var bytes = [Float32](repeating: 0, count: data.count / unitSize)
|
||||||
|
(data as NSData).getBytes(&bytes, length: data.count * unitSize)
|
||||||
|
|
||||||
|
guard bytes.count >= 3 else { return nil }
|
||||||
|
|
||||||
|
return AccelerometerValue(x: bytes[0], y: bytes[1], z: bytes[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBButtons.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 15/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
static let kCPBButtonsServiceUUID = CBUUID(string: "ADAF0600-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBButtonsCharacteristicUUID = CBUUID(string: "ADAF0601-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
enum SlideSwitchState: Int32 {
|
||||||
|
case right = 0
|
||||||
|
case left = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ButtonState: Int32 {
|
||||||
|
case released = 0
|
||||||
|
case pressed = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ButtonsState {
|
||||||
|
var slideSwitch: SlideSwitchState
|
||||||
|
var buttonA: ButtonState
|
||||||
|
var buttonB: ButtonState
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbButtonsCharacteristic: CBCharacteristic?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbButtonsCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbButtonsCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbButtonsCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: 0, responseHandler: { response in
|
||||||
|
|
||||||
|
switch response {
|
||||||
|
case let .success((data, uuid)):
|
||||||
|
let state = self.cpbButtonsDataToStateMask(data)
|
||||||
|
responseHandler(.success((state, uuid)))
|
||||||
|
case let .failure(error):
|
||||||
|
responseHandler(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}) { result in
|
||||||
|
switch result {
|
||||||
|
case let .success((version, characteristic)):
|
||||||
|
guard version == 1 else {
|
||||||
|
DLog("Warning: cpbButtonsEnable unknown version: \(version)")
|
||||||
|
completion?(.failure(PeripheralCPBError.unknownVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cpbButtonsCharacteristic = characteristic
|
||||||
|
completion?(.success(()))
|
||||||
|
|
||||||
|
case let .failure(error):
|
||||||
|
self.cpbButtonsCharacteristic = nil
|
||||||
|
completion?(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbButtonsEnabled() -> Bool {
|
||||||
|
return cpbButtonsCharacteristic != nil && cpbButtonsCharacteristic!.isNotifying
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbButtonsDisable() {
|
||||||
|
// Clear all specific data
|
||||||
|
defer {
|
||||||
|
cpbButtonsCharacteristic = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable notify
|
||||||
|
guard let characteristic = cpbButtonsCharacteristic, characteristic.isNotifying else { return }
|
||||||
|
disableNotify(for: characteristic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||||
|
guard let cpbButtonsCharacteristic = cpbButtonsCharacteristic else {
|
||||||
|
completion(.failure(PeripheralCPBError.invalidCharacteristic))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.readCharacteristic(cpbButtonsCharacteristic) { [weak self] (data, error) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
guard error == nil, let data = data as? Data else {
|
||||||
|
completion(.failure(error ?? PeripheralCPBError.invalidResponseData))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = self.cpbButtonsDataToStateMask(data)
|
||||||
|
completion(.success((state, self.identifier)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbButtonsLastValue() -> ButtonsState? {
|
||||||
|
guard let data = cpbButtonsCharacteristic?.value else { return nil }
|
||||||
|
return cpbButtonsDataToStateMask(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Utils
|
||||||
|
private func cpbButtonsDataToStateMask(_ data: Data) -> ButtonsState {
|
||||||
|
let stateMask = data.toInt32From32Bits()
|
||||||
|
|
||||||
|
let slideSwitchBit = stateMask & 0b1
|
||||||
|
let slideSwitchState = SlideSwitchState(rawValue: slideSwitchBit)!
|
||||||
|
|
||||||
|
let buttonABit = ( stateMask >> 1 ) & 0b1
|
||||||
|
let buttonAState = ButtonState(rawValue: buttonABit)!
|
||||||
|
|
||||||
|
let buttonBBit = ( stateMask >> 2 ) & 0b1
|
||||||
|
let buttonBState = ButtonState(rawValue: buttonBBit)!
|
||||||
|
|
||||||
|
return ButtonsState(slideSwitch: slideSwitchState , buttonA: buttonAState, buttonB: buttonBState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
//
|
||||||
|
// BlePeripehral+CPBCommon.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 13/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Costants
|
||||||
|
private static let kCPBMeasurementPeriodCharacteristicUUID = CBUUID(string: "ADAF0001-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBMeasurementVersionCharacteristicUUID = CBUUID(string: "ADAF0002-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
private static let kCPBDefaultVersionValue = 1 // Used as default version value if version characteristic cannot be read
|
||||||
|
|
||||||
|
// MARK: - Errors
|
||||||
|
enum PeripheralCPBError: Error {
|
||||||
|
case invalidCharacteristic
|
||||||
|
case enableNotifyFailed
|
||||||
|
case unknownVersion
|
||||||
|
case invalidResponseData
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
/*
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbMeasurementPeriodCharacteristic: CBCharacteristic?
|
||||||
|
//static var cpbVersionCharacteristic: CBCharacteristic?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbMeasurementPeriodCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbMeasurementPeriodCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbMeasurementPeriodCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbVersionCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbVersionCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbVersionCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// MARK: - Service Actions
|
||||||
|
func cpbServiceEnable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, completion: ((Result<(Int, CBCharacteristic), Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [unowned self] (characteristic, error) in
|
||||||
|
guard let characteristic = characteristic, error == nil else {
|
||||||
|
completion?(.failure(error ?? PeripheralCPBError.invalidCharacteristic))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check version
|
||||||
|
self.cpbVersion(serviceUuid: serviceUuid) { version in
|
||||||
|
completion?(.success((version, characteristic)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbServiceEnable(serviceUuid: CBUUID, mainCharacteristicUuid: CBUUID, timePeriod: TimeInterval?, responseHandler: @escaping(Result<(Data, UUID), Error>) -> Void, completion: ((Result<(Int, CBCharacteristic), Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.characteristic(uuid: mainCharacteristicUuid, serviceUuid: serviceUuid) { [unowned self] (characteristic, error) in
|
||||||
|
guard let characteristic = characteristic, error == nil else {
|
||||||
|
completion?(.failure(error ?? PeripheralCPBError.invalidCharacteristic))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check version
|
||||||
|
self.cpbVersion(serviceUuid: serviceUuid) { version in
|
||||||
|
// Prepare notification handler
|
||||||
|
let notifyHandler: ((Error?) -> Void)? = { [unowned self] error in
|
||||||
|
guard error == nil else {
|
||||||
|
responseHandler(.failure(error!))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let data = characteristic.value {
|
||||||
|
responseHandler(.success((data, self.identifier)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh period handler
|
||||||
|
let enableNotificationsHandler = {
|
||||||
|
// Enable notifications
|
||||||
|
if !characteristic.isNotifying {
|
||||||
|
self.enableNotify(for: characteristic, handler: notifyHandler, completion: { error in
|
||||||
|
guard error == nil else {
|
||||||
|
completion?(.failure(error!))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard characteristic.isNotifying else {
|
||||||
|
completion?(.failure(PeripheralCPBError.enableNotifyFailed))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completion?(.success((version, characteristic)))
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.updateNotifyHandler(for: characteristic, handler: notifyHandler)
|
||||||
|
completion?(.success((version, characteristic)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set timePeriod if not nil
|
||||||
|
if let timePeriod = timePeriod {
|
||||||
|
self.cpbSetPeriod(timePeriod, serviceUuid: serviceUuid) { result in
|
||||||
|
|
||||||
|
if Config.isDebugEnabled {
|
||||||
|
// Check period
|
||||||
|
self.cpbPeriod(serviceUuid: serviceUuid) { period in
|
||||||
|
DLog("service period: \(String(describing: period))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableNotificationsHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
enableNotificationsHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbVersion(serviceUuid: CBUUID, completion: @escaping(Int) -> Void) {
|
||||||
|
self.characteristic(uuid: BlePeripheral.kCPBMeasurementVersionCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
|
||||||
|
|
||||||
|
guard error == nil, let characteristic = characteristic, let data = characteristic.value else {
|
||||||
|
completion(BlePeripheral.kCPBDefaultVersionValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let version = data.toIntFrom32Bits()
|
||||||
|
completion(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func cpbPeriod(serviceUuid: CBUUID, completion: @escaping(TimeInterval?) -> Void) {
|
||||||
|
self.characteristic(uuid: BlePeripheral.kCPBMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
|
||||||
|
|
||||||
|
guard error == nil, let characteristic = characteristic else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.readCharacteristic(characteristic) { (data, error) in
|
||||||
|
guard error == nil, let data = data as? Data else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let period = TimeInterval(data.toIntFrom32Bits()) / 1000.0
|
||||||
|
completion(period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func cpbSetPeriod(_ period: TimeInterval, serviceUuid: CBUUID, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.characteristic(uuid: BlePeripheral.kCPBMeasurementPeriodCharacteristicUUID, serviceUuid: serviceUuid) { (characteristic, error) in
|
||||||
|
|
||||||
|
guard error == nil, let characteristic = characteristic else {
|
||||||
|
DLog("Error: cpbSetPeriod: \(String(describing: error))")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let periodMillis = Int32(period * 1000)
|
||||||
|
let data = periodMillis.littleEndian.data
|
||||||
|
self.write(data: data, for: characteristic, type: .withResponse) { error in
|
||||||
|
guard error == nil else {
|
||||||
|
DLog("Error: cpbSetPeriod \(error!)")
|
||||||
|
completion?(.failure(error!))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completion?(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBLight.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 13/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
static let kCPBLightServiceUUID = CBUUID(string: "ADAF0300-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBLightCharacteristicUUID = CBUUID(string: "ADAF0301-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
static let kCPBLightDefaultPeriod: TimeInterval = 0.1
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbLightCharacteristic: CBCharacteristic?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbLightCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbLightCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbLightCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBLightServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBLightCharacteristicUUID, timePeriod: BlePeripheral.kCPBLightDefaultPeriod, responseHandler: { response in
|
||||||
|
|
||||||
|
switch response {
|
||||||
|
case let .success((data, uuid)):
|
||||||
|
let light = self.cpbLightDataToFloat(data)
|
||||||
|
responseHandler(.success((light, uuid)))
|
||||||
|
case let .failure(error):
|
||||||
|
responseHandler(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}) { result in
|
||||||
|
switch result {
|
||||||
|
case let .success((version, characteristic)):
|
||||||
|
guard version == 1 else {
|
||||||
|
DLog("Warning: cpbLightEnable unknown version: \(version)")
|
||||||
|
completion?(.failure(PeripheralCPBError.unknownVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cpbLightCharacteristic = characteristic
|
||||||
|
completion?(.success(()))
|
||||||
|
|
||||||
|
case let .failure(error):
|
||||||
|
self.cpbLightCharacteristic = nil
|
||||||
|
completion?(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbLightEnabled() -> Bool {
|
||||||
|
return cpbLightCharacteristic != nil && cpbLightCharacteristic!.isNotifying
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbLightDisable() {
|
||||||
|
// Clear all specific data
|
||||||
|
defer {
|
||||||
|
cpbLightCharacteristic = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable notify
|
||||||
|
guard let characteristic = cpbLightCharacteristic, characteristic.isNotifying else { return }
|
||||||
|
disableNotify(for: characteristic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbLightLastValue() -> Float? {
|
||||||
|
guard let data = cpbLightCharacteristic?.value else { return nil }
|
||||||
|
return cpbLightDataToFloat(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Utils
|
||||||
|
private func cpbLightDataToFloat(_ data: Data) -> Float {
|
||||||
|
return data.toFloatFrom32Bits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBPixels.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 25/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Config
|
||||||
|
private static let kPixelsServiceNumberOfBitsPerPixel = 3
|
||||||
|
private static let kPixelsServiceNumPixels = 10
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
static let kCPBPixelsServiceUUID = CBUUID(string: "ADAF0900-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBPixelsDataCharacteristicUUID = CBUUID(string: "ADAF0903-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbPixelsDataCharacteristic: CBCharacteristic?
|
||||||
|
static var cpbPixelsDataValue: Data?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbPixelsDataCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbPixelsDataValue: Data {
|
||||||
|
get {
|
||||||
|
if let data = objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataValue) as! Data? {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
else { // Initial value
|
||||||
|
return Data(repeating: 0, count: BlePeripheral.kPixelsServiceNumPixels * BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbPixelsDataValue, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbPixelsEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBPixelsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBPixelsDataCharacteristicUUID) { result in
|
||||||
|
switch result {
|
||||||
|
case let .success((version, characteristic)):
|
||||||
|
guard version == 1 else {
|
||||||
|
DLog("Warning: cpbPixelsEnable unknown version: \(version)")
|
||||||
|
completion?(.failure(PeripheralCPBError.unknownVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cpbPixelsDataCharacteristic = characteristic
|
||||||
|
completion?(.success(()))
|
||||||
|
|
||||||
|
case let .failure(error):
|
||||||
|
self.cpbPixelsDataCharacteristic = nil
|
||||||
|
completion?(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbPixelsEnabled() -> Bool {
|
||||||
|
return cpbPixelsDataCharacteristic != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbPixelsDisable() {
|
||||||
|
// Clear all specific data
|
||||||
|
cpbPixelsDataCharacteristic = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbNumPixels() -> Int {
|
||||||
|
return BlePeripheral.kPixelsServiceNumPixels
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbPixelSetAllPixelsColor(_ color: UIColor) {
|
||||||
|
let colors = [UIColor](repeating: color, count: BlePeripheral.kPixelsServiceNumPixels)
|
||||||
|
cpbPixelsWriteData(offset: 0, colors: colors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbPixelSetPixelColor(index: Int, color: UIColor) {
|
||||||
|
let offset = UInt16(index * BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
|
||||||
|
cpbPixelsWriteData(offset: offset, colors: [color])
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbPixelSetColor(index: UInt, color: UIColor, pixelMask: [Bool]) {
|
||||||
|
guard let pixelData = pixelDataFromColorMask(color: color, pixelMask: pixelMask) else {
|
||||||
|
DLog("Error neopixelSetColor invalid color data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let offset = UInt16(index * UInt(BlePeripheral.kPixelsServiceNumberOfBitsPerPixel))
|
||||||
|
cpbPixelsWriteData(offset: offset, pixelData: pixelData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Low level actions
|
||||||
|
func cpbPixelsWriteData(offset: UInt16, colors: [UIColor]) {
|
||||||
|
let pixelData = BlePeripheral.pixelDataFromColors(colors)
|
||||||
|
cpbPixelsWriteData(offset: offset, pixelData: pixelData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbPixelsWriteData(offset: UInt16, pixelData: Data) {
|
||||||
|
guard let cpbPixelsDataCharacteristic = cpbPixelsDataCharacteristic else { return }
|
||||||
|
|
||||||
|
enum Flags: UInt8 {
|
||||||
|
case save = 0
|
||||||
|
case flush = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags = Flags.flush
|
||||||
|
|
||||||
|
let data = offset.littleEndian.data + flags.rawValue.littleEndian.data + pixelData
|
||||||
|
// self.write(data: data, for: cpbPixelsDataCharacteristic, type: .withResponse)
|
||||||
|
self.write(data: data, for: cpbPixelsDataCharacteristic, type: .withResponse) { [unowned self] error in
|
||||||
|
guard error == nil else { DLog("Error cpbPixelsWriteData: \(error!)"); return }
|
||||||
|
|
||||||
|
self.cpbPixelsDataValue = pixelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Utils
|
||||||
|
private func pixelDataFromColorMask(color: UIColor, pixelMask: [Bool]) -> Data? {
|
||||||
|
let colorData = BlePeripheral.pixelDataFromColor(color)
|
||||||
|
|
||||||
|
var pixelData = Data()
|
||||||
|
for (i, mask) in pixelMask.enumerated() {
|
||||||
|
if mask { // overwrite color
|
||||||
|
pixelData += colorData
|
||||||
|
}
|
||||||
|
else { // use current color
|
||||||
|
let existingColorData: Data
|
||||||
|
let byteOffset = i * BlePeripheral.kPixelsServiceNumberOfBitsPerPixel
|
||||||
|
DLog("cpbPixelsDataValue.count: \(cpbPixelsDataValue.count) ")
|
||||||
|
if byteOffset < cpbPixelsDataValue.count {
|
||||||
|
existingColorData = Data(cpbPixelsDataValue[byteOffset..<(byteOffset + BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
existingColorData = Data(repeating: 0, count: BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
|
||||||
|
}
|
||||||
|
pixelData += existingColorData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelData
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func pixelDataFromColors(_ colors: [UIColor]) -> Data {
|
||||||
|
var pixelData = Data()
|
||||||
|
|
||||||
|
for color in colors {
|
||||||
|
pixelData += pixelDataFromColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelData
|
||||||
|
}
|
||||||
|
|
||||||
|
static func pixelDataFromColor(_ color: UIColor) -> Data {
|
||||||
|
let bytes = pixelUInt8FromColor(color)
|
||||||
|
return bytes.data
|
||||||
|
}
|
||||||
|
|
||||||
|
static func pixelUInt8FromColor(_ color: UIColor) -> [UInt8] {
|
||||||
|
var pixelBytes: [UInt8]? = nil
|
||||||
|
|
||||||
|
let cgColor = color.cgColor
|
||||||
|
let numComponents = cgColor.numberOfComponents
|
||||||
|
if let components = cgColor.components {
|
||||||
|
if numComponents == 2 {
|
||||||
|
let white = UInt8(components[0] * 255)
|
||||||
|
//let alpha = UInt8(components[1] * 255)
|
||||||
|
|
||||||
|
pixelBytes = [white, white, white]
|
||||||
|
}
|
||||||
|
else if numComponents == 4 {
|
||||||
|
|
||||||
|
let r = UInt8(components[0] * 255)
|
||||||
|
let g = UInt8(components[1] * 255)
|
||||||
|
let b = UInt8(components[2] * 255)
|
||||||
|
//let alpha = UInt8(components[3] * 255)
|
||||||
|
|
||||||
|
pixelBytes = [g, r, b]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DLog("Error converting color (number of components is: \(numComponents))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelBytes ?? [UInt8](repeating: 0, count: BlePeripheral.kPixelsServiceNumberOfBitsPerPixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBTemperature.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 13/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
static let kCPBTemperatureServiceUUID = CBUUID(string: "ADAF0100-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBTemperatureCharacteristicUUID = CBUUID(string: "ADAF0101-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbTemperatureCharacteristic: CBCharacteristic?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbTemperatureCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBTemperatureServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBTemperatureCharacteristicUUID, timePeriod: 0.5, responseHandler: { response in
|
||||||
|
|
||||||
|
switch response {
|
||||||
|
case let .success((data, uuid)):
|
||||||
|
let temperature = self.cpbTemperatureDataToFloat(data)
|
||||||
|
responseHandler(.success((temperature, uuid)))
|
||||||
|
case let .failure(error):
|
||||||
|
responseHandler(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}) { result in
|
||||||
|
switch result {
|
||||||
|
case let .success((version, characteristic)):
|
||||||
|
guard version == 1 else {
|
||||||
|
DLog("Warning: cpbTemperatureEnable unknown version: \(version)")
|
||||||
|
completion?(.failure(PeripheralCPBError.unknownVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cpbTemperatureCharacteristic = characteristic
|
||||||
|
completion?(.success(()))
|
||||||
|
|
||||||
|
case let .failure(error):
|
||||||
|
self.cpbTemperatureCharacteristic = nil
|
||||||
|
completion?(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbTemperatureEnabled() -> Bool {
|
||||||
|
return cpbTemperatureCharacteristic != nil && cpbTemperatureCharacteristic!.isNotifying
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbTemperatureDisable() {
|
||||||
|
// Clear all specific data
|
||||||
|
defer {
|
||||||
|
cpbTemperatureCharacteristic = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable notify
|
||||||
|
guard let characteristic = cpbTemperatureCharacteristic, characteristic.isNotifying else { return }
|
||||||
|
disableNotify(for: characteristic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbTemperatureLastValue() -> Float? {
|
||||||
|
guard let data = cpbTemperatureCharacteristic?.value else { return nil }
|
||||||
|
return cpbTemperatureDataToFloat(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Utils
|
||||||
|
private func cpbTemperatureDataToFloat(_ data: Data) -> Float {
|
||||||
|
return data.toFloatFrom32Bits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBToneGenerator.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 18/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
static let kCPBToneGeneratorServiceUUID = CBUUID(string: "ADAF0C00-C332-42A8-93BD-25E905756CB8")
|
||||||
|
private static let kCPBToneGeneratorCharacteristicUUID = CBUUID(string: "ADAF0C01-C332-42A8-93BD-25E905756CB8")
|
||||||
|
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbToneGeneratorCharacteristic: CBCharacteristic?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbToneGeneratorCharacteristic: CBCharacteristic? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbToneGeneratorCharacteristic) as! CBCharacteristic?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbToneGeneratorCharacteristic, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbToneGeneratorEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBToneGeneratorServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBToneGeneratorCharacteristicUUID) { result in
|
||||||
|
switch result {
|
||||||
|
case let .success((version, characteristic)):
|
||||||
|
guard version == 1 else {
|
||||||
|
DLog("Warning: cpbToneGeneratorEnable unknown version: \(version)")
|
||||||
|
completion?(.failure(PeripheralCPBError.unknownVersion))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cpbToneGeneratorCharacteristic = characteristic
|
||||||
|
completion?(.success(()))
|
||||||
|
|
||||||
|
case let .failure(error):
|
||||||
|
self.cpbToneGeneratorCharacteristic = nil
|
||||||
|
completion?(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbToneGeneratorEnabled() -> Bool {
|
||||||
|
return cpbToneGeneratorCharacteristic != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbToneGeneratorDisable() {
|
||||||
|
// Clear all specific data
|
||||||
|
cpbToneGeneratorCharacteristic = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbToneGeneratorStartPlaying(frequency: UInt16, duration: UInt32 = 0) { // Duration 0 means non-stop
|
||||||
|
guard let cpbToneGeneratorCharacteristic = cpbToneGeneratorCharacteristic else { return }
|
||||||
|
|
||||||
|
let data = frequency.littleEndian.data + duration.littleEndian.data
|
||||||
|
self.write(data: data, for: cpbToneGeneratorCharacteristic, type: .withResponse)
|
||||||
|
//DLog("tone: \(frequency)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+ManufacturerAdafruit.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 10/12/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
private static let kManufacturerAdafruitIdentifier: [UInt8] = [0x22, 0x08]
|
||||||
|
|
||||||
|
func isManufacturerAdafruit() -> Bool {
|
||||||
|
guard let manufacturerIdentifier = advertisement.manufacturerIdentifier else { return false }
|
||||||
|
|
||||||
|
let manufacturerIdentifierBytes = [UInt8](manufacturerIdentifier)
|
||||||
|
//DLog("\(name) manufacturer: \(advertisement.manufacturerString)")
|
||||||
|
return manufacturerIdentifierBytes == BlePeripheral.kManufacturerAdafruitIdentifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,78 +6,38 @@
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import Foundation
|
||||||
import CoreBluetooth
|
import CoreBluetooth
|
||||||
|
|
||||||
class BleManagerSimulated: BleManager {
|
class BleManagerSimulated: BleManager {
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
static let simulated = BleManagerSimulated()
|
static let simulated = BleManagerSimulated()
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
override init() {
|
override init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Scanning
|
|
||||||
override func startScan(withServices services: [CBUUID]? = nil) {
|
override func startScan(withServices services: [CBUUID]? = nil) {
|
||||||
scanningStartTime = CACurrentMediaTime()
|
isScanning = true
|
||||||
|
|
||||||
// Add simulated peripherals
|
|
||||||
let simulatedCPB = BlePeripheralSimulated(model: .circuitPlaygroundBluefruit)
|
|
||||||
peripheralsFound[simulatedCPB.identifier] = simulatedCPB
|
|
||||||
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedCPB.identifier])
|
|
||||||
|
|
||||||
let simulatedClue = BlePeripheralSimulated(model: .clue_nRF52840)
|
|
||||||
peripheralsFound[simulatedClue.identifier] = simulatedClue
|
|
||||||
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedClue.identifier])
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add simulated peripheral
|
||||||
|
let simulatedBlePeripheral = BlePeripheralSimulated()
|
||||||
|
peripheralsFound[simulatedBlePeripheral.identifier] = simulatedBlePeripheral
|
||||||
|
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: simulatedBlePeripheral.identifier])
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopScan() {
|
override func stopScan() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Connect
|
|
||||||
override func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
override func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
||||||
|
|
||||||
guard let blePeripheral = peripheral as? BlePeripheralSimulated else { return }
|
|
||||||
blePeripheral.simulateConnect()
|
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||||
}
|
}
|
||||||
|
|
||||||
override func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
override func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Disconnect
|
|
||||||
override func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
|
||||||
guard let blePeripheral = peripheral as? BlePeripheralSimulated else { return }
|
|
||||||
|
|
||||||
DLog("disconnect")
|
|
||||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
|
||||||
|
|
||||||
if waitForQueuedCommands {
|
|
||||||
// Send the disconnection to the command queue, so all the previous command are executed before disconnecting
|
|
||||||
if let centralManager = centralManager {
|
|
||||||
blePeripheral.disconnect(centralManager: centralManager)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
didDisconnectPeripheral(blePeripheral: blePeripheral)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func didDisconnectPeripheral(blePeripheral: BlePeripheralSimulated) {
|
|
||||||
DLog("didDisconnectPeripheral")
|
|
||||||
|
|
||||||
// Clean
|
|
||||||
peripheralsFound[blePeripheral.identifier]?.reset()
|
|
||||||
|
|
||||||
// Notify
|
|
||||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: blePeripheral.identifier])
|
|
||||||
|
|
||||||
// Don't remove the peripheral from the peripheral list (so we can select again the simulated peripheral)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheralSimulated+AdafruitAccelerometer.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 25/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Structs
|
|
||||||
struct AccelerometerValue {
|
|
||||||
var x: Float
|
|
||||||
var y: Float
|
|
||||||
var z: Float
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitAccelerometerEnable(responseHandler: @escaping(Result<(AccelerometerValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitAccelerometerIsEnabled() -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitAccelerometerDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitAccelerometerLastValue() -> AccelerometerValue? {
|
|
||||||
return AccelerometerValue(x: 0, y: 0, z: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitBarometricPressure.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitBarometricPressureResponseDataTimer: Timer?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitBarometricPressureResponseDataTimer: Timer? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureResponseDataTimer) as! Timer?
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitBarometricPressureResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitBarometricPressureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
adafruitBarometricPressureResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard let value = self.adafruitBarometricPressureLastValue() else { return }
|
|
||||||
responseHandler(.success((value, self.identifier)))
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitBarometricPressureIsEnabled() -> Bool {
|
|
||||||
return self.adafruitManufacturerData()?.boardModel == .clue_nRF52840
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitBarometricPressureDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitBarometricPressureLastValue() -> Float? {
|
|
||||||
return Float.random(in: 1190.0 ..< 1191.0)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitHumidity.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 06/03/2020.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitHumidityResponseDataTimer: Timer?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitHumidityResponseDataTimer: Timer? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityResponseDataTimer) as! Timer?
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitHumidityResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitHumidityEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
adafruitHumidityResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard let value = self.adafruitHumidityLastValue() else { return }
|
|
||||||
responseHandler(.success((value, self.identifier)))
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitHumidityIsEnabled() -> Bool {
|
|
||||||
return self.adafruitManufacturerData()?.boardModel == .clue_nRF52840
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitHumidityDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitHumidityLastValue() -> Float? {
|
|
||||||
return Float.random(in: 28.5 ..< 29.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheralSimulated+AdafruitLight.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 13/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitLightResponseDataTimer: Timer?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitLightResponseDataTimer: Timer? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitLightResponseDataTimer) as! Timer?
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitLightResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
adafruitLightResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard let value = self.adafruitLightLastValue() else { return }
|
|
||||||
responseHandler(.success((value, self.identifier)))
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitLightIsEnabled() -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitLightDisable() {
|
|
||||||
adafruitLightResponseDataTimer?.invalidate()
|
|
||||||
adafruitLightResponseDataTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitLightLastValue() -> Float? {
|
|
||||||
let temperature = Float.random(in: 300 ..< 400)
|
|
||||||
return temperature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheralSimulated+AdafruitNeoPixels.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 25/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Config
|
|
||||||
private static let kAdafruitNeoPixelsServiceNumberOfBitsPerPixel = 3
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitNeoPixelsEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelsIsEnabled() -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelsDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
var adafruitNeoPixelsCount: Int {
|
|
||||||
return self.adafruitManufacturerData()?.boardModel?.neoPixelsCount ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelSetAllPixelsColor(_ color: UIColor) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelSetPixelColor(index: Int, color: UIColor) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitNeoPixelSetColor(index: UInt, color: UIColor, pixelMask: [Bool]) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Low level actions
|
|
||||||
func adafruitNeoPixelsWriteData(offset: UInt16, pixelData: Data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
static func pixelDataFromColor(_ color: UIColor) -> Data {
|
|
||||||
let bytes = pixelUInt8FromColor(color)
|
|
||||||
return bytes.data
|
|
||||||
}
|
|
||||||
|
|
||||||
static func pixelUInt8FromColor(_ color: UIColor) -> [UInt8] {
|
|
||||||
return [UInt8]([0, 0, 0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheral+AdafruitQuaternion.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 25/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// Structs
|
|
||||||
struct QuaternionValue {
|
|
||||||
var x: Float
|
|
||||||
var y: Float
|
|
||||||
var z: Float
|
|
||||||
var w: Float
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitQuaternionEnable(responseHandler: @escaping(Result<(QuaternionValue, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitQuaternionIsEnabled() -> Bool {
|
|
||||||
return self.adafruitManufacturerData()?.boardModel == .clue_nRF52840
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitQuaternionDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitQuaternionLastValue() -> QuaternionValue? {
|
|
||||||
return QuaternionValue(x: 0, y: 0, z: 0, w: 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheralSimulated+AdafruitTemperature.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 13/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// MARK: - Custom properties
|
|
||||||
private struct CustomPropertiesKeys {
|
|
||||||
static var adafruitTemperatureResponseDataTimer: Timer?
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adafruitTemperatureResponseDataTimer: Timer? {
|
|
||||||
get {
|
|
||||||
return objc_getAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureResponseDataTimer) as! Timer?
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
objc_setAssociatedObject(self, &CustomPropertiesKeys.adafruitTemperatureResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
|
|
||||||
adafruitTemperatureResponseDataTimer = Timer.scheduledTimer(withTimeInterval: BlePeripheral.kAdafruitSensorDefaultPeriod, repeats: true) { [weak self] _ in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard let value = self.adafruitTemperatureLastValue() else { return }
|
|
||||||
responseHandler(.success((value, self.identifier)))
|
|
||||||
}
|
|
||||||
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitTemperatureIsEnabled() -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitTemperatureDisable() {
|
|
||||||
adafruitTemperatureResponseDataTimer?.invalidate()
|
|
||||||
adafruitTemperatureResponseDataTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitTemperatureLastValue() -> Float? {
|
|
||||||
let temperature = Float.random(in: 18.5 ..< 19.5)
|
|
||||||
return temperature
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// BlePeripheralSimulated+AdafruitToneGenerator.swift
|
|
||||||
// BluefruitPlayground
|
|
||||||
//
|
|
||||||
// Created by Antonio García on 18/11/2019.
|
|
||||||
// Copyright © 2019 Adafruit. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreBluetooth
|
|
||||||
|
|
||||||
extension BlePeripheral {
|
|
||||||
// MARK: - Actions
|
|
||||||
func adafruitToneGeneratorEnable(completion: ((Result<Void, Error>) -> Void)?) {
|
|
||||||
completion?(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitToneGeneratorIsEnabled() -> Bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitToneGeneratorDisable() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func adafruitToneGeneratorStartPlaying(frequency: UInt16, duration: UInt32 = 0) { // Duration 0 means non-stop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// BlePeripheralSimulated+AdafruitButtons.swift
|
// BlePeripheral+CPBButtons.swift
|
||||||
// BluefruitPlayground
|
// BluefruitPlayground
|
||||||
//
|
//
|
||||||
// Created by Antonio García on 15/11/2019.
|
// Created by Antonio García on 15/11/2019.
|
||||||
|
|
@ -10,6 +10,8 @@ import Foundation
|
||||||
import CoreBluetooth
|
import CoreBluetooth
|
||||||
|
|
||||||
extension BlePeripheral {
|
extension BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
|
||||||
enum SlideSwitchState: Int32 {
|
enum SlideSwitchState: Int32 {
|
||||||
case right = 0
|
case right = 0
|
||||||
case left = 1
|
case left = 1
|
||||||
|
|
@ -26,27 +28,28 @@ extension BlePeripheral {
|
||||||
var buttonB: ButtonState
|
var buttonB: ButtonState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
func adafruitButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
completion?(.success(()))
|
completion?(.success(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func adafruitButtonsIsEnabled() -> Bool {
|
func isCpbButtonsEnabled() -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func adafruitButtonsDisable() {
|
func cpbButtonsDisable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func adafruitButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||||
guard let state = adafruitButtonsLastValue() else {
|
guard let state = cpbButtonsLastValue() else {
|
||||||
completion(.failure(PeripheralAdafruitError.invalidResponseData))
|
completion(.failure(PeripheralCPBError.invalidResponseData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(.success((state, self.identifier)))
|
completion(.success((state, self.identifier)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func adafruitButtonsLastValue() -> ButtonsState? {
|
func cpbButtonsLastValue() -> ButtonsState? {
|
||||||
return ButtonsState(slideSwitch: .left, buttonA: .pressed, buttonB: .released)
|
return ButtonsState(slideSwitch: .left, buttonA: .pressed, buttonB: .released)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBLight.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 13/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbLightEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
completion?(.success(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbLightEnabled() -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbLightDisable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbLightLastValue() -> Float? {
|
||||||
|
return 523
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// BlePeripheral+CPBTemperature.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 13/11/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
|
||||||
|
extension BlePeripheral {
|
||||||
|
// MARK: - Custom properties
|
||||||
|
private struct CustomPropertiesKeys {
|
||||||
|
static var cpbTemperatureResponseDataTimer: Timer?
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cpbTemperatureResponseDataTimer: Timer? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureResponseDataTimer) as! Timer?
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &CustomPropertiesKeys.cpbTemperatureResponseDataTimer, newValue, .OBJC_ASSOCIATION_RETAIN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
func cpbTemperatureEnable(responseHandler: @escaping(Result<(Float, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||||
|
|
||||||
|
cpbTemperatureResponseDataTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let temperature = self.cpbTemperatureLastValue() else { return }
|
||||||
|
responseHandler(.success((temperature, self.identifier)))
|
||||||
|
}
|
||||||
|
|
||||||
|
completion?(.success(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCpbTemperatureEnabled() -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbTemperatureDisable() {
|
||||||
|
cpbTemperatureResponseDataTimer?.invalidate()
|
||||||
|
cpbTemperatureResponseDataTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpbTemperatureLastValue() -> Float? {
|
||||||
|
let temperature = Float.random(in: 18.5 ..< 19.5)
|
||||||
|
return temperature
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,38 +10,24 @@ import Foundation
|
||||||
import CoreBluetooth
|
import CoreBluetooth
|
||||||
|
|
||||||
class BlePeripheralSimulated: BlePeripheral {
|
class BlePeripheralSimulated: BlePeripheral {
|
||||||
|
// Constants
|
||||||
|
private static let kSimulatedUUID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private var simulatedIdentifier = UUID()
|
|
||||||
override var identifier: UUID {
|
override var identifier: UUID {
|
||||||
return simulatedIdentifier
|
return BlePeripheralSimulated.kSimulatedUUID
|
||||||
}
|
}
|
||||||
|
|
||||||
override var name: String? {
|
override var name: String? {
|
||||||
let result: String
|
return "Simulated Peripheral"
|
||||||
switch model {
|
}
|
||||||
case .circuitPlaygroundBluefruit:
|
|
||||||
result = "CPB"
|
override var state: CBPeripheralState {
|
||||||
case .clue_nRF52840:
|
return .connected
|
||||||
result = "CLUE"
|
|
||||||
case .feather_nRF52832:
|
|
||||||
result = "Feather"
|
|
||||||
case .feather_nRF52840_express:
|
|
||||||
result = "Feather Express"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private var simulatedState: CBPeripheralState = .disconnected
|
|
||||||
override var state: CBPeripheralState {
|
|
||||||
return simulatedState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var model: AdafruitManufacturerData.BoardModel
|
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
// MARK: - Lifecycle
|
||||||
init(model: AdafruitManufacturerData.BoardModel) {
|
init() {
|
||||||
self.model = model
|
|
||||||
|
|
||||||
// Mocking CBPeripheral: https://forums.developer.apple.com/thread/29851
|
// Mocking CBPeripheral: https://forums.developer.apple.com/thread/29851
|
||||||
guard let peripheral = ObjectBuilder.createInstance(ofClass: "CBPeripheral") as? CBPeripheral else {
|
guard let peripheral = ObjectBuilder.createInstance(ofClass: "CBPeripheral") as? CBPeripheral else {
|
||||||
assertionFailure("Unable to mock CBPeripheral")
|
assertionFailure("Unable to mock CBPeripheral")
|
||||||
|
|
@ -49,34 +35,16 @@ class BlePeripheralSimulated: BlePeripheral {
|
||||||
super.init(peripheral: nilPeripheral, advertisementData: nil, rssi: nil)
|
super.init(peripheral: nilPeripheral, advertisementData: nil, rssi: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
peripheral.addObserver(peripheral, forKeyPath: "delegate", options: .new, context: nil)
|
peripheral.addObserver(peripheral, forKeyPath: "delegate", options: .new, context: nil)
|
||||||
|
|
||||||
let adafruitManufacturerIdentifier = BlePeripheral.kManufacturerAdafruitIdentifier
|
let manufacturerDataBytes: [UInt8] = [0x22, 0x08, 0x04, 0x01, 0x00, 0x45, 0x80] // Adafruit CPB
|
||||||
let boardId = model.identifier.first!
|
|
||||||
let boardField: [UInt8] = [0x04, 0x01, 0x00] + boardId
|
|
||||||
let manufacturerDataBytes: [UInt8] = adafruitManufacturerIdentifier + boardField
|
|
||||||
let advertisementData = [CBAdvertisementDataManufacturerDataKey: Data(manufacturerDataBytes)]
|
let advertisementData = [CBAdvertisementDataManufacturerDataKey: Data(manufacturerDataBytes)]
|
||||||
super.init(peripheral: peripheral, advertisementData: advertisementData, rssi: 20)
|
super.init(peripheral: peripheral, advertisementData: advertisementData, rssi: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Discover
|
// MARK: - Discover
|
||||||
override func discover(serviceUuids: [CBUUID]?, completion: ((Error?) -> Void)?) {
|
override func discover(serviceUuids: [CBUUID]?, completion: ((Error?) -> Void)?) {
|
||||||
completion?(nil)
|
completion?(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Connect
|
|
||||||
func simulateConnect() {
|
|
||||||
simulatedState = .connected
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Disconnect
|
|
||||||
override internal func disconnect(with command: BleCommand) {
|
|
||||||
// Simulate disconnection
|
|
||||||
simulatedState = .disconnected
|
|
||||||
BleManagerSimulated.simulated.didDisconnectPeripheral(blePeripheral: self)
|
|
||||||
|
|
||||||
// Finished
|
|
||||||
finishedExecutingCommand(error: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol UartPacketManagerDelegate: AnyObject {
|
protocol UartPacketManagerDelegate: class {
|
||||||
func onUartPacket(_ packet: UartPacket)
|
func onUartPacket(_ packet: UartPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ struct UartPacket { // A packet of data received or sent
|
||||||
var mode: TransferMode
|
var mode: TransferMode
|
||||||
var data: Data
|
var data: Data
|
||||||
var peripheralId: UUID?
|
var peripheralId: UUID?
|
||||||
|
|
||||||
init(peripheralId: UUID?, timestamp: CFAbsoluteTime? = nil, mode: TransferMode, data: Data) {
|
init(peripheralId: UUID?, timestamp: CFAbsoluteTime? = nil, mode: TransferMode, data: Data) {
|
||||||
self.peripheralId = peripheralId
|
self.peripheralId = peripheralId
|
||||||
self.timestamp = timestamp ?? CFAbsoluteTimeGetCurrent()
|
self.timestamp = timestamp ?? CFAbsoluteTimeGetCurrent()
|
||||||
|
|
@ -30,32 +30,34 @@ struct UartPacket { // A packet of data received or sent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UartPacketManagerBase {
|
|
||||||
|
|
||||||
|
class UartPacketManagerBase {
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
internal weak var delegate: UartPacketManagerDelegate?
|
internal weak var delegate: UartPacketManagerDelegate?
|
||||||
internal var packets = [UartPacket]()
|
internal var packets = [UartPacket]()
|
||||||
internal var packetsSemaphore = DispatchSemaphore(value: 1)
|
internal var packetsSemaphore = DispatchSemaphore(value: 1)
|
||||||
internal var isMqttEnabled: Bool
|
internal var isMqttEnabled: Bool
|
||||||
internal var isPacketCacheEnabled: Bool
|
internal var isPacketCacheEnabled: Bool
|
||||||
|
|
||||||
var receivedBytes: Int64 = 0
|
var receivedBytes: Int64 = 0
|
||||||
var sentBytes: Int64 = 0
|
var sentBytes: Int64 = 0
|
||||||
|
|
||||||
init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
||||||
self.isPacketCacheEnabled = isPacketCacheEnabled
|
self.isPacketCacheEnabled = isPacketCacheEnabled
|
||||||
self.isMqttEnabled = isMqttEnabled
|
self.isMqttEnabled = isMqttEnabled
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Received data
|
// MARK: - Received data
|
||||||
func rxPacketReceived(data: Data?, peripheralIdentifier: UUID?, error: Error?) {
|
func rxPacketReceived(data: Data?, peripheralIdentifier: UUID?, error: Error?) {
|
||||||
|
|
||||||
guard error == nil else { DLog("uartRxPacketReceived error: \(error!)"); return }
|
guard error == nil else { DLog("uartRxPacketReceived error: \(error!)"); return }
|
||||||
guard let data = data else { return }
|
guard let data = data else { return }
|
||||||
|
|
||||||
let uartPacket = UartPacket(peripheralId: peripheralIdentifier, mode: .rx, data: data)
|
let uartPacket = UartPacket(peripheralId: peripheralIdentifier, mode: .rx, data: data)
|
||||||
|
|
||||||
// Mqtt publish to RX. TODO: Remove the dependency with MqttSettings and pass parameters
|
// Mqtt publish to RX. TODO: Remove the dependency with MqttSettings and pass parameters
|
||||||
#if MQTT_ENABLED
|
#if MQTT_ENABLED
|
||||||
if isMqttEnabled {
|
if isMqttEnabled {
|
||||||
|
|
@ -70,31 +72,31 @@ class UartPacketManagerBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
packetsSemaphore.wait() // don't append more data, till the delegate has finished processing it
|
packetsSemaphore.wait() // don't append more data, till the delegate has finished processing it
|
||||||
receivedBytes += Int64(data.count)
|
receivedBytes += Int64(data.count)
|
||||||
if isPacketCacheEnabled {
|
if isPacketCacheEnabled {
|
||||||
packets.append(uartPacket)
|
packets.append(uartPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data to delegate
|
// Send data to delegate
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.onUartPacket(uartPacket)
|
self.delegate?.onUartPacket(uartPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
//DLog("packetsData: \(packetsData.count)")
|
//DLog("packetsData: \(packetsData.count)")
|
||||||
|
|
||||||
packetsSemaphore.signal()
|
packetsSemaphore.signal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearPacketsCache() {
|
func clearPacketsCache() {
|
||||||
packets.removeAll()
|
packets.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func packetsCache() -> [UartPacket] {
|
func packetsCache() -> [UartPacket] {
|
||||||
return packets
|
return packets
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Counters
|
// MARK: - Counters
|
||||||
func resetCounters() {
|
func resetCounters() {
|
||||||
receivedBytes = 0
|
receivedBytes = 0
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class CommandQueue<Element> {
|
||||||
func executeNext() {
|
func executeNext() {
|
||||||
queueLock.lock()
|
queueLock.lock()
|
||||||
guard !queue.isEmpty else { queueLock.unlock(); return }
|
guard !queue.isEmpty else { queueLock.unlock(); return }
|
||||||
|
|
||||||
//DLog("queue remove finished: \(queue.first)")
|
//DLog("queue remove finished: \(queue.first)")
|
||||||
// Delete finished command and trigger next execution if needed
|
// Delete finished command and trigger next execution if needed
|
||||||
queue.removeFirst()
|
queue.removeFirst()
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,19 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
extension Data {
|
extension Data {
|
||||||
func toFloatFrom32Bits() -> Float {
|
func toFloatFrom32Bits() -> Float {
|
||||||
return Float(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
|
return Float(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
func toIntFrom32Bits() -> Int {
|
func toIntFrom32Bits() -> Int {
|
||||||
return Int(Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) })))
|
return Int(Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) })))
|
||||||
}
|
}
|
||||||
|
|
||||||
func toInt32From32Bits() -> Int32 {
|
func toInt32From32Bits() -> Int32 {
|
||||||
return Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
|
return Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ struct HexUtils {
|
||||||
static func hexDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
|
static func hexDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
|
||||||
return data.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
|
return data.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func hexDescription(bytes: [UInt8], prefix: String = "", postfix: String = " ") -> String {
|
static func hexDescription(bytes: [UInt8], prefix: String = "", postfix: String = " ") -> String {
|
||||||
return bytes.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
|
return bytes.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func decimalDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
|
static func decimalDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
|
||||||
return data.reduce("") {$0 + String(format: "%@%ld%@", prefix, $1, postfix)}
|
return data.reduce("") {$0 + String(format: "%@%ld%@", prefix, $1, postfix)}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ extension UInt16: UIntToBytesConvertable {
|
||||||
|
|
||||||
extension UInt32: UIntToBytesConvertable {
|
extension UInt32: UIntToBytesConvertable {
|
||||||
var toBytes: [UInt8] {
|
var toBytes: [UInt8] {
|
||||||
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
|
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// TouchReleaseRectangularPaletteControl.swift
|
||||||
|
// BluefruitPlayground
|
||||||
|
//
|
||||||
|
// Created by Antonio García on 16/12/2019.
|
||||||
|
// Copyright © 2019 Adafruit. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import FlexColorPicker
|
||||||
|
|
||||||
|
class TouchReleaseRectangularPaletteControl: RectangularPaletteControl {
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Only override draw() if you perform custom drawing.
|
||||||
|
// An empty implementation adversely affects performance during animation.
|
||||||
|
override func draw(_ rect: CGRect) {
|
||||||
|
// Drawing code
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ extension DataConvertible where Self: ExpressibleByIntegerLiteral {
|
||||||
init?(data: Data) {
|
init?(data: Data) {
|
||||||
var value: Self = 0
|
var value: Self = 0
|
||||||
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
|
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
|
||||||
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)})
|
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
|
||||||
self = value
|
self = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,6 +39,7 @@ extension UInt32: DataConvertible { }
|
||||||
extension Float: DataConvertible { }
|
extension Float: DataConvertible { }
|
||||||
extension Double: DataConvertible { }
|
extension Double: DataConvertible { }
|
||||||
|
|
||||||
|
|
||||||
// Convert from [UInt8] to Data and from Data to [UInt8]
|
// Convert from [UInt8] to Data and from Data to [UInt8]
|
||||||
// from: https://stackoverflow.com/questions/31821709/nsdata-to-uint8-in-swift/31821838
|
// from: https://stackoverflow.com/questions/31821709/nsdata-to-uint8-in-swift/31821838
|
||||||
extension Data {
|
extension Data {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
|
||||||
startup()
|
startup()
|
||||||
|
|
||||||
ScreenFlowManager.enableBleStateManagement()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,8 +40,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func applicationWillTerminate(_ application: UIApplication) {
|
func applicationWillTerminate(_ application: UIApplication) {
|
||||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||||
|
|
||||||
ScreenFlowManager.disableBleStateManagement()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Startup
|
// MARK: - Startup
|
||||||
|
|
@ -52,7 +49,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
UINavigationBar.appearance().prefersLargeTitles = ConfigUI.prefersLargeTitles
|
UINavigationBar.appearance().prefersLargeTitles = ConfigUI.prefersLargeTitles
|
||||||
|
|
||||||
// Navigation bar: add background when large title is used
|
// Navigation bar: add background when large title is used
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
let appearance = UINavigationBarAppearance()
|
let appearance = UINavigationBarAppearance()
|
||||||
|
|
@ -71,3 +68,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "scanning_background.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "scanning_background@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "scanning_background@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "bluetooth_status.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "bluetooth_status@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "bluetooth_status@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_clue_back.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_clue_back@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_clue_back@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 687 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_clue_front.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_clue_front@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_clue_front@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 563 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_cpb.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_cpb@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "board_cpb@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 466 KiB |
|
Before Width: | Height: | Size: 745 KiB |
|
Before Width: | Height: | Size: 996 B After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
|
@ -7,12 +7,12 @@
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"color" : {
|
"color" : {
|
||||||
"color-space" : "srgb",
|
"color-space" : "display-p3",
|
||||||
"components" : {
|
"components" : {
|
||||||
"red" : "0.949",
|
"red" : "0.010",
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.961",
|
"blue" : "0.802",
|
||||||
"green" : "0.961"
|
"green" : "0.219"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "display-p3",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0.889",
|
|
||||||
"alpha" : "0.800",
|
|
||||||
"blue" : "0.360",
|
|
||||||
"green" : "0.400"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0x00",
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x80",
|
|
||||||
"green" : "0x80"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0.988",
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0.118",
|
|
||||||
"green" : "1.000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0xFF",
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0xB4",
|
|
||||||
"green" : "0xE5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0x00",
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x00",
|
|
||||||
"green" : "0x80"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "srgb",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0xFF",
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0x00",
|
|
||||||
"green" : "0x00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"colors" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"color" : {
|
|
||||||
"color-space" : "display-p3",
|
|
||||||
"components" : {
|
|
||||||
"red" : "0.275",
|
|
||||||
"alpha" : "1.000",
|
|
||||||
"blue" : "0.773",
|
|
||||||
"green" : "1.000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -2,17 +2,17 @@
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "scan_cpb.png",
|
"filename" : "cpb_circuit.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "scan_cpb@2x.png",
|
"filename" : "cpb_circuit@2x.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"filename" : "scan_cpb@3x.png",
|
"filename" : "cpb_circuit@3x.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
BIN
BluefruitPlayground/Assets.xcassets/cpb/cpb_circuit.imageset/cpb_circuit.png
vendored
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
BluefruitPlayground/Assets.xcassets/cpb/cpb_circuit.imageset/cpb_circuit@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 356 KiB |
BIN
BluefruitPlayground/Assets.xcassets/cpb/cpb_circuit.imageset/cpb_circuit@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 744 KiB |
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "humidity_fill.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "humidity_fill@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "humidity_fill@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 27 KiB |