Advertisement
Guest User

Untitled

a guest
Apr 21st, 2020
131
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 16.78 KB | None | 0 0
  1. //
  2. //  BLECentral.swift
  3. //  covidIsolate
  4. //
  5. //  Created by luick klippel on 12.04.20.
  6. //  Copyright © 2020 luick klippel. All rights reserved.
  7. //
  8.  
  9. import SwiftUI
  10. import Foundation
  11. import UIKit
  12. import CoreBluetooth
  13. import CoreData
  14. import os
  15.  
  16. class BLECentral : NSObject {
  17.  
  18.     public static let covidIsolateServiceUUID = CBUUID(string: "86223527-b64e-475d-b646-bc45127e1cbb")
  19.    
  20.     public static let characteristicUUID = CBUUID(string: "87aa09fa-7345-406b-8f92-f12f6ba3eceba")
  21.    
  22.     public static var loaded = false
  23.    
  24.     public static var pCIdSize = 0
  25.        
  26.     var centralManager: CBCentralManager!
  27.     var delContext = NSManagedObjectContext()
  28.    
  29.     let personnalContactIdSize = 320
  30.     var receiveBuffer:Data = Data()
  31.  
  32.     var discoveredPeripheral: CBPeripheral?
  33.     var transferCharacteristic: CBCharacteristic?
  34.     var writeIterationsComplete = 0
  35.     var connectionIterationsComplete = 0
  36.    
  37.     let defaultIterations = 5     // change this value based on test usecase
  38.    
  39.     var data = Data()
  40.  
  41.     // MARK: - view lifecycle
  42.    
  43.     public func loadBLECentral(context: NSManagedObjectContext) {
  44.         Alert(title: Text("Info"), message:     Text("beacon loaded"), dismissButton: .default(Text("ok")))
  45.         BLECentral.loaded = true
  46.         self.delContext = context
  47.         centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
  48.        
  49.     }
  50.    
  51.     public func stopBLECentral() {
  52.         BLECentral.loaded = false
  53.         centralManager.stopScan()
  54.         os_log("Scanning stopped")
  55.         receiveBuffer.removeAll(keepingCapacity: false)
  56.         data.removeAll(keepingCapacity: false)
  57.     }
  58.    
  59.     public func startScannig() {
  60.         retrievePeripheral()
  61.     }
  62.    
  63.     public func stopScanning() {
  64.         centralManager.stopScan()
  65.     }
  66.     // MARK: - Helper Methods
  67.  
  68.     /*
  69.      * We will first check if we are already connected to our counterpart
  70.      * Otherwise, scan for peripherals - specifically for our service's 128bit CBUUID
  71.      */
  72.     public func retrievePeripheral() {
  73.        
  74.         let connectedPeripherals: [CBPeripheral] = (centralManager.retrieveConnectedPeripherals(withServices: [BLECentral.covidIsolateServiceUUID]))
  75.        
  76.         os_log("Found connected Peripherals with transfer service: %@", connectedPeripherals)
  77.        
  78.         if let connectedPeripheral = connectedPeripherals.last {
  79.             os_log("Connecting to peripheral %@", connectedPeripheral)
  80.             self.discoveredPeripheral = connectedPeripheral
  81.             centralManager.connect(connectedPeripheral, options: nil)
  82.         } else {
  83.             // We were not connected to our counterpart, so start scanning
  84.             centralManager.scanForPeripherals(withServices: [BLECentral.covidIsolateServiceUUID],
  85.                                                options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
  86.         }
  87.     }
  88.    
  89.     /*
  90.      *  Call this when things either go wrong, or you're done with the connection.
  91.      *  This cancels any subscriptions if there are any, or straight disconnects if not.
  92.      *  (didUpdateNotificationStateForCharacteristic will cancel the connection if a subscription is involved)
  93.      */
  94.     private func cleanup() {
  95.         // Don't do anything if we're not connected
  96.         guard let discoveredPeripheral = discoveredPeripheral,
  97.             case .connected = discoveredPeripheral.state else { return }
  98.        
  99.         for service in (discoveredPeripheral.services ?? [] as [CBService]) {
  100.             for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) {
  101.                 if characteristic.uuid == BLECentral.characteristicUUID && characteristic.isNotifying {
  102.                     // It is notifying, so unsubscribe
  103.                     self.discoveredPeripheral?.setNotifyValue(false, for: characteristic)
  104.                 }
  105.             }
  106.         }
  107.        
  108.         // If we've gotten this far, we're connected, but we're not subscribed, so we just disconnect
  109.         centralManager.cancelPeripheralConnection(discoveredPeripheral)
  110.     }
  111.    
  112.     /*
  113.      *  Write some test data to peripheral
  114.      */
  115.     private func writeData() {
  116.    
  117.         guard let discoveredPeripheral = discoveredPeripheral,
  118.                 let transferCharacteristic = transferCharacteristic
  119.             else { return }
  120.        
  121.         // check to see if number of iterations completed and peripheral can accept more data
  122.         while writeIterationsComplete < defaultIterations && discoveredPeripheral.canSendWriteWithoutResponse {
  123.                    
  124.             let mtu = discoveredPeripheral.maximumWriteValueLength (for: .withoutResponse)
  125.             var rawPacket = [UInt8]()
  126.            
  127.             let bytesToCopy: size_t = min(mtu, data.count)
  128.             data.copyBytes(to: &rawPacket, count: bytesToCopy)
  129.             let packetData = Data(bytes: &rawPacket, count: bytesToCopy)
  130.            
  131.             let stringFromData = String(data: packetData, encoding: .utf8)
  132.             os_log("Writing %d bytes: %s", bytesToCopy, String(describing: stringFromData))
  133.            
  134.             discoveredPeripheral.writeValue(packetData, for: transferCharacteristic, type: .withoutResponse)
  135.            
  136.             writeIterationsComplete += 1
  137.            
  138.         }
  139.        
  140.         if writeIterationsComplete == defaultIterations {
  141.             // Cancel our subscription to the characteristic
  142.             discoveredPeripheral.setNotifyValue(false, for: transferCharacteristic)
  143.         }
  144.     }
  145.    
  146. }
  147.  
  148. extension BLECentral: CBCentralManagerDelegate {
  149.     // implementations of the CBCentralManagerDelegate methods
  150.    
  151.     internal func centralManagerDidUpdateState(_ central: CBCentralManager) {
  152.  
  153.         switch central.state {
  154.         case .poweredOn:
  155.             // ... so start working with the peripheral
  156.             os_log("Central CBManager is powered on")
  157.             retrievePeripheral()
  158.         case .poweredOff:
  159.             os_log("Central CBManager is not powered on")
  160.             // In a real app, you'd deal with all the states accordingly
  161.             return
  162.         case .resetting:
  163.             os_log("CBManager is resetting")
  164.             // In a real app, you'd deal with all the states accordingly
  165.             return
  166.         case .unauthorized:
  167.             // In a real app, you'd deal with all the states accordingly
  168.             if #available(iOS 13.0, *) {
  169.                 switch central.authorization {
  170.                 case .denied:
  171.                     os_log("You are not authorized to use Bluetooth")
  172.                 case .restricted:
  173.                     os_log("Bluetooth is restricted")
  174.                 default:
  175.                     os_log("Unexpected authorization")
  176.                 }
  177.             } else {
  178.                 // Fallback on earlier versions
  179.             }
  180.             return
  181.         case .unknown:
  182.             os_log("CBManager state is unknown")
  183.             // In a real app, you'd deal with all the states accordingly
  184.             return
  185.         case .unsupported:
  186.             os_log("Bluetooth is not supported on this device")
  187.             // In a real app, you'd deal with all the states accordingly
  188.             return
  189.         @unknown default:
  190.             os_log("A previously unknown central manager state occurred")
  191.             // In a real app, you'd deal with yet unknown cases that might occur in the future
  192.             return
  193.         }
  194.     }
  195.  
  196.     /*
  197.      *  This callback comes whenever a peripheral that is advertising the transfer serviceUUID is discovered.
  198.      *  We check the RSSI, to make sure it's close enough that we're interested in it, and if it is,
  199.      *  we start the connection process
  200.      */
  201.     func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
  202.                         advertisementData: [String: Any], rssi RSSI: NSNumber) {
  203.         Alert(title: Text("Discovered device "), message:     Text("-"), dismissButton: .default(Text("ok")))
  204.         // Reject if the signal strength is too low to attempt data transfer.
  205.         // Change the minimum RSSI value depending on your app’s use case.
  206.         guard RSSI.intValue >= -50
  207.             else {
  208.                 os_log("Discovered perhiperal not in expected range, at %d", RSSI.intValue)
  209.                 return
  210.         }
  211.        
  212.         os_log("Discovered %s at %d", String(describing: peripheral.name), RSSI.intValue)
  213.        
  214.         // Device is in range - have we already seen it?
  215.         if discoveredPeripheral != peripheral {
  216.            
  217.             // Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it.
  218.             discoveredPeripheral = peripheral
  219.            
  220.             // And finally, connect to the peripheral.
  221.             os_log("Connecting to perhiperal %@", peripheral)
  222.             centralManager.connect(peripheral, options: nil)
  223.         }
  224.     }
  225.  
  226.     /*
  227.      *  If the connection fails for whatever reason, we need to deal with it.
  228.      */
  229.     func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
  230.         Alert(title: Text("failed to connect "), message:     Text("-"), dismissButton: .default(Text("ok")))
  231.         os_log("Failed to connect to %@. %s", peripheral, String(describing: error))
  232.         cleanup()
  233.     }
  234.    
  235.     /*
  236.      *  We've connected to the peripheral, now we need to discover the services and characteristics to find the 'transfer' characteristic.
  237.      */
  238.     func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
  239.         Alert(title: Text("Peripheral discovered connected"), message:     Text("-"), dismissButton: .default(Text("ok")))
  240.         os_log("Peripheral Connected")
  241.        
  242.         // Stop scanning
  243.         centralManager.stopScan()
  244.         os_log("Scanning stopped")
  245.        
  246.         // set iteration info
  247.         connectionIterationsComplete += 1
  248.         writeIterationsComplete = 0
  249.        
  250.         // Clear the data that we may already have
  251.         data.removeAll(keepingCapacity: false)
  252.        
  253.         // Make sure we get the discovery callbacks
  254.         peripheral.delegate = self
  255.        
  256.         // Search only for services that match our UUID
  257.         peripheral.discoverServices([BLECentral.covidIsolateServiceUUID])
  258.     }
  259.    
  260.     /*
  261.      *  Once the disconnection happens, we need to clean up our local copy of the peripheral
  262.      */
  263.     func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
  264.         os_log("Perhiperal Disconnected")
  265.         discoveredPeripheral = nil
  266.        
  267.         // We're disconnected, so start scanning again
  268.         if connectionIterationsComplete < defaultIterations {
  269.             retrievePeripheral()
  270.         } else {
  271.             os_log("Connection iterations completed")
  272.         }
  273.     }
  274.  
  275. }
  276.  
  277. extension BLECentral: CBPeripheralDelegate {
  278.     // implementations of the CBPeripheralDelegate methods
  279.  
  280.     /*
  281.      *  The peripheral letting us know when services have been invalidated.
  282.      */
  283.     func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
  284.        
  285.         for service in invalidatedServices where service.uuid == BLECentral.covidIsolateServiceUUID {
  286.             os_log("Transfer service is invalidated - rediscover services")
  287.             peripheral.discoverServices([BLECentral.covidIsolateServiceUUID])
  288.         }
  289.     }
  290.  
  291.     /*
  292.      *  The Transfer Service was discovered
  293.      */
  294.     func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
  295.         if let error = error {
  296.             os_log("Error discovering services: %s", error.localizedDescription)
  297.             cleanup()
  298.             return
  299.         }
  300.        
  301.         // Discover the characteristic we want...
  302.        
  303.         // Loop through the newly filled peripheral.services array, just in case there's more than one.
  304.         guard let peripheralServices = peripheral.services else { return }
  305.         for service in peripheralServices {
  306.             peripheral.discoverCharacteristics([BLECentral.characteristicUUID], for: service)
  307.         }
  308.     }
  309.    
  310.     /*
  311.      *  The Transfer characteristic was discovered.
  312.      *  Once this has been found, we want to subscribe to it, which lets the peripheral know we want the data it contains
  313.      */
  314.     func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
  315.         // Deal with errors (if any).
  316.         if let error = error {
  317.             os_log("Error discovering characteristics: %s", error.localizedDescription)
  318.             cleanup()
  319.             return
  320.         }
  321.        
  322.         // Again, we loop through the array, just in case and check if it's the right one
  323.         guard let serviceCharacteristics = service.characteristics else { return }
  324.         for characteristic in serviceCharacteristics where characteristic.uuid == BLECentral.characteristicUUID {
  325.             // If it is, subscribe to it
  326.             transferCharacteristic = characteristic
  327.             peripheral.setNotifyValue(true, for: characteristic)
  328.         }
  329.        
  330.         // Once this is complete, we just need to wait for the data to come in.
  331.     }
  332.    
  333.     /*
  334.      *   This callback lets us know more data has arrived via notification on the characteristic
  335.      */
  336.     func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
  337.         // Deal with errors (if any)
  338.         if let error = error {
  339.             os_log("Error discovering characteristics: %s", error.localizedDescription)
  340.             cleanup()
  341.             return
  342.         }
  343.        
  344.         guard let characteristicData = characteristic.value,
  345.             let stringFromData = String(data: characteristicData, encoding: .utf8) else { return }
  346.        
  347.         os_log("Received %d bytes: %s", characteristicData.count, stringFromData)
  348.        
  349.         // Have we received the end-of-message token?
  350.         if stringFromData == "EOM" {
  351.             // End-of-message case: show the data.
  352.             // Dispatch the text view update to the main queue for updating the UI, because
  353.             // we don't know which thread this method will be called back on.
  354.             DispatchQueue.main.async() {
  355.                 print(String(data: self.data, encoding: .utf8))
  356.             }
  357.            
  358.             // received public contact ID from peripheral, sending own pCId
  359.             let user = cIUtils.fetchSingleUserFromCoreDb(context:self.delContext)!
  360.            
  361.             let personnalContactId = cIUtils.createPersonnalContactId(id: user.id, timeStamp: cIUtils.genStringTimeDateStamp(), privateKey: RSACrypto.getRSAKeyFromKeychain(user.keyPairChainTagName+"-private")!)
  362.            
  363.             // Get the data
  364.             let dataToSend = NSData(bytes: personnalContactId, length: personnalContactId.count) as! Data
  365.             // setting pCId to transfer data
  366.             data = dataToSend
  367.             writeData()
  368.         } else {
  369.             receiveBuffer.append(characteristicData)
  370.             if receiveBuffer.count == personnalContactIdSize {
  371.                 let pCIdListEntry = PersonnalContactIdList(entity: PersonnalContactIdList.entity(), insertInto: delContext)
  372.                 pCIdListEntry.contactId = receiveBuffer.base64EncodedString()
  373.                 receiveBuffer.removeAll()
  374.                 print("added pCId to pCId List")
  375.             }
  376.         }
  377.     }
  378.  
  379.     /*
  380.      *  The peripheral letting us know whether our subscribe/unsubscribe happened or not
  381.      */
  382.     func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
  383.         // Deal with errors (if any)
  384.         if let error = error {
  385.             os_log("Error changing notification state: %s", error.localizedDescription)
  386.             return
  387.         }
  388.        
  389.         // Exit if it's not the transfer characteristic
  390.         guard characteristic.uuid == BLECentral.characteristicUUID else { return }
  391.        
  392.         if characteristic.isNotifying {
  393.             // Notification has started
  394.             os_log("Notification began on %@", characteristic)
  395.         } else {
  396.             // Notification has stopped, so disconnect from the peripheral
  397.             os_log("Notification stopped on %@. Disconnecting", characteristic)
  398.             cleanup()
  399.         }
  400.        
  401.     }
  402.    
  403.     /*
  404.      *  This is called when peripheral is ready to accept more data when using write without response
  405.      */
  406.     func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
  407.         os_log("Peripheral is ready, send data")
  408.         // not sending data, waiting for public contact id from peripheral
  409.         // writeData()
  410.     }
  411.    
  412. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement