From 82b6266f0e06fbceb1e0e7891981165a98f4e326 Mon Sep 17 00:00:00 2001 From: DaneEvans Date: Sat, 6 Sep 2025 15:16:53 +1000 Subject: [PATCH] feat #2570, Add ExportAll to csv (#2989) --- .../java/com/geeksville/mesh/model/UIState.kt | 21 ++++++++++---- .../mesh/ui/settings/SettingsScreen.kt | 28 +++++++++++++++++-- app/src/main/res/values/strings.xml | 3 +- 3 files changed, 43 insertions(+), 9 deletions(-) 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