Digipeater Cleanup and move

pull/369/head
Mike 2024-12-06 19:48:41 -08:00
rodzic b830a12aa1
commit d19f5b4f6f
4 zmienionych plików z 228 dodań i 221 usunięć

Wyświetl plik

@ -61,7 +61,7 @@
<string name="service_sm_no_gps">SmartBeaconing™ requires GPS!</string>
<!-- service result codes -->
<string name="post_error">Error</string>
<string name="post_incmg">received</string>
<string name="post_incmg">Received</string>
<string name="post_info"></string>
<string name="post_connecting">Connecting to %1$s:%2$d...</string>
<string name="post_reconnect">Connection lost. Reconnect in %d seconds...</string>

Wyświetl plik

@ -75,10 +75,6 @@ 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", "WIDE1,WIDE2")
val handler = new Handler()
lazy val db = StorageDatabase.open(this)
@ -86,6 +82,7 @@ class AprsService extends Service {
lazy val msgService = new MessageService(this)
lazy val locSource = LocationSource.instanciateLocation(this, prefs)
lazy val msgNotifier = msgService.createMessageNotifier()
lazy val digipeaterService = new DigipeaterService(prefs, TAG, sendDigipeatedPacket)
var poster : AprsBackend = null
@ -343,16 +340,16 @@ class AprsService extends Service {
}
}
def sendTestPacket(packetString: String): Unit = {
def sendDigipeatedPacket(packetString: String): Unit = {
// Parse the incoming string to an APRSPacket object
try {
val testPacket = Parser.parse(packetString)
val digipeatedPacket = Parser.parse(packetString)
// Define additional information to be passed as status postfix
val digistatus = " - Digipeated"
val digistatus = "Digipeated"
// Send the packet with the additional status postfix
sendPacket(testPacket, digistatus)
sendPacket(digipeatedPacket, digistatus)
Log.d("APRSdroid.Service", s"Successfully sent packet: $packetString")
} catch {
@ -457,216 +454,7 @@ class AprsService extends Service {
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) {
Log.d(TAG, "POST STRING TEST: " + post) // Log the incoming post for debugging
// 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
}
//TODO, Add workaround for unsupported formats.
// Attempt to parse the incoming post to an APRSPacket.
val packet = try {
Parser.parse(post) // Attempt to parse
} catch {
case e: Exception =>
Log.e("Parsing FAILED!", s"Failed to parse packet: $post", e)
return // Exit the function if parsing fails
}
// 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 {
// 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)
val digipeaterPaths = digipeaterpath.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 && (digipeaterPaths.exists(path => component.split("-")(0) == path) || digipeaterPaths.contains(component) || component == callssid)) {
// We need to check if the first unused component matches digipeaterpath
if (acc.isEmpty || acc.last.endsWith("*")) {
// This is the first unused component
component match {
case w if w.matches(".*-(\\d+)$") =>
// Extract the number from the suffix
val number = w.split("-").last.toInt
// Decrement the number
val newNumber = number - 1
if (newNumber == 0 || w == callssid) {
// If the number is decremented to 0, remove the component and insert callssid*
(acc :+ s"$callssid*", true)
} else {
// Otherwise, decrement the number and keep the component
val newComponent = w.stripSuffix(s"-$number") + s"-$newNumber"
(acc :+ s"$callssid*" :+ newComponent, true)
}
case _ =>
// Leave unchanged if there's no -N suffix
(acc :+ component, hasModified)
}
} else {
// If the first unused component doesn't match digipeaterpath, keep unchanged
(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)
digipeaterService.processIncomingPost(post)
}
def postAbort(post : String) {

Wyświetl plik

@ -0,0 +1,219 @@
package org.aprsdroid.app
import _root_.android.util.Log
import scala.collection.mutable
import _root_.net.ab0oo.aprs.parser._
import java.time.Instant
class DigipeaterService(prefs: PrefsWrapper, TAG: String, sendDigipeatedPacket: String => Unit) {
private val recentDigipeats: mutable.Map[String, Instant] = mutable.Map()
def dedupeTime: Int = prefs.getStringInt("p.dedupe", 30) // Fetch the latest dedupe time from preferences
def digipeaterpath: String = prefs.getString("digipeater_path", "WIDE1,WIDE2") // Fetch digipeater path from preferences
// 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) {
Log.d(TAG, "POST STRING TEST: " + post) // Log the incoming post for debugging
// 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
}
//TODO, Add workaround for unsupported formats.
// Attempt to parse the incoming post to an APRSPacket.
val packet = try {
Parser.parse(post) // Attempt to parse
} catch {
case e: Exception =>
Log.e("Parsing FAILED!", s"Failed to parse packet: $post", e)
return // Exit the function if parsing fails
}
// 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")
sendDigipeatedPacket(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 {
// 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 digipeatedPacket = s"$sourceCall>$destinationCall,$modifiedDigiPath:$payload"
// Optionally, send a test packet with the formatted string only if a digipeat occurred
if (digipeatOccurred) {
sendDigipeatedPacket(digipeatedPacket)
// 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)
val digipeaterPaths = digipeaterpath.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 && (digipeaterPaths.exists(path => component.split("-")(0) == path) || digipeaterPaths.contains(component) || component == callssid)) {
// We need to check if the first unused component matches digipeaterpath
if (acc.isEmpty || acc.last.endsWith("*")) {
// This is the first unused component
component match {
case w if w.matches(".*-(\\d+)$") =>
// Extract the number from the suffix
val number = w.split("-").last.toInt
// Decrement the number
val newNumber = number - 1
if (newNumber == 0 || w == callssid) {
// If the number is decremented to 0, remove the component and insert callssid*
(acc :+ s"$callssid*", true)
} else {
// Otherwise, decrement the number and keep the component
val newComponent = w.stripSuffix(s"-$number") + s"-$newNumber"
(acc :+ s"$callssid*" :+ newComponent, true)
}
case _ =>
// Leave unchanged if there's no -N suffix
(acc :+ component, hasModified)
}
} else {
// If the first unused component doesn't match digipeaterpath, keep unchanged
(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)
}
}

Wyświetl plik

@ -64,8 +64,8 @@ class MessageService(s : AprsService) {
// Only check for duplicate ACKs if the feature is enabled
if (s.prefs.isAckDupeEnabled) {
if (lastAckTime.exists(time => (currentTime - time) < lastAckDupeTime)) {
Log.d(TAG, s"Duplicate msg, skipping ack for ${ap.getSourceCall()} messageNumber: $messageNumber")
if (lastAckTime.exists(time => (currentTime - time) < lastAckDupeTime) || lastAckDupeTime == 0) {
Log.d(TAG, s"Duplicate msg or ack disabled, skipping ack for ${ap.getSourceCall()} messageNumber: $messageNumber")
// Recent ACK exists, skip sending a new ACK
return
}