diff --git a/Playground/Battery/BatteryServiceParser.swift b/Playground/Battery/BatteryServiceParser.swift new file mode 100644 index 00000000..2ce5477a --- /dev/null +++ b/Playground/Battery/BatteryServiceParser.swift @@ -0,0 +1,61 @@ +// +// BatteryServiceParser.swift +// Playground +// +// Created by Nick Kibysh on 26/02/2024. +// Copyright © 2024 Nordic Semiconductor. All rights reserved. +// + +import Foundation +import CoreBluetooth +import iOS_Bluetooth_Numbers_Database + +struct BatteryServiceParser { + let batteryService: CBService + + // Characteristics + let batteryLevelCharacteristic: CBCharacteristic // Mandatory. Read. Notify (optional) + var batteryLevelDescriptor: CBDescriptor? // C.1. Read + + var batteryLevelStatusCharacteristic: CBCharacteristic? // Optional. Read, Notify + var estimatedServiceDateCharacteristic: CBCharacteristic? // C.2. Read, Notify. Indicate (optional) + var batteryCriticalStatusCharacteristic: CBCharacteristic? // C.2. Read, Indicate + var batteryEnergyStatusCharacteristic: CBCharacteristic? // C.2. Read, Notify. Indicate (optional) + var batteryTimeToFullCharacteristic: CBCharacteristic? // C.2. Read, Notify. Indicate (optional) + var batteryHealthCharacteristic: CBCharacteristic? // C.2. Read, Notify. Indicate (optional) + var batteryHealthInformationCharacteristic: CBCharacteristic? // C.3. Read, Indicate + var batteryInformationCharacteristic: CBCharacteristic? // C.2. Read, Indicate + var manufacturerNameStringCharacteristic: CBCharacteristic? // Optional. Read, Indicate + var modelNumberStringCharacteristic: CBCharacteristic? // Optional. Read, Indicate + var serialNumberStringCharacteristic: CBCharacteristic? // C.2. Read, Indicate + + // C.1: Mandatory if a device has more than one instance of Battery Service; otherwise optional. + // C.2: Optional if the Battery Level Status characteristic is exposed; otherwise excluded. + // C.3: Optional if the Battery Health Status characteristic is exposed; otherwise excluded. + + init(batteryService: CBService) { + assert(batteryService.uuid == Service.batteryService.uuid, "Battery Service is expected") + self.batteryService = batteryService + + batteryLevelCharacteristic = batteryService.characteristics!.first(where: { $0.uuid == Characteristic.batteryLevel.uuid })! + batteryLevelDescriptor = batteryLevelCharacteristic.descriptors?.first(where: { $0.uuid == Descriptor.gattClientCharacteristicConfiguration.uuid }) + + batteryLevelStatusCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryLevelState.uuid }) + /* + estimatedServiceDateCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.est.uuid }) + batteryCriticalStatusCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryCriticalStatus.uuid }) + batteryEnergyStatusCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryEnergyStatus.uuid }) + batteryTimeToFullCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryTimeToFull.uuid }) + batteryHealthCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryHealth.uuid }) + batteryHealthInformationCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryHealthInformation.uuid }) + batteryInformationCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.batteryInformation.uuid }) + */ + manufacturerNameStringCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.manufacturerNameString.uuid }) + modelNumberStringCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.modelNumberString.uuid }) + serialNumberStringCharacteristic = batteryService.characteristics?.first(where: { $0.uuid == Characteristic.serialNumberString.uuid }) + } + + mutating func findCharacteristics() { + + } +} diff --git a/Playground/Health Thermometer/DeviceInformation.swift b/Playground/Health Thermometer/DeviceInformation.swift new file mode 100644 index 00000000..494633ad --- /dev/null +++ b/Playground/Health Thermometer/DeviceInformation.swift @@ -0,0 +1,106 @@ +// +// DeviceInformation.swift +// Health Thermometer +// +// Created by Nick Kibysh on 25/01/2024. +// Copyright © 2024 Nordic Semiconductor. All rights reserved. +// + +import CoreBluetooth +import Foundation +import iOS_BLE_Library +import iOS_Bluetooth_Numbers_Database + +public struct DeviceInformation: CustomDebugStringConvertible { + public var manufacturerName: String? + public var modelNumber: String? + public var serialNumber: String? + public var hardwareRevision: String? + public var firmwareRevision: String? + public var softwareRevision: String? + public var systemID: String? + public var ieee11073: String? + + public var debugDescription: String { + var s = "" + if let manufacturerName = manufacturerName { + s += "Manufacturer Name: \(manufacturerName)\n" + } + if let modelNumber = modelNumber { + s += "Model Number: \(modelNumber)\n" + } + if let serialNumber = serialNumber { + s += "Serial Number: \(serialNumber)\n" + } + if let hardwareRevision = hardwareRevision { + s += "Hardware Revision: \(hardwareRevision)\n" + } + if let firmwareRevision = firmwareRevision { + s += "Firmware Revision: \(firmwareRevision)\n" + } + if let softwareRevision = softwareRevision { + s += "Software Revision: \(softwareRevision)\n" + } + if let systemID = systemID { + s += "System ID: \(systemID)\n" + } + if let ieee11073 = ieee11073 { + s += "IEEE 11073: \(ieee11073)\n" + } + return s + } +} + +public func readDeviceInformation(from service: CBService, peripheral: Peripheral) async throws -> DeviceInformation { + var di = DeviceInformation() + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.manufacturerNameString.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.manufacturerName = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.modelNumberString.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.modelNumber = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.serialNumberString.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.serialNumber = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.hardwareRevisionString.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.hardwareRevision = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.firmwareRevisionString.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.firmwareRevision = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.softwareRevisionString.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.softwareRevision = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.systemId.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.systemID = String(data: data, encoding: .utf8) + } + } + + if let c = service.characteristics?.first(where: { $0.uuid == CBUUID(string: Characteristic.ieee11073_20601RegulatoryCertificationDataList.uuidString) }) { + if let data = try await peripheral.readValue(for: c).firstValue { + di.ieee11073 = String(data: data, encoding: .utf8) + } + } + + return di +} diff --git a/Playground/Health Thermometer/HealthThermometerServiceParser.swift b/Playground/Health Thermometer/HealthThermometerServiceParser.swift new file mode 100644 index 00000000..0e14c86b --- /dev/null +++ b/Playground/Health Thermometer/HealthThermometerServiceParser.swift @@ -0,0 +1,59 @@ +// +// HealthThermometerServiceParser.swift +// Playground +// +// Created by Nick Kibysh on 03/03/2024. +// Copyright © 2024 Nordic Semiconductor. All rights reserved. +// + +import Foundation +import CoreBluetooth +import iOS_Bluetooth_Numbers_Database + +/* + +Temperature Measurement | M | Indicate | None. +Client Characteristic Configuration descriptor | M | Read, Write | None. +Temperature Type | O | Read | None. +Intermediate Temperature | O | Notify | None. +Client Characteristic Configuration descriptor | C.1 | Read, Write | None. +Measurement Interval | O | Read | Indicate, Write | Read: None. Writable with authentication. +Client Characteristic Configuration descriptor | C.2 | Read, Write | None. +Valid Range descriptor | C.3 | Read | None. + +*/ + +struct HealthThermometerServiceParser { + let htService: CBService + + let temperatureMeasurementCharacteristic: CBCharacteristic + let temperatureMeasurementDescriptor: CBDescriptor + + let temperatureTypeCharacteristic: CBCharacteristic? + + let intermediateTemperatureCharacteristic: CBCharacteristic? + let intermediateTemperatureDescriptor: CBDescriptor? + + let measurementIntervalCharacteristic: CBCharacteristic? + let measurementIntervalDescriptor: CBDescriptor? + + let validRangeDescriptor: CBDescriptor? + + init(htService: CBService) { + assert(htService.uuid == Service.healthThermometer.uuid, "Health Thermometer Service is expected") + self.htService = htService + + temperatureMeasurementCharacteristic = htService.characteristics!.first(where: { $0.uuid == Characteristic.temperatureMeasurement.uuid })! + temperatureMeasurementDescriptor = temperatureMeasurementCharacteristic.descriptors!.first(where: { $0.uuid == Descriptor.gattClientCharacteristicConfiguration.uuid })! + + temperatureTypeCharacteristic = htService.characteristics?.first(where: { $0.uuid == Characteristic.temperatureType.uuid }) + + intermediateTemperatureCharacteristic = htService.characteristics?.first(where: { $0.uuid == Characteristic.intermediateTemperature.uuid }) + intermediateTemperatureDescriptor = intermediateTemperatureCharacteristic?.descriptors?.first(where: { $0.uuid == Descriptor.gattClientCharacteristicConfiguration.uuid }) + + measurementIntervalCharacteristic = htService.characteristics?.first(where: { $0.uuid == Characteristic.measurementInterval.uuid }) + measurementIntervalDescriptor = measurementIntervalCharacteristic?.descriptors?.first(where: { $0.uuid == Descriptor.gattClientCharacteristicConfiguration.uuid }) + + validRangeDescriptor = htService.characteristics?.first(where: { $0.uuid == Characteristic.descriptorValueChanged.uuid })?.descriptors?.first(where: { $0.uuid == Descriptor.validRange.uuid }) + } +} diff --git a/Playground/Health Thermometer/TemperatureMeasurement.swift b/Playground/Health Thermometer/TemperatureMeasurement.swift new file mode 100644 index 00000000..7036f635 --- /dev/null +++ b/Playground/Health Thermometer/TemperatureMeasurement.swift @@ -0,0 +1,68 @@ +// +// TemperatureMeasurement.swift +// Health Thermometer +// +// Created by Nick Kibysh on 25/01/2024. +// Copyright © 2024 Nordic Semiconductor. All rights reserved. +// + +import Foundation + +struct ReservedFloatValues { + static let positiveInfinity: UInt32 = 0x007FFFFE + static let nan: UInt32 = 0x007FFFFF + static let nres: UInt32 = 0x00800000 + static let reserved: UInt32 = 0x00800001 + static let negativeInfinity: UInt32 = 0x00800002 + + static let firstReservedValue = ReservedFloatValues.positiveInfinity +} + + +func read(_ data: Data, fromOffset offset: Int = 0) -> R { + let length = MemoryLayout.size + guard offset + length <= data.count else { fatalError() } + return data.subdata(in: offset ..< offset + length).withUnsafeBytes { $0.load(as: R.self) } +} + +func readFloat(_ data: Data, from offset: Int = 0) -> Float { + let tempData: UInt32 = read(data, fromOffset: offset) + var mantissa = Int32(tempData & 0x00FFFFFF) + let exponent = Int8(bitPattern: UInt8(tempData >> 24)) + + var output : Float32 = 0 + + if mantissa >= 0x800000 { + mantissa = -((0xFFFFFF + 1) - mantissa) + } + let magnitude = pow(10.0, Double(exponent)) + output = Float32(mantissa) * Float32(magnitude) + + return output +} + +struct TemperatureMeasurement: CustomDebugStringConvertible { + enum Unit { + case fahrenheit, celsius + } + + var temperature: Double? + var timestamp: Date? + var unit: Unit + + init(data: Data) { + let flags: UInt8 = data[0] + let fahrenheit = flags & 0x01 == 0x01 + + unit = fahrenheit ? .fahrenheit : .celsius + temperature = Double(readFloat(data, from: 1)) + } + + var debugDescription: String { + var s = "" + if let temperature = temperature { + s += "\(temperature) \(unit == .celsius ? "°C" : "°F")" + } + return s + } +} diff --git a/Playground/Scanner.swift b/Playground/Scanner.swift new file mode 100644 index 00000000..0931d7b0 --- /dev/null +++ b/Playground/Scanner.swift @@ -0,0 +1,37 @@ +// +// Scanner.swift +// scanner +// +// Created by Nick Kibysh on 25/01/2024. +// Copyright © 2024 Nordic Semiconductor. All rights reserved. +// + +import CoreBluetooth +import Foundation +import iOS_BLE_Library + +public func scanAndConnect(to uuidString: String) async throws -> CBPeripheral { + let central = CentralManager() + + // Wait until CentralManager is in PowerON state + _ = try await central.stateChannel.first(where: { $0 == .poweredOn }).firstValue + + let scanResultPublisher = central.scanForPeripherals(withServices: nil) + .filter { $0.name != nil } + + var alreadyDiscovered: [ScanResult] = [] + + for try await scanResult in scanResultPublisher.values { + // Filter already discovered devices + if !alreadyDiscovered.contains(where: { $0.peripheral.identifier == scanResult.peripheral.identifier }) { + l.debug("\(scanResult.name!): \(scanResult.peripheral.identifier.uuidString)") + alreadyDiscovered.append(scanResult) + + if scanResult.peripheral.identifier.uuidString == uuidString { + return try await central.connect(scanResult.peripheral).firstValue + } + } + } + + fatalError("no peripheral discovered") +} diff --git a/Playground/main.swift b/Playground/main.swift new file mode 100644 index 00000000..6a33a246 --- /dev/null +++ b/Playground/main.swift @@ -0,0 +1,123 @@ +// +// main.swift +// Health Thermometer +// +// Created by Nick Kibysh on 25/01/2024. +// Copyright © 2024 Nordic Semiconductor. All rights reserved. +// + +import Foundation +import iOS_BLE_Library +import iOS_Bluetooth_Numbers_Database +import Combine + +import OSLog + +let l = Logger(subsystem: "com.nordicsemi.bles-playground", category: "playground") + +/** +You can find example of the Health Thermometer peripheral here: TODO add link +*/ + +/** +To discover the UUID of the peripheral you want to connect to, run the app and copy the UUID from the console. +*/ + +let deviceUUIDString = "99027DE5-3248-5D9B-55DA-616266D395DF" +let cbPeripheral = try await scanAndConnect(to: deviceUUIDString) + +let peripheral = Peripheral(peripheral: cbPeripheral) + +let services = try await peripheral.discoverServices(serviceUUIDs: nil).firstValue + +var cancelable = Set() + +l.info("ATTRIBUTE TABLE") +for service in services { + guard let s = Service.find(by: service.uuid) else { continue } + l.debug("|-- \(s.name) \(s.uuidString)") + + let characteristics = try await peripheral.discoverCharacteristics(nil, for: service).firstValue + for characteristic in characteristics { + guard let c = Characteristic.find(by: characteristic.uuid) else { continue } + l.debug("|---- \(c.name) \(c.uuidString)") + + let descriptors = try await peripheral.discoverDescriptors(for: characteristic).firstValue + for descriptor in descriptors { + guard let d = Descriptor.find(by: descriptor.uuid) else { continue } + l.debug("|------ \(d.name) \(c.uuidString)") + } + } +} +/* +if let userData = services.first(where: { $0.uuid == Service.userData.uuid }) { + l.debug("Discovered UserData service") + if let characteristic = userData.characteristics?.first(where: { $0.uuid == Characteristic.firstName.uuid }) { + l.debug("Discovered FirstName characteristic") + if let descriptor = characteristic.descriptors?.first(where: { $0.uuid == Descriptor.gattCharacteristicUserDescription.uuid }) { + l.debug("Discovered User descriptor") + do { + + let data = "Hello".data(using: .ascii)! + try await peripheral.writeValue(data, for: descriptor).firstValue + + let value = try await peripheral.readValue(for: descriptor).firstValue + + if let d = value as? String { + l.debug("Response: \(d)") + } else { + l.warning("Received Value \(value.debugDescription)") + } + + } catch { + l.error("\(error.localizedDescription)") + } + } else { + l.error("No Required Descriptor") + } + } else { + l.error("No Required Characteristic") + } +} else { + l.error("No Required Service") +} +*/ +l.info("BATTERY") + +if let batteryService = (peripheral.peripheral.services?.first(where: { $0.uuid == Service.batteryService.uuid })) { + do { + let batteryServiceParser = BatteryServiceParser(batteryService: batteryService) + _ = try await peripheral.setNotifyValue(true, for: batteryServiceParser.batteryLevelCharacteristic).firstValue + + peripheral.listenValues(for: batteryServiceParser.batteryLevelCharacteristic) + .sink { completion in + switch completion { + case .finished: l.info("Battery Level completed sending values") + case .failure(let error): l.error("Battery Level completed with failure: \(error.localizedDescription)") + } + } receiveValue: { batteryData in + l.debug("\(batteryData[0])") + } + .store(in: &cancelable) + + } catch { + l.error("\(error.localizedDescription)") + } +} + +l.info("HEALTH THERMOMETER") +if let htService = (peripheral.peripheral.services?.first(where: { $0.uuid == Service.healthThermometer.uuid })) { + do { + let parser = HealthThermometerServiceParser(htService: htService) + + _ = try await peripheral.setNotifyValue(true, for: parser.temperatureMeasurementCharacteristic).firstValue + + let stream = peripheral.listenValues(for: parser.temperatureMeasurementCharacteristic) + .map { TemperatureMeasurement(data: $0) } + + for try await m in stream.values { + l.debug("Measurement: \(m.debugDescription )") + } + } +} + diff --git a/nRF Toolbox.xcodeproj/project.pbxproj b/nRF Toolbox.xcodeproj/project.pbxproj index 45a2acaf..438ce7f5 100644 --- a/nRF Toolbox.xcodeproj/project.pbxproj +++ b/nRF Toolbox.xcodeproj/project.pbxproj @@ -24,9 +24,12 @@ 012C2BE72410F8AA005EC1A0 /* DFUUpdateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012C2BE52410F8AA005EC1A0 /* DFUUpdateViewController.swift */; }; 0133C73B23FAC6160067C86A /* UARTPresetCollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0133C73A23FAC6160067C86A /* UARTPresetCollectionViewDelegate.swift */; }; 01363A872417E4FA0030821E /* ControlSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01363A862417E4FA0030821E /* ControlSection.swift */; }; + 0141F1BA2B62C53E004A0789 /* iOS-BLE-Library in Frameworks */ = {isa = PBXBuildFile; productRef = 0141F1B92B62C53E004A0789 /* iOS-BLE-Library */; }; + 0141F1BC2B62CA3D004A0789 /* Scanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141F1BB2B62CA3D004A0789 /* Scanner.swift */; }; 01468B052361A30A00C29E4C /* StepperTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01468B032361A30A00C29E4C /* StepperTableViewCell.swift */; }; 01468B062361A30A00C29E4C /* StepperTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 01468B042361A30A00C29E4C /* StepperTableViewCell.xib */; }; 01468B082361C5ED00C29E4C /* TimeIntervalSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01468B072361C5ED00C29E4C /* TimeIntervalSection.swift */; }; + 014AC0AA2B94C4CF0094723E /* HealthThermometerServiceParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AC0A92B94C4CF0094723E /* HealthThermometerServiceParser.swift */; }; 014C4E892403F8AE0003E601 /* UARTMacrosTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014C4E882403F8AE0003E601 /* UARTMacrosTableViewController.swift */; }; 014C4E8B2403F9BD0003E601 /* UARTMacroSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014C4E8A2403F9BD0003E601 /* UARTMacroSaver.swift */; }; 0151170B240D18C1001E22F9 /* DFUFirmwareSizeSchemeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01511709240D18C1001E22F9 /* DFUFirmwareSizeSchemeCell.swift */; }; @@ -43,6 +46,11 @@ 01797E8323F4242500B26BF2 /* ModernIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01797E8223F4242500B26BF2 /* ModernIcon.swift */; }; 017D93C62ABAEAA70062287E /* DGCharts in Frameworks */ = {isa = PBXBuildFile; productRef = 017D93C52ABAEAA70062287E /* DGCharts */; }; 0192BB352428E63C003F7F6B /* DFUFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0192BB342428E63C003F7F6B /* DFUFileManager.swift */; }; + 0197A5BD2B8C9B84005E704A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141F1C42B62CEC0004A0789 /* main.swift */; }; + 0197A5BE2B8CA1B1005E704A /* TemperatureMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141F1D12B62D1D2004A0789 /* TemperatureMeasurement.swift */; }; + 0197A5BF2B8CA1B1005E704A /* DeviceInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0141F1CF2B62D0C2004A0789 /* DeviceInformation.swift */; }; + 0197A5C12B8CA20B005E704A /* iOS-Bluetooth-Numbers-Database in Frameworks */ = {isa = PBXBuildFile; productRef = 0197A5C02B8CA20B005E704A /* iOS-Bluetooth-Numbers-Database */; }; + 0197A5C52B8CA5FE005E704A /* BatteryServiceParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0197A5C42B8CA5FE005E704A /* BatteryServiceParser.swift */; }; 0197AFCF2420DAAC00ADF105 /* NotConnectedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0197AFCD2420DAAC00ADF105 /* NotConnectedViewController.swift */; }; 0197AFD02420DAAC00ADF105 /* NotConnectedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0197AFCE2420DAAC00ADF105 /* NotConnectedViewController.xib */; }; 019985E423DEF23F000CDCB6 /* NordicTextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019985E323DEF23F000CDCB6 /* NordicTextTableViewCell.swift */; }; @@ -289,6 +297,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 0141F1AE2B62C2FB004A0789 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 0E645A0E1CBF943000D36AD0 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 12; @@ -319,9 +336,15 @@ 012C2BE52410F8AA005EC1A0 /* DFUUpdateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFUUpdateViewController.swift; sourceTree = ""; }; 0133C73A23FAC6160067C86A /* UARTPresetCollectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UARTPresetCollectionViewDelegate.swift; sourceTree = ""; }; 01363A862417E4FA0030821E /* ControlSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlSection.swift; sourceTree = ""; }; + 0141F1B02B62C2FB004A0789 /* Playground */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Playground; sourceTree = BUILT_PRODUCTS_DIR; }; + 0141F1BB2B62CA3D004A0789 /* Scanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scanner.swift; sourceTree = ""; }; + 0141F1C42B62CEC0004A0789 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 0141F1CF2B62D0C2004A0789 /* DeviceInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceInformation.swift; sourceTree = ""; }; + 0141F1D12B62D1D2004A0789 /* TemperatureMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureMeasurement.swift; sourceTree = ""; }; 01468B032361A30A00C29E4C /* StepperTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepperTableViewCell.swift; sourceTree = ""; }; 01468B042361A30A00C29E4C /* StepperTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StepperTableViewCell.xib; sourceTree = ""; }; 01468B072361C5ED00C29E4C /* TimeIntervalSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeIntervalSection.swift; sourceTree = ""; }; + 014AC0A92B94C4CF0094723E /* HealthThermometerServiceParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthThermometerServiceParser.swift; sourceTree = ""; }; 014C4E882403F8AE0003E601 /* UARTMacrosTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UARTMacrosTableViewController.swift; sourceTree = ""; }; 014C4E8A2403F9BD0003E601 /* UARTMacroSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UARTMacroSaver.swift; sourceTree = ""; }; 014C4E922404038A0003E601 /* nRF_Toolbox_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = nRF_Toolbox_Tests.swift; sourceTree = ""; }; @@ -341,6 +364,8 @@ 016EF387241632AF005FFA30 /* LoggerTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LoggerTableViewController.xib; sourceTree = ""; }; 01797E8223F4242500B26BF2 /* ModernIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModernIcon.swift; sourceTree = ""; }; 0192BB342428E63C003F7F6B /* DFUFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFUFileManager.swift; sourceTree = ""; }; + 0197A5BC2B8BDAFB005E704A /* IOS-BLE-Library */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "IOS-BLE-Library"; path = "../IOS-BLE-Library"; sourceTree = ""; }; + 0197A5C42B8CA5FE005E704A /* BatteryServiceParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryServiceParser.swift; sourceTree = ""; }; 0197AFCD2420DAAC00ADF105 /* NotConnectedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotConnectedViewController.swift; sourceTree = ""; }; 0197AFCE2420DAAC00ADF105 /* NotConnectedViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NotConnectedViewController.xib; sourceTree = ""; }; 019985E323DEF23F000CDCB6 /* NordicTextTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NordicTextTableViewCell.swift; sourceTree = ""; }; @@ -580,6 +605,15 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 0141F1AD2B62C2FB004A0789 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0141F1BA2B62C53E004A0789 /* iOS-BLE-Library in Frameworks */, + 0197A5C12B8CA20B005E704A /* iOS-Bluetooth-Numbers-Database in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5217F3DF1859EE0000F2D5BB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -694,6 +728,27 @@ path = Section; sourceTree = ""; }; + 0141F1B12B62C2FB004A0789 /* Playground */ = { + isa = PBXGroup; + children = ( + 0141F1C42B62CEC0004A0789 /* main.swift */, + 0197A5C32B8CA5E5005E704A /* Battery */, + 0141F1C32B62CEC0004A0789 /* Health Thermometer */, + 0141F1BB2B62CA3D004A0789 /* Scanner.swift */, + ); + path = Playground; + sourceTree = ""; + }; + 0141F1C32B62CEC0004A0789 /* Health Thermometer */ = { + isa = PBXGroup; + children = ( + 0141F1CF2B62D0C2004A0789 /* DeviceInformation.swift */, + 0141F1D12B62D1D2004A0789 /* TemperatureMeasurement.swift */, + 014AC0A92B94C4CF0094723E /* HealthThermometerServiceParser.swift */, + ); + path = "Health Thermometer"; + sourceTree = ""; + }; 01468B022361A23300C29E4C /* Cells */ = { isa = PBXGroup; children = ( @@ -745,6 +800,14 @@ path = ContinuousGlucoseMonitor; sourceTree = ""; }; + 0197A5C32B8CA5E5005E704A /* Battery */ = { + isa = PBXGroup; + children = ( + 0197A5C42B8CA5FE005E704A /* BatteryServiceParser.swift */, + ); + path = Battery; + sourceTree = ""; + }; 0197AFCC2420DA3500ADF105 /* ZephyrDFU */ = { isa = PBXGroup; children = ( @@ -1641,9 +1704,11 @@ 5217F3D91859EE0000F2D5BB = { isa = PBXGroup; children = ( + 0197A5BC2B8BDAFB005E704A /* IOS-BLE-Library */, 5217F3EB1859EE0000F2D5BB /* nRF Toolbox */, 014C4E912404038A0003E601 /* nRF Toolbox Tests */, C39941E4273BCE0500E7AA33 /* nRF Toolbox DebugTests */, + 0141F1B12B62C2FB004A0789 /* Playground */, 5217F3E41859EE0000F2D5BB /* Frameworks */, 5217F3E31859EE0000F2D5BB /* Products */, ); @@ -1654,6 +1719,7 @@ children = ( 5217F3E21859EE0000F2D5BB /* nRF Toolbox Debug.app */, C39941E3273BCE0500E7AA33 /* nRF Toolbox DebugTests.xctest */, + 0141F1B02B62C2FB004A0789 /* Playground */, ); name = Products; sourceTree = ""; @@ -1766,6 +1832,27 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 0141F1AF2B62C2FB004A0789 /* Playground */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0141F1B72B62C2FB004A0789 /* Build configuration list for PBXNativeTarget "Playground" */; + buildPhases = ( + 0141F1AC2B62C2FB004A0789 /* Sources */, + 0141F1AD2B62C2FB004A0789 /* Frameworks */, + 0141F1AE2B62C2FB004A0789 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Playground; + packageProductDependencies = ( + 0141F1B92B62C53E004A0789 /* iOS-BLE-Library */, + 0197A5C02B8CA20B005E704A /* iOS-Bluetooth-Numbers-Database */, + ); + productName = scanner; + productReference = 0141F1B02B62C2FB004A0789 /* Playground */; + productType = "com.apple.product-type.tool"; + }; 5217F3E11859EE0000F2D5BB /* nRF Toolbox */ = { isa = PBXNativeTarget; buildConfigurationList = 5217F4141859EE0000F2D5BB /* Build configuration list for PBXNativeTarget "nRF Toolbox" */; @@ -1817,10 +1904,13 @@ 5217F3DA1859EE0000F2D5BB /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1310; + LastSwiftUpdateCheck = 1520; LastUpgradeCheck = 1250; ORGANIZATIONNAME = "Nordic Semiconductor"; TargetAttributes = { + 0141F1AF2B62C2FB004A0789 = { + CreatedOnToolsVersion = 15.2; + }; 5217F3E11859EE0000F2D5BB = { LastSwiftMigration = 1030; SystemCapabilities = { @@ -1853,6 +1943,8 @@ C3EFCAEE27A943EE00BFE8C4 /* XCRemoteSwiftPackageReference "IOS-DFU-Library" */, AAFC9CD027BA85EA00860638 /* XCRemoteSwiftPackageReference "IOS-nRF-Connect-Device-Manager" */, 017D93C42ABAEAA70062287E /* XCRemoteSwiftPackageReference "Charts" */, + 0141F1B82B62C53E004A0789 /* XCRemoteSwiftPackageReference "IOS-BLE-Library" */, + 0141F1BD2B62CAA3004A0789 /* XCRemoteSwiftPackageReference "iOS-Bluetooth-Numbers-Database" */, ); productRefGroup = 5217F3E31859EE0000F2D5BB /* Products */; projectDirPath = ""; @@ -1860,6 +1952,7 @@ targets = ( 5217F3E11859EE0000F2D5BB /* nRF Toolbox */, C39941E2273BCE0500E7AA33 /* nRF Toolbox DebugTests */, + 0141F1AF2B62C2FB004A0789 /* Playground */, ); }; /* End PBXProject section */ @@ -1938,6 +2031,19 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0141F1AC2B62C2FB004A0789 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0197A5BF2B8CA1B1005E704A /* DeviceInformation.swift in Sources */, + 0197A5BE2B8CA1B1005E704A /* TemperatureMeasurement.swift in Sources */, + 0197A5BD2B8C9B84005E704A /* main.swift in Sources */, + 0197A5C52B8CA5FE005E704A /* BatteryServiceParser.swift in Sources */, + 0141F1BC2B62CA3D004A0789 /* Scanner.swift in Sources */, + 014AC0AA2B94C4CF0094723E /* HealthThermometerServiceParser.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5217F3DE1859EE0000F2D5BB /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -2174,6 +2280,94 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0141F1B42B62C2FB004A0789 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 0141F1B52B62C2FB004A0789 /* Beta */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Beta; + }; + 0141F1B62B62C2FB004A0789 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 20FDA43523154494006EEBC1 /* Beta */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2614,6 +2808,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0141F1B72B62C2FB004A0789 /* Build configuration list for PBXNativeTarget "Playground" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0141F1B42B62C2FB004A0789 /* Debug */, + 0141F1B52B62C2FB004A0789 /* Beta */, + 0141F1B62B62C2FB004A0789 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; 5217F3DD1859EE0000F2D5BB /* Build configuration list for PBXProject "nRF Toolbox" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2647,6 +2851,22 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 0141F1B82B62C53E004A0789 /* XCRemoteSwiftPackageReference "IOS-BLE-Library" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/NordicSemiconductor/IOS-BLE-Library.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.3.0; + }; + }; + 0141F1BD2B62CAA3004A0789 /* XCRemoteSwiftPackageReference "iOS-Bluetooth-Numbers-Database" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/NickKibish/iOS-Bluetooth-Numbers-Database.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; 017D93C42ABAEAA70062287E /* XCRemoteSwiftPackageReference "Charts" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/danielgindi/Charts.git"; @@ -2690,11 +2910,21 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0141F1B92B62C53E004A0789 /* iOS-BLE-Library */ = { + isa = XCSwiftPackageProductDependency; + package = 0141F1B82B62C53E004A0789 /* XCRemoteSwiftPackageReference "IOS-BLE-Library" */; + productName = "iOS-BLE-Library"; + }; 017D93C52ABAEAA70062287E /* DGCharts */ = { isa = XCSwiftPackageProductDependency; package = 017D93C42ABAEAA70062287E /* XCRemoteSwiftPackageReference "Charts" */; productName = DGCharts; }; + 0197A5C02B8CA20B005E704A /* iOS-Bluetooth-Numbers-Database */ = { + isa = XCSwiftPackageProductDependency; + package = 0141F1BD2B62CAA3004A0789 /* XCRemoteSwiftPackageReference "iOS-Bluetooth-Numbers-Database" */; + productName = "iOS-Bluetooth-Numbers-Database"; + }; AA2CB313286C75450083DB9A /* iOSMcuManagerLibrary */ = { isa = XCSwiftPackageProductDependency; package = AAFC9CD027BA85EA00860638 /* XCRemoteSwiftPackageReference "IOS-nRF-Connect-Device-Manager" */; diff --git a/nRF Toolbox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/nRF Toolbox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 64bf3392..56f6c924 100644 --- a/nRF Toolbox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/nRF Toolbox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,35 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/danielgindi/Charts.git", "state" : { - "revision" : "0a229f8c914b0ec93798cee058cf75b339297513", - "version" : "5.0.0" + "revision" : "dd9c72e3d7e751e769971092a6bd72d39198ae63", + "version" : "5.1.0" + } + }, + { + "identity" : "corebluetoothmock-collection", + "kind" : "remoteSourceControl", + "location" : "https://github.com/NickKibish/CoreBluetoothMock-Collection.git", + "state" : { + "revision" : "91fc016e9345f37d0b4539f9fc6c270d6d5d6f75", + "version" : "1.0.0" + } + }, + { + "identity" : "ios-bluetooth-numbers-database", + "kind" : "remoteSourceControl", + "location" : "https://github.com/NickKibish/iOS-Bluetooth-Numbers-Database.git", + "state" : { + "revision" : "40ae8d7050d16fc2603c314dd560a7992c35f56f", + "version" : "1.0.0" + } + }, + { + "identity" : "ios-corebluetooth-mock", + "kind" : "remoteSourceControl", + "location" : "https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock.git", + "state" : { + "revision" : "500b23852e7d46b63ef8e2a826c2b6f3f6ed50ef", + "version" : "0.18.0" } }, { @@ -36,6 +63,24 @@ "revision" : "c9beefcdaae3c24a95c249ae20333fcf9a3b8ec6" } }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, { "identity" : "swiftcbor", "kind" : "remoteSourceControl", diff --git a/nRF Toolbox.xcodeproj/xcshareddata/xcschemes/Playground.xcscheme b/nRF Toolbox.xcodeproj/xcshareddata/xcschemes/Playground.xcscheme new file mode 100644 index 00000000..8b3883f5 --- /dev/null +++ b/nRF Toolbox.xcodeproj/xcshareddata/xcschemes/Playground.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +