Hello everyone,
I am new to Swift, it is my first project and I am a PhD Electrical Engineer student.
I am designing an iOS app for a device that we are designing that is capable of reading electrical brain data and sending them via BLE with a sampling frequency of 2400 Hz.
I created a Bluetooth service for the Swift app that every time it receives new data, processes it to split the different channels and add the new data to the Charts data arrays. Here is the code I've designed:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == Nordic_UART_TX_CHAR_UUID {
guard error == nil, let data = characteristic.value else {
print("[Bluetooth] Error receiving data or no data: \(error?.localizedDescription ?? "Unknown Error")")
return
}
DispatchQueue.global(qos: .background).async {
self.processReceivedData(data)
}
}
}
func processReceivedData(_ data: Data) {
var batch = [(Int, Int)]()
for i in stride(from: 0, to: data.count - 4, by: 4) {
let channel = Int(data[i] & 0xFF)
let value = Int((Int(data[i + 3] & 0xFF) << 16) | (Int(data[i + 2] & 0xFF) << 8) | (Int(data[i + 1] & 0xFF))) - 8388608
batch.append((channel, value))
}
DispatchQueue.main.async {
for (channel, value) in batch {
let nowTime = (Date().timeIntervalSince1970 - self.dataGraphService.startTime)
let newDataPoint = DataGraphService.VoltagePerTime(time: nowTime, voltage: Double(value)/8388608, channel: "Channel \(channel - 15)")
if channel == 16 {
self.dataGraphService.lastX1 = nowTime
self.dataGraphService.dataCh1.append(newDataPoint)
} else if channel == 17 {
self.dataGraphService.lastX2 = nowTime
self.dataGraphService.dataCh2.append(newDataPoint)
} else if channel == 18 {
self.dataGraphService.lastX3 = nowTime
self.dataGraphService.dataCh3.append(newDataPoint)
} else if channel == 19 {
self.dataGraphService.lastX4 = nowTime
self.dataGraphService.dataCh4.append(newDataPoint)
}
}
}
}
// DataGraphService.swift
struct VoltagePerTime {
var time: Double
var voltage: Double
var channel: String
}
@Published var dataCh1: [VoltagePerTime] = []
@Published var dataCh2: [VoltagePerTime] = []
@Published var dataCh3: [VoltagePerTime] = []
@Published var dataCh4: [VoltagePerTime] = []
@Published var windowSize: Double = 2.0
@Published var lastX1: Double = 0
@Published var lastX2: Double = 0
@Published var lastX3: Double = 0
@Published var lastX4: Double = 0
I also created a View that shows the real-time data from the different channels.
ChartView(
data: dataGraphService.dataCh1.filter {
dataGraphService.getXAxisRange(for: dataGraphService.dataCh1, windowSize: dataGraphService.windowSize).contains($0.time)
},
xAxisRange: dataGraphService.getXAxisRange(for: dataGraphService.dataCh1, windowSize: dataGraphService.windowSize),
channel: "Channel 1",
windowSize: dataGraphService.windowSize
)
// ChartView.swift
import SwiftUI
import Charts
struct ChartView: View {
var data: [DataGraphService.VoltagePerTime]
var xAxisRange: ClosedRange<Double>
var channel: String
var windowSize: Double
var body: some View {
RoundedRectangle(cornerRadius: 10)
.fill(Color.gray.opacity(0.1))
.overlay(
VStack{
Text("\(channel)")
.foregroundColor(Color.gray)
.font(.system(size: 16, weight: .semibold))
Chart(data, id: \.time) { item in
LineMark(
x: .value("Time [s]", item.time),
y: .value("Voltage [V]", item.voltage)
)
}
.chartYAxisLabel(position: .leading) {
Text("Voltage [V]")
}
.chartYScale(domain: [-1.6, 1.6])
.chartYAxis {
AxisMarks(position: .leading, values: [-1.6, -0.8, 0, 0.8, 1.6])
AxisMarks(values: [-1.6, -1.2, -0.8, -0.4, 0, 0.4, 0.8, 1.2, 1.6]) {
AxisGridLine()
}
}
.chartXAxisLabel(position: .bottom, alignment: .center) {
Text("Time [s]")
}
.chartXScale(domain: xAxisRange)
.chartXAxis {
AxisMarks(values: .automatic(desiredCount: Int(windowSize)*2))
AxisMarks(values: .automatic(desiredCount: 4*Int(windowSize)-2)) {
AxisGridLine()
}
}
.padding(5)
}
)
.padding(2.5)
.padding([.leading, .trailing], 5)
}
}
With these code I can receive and plot the data in real-time but after some time the CPU of the iPhone gets saturated and the app stop working. I have the guess that the code is designed in a way that the functions are called one inside the other one in a very fast speed that the CPU cannot handle.
My doubt is if there is any other way to code this real-time plotting actions without make the iPhone's CPU power hungry.
Thank you very much for your help!