ViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. //
  2. // ViewController.swift
  3. // JourneyGPSTracker
  4. //
  5. // Created by Thomas Chef on 2022-04-16.
  6. // Copyright © 2022 Thomas Chef. All rights reserved.
  7. //
  8. import UIKit
  9. import CoreLocation
  10. import CoreData
  11. class ViewController: UIViewController {
  12. struct GPS_POS_LOG: Codable {
  13. var latitude: Double
  14. var longitude: Double
  15. var ts: String
  16. var horizAcc: Int32
  17. }
  18. @IBOutlet weak var horizSpeedLabelUI: UILabel!
  19. @IBOutlet weak var distSliderUI: UISlider!
  20. @IBOutlet weak var distUpdateMetersUI: UILabel!
  21. @IBOutlet weak var GPSAccuracy: UILabel!
  22. @IBOutlet weak var noOfFailedUpdatesUI: UILabel!
  23. @IBOutlet weak var latestTSSent: UILabel!
  24. @IBOutlet weak var noOfRxGPSPos: UILabel!
  25. @IBOutlet weak var noOfSentGPSToServer: UILabel!
  26. @IBOutlet weak var pauseSwitch: UISwitch!
  27. @IBOutlet weak var clickButton: UIButton!
  28. @IBOutlet weak var trackingActivateSwitch: UISwitch!
  29. var locationManager: CLLocationManager?
  30. var noOfSentPos: Int = 0
  31. var noOfSentToServer: Int = 0
  32. var noOfFailedUpdates: Int = 0
  33. var gpsLogData: [NSManagedObject] = []
  34. let appDelegate = UIApplication.shared.delegate as! AppDelegate
  35. var latestDateSuccSent: Date = Date(timeIntervalSinceReferenceDate: -123456789.0)
  36. override func viewDidLoad() {
  37. super.viewDidLoad()
  38. locationManager = CLLocationManager()
  39. locationManager?.delegate = self
  40. locationManager?.requestWhenInUseAuthorization()
  41. locationManager?.requestAlwaysAuthorization()
  42. locationManager?.desiredAccuracy = kCLLocationAccuracyBest //kCLLocationAccuracyNearestTenMeters
  43. locationManager?.activityType = CLActivityType.otherNavigation
  44. locationManager?.distanceFilter = CLLocationDistance(distSliderUI.value)
  45. locationManager?.allowsBackgroundLocationUpdates = true
  46. pauseSwitch.isEnabled = false
  47. distUpdateMetersUI.text = String(distSliderUI.value)+" m"
  48. }
  49. @IBAction func distSlider(_ sender: UISlider) {
  50. let currentValue = Int(sender.value)
  51. distUpdateMetersUI.text = String(currentValue)+" m"
  52. locationManager?.distanceFilter = CLLocationDistance(currentValue)
  53. }
  54. // This timer is used to send data to the back-end server every 60 seconds
  55. // Data source is the core data DB
  56. func setupTimer() {
  57. _ = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [self] timer in
  58. var latestDate: Date = Date(timeIntervalSinceReferenceDate: -123456789.0)
  59. let cnxt = self.appDelegate.persistentContainer.viewContext
  60. let request: NSFetchRequest<GPS> = GPS.fetchRequest()
  61. let myPredicate = NSPredicate(format: "ts > %@", latestDateSuccSent as CVarArg)
  62. request.predicate = myPredicate
  63. do {
  64. let fd = try cnxt.fetch(request)
  65. print( "Timer: DB Request returned:" + String(fd.count) )
  66. if( fd.count > 0 ) {
  67. var positions: [GPS_POS_LOG] = []
  68. fd.forEach { row in
  69. let ts = convertStartDate(StartDate: row.ts!)
  70. let gpsPosLog = GPS_POS_LOG(latitude:row.latitude,longitude: row.longitud,ts: ts, horizAcc: row.horizAcc)
  71. positions.append(gpsPosLog)
  72. //cnxt.delete(row) // This row deletes the row in the DB
  73. self.noOfSentToServer += 1
  74. if( row.ts! > latestDate ) {
  75. latestDate = row.ts!; // Save the latest date
  76. }
  77. }
  78. sendToHttpServer(gpsPosLog: positions, latestDate: latestDate)
  79. noOfSentGPSToServer.text = String(noOfSentToServer) // Update GUI
  80. }
  81. else {
  82. print("Timer: No data to send to back-end");
  83. }
  84. } catch let error as NSError {
  85. print("Timer: Could not fetch. \(error), \(error.userInfo)")
  86. }
  87. if( self.trackingActivateSwitch.isOn == false ) {
  88. timer.invalidate()
  89. print("Timer: Stopping Timer")
  90. }
  91. latestTSSent.text = convertStartDate(StartDate: latestDate)
  92. }
  93. }
  94. enum MyError: Error {
  95. case DataIsNil
  96. case BadJSONResponse
  97. }
  98. func sendToHttpServer(gpsPosLog: [GPS_POS_LOG], latestDate: Date) {
  99. let jsonEncoder = JSONEncoder()
  100. jsonEncoder.outputFormatting = .prettyPrinted
  101. var uploadData:Data?
  102. do {
  103. uploadData = try jsonEncoder.encode(gpsPosLog)
  104. } catch {
  105. print(error.localizedDescription)
  106. return
  107. }
  108. //print( gpsPosLog )
  109. let url = URL(string: "http://chef.maya.se/gpsapi/registerGPSlocation.php")!
  110. var request = URLRequest(url: url)
  111. request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  112. request.httpMethod = "POST"
  113. let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
  114. do {
  115. if let error = error {
  116. print ("HTTP: error: ") //\(error)")
  117. throw error
  118. }
  119. guard let response = response as? HTTPURLResponse,
  120. (200...299).contains(response.statusCode) else {
  121. print ("HTTP: 200-299 server error")
  122. throw MyError.DataIsNil
  123. }
  124. if let mimeType = response.mimeType,
  125. mimeType == "text/html",
  126. let data = data,
  127. let dataString = String(data: data, encoding: .utf8) {
  128. print ("HTTP: got data: \(dataString)")
  129. print ("HTTP: Latest date sent: " + self.convertStartDate(StartDate: latestDate))
  130. //self.latestTSSent.text = self.convertStartDate(StartDate: latestDate)
  131. self.latestDateSuccSent = latestDate
  132. }
  133. else {
  134. print("HTTP: KALLE:" + String(response.mimeType!))
  135. throw MyError.DataIsNil
  136. //self.noOfFailedUpdates += 1
  137. }
  138. } catch {
  139. self.noOfFailedUpdates += 1
  140. //self.noOfFailedUpdatesUI.text = String(self.noOfFailedUpdates)
  141. print("CATCH ERROR !!!")
  142. }
  143. }
  144. task.resume()
  145. print("Resume.....")
  146. self.noOfFailedUpdatesUI.text = String(noOfFailedUpdates)
  147. }
  148. @IBAction func tackingPauseChanged(_ sender: UISwitch) {
  149. print("Pause-Switch Changed !")
  150. switch sender.isOn {
  151. case true:
  152. print("Pause ON")
  153. locationManager?.stopUpdatingLocation()
  154. default:
  155. print("Pause OFF")
  156. locationManager?.startUpdatingLocation()
  157. }
  158. }
  159. @IBAction func trackingEnabledChanged(_ sender: UISwitch) {
  160. print("Switch Changed !")
  161. switch sender.isOn {
  162. case true:
  163. let managedContext = appDelegate.persistentContainer.viewContext
  164. let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "GPS")
  165. let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
  166. do {
  167. try managedContext.execute(batchDeleteRequest)
  168. } catch {
  169. // Error Handling
  170. }
  171. latestDateSuccSent = Date(timeIntervalSinceReferenceDate: -123456789.0)
  172. print("ON")
  173. locationManager?.startUpdatingLocation()
  174. pauseSwitch.isEnabled = true
  175. noOfSentPos = 0
  176. noOfSentToServer = 0
  177. noOfFailedUpdates = 0
  178. noOfFailedUpdatesUI.text = String(noOfFailedUpdates)
  179. noOfRxGPSPos.text = String(noOfSentPos)
  180. noOfSentGPSToServer.text = String(noOfSentToServer)
  181. latestTSSent.text = "-"
  182. setupTimer()
  183. default:
  184. print("OFF")
  185. locationManager?.stopUpdatingLocation()
  186. pauseSwitch.isEnabled = false
  187. pauseSwitch.isOn = false
  188. }
  189. }
  190. }
  191. extension ViewController: CLLocationManagerDelegate {
  192. // Step 7: Handle the location information
  193. // This function is called when there is a new location available
  194. // The data is put into the core data DB
  195. func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  196. print("Got a new position: numberOfLocations received: \(locations.count)")
  197. let dateFormatter = DateFormatter()
  198. dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
  199. noOfSentPos += 1
  200. noOfRxGPSPos.text = String(noOfSentPos)
  201. let managedContext = appDelegate.persistentContainer.viewContext
  202. let entity = NSEntityDescription.entity(forEntityName: "GPS", in: managedContext)!
  203. // Loop through all GPS Positions and send them to the Database
  204. locations.forEach { (location) in
  205. //print("LocationManager horizontalAccuracy: \(location.horizontalAccuracy)")
  206. GPSAccuracy.text = String(location.horizontalAccuracy)+" m"
  207. horizSpeedLabelUI.text = String(format: "%.1f", location.speed * 3.6 ) + " km/h"
  208. let pos = NSManagedObject(entity: entity,insertInto: managedContext)
  209. pos.setValue(location.horizontalAccuracy, forKey: "horizAcc")
  210. pos.setValue(location.coordinate.longitude, forKey: "longitud")
  211. pos.setValue(location.coordinate.latitude, forKey: "latitude")
  212. pos.setValue(location.timestamp, forKey: "ts")
  213. do {
  214. try managedContext.save()
  215. } catch let error as NSError {
  216. print("Could not save. \(error), \(error.userInfo)")
  217. }
  218. }
  219. }
  220. func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
  221. print("LocationManager didFailWithError \(error.localizedDescription)")
  222. if let error = error as? CLError, error.code == .denied {
  223. // Location updates are not authorized.
  224. // To prevent forever looping of `didFailWithError` callback
  225. locationManager?.stopMonitoringSignificantLocationChanges()
  226. return
  227. }
  228. }
  229. func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
  230. print("location manager authorization status changed")
  231. switch status {
  232. case .authorizedAlways:
  233. print("user allow app to get location data when app is active or in background")
  234. case .authorizedWhenInUse:
  235. print("user allow app to get location data only when app is active")
  236. case .denied:
  237. print("user tap 'disallow' on the permission dialog, cant get location data")
  238. case .restricted:
  239. print("parental control setting disallow location data")
  240. case .notDetermined:
  241. print("the location permission dialog haven't shown before, user haven't tap allow/disallow")
  242. }
  243. }
  244. func convertStartDate(StartDate: Date) -> String {
  245. let dateFormatter = DateFormatter()
  246. dateFormatter.timeZone = TimeZone.current
  247. dateFormatter.dateFormat = "yyy-MM-dd' 'HH:mm:ss"
  248. let dateString = dateFormatter.string(from: StartDate)
  249. //dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss +0000"
  250. //let date = dateFormatter.date(from: dateString)
  251. return dateString
  252. }
  253. }