From 1615a738f86006123c78790eb5296aa8182bdfd3 Mon Sep 17 00:00:00 2001 From: miklosimate <megykereku@gmail.com> Date: Mon, 11 Mar 2024 00:20:33 +0100 Subject: [PATCH] File transfer working, cleanup needed --- SenzorDataCollectorApp/ContentView.swift | 18 +-- SenzorDataCollectorApp/ExportToJson.swift | 39 +++--- SenzorDataCollectorApp/Sensors.swift | 4 +- .../ServerCommunicationManager.swift | 64 ++++----- .../ServerConnectionView.swift | 126 ----------------- SenzorDataCollectorApp/StartingView.swift | 127 +++++++++++++----- 6 files changed, 152 insertions(+), 226 deletions(-) diff --git a/SenzorDataCollectorApp/ContentView.swift b/SenzorDataCollectorApp/ContentView.swift index 32b1acc..a3e055a 100644 --- a/SenzorDataCollectorApp/ContentView.swift +++ b/SenzorDataCollectorApp/ContentView.swift @@ -13,7 +13,7 @@ import MobileCoreServices import SensorKit struct ContentView: View { - + let manager = ServerCommunicationManager() //to be able to choose file location @State var showingFilePicker = false // The URL of the selected file @@ -33,7 +33,7 @@ struct ContentView: View { @State var dataColl = false @StateObject var sensor = SensorCollector(activeSensorList: []) - @State var fileLocations: [String] = [] + @State var fileLocations: [URL] = [] @State var MeasurementID: Int @ObservedObject var notificationManager = NotificationManager() @@ -139,10 +139,10 @@ struct ContentView: View { do { //calculate timestamps: let now = Date() - let timeInMiliseconds = Int(now.timeIntervalSince1970 * 1000) + let timeInMilliseconds = Int(now.timeIntervalSince1970 * 1000) if let selectedFolder = selectedFolder { - try sensor.exportAllSensorToJson(fileLocations: &fileLocations, filename_prefix: String(MeasurementID) + String(timeInMiliseconds), fileLocationURL: selectedFolder, deviceID: "-") + try sensor.exportAllSensorToJson(fileLocations: &fileLocations, filename_prefix: String(MeasurementID) + String(timeInMilliseconds), fileLocationURL: selectedFolder, deviceID: "-", manager: manager) } else { print("Please select a folder to save the JSON file.") } @@ -152,11 +152,11 @@ struct ContentView: View { } .disabled(selectedFolder == nil) ScrollView{ - VStack{ - ForEach(fileLocations, id: \.self) { location in - Text(location) - } - } +// VStack{ +// ForEach(fileLocations, id: \.self) { location in +// Text(location.absoluteString) // Convert URL to String +// } +// } } .frame(maxHeight: 200) }.onAppear{ diff --git a/SenzorDataCollectorApp/ExportToJson.swift b/SenzorDataCollectorApp/ExportToJson.swift index 33ffd84..88a26e9 100644 --- a/SenzorDataCollectorApp/ExportToJson.swift +++ b/SenzorDataCollectorApp/ExportToJson.swift @@ -11,20 +11,20 @@ import SwiftUI import Foundation import UniformTypeIdentifiers import UIKit - //calls crate jsonfile to the specific sensor - sensorData - has to be the name of the list where the data is stored //called by Sensors -> exportAllSensorToJson -func exportToJSON(fileLocations: inout [String], +func exportToJSON(fileLocations: inout [URL], sensorData: [(timeInMiliSec: Int, (x: Double, y: Double, z: Double))], fileName: String, sensorName: String, deviceID: String, - fileLocationURL: URL) throws { - try fileLocations.append(createJSONFile(FileName: fileName, SensorName: sensorName, Data: sensorData,fileLocationURL: fileLocationURL, deviceID: deviceID)) + fileLocationURL: URL, + manager: ServerCommunicationManager) throws { + try fileLocations.append(createJSONFile(FileName: fileName, SensorName: sensorName, Data: sensorData,fileLocationURL: fileLocationURL, deviceID: deviceID, manager: manager)) } //create the json file -func createJSONFile(FileName: String, SensorName: String, Data: [(timeInMiliSec: Int, (x: Double, y: Double, z: Double))], fileLocationURL: URL, deviceID: String) throws -> String { +func createJSONFile(FileName: String, SensorName: String, Data: [(timeInMiliSec: Int, (x: Double, y: Double, z: Double))], fileLocationURL: URL, deviceID: String, manager: ServerCommunicationManager) throws -> URL { let jsonData: [[String: Any]] = Data.map { tuple in return [ "timestamp": tuple.0, @@ -39,17 +39,28 @@ func createJSONFile(FileName: String, SensorName: String, Data: [(timeInMiliSec: ] let jsonDataObj = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) - + let jsonString = String(data: jsonDataObj, encoding: .utf8)! // Request permission to access the file URL - + var fileURL: URL do { try fileLocationURL.startAccessingSecurityScopedResource() // Now you can access the file at `selectedFileURL`. - let fileURL = fileLocationURL.appendingPathComponent("\(FileName).json") + fileURL = fileLocationURL.appendingPathComponent("\(FileName).json") try jsonDataObj.write(to: fileURL, options: .atomic) + //Server file sending +// manager.sendMessage(type: "json", filename: FileName, jsonData: jsonString) +// print("Export to Json called manager Json message sent: filename \(FileName), JsonData: \(jsonData)") + + do { + let jsonData = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) + manager.uploadJsonFile(deviceID: deviceID, jsonData: jsonData, filename: FileName) + } catch { + print("Error converting JSON data: \(error)") + } + fileLocationURL.stopAccessingSecurityScopedResource() } catch { print("Error accessing file: \(error)") @@ -57,14 +68,6 @@ func createJSONFile(FileName: String, SensorName: String, Data: [(timeInMiliSec: fileLocationURL.stopAccessingSecurityScopedResource() - return fileLocationURL.absoluteString -} - -func returnMeasurementJson() -> [String]{ - //TODO - let now = Date() - let timeInMiliSec = Int(now.timeIntervalSince1970 * 1000) - - let returnVal = ["{}"] - return returnVal + //return fileLocationURL.absoluteString + return fileURL } diff --git a/SenzorDataCollectorApp/Sensors.swift b/SenzorDataCollectorApp/Sensors.swift index e0972a1..e4e8c5b 100644 --- a/SenzorDataCollectorApp/Sensors.swift +++ b/SenzorDataCollectorApp/Sensors.swift @@ -224,11 +224,11 @@ class SensorCollector: ObservableObject { // ------------------------------------------------ J S O N E X P O R T -------------------------------------------- // only needed, to avoid passing the active sensor list to the exporter file //filename prefix is the timestamp and the ID of the measurement - func exportAllSensorToJson(fileLocations: inout [String], filename_prefix: String, fileLocationURL: URL, deviceID: String)throws{ + func exportAllSensorToJson(fileLocations: inout [URL], filename_prefix: String, fileLocationURL: URL, deviceID: String, manager: ServerCommunicationManager)throws{ //export all one-by-one for sens in activeSensorsList { do { - try exportToJSON(fileLocations: &fileLocations, sensorData: sens.sensorData, fileName: filename_prefix + sens.sensorName, sensorName: sens.sensorName, deviceID: deviceID, fileLocationURL: fileLocationURL) + try exportToJSON(fileLocations: &fileLocations, sensorData: sens.sensorData, fileName: filename_prefix + sens.sensorName, sensorName: sens.sensorName, deviceID: deviceID, fileLocationURL: fileLocationURL, manager: manager) } catch { print("Error exporting \(sens.sensorName) data to JSON: \(error.localizedDescription)") diff --git a/SenzorDataCollectorApp/ServerCommunicationManager.swift b/SenzorDataCollectorApp/ServerCommunicationManager.swift index 4d18116..bd804d3 100644 --- a/SenzorDataCollectorApp/ServerCommunicationManager.swift +++ b/SenzorDataCollectorApp/ServerCommunicationManager.swift @@ -6,8 +6,6 @@ // // ServerCommunicationManager.swift -// ServerCommunicationManager.swift - import Foundation class ServerCommunicationManager: ObservableObject { @@ -24,48 +22,45 @@ class ServerCommunicationManager: ObservableObject { @Published var state = "NoConnection" var portNumber = "3000" var current_cnt = 0 - - - func sendJson(type: String){ - //TODO - sendMessage(type: "JsonData") + //---------- SENDING JSON TO SERVER ----------------------------------------------- + func uploadJsonFile(deviceID: String, jsonData: Data, filename: String) { + guard let url = URL(string: "http://\(hostName):\(portNumber)/json/\(deviceID)/\(filename)") else { + self.serverResponse = "Invalid URL" + return + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = jsonData + + let session = URLSession.shared + let task = session.dataTask(with: request) { data, response, error in + if let error = error { + self.serverResponse = "Error: \(error.localizedDescription)" + } else if let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) { + self.serverResponse = "File uploaded successfully" + } else { + self.serverResponse = "Error uploading file" + } + } + task.resume() } - - func sendMessage( - type: String - ) { + + func sendMessage(type: String, filename: String="", jsonData: String = "") { guard let port = Int(portNumber), !hostName.isEmpty else { serverResponse = "Invalid Port Number or Host" return } - + var urlString = "http://\(hostName):\(port)/\(type)" - + //It this is not an init message, we want to be able to tell what device does the message comes from if type != "init", deviceID == deviceID { urlString += "/\(deviceID)" } - var jsonBody = ["string" : 0] - - if type == "stillHere" { - current_cnt += 1 - jsonBody = [ - "NumberOfMeasurements": numberOfMeasurements, - "current": current_cnt - ] - } - - if type == "JsonData"{ - //TODO - } - guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonBody) else { - serverResponse = "Error creating JSON body" - return - } - var request = URLRequest(url: URL(string: urlString)!) request.httpMethod = "POST" - request.httpBody = jsonData URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { @@ -128,10 +123,6 @@ class ServerCommunicationManager: ObservableObject { } } - if type == "stillHere" { - - } - } catch { self.serverResponse = "Error decoding JSON response: \(error.localizedDescription)" } @@ -166,7 +157,6 @@ class ServerCommunicationManager: ObservableObject { } func populateTimerList(){ - timerIntervalValues.removeAll() for _ in 0..<self.numberOfMeasurements { let randomDelay = Int.random(in: 0...self.delay) diff --git a/SenzorDataCollectorApp/ServerConnectionView.swift b/SenzorDataCollectorApp/ServerConnectionView.swift index a47d0e2..ff1ca1e 100644 --- a/SenzorDataCollectorApp/ServerConnectionView.swift +++ b/SenzorDataCollectorApp/ServerConnectionView.swift @@ -44,12 +44,6 @@ struct ServerConnectionView: View { manager.sendMessage(type: "init") } .padding() - - Button("Am I OK?") { - manager.sendMessage(type: "am-i-ok") - } - .padding() - .disabled(!manager.isAmIOkButtonEnabled) } Text("Server Response: \(manager.serverResponse)") @@ -60,126 +54,6 @@ struct ServerConnectionView: View { .navigationBarTitle("Server connection", displayMode: .inline) } } - -// public func sendMessage(type: String) { -// guard let port = Int(portNumber), !hostName.isEmpty else { -// serverResponse = "Invalid Port Number or Host" -// return -// } -// -// var urlString = "http://\(hostName):\(port)/\(type)" -// -// if type != "init", deviceID == deviceID { -// urlString += "/\(deviceID)" -// } -// -// let jsonBody: [String: Any] = [ -// "NumberOfMeasurements": numberOfMeasurements, -// "Intervals": intervals, -// "Delay": delay -// ] -// -// guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonBody) else { -// serverResponse = "Error creating JSON body" -// return -// } -// -// var request = URLRequest(url: URL(string: urlString)!) -// request.httpMethod = "POST" -// request.httpBody = jsonData -// -// URLSession.shared.dataTask(with: request) { data, response, error in -// if let error = error { -// self.serverResponse = "Error: \(error.localizedDescription)" -// } else if let data = data { -// if let responseString = String(data: data, encoding: .utf8) { -// DispatchQueue.main.async { -// self.handleResponse(responseString, type: type) -// } -// } -// } -// }.resume() -// } -// -// private func handleResponse(_ response: String?, type: String) { -// guard let response = response else { -// self.serverResponse = "Empty response" -// return -// } -// -// do { -// if type == "am-i-ok" { -// serverResponse = "init response: \(response)" -// if let data = response.data(using: .utf8), -// let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], -// let error = json["error"] as? String { -// -// if error.isEmpty { -// serverResponse = "OK" -// sendMessage(type: "Ready") -// state = "Ready" -// } else { -// serverResponse = "am i ok response with error: \(error)" -// } -// } else { -// serverResponse = "Invalid JSON format in am-i-ok response" -// } -// } -// -// if type == "init" { -// serverResponse = "init response: \(response)" -// if let data = response.data(using: .utf8), -// let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], -// let error = json["error"] as? String { -// -// if error.isEmpty { -// serverResponse = "Init response without error" -// if let deviceID = json["deviceID"] as? String { -// self.deviceID = deviceID -// } -// -// if let serverConfig = json["serverConfig"] as? [String: String] { -// updateServerConfig(serverConfig) -// } -// } else { -// serverResponse = "Init response with error: \(error)" -// } -// } else { -// serverResponse = "Invalid JSON format in init response" -// } -// } -// } catch { -// self.serverResponse = "Error decoding JSON response: \(error.localizedDescription)" -// } -// } -// -// private func updateServerConfig(_ config: [String: String]) { -// if let numberOfMeasurementsStr = config["NumberOfMeasurements"], -// let numberOfMeasurements = Int(numberOfMeasurementsStr) { -// self.numberOfMeasurements = numberOfMeasurements -// } else { -// serverResponse = "Invalid value for NumberOfMeasurements" -// } -// -// if let intervalsStr = config["Intervals"], -// let intervals = Int(intervalsStr) { -// self.intervals = intervals -// } else { -// serverResponse = "Invalid value for Intervals" -// } -// -// if let delayStr = config["Delay"], -// let delay = Int(delayStr) { -// self.delay = delay -// } else { -// serverResponse = "Invalid value for Delay" -// } -// -// isAmIOkButtonEnabled = true -// sendMessage(type: "am-i-ok") -// state = "Init" -// } - } struct ServerConnectionView_Previews: PreviewProvider { diff --git a/SenzorDataCollectorApp/StartingView.swift b/SenzorDataCollectorApp/StartingView.swift index 4627712..3415454 100644 --- a/SenzorDataCollectorApp/StartingView.swift +++ b/SenzorDataCollectorApp/StartingView.swift @@ -29,7 +29,7 @@ struct StartingView: View { @StateObject private var manager = ServerCommunicationManager() @State private var iterationCount = 0 @State var displayText = "" -// @State var timerValues = [0] + @State var first_open = true //----------TIMER SETUP SETTINGS AND VARS ----------------------------------------------------- @StateObject var customTimer : CustomTimer @@ -46,51 +46,67 @@ struct StartingView: View { let motionManager = CMMotionManager() let altimeter = CMAltimeter() - @State var fileLocations: [String] = [] + @State var fileLocations: [URL] = [] let activityManager = CMMotionActivityManager() + @State var debug = true var body: some View { NavigationView { VStack { - + Spacer() //----------------------------- DEBUG VALUE DISPAY --------------------------------- - Text("Notification list: \(manager.timerIntervalValues.map { "\($0)" }.joined(separator: ", "))") - Text("Next notification in: \(customTimer.timerStringValue)") - Text("The timer is: \(customTimer.isStarted ? "on" : "off")") + 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() - + .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") + }.padding().disabled(manager.state != "Ready").opacity(debug ? 0 : 1) Button("STOP Iterations") { sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter) - //Save json customTimer.stopTimer() - }.padding() - + }.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") } + ) + } + } + + Button("CreateDIR"){ createDirectory() - }.padding() + }.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 + .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 @@ -101,13 +117,27 @@ struct StartingView: View { 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) @@ -134,14 +164,12 @@ struct StartingView: View { .foregroundColor(.white) } ) - //----------------------------- STATUS INDICATOR SHEETS --------------------------------- .sheet(isPresented: $isServerSettingsSheetPresented, content: { ServerConnectionView( manager: manager ) - } - ) + }) .sheet(isPresented: $sensorDetailSheetPresented, content: { VStack{ ForEach($sensor.activeSensorsList, id: \.self) { $list in @@ -154,13 +182,41 @@ struct StartingView: View { 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") } - ) + + 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() + setNextMeasurement() + sensor.startAllRecording(motionManager: motionManager, altimeter: altimeter) + customTimer.startTimer() + first_open = false + } + } label: { + Text(manager.state != "Ready" ? "Get started" : manager.state) + .padding(.horizontal, 10) + .background( + getColorForStatus(manager.state) + .cornerRadius(5) + ) + .foregroundColor(.white) + } + // TODO - does not reflect correctly + 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") + + }) //----------------------------- TIMER REFRESH --------------------------------- .onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()){ _ in @@ -170,17 +226,19 @@ struct StartingView: View { } //----------------------------- NOTIFICAION HANDELING --------------------------------- .alert("ALERT!!!", isPresented: $customTimer.isFinished) { - Button("OK",role: .cancel){ + Button("Next",role: .cancel){ sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter) customTimer.stopTimer() + saveAllToJson() setNextMeasurement() - sensor.startAllRecording(motionManager: motionManager, altimeter: altimeter) customTimer.startTimer() } - Button("Cancel", role: .destructive){ - + Button("Stop", role: .destructive){ + sensor.stopAllRecording(motionManager: motionManager, altimeter: altimeter) + customTimer.stopTimer() + saveAllToJson() } } } @@ -192,13 +250,15 @@ struct StartingView: View { let timeInMilliseconds = Int(now.timeIntervalSince1970 * 1000) if let selectedFolder = selectedFolder { - try sensor.exportAllSensorToJson(fileLocations: &fileLocations, filename_prefix: String(iterationCount) + String(timeInMilliseconds), fileLocationURL: selectedFolder, deviceID: "-") + 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() { @@ -218,7 +278,7 @@ struct StartingView: View { print("Error creating directory: \(error)") } } - + } //----------------------------- STATUS INDICATOR COLOR PICKER --------------------------------- private func getColorForStatus(_ status: String) -> Color { @@ -231,7 +291,6 @@ struct StartingView: View { return .yellow } } - //----------------------------- SETTING UP NEXT "TIME" FOR TIMER --------------------------------- private func setNextMeasurement(){ if !manager.timerIntervalValues.isEmpty { @@ -249,7 +308,7 @@ struct StartingView: View { struct StartingView_Previews: PreviewProvider { static var previews: some View { StartingView( customTimer: CustomTimer()) - + } } -- GitLab