Compare commits

...

80 commits

Author SHA1 Message Date
TrevKnows
05e43d9a74
Merge pull request #6 from adafruit/dependabot/bundler/rexml-3.3.3
Bump rexml from 3.2.6 to 3.3.3
2025-03-04 09:57:54 -05:00
dependabot[bot]
5e5995eb5d
Bump rexml from 3.2.6 to 3.3.3
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.6 to 3.3.3.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.6...v3.3.3)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-02 01:50:28 +00:00
TrevKnows
78a3e93679
Merge pull request #4 from adafruit/xcode15_update
Xcode15 update
2024-02-29 11:11:28 -05:00
Antonio
47ffad0c0d Github Workflow: updated build action to use Xcode 15.0.1 because Xcode 15.1 is not available yet 2023-12-19 16:44:46 +01:00
Antonio
82a6a37a9b Bump version number 2023-12-19 16:22:46 +01:00
Antonio
4c924c2605 Add GitHub Action to build app 2023-12-19 16:05:48 +01:00
Antonio
d290b65d91 Fix warning about thread priority inversion 2023-12-19 15:54:22 +01:00
Antonio
f17d2017ce Update fastlane 2023-12-19 12:30:11 +01:00
Antonio
ad8e7ffade Update BluefruitPlayground-SimulateBluetooth target to make it work in Xcode15 2023-11-29 12:23:32 +01:00
Antonio
251bd8ec31 Updated pods and manually modified FlexColorPicker and Charts to make it work with Xcode15 2023-11-29 12:14:01 +01:00
Antonio
4d268e4f4f Updated Ble code to the current version of AdafruitKit (used in Bluefruit Connect) 2023-11-29 12:12:55 +01:00
Antonio
e2bd12b539 Update Bluetooth API usage (CBCharacteristic service is now optional) 2023-11-28 18:13:59 +01:00
Antonio
969e1eb7c0 Update to new Swift syntax
Minor cosmetic code changes
2023-11-28 18:13:05 +01:00
Antonio
d22f07df7d Force to use always the "Light" theme 2023-11-28 17:57:20 +01:00
Melissa LeBlanc-Williams
60c0ae8c6b
Merge pull request #1 from adafruit/dependabot/bundler/addressable-2.8.0
Bump addressable from 2.7.0 to 2.8.0
2022-06-07 10:51:41 -06:00
dependabot[bot]
263c1163e9
Bump addressable from 2.7.0 to 2.8.0
Bumps [addressable](https://github.com/sporkmonger/addressable) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/sporkmonger/addressable/releases)
- [Changelog](https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sporkmonger/addressable/compare/addressable-2.7.0...addressable-2.8.0)

---
updated-dependencies:
- dependency-name: addressable
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-13 06:23:25 +00:00
Antonio
c089e932da Neopixels: Fixed the number of bytes sent for animations. It was hardcoded and now depends on the board.
Updated for Swift 5.2 and fixed new warnings about tuples
2020-05-07 20:42:52 +02:00
Antonio
5125b08706 Sound: minor change to the amplitude value calculation 2020-05-07 16:26:01 +02:00
Antonio
5f22e72211 Sound: fixed a crash when receiving a sample with the -32768 value 2020-05-07 15:58:59 +02:00
Antonio
542f5ca8c8 Add CLUE screens to the automatic screenshot generation process 2020-03-20 11:09:49 +01:00
Antonio
0d33821c88 Fixed text links
Updated strings
2020-03-18 13:03:05 +01:00
Antonio
76ac60c734 Puppets: tweak rotation angles 2020-03-17 22:22:29 +01:00
Antonio
5f9bfb3ee9 Quaternion: fix z axis rotation 2020-03-17 21:56:13 +01:00
Antonio
ec13361e4f Puppets: make head to tilt with the jaw movement 2020-03-15 15:45:11 +01:00
Antonio
7c7ae8578f Minor Refactoring
Add CLUE simulated sensors
2020-03-15 14:46:44 +01:00
Antonio
3fdf7a15fe Scanner: sort peripherals by discovery time 2020-03-15 14:01:53 +01:00
Antonio
cd834da0a1 Puppet: change gesture to open mouth 2020-03-15 13:22:24 +01:00
Antonio
d487170a96 Puppet: make low pass filter depend on the accelerometer period 2020-03-14 19:38:49 +01:00
Antonio
d706ae85fd Scanner: refresh peripherals when the view appears 2020-03-14 19:38:19 +01:00
Antonio
0d7c091fb6 Orientation module: disable z rotation until gyro calibration is implemented 2020-03-14 18:53:09 +01:00
Antonio
5e9eeabe5e Sound: change unit to dBFS, cosmetic changes
Updated text strings
2020-03-14 18:47:23 +01:00
Antonio
d6269f59be Fix crash when the board is not sending data and the chart dataset is empty
Fix crash when the amplitude is -infinite
Improve charts performance
2020-03-14 18:46:49 +01:00
Antonio
13a4c4ff3d Fix dataSeries using wrong order when reloading charts
Fix showing old data for a moment when reloading the chart
Cosmetic code changes
2020-03-14 00:08:27 +01:00
Antonio
49b2508da4 Quaternion: adjust quaternion to take into account the sensor position
Refactor period interval for sensors
2020-03-13 19:06:46 +01:00
Antonio
44e4654301 Updated image for CLUE board
Accelerometer: flip X and Z values for CLUE
Show Accelerometer module only if Quaternion service is not available
Updated text strings
2020-03-13 17:10:24 +01:00
Antonio
b5394dab0a Remove debug parameters from charts DataSeries 2020-03-12 15:48:15 +01:00
Antonio
36872363d8 ToneGeneration: support for iPhone 4s 2020-03-12 15:28:46 +01:00
Antonio
cdf816f848 Autoconnect: cosmetic changes
Better compatibility for iPhone 4s
2020-03-12 15:26:57 +01:00
Antonio
380891dceb Autoconnect: update for CLUE 2020-03-12 14:50:57 +01:00
Antonio
2090698fc4 Refactor board assets 2020-03-12 14:37:22 +01:00
Antonio
91d5322795 Scanner: sort peripherals by RSSI value 2020-03-12 13:18:03 +01:00
Antonio
019befae61 added colors for new modules 2020-03-12 13:13:24 +01:00
Antonio
c02a36c976 Tips, About: Automatize ActiveLabel links generation 2020-03-12 13:13:02 +01:00
Antonio
3feac2a2e5 Added Sound module
Added Quaternion module
Pressure: minor UI changes
Accelerometer: refactor shared code with quaternion module
About: added more links (wip)
2020-03-12 10:51:35 +01:00
Antonio
a61def2b4c Adding sound module (work-in-progress) 2020-03-10 20:30:58 +01:00
Antonio
1c0cb8235e Fix version characteristic reading
Refactor adafruit service notify enable
2020-03-10 19:31:26 +01:00
Antonio
c1a88e264e Added Sound service 2020-03-09 15:14:36 +01:00
Antonio
a1cc6d6306 Tone Generator module updated for CLUE 2020-03-09 10:55:06 +01:00
Antonio
5244715073 Light module updated for CLUE
Button Status: updated help for CLUE
2020-03-09 10:50:42 +01:00
Antonio
19768e4c52 Neopixel module updated for CLUE 2020-03-09 10:39:31 +01:00
Antonio
d1e72eb47e Smoother animations for Light and Humidity changes 2020-03-08 20:43:17 +01:00
Antonio
90da0e9896 Barometric pressure module
Clue front board
2020-03-08 13:02:49 +01:00
Antonio
519022fcf5 Save advertisement data for reconnecting 2020-03-08 00:17:48 +01:00
Antonio
0b7259834e Refactor temperature panels 2020-03-07 22:48:30 +01:00
Antonio
3ba4fa2d9d Humidity module 2020-03-07 21:47:10 +01:00
Antonio
213910c54a Split main storyboard 2020-03-07 02:06:26 +01:00
Antonio
b020a9ee43 New assets for CLUE 2020-03-07 01:45:05 +01:00
Antonio
4eec8bb5b7 New Services: barometric pressure, color sensor, gyroscope, humidity, magnetometer 2020-03-06 22:03:31 +01:00
Antonio
a44b33e66f Refactored boards and added a boardManager to manage multiple connected boards in the future 2020-03-06 20:57:45 +01:00
Antonio
031bab1241 Modules: modules can change now depending on the board connected
Test: added new simulated sensors
2020-03-06 01:25:07 +01:00
Antonio
f48758ed77 Scanner: added ability to decode manufacturer data (board type, color...) 2020-03-05 20:39:46 +01:00
Antonio
b96425596a Refactor CPBle into AdafruitBoard (to support more boards) 2020-03-05 18:11:10 +01:00
Antonio
148a33a5d8 Update Autoconnect parameters 2020-03-05 02:21:01 +01:00
Antonio
e326b0b9d5 Fix comments for auto-connect helper functions 2020-02-19 11:27:06 +01:00
Antonio
a220f3dec4 Apply SwiftLint code format suggestions 2020-02-19 10:30:38 +01:00
Antonio
e4f4c8bcd1 Autoconnect: improved logic for selecting a peripheral 2020-02-19 02:10:31 +01:00
Antonio
a105506ba6 Updated automatic screenshot generation 2020-02-12 12:49:05 +01:00
Antonio
170809c2f8 Puppet: updated help
Manual Scanner: fix table layout warnings while in background
Simulated target: handle disconnections
2020-02-08 18:45:41 +01:00
Antonio
f75db839ff Puppet: update help
Update help view to allow images...
2020-02-08 11:41:12 +01:00
Antonio
0bf2400b88 Remove old unused code 2020-02-07 02:30:21 +01:00
Antonio
1c98c92bac Puppets: set is as a main module (currently it was an accelerometer submodule) 2020-02-07 02:17:16 +01:00
Antonio
379d61a3b1 Autoconnect: added running average to rssi calculation 2020-02-06 01:03:08 +01:00
Antonio
f5af18568e ToneGenerator: fix vertical views constraints 2020-02-05 03:07:52 +01:00
Antonio
f56cfee413 Puppet: add camera permissions to plist, fix camerabuttons position on iPad 2020-02-05 02:36:45 +01:00
Antonio
3933990111 Changed animation between modules and manual scan on disconnect 2020-02-05 01:48:39 +01:00
Antonio
eb4182c30e ToneGenerator: expand keyboard to take all the available width
Updated strings
2020-02-05 01:43:01 +01:00
Antonio
1dab5a618e Changed the color that represents each module 2020-02-05 01:32:06 +01:00
Antonio
eacad0dc42 Added Puppet mode
Refactor code
2020-02-04 22:41:48 +01:00
Antonio
462ea1c289 Remove unused code to check ble status 2020-02-03 19:11:57 +01:00
Antonio
9c8d6f2e00 Added config setting to disable auto-connect UI 2020-02-03 15:28:24 +01:00
362 changed files with 16417 additions and 9517 deletions

26
.github/workflows/build.yml vendored Normal file
View 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

View file

@ -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>

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -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])
}
}

View file

@ -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()
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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])
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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])
}
}

View file

@ -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)
}
}

View file

@ -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])
}
}

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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)")
}
}

View file

@ -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
}
}

View file

@ -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")
}

View file

@ -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
}

View file

@ -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

View file

@ -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")
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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])
}
}

View file

@ -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)
}
}

View file

@ -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(()))
}
}
}
}

View file

@ -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()
}
}

View file

@ -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)
}
}

View file

@ -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()
}
}

View file

@ -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)")
}
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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])
}
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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()

View file

@ -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) }))
}
}
}

View file

@ -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)}
}

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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 {
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -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"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}

View file

@ -11,8 +11,8 @@
"components" : {
"red" : "1.000",
"alpha" : "1.000",
"blue" : "0.486",
"green" : "0.918"
"blue" : "0.953",
"green" : "0.212"
}
}
}

View file

@ -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"
}
}
}
]
}

View file

@ -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"
}
}
}
]
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}
]
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}
]
}

View file

@ -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"
}
}
}
]
}

View file

@ -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"
}
}
}

View file

@ -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"
}
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 KiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Some files were not shown because too many files have changed in this diff Show more