kopia lustrzana https://github.com/ge0rg/aprsdroid
Digipeater Updates & Other Features
rodzic
5499576044
commit
dda032c3e5
|
@ -26,7 +26,6 @@ plugins {
|
|||
// obtain revision from git
|
||||
id 'org.ajoberstar.grgit' version '1.6.0'
|
||||
// gradle-amazon-app-store-publisher
|
||||
id "app.brant.amazonappstorepublisher" version "0.1.0"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
@ -69,13 +68,6 @@ def mapsApiKey() {
|
|||
properties.getProperty('mapsApiKey', "AIzaSyA12R_iI_upYQ33FWnPU_8GlMKrEmjDxiQ")
|
||||
}
|
||||
|
||||
amazon {
|
||||
securityProfile = file("amazon-publish-credentials.json")
|
||||
applicationId = "amzn1.devportal.mobileapp.90ffde1571a347f8a100e1083c64812e"
|
||||
pathToApks = [ file("build/outputs/apk/release/aprsdroid-release.apk") ]
|
||||
replaceEdit = true
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.2"
|
||||
|
|
|
@ -108,6 +108,21 @@
|
|||
<string name="ad_trans">\n\nTranslation:</string>
|
||||
<string name="ad_homepage">Home Page</string>
|
||||
|
||||
<string name="p_regenerate">Regenerate Packets</string>
|
||||
<string name="p_regenerate_summary">Dangerously regenerates heard packets with NO filter!!</string>
|
||||
|
||||
<string name="p_digipeating">Digipeating Functions</string>
|
||||
<string name="p_digipeating_entry">Digipeater</string>
|
||||
<string name="p_digipeating_summary">Enable Digipeater</string>
|
||||
|
||||
<string name="p_dedupe">Dedupe Timeout</string>
|
||||
<string name="p_dedupe_entry">Timeout in seconds</string>
|
||||
<string name="p_dedupe_summary">Digipeat timeout for duplicate packets</string>
|
||||
|
||||
<string name="p_digipeaterpath">Digipeater Path</string>
|
||||
<string name="p_digipeaterpath_entry">Set Digi Path</string>
|
||||
<string name="p_digipeaterpath_summary">Path to digipeat</string>
|
||||
|
||||
<!-- map view -->
|
||||
<string name="map_overlays">Overlays</string>
|
||||
<string name="map_google">Google: Map</string>
|
||||
|
@ -312,6 +327,15 @@
|
|||
<string name="status_linkoff">Error: %s</string>
|
||||
<string name="status_linkon">Connected: %s</string>
|
||||
|
||||
<string name="p_messaging">Messaging</string>
|
||||
<string name="p_message_retry">Message Retries</string>
|
||||
<string name="p_message_retry_entry">Number of messages to retry</string>
|
||||
<string name="p_message_retry_summary">Message retry limit</string>
|
||||
|
||||
<string name="p_retry_interval">Retry Interval</string>
|
||||
<string name="p_retry_interval_entry">Retry interval start rate</string>
|
||||
<string name="p_retry_interval_summary">Rate doubles each retry</string>
|
||||
|
||||
<!-- AFSK settings -->
|
||||
<string name="p_aprs_path">APRS digi path</string>
|
||||
<string name="p_aprs_path_hint">hop 1, hop 2, ...</string>
|
||||
|
@ -349,6 +373,10 @@
|
|||
<string name="p_sotimeout_summary">Time before resetting the connection</string>
|
||||
<string name="p_sotimeout_entry">Timeout value in seconds (0 = disable)</string>
|
||||
|
||||
<string name="p_reconnect">TCP reconnect timeout</string>
|
||||
<string name="p_reconnect_summary">Time before reconnecting</string>
|
||||
<string name="p_reconnect_entry">Timeout value in seconds</string>
|
||||
|
||||
<string name="p_mapfile">Map file name</string>
|
||||
<string name="p_mapfile_summary">MapsForge map file for APRSdroid</string>
|
||||
<string name="p_mapfile_choose">Choose map file</string>
|
||||
|
|
|
@ -45,6 +45,14 @@
|
|||
android:defaultValue="120"
|
||||
android:dialogTitle="@string/p_sotimeout_entry" />
|
||||
|
||||
<de.duenndns.EditTextPreferenceWithValue
|
||||
android:key="tcp.reconnect"
|
||||
android:inputType="number"
|
||||
android:title="@string/p_reconnect"
|
||||
android:summary="@string/p_reconnect"
|
||||
android:defaultValue="30"
|
||||
android:dialogTitle="@string/p_reconnect_entry" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -22,6 +22,15 @@
|
|||
android:defaultValue="120"
|
||||
android:dialogTitle="@string/p_sotimeout_entry" />
|
||||
|
||||
<de.duenndns.EditTextPreferenceWithValue
|
||||
android:key="tcp.reconnect"
|
||||
android:inputType="number"
|
||||
android:title="@string/p_reconnect"
|
||||
android:summary="@string/p_reconnect_summary"
|
||||
android:defaultValue="30"
|
||||
android:dialogTitle="@string/p_reconnect_entry" />
|
||||
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -224,6 +224,65 @@
|
|||
android:title="@string/p_themefile"
|
||||
android:summary="@string/p_themefile_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<!-- sub-screen "digipeating" -->
|
||||
<PreferenceCategory
|
||||
android:title="@string/p_digipeating">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="p.digipeating"
|
||||
android:title="@string/p_digipeating_entry"
|
||||
android:summary="@string/p_digipeating_summary"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<de.duenndns.EditTextPreferenceWithValue
|
||||
android:key="digipeater_path"
|
||||
android:hint="WIDE, TEMP, MTN"
|
||||
android:defaultValue="WIDE"
|
||||
android:inputType="textCapCharacters"
|
||||
android:digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ,-"
|
||||
android:title="@string/p_digipeaterpath"
|
||||
android:summary="@string/p_digipeaterpath_summary"
|
||||
android:dialogTitle="@string/p_digipeaterpath_entry" />
|
||||
|
||||
|
||||
<de.duenndns.EditTextPreferenceWithValue
|
||||
android:key="p.dedupe"
|
||||
android:defaultValue="30"
|
||||
android:inputType="number"
|
||||
android:title="@string/p_dedupe"
|
||||
android:summary="@string/p_dedupe_summary"
|
||||
android:dialogTitle="@string/p_dedupe_entry" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="p.regenerate"
|
||||
android:title="@string/p_regenerate"
|
||||
android:summary="@string/p_regenerate_summary"
|
||||
android:defaultValue="false" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<!-- sub-screen "messaging" -->
|
||||
<PreferenceCategory
|
||||
android:title="@string/p_messaging">
|
||||
|
||||
<de.duenndns.EditTextPreferenceWithValue
|
||||
android:key="p.messaging"
|
||||
android:defaultValue="7"
|
||||
android:inputType="number"
|
||||
android:title="@string/p_message_retry"
|
||||
android:summary="@string/p_message_retry_summary"
|
||||
android:dialogTitle="@string/p_message_retry_entry" />
|
||||
|
||||
<de.duenndns.EditTextPreferenceWithValue
|
||||
android:key="p.retry"
|
||||
android:defaultValue="30"
|
||||
android:inputType="number"
|
||||
android:title="@string/p_retry_interval"
|
||||
android:summary="@string/p_retry_interval_summary"
|
||||
android:dialogTitle="@string/p_retry_interval_entry" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -9,6 +9,8 @@ import _root_.android.util.Log
|
|||
import _root_.android.widget.Toast
|
||||
|
||||
import _root_.net.ab0oo.aprs.parser._
|
||||
import scala.collection.mutable
|
||||
import java.time.Instant
|
||||
|
||||
object AprsService {
|
||||
val PACKAGE = "org.aprsdroid.app"
|
||||
|
@ -73,6 +75,10 @@ class AprsService extends Service {
|
|||
|
||||
lazy val prefs = new PrefsWrapper(this)
|
||||
|
||||
lazy val dedupeTime = prefs.getStringInt("p.dedupe", 30) // Fetch NUM_OF_RETRIES from prefs, defaulting to 7 if not found
|
||||
|
||||
lazy val digipeaterpath = prefs.getString("digipeater_path", "WIDE")
|
||||
|
||||
val handler = new Handler()
|
||||
|
||||
lazy val db = StorageDatabase.open(this)
|
||||
|
@ -284,6 +290,21 @@ class AprsService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
def sendTestPacket(packetString: String): Unit = {
|
||||
// Parse the incoming string to an APRSPacket object
|
||||
try {
|
||||
val testPacket = Parser.parse(packetString)
|
||||
|
||||
// Send the packet with an empty status postfix
|
||||
sendPacket(testPacket)
|
||||
|
||||
Log.d("APRSdroid.Service", s"Successfully sent packet: $packetString")
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
Log.e("APRSdroid.Service", s"Failed to send packet: $packetString", e)
|
||||
}
|
||||
}
|
||||
|
||||
def parsePacket(ts : Long, message : String, source : Int) {
|
||||
try {
|
||||
var fap = Parser.parse(message)
|
||||
|
@ -372,7 +393,201 @@ class AprsService extends Service {
|
|||
}
|
||||
}
|
||||
def postSubmit(post : String) {
|
||||
// Log the incoming post message for debugging
|
||||
Log.d("APRSdroid.Service", s"Incoming post: $post")
|
||||
postAddPost(StorageDatabase.Post.TYPE_INCMG, R.string.post_incmg, post)
|
||||
|
||||
// Process the incoming post
|
||||
processIncomingPost(post)
|
||||
}
|
||||
|
||||
// Map to store recent digipeats with their timestamps
|
||||
val recentDigipeats: mutable.Map[String, Instant] = mutable.Map()
|
||||
|
||||
// Function to add or update the digipeat
|
||||
def storeDigipeat(sourceCall: String, destinationCall: String, payload: String): Unit = {
|
||||
// Unique identifier using source call, destination call, and payload
|
||||
val key = s"$sourceCall>$destinationCall:$payload"
|
||||
recentDigipeats(key) = Instant.now() // Store the current timestamp
|
||||
}
|
||||
|
||||
// Function to filter digipeats that are older than dedupeTime seconds
|
||||
def isDigipeatRecent(sourceCall: String, destinationCall: String, payload: String): Boolean = {
|
||||
// Unique identifier using source call, destination call, and payload
|
||||
val key = s"$sourceCall>$destinationCall:$payload"
|
||||
recentDigipeats.get(key) match {
|
||||
case Some(timestamp) =>
|
||||
// Check if the packet was heard within the last 30 seconds
|
||||
Instant.now().isBefore(timestamp.plusSeconds(dedupeTime))
|
||||
case None =>
|
||||
false // Not found in recent digipeats
|
||||
}
|
||||
}
|
||||
|
||||
// Function to clean up old entries
|
||||
def cleanupOldDigipeats(): Unit = {
|
||||
val now = Instant.now()
|
||||
// Retain only those digipeats that are within the last 30 seconds
|
||||
recentDigipeats.retain { case (_, timestamp) =>
|
||||
now.isBefore(timestamp.plusSeconds(dedupeTime))
|
||||
}
|
||||
}
|
||||
|
||||
def processIncomingPost(post: String) {
|
||||
|
||||
val packet = Parser.parse(post) // Parse the incoming post to an APRSPacket
|
||||
// Check if backendName contains "KISS" or "AFSK"
|
||||
if (prefs.getBackendName().contains("KISS") || prefs.getBackendName().contains("AFSK")) {
|
||||
android.util.Log.d("PrefsAct", "Backend contains KISS or AFSK")
|
||||
} else {
|
||||
android.util.Log.d("PrefsAct", "Backend does not contain KISS or AFSK")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Check if both digipeating and regeneration are enabled. Temp fix until re-implementation. Remove later on.
|
||||
if (prefs.isDigipeaterEnabled() && prefs.isRegenerateEnabled()) {
|
||||
Log.d("APRSdroid.Service", "Both Digipeating and Regeneration are enabled; Set Regen to false.")
|
||||
prefs.setBoolean("p.regenerate", false) // Disable regeneration
|
||||
|
||||
}
|
||||
|
||||
// New regen
|
||||
if (!prefs.isDigipeaterEnabled() && prefs.isRegenerateEnabled()) {
|
||||
Log.d("APRSdroid.Service", "Regen enabled")
|
||||
sendTestPacket(packet.toString)
|
||||
return // Exit if both digipeating and regeneration are enabled
|
||||
}
|
||||
|
||||
// Check if the digipeating setting is enabled
|
||||
if (!prefs.isDigipeaterEnabled()) {
|
||||
Log.d("APRSdroid.Service", "Digipeating is disabled; skipping processing.")
|
||||
return // Exit if digipeating is not enabled
|
||||
}
|
||||
|
||||
cleanupOldDigipeats() // Clean up old digipeats before processing
|
||||
|
||||
|
||||
// Try to parse the incoming post to an APRSPacket
|
||||
try {
|
||||
logReceivedPacket(packet) // Log the received packet
|
||||
|
||||
// Now you can access the source call from the packet
|
||||
val callssid = prefs.getCallSsid()
|
||||
val sourceCall = packet.getSourceCall()
|
||||
val destinationCall = packet.getDestinationCall();
|
||||
val lastUsedDigi = packet.getDigiString()
|
||||
val payload = packet.getAprsInformation()
|
||||
|
||||
val payloadString = packet.getAprsInformation().toString() // Ensure payload is a String
|
||||
|
||||
|
||||
// Check if callssid matches sourceCall; if they match, do not digipeat
|
||||
if (callssid == sourceCall) {
|
||||
Log.d("APRSdroid.Service", s"No digipeat: callssid ($callssid) matches source call ($sourceCall).")
|
||||
return // Exit if no digipeating is needed
|
||||
}
|
||||
|
||||
// Check if this packet has been digipeated recently
|
||||
if (isDigipeatRecent(sourceCall, destinationCall, payloadString)) {
|
||||
Log.d("APRSdroid.Service", s"Packet from $sourceCall to $destinationCall and $payload has been heard recently, skipping digipeating.")
|
||||
return // Skip processing this packet
|
||||
}
|
||||
|
||||
|
||||
val (modifiedDigiPath, digipeatOccurred) = processDigiPath(lastUsedDigi, callssid)
|
||||
|
||||
|
||||
Log.d("APRSdroid.Service", s"Source: $sourceCall")
|
||||
Log.d("APRSdroid.Service", s"Destination: $destinationCall")
|
||||
Log.d("APRSdroid.Service", s"Digi: $lastUsedDigi")
|
||||
Log.d("APRSdroid.Service", s"Modified Digi Path: $modifiedDigiPath")
|
||||
|
||||
Log.d("APRSdroid.Service", s"Payload: $payload")
|
||||
|
||||
// Format the string for sending
|
||||
val testPacket = s"$sourceCall>$destinationCall,$modifiedDigiPath:$payload"
|
||||
|
||||
// Optionally, send a test packet with the formatted string only if a digipeat occurred
|
||||
if (digipeatOccurred) {
|
||||
sendTestPacket(testPacket)
|
||||
|
||||
// Store the digipeat to the recent list
|
||||
storeDigipeat(sourceCall, destinationCall, payloadString)
|
||||
|
||||
} else {
|
||||
Log.d("APRSdroid.Service", "No digipeat occurred, not sending a test packet.")
|
||||
}
|
||||
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
Log.e("APRSdroid.Service", s"Failed to parse packet: $post", e)
|
||||
}
|
||||
}
|
||||
|
||||
def processDigiPath(lastUsedDigi: String, callssid: String): (String, Boolean) = {
|
||||
// Log the input Digi path
|
||||
Log.d("APRSdroid.Service", s"Original Digi Path: '$lastUsedDigi'")
|
||||
|
||||
// If lastUsedDigi is empty, return it unchanged
|
||||
if (lastUsedDigi.trim.isEmpty) {
|
||||
Log.d("APRSdroid.Service", "LastUsedDigi is empty, returning unchanged.")
|
||||
return (lastUsedDigi, false)
|
||||
}
|
||||
|
||||
// Remove leading comma for easier processing
|
||||
val trimmedPath = lastUsedDigi.stripPrefix(",")
|
||||
|
||||
// Split the path into components, avoiding empty strings
|
||||
val pathComponents = trimmedPath.split(",").toList.filter(_.nonEmpty)
|
||||
|
||||
// Create a new list of components with modifications
|
||||
val (modifiedPath, modified) = pathComponents.foldLeft((List.empty[String], false)) {
|
||||
case ((acc, hasModified), component) =>
|
||||
// Check if callssid* is in the path and skip if found
|
||||
if (component == s"$callssid*") {
|
||||
// Skip digipeating if callssid* is found
|
||||
return (lastUsedDigi, false) // Return the original path, do not modify
|
||||
} else if (!hasModified && component.startsWith(digipeaterpath)) {
|
||||
// Handle the first unused WIDE path
|
||||
component match {
|
||||
case w if w.endsWith("-2") =>
|
||||
// Change -2 to -1 and insert callssid* before it
|
||||
(acc :+ s"$callssid*" :+ w.stripSuffix("-2") + "-1", true)
|
||||
case w if w.endsWith("-1") =>
|
||||
// Remove the WIDE component entirely and insert callssid*
|
||||
(acc :+ s"$callssid*", true)
|
||||
case _ =>
|
||||
// Leave unchanged if there's no -1 or -2
|
||||
(acc :+ component, hasModified)
|
||||
}
|
||||
} else if (component.startsWith(callssid) && !component.endsWith("*")) {
|
||||
// Replace callssid with callssid* only if it hasn't been modified
|
||||
if (!hasModified) {
|
||||
(acc :+ s"$callssid*", true)
|
||||
} else {
|
||||
(acc :+ component, hasModified)
|
||||
}
|
||||
} else {
|
||||
// Keep the component as it is
|
||||
(acc :+ component, hasModified)
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the modified path
|
||||
val resultPath = modifiedPath.mkString(",")
|
||||
|
||||
// Log the modified path before returning
|
||||
Log.d("APRSdroid.Service", s"Modified Digi Path: '$resultPath'")
|
||||
|
||||
// If no modification occurred, return the original lastUsedDigi
|
||||
if (resultPath == trimmedPath) {
|
||||
Log.d("APRSdroid.Service", "No modifications were made; returning the original path.")
|
||||
return (lastUsedDigi, false)
|
||||
}
|
||||
|
||||
// Return the modified path with a leading comma
|
||||
(s"$resultPath", true)
|
||||
}
|
||||
|
||||
def postAbort(post : String) {
|
||||
|
|
|
@ -14,7 +14,6 @@ object MessageListAdapter {
|
|||
val LIST_FROM = Array("TSS", CALL, TEXT)
|
||||
val LIST_TO = Array(R.id.listts, R.id.liststatus, R.id.listmessage)
|
||||
|
||||
val NUM_OF_RETRIES = 7
|
||||
// null, incoming, out-new, out-acked, out-rejected, out-aborted
|
||||
val COLORS = Array(0, 0xff8080b0, 0xff80a080, 0xff30b030, 0xffb03030, 0xffa08080)
|
||||
}
|
||||
|
@ -25,6 +24,9 @@ class MessageListAdapter(context : Context, prefs : PrefsWrapper,
|
|||
|
||||
lazy val storage = StorageDatabase.open(context)
|
||||
|
||||
lazy val NUM_OF_RETRIES = prefs.getStringInt("p.messaging", 7) // Fetch NUM_OF_RETRIES from prefs, defaulting to 7 if not found
|
||||
|
||||
|
||||
reload()
|
||||
|
||||
lazy val locReceiver = new LocationReceiver2(load_cursor,
|
||||
|
@ -45,7 +47,7 @@ class MessageListAdapter(context : Context, prefs : PrefsWrapper,
|
|||
case TYPE_INCOMING =>
|
||||
targetcall
|
||||
case TYPE_OUT_NEW =>
|
||||
"%s %d/%d".format(mycall, retrycnt, MessageListAdapter.NUM_OF_RETRIES)
|
||||
"%s %d/%d".format(mycall, retrycnt, NUM_OF_RETRIES)
|
||||
case TYPE_OUT_ACKED =>
|
||||
mycall
|
||||
case TYPE_OUT_REJECTED =>
|
||||
|
|
|
@ -9,7 +9,11 @@ import _root_.net.ab0oo.aprs.parser._
|
|||
class MessageService(s : AprsService) {
|
||||
val TAG = "APRSdroid.MsgService"
|
||||
|
||||
val NUM_OF_RETRIES = 7
|
||||
val NUM_OF_RETRIES = s.prefs.getStringInt("p.messaging", 7)
|
||||
|
||||
val RETRY_INTERVAL = s.prefs.getStringInt("p.retry", 30)
|
||||
|
||||
|
||||
val pendingSender = new Runnable() { override def run() { sendPendingMessages() } }
|
||||
|
||||
def createMessageNotifier() = new BroadcastReceiver() {
|
||||
|
@ -59,7 +63,8 @@ class MessageService(s : AprsService) {
|
|||
}
|
||||
|
||||
// return 2^n * 30s, at most 32min
|
||||
def getRetryDelayMS(retrycnt : Int) = 30000 * (1 << math.min(retrycnt - 1, 6))
|
||||
def getRetryDelayMS(retrycnt : Int) = (RETRY_INTERVAL * 1000) * (1 << math.min(retrycnt - 1, NUM_OF_RETRIES))
|
||||
|
||||
|
||||
def scheduleNextSend(delay : Long) {
|
||||
// add some time to prevent fast looping
|
||||
|
|
|
@ -12,6 +12,13 @@ class PrefsWrapper(val context : Context) {
|
|||
def getString(key : String, defValue : String) = prefs.getString(key, defValue)
|
||||
def getBoolean(key : String, defValue : Boolean) = prefs.getBoolean(key, defValue)
|
||||
|
||||
def isDigipeaterEnabled(): Boolean = {
|
||||
prefs.getBoolean("p.digipeating", false)
|
||||
}
|
||||
def isRegenerateEnabled(): Boolean = {
|
||||
prefs.getBoolean("p.regenerate", false)
|
||||
}
|
||||
|
||||
// safely read integers
|
||||
def getStringInt(key : String, defValue : Int) = {
|
||||
try { prefs.getString(key, null).trim.toInt } catch { case _ : Throwable => defValue }
|
||||
|
|
Ładowanie…
Reference in New Issue