feat(position): Update position broadcast intervals (#3500)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich
2025-10-19 14:28:47 -05:00
committed by GitHub
parent 3c01e08b1a
commit fecb84dd69
13 changed files with 62 additions and 27 deletions

View File

@@ -938,4 +938,5 @@
<string name="datadog_link" translatable="false">Datadog https://www.datadoghq.com/</string>
<string name="for_more_information_see_our_privacy_policy">For more information, see our privacy policy.</string>
<string name="privacy_url" translatable="false">" https://meshtastic.org/docs/legal/privacy/"</string>
<string name="unset">Unset - 0</string>
</resources>

View File

@@ -24,6 +24,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@@ -49,6 +50,7 @@ fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(
val detectionSensorConfig = state.moduleConfig.detectionSensor
val formState = rememberConfigState(initialValue = detectionSensorConfig)
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.detection_sensor),
@@ -79,7 +81,7 @@ fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(
title = stringResource(R.string.minimum_broadcast_seconds),
selectedItem = formState.value.minimumBroadcastSecs.toLong(),
enabled = state.connected,
items = minimumBroadcastIntervals.map { it.value to it.toDisplayString() },
items = minimumBroadcastIntervals.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { minimumBroadcastSecs = it.toInt() } },
)
@@ -88,7 +90,7 @@ fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(
title = stringResource(R.string.state_broadcast_seconds),
selectedItem = formState.value.stateBroadcastSecs.toLong(),
enabled = state.connected,
items = stateBroadcastIntervals.map { it.value to it.toDisplayString() },
items = stateBroadcastIntervals.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { stateBroadcastSecs = it.toInt() } },
)
HorizontalDivider()

View File

@@ -38,6 +38,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@@ -108,6 +109,7 @@ fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack
}
}
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.device),
onBack = onBack,
@@ -145,7 +147,7 @@ fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack
title = stringResource(R.string.nodeinfo_broadcast_interval),
selectedItem = formState.value.nodeInfoBroadcastSecs.toLong(),
enabled = state.connected,
items = nodeInfoBroadcastIntervals.map { it.value to it.toDisplayString() },
items = nodeInfoBroadcastIntervals.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { nodeInfoBroadcastSecs = it.toInt() } },
)
}

View File

@@ -22,6 +22,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -41,6 +42,7 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val displayConfig = state.radioConfig.display
val formState = rememberConfigState(initialValue = displayConfig)
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.display),
@@ -104,7 +106,7 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
title = stringResource(R.string.screen_on_for),
summary = stringResource(id = R.string.config_display_screen_on_secs_summary),
enabled = state.connected,
items = screenOnIntervals.map { it to it.toDisplayString() },
items = screenOnIntervals.map { it to it.toDisplayString(context = context) },
selectedItem =
screenOnIntervals.find { it.value == formState.value.screenOnSecs.toLong() }
?: screenOnIntervals.first(),
@@ -115,7 +117,7 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
title = stringResource(R.string.carousel_interval),
summary = stringResource(id = R.string.config_display_auto_screen_carousel_secs_summary),
enabled = state.connected,
items = carouselIntervals.map { it to it.toDisplayString() },
items = carouselIntervals.map { it to it.toDisplayString(context = context) },
selectedItem =
carouselIntervals.find { it.value == formState.value.autoScreenCarouselSecs.toLong() }
?: carouselIntervals.first(),

View File

@@ -27,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@@ -53,6 +54,7 @@ fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewM
val formState = rememberConfigState(initialValue = extNotificationConfig)
var ringtoneInput by rememberSaveable(ringtone) { mutableStateOf(ringtone) }
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.external_notification),
@@ -189,7 +191,7 @@ fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewM
val outputItems = remember { IntervalConfiguration.OUTPUT.allowedIntervals }
DropDownPreference(
title = stringResource(R.string.output_duration_milliseconds),
items = outputItems.map { it.value to it.toDisplayString() },
items = outputItems.map { it.value to it.toDisplayString(context = context) },
selectedItem = formState.value.outputMs,
enabled = state.connected,
onItemSelected = { formState.value = formState.value.copy { outputMs = it.toInt() } },
@@ -198,7 +200,7 @@ fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewM
val nagItems = remember { IntervalConfiguration.NAG_TIMEOUT.allowedIntervals }
DropDownPreference(
title = stringResource(R.string.nag_timeout_seconds),
items = nagItems.map { it.value to it.toDisplayString() },
items = nagItems.map { it.value to it.toDisplayString(context = context) },
selectedItem = formState.value.nagTimeout,
enabled = state.connected,
onItemSelected = { formState.value = formState.value.copy { nagTimeout = it.toInt() } },

View File

@@ -34,6 +34,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@@ -64,6 +65,7 @@ fun MapReportingPreference(
onPublishIntervalSecsChanged: (Int) -> Unit = {},
enabled: Boolean,
) {
val context = LocalContext.current
Column {
var showMapReportingWarning by rememberSaveable { mutableStateOf(mapReportingEnabled) }
LaunchedEffect(mapReportingEnabled) { showMapReportingWarning = mapReportingEnabled }
@@ -124,7 +126,7 @@ fun MapReportingPreference(
DropDownPreference(
modifier = Modifier.padding(bottom = 16.dp),
title = stringResource(R.string.map_reporting_interval_seconds),
items = publishItems.map { it.value to it.toDisplayString() },
items = publishItems.map { it.value to it.toDisplayString(context = context) },
selectedItem = publishIntervalSecs,
enabled = enabled,
onItemSelected = { onPublishIntervalSecsChanged(it.toInt()) },

View File

@@ -23,6 +23,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@@ -44,6 +45,7 @@ fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), on
val paxcounterConfig = state.moduleConfig.paxcounter
val formState = rememberConfigState(initialValue = paxcounterConfig)
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.paxcounter),
@@ -72,7 +74,7 @@ fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), on
title = stringResource(R.string.update_interval_seconds),
selectedItem = formState.value.paxcounterUpdateInterval.toLong(),
enabled = state.connected,
items = items.map { it.value to it.toDisplayString() },
items = items.map { it.value to it.toDisplayString(context = context) },
onItemSelected = {
formState.value = formState.value.copy { paxcounterUpdateInterval = it.toInt() }
},

View File

@@ -34,6 +34,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.core.location.LocationCompat
@@ -118,7 +119,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
}
}
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.position),
onBack = onBack,
@@ -143,12 +144,12 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
) {
item {
TitledCard(title = stringResource(R.string.position_packet)) {
val items = remember { IntervalConfiguration.BROADCAST_MEDIUM.allowedIntervals }
val items = remember { IntervalConfiguration.POSITION_BROADCAST.allowedIntervals }
DropDownPreference(
title = stringResource(R.string.broadcast_interval),
summary = stringResource(id = R.string.config_position_broadcast_secs_summary),
enabled = state.connected,
items = items.map { it to it.toDisplayString() },
items = items.map { it to it.toDisplayString(context = context) },
selectedItem =
FixedUpdateIntervals.fromValue(formState.value.positionBroadcastSecs.toLong()) ?: items.first(),
onItemSelected = {
@@ -171,7 +172,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
summary =
stringResource(id = R.string.config_position_broadcast_smart_minimum_interval_secs_summary),
enabled = state.connected,
items = smartItems.map { it to it.toDisplayString() },
items = smartItems.map { it to it.toDisplayString(context = context) },
selectedItem =
FixedUpdateIntervals.fromValue(formState.value.broadcastSmartMinimumIntervalSecs.toLong())
?: smartItems.first(),
@@ -262,7 +263,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
title = stringResource(R.string.update_interval),
summary = stringResource(id = R.string.config_position_gps_update_interval_summary),
enabled = state.connected,
items = items.map { it to it.toDisplayString() },
items = items.map { it to it.toDisplayString(context = context) },
selectedItem =
FixedUpdateIntervals.fromValue(formState.value.gpsUpdateInterval.toLong()) ?: items.first(),
onItemSelected = {

View File

@@ -23,6 +23,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@@ -44,6 +45,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
val powerConfig = state.radioConfig.power
val formState = rememberConfigState(initialValue = powerConfig)
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.power),
@@ -73,7 +75,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
title = stringResource(R.string.shutdown_on_power_loss),
selectedItem = formState.value.onBatteryShutdownAfterSecs.toLong(),
enabled = state.connected,
items = items.map { it.value to it.toDisplayString() },
items = items.map { it.value to it.toDisplayString(context = context) },
onItemSelected = {
formState.value = formState.value.copy { onBatteryShutdownAfterSecs = it.toInt() }
},
@@ -104,7 +106,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
title = stringResource(R.string.wait_for_bluetooth_duration_seconds),
selectedItem = formState.value.waitBluetoothSecs.toLong(),
enabled = state.connected,
items = waitBluetoothItems.map { it.value to it.toDisplayString() },
items = waitBluetoothItems.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { waitBluetoothSecs = it.toInt() } },
)
HorizontalDivider()
@@ -114,7 +116,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
selectedItem = formState.value.sdsSecs.toLong(),
onItemSelected = { formState.value = formState.value.copy { sdsSecs = it.toInt() } },
enabled = state.connected,
items = sdsSecsItems.map { it.value to it.toDisplayString() },
items = sdsSecsItems.map { it.value to it.toDisplayString(context = context) },
)
HorizontalDivider()
val minWakeItems = remember { IntervalConfiguration.NAG_TIMEOUT.allowedIntervals }
@@ -122,7 +124,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
title = stringResource(R.string.minimum_wake_time_seconds),
selectedItem = formState.value.minWakeSecs.toLong(),
enabled = state.connected,
items = minWakeItems.map { it.value to it.toDisplayString() },
items = minWakeItems.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { minWakeSecs = it.toInt() } },
)
HorizontalDivider()

View File

@@ -22,6 +22,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -40,6 +41,7 @@ fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val rangeTestConfig = state.moduleConfig.rangeTest
val formState = rememberConfigState(initialValue = rangeTestConfig)
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.range_test),
@@ -68,7 +70,7 @@ fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(R.string.sender_message_interval_seconds),
selectedItem = formState.value.sender.toLong(),
enabled = state.connected,
items = rangeItems.map { it.value to it.toDisplayString() },
items = rangeItems.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { sender = it.toInt() } },
)
HorizontalDivider()

View File

@@ -22,6 +22,7 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -45,6 +46,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
val formState = rememberConfigState(initialValue = telemetryConfig)
val firmwareVersion = state.metadata?.firmwareVersion ?: "1"
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(id = R.string.telemetry),
@@ -76,7 +78,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(R.string.device_metrics_update_interval_seconds),
selectedItem = formState.value.deviceUpdateInterval.toLong(),
enabled = state.connected,
items = items.map { it.value to it.toDisplayString() },
items = items.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { deviceUpdateInterval = it.toInt() } },
)
HorizontalDivider()
@@ -93,7 +95,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(R.string.environment_metrics_update_interval_seconds),
selectedItem = formState.value.environmentUpdateInterval.toLong(),
enabled = state.connected,
items = envItems.map { it.value to it.toDisplayString() },
items = envItems.map { it.value to it.toDisplayString(context = context) },
onItemSelected = {
formState.value = formState.value.copy { environmentUpdateInterval = it.toInt() }
},
@@ -128,7 +130,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(R.string.air_quality_metrics_update_interval_seconds),
selectedItem = formState.value.airQualityInterval.toLong(),
enabled = state.connected,
items = airItems.map { it.value to it.toDisplayString() },
items = airItems.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { airQualityInterval = it.toInt() } },
)
HorizontalDivider()
@@ -145,7 +147,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(R.string.power_metrics_update_interval_seconds),
selectedItem = formState.value.powerUpdateInterval.toLong(),
enabled = state.connected,
items = powerItems.map { it.value to it.toDisplayString() },
items = powerItems.map { it.value to it.toDisplayString(context = context) },
onItemSelected = { formState.value = formState.value.copy { powerUpdateInterval = it.toInt() } },
)
HorizontalDivider()

View File

@@ -57,6 +57,11 @@ enum class FixedUpdateIntervals(val value: Long) {
FORTY_EIGHT_HOURS(TimeUnit.HOURS.toSeconds(48)),
SEVENTY_TWO_HOURS(TimeUnit.HOURS.toSeconds(72)),
ALWAYS_ON(Int.MAX_VALUE.toLong()),
SIXTY_SECONDS(TimeUnit.MINUTES.toSeconds(1)),
EIGHTY_SECONDS(TimeUnit.SECONDS.toSeconds(80)),
NINETY_SECONDS(TimeUnit.SECONDS.toSeconds(90)),
EIGHT_SECONDS(TimeUnit.SECONDS.toSeconds(8)),
FORTY_SECONDS(TimeUnit.SECONDS.toSeconds(40)),
;
companion object {
@@ -255,6 +260,10 @@ enum class IntervalConfiguration {
POSITION_BROADCAST ->
listOf(
FixedUpdateIntervals.UNSET,
FixedUpdateIntervals.SIXTY_SECONDS,
FixedUpdateIntervals.NINETY_SECONDS,
FixedUpdateIntervals.FIVE_MINUTES,
FixedUpdateIntervals.FIFTEEN_MINUTES,
FixedUpdateIntervals.ONE_HOUR,
FixedUpdateIntervals.TWO_HOURS,
FixedUpdateIntervals.THREE_HOURS,
@@ -270,8 +279,12 @@ enum class IntervalConfiguration {
)
GPS_UPDATE ->
listOf(
FixedUpdateIntervals.THIRTY_SECONDS,
FixedUpdateIntervals.ONE_MINUTE,
FixedUpdateIntervals.UNSET,
FixedUpdateIntervals.EIGHT_SECONDS,
FixedUpdateIntervals.TWENTY_SECONDS,
FixedUpdateIntervals.FORTY_SECONDS,
FixedUpdateIntervals.SIXTY_SECONDS,
FixedUpdateIntervals.EIGHTY_SECONDS,
FixedUpdateIntervals.TWO_MINUTES,
FixedUpdateIntervals.FIVE_MINUTES,
FixedUpdateIntervals.TEN_MINUTES,

View File

@@ -17,8 +17,10 @@
package org.meshtastic.feature.settings.util
fun FixedUpdateIntervals.toDisplayString(): String = if (this == FixedUpdateIntervals.UNSET) {
"Never"
import android.content.Context
fun FixedUpdateIntervals.toDisplayString(context: Context): String = if (this == FixedUpdateIntervals.UNSET) {
context.getString(org.meshtastic.core.strings.R.string.unset)
} else {
name.split('_').joinToString(" ") { word -> word.lowercase().replaceFirstChar { it.uppercase() } }
}