diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index b0a22392..fcd23d35 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -180,6 +180,7 @@ F8BDD450221CAA64006EAB84 /* TextsCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDD44F221CAA64006EAB84 /* TextsCommon.swift */; }; F8BDD452221DEAB2006EAB84 /* TextsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BDD451221DEAB1006EAB84 /* TextsSettingsView.swift */; }; F8BDD455221DEF22006EAB84 /* SettingsViews.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8BDD457221DEF22006EAB84 /* SettingsViews.strings */; }; + F8C5EBE522F297F000563B5F /* SensorSerialNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */; }; F8E3C3AB21FE17B700907A04 /* StringProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */; }; F8E3C3AD21FE551C00907A04 /* DexcomCalibrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */; }; F8EA6C8221B723BC0082976B /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6C8121B723BC0082976B /* Date.swift */; }; @@ -479,6 +480,7 @@ F8BDD451221DEAB1006EAB84 /* TextsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsSettingsView.swift; sourceTree = ""; }; F8BDD456221DEF22006EAB84 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SettingsViews.strings; sourceTree = ""; }; F8BDD458221DEF24006EAB84 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/SettingsViews.strings; sourceTree = ""; }; + F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorSerialNumber.swift; sourceTree = ""; }; F8E3C3AA21FE17B700907A04 /* StringProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringProtocol.swift; sourceTree = ""; }; F8E3C3AC21FE551C00907A04 /* DexcomCalibrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DexcomCalibrator.swift; sourceTree = ""; }; F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; @@ -861,6 +863,7 @@ F8A54AFB22D9179100934E7A /* Utilities */ = { isa = PBXGroup; children = ( + F8C5EBE422F297EF00563B5F /* SensorSerialNumber.swift */, F8A54AFC22D9179100934E7A /* CRC.swift */, F8A54AFD22D9179100934E7A /* ParseLibreData.swift */, F8A54AFE22D9179100934E7A /* SensorState.swift */, @@ -1471,6 +1474,7 @@ F8B3A856227F28DC004BA588 /* AlertTypeSettingsViewController.swift in Sources */, F8A54AE822D911BA00934E7A /* BatteryStatusRxMessage.swift in Sources */, F8A1584F22ECB281007F5B5D /* SettingsViewInfoViewModel.swift in Sources */, + F8C5EBE522F297F000563B5F /* SensorSerialNumber.swift in Sources */, F8B3A845227F090E004BA588 /* SettingsViewDexcomSettingsViewModel.swift in Sources */, F8A1585F22EDB81E007F5B5D /* ConstantsLog.swift in Sources */, F8A1586522EDB89D007F5B5D /* ConstantsDefaultAlertTypeSettings.swift in Sources */, diff --git a/xdrip/Supporting Files/Info.plist b/xdrip/Supporting Files/Info.plist index 7380c1e4..2739246a 100644 --- a/xdrip/Supporting Files/Info.plist +++ b/xdrip/Supporting Files/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.4.2 + 2.5.0 CFBundleVersion - 2.4.2 + 2.5.0 LSRequiresIPhoneOS NSBluetoothPeripheralUsageDescription diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift index cd984f10..42c6aed5 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift @@ -39,10 +39,13 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C /// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send var emptyArray: [RawGlucoseData] = [] + // current sensor serial number, if nil then it's not known yet + private var sensorSerialNumber:String? + // MARK: - Initialization /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil - init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date) { + init(address:String?, delegate:CGMTransmitterDelegate, timeStampLastBgReading:Date, sensorSerialNumber:String?) { // assign addressname and name or expected devicename var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: expectedDeviceNameBubble) @@ -50,6 +53,9 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C newAddressAndName = BluetoothTransmitter.DeviceAddressAndName.alreadyConnectedBefore(address: address) } + // initialize sensorSerialNumber + self.sensorSerialNumber = sensorSerialNumber + // assign CGMTransmitterDelegate cgmTransmitterDelegate = delegate @@ -130,6 +136,30 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C rxBuffer.append(value.suffix(from: 4)) if rxBuffer.count >= 352 { if (Crc.LibreCrc(data: &rxBuffer, headerOffset: BubbleHeaderLength)) { + + if let sensorSerialNumberData = SensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13))) { + let newSerialNumber = sensorSerialNumberData.serialNumber + + // verify serial number and if changed inform delegate + if newSerialNumber != sensorSerialNumber { + + os_log(" new sensor detected : %{public}@", log: log, type: .info, newSerialNumber) + + sensorSerialNumber = newSerialNumber + + // inform delegate about new sensor detected + cgmTransmitterDelegate?.newSensorDetected() + + // also reset timestamp last reading, to be sure that if new sensor is started, we get historic data + timeStampLastBgReading = Date(timeIntervalSince1970: 0) + + // inform delegate about sensorSerialNumber + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &emptyArray, transmitterBatteryInfo: nil, sensorState: nil, sensorTimeInMinutes: nil, firmware: nil, hardware: nil, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: sensorSerialNumber) + + } + + } + //get readings from buffer and send to delegate var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: BubbleHeaderLength) //TODO: sort glucosedata before calling newReadingsReceived @@ -156,7 +186,7 @@ class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, C // MARK: CGMTransmitter protocol functions - /// to ask pairing - empty function because G4 doesn't need pairing + /// to ask pairing - empty function because Bubble doesn't need pairing /// /// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments func initiatePairing() {} diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift index ed666c0d..3868e460 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/GNSEntry/CGMGNSEntryTransmitter.swift @@ -88,7 +88,7 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, /// used as parameter in call to cgmTransmitterDelegate.cgmTransmitterInfoReceived, when there's no glucosedata to send var emptyArray: [RawGlucoseData] = [] - // MARK: - functions + // MARK: - public functions /// - parameters: /// - address: if already connected before, then give here the address that was received during previous connect, if not give nil @@ -325,7 +325,9 @@ class CGMGNSEntryTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, } } - /// creates readable representation of characteristicUUID, for logging only + // MARK: - private helper functions + + /// creates CBUUID_Characteristic_UUID for the characteristicUUID private func receivedCharacteristicUUIDToCharacteristic(characteristicUUID:String) -> CBUUID_Characteristic_UUID? { if CBUUID_Characteristic_UUID.CBUUID_BatteryLevel.rawValue.containsIgnoringCase(find: characteristicUUID) { return CBUUID_Characteristic_UUID.CBUUID_BatteryLevel diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift index a6f3632d..3196dd96 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/MiaoMiao/CGMMiaoMiaoTransmitter.swift @@ -130,6 +130,9 @@ class CGMMiaoMiaoTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, let hardware = String(describing: rxBuffer[16...17].hexEncodedString()) let batteryPercentage = Int(rxBuffer[13]) + let serialNumber = SensorSerialNumber(withUID: Data(rxBuffer.subdata(in: 5..<13)))?.serialNumber ?? "-" + debuglogging("serialNumber = " + serialNumber) + //get readings from buffer and send to delegate var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: miaoMiaoHeaderLength) //TODO: sort glucosedata before calling newReadingsReceived diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorSerialNumber.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorSerialNumber.swift new file mode 100644 index 00000000..844c61c0 --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Utilities/SensorSerialNumber.swift @@ -0,0 +1,109 @@ +// +// FreestyleLibreSensor.swift +// LibreMonitor +// +// Created by Uwe Petersen on 01.04.18. +// Copyright © 2018 Uwe Petersen. All rights reserved. +// + +import Foundation + +struct SensorSerialNumber: CustomStringConvertible { + + let uid: Data + + fileprivate let lookupTable = ["0","1","2","3","4","5","6","7","8","9","A","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","T","U","V","W","X","Y","Z"] + + init?(withUID uid: Data) { + guard uid.count == 8 else {return nil} + self.uid = uid + } + + // MARK: - computed properties + + var serialNumber: String { + + // The serial number of the sensor can be derived from its uid. + // + // The numbers an letters of the serial number are coded a compressed scheme that uses only 32 numbers and letters, + // by omitting the letters B, I, O and S. This information is stored in consecutive units of five bits. + // + // The encoding thus is as follows: + // index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + // char: 0 1 2 3 4 5 6 7 8 9 A (B) C D E F G H (I) J K L M N (O) P Q R (S) T U V W X Y Z + // + // Example: 75 ce 86 00 00 a0 07 e0 + // Uid is E0 07 A0 00 00 25 90 5E, and the corresponding serial number is "0M00009DHCR" + // \ / \ / + // -+- -----+-------- + // | | + // | +-- This part encodes the serial number, see below + // +-- Standard first two bytes, where 0x07 is the code for "Texas Instruments Tag-it™", see https://en.wikipedia.org/wiki/ISO/IEC_15693 + // + // 1.) Convert the part without E007, i.e. A0 00 00 25 90 5E to binary representation + // + // A 0 0 0 0 0 2 5 9 0 5 E + // 1010 0000 0000 0000 0000 0000 0010 0101 1001 0000 0101 1110 + // + // 2.) Split this binary array in units of five bits length from the beginning and pad with two zeros at the end and + // calculate the corresponding integer and retreive the corresponding char from the table above + // + // Byte # 0 1 2 3 4 5 + // Bit # 8765 4321 8765 4321 8765 4321 8765 4321 8765 4321 8765 4321 + // + // +-- 1010 0000 0000 0000 0000 0000 0010 0101 1001 0000 0101 1110 + 00 + // | \ /\ /\ /\ / \ /\ /\ /\ / \ /\ / + // +-> 10100 00000 00000 00000 00000 01001 01100 10000 01011 11000 + // | | | | | | | | | | + // | | | | | | | | | +- = 24 -> "R" (Byte 6) << 2 Mask 0x1F + // | | | | | | | | +-------- = 11 -> "C" (Byte 5) >> 3 Mask 0x1F + // | | | | | | | +--------------- = 16 -> "H" (Byte 4) Mask 0x1F + // | | | | | | +---------------------- = 12 -> "D" (Byte 3) << 2 + (Byte 4) >> 5 Mask 0x1F + // | | | | | +----------------------------- = 9 -> "9" (Byte 3) >> 2 Mask 0x1F + // | | | | +------------------------------------ = 0 -> "0" (Byte 2) << 1 + (Byte 3) >> 7 Mask 0x1F + // | | | +------------------------------------------- = 0 -> "0" (Byte 1) << 4 + (Byte 2) >> 4 Mask 0x1F + // | | +-------------------------------------------------- = 0 -> "0" (Byte 1) >> 1 Mask 0x1F + // | +--------------------------------------------------------- = 0 -> "0" (Byte 0) << 2 + (Byte 1) >> 6 Mask 0x1F + // +---------------------------------------------------------------- = 20 -> "M" (byte 0) >> 3 Mask 0x1F + // + // + // 3.) Prepend "0" at the beginning an thus receive "0M00009DHCR" + + guard uid.count == 8 else {return "invalid uid"} + + let bytes = Array(uid.reversed().suffix(6)) // 5E 90 25 00 00 A0 07 E0" -> E0 07 A0 00 00 25 90 5E -> A0 00 00 25 90 5E + + // A0 00 00 25 90 5E -> "M00009DHCR" + var fiveBitsArray = [UInt8]() // Mask later with 0x1F to use only five bits + + fiveBitsArray.append( bytes[0] >> 3 ) + fiveBitsArray.append( bytes[0] << 2 + bytes[1] >> 6 ) + fiveBitsArray.append( bytes[1] >> 1 ) + fiveBitsArray.append( bytes[1] << 4 + bytes[2] >> 4 ) + fiveBitsArray.append( bytes[2] << 1 + bytes[3] >> 7 ) + fiveBitsArray.append( bytes[3] >> 2 ) + fiveBitsArray.append( bytes[3] << 3 + bytes[4] >> 5 ) + fiveBitsArray.append( bytes[4] ) + fiveBitsArray.append( bytes[5] >> 3 ) + fiveBitsArray.append( bytes[5] << 2 ) + + let serialNumber = fiveBitsArray.reduce("0", { // prepend with "0" according to step 3.) + $0 + lookupTable[ Int(0x1F & $1) ] // Mask with 0x1F to only take the five relevant bits + }) + return serialNumber + } + + var uidString: String { + return Data(self.uid).hexEncodedString() + } + + var prettyUidString: String { + let stringArray = self.uid.map({String(format: "%02X", $0)}) + return stringArray.dropFirst().reduce(stringArray.first!, {$0 + ":" + $1} ) + } + + // MARK: - CustomStringConvertible Protocoll + var description: String { + return "Uid is \(prettyUidString) and derived serial number is \(serialNumber)" + } +} diff --git a/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift b/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift index 56abd88c..f9893a3e 100644 --- a/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift +++ b/xdrip/Transmitter/GenericBluetoothTransmitter/BluetoothTransmitter.swift @@ -392,7 +392,8 @@ class BluetoothTransmitter: NSObject, CBCentralManagerDelegate, CBPeripheralDele func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { timeStampLastStatusUpdate = Date() - os_log("didDiscoverCharacteristicsFor", log: log, type: .info) + os_log("didDiscoverCharacteristicsFor for service with uuid %{public}@", log: log, type: .info, String(describing:service.uuid)) + if let error = error { os_log(" didDiscoverCharacteristicsFor error: %{public}@", log: log, type: .error , error.localizedDescription) } diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 694c3b37..fbbc65d2 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -596,7 +596,7 @@ final class RootViewController: UIViewController { calibrator = Libre1Calibrator() case .Bubble: - cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0)) + cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0), sensorSerialNumber: UserDefaults.standard.sensorSerialNumber) calibrator = Libre1Calibrator() case .GNSentry: