-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #32 from XYOracleNetwork/develop
address iOS 13.0 deprecations
- Loading branch information
Showing
56 changed files
with
3,697 additions
and
6 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
.github/workflows/sdkBuild.yml → .github/workflows/podBuild.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
name: Build | ||
name: Pod Build | ||
|
||
on: | ||
push: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
265 changes: 265 additions & 0 deletions
265
Sources/sdk-xyo-swift/BleInterface/XyoBluetoothDevice.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
// | ||
// XyoBluetoothDevice.swift | ||
// mod-ble-swift | ||
// | ||
// Created by Carter Harrison on 2/10/19. | ||
// Copyright © 2019 XYO Network. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import Promises | ||
import CoreBluetooth | ||
import XyBleSdk | ||
import sdk_core_swift | ||
|
||
/// A class that gets created with XYO pipe enabled devices arround the world. Each device complies to the | ||
/// XyoNetworkPipe interface, meaning that data can be send and recived beetwen them. Please note that | ||
/// one chould not use an instance of this class as a pipe, but tryCreatePipe() to get an instance of | ||
/// a pipe. | ||
open class XyoBluetoothDevice: XYBluetoothDeviceBase, XYBluetoothDeviceNotifyDelegate, XyoNetworkPipe { | ||
public func getNetworkHeuristics() -> [XyoObjectStructure] { | ||
return [] | ||
} | ||
|
||
/// The defining family for a XyoBluetoothDevice, this helps the process of creatig a device, and making | ||
/// sure that it complies to the XYO pipe spec. | ||
public static let family = XYDeviceFamily.init(uuid: UUID(uuidString: XyoBluetoothDevice.uuid)!, | ||
prefix: XyoBluetoothDevice.prefix, | ||
familyName: XyoBluetoothDevice.familyName, | ||
id: XyoBluetoothDevice.id) | ||
|
||
/// The ID of an XyoBluetoothDevice | ||
public static let id = "XYO" | ||
|
||
/// The primary service UUID of a XyoBluetoothDevice | ||
public static let uuid : String = XyoService.pipe.serviceUuid.uuidString | ||
|
||
/// The faimly name of a XyoBluetoothDevice | ||
public static let familyName : String = "XYO" | ||
|
||
/// The prefix of a XyoBluetoothDevice | ||
public static let prefix : String = "xy:ibeacon" | ||
|
||
/// The input stream of the device at the other end of the pipe. | ||
private var inputStream = XyoInputStream() | ||
|
||
/// The promise to wait when waiting for a new packed to be completed in the inputStream. | ||
private var recivePromise : Promise<[UInt8]?>? = nil | ||
|
||
/// Creates a new instance of XyoBluetoothDevice using an id and rssi. | ||
/// - Parameter id: The peripheral id of the device to create. | ||
/// - Parameter iBeacon: The IBeacon of the device. | ||
/// - Parameter rssi: The rssi of the device when scaned, will defualt to XYDeviceProximity.none.rawValue. | ||
public init(_ id: String, iBeacon: XYIBeaconDefinition? = nil, rssi: Int = XYDeviceProximity.none.rawValue) { | ||
super.init(id, rssi: rssi, family: XyoBluetoothDevice.family, iBeacon: iBeacon) | ||
} | ||
|
||
/// A convenience init that does not need an id. | ||
/// - Parameter iBeacon: The IBeacon of the device. | ||
/// - Parameter rssi: The rssi of the device when scaned, will defualt to XYDeviceProximity.none.rawValue. | ||
public convenience init(iBeacon: XYIBeaconDefinition, rssi: Int = XYDeviceProximity.none.rawValue) { | ||
self.init(iBeacon.xyId(from: XyoBluetoothDevice.family), iBeacon: iBeacon, rssi: rssi) | ||
} | ||
|
||
open func getMtu () -> Int { | ||
return peripheral?.maximumWriteValueLength(for: CBCharacteristicWriteType.withResponse) ?? 22 | ||
} | ||
|
||
/// A function to try and create a pipe. This should be the function used to create a pipe, not using this instance | ||
/// as a pipe, even though it may work, it will not work consistsnatly. | ||
/// - Warning: This function is blocking while waiting to subscribe to the device, and this function should be called | ||
/// withen a connection block | ||
public func tryCreatePipe () -> XyoNetworkPipe? { | ||
/// make sure to clear the input stream for a new pipe | ||
self.inputStream = XyoInputStream() | ||
|
||
/// we use a unique name as the delegate key to prevent overriding keys | ||
let result = self.subscribe(to: XyoService.pipe, delegate: (key: "notify [DBG: \(#function)]: \(Unmanaged.passUnretained(self).toOpaque())", delegate: self)) | ||
if (result.error == nil) { | ||
print("Created PIPE") | ||
return self | ||
} | ||
|
||
return nil | ||
} | ||
|
||
/// This function tries to attatch a XYPeripheral as the peripheral for this device model, this will work if the | ||
/// device has a XYO UUID in the advertisement | ||
/// - Parameter peripheral: The XYO pipe enabled peripheral to try and attatch | ||
/// - Returns: If the attatchment of the peripheral was sucessfull. | ||
override open func attachPeripheral(_ peripheral: XYPeripheral) -> Bool { | ||
guard | ||
self.peripheral == nil | ||
else { return false } | ||
|
||
if (checkForName(peripheral) || checkForXyoUuid(peripheral)) { | ||
// Set the peripheral and delegate to self | ||
self.peripheral = peripheral.peripheral | ||
self.peripheral?.delegate = self | ||
|
||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
/// Checks to see if an advertisement contains the XYO service UUID | ||
/// - Parameter peripheral: The device to check for the XYO service UUID. | ||
/// - Returns: If the device is advertising the XYO service UUID. | ||
private func checkForXyoUuid (_ peripheral: XYPeripheral) -> Bool { | ||
guard | ||
let services = peripheral.advertisementData?[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] | ||
else { return false } | ||
|
||
guard | ||
services.contains(CBUUID(string: XyoBluetoothDevice.uuid)) | ||
else { return false } | ||
|
||
return true | ||
} | ||
|
||
/// Checks to see if an advertisement contains the major and minor of this iBeacon device in the name. | ||
/// - Parameter peripheral: The device to check for name. | ||
/// - Returns: If the device contains the proper name. | ||
private func checkForName (_ peripheral: XYPeripheral) -> Bool { | ||
guard | ||
let major = self.iBeacon?.major, | ||
let minor = self.iBeacon?.minor, | ||
let deviceName = peripheral.advertisementData?[CBAdvertisementDataLocalNameKey] as? String | ||
else { | ||
return false | ||
} | ||
|
||
let majorMinorName = XyoGattNameEncoder.encode(major: major, minor: minor) | ||
return deviceName == majorMinorName | ||
} | ||
|
||
/// Gets the first data that was sent to this device, since the client is allways the one initing, this will | ||
/// allways return nil | ||
/// - Returns: Returns the first data srecived through the pipe if not initing. | ||
public func getInitiationData() -> XyoAdvertisePacket? { | ||
// this is because we are allways a client | ||
return nil | ||
} | ||
|
||
/// Sends data to the peripheral and waits for a response if the waitForResponse flag is set. | ||
/// - Warning: This function is blocking while it waits for bluetooth calls. | ||
/// - Parameter data: The data to send to the other device at the end of the pipe | ||
/// - Parameter waitForResponse: Weather or not to wait for a response after sending | ||
/// - Returns: Will return the response from the other party, will return nil if there was an error or if | ||
/// waitForResponse was set to false. | ||
public func send(data: [UInt8], waitForResponse: Bool, completion: @escaping ([UInt8]?) -> ()) { | ||
print("SENDING: \(data.toHexString())") | ||
if (!chunkSend(bytes: data, characteristic: XyoService.pipe, sizeOfChunkSize: XyoObjectSize.FOUR)) { | ||
completion(nil) | ||
return | ||
} | ||
|
||
if (waitForResponse) { | ||
completion(waitForRead()) | ||
return | ||
} | ||
|
||
completion(nil) | ||
} | ||
|
||
/// Sends data to the peripheral at the other end of the pipe, via the XYO pipe protocol. | ||
/// - Parameter bytes: The bytes to send to the other end of the pipe | ||
/// - Parameter characteristic: The charistic to chunk write to | ||
/// - Parameter sizeOfChunkSize: The number of bytes to prepend the size with when sening chunks | ||
/// - Warning: This function is blocking while it waits for bluetooth calls. | ||
/// - Returns: This function returns the success of the chunk send | ||
func chunkSend (bytes : [UInt8], characteristic: XYServiceCharacteristic, sizeOfChunkSize: XyoObjectSize) -> Bool { | ||
let sizeEncodedBytes = XyoBuffer() | ||
|
||
switch sizeOfChunkSize { | ||
case .ONE: sizeEncodedBytes.put(bits: UInt8(bytes.count + 1)) | ||
case .TWO: sizeEncodedBytes.put(bits: UInt16(bytes.count + 2)) | ||
case.FOUR: sizeEncodedBytes.put(bits: UInt32(bytes.count + 4)) | ||
case.EIGHT: sizeEncodedBytes.put(bits: UInt64(bytes.count + 8)) | ||
} | ||
|
||
sizeEncodedBytes.put(bytes: bytes) | ||
|
||
let chunks = XyoOutputStream.chunk(bytes: sizeEncodedBytes.toByteArray(), maxChunkSize: getMtu() - 3) | ||
|
||
for chunk in chunks { | ||
print("SENDING CHUNK \(chunk.toHexString())") | ||
let status = self.set(characteristic, value: XYBluetoothResult(data: Data(chunk)), withResponse: true) | ||
|
||
print("DONE SENDING CHUNK") | ||
|
||
// break the loop if there was an error | ||
if (status.error != nil) { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
/// Waits for the next read request to come in, if one has allready come in before calling this function, | ||
/// it will return it. | ||
/// - Warning: This function is blocking while it waits for bluetooth calls. | ||
/// - Returns: This function returns what the divice on the other end of the pipe just sent. | ||
private func waitForRead () -> [UInt8]? { | ||
print("WAITING FOR READ") | ||
var latestPacket : [UInt8]? = inputStream.getOldestPacket() | ||
if (latestPacket == nil) { | ||
recivePromise = Promise<[UInt8]?>.pending().timeout(20) | ||
do { | ||
latestPacket = try await(recivePromise.unsafelyUnwrapped) | ||
} catch { | ||
print("TIMEOUT") | ||
// timeout has occored | ||
return nil | ||
} | ||
} | ||
|
||
inputStream.removePacket() | ||
|
||
print("READ ENTIRE \(latestPacket?.toHexString() ?? "--")") | ||
return latestPacket | ||
} | ||
|
||
/// This function terminates the bluetooth connection and should be called beetwen creating pipes. | ||
public func close() { | ||
disconnect() | ||
} | ||
|
||
public func getNetworkHuerestics() -> [XyoObjectStructure] { | ||
var toReturn = [XyoObjectStructure]() | ||
|
||
let pwr = self.iBeacon?.powerLevel | ||
|
||
if pwr != nil { | ||
let pwrTag = XyoObjectStructure.newInstance(schema: XyoSchemas.BLE_POWER_LEVEL, bytes: XyoBuffer().put(bits: pwr!)) | ||
toReturn.append(pwrTag) | ||
} | ||
|
||
let unsignedRssi = UInt8(bitPattern: Int8(self.rssi)) | ||
let rssiTag = XyoObjectStructure.newInstance(schema: XyoSchemas.RSSI, bytes: XyoBuffer().put(bits: (unsignedRssi))) | ||
|
||
toReturn.append(rssiTag) | ||
|
||
return toReturn | ||
} | ||
|
||
/// This function is called whenever a charisteristic is updated, and is how the XYO pipe recives data. | ||
/// This function will also add to the input stream, and resume a read promise if there is one existing. | ||
/// - Parameter serviceCharacteristic: The characteristic that is being updated, this should be the XYO serivce | ||
/// - Parameter value: The value that characteristic has been changed to (or notifyed of) | ||
public func update(for serviceCharacteristic: XYServiceCharacteristic, value: XYBluetoothResult) { | ||
if (!value.hasError && value.asByteArray != nil) { | ||
print("GOT NOTIFACTION \(value.asByteArray!.toHexString())") | ||
inputStream.addChunk(packet: value.asByteArray!) | ||
|
||
guard let donePacket = inputStream.getOldestPacket() else { | ||
return | ||
} | ||
|
||
recivePromise?.fulfill(donePacket) | ||
} | ||
} | ||
} | ||
|
68 changes: 68 additions & 0 deletions
68
Sources/sdk-xyo-swift/BleInterface/XyoBluetoothDeviceCreator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// | ||
// XYOBluetoothDeviceCreator.swift | ||
// Pods-SampleiOS | ||
// | ||
// Created by Carter Harrison on 2/5/19. | ||
// | ||
|
||
import Foundation | ||
import sdk_core_swift | ||
import XyBleSdk | ||
|
||
/// A struct to manage the creation of XYO Devices to create pipes with. | ||
public struct XyoBluetoothDeviceCreator : XYDeviceCreator { | ||
/// This is a mapping of xyo manufactor IDs (first 8 bits of iBeacon minor) to a sepcial type of device. | ||
public static var manufactorMap = [UInt8 : XyoManufactorDeviceCreator]() | ||
|
||
private init () {} | ||
|
||
/// The UUID that should be used when creating an XYO device. | ||
public static let uuid : String = XyoBluetoothDevice.uuid | ||
|
||
/// The device family deffinition. | ||
public var family: XYDeviceFamily = XyoBluetoothDevice.family | ||
|
||
/// A function to create an XYO device from an iBeacon deffinition. | ||
/// - Parameter iBeacon: The IBeacon deffinion of the device. | ||
/// - Parameter rssi: The rssi to create the device with. | ||
public func createFromIBeacon (iBeacon: XYIBeaconDefinition, rssi: Int) -> XYBluetoothDevice? { | ||
guard let manufactorId = getXyoManufactorIdFromIbeacon(iBeacon: iBeacon) else { | ||
return XyoBluetoothDevice(iBeacon: iBeacon, rssi: rssi) | ||
} | ||
|
||
guard let creator = XyoBluetoothDeviceCreator.manufactorMap[manufactorId] else { | ||
return XyoBluetoothDevice(iBeacon: iBeacon, rssi: rssi) | ||
} | ||
|
||
return creator.createFromIBeacon(iBeacon: iBeacon, rssi: rssi) | ||
} | ||
|
||
/// Creae an XyoBluetoothDevice from its repected peripheral ID. | ||
/// - Parameter id: The peripheral ID of the device. | ||
public func createFromId(id: String) -> XYBluetoothDevice { | ||
return XyoBluetoothDevice(id) | ||
} | ||
|
||
/// Enable the creater to be active in XYBluetoothDeviceFactory so that it can be created from bluetooth scan results. | ||
/// - Parameter enable: If true, will enable. If false, will disbale. | ||
public static func enable (enable : Bool) { | ||
if (enable) { | ||
XYBluetoothDeviceFactory.addCreator(uuid: XyoBluetoothDevice.uuid.lowercased(), creator: XyoBluetoothDeviceCreator()) | ||
} else { | ||
XYBluetoothDeviceFactory.removeCreator(uuid: XyoBluetoothDevice.uuid.lowercased()) | ||
} | ||
} | ||
|
||
private func getXyoManufactorIdFromIbeacon (iBeacon: XYIBeaconDefinition) -> UInt8? { | ||
guard let major = iBeacon.major else { | ||
return nil | ||
} | ||
|
||
let byte = XyoBuffer() | ||
.put(bits: major) | ||
.getUInt8(offset: 1) | ||
|
||
// masks the byte with 00111111 | ||
return byte & 0x3f | ||
} | ||
} |
Oops, something went wrong.