123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- //
- // ViewController.swift
- // JourneyGPSTracker
- //
- // Created by Thomas Chef on 2022-04-16.
- // Copyright © 2022 Thomas Chef. All rights reserved.
- //
- import UIKit
- import CoreLocation
- import CoreData
- class ViewController: UIViewController {
-
- struct GPS_POS_LOG: Codable {
- var latitude: Double
- var longitude: Double
- var ts: String
- var horizAcc: Int32
- }
-
-
- @IBOutlet weak var horizSpeedLabelUI: UILabel!
- @IBOutlet weak var distSliderUI: UISlider!
- @IBOutlet weak var distUpdateMetersUI: UILabel!
- @IBOutlet weak var GPSAccuracy: UILabel!
- @IBOutlet weak var noOfFailedUpdatesUI: UILabel!
- @IBOutlet weak var latestTSSent: UILabel!
- @IBOutlet weak var noOfRxGPSPos: UILabel!
- @IBOutlet weak var noOfSentGPSToServer: UILabel!
- @IBOutlet weak var pauseSwitch: UISwitch!
- @IBOutlet weak var clickButton: UIButton!
- @IBOutlet weak var trackingActivateSwitch: UISwitch!
-
- var locationManager: CLLocationManager?
-
- var noOfSentPos: Int = 0
- var noOfSentToServer: Int = 0
- var noOfFailedUpdates: Int = 0
-
- var gpsLogData: [NSManagedObject] = []
-
- let appDelegate = UIApplication.shared.delegate as! AppDelegate
-
- var latestDateSuccSent: Date = Date(timeIntervalSinceReferenceDate: -123456789.0)
-
- override func viewDidLoad() {
- super.viewDidLoad()
- locationManager = CLLocationManager()
- locationManager?.delegate = self
- locationManager?.requestWhenInUseAuthorization()
- locationManager?.requestAlwaysAuthorization()
- locationManager?.desiredAccuracy = kCLLocationAccuracyBest //kCLLocationAccuracyNearestTenMeters
- locationManager?.activityType = CLActivityType.otherNavigation
- locationManager?.distanceFilter = CLLocationDistance(distSliderUI.value)
- locationManager?.allowsBackgroundLocationUpdates = true
- pauseSwitch.isEnabled = false
- distUpdateMetersUI.text = String(distSliderUI.value)+" m"
- }
-
- @IBAction func distSlider(_ sender: UISlider) {
- let currentValue = Int(sender.value)
- distUpdateMetersUI.text = String(currentValue)+" m"
- locationManager?.distanceFilter = CLLocationDistance(currentValue)
- }
-
-
- // This timer is used to send data to the back-end server every 60 seconds
- // Data source is the core data DB
- func setupTimer() {
-
- _ = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [self] timer in
-
- var latestDate: Date = Date(timeIntervalSinceReferenceDate: -123456789.0)
-
- let cnxt = self.appDelegate.persistentContainer.viewContext
- let request: NSFetchRequest<GPS> = GPS.fetchRequest()
- let myPredicate = NSPredicate(format: "ts > %@", latestDateSuccSent as CVarArg)
-
- request.predicate = myPredicate
-
- do {
- let fd = try cnxt.fetch(request)
- print( "Timer: DB Request returned:" + String(fd.count) )
-
- if( fd.count > 0 ) {
- var positions: [GPS_POS_LOG] = []
-
- fd.forEach { row in
- let ts = convertStartDate(StartDate: row.ts!)
- let gpsPosLog = GPS_POS_LOG(latitude:row.latitude,longitude: row.longitud,ts: ts, horizAcc: row.horizAcc)
- positions.append(gpsPosLog)
- //cnxt.delete(row) // This row deletes the row in the DB
- self.noOfSentToServer += 1
-
- if( row.ts! > latestDate ) {
- latestDate = row.ts!; // Save the latest date
- }
- }
- sendToHttpServer(gpsPosLog: positions, latestDate: latestDate)
- noOfSentGPSToServer.text = String(noOfSentToServer) // Update GUI
- }
- else {
- print("Timer: No data to send to back-end");
- }
-
- } catch let error as NSError {
- print("Timer: Could not fetch. \(error), \(error.userInfo)")
- }
- if( self.trackingActivateSwitch.isOn == false ) {
- timer.invalidate()
- print("Timer: Stopping Timer")
- }
-
- latestTSSent.text = convertStartDate(StartDate: latestDate)
- }
- }
-
- enum MyError: Error {
- case DataIsNil
- case BadJSONResponse
- }
- func sendToHttpServer(gpsPosLog: [GPS_POS_LOG], latestDate: Date) {
- let jsonEncoder = JSONEncoder()
- jsonEncoder.outputFormatting = .prettyPrinted
-
- var uploadData:Data?
- do {
- uploadData = try jsonEncoder.encode(gpsPosLog)
- } catch {
- print(error.localizedDescription)
- return
- }
- //print( gpsPosLog )
-
- let url = URL(string: "http://chef.maya.se/gpsapi/registerGPSlocation.php")!
- var request = URLRequest(url: url)
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
- request.httpMethod = "POST"
-
- let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
- do {
- if let error = error {
- print ("HTTP: error: ") //\(error)")
- throw error
- }
- guard let response = response as? HTTPURLResponse,
- (200...299).contains(response.statusCode) else {
- print ("HTTP: 200-299 server error")
-
- throw MyError.DataIsNil
- }
- if let mimeType = response.mimeType,
- mimeType == "text/html",
- let data = data,
- let dataString = String(data: data, encoding: .utf8) {
- print ("HTTP: got data: \(dataString)")
- print ("HTTP: Latest date sent: " + self.convertStartDate(StartDate: latestDate))
- //self.latestTSSent.text = self.convertStartDate(StartDate: latestDate)
- self.latestDateSuccSent = latestDate
- }
- else {
- print("HTTP: KALLE:" + String(response.mimeType!))
- throw MyError.DataIsNil
- //self.noOfFailedUpdates += 1
- }
- } catch {
- self.noOfFailedUpdates += 1
- //self.noOfFailedUpdatesUI.text = String(self.noOfFailedUpdates)
- print("CATCH ERROR !!!")
- }
- }
- task.resume()
- print("Resume.....")
- self.noOfFailedUpdatesUI.text = String(noOfFailedUpdates)
- }
-
- @IBAction func tackingPauseChanged(_ sender: UISwitch) {
- print("Pause-Switch Changed !")
- switch sender.isOn {
- case true:
- print("Pause ON")
- locationManager?.stopUpdatingLocation()
- default:
- print("Pause OFF")
- locationManager?.startUpdatingLocation()
- }
- }
-
- @IBAction func trackingEnabledChanged(_ sender: UISwitch) {
- print("Switch Changed !")
- switch sender.isOn {
- case true:
- let managedContext = appDelegate.persistentContainer.viewContext
- let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "GPS")
- let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
- do {
- try managedContext.execute(batchDeleteRequest)
- } catch {
- // Error Handling
- }
- latestDateSuccSent = Date(timeIntervalSinceReferenceDate: -123456789.0)
- print("ON")
- locationManager?.startUpdatingLocation()
- pauseSwitch.isEnabled = true
- noOfSentPos = 0
- noOfSentToServer = 0
- noOfFailedUpdates = 0
- noOfFailedUpdatesUI.text = String(noOfFailedUpdates)
- noOfRxGPSPos.text = String(noOfSentPos)
- noOfSentGPSToServer.text = String(noOfSentToServer)
- latestTSSent.text = "-"
- setupTimer()
- default:
- print("OFF")
- locationManager?.stopUpdatingLocation()
- pauseSwitch.isEnabled = false
- pauseSwitch.isOn = false
- }
- }
-
-
- }
- extension ViewController: CLLocationManagerDelegate {
-
- // Step 7: Handle the location information
- // This function is called when there is a new location available
- // The data is put into the core data DB
- func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
-
- print("Got a new position: numberOfLocations received: \(locations.count)")
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
-
-
-
- noOfSentPos += 1
- noOfRxGPSPos.text = String(noOfSentPos)
-
- let managedContext = appDelegate.persistentContainer.viewContext
- let entity = NSEntityDescription.entity(forEntityName: "GPS", in: managedContext)!
- // Loop through all GPS Positions and send them to the Database
- locations.forEach { (location) in
- //print("LocationManager horizontalAccuracy: \(location.horizontalAccuracy)")
-
- GPSAccuracy.text = String(location.horizontalAccuracy)+" m"
- horizSpeedLabelUI.text = String(format: "%.1f", location.speed * 3.6 ) + " km/h"
-
- let pos = NSManagedObject(entity: entity,insertInto: managedContext)
- pos.setValue(location.horizontalAccuracy, forKey: "horizAcc")
- pos.setValue(location.coordinate.longitude, forKey: "longitud")
- pos.setValue(location.coordinate.latitude, forKey: "latitude")
- pos.setValue(location.timestamp, forKey: "ts")
- do {
- try managedContext.save()
- } catch let error as NSError {
- print("Could not save. \(error), \(error.userInfo)")
- }
- }
- }
-
- func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
- print("LocationManager didFailWithError \(error.localizedDescription)")
- if let error = error as? CLError, error.code == .denied {
- // Location updates are not authorized.
- // To prevent forever looping of `didFailWithError` callback
- locationManager?.stopMonitoringSignificantLocationChanges()
- return
- }
- }
-
- func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
- print("location manager authorization status changed")
-
- switch status {
- case .authorizedAlways:
- print("user allow app to get location data when app is active or in background")
- case .authorizedWhenInUse:
- print("user allow app to get location data only when app is active")
- case .denied:
- print("user tap 'disallow' on the permission dialog, cant get location data")
- case .restricted:
- print("parental control setting disallow location data")
- case .notDetermined:
- print("the location permission dialog haven't shown before, user haven't tap allow/disallow")
- }
- }
-
- func convertStartDate(StartDate: Date) -> String {
- let dateFormatter = DateFormatter()
- dateFormatter.timeZone = TimeZone.current
- dateFormatter.dateFormat = "yyy-MM-dd' 'HH:mm:ss"
- let dateString = dateFormatter.string(from: StartDate)
- //dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss +0000"
- //let date = dateFormatter.date(from: dateString)
- return dateString
- }
- }
|