From 1ad9475d55543538009efee02d4698e76fe37847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Fri, 13 Jun 2025 08:48:56 +0200 Subject: [PATCH 1/7] Libre 2 plus pairing fixes --- LibreSensor/SensorPairing/SensorPairingService.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LibreSensor/SensorPairing/SensorPairingService.swift b/LibreSensor/SensorPairing/SensorPairingService.swift index 492d64c..06e45d1 100644 --- a/LibreSensor/SensorPairing/SensorPairingService.swift +++ b/LibreSensor/SensorPairing/SensorPairingService.swift @@ -169,9 +169,11 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor tag.customCommand(requestFlags: .highDataRate, customCommandCode: Int(cmd.code), customRequestParameters: cmd.parameters) { response, _ in var streamingEnabled = false + var macAddress = "" if subCmd == .enableStreaming && response.count == 6 { streamingEnabled = true + macAddress = Data(response.reversed()).hexEncodedString().uppercased() } @@ -179,7 +181,7 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor let patchHex = patchInfo.hexEncodedString() let sensorType = SensorType(patchInfo: patchInfo) - print("got patchhex: \(patchHex) and sensorType: \(sensorType)") + print("got patchhex: \(patchHex) and sensorType: \(sensorType), with mac address: \(macAddress)") guard sensorUID.count == 8 && patchInfo.count == 6 && fram.count == 344 else { // self.readingsSubject.send(completion: .failure(LibreError.noSensorData)) From 6d8f47c844fa86b3bede88f3ff7f32f10028a8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Fri, 13 Jun 2025 09:40:34 +0200 Subject: [PATCH 2/7] l2: collect mac address --- Bluetooth/LibreTransmitterMetadata.swift | 2 +- .../Transmitter/Libre2DirectTransmitter.swift | 2 +- .../LibreTransmitterProxyProtocol.swift | 3 +- LibreSensor/SensorContents/Sensor.swift | 49 ++----------------- LibreSensor/SensorPairing/SensorPairing.swift | 8 ++- .../SensorPairing/SensorPairingService.swift | 2 +- 6 files changed, 14 insertions(+), 52 deletions(-) diff --git a/Bluetooth/LibreTransmitterMetadata.swift b/Bluetooth/LibreTransmitterMetadata.swift index 9f5c836..dfe50db 100644 --- a/Bluetooth/LibreTransmitterMetadata.swift +++ b/Bluetooth/LibreTransmitterMetadata.swift @@ -110,7 +110,7 @@ public enum SensorType: String, CustomStringConvertible { case 0xDF, 0xA2: self = .libre1 case 0xE5, 0xE6: self = .libreUS14day case 0x70: self = .libreProH - case 0xC5, 0x9D, 0xC6: self = .libre2 + case 0xC5, 0x9D, 0xC6, 0x7F: self = .libre2 case 0x76: self = patchInfo[3] == 0x02 ? .libre2US : patchInfo[3] == 0x04 ? .libre2CA : patchInfo[2] >> 4 == 7 ? .libreSense : .unknown default: if patchInfo.count == 24 { diff --git a/Bluetooth/Transmitter/Libre2DirectTransmitter.swift b/Bluetooth/Transmitter/Libre2DirectTransmitter.swift index f57e63a..55e1cb2 100644 --- a/Bluetooth/Transmitter/Libre2DirectTransmitter.swift +++ b/Bluetooth/Transmitter/Libre2DirectTransmitter.swift @@ -143,7 +143,7 @@ class Libre2DirectTransmitter: LibreTransmitterProxyProtocol { metadata = LibreTransmitterMetadata(hardware: nil, firmware: nil, battery: 100, name: Self.shortTransmitterName, - macAddress: nil, + macAddress: sensor.macAddress, patchInfo: sensor.patchInfo, uid: [UInt8](sensor.uuid)) diff --git a/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift b/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift index 4bcf343..82953a0 100644 --- a/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift +++ b/Bluetooth/Transmitter/LibreTransmitterProxyProtocol.swift @@ -37,7 +37,8 @@ public protocol LibreTransmitterProxyProtocol: AnyObject { extension LibreTransmitterProxyProtocol { func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool { - Self.canSupportPeripheral(peripheral) + print("canSupportPeripheral called") + return Self.canSupportPeripheral(peripheral) } public var staticType: LibreTransmitterProxyProtocol.Type { Self.self diff --git a/LibreSensor/SensorContents/Sensor.swift b/LibreSensor/SensorContents/Sensor.swift index e947655..58e7651 100644 --- a/LibreSensor/SensorContents/Sensor.swift +++ b/LibreSensor/SensorContents/Sensor.swift @@ -80,66 +80,23 @@ public struct CalibrationToSensorMapping: Codable { public struct Sensor: Codable { public let uuid: Data public let patchInfo: Data - // public let calibrationInfo: SensorData.CalibrationInfo - // public let family: SensorFamily - // public let type: SensorType - // public let region: SensorRegion - // public let serial: String? - // public var state: SensorState public var age: Int? public var maxAge: Int - // public var lifetime: Int public var unlockCount: Int var sensorName : String? + var macAddress : String? - /* - public var unlockCount: Int { - get { - return UserDefaults.standard.integer(forKey: Key.sensorUnlockCount.rawValue) - } - set { - UserDefaults.standard.setValue(newValue, forKey: Key.sensorUnlockCount.rawValue) - } - }*/ - - /* - public var elapsedLifetime: Int? { - get { - if let remainingLifetime { - return max(0, lifetime - remainingLifetime) - } - - return nil - } - } - - public var remainingLifetime: Int? { - get { - if let age { - return max(0, lifetime - age) - } - - return nil - } - } */ - - public init(uuid: Data, patchInfo: Data, maxAge: Int, unlockCount: Int = 0, sensorName: String? = nil) { + public init(uuid: Data, patchInfo: Data, maxAge: Int, unlockCount: Int = 0, sensorName: String? = nil, macAddress: String? = nil) { self.uuid = uuid self.patchInfo = patchInfo - - // self.family = SensorFamily(patchInfo: patchInfo) - // self.type = SensorType(patchInfo: patchInfo) - // self.region = SensorRegion(patchInfo: patchInfo) - // self.serial = sensorSerialNumber(sensorUID: self.uuid, sensorFamily: self.family) - // self.state = SensorState(fram: fram) - // self.lifetime = Int(fram[327]) << 8 + Int(fram[326]) self.unlockCount = 0 self.maxAge = maxAge // self.calibrationInfo = calibrationInfo self.sensorName = sensorName + self.macAddress = macAddress } public var description: String { diff --git a/LibreSensor/SensorPairing/SensorPairing.swift b/LibreSensor/SensorPairing/SensorPairing.swift index a495065..fc864f8 100644 --- a/LibreSensor/SensorPairing/SensorPairing.swift +++ b/LibreSensor/SensorPairing/SensorPairing.swift @@ -15,9 +15,10 @@ public class SensorPairingInfo: ObservableObject, Codable { @Published public var streamingEnabled: Bool @Published public var sensorName : String? = nil + @Published public var macAddress : String? = nil enum CodingKeys: CodingKey { - case uuid, patchInfo, fram, streamingEnabled, sensorName + case uuid, patchInfo, fram, streamingEnabled, sensorName, macAddress } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -27,6 +28,7 @@ public class SensorPairingInfo: ObservableObject, Codable { try container.encode(fram, forKey: .fram) try container.encode(streamingEnabled, forKey: .streamingEnabled) try container.encode(sensorName, forKey: .sensorName) + try container.encode(macAddress, forKey: .macAddress) } @@ -39,15 +41,17 @@ public class SensorPairingInfo: ObservableObject, Codable { fram = try container.decode(Data.self, forKey: .fram) streamingEnabled = try container.decode(Bool.self, forKey: .streamingEnabled) sensorName = try container.decode(String?.self, forKey: .sensorName) + macAddress = try container.decode(String?.self, forKey: .macAddress) } - public init(uuid: Data=Data(), patchInfo: Data=Data(), fram: Data=Data(), streamingEnabled: Bool = false, sensorName: String? = nil ) { + public init(uuid: Data=Data(), patchInfo: Data=Data(), fram: Data=Data(), streamingEnabled: Bool = false, sensorName: String? = nil, macAddress: String? = nil ) { self.uuid = uuid self.patchInfo = patchInfo self.fram = fram self.streamingEnabled = streamingEnabled self.sensorName = sensorName + self.macAddress = macAddress } public var sensorData: SensorData? { diff --git a/LibreSensor/SensorPairing/SensorPairingService.swift b/LibreSensor/SensorPairing/SensorPairingService.swift index 06e45d1..62c1e2f 100644 --- a/LibreSensor/SensorPairing/SensorPairingService.swift +++ b/LibreSensor/SensorPairing/SensorPairingService.swift @@ -201,7 +201,7 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor do { let decryptedBytes = try Libre2.decryptFRAM(type: sensorType, id: [UInt8](sensorUID), info: patchInfo, data: [UInt8](fram)) - self.sendUpdate(SensorPairingInfo(uuid: sensorUID, patchInfo: patchInfo, fram: Data(decryptedBytes), streamingEnabled: streamingEnabled)) + self.sendUpdate(SensorPairingInfo(uuid: sensorUID, patchInfo: patchInfo, fram: Data(decryptedBytes), streamingEnabled: streamingEnabled, macAddress: macAddress)) session.invalidate() return } catch { From 704d9befbfe819827cd963a8cb3ace3f38c1f5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Fri, 13 Jun 2025 10:00:53 +0200 Subject: [PATCH 3/7] l2: allow matching using either serialnumber or macaddress --- .../Transmitter/Libre2DirectTransmitter.swift | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Bluetooth/Transmitter/Libre2DirectTransmitter.swift b/Bluetooth/Transmitter/Libre2DirectTransmitter.swift index 55e1cb2..666a049 100644 --- a/Bluetooth/Transmitter/Libre2DirectTransmitter.swift +++ b/Bluetooth/Transmitter/Libre2DirectTransmitter.swift @@ -46,7 +46,27 @@ class Libre2DirectTransmitter: LibreTransmitterProxyProtocol { private var metadata: LibreTransmitterMetadata? class func canSupportPeripheral(_ peripheral: PeripheralProtocol) -> Bool { - peripheral.name?.lowercased().starts(with: "abbott") ?? false + // name can be one of the following formats: + // : example + //ABBOT: ABBOTT3MH015PCNC4 + // : A4B7C9023F8D + + guard let name = peripheral.name else { + return false + } + + if name.lowercased().starts(with: "abbott") == true { + print("Libre 2 detected using legacy name format as matcher") + return true + } + + print("Libre 2 detection using MAC address as matcher: \(UserDefaults.standard.preSelectedSensor?.macAddress?.lowercased()) vs \(name.lowercased())") + if let sensor = UserDefaults.standard.preSelectedSensor, let macAddress = sensor.macAddress { + return name.lowercased().contains(macAddress.lowercased()) + } + + + return false } class func getDeviceDetailsFromAdvertisement(advertisementData: [String: Any]?) -> String? { From e8c485e7433fded3898eb66bc8048178ecbb60f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Fri, 13 Jun 2025 10:27:47 +0200 Subject: [PATCH 4/7] l2: collect macaddress from pairinginfo in preselected sensor struct --- LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift index 8983ae1..cdf4303 100644 --- a/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift +++ b/LibreTransmitterUI/Views/Setup/Libre2DirectSetup.swift @@ -73,7 +73,7 @@ struct Libre2DirectSetup: View { let max = info.sensorData?.maxMinutesWearTime ?? 0 - let sensor = Sensor(uuid: info.uuid, patchInfo: info.patchInfo, maxAge: max, sensorName: info.sensorName) + let sensor = Sensor(uuid: info.uuid, patchInfo: info.patchInfo, maxAge: max, sensorName: info.sensorName, macAddress: info.macAddress) UserDefaults.standard.preSelectedSensor = sensor SelectionState.shared.selectedUID = pairingInfo.uuid From 6af8dab01d10c17de74bd6aa7fc1041bb8c371a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Fri, 13 Jun 2025 10:30:07 +0200 Subject: [PATCH 5/7] =?UTF-8?q?l2:=20make=20macaddress=20truly=20nil=C3=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LibreSensor/SensorPairing/SensorPairingService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibreSensor/SensorPairing/SensorPairingService.swift b/LibreSensor/SensorPairing/SensorPairingService.swift index 62c1e2f..ce31cd4 100644 --- a/LibreSensor/SensorPairing/SensorPairingService.swift +++ b/LibreSensor/SensorPairing/SensorPairingService.swift @@ -169,7 +169,7 @@ public class SensorPairingService: NSObject, NFCTagReaderSessionDelegate, Sensor tag.customCommand(requestFlags: .highDataRate, customCommandCode: Int(cmd.code), customRequestParameters: cmd.parameters) { response, _ in var streamingEnabled = false - var macAddress = "" + var macAddress : String? if subCmd == .enableStreaming && response.count == 6 { streamingEnabled = true From d73e686cb9f17fae55d3030a05c37d4cf6d19609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Thu, 17 Jul 2025 17:32:45 +0200 Subject: [PATCH 6/7] l2: verify also using new method on initial bluetooth connection --- .../LibreTransmitterProxyManager.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift b/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift index 7b30801..b18b00f 100644 --- a/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift +++ b/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift @@ -448,6 +448,7 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg } } + private func verifyLibre2ManufacturerData(peripheral: CBPeripheral, selectedUid: Data ,advertisementData: [String: Any]) -> Bool { guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data else { logger.debug("manufacturerData was not retrieved") @@ -488,9 +489,22 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg let sensor = UserDefaults.standard.preSelectedSensor logger.debug("preselected sensor is: \(String(describing:sensor))") + let verified : Bool + + // Starting in mid 2025, libre2 plus sensors in europe identify them self with + // their mac address in the device name + if let peripheralName = peripheral.name, let preselectedMac = sensor?.macAddress { + verified = peripheralName == preselectedMac + logger.debug("Verifiying libre2 connection using mac address method:. \(verified)") + } else { + verified = verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) + logger.debug("Verifiying libre2 connection using legacy manufacturerData method: \(verified)") + + + } - if !verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) { - logger.debug("failed Verifiying libre2 connection using manufacturerData") + if !verified { + logger.debug("verification failed, not connecting") return } From a1456fb46689f4e69cef6b5121b8abec5f07c4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Inge=20Berg?= Date: Thu, 17 Jul 2025 17:36:51 +0200 Subject: [PATCH 7/7] l2: typo --- Bluetooth/Transmitter/LibreTransmitterProxyManager.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift b/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift index b18b00f..7a28039 100644 --- a/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift +++ b/Bluetooth/Transmitter/LibreTransmitterProxyManager.swift @@ -489,18 +489,19 @@ public final class LibreTransmitterProxyManager: NSObject, CBCentralManagerDeleg let sensor = UserDefaults.standard.preSelectedSensor logger.debug("preselected sensor is: \(String(describing:sensor))") - let verified : Bool + var verified = false // Starting in mid 2025, libre2 plus sensors in europe identify them self with // their mac address in the device name if let peripheralName = peripheral.name, let preselectedMac = sensor?.macAddress { verified = peripheralName == preselectedMac logger.debug("Verifiying libre2 connection using mac address method:. \(verified)") - } else { + } + + if !verified { verified = verifyLibre2ManufacturerData(peripheral: peripheral, selectedUid: selectedUid, advertisementData: advertisementData) logger.debug("Verifiying libre2 connection using legacy manufacturerData method: \(verified)") - } if !verified {