feat(config): implement excluded modules validation (#1460)

* feat(config): implement excluded modules validation

* feat: hide excluded configs from metadata

* refactor: save local metadata from WantConfig

* refactor: delete metadata from deleted nodes

* fix: always request metadata for admin routes

* feat: show node firmware when metadata is available

* refactor: rename filter function

* feat: add `ServiceAction` request metadata
This commit is contained in:
Andre K 2025-01-02 06:38:33 -03:00 committed by GitHub
parent bdefbc3ce2
commit 60e7e18116
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 1164 additions and 358 deletions

View file

@ -71,6 +71,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.geeksville.mesh.MeshProtos.DeviceMetadata
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.model.MetricsViewModel
@ -235,6 +236,18 @@ enum class ConfigRoute(val title: String, val route: Route, val icon: ImageVecto
LORA("LoRa", Route.LoRa, Icons.Default.CellTower, 5),
BLUETOOTH("Bluetooth", Route.Bluetooth, Icons.Default.Bluetooth, 6),
SECURITY("Security", Route.Security, Icons.Default.Security, type = 7),
;
companion object {
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
when {
metadata == null -> true
it == BLUETOOTH -> metadata.hasBluetooth
it == NETWORK -> metadata.hasWifi || metadata.hasEthernet
else -> true // Include all other routes by default
}
}
}
}
// ModuleConfig (type = AdminProtos.AdminMessage.ModuleConfigType)
@ -252,6 +265,18 @@ enum class ModuleRoute(val title: String, val route: Route, val icon: ImageVecto
AMBIENT_LIGHTING("Ambient Lighting", Route.AmbientLighting, Icons.Default.LightMode, 10),
DETECTION_SENSOR("Detection Sensor", Route.DetectionSensor, Icons.Default.Sensors, 11),
PAXCOUNTER("Paxcounter", Route.Paxcounter, Icons.Default.PermScanWifi, 12),
;
val bitfield: Int get() = 1 shl ordinal
companion object {
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ModuleRoute> = entries.filter {
when (metadata) {
null -> true
else -> metadata.excludedModules and it.bitfield == 0
}
}
}
}
/**

View file

@ -58,6 +58,7 @@ import androidx.compose.material.icons.filled.KeyOff
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Map
import androidx.compose.material.icons.filled.Memory
import androidx.compose.material.icons.filled.Numbers
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Power
@ -91,11 +92,11 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.MetricsState
import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.ui.components.PreferenceCategory
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
import com.geeksville.mesh.ui.theme.AppTheme
import com.geeksville.mesh.util.DistanceUnit
import com.geeksville.mesh.util.formatAgo
@ -132,7 +133,7 @@ fun NodeDetailScreen(
@Composable
private fun NodeDetailList(
modifier: Modifier = Modifier,
node: NodeEntity,
node: Node,
metricsState: MetricsState,
onNavigate: (Any) -> Unit = {},
) {
@ -257,7 +258,7 @@ private fun DeviceDetailsContent(
@Composable
private fun NodeDetailsContent(
node: NodeEntity,
node: Node,
) {
if (node.mismatchKey) {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -304,6 +305,13 @@ private fun NodeDetailsContent(
value = formatUptime(node.deviceMetrics.uptimeSeconds)
)
}
if (node.metadata != null) {
NodeDetailRow(
label = "Firmware version",
icon = Icons.Default.Memory,
value = node.metadata.firmwareVersion.substringBeforeLast(".")
)
}
NodeDetailRow(
label = "Last heard",
icon = Icons.Default.History,
@ -413,7 +421,7 @@ private fun InfoCard(
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
private fun EnvironmentMetrics(
node: NodeEntity,
node: Node,
isFahrenheit: Boolean = false,
) = with(node.environmentMetrics) {
FlowRow(
@ -543,7 +551,7 @@ private fun calculateDewPoint(tempCelsius: Float, humidity: Float): Float {
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun PowerMetrics(node: NodeEntity) = with(node.powerMetrics) {
private fun PowerMetrics(node: Node) = with(node.powerMetrics) {
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
@ -597,8 +605,8 @@ private fun PowerMetrics(node: NodeEntity) = with(node.powerMetrics) {
@Preview(showBackground = true)
@Composable
private fun NodeDetailsPreview(
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
node: NodeEntity
@PreviewParameter(NodePreviewParameterProvider::class)
node: Node
) {
AppTheme {
NodeDetailList(

View file

@ -59,14 +59,14 @@ import com.geeksville.mesh.ConfigProtos.Config.DeviceConfig
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.ui.components.NodeMenuAction
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.ui.components.NodeKeyStatusIcon
import com.geeksville.mesh.ui.components.NodeMenu
import com.geeksville.mesh.ui.components.NodeMenuAction
import com.geeksville.mesh.ui.components.SignalInfo
import com.geeksville.mesh.ui.compose.ElevationInfo
import com.geeksville.mesh.ui.compose.SatelliteCountInfo
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
import com.geeksville.mesh.ui.theme.AppTheme
import com.geeksville.mesh.util.toDistanceString
@ -74,8 +74,8 @@ import com.geeksville.mesh.util.toDistanceString
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NodeItem(
thisNode: NodeEntity?,
thatNode: NodeEntity,
thisNode: Node?,
thatNode: Node,
gpsFormat: Int,
distanceUnits: Int,
tempInFahrenheit: Boolean,
@ -293,8 +293,8 @@ fun NodeItem(
@Preview(showBackground = false)
fun NodeInfoSimplePreview() {
AppTheme {
val thisNode = NodeEntityPreviewParameterProvider().values.first()
val thatNode = NodeEntityPreviewParameterProvider().values.last()
val thisNode = NodePreviewParameterProvider().values.first()
val thatNode = NodePreviewParameterProvider().values.last()
NodeItem(
thisNode = thisNode,
thatNode = thatNode,
@ -312,11 +312,11 @@ fun NodeInfoSimplePreview() {
uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES,
)
fun NodeInfoPreview(
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
thatNode: NodeEntity
@PreviewParameter(NodePreviewParameterProvider::class)
thatNode: Node
) {
AppTheme {
val thisNode = NodeEntityPreviewParameterProvider().values.first()
val thisNode = NodePreviewParameterProvider().values.first()
Column {
Text(
text = "Details Collapsed",

View file

@ -65,6 +65,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
import com.geeksville.mesh.R
import com.geeksville.mesh.model.RadioConfigState
import com.geeksville.mesh.model.RadioConfigViewModel
import com.geeksville.mesh.ui.components.PreferenceCategory
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
@ -150,8 +151,7 @@ fun RadioConfigScreen(
}
RadioConfigItemList(
enabled = state.connected && !isWaiting,
isLocal = state.isLocal,
state = state,
modifier = modifier,
onRouteClick = { route ->
isWaiting = true
@ -285,28 +285,28 @@ private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Un
@Composable
private fun RadioConfigItemList(
enabled: Boolean = true,
isLocal: Boolean = true,
state: RadioConfigState,
modifier: Modifier = Modifier,
onRouteClick: (Enum<*>) -> Unit = {},
onImport: () -> Unit = {},
onExport: () -> Unit = {},
) {
val enabled = state.connected && !state.responseState.isWaiting()
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(horizontal = 16.dp),
) {
item { PreferenceCategory(stringResource(R.string.device_settings)) }
items(ConfigRoute.entries) {
items(ConfigRoute.filterExcludedFrom(state.metadata)) {
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
}
item { PreferenceCategory(stringResource(R.string.module_settings)) }
items(ModuleRoute.entries) {
items(ModuleRoute.filterExcludedFrom(state.metadata)) {
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
}
if (isLocal) {
if (state.isLocal) {
item {
PreferenceCategory("Backup & Restore")
NavCard(
@ -331,5 +331,7 @@ private fun RadioConfigItemList(
@Preview(showBackground = true)
@Composable
private fun RadioSettingsScreenPreview() {
RadioConfigItemList()
RadioConfigItemList(
RadioConfigState(isLocal = true, connected = true)
)
}

View file

@ -39,7 +39,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.components.NodeMenuAction
import com.geeksville.mesh.ui.components.NodeFilterTextField
@ -53,7 +53,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
private val model: UIViewModel by activityViewModels()
private fun navigateToMessages(node: NodeEntity) = node.user.let { user ->
private fun navigateToMessages(node: Node) = node.user.let { user ->
val hasPKC = model.ourNodeInfo.value?.hasPKC == true && node.hasPKC // TODO use meta.hasPKC
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
val contactKey = "$channel${user.id}"
@ -91,7 +91,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
@Suppress("LongMethod")
fun NodesScreen(
model: UIViewModel = hiltViewModel(),
navigateToMessages: (NodeEntity) -> Unit,
navigateToMessages: (Node) -> Unit,
navigateToNodeDetails: (Int) -> Unit,
) {
val state by model.nodesUiState.collectAsStateWithLifecycle()

View file

@ -36,12 +36,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.Node
@Suppress("LongMethod")
@Composable
fun NodeMenu(
node: NodeEntity,
node: Node,
showFullMenu: Boolean = false,
onDismissRequest: () -> Unit,
expanded: Boolean = false,
@ -150,11 +150,11 @@ fun NodeMenu(
}
sealed class NodeMenuAction {
data class Remove(val node: NodeEntity) : NodeMenuAction()
data class Ignore(val node: NodeEntity) : NodeMenuAction()
data class DirectMessage(val node: NodeEntity) : NodeMenuAction()
data class RequestUserInfo(val node: NodeEntity) : NodeMenuAction()
data class RequestPosition(val node: NodeEntity) : NodeMenuAction()
data class TraceRoute(val node: NodeEntity) : NodeMenuAction()
data class MoreDetails(val node: NodeEntity) : NodeMenuAction()
data class Remove(val node: Node) : NodeMenuAction()
data class Ignore(val node: Node) : NodeMenuAction()
data class DirectMessage(val node: Node) : NodeMenuAction()
data class RequestUserInfo(val node: Node) : NodeMenuAction()
data class RequestPosition(val node: Node) : NodeMenuAction()
data class TraceRoute(val node: Node) : NodeMenuAction()
data class MoreDetails(val node: Node) : NodeMenuAction()
}

View file

@ -26,8 +26,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
import com.geeksville.mesh.ui.theme.AppTheme
const val MAX_VALID_SNR = 100F
@ -36,7 +36,7 @@ const val MAX_VALID_RSSI = 0
@Composable
fun SignalInfo(
modifier: Modifier = Modifier,
node: NodeEntity,
node: Node,
isThisNode: Boolean
) {
val text = if (isThisNode) {
@ -81,7 +81,7 @@ fun SignalInfo(
fun SignalInfoSimplePreview() {
AppTheme {
SignalInfo(
node = NodeEntity(
node = Node(
num = 1,
lastHeard = 0,
channel = 0,
@ -97,8 +97,8 @@ fun SignalInfoSimplePreview() {
@PreviewLightDark
@Composable
fun SignalInfoPreview(
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
node: NodeEntity
@PreviewParameter(NodePreviewParameterProvider::class)
node: Node
) {
AppTheme {
SignalInfo(
@ -111,8 +111,8 @@ fun SignalInfoPreview(
@Composable
@PreviewLightDark
fun SignalInfoSelfPreview(
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
node: NodeEntity
@PreviewParameter(NodePreviewParameterProvider::class)
node: Node
) {
AppTheme {
SignalInfo(

View file

@ -81,6 +81,8 @@ fun NetworkConfigScreen(
}
NetworkConfigItemList(
hasWifi = state.metadata?.hasWifi ?: true,
hasEthernet = state.metadata?.hasEthernet ?: true,
networkConfig = state.radioConfig.network,
enabled = state.connected,
onSaveClicked = { networkInput ->
@ -94,8 +96,11 @@ private fun extractWifiCredentials(qrCode: String) = Regex("""WIFI:S:(.*?);.*?P:
.find(qrCode)?.destructured
?.let { (ssid, password) -> ssid to password } ?: (null to null)
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun NetworkConfigItemList(
hasWifi: Boolean,
hasEthernet: Boolean,
networkConfig: NetworkConfig,
enabled: Boolean,
onSaveClicked: (NetworkConfig) -> Unit,
@ -137,16 +142,16 @@ fun NetworkConfigItemList(
item {
SwitchPreference(title = "WiFi enabled",
checked = networkInput.wifiEnabled,
enabled = enabled,
enabled = enabled && hasWifi,
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } })
Divider()
}
item { Divider() }
item {
EditTextPreference(title = "SSID",
value = networkInput.wifiSsid,
maxSize = 32, // wifi_ssid max_size:33
enabled = enabled,
enabled = enabled && hasWifi,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
@ -161,7 +166,7 @@ fun NetworkConfigItemList(
EditPasswordPreference(title = "PSK",
value = networkInput.wifiPsk,
maxSize = 64, // wifi_psk max_size:65
enabled = enabled,
enabled = enabled && hasWifi,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } })
}
@ -173,12 +178,20 @@ fun NetworkConfigItemList(
.fillMaxWidth()
.padding(vertical = 8.dp)
.height(48.dp),
enabled = enabled,
enabled = enabled && hasWifi,
) {
Text(text = stringResource(R.string.wifi_qr_code_scan))
}
}
item {
SwitchPreference(title = "Ethernet enabled",
checked = networkInput.ethEnabled,
enabled = enabled && hasEthernet,
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } })
Divider()
}
item {
EditTextPreference(title = "NTP server",
value = networkInput.ntpServer,
@ -209,14 +222,6 @@ fun NetworkConfigItemList(
})
}
item {
SwitchPreference(title = "Ethernet enabled",
checked = networkInput.ethEnabled,
enabled = enabled,
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } })
}
item { Divider() }
item {
DropDownPreference(title = "IPv4 mode",
enabled = enabled,
@ -225,8 +230,8 @@ fun NetworkConfigItemList(
.map { it to it.name },
selectedItem = networkInput.addressMode,
onItemSelected = { networkInput = networkInput.copy { addressMode = it } })
Divider()
}
item { Divider() }
item {
EditIPv4Preference(title = "IP",
@ -292,6 +297,8 @@ fun NetworkConfigItemList(
@Composable
private fun NetworkConfigPreview() {
NetworkConfigItemList(
hasWifi = true,
hasEthernet = true,
networkConfig = NetworkConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },

View file

@ -65,8 +65,8 @@ import com.geeksville.mesh.android.gpsDisabled
import com.geeksville.mesh.android.hasGps
import com.geeksville.mesh.android.hasLocationPermission
import com.geeksville.mesh.copy
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.map.CustomTileSource
import com.geeksville.mesh.model.map.MarkerWithLabel
@ -311,7 +311,7 @@ fun MapView(
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_location_on_24)
}
fun MapView.onNodesChanged(nodes: Collection<NodeEntity>): List<MarkerWithLabel> {
fun MapView.onNodesChanged(nodes: Collection<Node>): List<MarkerWithLabel> {
val nodesWithPosition = nodes.filter { it.validPosition != null }
val ourNode = model.ourNodeInfo.value
val gpsFormat = model.config.display.gpsFormat.number

View file

@ -90,8 +90,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.getChannel
import com.geeksville.mesh.ui.components.NodeKeyStatusIcon
@ -116,7 +116,7 @@ internal fun FragmentManager.navigateToMessages(contactKey: String, message: Str
class MessagesFragment : Fragment(), Logging {
private val model: UIViewModel by activityViewModels()
private fun navigateToMessages(node: NodeEntity) = node.user.let { user ->
private fun navigateToMessages(node: Node) = node.user.let { user ->
val hasPKC = model.ourNodeInfo.value?.hasPKC == true && node.hasPKC // TODO use meta.hasPKC
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
val contactKey = "$channel${user.id}"
@ -168,7 +168,7 @@ internal fun MessageScreen(
contactKey: String,
message: String,
viewModel: UIViewModel = hiltViewModel(),
navigateToMessages: (NodeEntity) -> Unit,
navigateToMessages: (Node) -> Unit,
navigateToNodeDetails: (Int) -> Unit,
onNavigateBack: () -> Unit
) {

View file

@ -60,16 +60,16 @@ import androidx.compose.ui.unit.sp
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.ui.components.AutoLinkText
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
import com.geeksville.mesh.ui.theme.AppTheme
@Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
internal fun MessageItem(
node: NodeEntity,
node: Node,
messageText: String?,
messageTime: String,
messageStatus: MessageStatus?,
@ -197,7 +197,7 @@ internal fun MessageItem(
private fun MessageItemPreview() {
AppTheme {
MessageItem(
node = NodeEntityPreviewParameterProvider().values.first(),
node = NodePreviewParameterProvider().values.first(),
messageText = stringResource(R.string.sample_message),
messageTime = "10:00",
messageStatus = MessageStatus.DELIVERED,

View file

@ -20,19 +20,17 @@ package com.geeksville.mesh.ui.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.geeksville.mesh.DeviceMetrics.Companion.currentTime
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.deviceMetrics
import com.geeksville.mesh.environmentMetrics
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.paxcount
import com.geeksville.mesh.position
import com.geeksville.mesh.telemetry
import com.geeksville.mesh.user
import com.google.protobuf.ByteString
import kotlin.random.Random
class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity> {
val mickeyMouse = NodeEntity(
class NodePreviewParameterProvider : PreviewParameterProvider<Node> {
val mickeyMouse = Node(
num = 1955,
user = user {
id = "mickeyMouseId"
@ -40,28 +38,22 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
shortName = "MM"
hwModel = MeshProtos.HardwareModel.TBEAM
},
longName = "Mickey Mouse",
shortName = "MM",
position = position {
latitudeI = 338125110
longitudeI = -1179189760
altitude = 138
satsInView = 4
},
latitude = 33.812511,
longitude = -117.918976,
lastHeard = currentTime(),
channel = 0,
snr = 12.5F,
rssi = -42,
deviceTelemetry = telemetry {
deviceMetrics = deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
}
deviceMetrics = deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
},
hopsAway = 0
)
@ -74,17 +66,13 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
id = "minnieMouseId"
hwModel = MeshProtos.HardwareModel.HELTEC_V3
},
longName = "Minnie Mouse",
shortName = "MiMo",
snr = 12.5F,
rssi = -42,
position = position {},
latitude = 0.0,
longitude = 0.0,
hopsAway = 1
)
private val donaldDuck = NodeEntity(
private val donaldDuck = Node(
num = Random.nextInt(),
position = position {
latitudeI = 338052347
@ -92,20 +80,16 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
altitude = 121
satsInView = 66
},
latitude = 33.8052347,
longitude = -117.9208460,
lastHeard = currentTime() - 300,
channel = 0,
snr = 12.5F,
rssi = -42,
deviceTelemetry = telemetry {
deviceMetrics = deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
}
deviceMetrics = deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
},
user = user {
id = "donaldDuckId"
@ -114,18 +98,14 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
hwModel = MeshProtos.HardwareModel.HELTEC_V3
publicKey = ByteString.copyFrom(ByteArray(32) { 1 })
},
longName = "Donald Duck, the Grand Duck of the Ducks",
shortName = "DoDu",
environmentTelemetry = telemetry {
environmentMetrics = environmentMetrics {
temperature = 28.0F
relativeHumidity = 50.0F
barometricPressure = 1013.25F
gasResistance = 0.0F
voltage = 3.7F
current = 0.0F
iaq = 100
}
environmentMetrics = environmentMetrics {
temperature = 28.0F
relativeHumidity = 50.0F
barometricPressure = 1013.25F
gasResistance = 0.0F
voltage = 3.7F
current = 0.0F
iaq = 100
},
paxcounter = paxcount {
wifi = 30
@ -142,19 +122,15 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
shortName = "myId"
hwModel = MeshProtos.HardwareModel.UNSET
},
longName = "Meshtastic myId",
shortName = null,
environmentTelemetry = telemetry {
environmentMetrics = environmentMetrics {}
},
environmentMetrics = environmentMetrics {},
paxcounter = paxcount {},
)
private val almostNothing = NodeEntity(
private val almostNothing = Node(
num = Random.nextInt(),
)
override val values: Sequence<NodeEntity>
override val values: Sequence<Node>
get() = sequenceOf(
mickeyMouse, // "this" node
unknown,