diff --git a/Podfile.lock b/Podfile.lock index b9fb0cc6..ebfbe774 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -18,4 +18,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 347a5d9f3343257452289e61c10025c2eed103bb -COCOAPODS: 1.6.1 +COCOAPODS: 1.7.3 diff --git a/xdrip.xcodeproj/project.pbxproj b/xdrip.xcodeproj/project.pbxproj index 689b70cc..a1a1cdb3 100644 --- a/xdrip.xcodeproj/project.pbxproj +++ b/xdrip.xcodeproj/project.pbxproj @@ -3,11 +3,12 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ A48D2DE552F4A356AA32746A /* Pods_xdrip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 662BEA7F7991B9BD2E7D3EA4 /* Pods_xdrip.framework */; }; + EE9947E822EEDD2E00DCB876 /* CGMBubbleTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9947E722EEDD2E00DCB876 /* CGMBubbleTransmitter.swift */; }; F8025C0A21D94FD700ECF0C0 /* CBManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8025C0921D94FD700ECF0C0 /* CBManagerState.swift */; }; F8025C1121DA5E8F00ECF0C0 /* BluetoothTransmitterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8025C1021DA5E8F00ECF0C0 /* BluetoothTransmitterDelegate.swift */; }; F8025C1321DA683400ECF0C0 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8025C1221DA683400ECF0C0 /* Data.swift */; }; @@ -190,6 +191,7 @@ 148E05A6AF0290AE5815B0F9 /* Pods-xdrip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xdrip.debug.xcconfig"; path = "Target Support Files/Pods-xdrip/Pods-xdrip.debug.xcconfig"; sourceTree = ""; }; 662BEA7F7991B9BD2E7D3EA4 /* Pods_xdrip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xdrip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E2648F65F347D56D7DFFFAB7 /* Pods-xdrip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xdrip.release.xcconfig"; path = "Target Support Files/Pods-xdrip/Pods-xdrip.release.xcconfig"; sourceTree = ""; }; + EE9947E722EEDD2E00DCB876 /* CGMBubbleTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMBubbleTransmitter.swift; sourceTree = ""; }; F8025C0921D94FD700ECF0C0 /* CBManagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBManagerState.swift; sourceTree = ""; }; F8025C1021DA5E8F00ECF0C0 /* BluetoothTransmitterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitterDelegate.swift; sourceTree = ""; }; F8025C1221DA683400ECF0C0 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; @@ -515,6 +517,14 @@ path = Pods; sourceTree = ""; }; + EE9947E622EEDD0700DCB876 /* Bubble */ = { + isa = PBXGroup; + children = ( + EE9947E722EEDD2E00DCB876 /* CGMBubbleTransmitter.swift */, + ); + path = Bubble; + sourceTree = ""; + }; F8025C0B21D9513400ECF0C0 /* Extensions */ = { isa = PBXGroup; children = ( @@ -813,6 +823,7 @@ F8A54AEC22D9156600934E7A /* Libre */ = { isa = PBXGroup; children = ( + EE9947E622EEDD0700DCB876 /* Bubble */, F8A54AFB22D9179100934E7A /* Utilities */, F8A54AED22D9156600934E7A /* GNSEntry */, F8A54AEF22D9156600934E7A /* MiaoMiao */, @@ -1282,16 +1293,11 @@ files = ( ); inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-xdrip/Pods-xdrip-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/ActionClosurable/ActionClosurable.framework", + "${PODS_ROOT}/Target Support Files/Pods-xdrip/Pods-xdrip-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActionClosurable.framework", + "${PODS_ROOT}/Target Support Files/Pods-xdrip/Pods-xdrip-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1379,6 +1385,7 @@ F8A54AB822D9111900934E7A /* TransmitterBatteryInfo.swift in Sources */, F8B3A82D227F07D6004BA588 /* SettingsNavigationController.swift in Sources */, F8A54AB722D9111900934E7A /* CGMTransmitter.swift in Sources */, + EE9947E822EEDD2E00DCB876 /* CGMBubbleTransmitter.swift in Sources */, F8B3A830227F085A004BA588 /* SettingsTableViewCell.swift in Sources */, F8A1586122EDB844007F5B5D /* ConstantsNotifications.swift in Sources */, F8B3A81C227DEC92004BA588 /* AlertEntriesAccessor.swift in Sources */, diff --git a/xdrip/Constants/ConstantsDefaultAlertLevels.swift b/xdrip/Constants/ConstantsDefaultAlertLevels.swift index ecdc7bbe..f37a8085 100644 --- a/xdrip/Constants/ConstantsDefaultAlertLevels.swift +++ b/xdrip/Constants/ConstantsDefaultAlertLevels.swift @@ -4,6 +4,7 @@ enum ConstantsDefaultAlertLevels { static let defaultBatteryAlertLevelDexcomG5 = 300 static let defaultBatteryAlertLevelDexcomG4 = 210 static let defaultBatteryAlertLevelMiaoMiao = 20 + static let defaultBatteryAlertLevelBubble = 20 static let defaultBatteryAlertLevelGNSEntry = 20 static let defaultBatteryAlertLevelBlucon = 20 diff --git a/xdrip/Constants/ConstantsLog.swift b/xdrip/Constants/ConstantsLog.swift index 9601fb91..97ff69f3 100644 --- a/xdrip/Constants/ConstantsLog.swift +++ b/xdrip/Constants/ConstantsLog.swift @@ -6,6 +6,8 @@ enum ConstantsLog { static let categoryBlueTooth = "bluetooth" /// for use in cgm transmitter miaomiao static let categoryCGMMiaoMiao = "cgmmiaomiao" + /// for use in cgm transmitter bubble + static let categoryCGMBubble = "cgmbubble" /// for use in cgm xdripg4 static let categoryCGMxDripG4 = "cgmxdripg4" /// for use in firstview diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift index 8fc343b4..fff90ab7 100644 --- a/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Generic/CGMTransmitter.swift @@ -64,6 +64,9 @@ enum CGMTransmitterType:String, CaseIterable { /// Blucon case Blucon = "Blucon" + /// Bubble + case Bubble = "Bubble" + /// does the transmitter need a transmitter id ? /// /// can be used in UI stuff, if reset not possible then there's no need to show that option in the settings UI @@ -76,7 +79,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG5, .dexcomG6: return true - case .miaomiao: + case .miaomiao, .Bubble: return false case .GNSentry: @@ -102,7 +105,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG5, .dexcomG6: return false - case .miaomiao: + case .miaomiao, .Bubble: return true case .GNSentry: @@ -139,7 +142,7 @@ enum CGMTransmitterType:String, CaseIterable { } return nil - case .miaomiao, .GNSentry: + case .miaomiao, .GNSentry, .Bubble: return nil case .Blucon: @@ -161,6 +164,9 @@ enum CGMTransmitterType:String, CaseIterable { case .miaomiao: return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelMiaoMiao + case .Bubble: + return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelBubble + case .GNSentry: return ConstantsDefaultAlertLevels.defaultBatteryAlertLevelGNSEntry @@ -182,7 +188,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG5, .dexcomG6: return true - case .miaomiao: + case .miaomiao, .Bubble: return false case .GNSentry: @@ -205,7 +211,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG5, .dexcomG6: return "voltA" - case .miaomiao: + case .miaomiao, .Bubble: return "%" case .GNSentry: @@ -228,7 +234,7 @@ enum CGMTransmitterType:String, CaseIterable { case .dexcomG5, .dexcomG6: return true - case .miaomiao: + case .miaomiao, .Bubble: return false case .GNSentry: diff --git a/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift new file mode 100644 index 00000000..3f2c927b --- /dev/null +++ b/xdrip/Transmitter/CGMBluetoothTransmitter/Libre/Bubble/CGMBubbleTransmitter.swift @@ -0,0 +1,176 @@ +import Foundation +import CoreBluetooth +import os + +class CGMBubbleTransmitter:BluetoothTransmitter, BluetoothTransmitterDelegate, CGMTransmitter { + // MARK: - properties + + /// service to be discovered + let CBUUID_Service_Bubble: String = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" + /// receive characteristic + let CBUUID_ReceiveCharacteristic_Bubble: String = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + /// write characteristic + let CBUUID_WriteCharacteristic_Bubble: String = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" + + /// expected device name + let expectedDeviceNameBubble:String = "Bubble" + + /// will be used to pass back bluetooth and cgm related events + private(set) weak var cgmTransmitterDelegate: CGMTransmitterDelegate? + + // maximum times resend request due to crc error + let maxPacketResendRequests = 3; + + /// for OS_log + private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMBubble) + + // used in parsing packet + private var timeStampLastBgReading:Date + + // counts number of times resend was requested due to crc error + private var resendPacketCounter:Int = 0 + + /// used when processing Bubble data packet + private var startDate:Date + // receive buffer for bubble packets + private var rxBuffer:Data + // how long to wait for next packet before sending startreadingcommand + private static let maxWaitForpacketInSeconds = 60.0 + // length of header added by Bubble in front of data dat is received from Libre sensor + private let BubbleHeaderLength = 8 + + // 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) { + + // assign addressname and name or expected devicename + var newAddressAndName:BluetoothTransmitter.DeviceAddressAndName = BluetoothTransmitter.DeviceAddressAndName.notYetConnected(expectedName: expectedDeviceNameBubble) + if let address = address { + newAddressAndName = BluetoothTransmitter.DeviceAddressAndName.alreadyConnectedBefore(address: address) + } + + // assign CGMTransmitterDelegate + cgmTransmitterDelegate = delegate + + // initialize rxbuffer + rxBuffer = Data() + startDate = Date() + + //initialize timeStampLastBgReading + self.timeStampLastBgReading = timeStampLastBgReading + + super.init(addressAndName: newAddressAndName, CBUUID_Advertisement: nil, servicesCBUUIDs: [CBUUID(string: CBUUID_Service_Bubble)], CBUUID_ReceiveCharacteristic: CBUUID_ReceiveCharacteristic_Bubble, CBUUID_WriteCharacteristic: CBUUID_WriteCharacteristic_Bubble, startScanningAfterInit: CGMTransmitterType.Bubble.startScanningAfterInit()) + + // set self as delegate for BluetoothTransmitterDelegate - this parameter is defined in the parent class BluetoothTransmitter + bluetoothTransmitterDelegate = self + } + + // MARK: - public functions + + func sendStartReadingCommmand() -> Bool { + if writeDataToPeripheral(data: Data([0x00, 0x00, 0x5]), type: .withoutResponse) { + return true + } else { + os_log("in sendStartReadingCommmand, write failed", log: log, type: .error) + return false + } + } + + // MARK: - BluetoothTransmitterDelegate functions + + func centralManagerDidConnect(address:String?, name:String?) { + cgmTransmitterDelegate?.cgmTransmitterDidConnect(address: address, name: name) + } + + func centralManagerDidFailToConnect(error: Error?) { + } + + func centralManagerDidUpdateState(state: CBManagerState) { + cgmTransmitterDelegate?.deviceDidUpdateBluetoothState(state: state) + } + + func centralManagerDidDisconnectPeripheral(error: Error?) { + cgmTransmitterDelegate?.cgmTransmitterDidDisconnect() + } + + func peripheralDidUpdateNotificationStateFor(characteristic: CBCharacteristic, error: Error?) { + if error == nil && characteristic.isNotifying { + _ = sendStartReadingCommmand() + } + } + + var hardware = "" + var firmware = "" + var batteryPercentage = 0 + func peripheralDidUpdateValueFor(characteristic: CBCharacteristic, error: Error?) { + + if let value = characteristic.value { + + //check if buffer needs to be reset + if (Date() > startDate.addingTimeInterval(CGMBubbleTransmitter.maxWaitForpacketInSeconds - 1)) { + os_log("in peripheral didUpdateValueFor, more than %{public}d seconds since last update - or first update since app launch, resetting buffer", log: log, type: .info, CGMBubbleTransmitter.maxWaitForpacketInSeconds) + resetRxBuffer() + } + + if let firstByte = value.first { + if firstByte == 192 { + rxBuffer.append(value.subdata(in: 2..<10)) + return + } + + if firstByte == 128 { + hardware = value[2].description + ".0" + firmware = value[1].description + ".0" + batteryPercentage = Int(value[4]) + + let _ = writeDataToPeripheral(data: Data([0x02, 0x00, 0x00, 0x00, 0x00, 0x2B]), type: .withoutResponse) + } + + if firstByte == 130 { + rxBuffer.append(value.suffix(from: 4)) + } + + if rxBuffer.count >= 352 { + if (Crc.LibreCrc(data: &rxBuffer, headerOffset: BubbleHeaderLength)) { + //get readings from buffer and send to delegate + var result = parseLibreData(data: &rxBuffer, timeStampLastBgReadingStoredInDatabase: timeStampLastBgReading, headerOffset: BubbleHeaderLength) + //TODO: sort glucosedata before calling newReadingsReceived + cgmTransmitterDelegate?.cgmTransmitterInfoReceived(glucoseData: &result.glucoseData, transmitterBatteryInfo: TransmitterBatteryInfo.percentage(percentage: batteryPercentage), sensorState: result.sensorState, sensorTimeInMinutes: result.sensorTimeInMinutes, firmware: firmware, hardware: hardware, hardwareSerialNumber: nil, bootloader: nil, sensorSerialNumber: nil) + + //set timeStampLastBgReading to timestamp of latest reading in the response so that next time we parse only the more recent readings + if result.glucoseData.count > 0 { + timeStampLastBgReading = result.glucoseData[0].timeStamp + } + + //reset the buffer + resetRxBuffer() + } + } + } + } else { + os_log("in peripheral didUpdateValueFor, value is nil, no further processing", log: log, type: .error) + } + } + + // MARK: CGMTransmitter protocol functions + + /// to ask pairing - empty function because G4 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() {} + + /// to ask transmitter reset - empty function because Bubble doesn't support reset + /// + /// this function is not implemented in BluetoothTransmitter.swift, otherwise it might be forgotten to look at in future CGMTransmitter developments + func reset(requested:Bool) {} + + // MARK: - helpers + + /// reset rxBuffer, reset startDate, stop packetRxMonitorTimer, set resendPacketCounter to 0 + private func resetRxBuffer() { + rxBuffer = Data() + startDate = Date() + resendPacketCounter = 0 + } +} diff --git a/xdrip/View Controllers/Root View Controller/RootViewController.swift b/xdrip/View Controllers/Root View Controller/RootViewController.swift index 189296c7..623573f5 100644 --- a/xdrip/View Controllers/Root View Controller/RootViewController.swift +++ b/xdrip/View Controllers/Root View Controller/RootViewController.swift @@ -595,6 +595,10 @@ final class RootViewController: UIViewController { cgmTransmitter = CGMMiaoMiaoTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0)) calibrator = Libre1Calibrator() + case .Bubble: + cgmTransmitter = CGMBubbleTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0)) + calibrator = Libre1Calibrator() + case .GNSentry: cgmTransmitter = CGMGNSEntryTransmitter(address: UserDefaults.standard.bluetoothDeviceAddress, delegate: self, timeStampLastBgReading: Date(timeIntervalSince1970: 0)) calibrator = Libre1Calibrator() diff --git a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift index 8d0aa80a..8fac6760 100644 --- a/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift +++ b/xdrip/View Controllers/SettingsNavigationController/SettingsViewController/SettingsViewModels/SettingsViewDexcomSettingsViewModel.swift @@ -28,7 +28,7 @@ class SettingsViewDexcomSettingsViewModel:SettingsViewModelProtocol { if let transmitterType = UserDefaults.standard.transmitterType { switch transmitterType { - case .dexcomG4, .miaomiao, .GNSentry, .Blucon: + case .dexcomG4, .miaomiao, .GNSentry, .Blucon, .Bubble: return true case .dexcomG5, .dexcomG6: