Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.vscode
.vscode

.docc-build/
**/.docc-build/
AsyncCoreBluetooth.doccarchive
swift-docs
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2025-03-07

### Added
- New documentation in `Sources/AsyncCoreBluetooth/AsyncCoreBluetooth.docc` ([2fea786](https://github.com/meech-ward/AsyncCoreBluetooth/commit/2fea7860a82112ca6fa8afa2b130239674761c81))

### Changed
- Make sure all characteristic continuations are canceled on peripheral disconnect
- Make sure notifications can't be enabled or disabled if they are already in that state
- Make data for read and notify not optional ([78629a6](https://github.com/meech-ward/AsyncCoreBluetooth/commit/78629a694a84165df5959ff916ea564c86bfdfd4))
- Make sure a new peripheral actor is created for each new peripheral instance

### Fixed
- Remove nonisolated lazy property from Peripheral actor

## [0.1.x] - 2024-XX-XX

- Initial releases with core functionality
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ struct ContentView: View {
Your application should handle all the possible cases of the ble state. It's very common for someone to turn off bluetooth or turn on airplane mode and your application's UI should reflect these states. However, the most common case is `.poweredOn`, so if you're only interested in running code as soon as the device is in that state, you can use the following:

```swift
_ = await centralManager.startStream().first(where: {$0 == .poweredOn})
for await _ in await centralManager.startStream().first(where: {$0 == .poweredOn}) {
// re run the setup code if it goes off then back on
}
```

Keep in mind that familiarity with swift concurrency is going to make using this library a lot easier.
Expand Down Expand Up @@ -270,3 +272,7 @@ you can requst a new async stream or break out of these streams as much as you l
```
swift test --no-parallel
```

## Building Documentation

check build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Getting Started with Async Core Bluetooth and Accessory Setup Kit


@Metadata {
@PageImage(purpose: card, source: "icon", alt: "Async Core Bluetooth")
}

## Overview

Async Core Bluetooth

### Initializing The Central Manager

Setup the central manager and check the current ble state:

```swift
import AccessorySetupKit

// Create a session
var session = ASAccessorySession()

// Activate session with event handler
session.activate(on: DispatchQueue.main, eventHandler: handleSessionEvent(event:))

// Handle event
func handleSessionEvent(event: ASAccessoryEvent) {
switch event.eventType {
case .activated:
print("Session is activated and ready to use")
print(session.accessories)
default:
print("Received event type \(event.eventType)")
}
}
```


```swift
// Create descriptor for pink dice
let pinkDescriptor = ASDiscoveryDescriptor()
pinkDescriptor.bluetoothServiceUUID = pinkUUID
// Create descriptor for blue dice
let blueDescriptor = ASDiscoveryDescriptor()
blueDescriptor.bluetoothServiceUUID = blueUUID

// Create picker display items
let pinkDisplayItem = ASPickerDisplayItem(
name: "Pink Dice",
productImage: UIImage(named: "pink")!,
descriptor: pinkDescriptor
)
let blueDisplayItem = ASPickerDisplayItem(
name: "Blue Dice",
productImage: UIImage(named: "blue")!,
descriptor: blueDescriptor
)
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Like Core Bluetooth, but async.

## Overview

AsyncCoreBluetooth provides code.
AsyncCoreBluetooth is a library.

### Featured

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# ``AsyncCoreBluetooth/CentralManager``

@Metadata {
@DocumentationExtension(mergeBehavior: append)
}

Central Manager is the core class responsible for managing and interacting with CoreBluetooth. This class provides an async interface for interacting with CoreBluetooth.

<!--
## Topics

### Initialization
Expand Down Expand Up @@ -38,5 +35,5 @@ Central Manager is the core class responsible for managing and interacting with

### Inspecting Feature Support

- ``supports(_:)``
- ``supports(_:)`` -->

Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# ``AsyncCoreBluetooth/Peripheral``

@Metadata {
@DocumentationExtension(mergeBehavior: append)
}

This class provides an async interface for interacting with CoreBluetooth Peripherals.

## Topics
This is from the md docs

<!-- ## Topics

### Peripheral Connection State

- ``ConnectionState-swift.enum``
- ``connectionState-swift.property``

-->
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import AsyncCoreBluetooth

let centralManager = CentralManager()

for await bleState in await centralManager.start() {
for await bleState in await centralManager.startStream() {
switch bleState {
case .unknown:
print("Unkown")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Foundation
import CoreBluetooth
import AsyncCoreBluetooth

actor MyAppsBLEManager {
let centralManager = CentralManager()

func start() async {
for await bleState in await centralManager.startStream() {
switch bleState {
case .unknown:
print("Unkown")
case .resetting:
print("Resetting")
case .unsupported:
print("Unsupported")
case .unauthorized:
print("Unauthorized")
case .poweredOff:
print("Powered Off")
case .poweredOn:
print("Powered On, ready to scan")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation
import CoreBluetooth
import AsyncCoreBluetooth


actor MyAppsBLEManager {
let centralManager = CentralManager()
var peripheral: Peripheral?

func start() async {
for await bleState in await centralManager.startStream() {
switch bleState {
case .unknown:
print("Unkown")
case .resetting:
print("Resetting")
case .unsupported:
print("Unsupported")
case .unauthorized:
print("Unauthorized")
case .poweredOff:
print("Powered Off")
case .poweredOn:
print("Powered On, ready to scan")
}
}
}

func scanForPeripheral() async {
let heartRateServiceUUID = CBUUID(string: "180D")

do {
let peripherals = try await centralManager.scanForPeripherals(withServices: [heartRateServiceUUID])
let peripheral = peripherals[heartRateServiceUUID]
print("found peripheral \(peripheral)")
} catch {
// This only happens when ble state is not powered on or you're already scanning
print("error scanning for peripherals \(error)")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation
import CoreBluetooth
import AsyncCoreBluetooth


actor MyAppsBLEManager {
let centralManager = CentralManager()
var peripheral: Peripheral?

func start() async {
for await bleState in await centralManager.startStream() {
switch bleState {
case .unknown:
print("Unkown")
case .resetting:
print("Resetting")
case .unsupported:
print("Unsupported")
case .unauthorized:
print("Unauthorized")
case .poweredOff:
print("Powered Off")
case .poweredOn:
print("Powered On, ready to scan")
}
}
}

func scanForPeripheral() async {
let heartRateServiceUUID = CBUUID(string: "180D")

do {
let peripherals = try await centralManager.scanForPeripherals(withServices: [heartRateServiceUUID])
let peripheral = peripherals[heartRateServiceUUID]
print("found peripheral \(peripheral)")
} catch {
// This only happens when ble state is not powered on or you're already scanning
print("error scanning for peripherals \(error)")
}
}

func connectToPeripheral() async {
guard let peripheral else {
return
}
for await connectionState in await centralManager.connect(self.peripheral) {
print(connectionState)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import SwiftUI
import AsyncCoreBluetooth

struct ContentView: View {
var centralManager = CentralManager()
var body: some View {
NavigationStack {
VStack {
switch centralManager.state.bleState {
case .unknown:
Text("Unkown")
case .resetting:
Text("Resetting")
case .unsupported:
Text("Unsupported")
case .unauthorized:
Text("Unauthorized")
case .poweredOff:
Text("Powered Off")
case .poweredOn:
Text("Powered On, ready to scan")
}
}
.padding()
.navigationTitle("App")
}
.task {
await centralManager.start()
// or startStream if you want the async stream returned from start
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import SwiftUI
import AsyncCoreBluetooth

struct ScanningPeripherals: View {
let heartRateServiceUUID = UUID(string: "180D")
var centralManager: CentralManager
@MainActor @State private var peripherals: Set<Peripheral> = []

var body: some View {
VStack {
List(Array(peripherals), id: \.identifier) { peripheral in
Section {
ScannedPeripheralRow(centralManager: centralManager, peripheral: peripheral)
}
}
}
.task {
do {
for await peripheral in try await centralManager.scanForPeripherals(withServices: [heartRateServiceUUID]) {
peripherals.insert(peripheral)
// break out of the loop or terminate the continuation to stop the scan
}
} catch {
// This only happens when ble state is not powered on or you're already scanning
print("error scanning for peripherals \(error)")
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions Sources/AsyncCoreBluetooth/AsyncCoreBluetooth.docc/RootLanding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# AsyncCoreBluetooth

@Metadata {
@TechnologyRoot
@PageImage(
purpose: icon,
source: "icon",
alt: "A technology icon representing the AsyncCoreBluetooth framework."
)
@PageColor(green)
}

## Overview

This is the **root page** for the AsyncCoreBluetooth technology.

- [Documentation](documentation)
- [Tutorials](tutorials)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@Tutorials(name: "AsyncCoreBluetooth") {
@Intro(title: "Meet AsyncCoreBluetooth") {
Setup a BLE Central to connect to an external Peripheral.

@Image(source: ble-intro.png, alt: "BLE")
}

@Chapter(name: "AsyncCoreBluetooth Essentials") {
@Image(source: ble-chapter1.png, alt: "ble")

Create a BLE Central to connect to an external Peripheral.

@TutorialReference(tutorial: "doc:Creating-Central")
}
}
Loading