diff --git a/src/AprsService.scala b/src/AprsService.scala index 5178986..3a478b1 100644 --- a/src/AprsService.scala +++ b/src/AprsService.scala @@ -24,8 +24,6 @@ object AprsService { val STATUS = PACKAGE + ".STATUS" val PACKET = PACKAGE + ".PACKET" - val FAST_LANE_ACT = 30000 - def intent(ctx : Context, action : String) : Intent = { new Intent(action, null, ctx, classOf[AprsService]) } @@ -39,26 +37,23 @@ object AprsService { } -class AprsService extends Service with LocationListener { +class AprsService extends Service { import AprsService._ val TAG = "APRSdroid.Service" lazy val prefs = new PrefsWrapper(this) - lazy val locMan = getSystemService(Context.LOCATION_SERVICE).asInstanceOf[LocationManager] - val handler = new Handler() lazy val db = StorageDatabase.open(this) lazy val msgService = new MessageService(this) + lazy val locSource = new PeriodicGPS(this, prefs) lazy val msgNotifier = msgService.createMessageNotifier() var poster : AprsIsUploader = null var singleShot = false - var lastLoc : Location = null - var fastLaneLoc : Location = null override def onStart(i : Intent, startId : Int) { Log.d(TAG, "onStart: " + i + ", " + startId); @@ -72,25 +67,6 @@ class AprsService extends Service with LocationListener { Service.START_REDELIVER_INTENT } - def requestLocations(stay_on : Boolean) { - // get update interval and distance - val upd_int = prefs.getStringInt("interval", 10) - val upd_dist = prefs.getStringInt("distance", 10) - val gps_act = prefs.getString("gps_activation", "med") - if (stay_on || (gps_act == "always")) { - locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, - 0, 0, this) - } else { - // for GPS precision == medium, we use getGpsInterval() - locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, - upd_int * 60000 - getGpsInterval(), upd_dist * 1000, this) - } - if (prefs.getBoolean("netloc", false)) { - locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - upd_int * 60000, upd_dist * 1000, this) - } - } - def handleStart(i : Intent) { // get update interval and distance val upd_int = prefs.getStringInt("interval", 10) @@ -98,11 +74,10 @@ class AprsService extends Service with LocationListener { // display notification (even though we are not actually started yet, // but we need this to prevent error message reordering) - fastLaneLoc = null if (i.getAction() == SERVICE_ONCE) { // if already running, we want to send immediately and continue; // otherwise, we finish after a single position report - lastLoc = null + locSource.restart() // set to true if not yet running or already running singleShot singleShot = !running || singleShot if (singleShot) @@ -120,7 +95,7 @@ class AprsService extends Service with LocationListener { } // continuous GPS tracking for single shot mode - requestLocations(singleShot) + locSource.start(singleShot) val callssid = prefs.getCallSsid() val message = "%s: %d min, %d km".format(callssid, upd_int, upd_dist) @@ -151,150 +126,17 @@ class AprsService extends Service with LocationListener { showToast(getString(R.string.service_stop)) } msgService.stop() - locMan.removeUpdates(this); + locSource.stop() unregisterReceiver(msgNotifier) ServiceNotifier.instance.stop(this) } - def getGpsInterval() : Int = { - val gps_act = prefs.getString("gps_activation", "med") - if (gps_act == "med") FAST_LANE_ACT - else 0 - } - - def startFastLane() { - Log.d(TAG, "switching to fast lane"); - // request fast update rate - locMan.removeUpdates(this); - requestLocations(true) - handler.postDelayed({ stopFastLane(true) }, FAST_LANE_ACT) - } - - def stopFastLane(post : Boolean) { - if (!running) - return; - Log.d(TAG, "switching to slow lane"); - if (post && fastLaneLoc != null) { - Log.d(TAG, "stopFastLane: posting " + fastLaneLoc); - postLocation(fastLaneLoc) - } - fastLaneLoc = null - // reset update speed - locMan.removeUpdates(this); - requestLocations(false) - } - - def goingFastLane(location : Location) : Boolean = { - if (fastLaneLoc == null) { - // need to set fastLaneLoc before re-requesting locations - fastLaneLoc = location - startFastLane() - } else - fastLaneLoc = location - return true - } - - def smartBeaconSpeedRate(speed : Float) : Int = { - val SB_FAST_SPEED = 28 // [m/s] = ~100km/h - val SB_FAST_RATE = 60 - val SB_SLOW_SPEED = 1 // [m/s] = 3.6km/h - val SB_SLOW_RATE = 1200 - if (speed <= SB_SLOW_SPEED) - SB_SLOW_RATE - else if (speed >= SB_FAST_SPEED) - SB_FAST_RATE - else - ((SB_SLOW_RATE - SB_FAST_RATE) * (SB_FAST_SPEED - speed) / (SB_FAST_SPEED-SB_SLOW_SPEED)).toInt - } - - // returns the angle between two bearings - def getBearingAngle(alpha : Float, beta : Float) : Float = { - val delta = math.abs(alpha-beta)%360 - if (delta <= 180) delta else (360-delta) - } - // obtain max speed in [m/s] from moved distance, last and current location - def getSpeed(location : Location) : Float = { - val dist = location.distanceTo(lastLoc) - val t_diff = location.getTime - lastLoc.getTime - math.max(math.max(dist*1000/t_diff, location.getSpeed), lastLoc.getSpeed) - } - - def smartBeaconCornerPeg(location : Location) : Boolean = { - val SB_TURN_TIME = 15 - val SB_TURN_MIN = 10 - val SB_TURN_SLOPE = 240.0 - - val speed = getSpeed(location) - val t_diff = location.getTime - lastLoc.getTime - val turn = getBearingAngle(location.getBearing, lastLoc.getBearing) - - // no bearing / stillstand -> no corner pegging - if (!location.hasBearing || speed == 0) - return false - - // if last bearing unknown, deploy turn_time - if (!lastLoc.hasBearing) - return (t_diff/1000 >= SB_TURN_TIME) - - // threshold depends on slope/speed [mph] - val threshold = SB_TURN_MIN + SB_TURN_SLOPE/(speed*2.23693629) - - Log.d(TAG, "smartBeaconCornerPeg: %1.0f < %1.0f %d/%d".format(turn, threshold, - t_diff/1000, SB_TURN_TIME)) - // need to corner peg if turn time reached and turn > threshold - (t_diff/1000 >= SB_TURN_TIME && turn > threshold) - } - - // return true if current position is "new enough" vs. lastLoc - def smartBeaconCheck(location : Location) : Boolean = { - if (lastLoc == null) - return true - if (smartBeaconCornerPeg(location)) - return true - val dist = location.distanceTo(lastLoc) - val t_diff = location.getTime - lastLoc.getTime - val speed = getSpeed(location) - //if (location.hasSpeed && location.hasBearing) - val speed_rate = smartBeaconSpeedRate(speed) - Log.d(TAG, "smartBeaconCheck: %1.0fm, %1.2fm/s -> %d/%ds - %s".format(dist, speed, - t_diff/1000, speed_rate, (t_diff/1000 >= speed_rate).toString)) - if (t_diff/1000 >= speed_rate) - true - else - false - } - - // LocationListener interface - override def onLocationChanged(location : Location) { - val upd_int = prefs.getStringInt("interval", 10) * 60000 - val upd_dist = prefs.getStringInt("distance", 10) * 1000 - if (prefs.getBoolean("smartbeaconing", true)) { - if (!smartBeaconCheck(location)) - return - } else /* no smartbeaconing */ - if (lastLoc != null && - (location.getTime - lastLoc.getTime < (upd_int - getGpsInterval()) || - location.distanceTo(lastLoc) < upd_dist)) { - //Log.d(TAG, "onLocationChanged: ignoring premature location") - return - } - // check if we need to go fast lane - val gps_act = prefs.getString("gps_activation", "med") - if (gps_act == "med" && location.getProvider == LocationManager.GPS_PROVIDER) { - if (goingFastLane(location)) - return - } - postLocation(location) - } - def appVersion() : String = { val pi = getPackageManager().getPackageInfo(getPackageName(), 0) "APDR%s".format(pi.versionName filter (_.isDigit) take 2) } def postLocation(location : Location) { - lastLoc = location - val i = new Intent(UPDATE) i.putExtra(LOCATION, location) @@ -329,23 +171,6 @@ class AprsService extends Service with LocationListener { } } - override def onProviderDisabled(provider : String) { - Log.d(TAG, "onProviderDisabled: " + provider) - val netloc_available = locMan.getProviders(true).contains(LocationManager.NETWORK_PROVIDER) - val netloc_usable = netloc_available && prefs.getBoolean("netloc", false) - if (provider == LocationManager.GPS_PROVIDER && - netloc_usable == false) { - // GPS was our last data source, we have to complain! - Toast.makeText(this, R.string.service_no_location, Toast.LENGTH_LONG).show() - } - } - override def onProviderEnabled(provider : String) { - Log.d(TAG, "onProviderEnabled: " + provider) - } - override def onStatusChanged(provider : String, st: Int, extras : Bundle) { - Log.d(TAG, "onStatusChanged: " + provider) - } - def parsePacket(ts : Long, message : String) { try { val fap = new Parser().parse(message) diff --git a/src/backend/TcpUploader.scala b/src/backend/TcpUploader.scala index 96b537f..bd94692 100644 --- a/src/backend/TcpUploader.scala +++ b/src/backend/TcpUploader.scala @@ -1,6 +1,7 @@ package org.aprsdroid.app import _root_.android.app.Service +import _root_.android.content.Context import _root_.android.location.{Location, LocationManager} import _root_.android.util.Log import _root_.java.io.{BufferedReader, InputStreamReader, OutputStreamWriter, PrintWriter} @@ -23,7 +24,8 @@ class TcpUploader(service : AprsService, prefs : PrefsWrapper) extends AprsIsUpl val filterdist = prefs.getStringInt("tcp.filterdist", 50) val userfilter = prefs.getString("tcp.filter", "") val lastloc = AprsPacket.formatRangeFilter( - service.locMan.getLastKnownLocation(LocationManager.GPS_PROVIDER), filterdist) + service.getSystemService(Context.LOCATION_SERVICE).asInstanceOf[LocationManager] + .getLastKnownLocation(LocationManager.GPS_PROVIDER), filterdist) if (filterdist == 0) return " filter %s %s".format(userfilter, lastloc) else return " filter m/%d %s %s".format(filterdist, userfilter, lastloc) } diff --git a/src/location/PeriodicGPS.scala b/src/location/PeriodicGPS.scala new file mode 100644 index 0000000..0aea92f --- /dev/null +++ b/src/location/PeriodicGPS.scala @@ -0,0 +1,207 @@ +package org.aprsdroid.app + +import _root_.android.content.Context +import _root_.android.location._ +import _root_.android.os.{Bundle, Handler} +import _root_.android.util.Log +import _root_.android.widget.Toast + +class PeriodicGPS(service : AprsService, prefs : PrefsWrapper) extends LocationListener { + val TAG = "APRSdroid.PeriodicGPS" + + val FAST_LANE_ACT = 30000 + + lazy val locMan = service.getSystemService(Context.LOCATION_SERVICE).asInstanceOf[LocationManager] + + var lastLoc : Location = null + var fastLaneLoc : Location = null + + def start(singleShot : Boolean) { + requestLocations(singleShot) + } + + def restart() { + fastLaneLoc = null + lastLoc = null + } + + def requestLocations(stay_on : Boolean) { + // get update interval and distance + val upd_int = prefs.getStringInt("interval", 10) + val upd_dist = prefs.getStringInt("distance", 10) + val gps_act = prefs.getString("gps_activation", "med") + if (stay_on || (gps_act == "always")) { + locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, + 0, 0, this) + } else { + // for GPS precision == medium, we use getGpsInterval() + locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, + upd_int * 60000 - getGpsInterval(), upd_dist * 1000, this) + } + if (prefs.getBoolean("netloc", false)) { + locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, + upd_int * 60000, upd_dist * 1000, this) + } + } + + + def stop() { + locMan.removeUpdates(this); + } + + def getGpsInterval() : Int = { + val gps_act = prefs.getString("gps_activation", "med") + if (gps_act == "med") FAST_LANE_ACT + else 0 + } + + def startFastLane() { + import AprsService.block2runnable + Log.d(TAG, "switching to fast lane"); + // request fast update rate + locMan.removeUpdates(this); + requestLocations(true) + service.handler.postDelayed({ stopFastLane(true) }, FAST_LANE_ACT) + } + + def stopFastLane(post : Boolean) { + if (!AprsService.running) + return; + Log.d(TAG, "switching to slow lane"); + if (post && fastLaneLoc != null) { + Log.d(TAG, "stopFastLane: posting " + fastLaneLoc); + postLocation(fastLaneLoc) + } + fastLaneLoc = null + // reset update speed + locMan.removeUpdates(this); + requestLocations(false) + } + + def goingFastLane(location : Location) : Boolean = { + if (fastLaneLoc == null) { + // need to set fastLaneLoc before re-requesting locations + fastLaneLoc = location + startFastLane() + } else + fastLaneLoc = location + return true + } + + def smartBeaconSpeedRate(speed : Float) : Int = { + val SB_FAST_SPEED = 28 // [m/s] = ~100km/h + val SB_FAST_RATE = 60 + val SB_SLOW_SPEED = 1 // [m/s] = 3.6km/h + val SB_SLOW_RATE = 1200 + if (speed <= SB_SLOW_SPEED) + SB_SLOW_RATE + else if (speed >= SB_FAST_SPEED) + SB_FAST_RATE + else + ((SB_SLOW_RATE - SB_FAST_RATE) * (SB_FAST_SPEED - speed) / (SB_FAST_SPEED-SB_SLOW_SPEED)).toInt + } + + // returns the angle between two bearings + def getBearingAngle(alpha : Float, beta : Float) : Float = { + val delta = math.abs(alpha-beta)%360 + if (delta <= 180) delta else (360-delta) + } + // obtain max speed in [m/s] from moved distance, last and current location + def getSpeed(location : Location) : Float = { + val dist = location.distanceTo(lastLoc) + val t_diff = location.getTime - lastLoc.getTime + math.max(math.max(dist*1000/t_diff, location.getSpeed), lastLoc.getSpeed) + } + + def smartBeaconCornerPeg(location : Location) : Boolean = { + val SB_TURN_TIME = 15 + val SB_TURN_MIN = 10 + val SB_TURN_SLOPE = 240.0 + + val speed = getSpeed(location) + val t_diff = location.getTime - lastLoc.getTime + val turn = getBearingAngle(location.getBearing, lastLoc.getBearing) + + // no bearing / stillstand -> no corner pegging + if (!location.hasBearing || speed == 0) + return false + + // if last bearing unknown, deploy turn_time + if (!lastLoc.hasBearing) + return (t_diff/1000 >= SB_TURN_TIME) + + // threshold depends on slope/speed [mph] + val threshold = SB_TURN_MIN + SB_TURN_SLOPE/(speed*2.23693629) + + Log.d(TAG, "smartBeaconCornerPeg: %1.0f < %1.0f %d/%d".format(turn, threshold, + t_diff/1000, SB_TURN_TIME)) + // need to corner peg if turn time reached and turn > threshold + (t_diff/1000 >= SB_TURN_TIME && turn > threshold) + } + + // return true if current position is "new enough" vs. lastLoc + def smartBeaconCheck(location : Location) : Boolean = { + if (lastLoc == null) + return true + if (smartBeaconCornerPeg(location)) + return true + val dist = location.distanceTo(lastLoc) + val t_diff = location.getTime - lastLoc.getTime + val speed = getSpeed(location) + //if (location.hasSpeed && location.hasBearing) + val speed_rate = smartBeaconSpeedRate(speed) + Log.d(TAG, "smartBeaconCheck: %1.0fm, %1.2fm/s -> %d/%ds - %s".format(dist, speed, + t_diff/1000, speed_rate, (t_diff/1000 >= speed_rate).toString)) + if (t_diff/1000 >= speed_rate) + true + else + false + } + + // LocationListener interface + override def onLocationChanged(location : Location) { + val upd_int = prefs.getStringInt("interval", 10) * 60000 + val upd_dist = prefs.getStringInt("distance", 10) * 1000 + if (prefs.getBoolean("smartbeaconing", true)) { + if (!smartBeaconCheck(location)) + return + } else /* no smartbeaconing */ + if (lastLoc != null && + (location.getTime - lastLoc.getTime < (upd_int - getGpsInterval()) || + location.distanceTo(lastLoc) < upd_dist)) { + //Log.d(TAG, "onLocationChanged: ignoring premature location") + return + } + // check if we need to go fast lane + val gps_act = prefs.getString("gps_activation", "med") + if (gps_act == "med" && location.getProvider == LocationManager.GPS_PROVIDER) { + if (goingFastLane(location)) + return + } + postLocation(location) + } + + override def onProviderDisabled(provider : String) { + Log.d(TAG, "onProviderDisabled: " + provider) + val netloc_available = locMan.getProviders(true).contains(LocationManager.NETWORK_PROVIDER) + val netloc_usable = netloc_available && prefs.getBoolean("netloc", false) + if (provider == LocationManager.GPS_PROVIDER && + netloc_usable == false) { + // GPS was our last data source, we have to complain! + Toast.makeText(service, R.string.service_no_location, Toast.LENGTH_LONG).show() + } + } + override def onProviderEnabled(provider : String) { + Log.d(TAG, "onProviderEnabled: " + provider) + } + override def onStatusChanged(provider : String, st: Int, extras : Bundle) { + Log.d(TAG, "onStatusChanged: " + provider) + } + + + def postLocation(location : Location) { + lastLoc = location + + service.postLocation(location) + } +}