Select Git revision
StartingView.swift
StartingView.swift 15.44 KiB
//
// startingView.swift
// sensorDataCollectorApp
//
// Created by Miklósi Máté on 20/01/2024.
import Foundation
import SwiftUI
import BackgroundTasks
import CoreData
import CoreMotion
import UniformTypeIdentifiers
import MobileCoreServices
import SensorKit
import Combine
import Network
struct StartingView: View {
//----------SERVER CONNECTION SETTINGS AND VARS -----------------------------------------------
@State private var isServerSettingsSheetPresented = false
@StateObject private var manager = ServerCommunicationManager()
@State private var iterationCount = 0
@State var displayText = ""
@State var first_open = true
@State private var shouldStartRecording = false
//----------TIMER SETUP SETTINGS AND VARS -----------------------------------------------------
@StateObject var customTimer : CustomTimer
private let backgroundTaskIdentifier = "hu.itk.ppke.backgroundTask"
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
//----------MEASURMENT SETUP AND VARS ---------------------------------------------------------
@State var demostart = false
@State var sensorDetailSheetPresented = false
@State var showingFilePicker = false
@State var selectedFolder: URL?
@State var dataColl = false
@StateObject var sensor = SensorCollector(activeSensorList: [])
let motionManager = CMMotionManager()
let altimeter = CMAltimeter()
@State var fileLocations: [URL] = []
let activityManager = CMMotionActivityManager()
@State private var wifiName: String = "Not Available"
@State var debug = true
var body: some View {
NavigationView {
VStack {
Spacer()
//----------------------------- DEBUG VALUE DISPAY ---------------------------------
Text("Notification list: \(manager.timerIntervalValues.map { "\($0)" }.joined(separator: ", "))").opacity(debug ? 0 : 1)
Text("Next notification in: \(customTimer.timerStringValue)").opacity(debug ? 0 : 1)
Text("\(manager.serverResponse)")
.padding().opacity(debug ? 0 : 1)
//----------------------------- STARTING - STOPPING THE ITERATION CYCL ---------------
Button("Set next"){
setNextMeasurement()
}.opacity(debug ? 0 : 1)
Button("Start Iterations") {
setNextMeasurement()
sensor.startAllRecording(motionManager: motionManager, altimeter: altimeter)
customTimer.startTimer()
}.padding().disabled(manager.state != "Ready").opacity(debug ? 0 : 1)
// Button("STOP Iterations") {
// customTimer.stopTimer()
// sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter)
//
// }.padding().opacity(debug ? 0 : 1)
Text("\(manager.numberOfMeasurements - manager.timerIntervalValues.count >= 0 ? "\(manager.numberOfMeasurements - manager.timerIntervalValues.count)/\(manager.numberOfMeasurements)" : "Not initialised")")
.padding()
ForEach($sensor.activeSensorsList, id: \.self) { $list in
HStack{
Text(list.sensorName)
Text(String(list.sensorData.count))
NavigationLink(
destination: SensorDetailView(
x: $list.sensorData.last?.1.x ?? .constant(0.0),
y: $list.sensorData.last?.1.y ?? .constant(0.0),
z: $list.sensorData.last?.1.z ?? .constant(0.0), sensorName: $list.sensorName ),
label: { Text("Details") }
)
}
}.opacity(debug ? 0 : 1)
Button("CreateDIR"){
createDirectory()
}.padding().opacity(debug ? 0 : 1)
Button("Select destination") {
showingFilePicker = true
}.padding()
.fileImporter(isPresented: $showingFilePicker, allowedContentTypes: [.folder], allowsMultipleSelection: false) { result in
// Handle the user-selected file or folder
switch result {
case .success(let urls):
// Check if any URLs were selected
if let url = urls.first {
selectedFolder = url // Store the selected folder in selectedFolder
print(url)
}
case .failure(let error):
print("Error selecting folder: \(error.localizedDescription)")
}
}.opacity(debug ? 0 : 1)
Button("Save All to JSON") {
saveAllToJson()
}
.disabled(selectedFolder == nil)
.opacity(debug ? 0 : 1)
Spacer()
Button(action: {
debug.toggle() // Toggle debug mode
}) {
Text("Debug Mode")
.padding(5)
.background(
RoundedRectangle(cornerRadius: 5)
.foregroundColor(!debug ? Color.red : Color.yellow)
)
.foregroundColor(.white)
}
}
//----------------------------- TITLE ---------------------------------
.navigationBarTitle("Starting View", displayMode: .inline)
//----------------------------- DEVICE ID ---------------------------------
.navigationBarItems(
leading: Button(action: {
sensorDetailSheetPresented = true
}) {
Text("\(manager.deviceID)")
}
)
//----------------------------- STATUS INDICATOR ---------------------------------
.navigationBarItems(
trailing:
Button(action: {
isServerSettingsSheetPresented = true
}) {
Text("\(manager.state)")
.padding(.horizontal, 10)
.background(
getColorForStatus(manager.state)
.cornerRadius(5)
)
.foregroundColor(.white)
}
)
//----------------------------- STATUS INDICATOR SHEETS ---------------------------------
.sheet(isPresented: $isServerSettingsSheetPresented, content: {
ServerConnectionView(
manager: manager
)
})
.sheet(isPresented: $sensorDetailSheetPresented, content: {
VStack{
ForEach($sensor.activeSensorsList, id: \.self) { $list in
HStack{
Text(list.sensorName)
Spacer()
Text(String(list.sensorData.count))
NavigationLink(
destination: SensorDetailView(
x: $list.sensorData.last?.1.x ?? .constant(0.0),
y: $list.sensorData.last?.1.y ?? .constant(0.0),
z: $list.sensorData.last?.1.z ?? .constant(0.0), sensorName: $list.sensorName ),
label: { Text("Details") }
)
}
}
}.padding()
})
//---------------------------------- FIRST STARTUP ----------------------------------------
.sheet(isPresented: $first_open, content : {
Button {
if manager.state != "Ready" {
manager.sendMessage(type: "init")
}
if manager.state == "Ready" {
createDirectory()
first_open = false
demostart = true
}
} label: {
Text(manager.state != "Ready" ? "Get started" : manager.state)
.padding(.horizontal, 10)
.background(
getColorForStatus(manager.state)
.cornerRadius(5)
)
.foregroundColor(.white)
}
//
Text(manager.serverResponse == "Basic response" || manager.serverResponse == "OK" ? "" : manager.serverResponse)
Text(manager.state != "Ready" ? "Click the button to connect to the server " : "Click the button to start the measurements")
Text("Status: \(manager.state)").padding().opacity(manager.state == "Ready" ? 0 : 1)
HStack{
TextField("Enter Host", text: $manager.hostName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.vertical)
Text(":")
TextField("Enter Port Number", text: $manager.portNumber)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.vertical)
}.padding().opacity(manager.state == "Ready" ? 0 : 1)
})
//----------------------------- TIMER REFRESH ---------------------------------
.onReceive(timer){ _ in
if customTimer.isStarted{
customTimer.updateTimer()
print("Timer: \(customTimer.totalSeconds)")
// if customTimer.totalSeconds == 2 {
// shouldStartRecording = true
// }
}
}
// .onReceive(Just(shouldStartRecording)) { _ in
// if shouldStartRecording {
// print("Starting measurements")
// sensor.startAllRecording(motionManager: motionManager, altimeter: altimeter)
// shouldStartRecording = false
// }
// }
//----------------------------- NOTIFICAION HANDELING ---------------------------------
.alert("\(manager.numberOfMeasurements - manager.timerIntervalValues.count >= -1 ? "\(manager.numberOfMeasurements - manager.timerIntervalValues.count)/\(manager.numberOfMeasurements)" : "Not initialised") Measurement", isPresented: $customTimer.isFinished) {
Button(manager.timerIntervalValues.count == 0 ? "End" : "Next" ,role: .cancel){
customTimer.stopTimer()
sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter)
saveAllToJson()
setNextMeasurement()
if manager.timerIntervalValues.count != 0 {
sensor.startAllRecording(motionManager: motionManager, altimeter: altimeter)
customTimer.startTimer()
}
}
Button("Stop", role: .destructive){
customTimer.stopTimer()
sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter)
saveAllToJson()
}
}
//------------------ DEMO NOTIFICATION - MEASUREMENT STARTING ----------------------
.alert("Demo Alert - press start if you are ready to start!", isPresented: $demostart){
Button("Start",role: .cancel){
customTimer.stopTimer()
sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter)
setNextMeasurement()
if customTimer.totalSeconds != 0 {
sensor.startAllRecording(motionManager: motionManager, altimeter: altimeter)
customTimer.startTimer()
}
}
Button("Stop", role: .destructive){
first_open = true
}
}
}
}
func saveAllToJson(){
do {
let now = Date()
let timeInMilliseconds = Int(now.timeIntervalSince1970 * 1000)
if let selectedFolder = selectedFolder {
try sensor.exportAllSensorToJson(fileLocations: &fileLocations, filename_prefix: String(iterationCount) + String(timeInMilliseconds), fileLocationURL: selectedFolder, deviceID: manager.deviceID, manager: manager)
} else {
print("Please select a folder to save the JSON file.")
}
} catch {
print("Error exporting to JSON: \(error.localizedDescription)")
}
// print("File locations: \(fileLocations)")
}
//----------------------------- JSON LOCATION DIR CREATION ------------------------------------
func createDirectory() {
if manager.state == "Ready" {
let fileManager = FileManager.default
do {
let documentsDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let directoryURL = documentsDirectory.appendingPathComponent(manager.deviceID)
if !fileManager.fileExists(atPath: directoryURL.path) {
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
print("Directory created at: \(directoryURL)")
selectedFolder = directoryURL
} else {
print("Directory already exists at: \(directoryURL)")
}
} catch {
print("Error creating directory: \(error)")
}
}
}
//----------------------------- STATUS INDICATOR COLOR PICKER ---------------------------------
private func getColorForStatus(_ status: String) -> Color {
switch status {
case "Ready":
return .green
case "NoConnection":
return .red
default:
return .yellow
}
}
//----------------------------- SETTING UP NEXT "TIME" FOR TIMER ---------------------------------
private func setNextMeasurement(){
if !manager.timerIntervalValues.isEmpty {
customTimer.seconds = manager.timerIntervalValues.removeFirst()
customTimer.minutes = 0
} else {
customTimer.seconds = 00
customTimer.minutes = 00
}
customTimer.refreshTimer()
}
}
struct StartingView_Previews: PreviewProvider {
static var previews: some View {
StartingView( customTimer: CustomTimer())
}
}