diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt
index 4f677308f..65985983f 100644
--- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt
+++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt
@@ -845,13 +845,21 @@ constructor(
}
}
- /** Write the persisted packet data out to a CSV file in the specified location. */
+ /**
+ * Export all persisted packet data to a CSV file at the given URI.
+ *
+ * The CSV will include all packets, or only those matching the given port number if specified. Each row contains:
+ * date, time, sender node number, sender name, sender latitude, sender longitude, receiver latitude, receiver
+ * longitude, receiver elevation, received SNR, distance, hop limit, and payload.
+ *
+ * @param uri The destination URI for the CSV file.
+ * @param filterPortnum If provided, only packets with this port number will be exported.
+ */
@Suppress("detekt:CyclomaticComplexMethod", "detekt:LongMethod")
- fun saveRangeTestCsv(uri: Uri) {
+ fun saveDataCsv(uri: Uri, filterPortnum: Int? = null) {
viewModelScope.launch(Dispatchers.Main) {
// Extract distances to this device from position messages and put (node,SNR,distance)
- // in
- // the file_uri
+ // in the file_uri
val myNodeNum = myNodeNum ?: return@launch
// Capture the current node value while we're still on main thread
@@ -888,9 +896,10 @@ constructor(
}
}
- // Only look at range test messages, with SNR reported.
+ // packets must have rxSNR, and optionally match the filter given as a param.
if (
- proto.decoded.portnumValue == Portnums.PortNum.RANGE_TEST_APP_VALUE && proto.rxSnr != 0.0f
+ (filterPortnum == null || proto.decoded.portnumValue == filterPortnum) &&
+ proto.rxSnr != 0.0f
) {
val rxDateTime = dateFormat.format(packet.received_date)
val rxFrom = proto.from.toUInt()
diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt
index eede0aafc..b7fcb9ad3 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsScreen.kt
@@ -66,6 +66,9 @@ import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
import com.geeksville.mesh.util.LanguageUtils
import kotlinx.coroutines.delay
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
import kotlin.time.Duration.Companion.seconds
@Suppress("LongMethod", "CyclomaticComplexMethod")
@@ -222,11 +225,12 @@ fun SettingsScreen(
choices = themeMap.mapValues { (_, value) -> { uiViewModel.setTheme(value) } },
)
}
+ val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val exportRangeTestLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
- it.data?.data?.let { uri -> uiViewModel.saveRangeTestCsv(uri) }
+ it.data?.data?.let { uri -> uiViewModel.saveDataCsv(uri) }
}
}
SettingsItem(
@@ -238,11 +242,31 @@ fun SettingsScreen(
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/csv"
- putExtra(Intent.EXTRA_TITLE, "rangetest.csv")
+ putExtra(Intent.EXTRA_TITLE, "Meshtastic_rangetest_$timestamp.csv")
}
exportRangeTestLauncher.launch(intent)
}
+ val exportDataLauncher =
+ rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == RESULT_OK) {
+ it.data?.data?.let { uri -> uiViewModel.saveDataCsv(uri) }
+ }
+ }
+ SettingsItem(
+ text = stringResource(R.string.export_data_csv),
+ leadingIcon = Icons.Rounded.Output,
+ trailingIcon = null,
+ ) {
+ val intent =
+ Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "application/csv"
+ putExtra(Intent.EXTRA_TITLE, "Meshtastic_datalog_$timestamp.csv")
+ }
+ exportDataLauncher.launch(intent)
+ }
+
SettingsItem(
text = stringResource(R.string.intro_show),
leadingIcon = Icons.Rounded.WavingHand,
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51faa4391..35ff4cd95 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -156,7 +156,8 @@
OK
You must set a region!
Couldn\'t change channel, because radio is not yet connected. Please try again.
- Export rangetest.csv
+ Export rangetest packets
+ Export all packets
Reset
Scan
Add