diff --git a/TTN.xcodeproj/project.xcworkspace/xcuserdata/tristan.xcuserdatad/UserInterfaceState.xcuserstate b/TTN.xcodeproj/project.xcworkspace/xcuserdata/tristan.xcuserdatad/UserInterfaceState.xcuserstate index cae03e5..817e38e 100644 Binary files a/TTN.xcodeproj/project.xcworkspace/xcuserdata/tristan.xcuserdatad/UserInterfaceState.xcuserstate and b/TTN.xcodeproj/project.xcworkspace/xcuserdata/tristan.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TTN/Assets.xcassets/logo_iut.imageset/Contents.json b/TTN/Assets.xcassets/logo_iut.imageset/Contents.json new file mode 100644 index 0000000..a4071bf --- /dev/null +++ b/TTN/Assets.xcassets/logo_iut.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "logo_iut.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TTN/Assets.xcassets/logo_iut.imageset/logo_iut.png b/TTN/Assets.xcassets/logo_iut.imageset/logo_iut.png new file mode 100644 index 0000000..e13fffa Binary files /dev/null and b/TTN/Assets.xcassets/logo_iut.imageset/logo_iut.png differ diff --git a/TTN/ContentView.swift b/TTN/ContentView.swift index d1605ba..b27fb55 100644 --- a/TTN/ContentView.swift +++ b/TTN/ContentView.swift @@ -1,29 +1,113 @@ import SwiftUI +import Combine +import Charts +import UserNotifications struct ContentView: View { @StateObject private var dataManager = DataManager() + @State private var showHistory = false var body: some View { - VStack { - Text("Température : \(dataManager.temperature, specifier: "%.2f")°C") - .font(.largeTitle) - - Text("Humidité : \(dataManager.humidity, specifier: "%.2f")%") - .font(.title) - - List(dataManager.previousData, id: \.temperature) { data in + TabView { + // Page principale avec les valeurs actuelles + VStack(spacing: 20) { + + // Logo IUT + Image("logo_iut") + .resizable() + .scaledToFit() + .frame(height: 100) + .padding(.leading, 20) // Garder l'alignement à gauche + + // Température avec animation et couleur dynamique + HStack { + Image(systemName: "thermometer.medium") + .font(.system(size: 40)) + .foregroundColor(dataManager.temperatureColor) + .scaleEffect(dataManager.temperatureScale) + .animation(.easeInOut(duration: 0.5)) + Text("\(dataManager.temperature, specifier: "%.2f")°C") + .font(.largeTitle) + .fontWeight(.bold) + .foregroundColor(dataManager.temperatureColor) + } + .padding(.leading, 20) // Garder l'alignement à gauche + + // CO2 avec animation et couleur dynamique HStack { - Text("Température : \(data.temperature, specifier: "%.2f")°C") - Spacer() - Text("Humidité : \(data.humidity, specifier: "%.2f")%") + Image(systemName: "carbon.dioxide.cloud") + .font(.system(size: 40)) + .foregroundColor(dataManager.co2Color) + .scaleEffect(dataManager.co2Scale) + .animation(.easeInOut(duration: 0.5)) + Text("\(dataManager.co2, specifier: "%.2f") ppm") + .font(.largeTitle) + .fontWeight(.bold) + .foregroundColor(dataManager.co2Color) + } + .padding(.leading, 20) // Garder l'alignement à gauche + + // Graphique des valeurs + Chart(dataManager.previousData) { + LineMark( + x: .value("Temps", $0.time), + y: .value("Température", $0.temperature) + ) + .foregroundStyle(.red) + + LineMark( + x: .value("Temps", $0.time), + y: .value("CO2", $0.co2) + ) + .foregroundStyle(.blue) + } + .frame(height: 200) + .padding(.horizontal, 20) // Espacement horizontal + } + .padding(.top, 10) + .onAppear { + // Activer les notifications locales + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in + if success { + print("Notifications autorisées") + } else if let error = error { + print(error.localizedDescription) + } + } + + // Rafraîchir toutes les 60 secondes + Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in + dataManager.fetchData() } } + .tabItem { + Image(systemName: "house") + Text("Accueil") + } + + // Page Historique + HistoryView(dataManager: dataManager) + .tabItem { + Image(systemName: "clock") + Text("Historique") + } } - .padding() - .onAppear { - // Rafraîchir toutes les 60 secondes - Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in - dataManager.fetchData() + } +} + +struct HistoryView: View { + @ObservedObject var dataManager: DataManager + + var body: some View { + List(dataManager.previousData, id: \.time) { data in + HStack { + Image(systemName: "thermometer.medium") + Text("Température : \(data.temperature, specifier: "%.2f")°C") + + Spacer() + + Image(systemName: "carbon.dioxide.cloud") + Text("CO2 : \(data.co2, specifier: "%.2f") ppm") } } } diff --git a/TTN/DataManager.swift b/TTN/DataManager.swift index 978fe91..f4d3b04 100644 --- a/TTN/DataManager.swift +++ b/TTN/DataManager.swift @@ -1,59 +1,73 @@ import SwiftUI import Combine +struct DataModel: Identifiable { + var id = UUID() // Ajout de l'identifiant unique + var time: Date + var temperature: Double + var co2: Double +} + class DataManager: ObservableObject { @Published var temperature: Double = 0.0 - @Published var humidity: Double = 0.0 - @Published var previousData: [(temperature: Double, humidity: Double)] = [] + @Published var co2: Double = 0.0 + @Published var previousData: [DataModel] = [] + + var temperatureColor: Color { + switch temperature { + case ..<15: return .blue + case 15..<25: return .green + case 25..<35: return .orange + default: return .red + } + } + + var co2Color: Color { + switch co2 { + case ..<600: return .green + case 600..<1000: return .yellow + default: return .red + } + } - private var cancellable: AnyCancellable? + var temperatureScale: CGFloat { + return temperature > 30 ? 1.2 : 1.0 + } + + var co2Scale: CGFloat { + return co2 > 1000 ? 1.2 : 1.0 + } func fetchData() { - let urlString = "https://.cloud.thethings.network/api/v3/as/applications//devices//packages/storage/uplink_message" - guard let url = URL(string: urlString) else { return } + // Remplacer cette partie par les vraies données de The Things Network + let newTemperature = Double.random(in: 10...40) + let newCO2 = Double.random(in: 300...1200) + + // Mise à jour des données actuelles + temperature = newTemperature + co2 = newCO2 - var request = URLRequest(url: url) - request.addValue("Bearer ", forHTTPHeaderField: "Authorization") + // Stockage des données précédentes + let newData = DataModel(time: Date(), temperature: newTemperature, co2: newCO2) + previousData.append(newData) - cancellable = URLSession.shared.dataTaskPublisher(for: request) - .tryMap { (data, response) -> Data in - guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { - throw URLError(.badServerResponse) - } - return data - } - .decode(type: TTNResponse.self, decoder: JSONDecoder()) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { completion in - switch completion { - case .failure(let error): - print("Error fetching data: \(error)") - case .finished: - break - } - }, receiveValue: { [weak self] response in - guard let self = self else { return } - - // Stocker les anciennes données - self.previousData.append((temperature: self.temperature, humidity: self.humidity)) - - // Mettre à jour les nouvelles valeurs - self.temperature = response.uplinkMessage.decodedPayload.temperature - self.humidity = response.uplinkMessage.decodedPayload.humidity - }) + // Envoi d'une notification si des valeurs critiques sont dépassées + if newTemperature > 35 { + sendNotification(title: "Température élevée", message: "La température a dépassé 35°C !") + } + + if newCO2 > 1000 { + sendNotification(title: "CO2 élevé", message: "Le niveau de CO2 a dépassé 1000 ppm !") + } + } + + func sendNotification(title: String, message: String) { + let content = UNMutableNotificationContent() + content.title = title + content.body = message + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) + UNUserNotificationCenter.current().add(request) } -} - -// Struct pour correspondre à la réponse TTN -struct TTNResponse: Codable { - let uplinkMessage: UplinkMessage -} - -struct UplinkMessage: Codable { - let decodedPayload: DecodedPayload -} - -struct DecodedPayload: Codable { - let temperature: Double - let humidity: Double }