Skip to content

Commit

Permalink
Allow to change hardware sample rate for Biopot
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Apr 9, 2024
1 parent ecd774e commit 1a8b0e0
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 19 deletions.
1 change: 1 addition & 0 deletions NAMS/Devices/Biopot/BiopotDevice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ extension BiopotDevice {
temperatureValue: 23,
batteryCharging: true
))
biopot.service.$samplingConfiguration.inject(SamplingConfiguration())
biopot.deviceInformation.$firmwareRevision.inject("1.2.3")
biopot.deviceInformation.$serialNumber.inject(serial)
biopot.deviceInformation.$hardwareRevision.inject("3.1")
Expand Down
13 changes: 13 additions & 0 deletions NAMS/Devices/Biopot/BiopotService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ class BiopotService: BluetoothService {
}
}

@EEGProcessing
func updateSamplingConfiguration<Value>(set keyPath: WritableKeyPath<SamplingConfiguration, Value>, to value: Value) async throws {
var configuration: SamplingConfiguration
if let samplingConfiguration {
configuration = samplingConfiguration
} else {
configuration = try await $samplingConfiguration.read()
}

configuration[keyPath: keyPath] = value
try await $samplingConfiguration.write(configuration)
}


func handleDataAcquisition(data: Data) {
guard let deviceConfiguration = deviceConfiguration,
Expand Down
16 changes: 9 additions & 7 deletions NAMS/Devices/Biopot/Characteristics/SamplingConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import NIOCore


struct SamplingConfiguration {
static let supportedSamplingRates: [UInt16] = [250, 500, 1000, 2000]

/// Channel off/on bits. Only used with 16-bit. Not used with 24-bit configuration.
let channelsBitMask: UInt32
var channelsBitMask: UInt32
/// Hardware low-pass filter. Not used with 24-bit configuration.
let lowPassFilter: LowPassFilter
var lowPassFilter: LowPassFilter
/// High pass filter (Hardware for 16-bit configuration; Software for 24-bit configuration)
let highPassFilter: HighPassFilter
var highPassFilter: HighPassFilter
/// 500 (default) / 1000 / 2000
let hardwareSamplingRate: UInt16
var hardwareSamplingRate: UInt16
/// Not used with 24 bits.
let impedanceFrequency: UInt8
let impedanceScale: UInt8
let softwareLowPassFilter: SoftwareLowPassFilter
var impedanceFrequency: UInt8
var impedanceScale: UInt8
var softwareLowPassFilter: SoftwareLowPassFilter


init(
Expand Down
44 changes: 44 additions & 0 deletions NAMS/Devices/Biopot/Views/BiopotDeviceDetailsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ struct BiopotDeviceDetailsView: View {
@Environment(\.dismiss)
private var dismiss

@State private var viewState: ViewState = .idle

@State private var samplingConfigurationPresent = false
@State private var selectedSamplingConfiguration: UInt16 = 250

var hasAboutInformation: Bool {
let deviceInformation = biopot.deviceInformation
return deviceInformation.firmwareRevision != nil
Expand Down Expand Up @@ -48,6 +53,14 @@ struct BiopotDeviceDetailsView: View {
}

Section {
if samplingConfigurationPresent {
Picker("Samplerate", selection: $selectedSamplingConfiguration) {
ForEach(SamplingConfiguration.supportedSamplingRates, id: \.self) { samplerate in
Text(verbatim: "\(samplerate) Hz")
.tag(samplerate)
}
}
}
NavigationLink {
BiopotElectrodeLocationsEditView(biopot: biopot)
} label: {
Expand Down Expand Up @@ -84,11 +97,42 @@ struct BiopotDeviceDetailsView: View {
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(title)
.viewStateAlert(state: $viewState)
.onChange(of: biopot.state) {
if isDisconnected {
dismiss()
}
}
.onChange(of: biopot.service.samplingConfiguration?.hardwareSamplingRate, initial: true) {
if let samplerate = biopot.service.samplingConfiguration?.hardwareSamplingRate {
samplingConfigurationPresent = true
selectedSamplingConfiguration = samplerate
} else {
samplingConfigurationPresent = false
}
}
.onChange(of: selectedSamplingConfiguration) {
guard let originalRate = biopot.service.samplingConfiguration?.hardwareSamplingRate else {
return // wasn't available
}

let selectedSamplingConfiguration = selectedSamplingConfiguration
guard selectedSamplingConfiguration != originalRate else {
return // its the same, no reason to update
}

Task {
do {
try await biopot.service.updateSamplingConfiguration(set: \.hardwareSamplingRate, to: selectedSamplingConfiguration)
} catch {
viewState = .error(AnyLocalizedError(
error: error,
defaultErrorDescription: "Failed to update hardware sampling rate."
))
self.selectedSamplingConfiguration = originalRate // reset back to original rate
}
}
}
}

@ViewBuilder var disconnectButton: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct BiopotElectrodeLocationsEditView: View {
}
.pickerStyle(.inline)
} footer: {
Text("You can select a predefined electrode assignment or provide a custom one.")
Text("You can select one of the predefined electrode assignment or provide a custom one.")
}

Section("Assignment") {
Expand Down
2 changes: 1 addition & 1 deletion NAMS/EEG/EEGRecordingSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class EEGRecordingSession {
}

let visualizedSignals = signals.map { signal in
let sampleRate = signal.sampleCount / fileInformation.recordDuration
let sampleRate = signal.sampleCount / Int(fileInformation.recordDuration) // TODO: review conversion to Int (builds upon assumptions)
let batching: BatchingConfiguration? = .downsample(targetSampleRate: Self.uiTargetSampleRate, sourceSampleRate: sampleRate)
return VisualizedSignal(label: signal.label, sourceSampleRate: sampleRate, batching: batching)
}
Expand Down
11 changes: 3 additions & 8 deletions NAMS/EEG/Views/Chart/ChangeChartLayoutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import SwiftUI
import SpeziViews


struct ChangeChartLayoutView: View {
Expand Down Expand Up @@ -50,23 +51,17 @@ struct ChangeChartLayoutView: View {
in: 4.0...15.0,
step: 0.5
) {
HStack {
Text("Display Interval")
Spacer()
ListRow("Display Interval") {
Text(verbatim: "\(String(format: "%.1f", displayInterval))s")
.foregroundColor(.secondary)
}
}
Stepper(
value: $valueInterval,
in: 10...2000,
step: intervalStepValue
) {
HStack {
Text("Value Interval")
Spacer()
ListRow("Value Interval") {
Text(verbatim: "\(valueInterval)uV")
.foregroundColor(.secondary)
}
}
.onChange(of: valueInterval) { oldValue, newValue in
Expand Down
7 changes: 5 additions & 2 deletions NAMS/EEG/Views/EEGRecordingSessionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ struct EEGRecordingSessionView: View {
}
.interactiveDismissDisabled() // TODO: support cancellation?
.toolbar {
ToolbarItem(placement: .secondaryAction) {
ToolbarItemGroup(placement: .secondaryAction) {
Button("Edit Chart Layout", systemImage: "pencil") {
presentingChartControls = true
}
Button("More Information", systemImage: "info.square") {
// TODO: add button to view more details (e.g., current samples per second average, etc).
}
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
Expand All @@ -91,7 +94,7 @@ struct EEGRecordingSessionView: View {
.frame(minWidth: 450, minHeight: 250) // frame for the popover
}
}
.confirmationDialog("Do you want to cancel the ongoing recording?", isPresented: $presentingChartControls, titleVisibility: .visible) {
.confirmationDialog("Do you want to cancel the ongoing recording?", isPresented: $presentingCancelConfirmation, titleVisibility: .visible) {
Button("Cancel Recording", role: .destructive) {
dismiss()
}
Expand Down

0 comments on commit 1a8b0e0

Please sign in to comment.