Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 35 additions & 34 deletions Bluetooth/BluetoothSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@

import CoreBluetooth
import Foundation
import LibreTransmitter
import OSLog
import UIKit

import Combine

struct RSSIInfo {
let bledeviceID: String
let signalStrength: Int
public struct RSSIInfo {
public let bledeviceID: String
public let signalStrength: Int

var totalBars: Int {
public var totalBars: Int {
3
}

var signalBars: Int {
public var signalBars: Int {
if signalStrength < -80 {
return 1 // near
}
Expand All @@ -36,30 +35,35 @@ struct RSSIInfo {

}

final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
public protocol BluetoothSearcher {
func disconnectManually()
func scanForCompatibleDevices()
func stopTimer()

var passThroughMetaData: PassthroughSubject<(PeripheralProtocol, [String: Any]), Never> { get }
var throttledRSSI: GenericThrottler<RSSIInfo, String> { get }
}


public final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, BluetoothSearcher {

var centralManager: CBCentralManager!

fileprivate lazy var logger = Logger(forType: Self.self)

// fileprivate let deviceNames = SupportedDevices.allNames
// fileprivate let serviceUUIDs:[CBUUID]? = [CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")]

private var discoveredDevices = [CBPeripheral]()

public let passThrough = PassthroughSubject<CBPeripheral, Never>()
public let passThroughMetaData = PassthroughSubject<(CBPeripheral, [String: Any]), Never>()
public let passThroughMetaData = PassthroughSubject<(PeripheralProtocol, [String: Any]), Never>()
public let throttledRSSI = GenericThrottler(identificator: \RSSIInfo.bledeviceID, interval: 5)

private var rescanTimerBag = Set<AnyCancellable>()

public func addDiscoveredDevice(_ device: CBPeripheral, with metadata: [String: Any], rssi: Int) {
passThrough.send(device)
passThroughMetaData.send((device, metadata))
throttledRSSI.incoming.send(RSSIInfo(bledeviceID: device.identifier.uuidString, signalStrength: rssi))
}

override init() {
public override init() {
super.init()
// calling readrssi on a peripheral is only supported on connected peripherals
// here we want the AllowDuplicatesKey to be true so that we get a continous feed of new rssi values for
Expand Down Expand Up @@ -106,7 +110,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph
self.scanForCompatibleDevices()
}

func scanForCompatibleDevices() {
public func scanForCompatibleDevices() {

if centralManager.state == .poweredOn && !centralManager.isScanning {
logger.debug("Before scan for transmitter while central manager state \(String(describing: self.centralManager.state.rawValue))")
Expand All @@ -120,35 +124,32 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph
}
}

func disconnectManually() {
public func disconnectManually() {
logger.debug("did disconnect manually")
// NotificationManager.scheduleDebugNotification(message: "Timer fired in Background", wait: 3)
// _ = Timer(timeInterval: 150, repeats: false, block: {timer in NotificationManager.scheduleDebugNotification(message: "Timer fired in Background", wait: 0.5)})

if centralManager.isScanning {
centralManager.stopScan()
}
if centralManager.isScanning {
centralManager.stopScan()
}
}

// MARK: - CBCentralManagerDelegate

func centralManagerDidUpdateState(_ central: CBCentralManager) {
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
logger.debug("Central Manager did update state to \(String(describing: central.state.rawValue))")
switch central.state {
case .poweredOff, .resetting, .unauthorized, .unknown, .unsupported:
logger.debug("Central Manager was either .poweredOff, .resetting, .unauthorized, .unknown, .unsupported: \(String(describing: central.state))")
case .poweredOn:
// we don't want this to start scanning right away, but rather wait until the view has appeared
// this means that the view is responsible for calling scanForCompatibleDevices it self
// scanForCompatibleDevices() // power was switched on, while app is running -> reconnect.
//scanForCompatibleDevices() // power was switched on, while app is running -> reconnect.
break

@unknown default:
fatalError("libre bluetooth state unhandled")
}
}

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
guard let name = peripheral.name?.lowercased() else {
logger.debug("could not find name for device \(peripheral.identifier.uuidString)")
return
Expand Down Expand Up @@ -176,22 +177,22 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph
}
}

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// self.lastConnectedIdentifier = peripheral.identifier.uuidString

}

func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
logger.error("did fail to connect")
}

func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
logger.debug("did didDisconnectPeripheral")
}

// MARK: - CBPeripheralDelegate

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
logger.debug("Did discover services")
if let error {
logger.error("Did discover services error: \(error.localizedDescription)")
Expand All @@ -206,7 +207,7 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph
}
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
logger.debug("Did discover characteristics for service \(String(describing: peripheral.name))")

if let error {
Expand All @@ -230,22 +231,22 @@ final class BluetoothSearchManager: NSObject, CBCentralManagerDelegate, CBPeriph
}
}

func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {

// throttledRSSI.incoming.send(RSSIInfo(bledeviceID: peripheral.identifier.uuidString, signalStrength: RSSI.intValue))

// peripheral.readRSSI() //we keep contuing to update the rssi (only works if peripheral is already connected....

}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
logger.debug("Did update notification state for characteristic: \(String(describing: characteristic.debugDescription))")
}

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
logger.debug("Did update value for characteristic: \(String(describing: characteristic.debugDescription))")
}

func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
logger.debug("Did Write value \(String(characteristic.value.debugDescription)) for characteristic \(String(characteristic.debugDescription))")
}

Expand Down
53 changes: 1 addition & 52 deletions Bluetooth/CBPeripheralExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,8 @@ public enum Either<A, B> {
case Right(B)
}

public typealias SomePeripheral = Either<CBPeripheral, MockedPeripheral>

extension SomePeripheral: PeripheralProtocol, Identifiable, Hashable, Equatable {
public static func == (lhs: Either<A, B>, rhs: Either<A, B>) -> Bool {
lhs.asStringIdentifier == rhs.asStringIdentifier
}

public var id: String {
actualPeripheral.asStringIdentifier
}

public func hash(into hasher: inout Hasher) {
hasher.combine(actualPeripheral.asStringIdentifier)
}

private var actualPeripheral: PeripheralProtocol {
switch self {
case let .Left(real):
return real
case let .Right(mocked):
return mocked
}
}
public var name: String? {
actualPeripheral.name
}

public var name2: String {
actualPeripheral.name2
}

public var asStringIdentifier: String {
actualPeripheral.asStringIdentifier
}
}

extension CBPeripheral: PeripheralProtocol, Identifiable {
extension CBPeripheral: PeripheralProtocol {
public var name2: String {
self.name ?? ""
}
Expand All @@ -66,19 +31,3 @@ extension CBPeripheral: PeripheralProtocol, Identifiable {
self.identifier.uuidString
}
}

public class MockedPeripheral: PeripheralProtocol, Identifiable {
public var name: String?

public var name2: String {
name ?? "unknown-device"
}

public var asStringIdentifier: String {
name2
}

public init(name: String) {
self.name = name
}
}
4 changes: 2 additions & 2 deletions Bluetooth/GenericThrottler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation
import Combine

class GenericThrottler<T, U: Hashable> {
public class GenericThrottler<T, U: Hashable> {

public var throttledPublisher: AnyPublisher<T, Never> {
throttledSubject.eraseToAnyPublisher()
Expand Down Expand Up @@ -88,7 +88,7 @@ class GenericThrottler<T, U: Hashable> {
.store(in: &bag)
}

init(identificator: KeyPath<T, U>, interval: TimeInterval) {
public init(identificator: KeyPath<T, U>, interval: TimeInterval) {
self.identificator = identificator
self.interval = interval

Expand Down
2 changes: 1 addition & 1 deletion Bluetooth/LibreTransmitterMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public enum SensorType: String, CustomStringConvertible {
case 0xDF, 0xA2: self = .libre1
case 0xE5, 0xE6: self = .libreUS14day
case 0x70: self = .libreProH
case 0x9D: self = .libre2
case 0xC5, 0x9D: self = .libre2
case 0x76: self = patchInfo[3] == 0x02 ? .libre2US : patchInfo[3] == 0x04 ? .libre2CA : patchInfo[2] >> 4 == 7 ? .libreSense : .unknown
default:
if patchInfo.count == 24 {
Expand Down
4 changes: 2 additions & 2 deletions Bluetooth/Transmitter/BubbleTransmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class BubbleTransmitter: MiaoMiaoTransmitter {
UIImage(named: "bubble", in: Bundle.current, compatibleWith: nil)
}

override static func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool {
override static func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool {
peripheral.name?.lowercased().starts(with: "bubble") ?? false
}

Expand Down Expand Up @@ -93,7 +93,7 @@ class BubbleTransmitter: MiaoMiaoTransmitter {

private static func getDeviceDetailsFromAdvertisementInternal(advertisementData: [String: Any]?) -> (String?, String?, String?) {

guard let data = advertisementData?["kCBAdvDataManufacturerData"] as? Data else {
guard let data = advertisementData?[CBAdvertisementDataManufacturerDataKey] as? Data else {
return (nil, nil, nil)
}
var mac = ""
Expand Down
2 changes: 1 addition & 1 deletion Bluetooth/Transmitter/Libre2DirectTransmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Libre2DirectTransmitter: LibreTransmitterProxyProtocol {
private var sensorData: SensorData?
private var metadata: LibreTransmitterMetadata?

class func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool {
class func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool {
peripheral.name?.lowercased().starts(with: "abbott") ?? false
}

Expand Down
33 changes: 4 additions & 29 deletions Bluetooth/Transmitter/LibreTransmitterProxyManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -449,28 +449,15 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg
var foundUUID = manufacturerData.subdata(in: 2..<8)
foundUUID.append(contentsOf: [0x07, 0xe0])

logger.debug("ManufacturerData: \(manufacturerData), found uid: \(foundUUID)")
logger.debug("ManufacturerData: \(manufacturerData.hex), found uid: \(foundUUID.hex)")

guard foundUUID == selectedUid && Libre2DirectTransmitter.canSupportPeripheral(peripheral) else {
return false
}



return true
}

private func verifyLibre2ByName(peripheral: CBPeripheral, name: String) -> Bool {

// This method is not as robust, and should only be used in cases where manufacturerdata is not available, such as when using the tzachi-dar simulator
guard peripheral.name?.contains(name) == true else {
return false
}
return Libre2DirectTransmitter.canSupportPeripheral(peripheral)


}

public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
dispatchPrecondition(condition: .onQueue(managerQueue))

Expand All @@ -488,22 +475,10 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg
logger.debug("preselected sensor is: \(String(describing:sensor))")


if let sensor = UserDefaults.standard.preSelectedSensor, let name = sensor.sensorName, sensor.initialIdentificationStrategy == .byFakeSensorName {
logger.debug("Verifiying libre2 connection using sensor name")
if !verifyLibre2ByName(peripheral: peripheral, name: name) {
logger.debug("failed Verifiying libre2 connection using sensor name")
return
}

} else {
logger.debug("Verifiying libre2 connection using manufacturerData")
if !verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) {
logger.debug("failed Verifiying libre2 connection using manufacturerData")
return
}
if !verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) {
logger.debug("failed Verifiying libre2 connection using manufacturerData")
return
}



// next time we search via bluetooth, let's identify the sensor with its bluetooth identifier
UserDefaults.standard.preSelectedUid = nil
Expand Down
6 changes: 3 additions & 3 deletions Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public protocol LibreTransmitterProxyProtocol: AnyObject {
static var manufacturerer: String { get }
static var requiresPhoneNFC: Bool { get }
static var requiresSetup: Bool { get }
static func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool
static func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool

static var writeCharacteristic: UUIDContainer? { get set }
static var notifyCharacteristic: UUIDContainer? { get set }
Expand All @@ -36,7 +36,7 @@ public protocol LibreTransmitterProxyProtocol: AnyObject {
}

extension LibreTransmitterProxyProtocol {
func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool {
func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool {
Self.canSupportPeripheral(peripheral)
}
public var staticType: LibreTransmitterProxyProtocol.Type {
Expand Down Expand Up @@ -78,7 +78,7 @@ public enum LibreTransmitters {
getSupportedPlugins(peripheral)?.isEmpty == false
}

public static func getSupportedPlugins(_ peripheral: CBPeripheral) -> [LibreTransmitterProxyProtocol.Type]? {
public static func getSupportedPlugins(_ peripheral: PeripheralProtocol) -> [LibreTransmitterProxyProtocol.Type]? {
all.enumerated().compactMap {
$0.element.canSupportPeripheral(peripheral) ? $0.element : nil
}
Expand Down
2 changes: 1 addition & 1 deletion Bluetooth/Transmitter/MiaomiaoTransmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ class MiaoMiaoTransmitter: LibreTransmitterProxyProtocol {
private var sensorData: SensorData?
private var metadata: LibreTransmitterMetadata?

class func canSupportPeripheral(_ peripheral: CBPeripheral) -> Bool {
class func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool {
peripheral.name?.lowercased().starts(with: "miaomiao") ?? false
}

Expand Down
Loading