Open sureshjoshi opened 3 years ago
I am working on a more comprehensive package to enable this feature, but if you want to get started now, you can use the two extensions below, which somewhat resemble the Rx extensions API. Example and implementation follows. This work isn't complete, it probably needs to consider things like cancellations and what not, I hope to publish something eventually alongside an open source project of mine, making this an official extension as the APIs evolve with real world usage.
// Monitor Bluetooth state
for await state in manager.state() {
// Skip execution until we reach a poweredOn event
guard state == .poweredOn else { continue }
// Scan for devices
for await device in manager.scan() {
// Wait till we find our device
guard device.name == "MyDevice" else { continue }
// Stop scanning once we have found our device...
manager.stopScan()
// ...and connect!
await device.connect()
// Discover services
let services = try await device.services()
for service in services {
// Discover characteristics for a service
let result = try await device.characteristics(for: service)
for characterisic in result.characteristics {
// Print each value for a given characteristic if it has one
guard let value = try? await device.read(characterisic, in: service) else { return }
print("\(device.id):\(service.uuid):\(characterisic.uuid): \(value)")
}
}
}
}
import SwiftyTeeth
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public extension SwiftyTeeth {
func state() -> AsyncStream<BluetoothState> {
AsyncStream { continuation in
stateChangedHandler = {
continuation.yield($0)
}
}
}
func scan() -> AsyncStream<Device> {
AsyncStream { continuation in
scan {
continuation.yield($0)
}
}
}
func scan(timeout: TimeInterval) async -> [Device] {
await withCheckedContinuation { continuation in
scan(for: timeout) {
continuation.resume(returning: $0)
}
}
}
}
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public extension Device {
@discardableResult
func connect(timeout: TimeInterval? = nil, autoReconnect: Bool = true) async -> ConnectionState {
await withCheckedContinuation { continuation in
connect(with: timeout, autoReconnect: autoReconnect) {
continuation.resume(returning: $0)
}
}
}
func services(with uuids: [UUID]? = nil) async throws -> [Service] {
try await withCheckedThrowingContinuation { continuation in
discoverServices(with: uuids) {
continuation.resume(with: $0)
}
}
}
func characteristics(with uuids: [UUID]? = nil, for service: Service) async throws -> DiscoveredCharacteristic {
try await withCheckedThrowingContinuation { continuation in
discoverCharacteristics(with: uuids, for: service) {
continuation.resume(with: $0)
}
}
}
func read(_ characteristic: Characteristic, in service: Service) async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
read(from: characteristic.uuid, in: service.uuid) {
continuation.resume(with: $0)
}
}
}
func write(_ data: Data, to characteristic: Characteristic, in service: Service) async throws {
try await withCheckedThrowingContinuation { continuation in
write(data: data, to: characteristic.uuid, in: service.uuid) {
continuation.resume(with: $0)
}
}
}
func subscribe(to characteristic: Characteristic, in service: Service) -> AsyncThrowingStream<Data, Error> {
AsyncThrowingStream { continuation in
subscribe(to: characteristic.uuid, in: service.uuid) {
continuation.yield(with: $0)
}
}
}
}
@ericlewis Legend!
Do you happen to know if Swift Package Manager yet lets us use multiple packages with multiple OS requirements? I last looked into this like 6 months ago, and it wasn't possible at the time.
I'd like to consolidate all the SwiftyTeeth extensions into a single repo - for maintainability.
Yeah, it should be possible I think. If not, then the availability checks can at least stop the code from being compiled while being a separate package.
That's true - my thinking was mostly in cases where there are deps installed, but in this case, you're right - it doesn't matter
Just a note for posterity, I've been using these in my apps that targeted iOS 15+, however, since async/await was back ported a bit - I've been using them in a few other apps.
Haven't landed them in the repo because of the naming conflicts
For call/response, async await can really clean up code