Compare commits
80 commits
puppetModu
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e43d9a74 | ||
|
|
5e5995eb5d | ||
|
|
78a3e93679 | ||
|
|
47ffad0c0d | ||
|
|
82a6a37a9b | ||
|
|
4c924c2605 | ||
|
|
d290b65d91 | ||
|
|
f17d2017ce | ||
|
|
ad8e7ffade | ||
|
|
251bd8ec31 | ||
|
|
4d268e4f4f | ||
|
|
e2bd12b539 | ||
|
|
969e1eb7c0 | ||
|
|
d22f07df7d | ||
|
|
60c0ae8c6b | ||
|
|
263c1163e9 | ||
|
|
c089e932da | ||
|
|
5125b08706 | ||
|
|
5f22e72211 | ||
|
|
542f5ca8c8 | ||
|
|
0d33821c88 | ||
|
|
76ac60c734 | ||
|
|
5f9bfb3ee9 | ||
|
|
ec13361e4f | ||
|
|
7c7ae8578f | ||
|
|
3fdf7a15fe | ||
|
|
cd834da0a1 | ||
|
|
d487170a96 | ||
|
|
d706ae85fd | ||
|
|
0d7c091fb6 | ||
|
|
5e9eeabe5e | ||
|
|
d6269f59be | ||
|
|
13a4c4ff3d | ||
|
|
49b2508da4 | ||
|
|
44e4654301 | ||
|
|
b5394dab0a | ||
|
|
36872363d8 | ||
|
|
cdf816f848 | ||
|
|
380891dceb | ||
|
|
2090698fc4 | ||
|
|
91d5322795 | ||
|
|
019befae61 | ||
|
|
c02a36c976 | ||
|
|
3feac2a2e5 | ||
|
|
a61def2b4c | ||
|
|
1c0cb8235e | ||
|
|
c1a88e264e | ||
|
|
a1cc6d6306 | ||
|
|
5244715073 | ||
|
|
19768e4c52 | ||
|
|
d1e72eb47e | ||
|
|
90da0e9896 | ||
|
|
519022fcf5 | ||
|
|
0b7259834e | ||
|
|
3ba4fa2d9d | ||
|
|
213910c54a | ||
|
|
b020a9ee43 | ||
|
|
4eec8bb5b7 | ||
|
|
a44b33e66f | ||
|
|
031bab1241 | ||
|
|
f48758ed77 | ||
|
|
b96425596a | ||
|
|
148a33a5d8 | ||
|
|
e326b0b9d5 | ||
|
|
a220f3dec4 | ||
|
|
e4f4c8bcd1 | ||
|
|
a105506ba6 | ||
|
|
170809c2f8 | ||
|
|
f75db839ff | ||
|
|
0bf2400b88 | ||
|
|
1c98c92bac | ||
|
|
379d61a3b1 | ||
|
|
f5af18568e | ||
|
|
f56cfee413 | ||
|
|
3933990111 | ||
|
|
eb4182c30e | ||
|
|
1dab5a618e | ||
|
|
eacad0dc42 | ||
|
|
462ea1c289 | ||
|
|
9c8d6f2e00 |
26
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
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,6 +28,8 @@
|
|||
<string>This app needs access to Bluetooth to connect to Circuit Playground Bluefruit devices</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<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>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1120"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1130"
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// 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)")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// 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,29 +8,24 @@
|
|||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
import QuartzCore
|
||||
|
||||
#if COMMANDLINE
|
||||
#else
|
||||
import MSWeakTimer
|
||||
#endif
|
||||
|
||||
class BleManager: NSObject {
|
||||
public class BleManager: NSObject {
|
||||
// Configuration
|
||||
private static let kStopScanningWhenConnectingToPeripheral = false
|
||||
private static let kAlwaysAllowDuplicateKeys = true
|
||||
|
||||
// Singleton
|
||||
static let shared = BleManager()
|
||||
public static let shared = BleManager()
|
||||
|
||||
// Ble
|
||||
var centralManager: CBCentralManager?
|
||||
private var centralManagerPoweredOnSemaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
// Scanning
|
||||
var isScanning: Bool {
|
||||
public var isScanning: Bool {
|
||||
return scanningStartTime != nil
|
||||
}
|
||||
var scanningElapsedTime: TimeInterval? {
|
||||
public var scanningElapsedTime: TimeInterval? {
|
||||
guard let scanningStartTime = scanningStartTime else { return nil }
|
||||
return CACurrentMediaTime() - scanningStartTime
|
||||
}
|
||||
|
|
@ -38,20 +33,22 @@ class BleManager: NSObject {
|
|||
internal var scanningStartTime: TimeInterval? // Time when the scanning started. nil if stopped
|
||||
private var scanningServicesFilter: [CBUUID]?
|
||||
internal var peripheralsFound = [UUID: BlePeripheral]()
|
||||
private var peripheralsFoundLock = NSLock()
|
||||
private var peripheralsFoundFirstTime = [UUID: Date]() // Date that the perihperal was discovered for the first time. Useful for sorting
|
||||
internal var peripheralsFoundLock = NSLock()
|
||||
|
||||
// Connecting
|
||||
private var connectionTimeoutTimers = [UUID: MSWeakTimer]()
|
||||
private var connectionTimeoutTimers = [UUID: Foundation.Timer]()
|
||||
private var autoreconnectOnDisconnection = Set<UUID>() // List of peripheral IDs to automatically reconnect if disconnected
|
||||
|
||||
// Notifications
|
||||
enum NotificationUserInfoKey: String {
|
||||
public enum NotificationUserInfoKey: String {
|
||||
case uuid = "uuid"
|
||||
case error = "error"
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
centralManagerPoweredOnSemaphore.wait()
|
||||
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.global(qos: .background), options: [:])
|
||||
// centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main, options: [:])
|
||||
}
|
||||
|
|
@ -59,20 +56,16 @@ class BleManager: NSObject {
|
|||
deinit {
|
||||
scanningServicesFilter?.removeAll()
|
||||
peripheralsFound.removeAll()
|
||||
peripheralsFoundFirstTime.removeAll()
|
||||
}
|
||||
|
||||
|
||||
public var state: CBManagerState {
|
||||
return centralManager?.state ?? .unknown
|
||||
}
|
||||
|
||||
|
||||
func restoreCentralManager() {
|
||||
DLog("Restoring central manager")
|
||||
/*
|
||||
guard centralManager?.delegate !== self else {
|
||||
DLog("No need to restore it. It it still ours")
|
||||
return
|
||||
}*/
|
||||
|
||||
|
||||
// Restore peripherals status
|
||||
peripheralsFoundLock.lock()
|
||||
|
||||
|
|
@ -101,12 +94,9 @@ class BleManager: NSObject {
|
|||
startScan()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Scan
|
||||
func startScan(withServices services: [CBUUID]? = nil) {
|
||||
centralManagerPoweredOnSemaphore.wait()
|
||||
centralManagerPoweredOnSemaphore.signal()
|
||||
|
||||
// MARK: - Scan
|
||||
public func startScan(withServices services: [CBUUID]? = nil) {
|
||||
isScanningWaitingToStart = true
|
||||
guard let centralManager = centralManager, centralManager.state != .poweredOff && centralManager.state != .unauthorized && centralManager.state != .unsupported else {
|
||||
DLog("startScan failed because central manager is not ready")
|
||||
|
|
@ -129,32 +119,55 @@ class BleManager: NSObject {
|
|||
isScanningWaitingToStart = false
|
||||
}
|
||||
|
||||
func stopScan() {
|
||||
public func stopScan() {
|
||||
// DLog("stop scan")
|
||||
centralManager?.stopScan()
|
||||
scanningStartTime = nil
|
||||
isScanningWaitingToStart = false
|
||||
NotificationCenter.default.post(name: .didStopScanning, object: nil)
|
||||
}
|
||||
|
||||
func peripherals() -> [BlePeripheral] {
|
||||
|
||||
public func numPeripherals() -> Int {
|
||||
return peripheralsFound.count
|
||||
}
|
||||
|
||||
public func peripherals() -> [BlePeripheral] {
|
||||
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
||||
return Array(peripheralsFound.values)
|
||||
}
|
||||
|
||||
func connectedPeripherals() -> [BlePeripheral] {
|
||||
public func peripheralsSortedByFirstDiscovery() -> [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}
|
||||
}
|
||||
|
||||
func connectingPeripherals() -> [BlePeripheral] {
|
||||
public func connectingPeripherals() -> [BlePeripheral] {
|
||||
return peripherals().filter {$0.state == .connecting}
|
||||
}
|
||||
|
||||
func connectedOrConnectingPeripherals() -> [BlePeripheral] {
|
||||
|
||||
public func connectedOrConnectingPeripherals() -> [BlePeripheral] {
|
||||
return peripherals().filter {$0.state == .connected || $0.state == .connecting}
|
||||
}
|
||||
|
||||
func refreshPeripherals() {
|
||||
public func refreshPeripherals() {
|
||||
stopScan()
|
||||
|
||||
peripheralsFoundLock.lock()
|
||||
|
|
@ -162,6 +175,7 @@ class BleManager: NSObject {
|
|||
for (identifier, peripheral) in peripheralsFound {
|
||||
if peripheral.state != .connected && peripheral.state != .connecting {
|
||||
peripheralsFound.removeValue(forKey: identifier)
|
||||
peripheralsFoundFirstTime.removeValue(forKey: identifier)
|
||||
}
|
||||
}
|
||||
peripheralsFoundLock.unlock()
|
||||
|
|
@ -172,12 +186,14 @@ class BleManager: NSObject {
|
|||
}
|
||||
|
||||
// MARK: - Connection Management
|
||||
func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
||||
public func connect(to peripheral: BlePeripheral, timeout: TimeInterval? = nil, shouldNotifyOnConnection: Bool = false, shouldNotifyOnDisconnection: Bool = false, shouldNotifyOnNotification: Bool = false) {
|
||||
|
||||
centralManagerPoweredOnSemaphore.wait()
|
||||
centralManagerPoweredOnSemaphore.signal()
|
||||
|
||||
// Stop scanning when connecting to a peripheral
|
||||
guard centralManager?.state == .poweredOn else {
|
||||
DLog("connect failed because central manager is not ready")
|
||||
return
|
||||
}
|
||||
|
||||
// Stop scanning when connecting to a peripheral
|
||||
if BleManager.kStopScanningWhenConnectingToPeripheral {
|
||||
stopScan()
|
||||
}
|
||||
|
|
@ -196,14 +212,14 @@ class BleManager: NSObject {
|
|||
#endif
|
||||
|
||||
if let timeout = timeout {
|
||||
connectionTimeoutTimers[peripheral.identifier] = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false, dispatchQueue: .global(qos: .background))
|
||||
self.connectionTimeoutTimers[peripheral.identifier] = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.connectionTimeoutFired), userInfo: peripheral.identifier, repeats: false)
|
||||
}
|
||||
centralManager?.connect(peripheral.peripheral, options: options)
|
||||
}
|
||||
|
||||
@objc private func connectionTimeoutFired(timer: MSWeakTimer) {
|
||||
let peripheralIdentifier = timer.userInfo() as! UUID
|
||||
DLog("connection timeout fired: \(peripheralIdentifier)")
|
||||
@objc private func connectionTimeoutFired(timer: Foundation.Timer) {
|
||||
let peripheralIdentifier = timer.userInfo as! UUID
|
||||
DLog("connection timeout for: \(peripheralIdentifier)")
|
||||
connectionTimeoutTimers[peripheralIdentifier] = nil
|
||||
|
||||
NotificationCenter.default.post(name: .willDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
||||
|
|
@ -211,45 +227,57 @@ class BleManager: NSObject {
|
|||
if let blePeripheral = peripheralsFound[peripheralIdentifier] {
|
||||
centralManager?.cancelPeripheralConnection(blePeripheral.peripheral)
|
||||
} else {
|
||||
DLog("simulate disconnection")
|
||||
//DLog("simulate disconnection")
|
||||
// The blePeripheral is available on peripheralsFound, so simulate the disconnection
|
||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheralIdentifier])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
||||
public func disconnect(from peripheral: BlePeripheral, waitForQueuedCommands: Bool = false) {
|
||||
guard let centralManager = centralManager else { return}
|
||||
|
||||
DLog("disconnect: \(peripheral.identifier)")
|
||||
autoreconnectOnDisconnection.remove(peripheral.identifier) // Disable autoreconnection because user initiated the disconection
|
||||
|
||||
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
|
||||
peripheral.disconnect(centralManager: centralManager)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
centralManager.cancelPeripheralConnection(peripheral.peripheral)
|
||||
}
|
||||
}
|
||||
|
||||
func discoverConnectedPeripherals(services: [CBUUID]) {
|
||||
guard let centralManager = centralManager else { return}
|
||||
|
||||
func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
var reconnecting = false
|
||||
|
||||
let knownPeripherals = centralManager?.retrievePeripherals(withIdentifiers: identifiers)
|
||||
if let peripherals = knownPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
|
||||
for peripheral in peripherals {
|
||||
discovered(peripheral: peripheral)
|
||||
if let blePeripheral = peripheralsFound[peripheral.identifier] {
|
||||
connect(to: blePeripheral, timeout: timeout)
|
||||
reconnecting = true
|
||||
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) {
|
||||
DLog("Discovered peripheral with known service: \(peripheral.identifier)")
|
||||
let advertisementData = [CBAdvertisementDataServiceUUIDsKey: services]
|
||||
discovered(peripheral: peripheral, advertisementData: advertisementData )
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let connectedPeripherals = centralManager?.retrieveConnectedPeripherals(withServices: services)
|
||||
if let peripherals = connectedPeripherals?.filter({identifiers.contains($0.identifier)}), !peripherals.isEmpty {
|
||||
for peripheral in peripherals {
|
||||
discovered(peripheral: peripheral)
|
||||
}
|
||||
}
|
||||
func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
|
||||
guard let centralManager = centralManager else { return false }
|
||||
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] {
|
||||
connect(to: blePeripheral, timeout: timeout)
|
||||
reconnecting = true
|
||||
|
|
@ -257,17 +285,35 @@ 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
|
||||
}
|
||||
|
||||
private func discovered(peripheral: CBPeripheral, advertisementData: [String: Any]? = nil, rssi: Int? = nil) {
|
||||
peripheralsFoundLock.lock(); defer { peripheralsFoundLock.unlock() }
|
||||
|
||||
|
||||
if let existingPeripheral = peripheralsFound[peripheral.identifier] {
|
||||
existingPeripheral.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
if let rssi = rssi, rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
|
||||
if let rssi = rssi, rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
|
||||
existingPeripheral.rssi = rssi
|
||||
}
|
||||
|
||||
|
|
@ -280,30 +326,35 @@ class BleManager: NSObject {
|
|||
} else { // New peripheral found
|
||||
let blePeripheral = BlePeripheral(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
||||
peripheralsFound[peripheral.identifier] = blePeripheral
|
||||
peripheralsFoundFirstTime[peripheral.identifier] = Date()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Notifications
|
||||
func peripheral(from notification: Notification) -> BlePeripheral? {
|
||||
guard let uuid = notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID else { return nil }
|
||||
public func peripheralUUID(from notification: Notification) -> UUID? {
|
||||
return notification.userInfo?[NotificationUserInfoKey.uuid.rawValue] as? UUID
|
||||
}
|
||||
|
||||
public func peripheral(from notification: Notification) -> BlePeripheral? {
|
||||
guard let uuid = peripheralUUID(from: notification) else { return nil }
|
||||
|
||||
return peripheral(with: uuid)
|
||||
}
|
||||
|
||||
func peripheral(with uuid: UUID) -> BlePeripheral? {
|
||||
public func error(from notification: Notification) -> Error? {
|
||||
return notification.userInfo?[NotificationUserInfoKey.error.rawValue] as? Error
|
||||
}
|
||||
|
||||
public func peripheral(with uuid: UUID) -> BlePeripheral? {
|
||||
return peripheralsFound[uuid]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CBCentralManagerDelegate
|
||||
extension BleManager: CBCentralManagerDelegate {
|
||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
|
||||
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
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
|
||||
if central.state == .poweredOn {
|
||||
|
|
@ -315,73 +366,106 @@ extension BleManager: CBCentralManagerDelegate {
|
|||
isScanningWaitingToStart = true
|
||||
}
|
||||
scanningStartTime = nil
|
||||
|
||||
|
||||
// Remove all peripherals found (Important because the BlePeripheral queues could contain old commands that were processing when the bluetooth state changed)
|
||||
peripheralsFound.removeAll()
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .didUpdateBleState, object: nil)
|
||||
|
||||
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]) {
|
||||
|
||||
}*/
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||
// DLog("didDiscover: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
let rssi = RSSI.intValue
|
||||
DispatchQueue.main.async { // This Fixes iOS12 race condition on cached filtered peripherals. TODO: investigate
|
||||
self.discovered(peripheral: peripheral, advertisementData: advertisementData, rssi: rssi)
|
||||
NotificationCenter.default.post(name: .didDiscoverPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
DLog("didConnect: \(peripheral.identifier)")
|
||||
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
DLog("didConnect: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
|
||||
// Remove connection timeout if exists
|
||||
if let timer = connectionTimeoutTimers[peripheral.identifier] {
|
||||
timer.invalidate()
|
||||
connectionTimeoutTimers[peripheral.identifier] = nil
|
||||
}
|
||||
|
||||
|
||||
// Set reconnection flag
|
||||
autoreconnectOnDisconnection.insert(peripheral.identifier)
|
||||
|
||||
// Send notification
|
||||
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
}
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
DLog("didFailToConnect")
|
||||
|
||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||
DLog("didDisconnectPeripheral")
|
||||
|
||||
|
||||
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
DLog("didFailToConnect: \(peripheral.name ?? peripheral.identifier.uuidString). \(String(describing: error))")
|
||||
|
||||
// Clean
|
||||
peripheralsFound[peripheral.identifier]?.reset()
|
||||
|
||||
|
||||
// Notify
|
||||
NotificationCenter.default.post(name: .didDisconnectFromPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
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?) {
|
||||
|
||||
let peripheralIdentifier = peripheral.identifier
|
||||
DLog("didDisconnectPeripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
|
||||
|
||||
// Remove from peripheral list (after sending notification so the receiving objects can query about the peripheral before being removed)
|
||||
peripheralsFoundLock.lock()
|
||||
peripheralsFound.removeValue(forKey: peripheral.identifier)
|
||||
peripheralsFoundLock.unlock()
|
||||
// Clean
|
||||
peripheralsFound[peripheralIdentifier]?.reset()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// Try to reconnect automatically
|
||||
if self.autoreconnectOnDisconnection.contains(peripheralIdentifier),
|
||||
let blePeripheral = self.peripheralsFound[peripheralIdentifier] {
|
||||
self.autoreconnectOnDisconnection.remove(peripheral.identifier)
|
||||
|
||||
DLog("Trying to reconnect to peripheral: \(peripheral.name ?? peripheralIdentifier.uuidString)")
|
||||
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
|
||||
extension Notification.Name {
|
||||
private static let kPrefix = Bundle.main.bundleIdentifier!
|
||||
static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
|
||||
static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
|
||||
static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
|
||||
static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
|
||||
static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
|
||||
static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
|
||||
static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
|
||||
static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
|
||||
static let didDisconnectFromPeripheral = Notification.Name(kPrefix+".didDisconnectFromPeripheral")
|
||||
public static let didUpdateBleState = Notification.Name(kPrefix+".didUpdateBleState")
|
||||
public static let didStartScanning = Notification.Name(kPrefix+".didStartScanning")
|
||||
public static let didStopScanning = Notification.Name(kPrefix+".didStopScanning")
|
||||
public static let didDiscoverPeripheral = Notification.Name(kPrefix+".didDiscoverPeripheral")
|
||||
public static let didUnDiscoverPeripheral = Notification.Name(kPrefix+".didUnDiscoverPeripheral")
|
||||
public static let willConnectToPeripheral = Notification.Name(kPrefix+".willConnectToPeripheral")
|
||||
public static let didConnectToPeripheral = Notification.Name(kPrefix+".didConnectToPeripheral")
|
||||
public static let willDisconnectFromPeripheral = Notification.Name(kPrefix+".willDisconnectFromPeripheral")
|
||||
public 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)) {
|
||||
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 }
|
||||
|
||||
|
||||
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 ?? "")")
|
||||
handler(-1, error)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let level = Int(data[0]) // from 0 to 100
|
||||
handler(level, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func startReadingBatteryLevel(handler: @escaping ((Int) -> Void)) {
|
||||
|
||||
|
||||
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 }
|
||||
|
||||
|
||||
// Read current value
|
||||
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 }
|
||||
|
||||
|
||||
let level = Int(data[0]) // from 0 to 100
|
||||
handler(level)
|
||||
}
|
||||
|
||||
|
||||
// Enable notifications to receive value changes
|
||||
self.enableNotify(for: characteristic, handler: { error in
|
||||
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 }
|
||||
|
||||
|
||||
let level = Int(data[0]) // from 0 to 100
|
||||
handler(level)
|
||||
})
|
||||
|
|
@ -60,16 +60,16 @@ extension BlePeripheral {
|
|||
func stopReadingBatteryLevel() {
|
||||
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 }
|
||||
|
||||
|
||||
self.disableNotify(for: characteristic)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Utils
|
||||
func isBatteryAdvertised() -> Bool {
|
||||
return advertisement.services?.contains(BlePeripheral.kBatteryServiceUUID) ?? false
|
||||
}
|
||||
|
||||
|
||||
func hasBattery() -> Bool {
|
||||
return peripheral.services?.first(where: {$0.uuid == BlePeripheral.kBatteryServiceUUID}) != nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,10 +72,13 @@ extension BlePeripheral {
|
|||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
func uartEnable(uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
|
||||
func uartEnable(uartServiceUuid: CBUUID = BlePeripheral.kUartServiceUUID,
|
||||
txCharacteristicUuid: CBUUID = BlePeripheral.kUartTxCharacteristicUUID,
|
||||
rxCharacteristicUuid: CBUUID = BlePeripheral.kUartRxCharacteristicUUID,
|
||||
uartRxHandler: ((Data?, UUID, Error?) -> Void)?, completion: ((Error?) -> Void)?) {
|
||||
|
||||
// Get uart communications characteristic
|
||||
characteristic(uuid: BlePeripheral.kUartTxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
|
||||
characteristic(uuid: txCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
|
||||
guard let characteristic = characteristic, error == nil else {
|
||||
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
||||
return
|
||||
|
|
@ -85,7 +88,7 @@ extension BlePeripheral {
|
|||
self.uartTxCharacteristicWriteType = characteristic.properties.contains(.writeWithoutResponse) ? .withoutResponse:.withResponse
|
||||
//self.uartTxCharacteristicWriteType = .withResponse // Debug: force withResponse
|
||||
|
||||
self.characteristic(uuid: BlePeripheral.kUartRxCharacteristicUUID, serviceUuid: BlePeripheral.kUartServiceUUID) { [unowned self] (characteristic, error) in
|
||||
self.characteristic(uuid: rxCharacteristicUuid, serviceUuid: uartServiceUuid) { [unowned self] (characteristic, error) in
|
||||
guard let characteristic = characteristic, error == nil else {
|
||||
completion?(error != nil ? error : PeripheralUartError.invalidCharacteristic)
|
||||
return
|
||||
|
|
@ -122,17 +125,17 @@ extension BlePeripheral {
|
|||
}
|
||||
|
||||
func uartDisable() {
|
||||
// Clear all Uart specific data
|
||||
defer {
|
||||
uartRxCharacteristic = nil
|
||||
uartTxCharacteristic = nil
|
||||
uartTxCharacteristicWriteType = nil
|
||||
}
|
||||
|
||||
// Disable notify
|
||||
guard let characteristic = uartRxCharacteristic, characteristic.isNotifying else { return }
|
||||
|
||||
disableNotify(for: characteristic) { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
|
||||
disableNotify(for: characteristic)
|
||||
// Clear all Uart specific data
|
||||
self.uartRxCharacteristic = nil
|
||||
self.uartTxCharacteristic = nil
|
||||
self.uartTxCharacteristicWriteType = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Send
|
||||
|
|
|
|||
|
|
@ -8,19 +8,18 @@
|
|||
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
#if COMMANDLINE
|
||||
#else
|
||||
import MSWeakTimer
|
||||
#endif
|
||||
|
||||
// TODO: Modernize completion blocks to use Swift.Result
|
||||
|
||||
class BlePeripheral: NSObject {
|
||||
open class BlePeripheral: NSObject {
|
||||
// Config
|
||||
private static var kProfileCharacteristicUpdates = true
|
||||
|
||||
// Constants
|
||||
static var kUndefinedRssiValue = 127
|
||||
|
||||
// Notifications
|
||||
enum NotificationUserInfoKey: String {
|
||||
public enum NotificationUserInfoKey: String {
|
||||
case uuid = "uuid"
|
||||
case name = "name"
|
||||
case invalidatedServices = "invalidatedServices"
|
||||
|
|
@ -32,22 +31,48 @@ class BlePeripheral: NSObject {
|
|||
|
||||
// Data
|
||||
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
|
||||
|
||||
var identifier: UUID {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
var name: String? {
|
||||
open var name: String? {
|
||||
return peripheral.name
|
||||
}
|
||||
|
||||
var state: CBPeripheralState {
|
||||
return peripheral.state
|
||||
|
||||
public var debugName: String {
|
||||
return peripheral.name ?? peripheral.identifier.uuidString
|
||||
}
|
||||
|
||||
struct Advertisement {
|
||||
open var state: CBPeripheralState {
|
||||
return peripheral.state
|
||||
}
|
||||
|
||||
func maximumWriteValueLength(for: CBCharacteristicWriteType) -> Int {
|
||||
return peripheral.maximumWriteValueLength(for: .withoutResponse)
|
||||
}
|
||||
|
||||
public struct Advertisement {
|
||||
var advertisementData: [String: Any]
|
||||
|
||||
init(advertisementData: [String: Any]?) {
|
||||
|
|
@ -55,71 +80,73 @@ class BlePeripheral: NSObject {
|
|||
}
|
||||
|
||||
// Advertisement data formatted
|
||||
var localName: String? {
|
||||
public var localName: String? {
|
||||
return advertisementData[CBAdvertisementDataLocalNameKey] as? String
|
||||
}
|
||||
|
||||
var manufacturerData: Data? {
|
||||
|
||||
public var manufacturerData: Data? {
|
||||
return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||
}
|
||||
|
||||
var manufacturerHexDescription: String? {
|
||||
|
||||
public var manufacturerHexDescription: String? {
|
||||
guard let manufacturerData = manufacturerData else { return nil }
|
||||
return HexUtils.hexDescription(data: manufacturerData)
|
||||
// return String(data: manufacturerData, encoding: .utf8)
|
||||
}
|
||||
|
||||
var manufacturerIdentifier: Data? {
|
||||
|
||||
public var manufacturerIdentifier: Data? {
|
||||
guard let manufacturerData = manufacturerData, manufacturerData.count >= 2 else { return nil }
|
||||
let manufacturerIdentifierData = manufacturerData[0..<2]
|
||||
return manufacturerIdentifierData
|
||||
}
|
||||
|
||||
var services: [CBUUID]? {
|
||||
public var services: [CBUUID]? {
|
||||
return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
|
||||
}
|
||||
|
||||
var servicesOverflow: [CBUUID]? {
|
||||
public var servicesOverflow: [CBUUID]? {
|
||||
return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
|
||||
}
|
||||
|
||||
var servicesSolicited: [CBUUID]? {
|
||||
public var servicesSolicited: [CBUUID]? {
|
||||
return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
|
||||
}
|
||||
|
||||
var serviceData: [CBUUID: Data]? {
|
||||
|
||||
public var serviceData: [CBUUID: Data]? {
|
||||
return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
|
||||
}
|
||||
|
||||
var txPower: Int? {
|
||||
public var txPower: Int? {
|
||||
let number = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
|
||||
return number?.intValue
|
||||
}
|
||||
|
||||
var isConnectable: Bool? {
|
||||
public var isConnectable: Bool? {
|
||||
let connectableNumber = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber
|
||||
return connectableNumber?.boolValue
|
||||
}
|
||||
}
|
||||
var advertisement: Advertisement
|
||||
public var advertisement: Advertisement
|
||||
|
||||
typealias CapturedReadCompletionHandler = ((_ value: Any?, _ error: Error?) -> Void)
|
||||
private class CaptureReadHandler {
|
||||
|
||||
var identifier: String
|
||||
var result: CapturedReadCompletionHandler
|
||||
var timeoutTimer: MSWeakTimer?
|
||||
var timeoutAction: ((String)->())?
|
||||
var timeoutTimer: Foundation.Timer?
|
||||
var timeoutAction: ((String) -> Void)?
|
||||
var isNotifyOmitted: Bool
|
||||
|
||||
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction:((String)->())?, isNotifyOmitted: Bool = false) {
|
||||
init(identifier: String, result: @escaping CapturedReadCompletionHandler, timeout: Double?, timeoutAction: ((String) -> Void)?, isNotifyOmitted: Bool = false) {
|
||||
self.identifier = identifier
|
||||
self.result = result
|
||||
self.isNotifyOmitted = isNotifyOmitted
|
||||
|
||||
if let timeout = timeout {
|
||||
self.timeoutAction = timeoutAction
|
||||
timeoutTimer = MSWeakTimer.scheduledTimer(withTimeInterval: timeout, target: self, selector: #selector(timerFired), userInfo: nil, repeats: false, dispatchQueue: .global(qos: .background))
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
self.timeoutTimer = Timer.scheduledTimer(timeInterval: timeout, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,12 +161,12 @@ class BlePeripheral: NSObject {
|
|||
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 }
|
||||
// DLog("captureReadHandlers index: \(index) / \(captureReadHandlers.count)")
|
||||
|
||||
|
||||
// Remove capture handler
|
||||
captureReadHandlers.remove(at: index)
|
||||
finishedExecutingCommand(error: PeripheralError.timeout)
|
||||
}
|
||||
|
||||
|
||||
// Internal data
|
||||
private var notifyHandlers = [String: ((Error?) -> Void)]() // Nofify handlers for each service-characteristic
|
||||
private var captureReadHandlers = [CaptureReadHandler]()
|
||||
|
|
@ -149,13 +176,13 @@ class BlePeripheral: NSObject {
|
|||
//private var profileStartTime: CFTimeInterval = 0
|
||||
|
||||
// MARK: - Init
|
||||
init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
|
||||
public init(peripheral: CBPeripheral, advertisementData: [String: Any]?, rssi: Int?) {
|
||||
self.peripheral = peripheral
|
||||
self.advertisement = Advertisement(advertisementData: advertisementData)
|
||||
self.rssi = rssi
|
||||
self.lastSeenTime = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
super.init()
|
||||
self.rssi = rssi
|
||||
self.peripheral.delegate = self
|
||||
// DLog("create peripheral: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
commandQueue.executeHandler = executeCommand
|
||||
|
|
@ -206,7 +233,7 @@ class BlePeripheral: NSObject {
|
|||
let command = BleCommand(type: .discoverDescriptor, parameters: [characteristic], completion: completion)
|
||||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Connection
|
||||
func disconnect(centralManager: CBCentralManager) {
|
||||
let command = BleCommand(type: .disconnect, parameters: [centralManager], completion: nil)
|
||||
|
|
@ -254,7 +281,7 @@ class BlePeripheral: NSObject {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func characteristic(uuid: CBUUID, serviceUuid: CBUUID, completion: ((CBCharacteristic?, Error?) -> Void)?) {
|
||||
if let discoveredService = discoveredService(uuid: serviceUuid) { // Service was already discovered
|
||||
characteristic(uuid: uuid, service: discoveredService, completion: completion)
|
||||
|
|
@ -273,7 +300,7 @@ class BlePeripheral: NSObject {
|
|||
let command = BleCommand(type: .setNotify, parameters: [characteristic, true, handler as Any], completion: completion)
|
||||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
|
||||
func disableNotify(for characteristic: CBCharacteristic, completion: ((Error?) -> Void)? = nil) {
|
||||
let command = BleCommand(type: .setNotify, parameters: [characteristic, false], completion: completion)
|
||||
commandQueue.append(command)
|
||||
|
|
@ -308,14 +335,14 @@ class BlePeripheral: NSObject {
|
|||
let command = BleCommand(type: .readDescriptor, parameters: [descriptor, readCompletion as Any], completion: nil)
|
||||
commandQueue.append(command)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Rssi
|
||||
func readRssi() {
|
||||
peripheral.readRSSI()
|
||||
}
|
||||
|
||||
// MARK: - Command Queue
|
||||
private class BleCommand: Equatable {
|
||||
internal class BleCommand: Equatable {
|
||||
enum CommandType {
|
||||
case discoverService
|
||||
case discoverCharacteristic
|
||||
|
|
@ -375,14 +402,16 @@ class BlePeripheral: NSObject {
|
|||
}
|
||||
|
||||
private func handlerIdentifier(from characteristic: CBCharacteristic) -> String {
|
||||
return "\(characteristic.service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
|
||||
guard let service = characteristic.service else { DLog("Error: handleIdentifier with nil characteritic service"); return "" }
|
||||
return "\(service.uuid.uuidString)-\(characteristic.uuid.uuidString)"
|
||||
}
|
||||
|
||||
private func handlerIdentifier(from descriptor: CBDescriptor) -> String {
|
||||
return "\(descriptor.characteristic.service.uuid.uuidString)-\(descriptor.characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
|
||||
guard let characteristic = descriptor.characteristic, let service = characteristic.service else { DLog("Error: handleIdentifier with nil descriptor service"); return "" }
|
||||
return "\(service.uuid.uuidString)-\(characteristic.uuid.uuidString)-\(descriptor.uuid.uuidString)"
|
||||
}
|
||||
|
||||
private func finishedExecutingCommand(error: Error?) {
|
||||
internal func finishedExecutingCommand(error: Error?) {
|
||||
//DLog("finishedExecutingCommand")
|
||||
|
||||
// Result Callback
|
||||
|
|
@ -410,7 +439,7 @@ class BlePeripheral: NSObject {
|
|||
if discoverAll || (serviceUuids != nil && serviceUuids!.count > 0) {
|
||||
peripheral.discoverServices(serviceUuids)
|
||||
} else {
|
||||
// Everthing was already discovered
|
||||
// Everything was already discovered
|
||||
finishedExecutingCommand(error: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -473,21 +502,32 @@ class BlePeripheral: NSObject {
|
|||
let writeType = command.parameters![1] as! CBCharacteristicWriteType
|
||||
let data = command.parameters![2] as! Data
|
||||
|
||||
peripheral.writeValue(data, for: characteristic, type: writeType)
|
||||
|
||||
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 {
|
||||
let readCharacteristic = command.parameters![3] as! CBCharacteristic
|
||||
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
||||
let timeout = command.parameters![5] as? Double
|
||||
|
||||
|
||||
let identifier = handlerIdentifier(from: readCharacteristic)
|
||||
let captureReadHandler = CaptureReadHandler(identifier: identifier, result: readCompletion, timeout: timeout, timeoutAction: timeOutRemoveCaptureHandler)
|
||||
captureReadHandlers.append(captureReadHandler)
|
||||
}
|
||||
|
||||
|
||||
finishedExecutingCommand(error: nil)
|
||||
}
|
||||
else {
|
||||
peripheral.writeValue(data, for: characteristic, type: writeType)
|
||||
}
|
||||
}
|
||||
|
||||
private func readDescriptor(with command: BleCommand) {
|
||||
|
|
@ -500,8 +540,8 @@ class BlePeripheral: NSObject {
|
|||
|
||||
peripheral.readValue(for: descriptor)
|
||||
}
|
||||
|
||||
private func disconnect(with command: BleCommand) {
|
||||
|
||||
internal func disconnect(with command: BleCommand) {
|
||||
let centralManager = command.parameters!.first as! CBCentralManager
|
||||
centralManager.cancelPeripheralConnection(self.peripheral)
|
||||
finishedExecutingCommand(error: nil)
|
||||
|
|
@ -510,31 +550,31 @@ class BlePeripheral: NSObject {
|
|||
|
||||
// MARK: - CBPeripheralDelegate
|
||||
extension BlePeripheral: CBPeripheralDelegate {
|
||||
func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
|
||||
public func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
|
||||
DLog("peripheralDidUpdateName: \(name ?? "{ No Name }")")
|
||||
NotificationCenter.default.post(name: .peripheralDidUpdateName, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.name.rawValue: name as Any])
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
|
||||
DLog("didModifyServices")
|
||||
NotificationCenter.default.post(name: .peripheralDidModifyServices, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier, NotificationUserInfoKey.invalidatedServices.rawValue: invalidatedServices])
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||
DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||
//DLog("didDiscoverServices for: \(peripheral.name ?? peripheral.identifier.uuidString)")
|
||||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
|
||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
///DLog("didDiscoverCharacteristicsFor: \(service.uuid.uuidString)")
|
||||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
||||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
|
||||
let identifier = handlerIdentifier(from: characteristic)
|
||||
|
||||
|
|
@ -586,8 +626,8 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
|
||||
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
if let command = commandQueue.first(), !command.isCancelled, command.type == .writeCharacteristicAndWaitNofity {
|
||||
let characteristic = command.parameters![3] as! CBCharacteristic
|
||||
let readCompletion = command.parameters![4] as! CapturedReadCompletionHandler
|
||||
|
|
@ -602,11 +642,11 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
||||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
|
||||
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
|
||||
let identifier = handlerIdentifier(from: descriptor)
|
||||
|
||||
if captureReadHandlers.count > 0, let index = captureReadHandlers.firstIndex(where: {$0.identifier == identifier}) {
|
||||
|
|
@ -620,15 +660,15 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
finishedExecutingCommand(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
||||
|
||||
public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
||||
guard error == nil else { DLog("didReadRSSI error: \(error!.localizedDescription)"); return }
|
||||
|
||||
|
||||
let rssi = RSSI.intValue
|
||||
if rssi != 127 { // only update rssi value if is defined ( 127 means undefined )
|
||||
if rssi != BlePeripheral.kUndefinedRssiValue { // only update rssi value if is defined ( 127 means undefined )
|
||||
self.rssi = rssi
|
||||
}
|
||||
|
||||
|
||||
NotificationCenter.default.post(name: .peripheralDidUpdateRssi, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
}
|
||||
}
|
||||
|
|
@ -636,7 +676,7 @@ extension BlePeripheral: CBPeripheralDelegate {
|
|||
// MARK: - Custom Notifications
|
||||
extension Notification.Name {
|
||||
private static let kPrefix = Bundle.main.bundleIdentifier!
|
||||
static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
|
||||
static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
|
||||
static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
|
||||
public static let peripheralDidUpdateName = Notification.Name(kPrefix+".peripheralDidUpdateName")
|
||||
public static let peripheralDidModifyServices = Notification.Name(kPrefix+".peripheralDidModifyServices")
|
||||
public static let peripheralDidUpdateRssi = Notification.Name(kPrefix+".peripheralDidUpdateRssi")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
protocol UartDataManagerDelegate: class {
|
||||
protocol UartDataManagerDelegate: AnyObject {
|
||||
func onUartRx(data: Data, peripheralIdentifier: UUID) // data contents depends on the isRxCacheEnabled flag
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import Foundation
|
|||
|
||||
class UartPacketManager: UartPacketManagerBase {
|
||||
|
||||
// Params
|
||||
var isResetPacketsOnReconnectionEnabled = true
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
||||
super.init(delegate: delegate, isPacketCacheEnabled: isPacketCacheEnabled, isMqttEnabled: isMqttEnabled)
|
||||
|
||||
|
|
@ -25,14 +29,20 @@ class UartPacketManager: UartPacketManagerBase {
|
|||
private func registerNotifications(enabled: Bool) {
|
||||
let notificationCenter = NotificationCenter.default
|
||||
if enabled {
|
||||
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {[weak self] _ in self?.clearPacketsCache()}
|
||||
didConnectToPeripheralObserver = notificationCenter.addObserver(forName: .didConnectToPeripheral, object: nil, queue: .main) {
|
||||
[weak self] _ in
|
||||
guard let self = self else { return }
|
||||
if self.isResetPacketsOnReconnectionEnabled {
|
||||
self.clearPacketsCache()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let didConnectToPeripheralObserver = didConnectToPeripheralObserver {notificationCenter.removeObserver(didConnectToPeripheralObserver)}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Send data
|
||||
func send(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
|
||||
private func sendUart(blePeripheral: BlePeripheral, data: Data?, progress: ((Float)->Void)? = nil, completion: ((Error?) -> Void)? = nil) {
|
||||
sentBytes += Int64(data?.count ?? 0)
|
||||
blePeripheral.uartSend(data: data, progress: progress, completion: completion)
|
||||
}
|
||||
|
|
@ -51,13 +61,16 @@ class UartPacketManager: UartPacketManagerBase {
|
|||
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 isMqttEnabled {
|
||||
// Mqtt publish to TX
|
||||
let mqttSettings = MqttSettings.shared
|
||||
if mqttSettings.isPublishEnabled {
|
||||
if mqttSettings.isPublishEnabled, let text = String(data: data, encoding: .utf8) {
|
||||
if let topic = mqttSettings.getPublishTopic(index: MqttSettings.PublishFeed.tx.rawValue) {
|
||||
let qos = mqttSettings.getPublishQos(index: MqttSettings.PublishFeed.tx.rawValue)
|
||||
MqttManager.shared.publish(message: text, topic: topic, qos: qos)
|
||||
|
|
@ -67,27 +80,25 @@ class UartPacketManager: UartPacketManagerBase {
|
|||
#endif
|
||||
|
||||
// Create data and send to Uart
|
||||
if let data = text.data(using: .utf8) {
|
||||
let uartPacket = UartPacket(peripheralId: blePeripheral.identifier, mode: .tx, data: data)
|
||||
|
||||
// Add Packet
|
||||
packetsSemaphore.wait()
|
||||
packets.append(uartPacket)
|
||||
packetsSemaphore.signal()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.onUartPacket(uartPacket)
|
||||
}
|
||||
|
||||
#if MQTT_ENABLED
|
||||
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
|
||||
#else
|
||||
let shouldBeSent = true
|
||||
#endif
|
||||
|
||||
if shouldBeSent {
|
||||
send(blePeripheral: blePeripheral, data: data)
|
||||
}
|
||||
let uartPacket = UartPacket(peripheralId: blePeripheral.identifier, mode: .tx, data: data)
|
||||
|
||||
// Add Packet
|
||||
packetsSemaphore.wait()
|
||||
packets.append(uartPacket)
|
||||
packetsSemaphore.signal()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.onUartPacket(uartPacket)
|
||||
}
|
||||
|
||||
#if MQTT_ENABLED
|
||||
let shouldBeSent = !wasReceivedFromMqtt || (isMqttEnabled && MqttSettings.shared.subscribeBehaviour == .transmit)
|
||||
#else
|
||||
let shouldBeSent = true
|
||||
#endif
|
||||
|
||||
if shouldBeSent {
|
||||
sendUart(blePeripheral: blePeripheral, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
//
|
||||
// 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)?) {
|
||||
|
||||
let timePeriod: TimeInterval = 0 // 0 means that the responseHandler will be called only when there is a change
|
||||
self.cpbServiceEnable(serviceUuid: BlePeripheral.kCPBButtonsServiceUUID, mainCharacteristicUuid: BlePeripheral.kCPBButtonsCharacteristicUUID, timePeriod: timePeriod, 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
|
||||
|
||||
if timePeriod == 0 { // Read initial state if the timePeriod is 0 (update only when changed)
|
||||
CPBBle.shared.buttonsReadState { 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.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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
//
|
||||
// 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(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// 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")
|
||||
|
||||
private 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// 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")
|
||||
|
||||
private static let kCPBTemperatureDefaultPeriod: TimeInterval = 0.1
|
||||
|
||||
// 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: BlePeripheral.kCPBTemperatureDefaultPeriod, 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
//
|
||||
// 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)")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -10,34 +10,74 @@ import UIKit
|
|||
import CoreBluetooth
|
||||
|
||||
class BleManagerSimulated: BleManager {
|
||||
|
||||
|
||||
// Singleton
|
||||
static let simulated = BleManagerSimulated()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
override init() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Scanning
|
||||
override func startScan(withServices services: [CBUUID]? = nil) {
|
||||
scanningStartTime = CACurrentMediaTime()
|
||||
|
||||
// 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() {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Connect
|
||||
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
|
||||
NotificationCenter.default.post(name: .didConnectToPeripheral, object: nil, userInfo: [NotificationUserInfoKey.uuid.rawValue: peripheral.identifier])
|
||||
}
|
||||
|
||||
override func reconnecToPeripherals(withIdentifiers identifiers: [UUID], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
|
||||
override func reconnecToPeripherals(peripheralsData: [(identifier: UUID, advertisementData: [String: Any])], withServices services: [CBUUID], timeout: Double? = nil) -> Bool {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// 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,5 +1,5 @@
|
|||
//
|
||||
// BlePeripheral+CPBButtons.swift
|
||||
// BlePeripheralSimulated+AdafruitButtons.swift
|
||||
// BluefruitPlayground
|
||||
//
|
||||
// Created by Antonio García on 15/11/2019.
|
||||
|
|
@ -10,8 +10,6 @@ import Foundation
|
|||
import CoreBluetooth
|
||||
|
||||
extension BlePeripheral {
|
||||
// Constants
|
||||
|
||||
enum SlideSwitchState: Int32 {
|
||||
case right = 0
|
||||
case left = 1
|
||||
|
|
@ -28,28 +26,27 @@ extension BlePeripheral {
|
|||
var buttonB: ButtonState
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Actions
|
||||
func cpbButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
completion?(.success(()))
|
||||
func adafruitButtonsEnable(responseHandler: @escaping(Result<(ButtonsState, UUID), Error>) -> Void, completion: ((Result<Void, Error>) -> Void)?) {
|
||||
completion?(.success(()))
|
||||
}
|
||||
|
||||
func isCpbButtonsEnabled() -> Bool {
|
||||
func adafruitButtonsIsEnabled() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func cpbButtonsDisable() {
|
||||
func adafruitButtonsDisable() {
|
||||
}
|
||||
|
||||
func cpbButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||
guard let state = cpbButtonsLastValue() else {
|
||||
completion(.failure(PeripheralCPBError.invalidResponseData))
|
||||
func adafruitButtonsReadState(completion: @escaping(Result<(ButtonsState, UUID), Error>) -> Void) {
|
||||
guard let state = adafruitButtonsLastValue() else {
|
||||
completion(.failure(PeripheralAdafruitError.invalidResponseData))
|
||||
return
|
||||
}
|
||||
completion(.success((state, self.identifier)))
|
||||
}
|
||||
|
||||
func cpbButtonsLastValue() -> ButtonsState? {
|
||||
func adafruitButtonsLastValue() -> ButtonsState? {
|
||||
return ButtonsState(slideSwitch: .left, buttonA: .pressed, buttonB: .released)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// 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,29 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
//
|
||||
// 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,24 +10,38 @@ import Foundation
|
|||
import CoreBluetooth
|
||||
|
||||
class BlePeripheralSimulated: BlePeripheral {
|
||||
// Constants
|
||||
private static let kSimulatedUUID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
|
||||
|
||||
// Data
|
||||
private var simulatedIdentifier = UUID()
|
||||
override var identifier: UUID {
|
||||
return BlePeripheralSimulated.kSimulatedUUID
|
||||
return simulatedIdentifier
|
||||
}
|
||||
|
||||
|
||||
override var name: String? {
|
||||
return "Simulated Peripheral"
|
||||
let result: String
|
||||
switch model {
|
||||
case .circuitPlaygroundBluefruit:
|
||||
result = "CPB"
|
||||
case .clue_nRF52840:
|
||||
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 .connected
|
||||
return simulatedState
|
||||
}
|
||||
|
||||
private var model: AdafruitManufacturerData.BoardModel
|
||||
|
||||
// MARK: - Lifecycle
|
||||
init() {
|
||||
init(model: AdafruitManufacturerData.BoardModel) {
|
||||
self.model = model
|
||||
|
||||
// Mocking CBPeripheral: https://forums.developer.apple.com/thread/29851
|
||||
guard let peripheral = ObjectBuilder.createInstance(ofClass: "CBPeripheral") as? CBPeripheral else {
|
||||
assertionFailure("Unable to mock CBPeripheral")
|
||||
|
|
@ -35,16 +49,34 @@ class BlePeripheralSimulated: BlePeripheral {
|
|||
super.init(peripheral: nilPeripheral, advertisementData: nil, rssi: nil)
|
||||
return
|
||||
}
|
||||
peripheral.addObserver(peripheral, forKeyPath: "delegate", options: .new, context: nil)
|
||||
|
||||
let manufacturerDataBytes: [UInt8] = [0x22, 0x08, 0x04, 0x01, 0x00, 0x45, 0x80] // Adafruit CPB
|
||||
peripheral.addObserver(peripheral, forKeyPath: "delegate", options: .new, context: nil)
|
||||
|
||||
let adafruitManufacturerIdentifier = BlePeripheral.kManufacturerAdafruitIdentifier
|
||||
let boardId = model.identifier.first!
|
||||
let boardField: [UInt8] = [0x04, 0x01, 0x00] + boardId
|
||||
let manufacturerDataBytes: [UInt8] = adafruitManufacturerIdentifier + boardField
|
||||
let advertisementData = [CBAdvertisementDataManufacturerDataKey: Data(manufacturerDataBytes)]
|
||||
super.init(peripheral: peripheral, advertisementData: advertisementData, rssi: 20)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Discover
|
||||
override func discover(serviceUuids: [CBUUID]?, completion: ((Error?) -> Void)?) {
|
||||
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
|
||||
|
||||
protocol UartPacketManagerDelegate: class {
|
||||
protocol UartPacketManagerDelegate: AnyObject {
|
||||
func onUartPacket(_ packet: UartPacket)
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ struct UartPacket { // A packet of data received or sent
|
|||
var mode: TransferMode
|
||||
var data: Data
|
||||
var peripheralId: UUID?
|
||||
|
||||
|
||||
init(peripheralId: UUID?, timestamp: CFAbsoluteTime? = nil, mode: TransferMode, data: Data) {
|
||||
self.peripheralId = peripheralId
|
||||
self.timestamp = timestamp ?? CFAbsoluteTimeGetCurrent()
|
||||
|
|
@ -30,34 +30,32 @@ struct UartPacket { // A packet of data received or sent
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class UartPacketManagerBase {
|
||||
|
||||
|
||||
// Data
|
||||
internal weak var delegate: UartPacketManagerDelegate?
|
||||
internal var packets = [UartPacket]()
|
||||
internal var packetsSemaphore = DispatchSemaphore(value: 1)
|
||||
internal var isMqttEnabled: Bool
|
||||
internal var isPacketCacheEnabled: Bool
|
||||
|
||||
internal var isPacketCacheEnabled: Bool
|
||||
|
||||
var receivedBytes: Int64 = 0
|
||||
var sentBytes: Int64 = 0
|
||||
|
||||
|
||||
init(delegate: UartPacketManagerDelegate?, isPacketCacheEnabled: Bool, isMqttEnabled: Bool) {
|
||||
self.isPacketCacheEnabled = isPacketCacheEnabled
|
||||
self.isMqttEnabled = isMqttEnabled
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Received data
|
||||
func rxPacketReceived(data: Data?, peripheralIdentifier: UUID?, error: Error?) {
|
||||
|
||||
|
||||
guard error == nil else { DLog("uartRxPacketReceived error: \(error!)"); return }
|
||||
guard let data = data else { return }
|
||||
|
||||
|
||||
let uartPacket = UartPacket(peripheralId: peripheralIdentifier, mode: .rx, data: data)
|
||||
|
||||
|
||||
// Mqtt publish to RX. TODO: Remove the dependency with MqttSettings and pass parameters
|
||||
#if MQTT_ENABLED
|
||||
if isMqttEnabled {
|
||||
|
|
@ -72,31 +70,31 @@ class UartPacketManagerBase {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
packetsSemaphore.wait() // don't append more data, till the delegate has finished processing it
|
||||
receivedBytes += Int64(data.count)
|
||||
if isPacketCacheEnabled {
|
||||
packets.append(uartPacket)
|
||||
}
|
||||
|
||||
|
||||
// Send data to delegate
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.onUartPacket(uartPacket)
|
||||
}
|
||||
|
||||
|
||||
//DLog("packetsData: \(packetsData.count)")
|
||||
|
||||
|
||||
packetsSemaphore.signal()
|
||||
}
|
||||
|
||||
|
||||
func clearPacketsCache() {
|
||||
packets.removeAll()
|
||||
}
|
||||
|
||||
|
||||
func packetsCache() -> [UartPacket] {
|
||||
return packets
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Counters
|
||||
func resetCounters() {
|
||||
receivedBytes = 0
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class CommandQueue<Element> {
|
|||
func executeNext() {
|
||||
queueLock.lock()
|
||||
guard !queue.isEmpty else { queueLock.unlock(); return }
|
||||
|
||||
|
||||
//DLog("queue remove finished: \(queue.first)")
|
||||
// Delete finished command and trigger next execution if needed
|
||||
queue.removeFirst()
|
||||
|
|
|
|||
|
|
@ -8,19 +8,17 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension Data {
|
||||
func toFloatFrom32Bits() -> Float {
|
||||
return Float(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) }))
|
||||
}
|
||||
|
||||
|
||||
func toIntFrom32Bits() -> Int {
|
||||
return Int(Int32(bitPattern: UInt32(littleEndian: self.withUnsafeBytes { $0.load(as: UInt32.self) })))
|
||||
}
|
||||
|
||||
|
||||
func toInt32From32Bits() -> Int32 {
|
||||
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 {
|
||||
return data.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
|
||||
}
|
||||
|
||||
|
||||
static func hexDescription(bytes: [UInt8], prefix: String = "", postfix: String = " ") -> String {
|
||||
return bytes.reduce("") {$0 + String(format: "%@%02X%@", prefix, $1, postfix)}
|
||||
}
|
||||
|
||||
|
||||
static func decimalDescription(data: Data, prefix: String = "", postfix: String = " ") -> String {
|
||||
return data.reduce("") {$0 + String(format: "%@%ld%@", prefix, $1, postfix)}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ extension UInt16: UIntToBytesConvertable {
|
|||
|
||||
extension UInt32: UIntToBytesConvertable {
|
||||
var toBytes: [UInt8] {
|
||||
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
|
||||
return toByteArr(endian: self.littleEndian, count: MemoryLayout<UInt32>.size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ extension DataConvertible where Self: ExpressibleByIntegerLiteral {
|
|||
init?(data: Data) {
|
||||
var value: Self = 0
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,6 @@ extension UInt32: DataConvertible { }
|
|||
extension Float: DataConvertible { }
|
||||
extension Double: DataConvertible { }
|
||||
|
||||
|
||||
// Convert from [UInt8] to Data and from Data to [UInt8]
|
||||
// from: https://stackoverflow.com/questions/31821709/nsdata-to-uint8-in-swift/31821838
|
||||
extension Data {
|
||||
|
|
|
|||
|
|
@ -10,42 +10,41 @@ import UIKit
|
|||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
|
||||
|
||||
startup()
|
||||
|
||||
|
||||
ScreenFlowManager.enableBleStateManagement()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
|
||||
|
||||
ScreenFlowManager.disableBleStateManagement()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Startup
|
||||
private func startup() {
|
||||
// Settings
|
||||
|
|
@ -53,7 +52,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
// UI
|
||||
UINavigationBar.appearance().prefersLargeTitles = ConfigUI.prefersLargeTitles
|
||||
|
||||
|
||||
// Navigation bar: add background when large title is used
|
||||
if #available(iOS 13.0, *) {
|
||||
let appearance = UINavigationBarAppearance()
|
||||
|
|
@ -72,4 +71,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 261 KiB |
23
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/board_clue_back.png
vendored
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/board_clue_back@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 687 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_back.imageset/board_clue_back@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
23
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/board_clue_front.png
vendored
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/board_clue_front@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 563 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_clue_front.imageset/board_clue_front@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -2,17 +2,17 @@
|
|||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpb_circuit.png",
|
||||
"filename" : "board_cpb.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpb_circuit@2x.png",
|
||||
"filename" : "board_cpb@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cpb_circuit@3x.png",
|
||||
"filename" : "board_cpb@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
BIN
BluefruitPlayground/Assets.xcassets/boards/board_cpb.imageset/board_cpb.png
vendored
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_cpb.imageset/board_cpb@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 466 KiB |
BIN
BluefruitPlayground/Assets.xcassets/boards/board_cpb.imageset/board_cpb@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 745 KiB |
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.910",
|
||||
"red" : "0.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.824",
|
||||
"green" : "0.486"
|
||||
"blue" : "0.000",
|
||||
"green" : "0.839"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.910",
|
||||
"red" : "0.545",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.374",
|
||||
"green" : "0.429"
|
||||
"blue" : "1.000",
|
||||
"green" : "0.169"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
"components" : {
|
||||
"red" : "1.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.486",
|
||||
"green" : "0.918"
|
||||
"blue" : "0.953",
|
||||
"green" : "0.212"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0x00",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x80",
|
||||
"green" : "0x80"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.138",
|
||||
"red" : "0.180",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.726",
|
||||
"green" : "0.218"
|
||||
"blue" : "1.000",
|
||||
"green" : "0.282"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0xFF",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xB4",
|
||||
"green" : "0xE5"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.564",
|
||||
"red" : "0.244",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.910",
|
||||
"green" : "0.362"
|
||||
"blue" : "0.965",
|
||||
"green" : "0.544"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0x00",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x80"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0xFF",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"red" : "0.404",
|
||||
"red" : "1.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.692",
|
||||
"green" : "0.910"
|
||||
"blue" : "0.094",
|
||||
"green" : "0.341"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 744 KiB |
23
BluefruitPlayground/Assets.xcassets/humidity/humidity_fill.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/humidity/humidity_fill.imageset/humidity_fill.png
vendored
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
BluefruitPlayground/Assets.xcassets/humidity/humidity_fill.imageset/humidity_fill@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
BluefruitPlayground/Assets.xcassets/humidity/humidity_fill.imageset/humidity_fill@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
23
BluefruitPlayground/Assets.xcassets/humidity/humidity_outline.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "humidity_outline.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "humidity_outline@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "humidity_outline@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
BluefruitPlayground/Assets.xcassets/humidity/humidity_outline.imageset/humidity_outline.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
BluefruitPlayground/Assets.xcassets/humidity/humidity_outline.imageset/humidity_outline@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
BluefruitPlayground/Assets.xcassets/humidity/humidity_outline.imageset/humidity_outline@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 56 KiB |
6
BluefruitPlayground/Assets.xcassets/light/Contents.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |