Digipeater Updates & Other Features

pull/369/head
Mike 2024-10-27 09:59:05 -07:00
rodzic 5499576044
commit dda032c3e5
9 zmienionych plików z 337 dodań i 12 usunięć

Wyświetl plik

@ -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"

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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 =>

Wyświetl plik

@ -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

Wyświetl plik

@ -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 }