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

EEG Device Pairing #19

Merged
merged 39 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1565145
Add initial draft of the muse integration
Supereg Sep 13, 2023
7d2338b
Some restructuring, some stability, some improvements
Supereg Sep 16, 2023
cf3aa1d
Display battery in device list
Supereg Sep 16, 2023
3c0dcd8
Minor adjustments
Supereg Sep 16, 2023
c8cce81
Add basic eeg recording charts view
Supereg Sep 17, 2023
31f435f
Introduce a small device abstraction layer to allow builds on simulat…
Supereg Sep 17, 2023
6797798
Add Muse as a submodule and check actions ability to checkout
Supereg Sep 19, 2023
bae880e
Pull submodules via access token
Supereg Sep 19, 2023
5c96829
Try out the updated build action
Supereg Sep 19, 2023
6552880
Pass PAT as secret
Supereg Sep 19, 2023
f605546
Remove checkout test again
Supereg Sep 19, 2023
a65f1af
Fix REUSE compliance, remove unnecessary Info.plist duplicate
Supereg Sep 19, 2023
9fcbbc5
Fix REUSE
Supereg Sep 19, 2023
363da88
Upgrade to new action version
Supereg Sep 20, 2023
f98bcff
Use stanford bdhg for codeql
Supereg Sep 25, 2023
ae87b70
Update to fixed CI
Supereg Sep 25, 2023
93cfde8
Pick eeg frequency, instant battery icon, more debug
Supereg Sep 26, 2023
75bf8e4
Some major cleanup and improved previews
Supereg Sep 28, 2023
48ac6b3
Restore ci file
Supereg Sep 28, 2023
2a69de6
First iteration of new device details view
Supereg Sep 28, 2023
bf8b27b
Merge branch 'main' into feature/eeg-device-pairing
PSchmiedmayer Sep 29, 2023
7022d66
Update Fastlane Setup
PSchmiedmayer Sep 29, 2023
f9fa11e
Test Beta
PSchmiedmayer Sep 29, 2023
e523c2f
Update Appfile
PSchmiedmayer Sep 29, 2023
7ccaf99
Update GitHub Actions
PSchmiedmayer Sep 29, 2023
31e09bb
Update GitHub Action
PSchmiedmayer Sep 29, 2023
fcb8209
Re-Add Password Autofill Disable
PSchmiedmayer Sep 29, 2023
78c603f
Refine UI and bump target OS
Supereg Oct 16, 2023
d70b5d2
Minor adjustments, fix CodeQL run
Supereg Oct 16, 2023
e8f94de
Mock EEG recordings and resolve all todos
Supereg Oct 16, 2023
5dc3536
Revert changes to schemes?
Supereg Oct 16, 2023
88b303a
Disable CodeQL for now
Supereg Oct 16, 2023
dc3dea5
Fix button style for nearby device row
Supereg Oct 17, 2023
9f10da9
First eeg device ui tests
Supereg Oct 17, 2023
acbe8a5
Add UI tests for recordings view
Supereg Oct 17, 2023
fe89795
Actually add the tests
Supereg Oct 17, 2023
7e532e0
Reenable CI steps for beta deployment
Supereg Oct 23, 2023
280b866
Trying to fix test coverage
Supereg Oct 23, 2023
059076e
Update README
Supereg Oct 23, 2023
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/beta-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ jobs:
runsonlabels: '["macOS-13"]'
fastlanelane: beta
setupsigning: true
checkout_submodules: true
9 changes: 0 additions & 9 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ jobs:
swiftlint:
name: SwiftLint
uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v2
codeql:
name: CodeQL
uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
codeql: true
xcodeversion: '14.3.1'
fastlanelane: codeql
buildandtest:
name: Build and Test
uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand All @@ -35,8 +28,6 @@ jobs:
runsonlabels: '["macOS", "self-hosted"]'
setupfirebaseemulator: true
customcommand: "firebase emulators:exec 'fastlane test'"
secrets:
checkout_token: ${{ secrets.MUSE_PATH }}
uploadcoveragereport:
name: Upload Coverage Report
needs: buildandtest
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "Muse.framework"]
path = Muse.framework
url = [email protected]:StanfordBDHG/MuseSDK.git
5 changes: 5 additions & 0 deletions .gitmodules.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the Neurodevelopment Assessment and Monitoring System (NAMS) project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
1 change: 1 addition & 0 deletions Muse.framework
Submodule Muse.framework added at e437fa
1,248 changes: 1,066 additions & 182 deletions NAMS.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,15 @@
"version" : "0.4.2"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307",
"version" : "1.0.5"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
Expand Down
66 changes: 66 additions & 0 deletions NAMS.xcodeproj/xcshareddata/xcschemes/NAMS Muse.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A926D7642AB7A552000C4C2F"
BuildableName = "NAMS Muse.app"
BlueprintName = "NAMS Muse"
ReferencedContainer = "container:NAMS.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A926D7642AB7A552000C4C2F"
BuildableName = "NAMS Muse.app"
BlueprintName = "NAMS Muse"
ReferencedContainer = "container:NAMS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

This source file is part of the Stanford Spezi Template Application project

SPDX-FileCopyrightText: 2023 Stanford University

SPDX-License-Identifier: MIT
50 changes: 2 additions & 48 deletions NAMS.xcodeproj/xcshareddata/xcschemes/NAMS.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down Expand Up @@ -33,28 +32,6 @@
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "653A255C28338800005D4D48"
BuildableName = "NAMSTests.xctest"
BlueprintName = "NAMSTests"
ReferencedContainer = "container:NAMS.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "653A256628338800005D4D48"
BuildableName = "NAMSUITests.xctest"
BlueprintName = "NAMSUITests"
ReferencedContainer = "container:NAMS.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -76,28 +53,6 @@
ReferencedContainer = "container:NAMS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "--disableFirebase"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--testSchedule"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--showOnboarding"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--skipOnboarding"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--useFirebaseEmulator"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
Expand All @@ -109,16 +64,15 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "653A254C283387FE005D4D48"
BuildableName = "NAMS.app"
BlueprintName = "NAMS"
ReferencedContainer = "container:NAMS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
36 changes: 36 additions & 0 deletions NAMS/Bluetooth/BluetoothManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import CoreBluetooth


class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate {
private let bluetoothManager: CBCentralManager
private let dispatchQueue: DispatchQueue

@Published private(set) var bluetoothState: CBManagerState

override init() {
// We use a separate dispatch queue, be aware that all delegate calls are not getting on the main thread.
// So make sure to interact with @Published properties only via the main thread
self.dispatchQueue = DispatchQueue(label: "CBCentralManager")
self.bluetoothManager = CBCentralManager(delegate: nil, queue: dispatchQueue)
self.bluetoothState = bluetoothManager.state

super.init()

// CBCentralManager declares the delegate as weak
self.bluetoothManager.delegate = self // cannot use self before super.init()
}

func centralManagerDidUpdateState(_ central: CBCentralManager) {
Task { @MainActor in
self.bluetoothState = central.state
}
}
}
43 changes: 43 additions & 0 deletions NAMS/EEG/EEGChannelMark.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Charts
import SwiftUI


struct EEGChannelMark: ChartContent {
private let time: TimeInterval
private let reading: EEGReading

var body: some ChartContent {
LineMark(
x: .value("Seconds", time),
y: .value("Micro-Volt", reading.value)
)
.lineStyle(StrokeStyle(lineWidth: 2.0))
.foregroundStyle(by: .value("Channel", reading.channel.rawValue))
}


init(time: TimeInterval, reading: EEGReading) {
self.time = time
self.reading = reading
}
}


#if DEBUG
struct EEGChannelMark_Previews: PreviewProvider {
static let randomSamples = EEGMeasurementGenerator(sampleRate: 60)

static var previews: some View {
let generated = randomSamples.generateRecording(sampleTime: 5, recordingOffset: 10)
EEGChart(measurements: generated.data.suffix(from: 0), for: .af7, baseTime: generated.baseTime)
}
}
#endif
93 changes: 93 additions & 0 deletions NAMS/EEG/EEGChart.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Charts
import SwiftUI


struct EEGChart: View {
private let measurements: ArraySlice<EEGSeries>
private let channel: EEGChannel
/// The base time interval since 1970.
private let baseTime: TimeInterval?

private var lowerScale: TimeInterval {
if let first = measurements.first,
let baseTime {
return max(0, first.timestamp.timeIntervalSince1970 - baseTime)
}
return 0

Check warning on line 24 in NAMS/EEG/EEGChart.swift

View check run for this annotation

Codecov / codecov/patch

NAMS/EEG/EEGChart.swift#L24

Added line #L24 was not covered by tests
}

private var upperScale: TimeInterval {
if let last = measurements.last,
let baseTime {
return max(1, last.timestamp.timeIntervalSince1970 - baseTime)
}
return 1

Check warning on line 32 in NAMS/EEG/EEGChart.swift

View check run for this annotation

Codecov / codecov/patch

NAMS/EEG/EEGChart.swift#L32

Added line #L32 was not covered by tests
}

var body: some View {
Chart(measurements) { series in
// base time exists if there is at least one measurement
let baseTime = baseTime ?? 0

EEGChannelMark(time: max(0.0, series.timestamp.timeIntervalSince1970 - baseTime), reading: series.reading(for: channel))
}
.chartXScale(domain: lowerScale...upperScale)
.chartXAxis {
AxisMarks(values: .automatic(desiredCount: 12)) { value in
if let doubleValue = value.as(Double.self),
let intValue = value.as(Int.self) {
if doubleValue - Double(intValue) == 0 {
AxisTick(stroke: .init(lineWidth: 1))
.foregroundStyle(.gray)
AxisValueLabel {
Text("\(intValue)s")
}
AxisGridLine(stroke: .init(lineWidth: 1))
.foregroundStyle(.gray)
} else {
AxisGridLine(stroke: .init(lineWidth: 1))
.foregroundStyle(.gray.opacity(0.25))
}
}
}
}
.chartYAxis {
AxisMarks(values: .automatic(desiredCount: 14)) { _ in
AxisGridLine(stroke: .init(lineWidth: 1))
.foregroundStyle(.gray.opacity(0.25))
}
}
.chartPlotStyle {
$0.border(Color.gray)
}
.frame(height: 200)
}


init(measurements: ArraySlice<EEGSeries>, for channel: EEGChannel, baseTime: TimeInterval?) {
self.measurements = measurements
self.channel = channel
self.baseTime = baseTime
}
}


#if DEBUG
struct EEGChart_Previews: PreviewProvider {
static let randomSamples = EEGMeasurementGenerator(sampleRate: 60)

static var previews: some View {
let generated = randomSamples.generateRecording(sampleTime: 5, recordingOffset: 10)
EEGChart(measurements: generated.data.suffix(from: 0), for: .af7, baseTime: generated.baseTime)
EEGChart(measurements: generated.data.suffix(from: 0), for: .af8, baseTime: generated.baseTime)
}
}
#endif
Loading