Skip to content

Commit

Permalink
Merge pull request #32 from XYOracleNetwork/develop
Browse files Browse the repository at this point in the history
address iOS 13.0 deprecations
  • Loading branch information
Phillip Lorenzo authored Mar 3, 2020
2 parents b0ec2b4 + 94d779a commit 43b549d
Show file tree
Hide file tree
Showing 56 changed files with 3,697 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build
name: Pod Build

on:
push:
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/XYOracleNetwork/sdk-ble-swift.git", from: "3.1.5"),
.package(url: "https://github.com/XYOracleNetwork/sdk-ble-swift.git", from: "3.1.6"),
.package(url: "https://github.com/XYOracleNetwork/sdk-core-swift.git", from: "3.1.1"),
],
targets: [
Expand Down
265 changes: 265 additions & 0 deletions Sources/sdk-xyo-swift/BleInterface/XyoBluetoothDevice.swift
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 Sources/sdk-xyo-swift/BleInterface/XyoBluetoothDeviceCreator.swift
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
}
}
Loading

0 comments on commit 43b549d

Please sign in to comment.