IOS-CoreBluetooth-Mock/Example/nRFBlinky/MockPeripherals.swift

305 lines
11 KiB
Swift

/*
* Copyright (c) 2020, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import Foundation
import CoreBluetoothMock
// MARK: - Mock nRF Blinky
extension CBMUUID {
static let nordicBlinkyService = CBMUUID(string: "00001523-1212-EFDE-1523-785FEABCD123")
static let buttonCharacteristic = CBMUUID(string: "00001524-1212-EFDE-1523-785FEABCD123")
static let ledCharacteristic = CBMUUID(string: "00001525-1212-EFDE-1523-785FEABCD123")
}
extension CBMCharacteristicMock {
static let buttonCharacteristic = CBMCharacteristicMock(
type: .buttonCharacteristic,
properties: [.notify, .read],
descriptors: CBMClientCharacteristicConfigurationDescriptorMock()
)
static let ledCharacteristic = CBMCharacteristicMock(
type: .ledCharacteristic,
properties: [.write, .read]
)
}
extension CBMServiceMock {
static let blinkyService = CBMServiceMock(
type: .nordicBlinkyService, primary: true,
characteristics:
.buttonCharacteristic,
.ledCharacteristic
)
}
private class BlinkyCBMPeripheralSpecDelegate: CBMPeripheralSpecDelegate {
private var ledEnabled: Bool = false
private var buttonPressed: Bool = false
private var ledData: Data {
return ledEnabled ? Data([0x01]) : Data([0x00])
}
private var buttonData: Data {
return buttonPressed ? Data([0x01]) : Data([0x00])
}
func reset() {
ledEnabled = false
buttonPressed = false
}
func peripheral(_ peripheral: CBMPeripheralSpec,
didReceiveReadRequestFor characteristic: CBMCharacteristicMock)
-> Result<Data, Error> {
if characteristic.uuid == .ledCharacteristic {
return .success(ledData)
} else {
return .success(buttonData)
}
}
func peripheral(_ peripheral: CBMPeripheralSpec,
didReceiveWriteRequestFor characteristic: CBMCharacteristicMock,
data: Data) -> Result<Void, Error> {
if data.count > 0 {
ledEnabled = data[0] != 0x00
}
return .success(())
}
}
let blinky = CBMPeripheralSpec
.simulatePeripheral(proximity: .outOfRange)
.advertising(
advertisementData: [
CBMAdvertisementDataLocalNameKey : "nRF Blinky",
CBMAdvertisementDataServiceUUIDsKey : [CBMUUID.nordicBlinkyService],
CBMAdvertisementDataIsConnectable : true as NSNumber
],
withInterval: 0.250,
alsoWhenConnected: false)
.connectable(
name: "nRF Blinky",
services: [.blinkyService],
delegate: BlinkyCBMPeripheralSpecDelegate(),
connectionInterval: 0.150,
mtu: 23)
.build()
// MARK: - Mock Nordic HRM
extension CBMServiceMock {
static let hrmService = CBMServiceMock(
type: CBMUUID(string: "180D"), primary: true,
characteristics:
CBMCharacteristicMock(
type: CBMUUID(string: "2A37"), // Heart Rate Measurement
properties: [.notify],
descriptors: CBMClientCharacteristicConfigurationDescriptorMock()
),
CBMCharacteristicMock(
type: CBMUUID(string: "2A38"), // Body Sensor Location
properties: [.read]
)
)
}
private struct DummyCBMPeripheralSpecDelegate: CBMPeripheralSpecDelegate {
// Let's use default implementation.
// The HRM will not show up in the scan result, as it
// doesn't advertise with Nordic LED Button service.
// If you uncomment the line below, and try to connect,
// connection will fail on "Device not supported" error.
}
let hrm = CBMPeripheralSpec
.simulatePeripheral(proximity: .outOfRange)
.advertising(
advertisementData: [
CBMAdvertisementDataLocalNameKey : "NordicHRM",
CBMAdvertisementDataServiceUUIDsKey : [
CBMUUID(string: "180D"), // Heart Rate
CBMUUID(string: "180A"), // Device Information
// BlinkyPeripheral.nordicBlinkyServiceUUID // <- this line
],
CBMAdvertisementDataIsConnectable : true as NSNumber
],
withInterval: 0.100)
.connectable(
name: "NordicHRM",
services: [.hrmService],
delegate: DummyCBMPeripheralSpecDelegate(),
connectionInterval: 0.250,
mtu: 251)
.build()
// MARK: - Physical Web Beacon
let thingy = CBMPeripheralSpec
.simulatePeripheral(proximity: .outOfRange)
.advertising(
advertisementData: [
CBMAdvertisementDataServiceUUIDsKey : [
CBMUUID(string: "FEAA") // Eddystone
],
CBMAdvertisementDataServiceDataKey : [
// Physical Web beacon: 10ee03676f2e676c2f7049576466972
// type: URL
// TX Power: -18 dBm
// URL: https://goo.gl/pIWdir -> Thingy:52
CBMUUID(string: "FEAA") : Data(base64Encoded: "EO4DZ28uZ2wvcElXZGaXIA==")
]
],
withInterval: 0.250,
alsoWhenConnected: true)
.advertising(
advertisementData: [
CBMAdvertisementDataServiceUUIDsKey : [
CBMUUID(string: "EF680100-9B35-4933-9B10-52FFA9740042")],
CBMAdvertisementDataIsConnectable : true as NSNumber,
CBMAdvertisementDataManufacturerDataKey : Data([0x59, 0x00, 0x5E, 0x91, 0xD9, 0xC3]),
CBMAdvertisementDataLocalNameKey : "Thingy:52",
],
withInterval: 0.100,
delay: 2.0)
.build()
// MARK: - A device with 2 batteries
extension CBMUUID {
static let batteryService = CBMUUID(string: "180F")
static let batteryLevelCharacteristic = CBMUUID(string: "2A19")
static let characteristicUserDesr = CBMUUID(string: "2901")
}
// The mocked Power Pack device has 2 batteries: primary and secondary.
// Battery Service specification allows only one Battery Level
// characteristic in each service, therefore below we define
// two Battery Services, one for each battery.
//
// The service instance cannot be reused, as each has its own unique
// identifier, that is used to distinguish them.
extension CBMCharacteristicMock {
static let primaryBatteryLevelCharacteristic = CBMCharacteristicMock(
type: .batteryLevelCharacteristic,
properties: [.notify, .read],
descriptors: CBMDescriptorMock(type: .characteristicUserDesr)
)
static let secondaryBatteryLevelCharacteristic = CBMCharacteristicMock(
type: .batteryLevelCharacteristic,
properties: [.notify, .read],
descriptors: CBMDescriptorMock(type: .characteristicUserDesr)
)
}
extension CBMServiceMock {
static let primaryBatteryService = CBMServiceMock(
type: .batteryService, primary: true,
characteristics: .primaryBatteryLevelCharacteristic
)
static let secondaryBatteryService = CBMServiceMock(
type: .batteryService, primary: true,
characteristics: .secondaryBatteryLevelCharacteristic
)
}
private class PowerPackCBMPeripheralSpecDelegate: CBMPeripheralSpecDelegate {
private var primaryBatteryLevel: UInt8 = 75
private var secondaryBatteryLevel: UInt8 = 100
private var primaryBatteryLevelData: Data {
return Data([primaryBatteryLevel])
}
private var secondaryBatteryLevelData: Data {
return Data([secondaryBatteryLevel])
}
func peripheral(_ peripheral: CBMPeripheralSpec,
didReceiveReadRequestFor characteristic: CBMCharacteristicMock)
-> Result<Data, Error> {
if characteristic == CBMCharacteristicMock.primaryBatteryLevelCharacteristic {
return .success(primaryBatteryLevelData)
}
if characteristic == CBMCharacteristicMock.secondaryBatteryLevelCharacteristic {
return .success(secondaryBatteryLevelData)
}
return .failure(CBMATTError(.invalidHandle))
}
func peripheral(_ peripheral: CBMPeripheralSpec,
didReceiveReadRequestFor descriptor: CBMDescriptorMock)
-> Result<Data, Error> {
if descriptor.characteristic == CBMCharacteristicMock.primaryBatteryLevelCharacteristic {
return .success("Primary".data(using: .utf8)!)
}
if descriptor.characteristic == CBMCharacteristicMock.secondaryBatteryLevelCharacteristic {
return .success("Secondary".data(using: .utf8)!)
}
return .failure(CBMATTError(.invalidHandle))
}
}
let powerPack = CBMPeripheralSpec
.simulatePeripheral(proximity: .outOfRange)
.advertising(
advertisementData: [
CBMAdvertisementDataLocalNameKey : "Power Pack",
CBMAdvertisementDataServiceUUIDsKey : [CBMUUID.batteryService],
CBMAdvertisementDataIsConnectable : true as NSNumber
],
withInterval: 0.250)
.connectable(
name: "Power Pack",
services: [
.primaryBatteryService,
.secondaryBatteryService,
],
delegate: PowerPackCBMPeripheralSpecDelegate(),
connectionInterval: 0.045,
mtu: 186)
.build()