aprsdroid/src/DigipeaterService.scala

213 wiersze
7.9 KiB
Scala

package org.aprsdroid.app
import _root_.android.util.Log
import scala.collection.mutable
import _root_.net.ab0oo.aprs.parser._
import java.util.Date
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) = new.Date() // 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 dedupeTime seconds
val now = new Date()
now.getTime - timestamp.getTime < (dedupeTime * 1000)
case None =>
false // Not found in recent digipeats
}
}
// Function to clean up old entries
def cleanupOldDigipeats(): Unit = {
val now = new Date()
// Retain only those digipeats that are within the last dedupeTime seconds
recentDigipeats.retain { case (_, timestamp) =>
now.getTime - timestamp.getTime < (dedupeTime * 1000)
}
}
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
}
// 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)
}
}