Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pre-implemented Services, reusable UI components and unit testing setup #10

Merged
merged 80 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
f6627c0
Added some predfined services and characteristics
Supereg Jan 23, 2024
1681c43
Implement onChange for @Characteristic
Supereg Jan 23, 2024
488e6f6
Minor changes
Supereg Jan 24, 2024
6edff92
Remove test code
Supereg Jan 24, 2024
409e041
Introduce observable model for service and characteristic classes
Supereg Jan 24, 2024
e76c75c
Fix change propagation
Supereg Jan 24, 2024
22e4c3c
Some minor debugging
Supereg Jan 24, 2024
5ef0450
Upsie
Supereg Jan 24, 2024
314dcc9
Make sure all changes propagate
Supereg Jan 25, 2024
a9b5c18
Make autoconnect feature based on rssi
Supereg Jan 25, 2024
43c4218
Minor changes on how advertisement local name is handled
Supereg Jan 25, 2024
f9db62d
Implement projected values for Service and DeviceState. Support onCha…
Supereg Jan 25, 2024
e6e1b00
Reduce surface of the BluetoothScanner protocol
Supereg Jan 27, 2024
2b3cbb4
Some modeling updates
Supereg Jan 31, 2024
0e241fb
Improve error message
Supereg Jan 31, 2024
230c7ec
Improved debugging
Supereg Jan 31, 2024
bbc808d
Minor changes and logging
Supereg Jan 31, 2024
8525490
Refactor thread model for the BluetoothPeripheral
Supereg Jan 31, 2024
c485e63
Update task scheduling
Supereg Jan 31, 2024
e7ff217
Binding all changes of observable state to MainActor
Supereg Jan 31, 2024
e99cb3a
Remove outdated assumption
Supereg Jan 31, 2024
85352ff
Last few todos
Supereg Jan 31, 2024
959608f
Compiler fixes
Supereg Jan 31, 2024
c60a3bf
Delay switch to connected state once everything is fully connected
Supereg Jan 31, 2024
45595c7
Ensure disconnecting devies are cleaned while scanning
Supereg Jan 31, 2024
c95e171
Upsie
Supereg Jan 31, 2024
696941c
Fixing hierarchy of nearby scan modifiers
Supereg Jan 31, 2024
b31c420
Make services self-contained, allow for async onChange handlers
Supereg Jan 31, 2024
ba02219
Reusable views + start documentation catalogs
Supereg Feb 1, 2024
c39d252
Introduce AsyncSemaphore as proper abstraction, some fixes and starti…
Supereg Feb 5, 2024
dc4e08a
BluetoothManager now an actor and BluetoothPeripheral share same acto…
Supereg Feb 5, 2024
afabc0c
Pull through with the new Actor model
Supereg Feb 6, 2024
0f602ee
Introduce a simple value observation protocol
Supereg Feb 7, 2024
07b4f43
Some progress. Back to the Task approach
Supereg Feb 8, 2024
54447a5
Last few steps in the core SpeziBluetooth target
Supereg Feb 9, 2024
05c70ac
Fix index out of range
Supereg Feb 9, 2024
aa9e7cc
Fix delivery of disconnected events
Supereg Feb 9, 2024
73c575b
Same for characteristics onChange
Supereg Feb 9, 2024
2e1d229
Compiler
Supereg Feb 9, 2024
974e52e
Make sure parent modifier doesnt stop a child modifiers scan
Supereg Feb 9, 2024
c629803
Provide fallback values for DeviceStates in preview conditions
Supereg Feb 9, 2024
5d19067
Fix nested optionals
Supereg Feb 9, 2024
374773c
Support injecting values for preview support
Supereg Feb 9, 2024
12863d9
pass value
Supereg Feb 9, 2024
a20bc8b
Support injection of DeviceState values
Supereg Feb 9, 2024
114dc3c
Support injecting custom device action behavior
Supereg Feb 9, 2024
606f2a9
projectedValue cannot be part of the SPI
Supereg Feb 9, 2024
a08933c
Explicit dependency
Supereg Feb 9, 2024
6a7e3ed
Trying stuff
Supereg Feb 9, 2024
16ce491
Fallback check for simulator
Supereg Feb 10, 2024
9c2cea7
Implement TestPeripheral and resolve todos
Supereg Feb 11, 2024
b559c80
Implement test app and write unit test sketch
Supereg Feb 11, 2024
446f988
Implement UI tests for SpeziBluetooth device interactions
Supereg Feb 12, 2024
d786a54
Fix timing
Supereg Feb 12, 2024
b8e6fb2
Be more lenient
Supereg Feb 12, 2024
843cd47
Fix a small crash
Supereg Feb 12, 2024
285716e
Use ScenePhase instead of onReceive modifiers
Supereg Feb 20, 2024
133c833
Support encrypted notifies and indicates
Supereg Feb 20, 2024
039b04c
Add specification references for all Characteristic implementations
Supereg Feb 20, 2024
75a733a
Run ui tests on mac catalyst
Supereg Feb 21, 2024
0b90c21
Still run in simulator
Supereg Feb 21, 2024
089224f
Fix runners
Supereg Feb 21, 2024
67408d0
Do not set up simulators for macos. Doesnt make sense
Supereg Feb 21, 2024
d71c3ff
Remove entitlement file to not require signing
Supereg Feb 21, 2024
8f57b66
Adding service script and template
Supereg Feb 21, 2024
a36b07a
Some troubleshooting steps
Supereg Feb 21, 2024
2cd1a76
Setup Signing
PSchmiedmayer Feb 22, 2024
dfa18f9
Update Action
PSchmiedmayer Feb 22, 2024
7707052
Update Setup
PSchmiedmayer Feb 22, 2024
2f59d92
Update Setup
PSchmiedmayer Feb 22, 2024
939e1b2
Update Configuration
PSchmiedmayer Feb 22, 2024
9c2b0d9
Update Setup
PSchmiedmayer Feb 22, 2024
0b83c1e
Update Profile
PSchmiedmayer Feb 22, 2024
cb8eac8
Update Setup
PSchmiedmayer Feb 22, 2024
aafdc1b
Update Setup
PSchmiedmayer Feb 22, 2024
0e906fe
Update Setup
PSchmiedmayer Feb 22, 2024
302273e
Update Setup
PSchmiedmayer Feb 22, 2024
88118dc
Remove catalyst workaround
Supereg Feb 22, 2024
17a0d95
Some additional comments
Supereg Feb 22, 2024
ed21790
Add automationmodetool command
Supereg Feb 22, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
setupSimulators: true
path: 'Tests/UITests'
scheme: TestApp
artifactname: TestApp.xcresult
Expand Down
2 changes: 2 additions & 0 deletions .spi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ builder:
configs:
- platform: ios
documentation_targets:
- BluetoothServices
- SpeziBluetooth
- XCTBluetooth
37 changes: 34 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ let package = Package(
name: "SpeziBluetooth",
defaultLocalization: "en",
platforms: [
.iOS(.v17)
.iOS(.v17),
.macCatalyst(.v17),
.macOS(.v14)
],
products: [
.library(name: "BluetoothServices", targets: ["BluetoothServices"]),
.library(name: "BluetoothViews", targets: ["BluetoothViews"]),
.library(name: "SpeziBluetooth", targets: ["SpeziBluetooth"]),
.library(name: "XCTBluetooth", targets: ["XCTBluetooth"])
],
dependencies: [
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", from: "1.0.0"),
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.0.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews", from: "1.1.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.59.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.4")
],
Expand All @@ -33,21 +39,46 @@ let package = Package(
.product(name: "Spezi", package: "Spezi"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
.product(name: "OrderedCollections", package: "swift-collections")
.product(name: "OrderedCollections", package: "swift-collections"),
// We have an issue in Xcode projects when importing XCTBluetooth in a test target that it fails
// to link with SpeziFoundation. lol. :)
.product(name: "SpeziFoundation", package: "SpeziFoundation")
Supereg marked this conversation as resolved.
Show resolved Hide resolved
],
resources: [
.process("Resources")
]
),
.target(
name: "XCTBluetooth",
name: "BluetoothServices",
dependencies: [
.target(name: "SpeziBluetooth")
]
),
.target(
name: "BluetoothViews",
dependencies: [
.target(name: "SpeziBluetooth"),
.product(name: "SpeziViews", package: "SpeziViews")
]
),
.target(
name: "XCTBluetooth",
dependencies: [
.target(name: "SpeziBluetooth"),
.product(name: "NIO", package: "swift-nio")
]
),
.executableTarget(
name: "TestPeripheral",
dependencies: [
.target(name: "SpeziBluetooth"),
.target(name: "BluetoothServices")
]
),
.testTarget(
name: "SpeziBluetoothTests",
dependencies: [
.target(name: "BluetoothServices"),
.target(name: "SpeziBluetooth"),
.target(name: "XCTBluetooth"),
.product(name: "NIO", package: "swift-nio")
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ class MyDevice: BluetoothDevice {
@DeviceState(\.state)
var state: PeripheralState

@Service(id: "180A")
var deviceInformation = DeviceInformationService()
@Service var deviceInformation = DeviceInformationService()

@DeviceAction(\.connect)
var connect
Expand All @@ -130,7 +129,7 @@ class ExampleDelegate: SpeziAppDelegate {
Configuration {
Bluetooth {
// Define which devices type to discover by what criteria .
// In this case we search for some custom FFF0 characteristic that is advertised.
// In this case we search for some custom FFF0 service that is advertised.
Discover(MyDevice.self, by: .advertisedService("FFF0"))
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ``BluetoothServices``

Reusable Bluetooth Service implementations.

<!--
#
# This source file is part of the Stanford Spezi open source project
#
# SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
#
# SPDX-License-Identifier: MIT
#
-->

## Overview

This target provides reusable Bluetooth service implementations of standardized Bluetooth services.

## Topics

### Core Services

- ``DeviceInformationService``

### Health Domain

- ``HealthThermometerService``
138 changes: 138 additions & 0 deletions Sources/BluetoothServices/Characteristics/DateTime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import NIO
import SpeziBluetooth


/// Date Time characteristic to represent date and time.
///
/// Refer to GATT Specification Supplement, 3.70 Date Time
public struct DateTime {
public enum Month: UInt8 {
/// Unknown month.
case unknown
/// The month January.
case january
/// The month February.
case february
/// The month March.
case march
/// The month April.
case april
/// The month Mai.
case mai
/// The month June.
case june
/// The month July.
case july
/// The month August.
case august
/// The month September.
case september
/// The month October.
case october
/// The month November.
case november
/// The month December.
case december
}

/// Year as defined by the Gregorian calendar.
///
/// Valid range 1582 to 9999.
/// A value of 0 means that the year is not known. All other values are Reserved for Future Use.
public let year: UInt16
/// Month of the year as defined by the Gregorian calendar.
///
/// Valid range 1 (January) to 12 (December).
/// A value of 0 means that the month is not known.
public let month: Month
/// Day of the month as defined by the Gregorian calendar.
///
/// Valid range 1 to 31.
/// A value of 0 means that the day of month is not known.
public let day: UInt8
/// Number of hours past midnight.
///
/// Valid range 0 to 23.
public let hours: UInt8
/// Number of minutes since the start of the hour.
///
/// Valid range 0 to 59.
public let minutes: UInt8
/// Number of seconds since the start of the minute.
///
/// Valid range 0 to 59.
public let seconds: UInt8


/// Create a new Date Time.
/// - Parameters:
/// - year: The year.
/// - month: The month.
/// - day: The day.
/// - hours: The hours.
/// - minutes: The minutes.
/// - seconds: The seconds.
public init(year: UInt16 = 0, month: Month = .unknown, day: UInt8 = 0, hours: UInt8, minutes: UInt8, seconds: UInt8) {
// swiftlint:disable:previous function_default_parameter_at_end
self.year = year
self.month = month
self.day = day
self.hours = hours
self.minutes = minutes
self.seconds = seconds
}
}


extension DateTime.Month: Equatable {}


extension DateTime: Equatable {}


extension DateTime.Month: ByteCodable {
public init?(from byteBuffer: inout ByteBuffer) {
guard let value = UInt8(from: &byteBuffer) else {
return nil

Check warning on line 104 in Sources/BluetoothServices/Characteristics/DateTime.swift

View check run for this annotation

Codecov / codecov/patch

Sources/BluetoothServices/Characteristics/DateTime.swift#L104

Added line #L104 was not covered by tests
}

self.init(rawValue: value)
}

public func encode(to byteBuffer: inout ByteBuffer) {
rawValue.encode(to: &byteBuffer)
}
}


extension DateTime: ByteCodable {
public init?(from byteBuffer: inout ByteBuffer) {
guard let year = UInt16(from: &byteBuffer),
let month = Month(from: &byteBuffer),
let day = UInt8(from: &byteBuffer),
let hours = UInt8(from: &byteBuffer),
let minutes = UInt8(from: &byteBuffer),
let seconds = UInt8(from: &byteBuffer) else {
return nil

Check warning on line 124 in Sources/BluetoothServices/Characteristics/DateTime.swift

View check run for this annotation

Codecov / codecov/patch

Sources/BluetoothServices/Characteristics/DateTime.swift#L124

Added line #L124 was not covered by tests
}

self.init(year: year, month: month, day: day, hours: hours, minutes: minutes, seconds: seconds)
}

public func encode(to byteBuffer: inout ByteBuffer) {
year.encode(to: &byteBuffer)
month.encode(to: &byteBuffer)
day.encode(to: &byteBuffer)
hours.encode(to: &byteBuffer)
minutes.encode(to: &byteBuffer)
seconds.encode(to: &byteBuffer)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import NIO
import SpeziBluetooth


/// Represents the time between measurements.
public enum MeasurementInterval {
Supereg marked this conversation as resolved.
Show resolved Hide resolved
/// No periodic measurement
case noPeriodicMeasurement
/// Duration of measurement interval.
case duration(_ seconds: UInt16)
}


extension MeasurementInterval: Equatable {}


extension MeasurementInterval: RawRepresentable {
public var rawValue: UInt16 {
switch self {
case .noPeriodicMeasurement:
0
case let .duration(seconds):
seconds
}
}

public init(rawValue: UInt16) {
switch rawValue {
case 0:
self = .noPeriodicMeasurement
default:
self = .duration(rawValue)
}
}
}


extension MeasurementInterval: ByteCodable {
public init?(from byteBuffer: inout ByteBuffer) {
guard let value = UInt16(from: &byteBuffer) else {
return nil

Check warning on line 49 in Sources/BluetoothServices/Characteristics/MeasurementInterval.swift

View check run for this annotation

Codecov / codecov/patch

Sources/BluetoothServices/Characteristics/MeasurementInterval.swift#L49

Added line #L49 was not covered by tests
}

self.init(rawValue: value)
}

public func encode(to byteBuffer: inout ByteBuffer) {
rawValue.encode(to: &byteBuffer)
}
}
Loading
Loading