Last active
May 19, 2020 13:37
-
-
Save DaisukeHirata/d40e7e6d22a281e1525fa6e7fe6fcc0b to your computer and use it in GitHub Desktop.
Beacon Round Robin and Core Bluetooth to stay awake forever next to each other
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
// | |
// BeaconRoundRobinHandler.swift | |
// use beacon ranging the os handles bringing your app back up, | |
// if you make the devices round robin the devices pretty much stay awake forever next to each other. | |
// The round robin causes the device to broadcast as a new location beacon causing other phones to wake up. | |
// This implementation comes from Fuzz. | |
// | |
import UIKit | |
import CoreLocation | |
import CoreBluetooth | |
enum Beacon: String, CaseIterable { | |
case beacon1 = "D73D3920-1711-4F41-88D6-39BF383C4D7F" | |
case beacon2 = "0D712E02-7D43-4014-9404-2E6E13663B23" | |
case beacon3 = "8830D5FA-54C2-4D19-BD67-CA63C24BF751" | |
case beacon4 = "D66B66E2-8C9C-4BE5-90BC-6853F4C9869B" | |
case beacon5 = "D3E5EC73-3E45-4A6B-8159-CD32BD2F98EA" | |
case beacon6 = "BB109B2B-AF2E-49D3-B7FF-776F238C77EA" | |
var uuid: UUID? { | |
return UUID(uuidString: rawValue) | |
} | |
var major: CLBeaconMajorValue? { | |
return CLBeaconMajorValue("0") | |
} | |
var minor: CLBeaconMinorValue? { | |
return CLBeaconMinorValue("0") | |
} | |
var region: CLBeaconRegion? { | |
if let cachedRegion = Beacon.regionCache[self] { return cachedRegion } | |
guard let uuid = uuid else { return nil } | |
guard let major = major else { return nil } | |
guard let minor = minor else { return nil } | |
let region = CLBeaconRegion(proximityUUID: uuid, major: major, minor: minor, identifier: rawValue) | |
region.notifyOnEntry = true | |
region.notifyOnExit = true | |
region.notifyEntryStateOnDisplay = true | |
Beacon.regionCache[self] = region | |
return region | |
} | |
var next: Beacon { | |
if let index = Beacon.allCases.index(of: self){ | |
if index + 1 < Beacon.allCases.count { | |
return Beacon.allCases[index + 1] | |
} | |
} | |
return Beacon.allCases[0] | |
} | |
static private var regionCache = [Beacon: CLBeaconRegion]() | |
static var regions: [CLBeaconRegion] { Beacon.allCases.compactMap { $0.region } } | |
} | |
extension CLRegion { | |
// This is helpfull because all location managers | |
// share region monitoring. So we need to filter for | |
// the correct regions when using delegate methods | |
var isOutbreakBeaconRegion: Bool { | |
Beacon.regions.contains(where: { $0.identifier == self.identifier }) | |
} | |
} | |
class BeaconReceiver: NSObject, CLLocationManagerDelegate { | |
var locationManager = CLLocationManager() | |
func start() { | |
if #available(iOS 11.0, *) { | |
locationManager.showsBackgroundLocationIndicator = false | |
} | |
locationManager.allowsBackgroundLocationUpdates = true | |
locationManager.desiredAccuracy = kCLLocationAccuracyBest | |
locationManager.delegate = self | |
locationManager.requestAlwaysAuthorization() | |
locationManager.monitoredRegions.filter({ $0.isOutbreakBeaconRegion }).forEach { | |
self.locationManager.stopMonitoring(for: $0) | |
} | |
Beacon.regions.forEach { | |
locationManager.startMonitoring(for: $0) | |
} | |
locationManager.startUpdatingLocation() | |
} | |
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { | |
guard region.isOutbreakBeaconRegion else { return } | |
print("enter region: \(region)") | |
UpdateTimer.getInstance().wakeupFromBackground() | |
} | |
} | |
class BeaconBroadcaster: NSObject, CBPeripheralManagerDelegate { | |
var peripheral: CBPeripheralManager? | |
var timer: Timer? | |
var beacon: Beacon? | |
let refreshRate = 10.0 | |
func start() { | |
peripheral?.stopAdvertising() | |
peripheral = CBPeripheralManager(delegate: self, queue: nil) | |
} | |
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { | |
print(peripheral.state.rawValue) | |
if peripheral.state == .poweredOn { | |
broadcast(beacon: self.beacon?.next ?? Beacon.beacon1) | |
} | |
} | |
private func broadcast(beacon: Beacon) { | |
guard let region = beacon.region else { return } | |
let peripheralData: NSMutableDictionary = region.peripheralData(withMeasuredPower: nil) | |
peripheral?.startAdvertising(((peripheralData as NSDictionary) as! [String : Any])) | |
timer = Timer.scheduledTimer(withTimeInterval: refreshRate, repeats: false) { _ in | |
DispatchQueue.main.async { [unowned self] in | |
self.peripheral?.stopAdvertising() | |
self.broadcast(beacon: beacon.next) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment