Skip to content
Open
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
61 changes: 61 additions & 0 deletions Playground/Battery/BatteryServiceParser.swift
Original file line number Diff line number Diff line change
@@ -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() {

}
}
106 changes: 106 additions & 0 deletions Playground/Health Thermometer/DeviceInformation.swift
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions Playground/Health Thermometer/HealthThermometerServiceParser.swift
Original file line number Diff line number Diff line change
@@ -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 })
}
}
68 changes: 68 additions & 0 deletions Playground/Health Thermometer/TemperatureMeasurement.swift
Original file line number Diff line number Diff line change
@@ -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<R: FixedWidthInteger>(_ data: Data, fromOffset offset: Int = 0) -> R {
let length = MemoryLayout<R>.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
}
}
37 changes: 37 additions & 0 deletions Playground/Scanner.swift
Original file line number Diff line number Diff line change
@@ -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")
}
Loading